]>
Commit | Line | Data |
---|---|---|
b72a32da RL |
1 | /* |
2 | * This file is part of the Chelsio T4 Ethernet driver for Linux. | |
3 | * | |
4 | * Copyright (c) 2016 Chelsio Communications, Inc. All rights reserved. | |
5 | * | |
6 | * This software is available to you under a choice of one of two | |
7 | * licenses. You may choose to be licensed under the terms of the GNU | |
8 | * General Public License (GPL) Version 2, available from the file | |
9 | * COPYING in the main directory of this source tree, or the | |
10 | * OpenIB.org BSD license below: | |
11 | * | |
12 | * Redistribution and use in source and binary forms, with or | |
13 | * without modification, are permitted provided that the following | |
14 | * conditions are met: | |
15 | * | |
16 | * - Redistributions of source code must retain the above | |
17 | * copyright notice, this list of conditions and the following | |
18 | * disclaimer. | |
19 | * | |
20 | * - Redistributions in binary form must reproduce the above | |
21 | * copyright notice, this list of conditions and the following | |
22 | * disclaimer in the documentation and/or other materials | |
23 | * provided with the distribution. | |
24 | * | |
25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
29 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
30 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
31 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
32 | * SOFTWARE. | |
33 | */ | |
34 | ||
35 | #include <linux/module.h> | |
36 | #include <linux/netdevice.h> | |
37 | ||
38 | #include "cxgb4.h" | |
39 | #include "sched.h" | |
40 | ||
41 | /* Spinlock must be held by caller */ | |
42 | static int t4_sched_class_fw_cmd(struct port_info *pi, | |
43 | struct ch_sched_params *p, | |
44 | enum sched_fw_ops op) | |
45 | { | |
46 | struct adapter *adap = pi->adapter; | |
47 | struct sched_table *s = pi->sched_tbl; | |
48 | struct sched_class *e; | |
49 | int err = 0; | |
50 | ||
51 | e = &s->tab[p->u.params.class]; | |
52 | switch (op) { | |
53 | case SCHED_FW_OP_ADD: | |
54 | err = t4_sched_params(adap, p->type, | |
55 | p->u.params.level, p->u.params.mode, | |
56 | p->u.params.rateunit, | |
57 | p->u.params.ratemode, | |
58 | p->u.params.channel, e->idx, | |
59 | p->u.params.minrate, p->u.params.maxrate, | |
60 | p->u.params.weight, p->u.params.pktsize); | |
61 | break; | |
62 | default: | |
63 | err = -ENOTSUPP; | |
64 | break; | |
65 | } | |
66 | ||
67 | return err; | |
68 | } | |
69 | ||
6cede1f1 RL |
70 | /* Spinlock must be held by caller */ |
71 | static int t4_sched_bind_unbind_op(struct port_info *pi, void *arg, | |
72 | enum sched_bind_type type, bool bind) | |
73 | { | |
74 | struct adapter *adap = pi->adapter; | |
75 | u32 fw_mnem, fw_class, fw_param; | |
76 | unsigned int pf = adap->pf; | |
77 | unsigned int vf = 0; | |
78 | int err = 0; | |
79 | ||
80 | switch (type) { | |
81 | case SCHED_QUEUE: { | |
82 | struct sched_queue_entry *qe; | |
83 | ||
84 | qe = (struct sched_queue_entry *)arg; | |
85 | ||
86 | /* Create a template for the FW_PARAMS_CMD mnemonic and | |
87 | * value (TX Scheduling Class in this case). | |
88 | */ | |
89 | fw_mnem = (FW_PARAMS_MNEM_V(FW_PARAMS_MNEM_DMAQ) | | |
90 | FW_PARAMS_PARAM_X_V( | |
91 | FW_PARAMS_PARAM_DMAQ_EQ_SCHEDCLASS_ETH)); | |
92 | fw_class = bind ? qe->param.class : FW_SCHED_CLS_NONE; | |
93 | fw_param = (fw_mnem | FW_PARAMS_PARAM_YZ_V(qe->cntxt_id)); | |
94 | ||
95 | pf = adap->pf; | |
96 | vf = 0; | |
97 | break; | |
98 | } | |
99 | default: | |
100 | err = -ENOTSUPP; | |
101 | goto out; | |
102 | } | |
103 | ||
104 | err = t4_set_params(adap, adap->mbox, pf, vf, 1, &fw_param, &fw_class); | |
105 | ||
106 | out: | |
107 | return err; | |
108 | } | |
109 | ||
110 | static struct sched_class *t4_sched_queue_lookup(struct port_info *pi, | |
111 | const unsigned int qid, | |
112 | int *index) | |
113 | { | |
114 | struct sched_table *s = pi->sched_tbl; | |
115 | struct sched_class *e, *end; | |
116 | struct sched_class *found = NULL; | |
117 | int i; | |
118 | ||
119 | /* Look for a class with matching bound queue parameters */ | |
120 | end = &s->tab[s->sched_size]; | |
121 | for (e = &s->tab[0]; e != end; ++e) { | |
122 | struct sched_queue_entry *qe; | |
123 | ||
124 | i = 0; | |
125 | if (e->state == SCHED_STATE_UNUSED) | |
126 | continue; | |
127 | ||
128 | list_for_each_entry(qe, &e->queue_list, list) { | |
129 | if (qe->cntxt_id == qid) { | |
130 | found = e; | |
131 | if (index) | |
132 | *index = i; | |
133 | break; | |
134 | } | |
135 | i++; | |
136 | } | |
137 | ||
138 | if (found) | |
139 | break; | |
140 | } | |
141 | ||
142 | return found; | |
143 | } | |
144 | ||
145 | static int t4_sched_queue_unbind(struct port_info *pi, struct ch_sched_queue *p) | |
146 | { | |
147 | struct adapter *adap = pi->adapter; | |
148 | struct sched_class *e; | |
149 | struct sched_queue_entry *qe = NULL; | |
150 | struct sge_eth_txq *txq; | |
151 | unsigned int qid; | |
152 | int index = -1; | |
153 | int err = 0; | |
154 | ||
155 | if (p->queue < 0 || p->queue >= pi->nqsets) | |
156 | return -ERANGE; | |
157 | ||
158 | txq = &adap->sge.ethtxq[pi->first_qset + p->queue]; | |
159 | qid = txq->q.cntxt_id; | |
160 | ||
161 | /* Find the existing class that the queue is bound to */ | |
162 | e = t4_sched_queue_lookup(pi, qid, &index); | |
163 | if (e && index >= 0) { | |
164 | int i = 0; | |
165 | ||
166 | spin_lock(&e->lock); | |
167 | list_for_each_entry(qe, &e->queue_list, list) { | |
168 | if (i == index) | |
169 | break; | |
170 | i++; | |
171 | } | |
172 | err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE, | |
173 | false); | |
174 | if (err) { | |
175 | spin_unlock(&e->lock); | |
176 | goto out; | |
177 | } | |
178 | ||
179 | list_del(&qe->list); | |
180 | t4_free_mem(qe); | |
181 | if (atomic_dec_and_test(&e->refcnt)) { | |
182 | e->state = SCHED_STATE_UNUSED; | |
183 | memset(&e->info, 0, sizeof(e->info)); | |
184 | } | |
185 | spin_unlock(&e->lock); | |
186 | } | |
187 | out: | |
188 | return err; | |
189 | } | |
190 | ||
191 | static int t4_sched_queue_bind(struct port_info *pi, struct ch_sched_queue *p) | |
192 | { | |
193 | struct adapter *adap = pi->adapter; | |
194 | struct sched_table *s = pi->sched_tbl; | |
195 | struct sched_class *e; | |
196 | struct sched_queue_entry *qe = NULL; | |
197 | struct sge_eth_txq *txq; | |
198 | unsigned int qid; | |
199 | int err = 0; | |
200 | ||
201 | if (p->queue < 0 || p->queue >= pi->nqsets) | |
202 | return -ERANGE; | |
203 | ||
204 | qe = t4_alloc_mem(sizeof(struct sched_queue_entry)); | |
205 | if (!qe) | |
206 | return -ENOMEM; | |
207 | ||
208 | txq = &adap->sge.ethtxq[pi->first_qset + p->queue]; | |
209 | qid = txq->q.cntxt_id; | |
210 | ||
211 | /* Unbind queue from any existing class */ | |
212 | err = t4_sched_queue_unbind(pi, p); | |
67b11e2e CIK |
213 | if (err) { |
214 | t4_free_mem(qe); | |
6cede1f1 | 215 | goto out; |
67b11e2e | 216 | } |
6cede1f1 RL |
217 | |
218 | /* Bind queue to specified class */ | |
219 | memset(qe, 0, sizeof(*qe)); | |
220 | qe->cntxt_id = qid; | |
221 | memcpy(&qe->param, p, sizeof(qe->param)); | |
222 | ||
223 | e = &s->tab[qe->param.class]; | |
224 | spin_lock(&e->lock); | |
225 | err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE, true); | |
226 | if (err) { | |
227 | t4_free_mem(qe); | |
228 | spin_unlock(&e->lock); | |
229 | goto out; | |
230 | } | |
231 | ||
232 | list_add_tail(&qe->list, &e->queue_list); | |
233 | atomic_inc(&e->refcnt); | |
234 | spin_unlock(&e->lock); | |
235 | out: | |
236 | return err; | |
237 | } | |
238 | ||
239 | static void t4_sched_class_unbind_all(struct port_info *pi, | |
240 | struct sched_class *e, | |
241 | enum sched_bind_type type) | |
242 | { | |
243 | if (!e) | |
244 | return; | |
245 | ||
246 | switch (type) { | |
247 | case SCHED_QUEUE: { | |
248 | struct sched_queue_entry *qe; | |
249 | ||
250 | list_for_each_entry(qe, &e->queue_list, list) | |
251 | t4_sched_queue_unbind(pi, &qe->param); | |
252 | break; | |
253 | } | |
254 | default: | |
255 | break; | |
256 | } | |
257 | } | |
258 | ||
259 | static int t4_sched_class_bind_unbind_op(struct port_info *pi, void *arg, | |
260 | enum sched_bind_type type, bool bind) | |
261 | { | |
262 | int err = 0; | |
263 | ||
264 | if (!arg) | |
265 | return -EINVAL; | |
266 | ||
267 | switch (type) { | |
268 | case SCHED_QUEUE: { | |
269 | struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; | |
270 | ||
271 | if (bind) | |
272 | err = t4_sched_queue_bind(pi, qe); | |
273 | else | |
274 | err = t4_sched_queue_unbind(pi, qe); | |
275 | break; | |
276 | } | |
277 | default: | |
278 | err = -ENOTSUPP; | |
279 | break; | |
280 | } | |
281 | ||
282 | return err; | |
283 | } | |
284 | ||
285 | /** | |
286 | * cxgb4_sched_class_bind - Bind an entity to a scheduling class | |
287 | * @dev: net_device pointer | |
288 | * @arg: Entity opaque data | |
289 | * @type: Entity type (Queue) | |
290 | * | |
291 | * Binds an entity (queue) to a scheduling class. If the entity | |
292 | * is bound to another class, it will be unbound from the other class | |
293 | * and bound to the class specified in @arg. | |
294 | */ | |
295 | int cxgb4_sched_class_bind(struct net_device *dev, void *arg, | |
296 | enum sched_bind_type type) | |
297 | { | |
298 | struct port_info *pi = netdev2pinfo(dev); | |
299 | struct sched_table *s; | |
300 | int err = 0; | |
301 | u8 class_id; | |
302 | ||
303 | if (!can_sched(dev)) | |
304 | return -ENOTSUPP; | |
305 | ||
306 | if (!arg) | |
307 | return -EINVAL; | |
308 | ||
309 | switch (type) { | |
310 | case SCHED_QUEUE: { | |
311 | struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; | |
312 | ||
313 | class_id = qe->class; | |
314 | break; | |
315 | } | |
316 | default: | |
317 | return -ENOTSUPP; | |
318 | } | |
319 | ||
320 | if (!valid_class_id(dev, class_id)) | |
321 | return -EINVAL; | |
322 | ||
323 | if (class_id == SCHED_CLS_NONE) | |
324 | return -ENOTSUPP; | |
325 | ||
326 | s = pi->sched_tbl; | |
327 | write_lock(&s->rw_lock); | |
328 | err = t4_sched_class_bind_unbind_op(pi, arg, type, true); | |
329 | write_unlock(&s->rw_lock); | |
330 | ||
331 | return err; | |
332 | } | |
333 | ||
334 | /** | |
335 | * cxgb4_sched_class_unbind - Unbind an entity from a scheduling class | |
336 | * @dev: net_device pointer | |
337 | * @arg: Entity opaque data | |
338 | * @type: Entity type (Queue) | |
339 | * | |
340 | * Unbinds an entity (queue) from a scheduling class. | |
341 | */ | |
342 | int cxgb4_sched_class_unbind(struct net_device *dev, void *arg, | |
343 | enum sched_bind_type type) | |
344 | { | |
345 | struct port_info *pi = netdev2pinfo(dev); | |
346 | struct sched_table *s; | |
347 | int err = 0; | |
348 | u8 class_id; | |
349 | ||
350 | if (!can_sched(dev)) | |
351 | return -ENOTSUPP; | |
352 | ||
353 | if (!arg) | |
354 | return -EINVAL; | |
355 | ||
356 | switch (type) { | |
357 | case SCHED_QUEUE: { | |
358 | struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; | |
359 | ||
360 | class_id = qe->class; | |
361 | break; | |
362 | } | |
363 | default: | |
364 | return -ENOTSUPP; | |
365 | } | |
366 | ||
367 | if (!valid_class_id(dev, class_id)) | |
368 | return -EINVAL; | |
369 | ||
370 | s = pi->sched_tbl; | |
371 | write_lock(&s->rw_lock); | |
372 | err = t4_sched_class_bind_unbind_op(pi, arg, type, false); | |
373 | write_unlock(&s->rw_lock); | |
374 | ||
375 | return err; | |
376 | } | |
377 | ||
b72a32da RL |
378 | /* If @p is NULL, fetch any available unused class */ |
379 | static struct sched_class *t4_sched_class_lookup(struct port_info *pi, | |
380 | const struct ch_sched_params *p) | |
381 | { | |
382 | struct sched_table *s = pi->sched_tbl; | |
383 | struct sched_class *e, *end; | |
384 | struct sched_class *found = NULL; | |
385 | ||
386 | if (!p) { | |
387 | /* Get any available unused class */ | |
388 | end = &s->tab[s->sched_size]; | |
389 | for (e = &s->tab[0]; e != end; ++e) { | |
390 | if (e->state == SCHED_STATE_UNUSED) { | |
391 | found = e; | |
392 | break; | |
393 | } | |
394 | } | |
395 | } else { | |
396 | /* Look for a class with matching scheduling parameters */ | |
397 | struct ch_sched_params info; | |
398 | struct ch_sched_params tp; | |
399 | ||
400 | memset(&info, 0, sizeof(info)); | |
401 | memset(&tp, 0, sizeof(tp)); | |
402 | ||
403 | memcpy(&tp, p, sizeof(tp)); | |
404 | /* Don't try to match class parameter */ | |
405 | tp.u.params.class = SCHED_CLS_NONE; | |
406 | ||
407 | end = &s->tab[s->sched_size]; | |
408 | for (e = &s->tab[0]; e != end; ++e) { | |
409 | if (e->state == SCHED_STATE_UNUSED) | |
410 | continue; | |
411 | ||
412 | memset(&info, 0, sizeof(info)); | |
413 | memcpy(&info, &e->info, sizeof(info)); | |
414 | /* Don't try to match class parameter */ | |
415 | info.u.params.class = SCHED_CLS_NONE; | |
416 | ||
417 | if ((info.type == tp.type) && | |
418 | (!memcmp(&info.u.params, &tp.u.params, | |
419 | sizeof(info.u.params)))) { | |
420 | found = e; | |
421 | break; | |
422 | } | |
423 | } | |
424 | } | |
425 | ||
426 | return found; | |
427 | } | |
428 | ||
429 | static struct sched_class *t4_sched_class_alloc(struct port_info *pi, | |
430 | struct ch_sched_params *p) | |
431 | { | |
432 | struct sched_table *s = pi->sched_tbl; | |
433 | struct sched_class *e; | |
434 | u8 class_id; | |
435 | int err; | |
436 | ||
437 | if (!p) | |
438 | return NULL; | |
439 | ||
440 | class_id = p->u.params.class; | |
441 | ||
442 | /* Only accept search for existing class with matching params | |
443 | * or allocation of new class with specified params | |
444 | */ | |
445 | if (class_id != SCHED_CLS_NONE) | |
446 | return NULL; | |
447 | ||
448 | write_lock(&s->rw_lock); | |
449 | /* See if there's an exisiting class with same | |
450 | * requested sched params | |
451 | */ | |
452 | e = t4_sched_class_lookup(pi, p); | |
453 | if (!e) { | |
454 | struct ch_sched_params np; | |
455 | ||
456 | /* Fetch any available unused class */ | |
457 | e = t4_sched_class_lookup(pi, NULL); | |
458 | if (!e) | |
459 | goto out; | |
460 | ||
461 | memset(&np, 0, sizeof(np)); | |
462 | memcpy(&np, p, sizeof(np)); | |
463 | np.u.params.class = e->idx; | |
464 | ||
465 | spin_lock(&e->lock); | |
466 | /* New class */ | |
467 | err = t4_sched_class_fw_cmd(pi, &np, SCHED_FW_OP_ADD); | |
468 | if (err) { | |
469 | spin_unlock(&e->lock); | |
470 | e = NULL; | |
471 | goto out; | |
472 | } | |
473 | memcpy(&e->info, &np, sizeof(e->info)); | |
474 | atomic_set(&e->refcnt, 0); | |
475 | e->state = SCHED_STATE_ACTIVE; | |
476 | spin_unlock(&e->lock); | |
477 | } | |
478 | ||
479 | out: | |
480 | write_unlock(&s->rw_lock); | |
481 | return e; | |
482 | } | |
483 | ||
484 | /** | |
485 | * cxgb4_sched_class_alloc - allocate a scheduling class | |
486 | * @dev: net_device pointer | |
487 | * @p: new scheduling class to create. | |
488 | * | |
489 | * Returns pointer to the scheduling class created. If @p is NULL, then | |
490 | * it allocates and returns any available unused scheduling class. If a | |
491 | * scheduling class with matching @p is found, then the matching class is | |
492 | * returned. | |
493 | */ | |
494 | struct sched_class *cxgb4_sched_class_alloc(struct net_device *dev, | |
495 | struct ch_sched_params *p) | |
496 | { | |
497 | struct port_info *pi = netdev2pinfo(dev); | |
498 | u8 class_id; | |
499 | ||
500 | if (!can_sched(dev)) | |
501 | return NULL; | |
502 | ||
503 | class_id = p->u.params.class; | |
504 | if (!valid_class_id(dev, class_id)) | |
505 | return NULL; | |
506 | ||
507 | return t4_sched_class_alloc(pi, p); | |
508 | } | |
509 | ||
6cede1f1 RL |
510 | static void t4_sched_class_free(struct port_info *pi, struct sched_class *e) |
511 | { | |
512 | t4_sched_class_unbind_all(pi, e, SCHED_QUEUE); | |
513 | } | |
514 | ||
b72a32da RL |
515 | struct sched_table *t4_init_sched(unsigned int sched_size) |
516 | { | |
517 | struct sched_table *s; | |
518 | unsigned int i; | |
519 | ||
520 | s = t4_alloc_mem(sizeof(*s) + sched_size * sizeof(struct sched_class)); | |
521 | if (!s) | |
522 | return NULL; | |
523 | ||
524 | s->sched_size = sched_size; | |
525 | rwlock_init(&s->rw_lock); | |
526 | ||
527 | for (i = 0; i < s->sched_size; i++) { | |
528 | memset(&s->tab[i], 0, sizeof(struct sched_class)); | |
529 | s->tab[i].idx = i; | |
530 | s->tab[i].state = SCHED_STATE_UNUSED; | |
6cede1f1 | 531 | INIT_LIST_HEAD(&s->tab[i].queue_list); |
b72a32da RL |
532 | spin_lock_init(&s->tab[i].lock); |
533 | atomic_set(&s->tab[i].refcnt, 0); | |
534 | } | |
535 | return s; | |
536 | } | |
537 | ||
538 | void t4_cleanup_sched(struct adapter *adap) | |
539 | { | |
540 | struct sched_table *s; | |
541 | unsigned int i; | |
542 | ||
543 | for_each_port(adap, i) { | |
544 | struct port_info *pi = netdev2pinfo(adap->port[i]); | |
545 | ||
546 | s = pi->sched_tbl; | |
6cede1f1 RL |
547 | for (i = 0; i < s->sched_size; i++) { |
548 | struct sched_class *e; | |
549 | ||
550 | write_lock(&s->rw_lock); | |
551 | e = &s->tab[i]; | |
552 | if (e->state == SCHED_STATE_ACTIVE) | |
553 | t4_sched_class_free(pi, e); | |
554 | write_unlock(&s->rw_lock); | |
555 | } | |
b72a32da RL |
556 | t4_free_mem(s); |
557 | } | |
558 | } |