694 lines · c
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 two7 * licenses. You may choose to be licensed under the terms of the GNU8 * General Public License (GPL) Version 2, available from the file9 * COPYING in the main directory of this source tree, or the10 * OpenIB.org BSD license below:11 *12 * Redistribution and use in source and binary forms, with or13 * without modification, are permitted provided that the following14 * conditions are met:15 *16 * - Redistributions of source code must retain the above17 * copyright notice, this list of conditions and the following18 * disclaimer.19 *20 * - Redistributions in binary form must reproduce the above21 * copyright notice, this list of conditions and the following22 * disclaimer in the documentation and/or other materials23 * 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 OF27 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND28 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS29 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN30 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN31 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE32 * SOFTWARE.33 */34 35#include <linux/module.h>36#include <linux/netdevice.h>37 38#include "cxgb4.h"39#include "sched.h"40 41static int t4_sched_class_fw_cmd(struct port_info *pi,42 struct ch_sched_params *p,43 enum sched_fw_ops op)44{45 struct adapter *adap = pi->adapter;46 struct sched_table *s = pi->sched_tbl;47 struct sched_class *e;48 int err = 0;49 50 e = &s->tab[p->u.params.class];51 switch (op) {52 case SCHED_FW_OP_ADD:53 case SCHED_FW_OP_DEL: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 p->u.params.burstsize);62 break;63 default:64 err = -ENOTSUPP;65 break;66 }67 68 return err;69}70 71static 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 and87 * 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 98 err = t4_set_params(adap, adap->mbox, pf, vf, 1,99 &fw_param, &fw_class);100 break;101 }102 case SCHED_FLOWC: {103 struct sched_flowc_entry *fe;104 105 fe = (struct sched_flowc_entry *)arg;106 107 fw_class = bind ? fe->param.class : FW_SCHED_CLS_NONE;108 err = cxgb4_ethofld_send_flowc(adap->port[pi->port_id],109 fe->param.tid, fw_class);110 break;111 }112 default:113 err = -ENOTSUPP;114 break;115 }116 117 return err;118}119 120static void *t4_sched_entry_lookup(struct port_info *pi,121 enum sched_bind_type type,122 const u32 val)123{124 struct sched_table *s = pi->sched_tbl;125 struct sched_class *e, *end;126 void *found = NULL;127 128 /* Look for an entry with matching @val */129 end = &s->tab[s->sched_size];130 for (e = &s->tab[0]; e != end; ++e) {131 if (e->state == SCHED_STATE_UNUSED ||132 e->bind_type != type)133 continue;134 135 switch (type) {136 case SCHED_QUEUE: {137 struct sched_queue_entry *qe;138 139 list_for_each_entry(qe, &e->entry_list, list) {140 if (qe->cntxt_id == val) {141 found = qe;142 break;143 }144 }145 break;146 }147 case SCHED_FLOWC: {148 struct sched_flowc_entry *fe;149 150 list_for_each_entry(fe, &e->entry_list, list) {151 if (fe->param.tid == val) {152 found = fe;153 break;154 }155 }156 break;157 }158 default:159 return NULL;160 }161 162 if (found)163 break;164 }165 166 return found;167}168 169struct sched_class *cxgb4_sched_queue_lookup(struct net_device *dev,170 struct ch_sched_queue *p)171{172 struct port_info *pi = netdev2pinfo(dev);173 struct sched_queue_entry *qe = NULL;174 struct adapter *adap = pi->adapter;175 struct sge_eth_txq *txq;176 177 if (p->queue < 0 || p->queue >= pi->nqsets)178 return NULL;179 180 txq = &adap->sge.ethtxq[pi->first_qset + p->queue];181 qe = t4_sched_entry_lookup(pi, SCHED_QUEUE, txq->q.cntxt_id);182 return qe ? &pi->sched_tbl->tab[qe->param.class] : NULL;183}184 185static int t4_sched_queue_unbind(struct port_info *pi, struct ch_sched_queue *p)186{187 struct sched_queue_entry *qe = NULL;188 struct adapter *adap = pi->adapter;189 struct sge_eth_txq *txq;190 struct sched_class *e;191 int err = 0;192 193 if (p->queue < 0 || p->queue >= pi->nqsets)194 return -ERANGE;195 196 txq = &adap->sge.ethtxq[pi->first_qset + p->queue];197 198 /* Find the existing entry that the queue is bound to */199 qe = t4_sched_entry_lookup(pi, SCHED_QUEUE, txq->q.cntxt_id);200 if (qe) {201 err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE,202 false);203 if (err)204 return err;205 206 e = &pi->sched_tbl->tab[qe->param.class];207 list_del(&qe->list);208 kvfree(qe);209 if (atomic_dec_and_test(&e->refcnt))210 cxgb4_sched_class_free(adap->port[pi->port_id], e->idx);211 }212 return err;213}214 215static int t4_sched_queue_bind(struct port_info *pi, struct ch_sched_queue *p)216{217 struct sched_table *s = pi->sched_tbl;218 struct sched_queue_entry *qe = NULL;219 struct adapter *adap = pi->adapter;220 struct sge_eth_txq *txq;221 struct sched_class *e;222 unsigned int qid;223 int err = 0;224 225 if (p->queue < 0 || p->queue >= pi->nqsets)226 return -ERANGE;227 228 qe = kvzalloc(sizeof(struct sched_queue_entry), GFP_KERNEL);229 if (!qe)230 return -ENOMEM;231 232 txq = &adap->sge.ethtxq[pi->first_qset + p->queue];233 qid = txq->q.cntxt_id;234 235 /* Unbind queue from any existing class */236 err = t4_sched_queue_unbind(pi, p);237 if (err)238 goto out_err;239 240 /* Bind queue to specified class */241 qe->cntxt_id = qid;242 memcpy(&qe->param, p, sizeof(qe->param));243 244 e = &s->tab[qe->param.class];245 err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE, true);246 if (err)247 goto out_err;248 249 list_add_tail(&qe->list, &e->entry_list);250 e->bind_type = SCHED_QUEUE;251 atomic_inc(&e->refcnt);252 return err;253 254out_err:255 kvfree(qe);256 return err;257}258 259static int t4_sched_flowc_unbind(struct port_info *pi, struct ch_sched_flowc *p)260{261 struct sched_flowc_entry *fe = NULL;262 struct adapter *adap = pi->adapter;263 struct sched_class *e;264 int err = 0;265 266 if (p->tid < 0 || p->tid >= adap->tids.neotids)267 return -ERANGE;268 269 /* Find the existing entry that the flowc is bound to */270 fe = t4_sched_entry_lookup(pi, SCHED_FLOWC, p->tid);271 if (fe) {272 err = t4_sched_bind_unbind_op(pi, (void *)fe, SCHED_FLOWC,273 false);274 if (err)275 return err;276 277 e = &pi->sched_tbl->tab[fe->param.class];278 list_del(&fe->list);279 kvfree(fe);280 if (atomic_dec_and_test(&e->refcnt))281 cxgb4_sched_class_free(adap->port[pi->port_id], e->idx);282 }283 return err;284}285 286static int t4_sched_flowc_bind(struct port_info *pi, struct ch_sched_flowc *p)287{288 struct sched_table *s = pi->sched_tbl;289 struct sched_flowc_entry *fe = NULL;290 struct adapter *adap = pi->adapter;291 struct sched_class *e;292 int err = 0;293 294 if (p->tid < 0 || p->tid >= adap->tids.neotids)295 return -ERANGE;296 297 fe = kvzalloc(sizeof(*fe), GFP_KERNEL);298 if (!fe)299 return -ENOMEM;300 301 /* Unbind flowc from any existing class */302 err = t4_sched_flowc_unbind(pi, p);303 if (err)304 goto out_err;305 306 /* Bind flowc to specified class */307 memcpy(&fe->param, p, sizeof(fe->param));308 309 e = &s->tab[fe->param.class];310 err = t4_sched_bind_unbind_op(pi, (void *)fe, SCHED_FLOWC, true);311 if (err)312 goto out_err;313 314 list_add_tail(&fe->list, &e->entry_list);315 e->bind_type = SCHED_FLOWC;316 atomic_inc(&e->refcnt);317 return err;318 319out_err:320 kvfree(fe);321 return err;322}323 324static void t4_sched_class_unbind_all(struct port_info *pi,325 struct sched_class *e,326 enum sched_bind_type type)327{328 if (!e)329 return;330 331 switch (type) {332 case SCHED_QUEUE: {333 struct sched_queue_entry *qe;334 335 list_for_each_entry(qe, &e->entry_list, list)336 t4_sched_queue_unbind(pi, &qe->param);337 break;338 }339 case SCHED_FLOWC: {340 struct sched_flowc_entry *fe;341 342 list_for_each_entry(fe, &e->entry_list, list)343 t4_sched_flowc_unbind(pi, &fe->param);344 break;345 }346 default:347 break;348 }349}350 351static int t4_sched_class_bind_unbind_op(struct port_info *pi, void *arg,352 enum sched_bind_type type, bool bind)353{354 int err = 0;355 356 if (!arg)357 return -EINVAL;358 359 switch (type) {360 case SCHED_QUEUE: {361 struct ch_sched_queue *qe = (struct ch_sched_queue *)arg;362 363 if (bind)364 err = t4_sched_queue_bind(pi, qe);365 else366 err = t4_sched_queue_unbind(pi, qe);367 break;368 }369 case SCHED_FLOWC: {370 struct ch_sched_flowc *fe = (struct ch_sched_flowc *)arg;371 372 if (bind)373 err = t4_sched_flowc_bind(pi, fe);374 else375 err = t4_sched_flowc_unbind(pi, fe);376 break;377 }378 default:379 err = -ENOTSUPP;380 break;381 }382 383 return err;384}385 386/**387 * cxgb4_sched_class_bind - Bind an entity to a scheduling class388 * @dev: net_device pointer389 * @arg: Entity opaque data390 * @type: Entity type (Queue)391 *392 * Binds an entity (queue) to a scheduling class. If the entity393 * is bound to another class, it will be unbound from the other class394 * and bound to the class specified in @arg.395 */396int cxgb4_sched_class_bind(struct net_device *dev, void *arg,397 enum sched_bind_type type)398{399 struct port_info *pi = netdev2pinfo(dev);400 u8 class_id;401 402 if (!can_sched(dev))403 return -ENOTSUPP;404 405 if (!arg)406 return -EINVAL;407 408 switch (type) {409 case SCHED_QUEUE: {410 struct ch_sched_queue *qe = (struct ch_sched_queue *)arg;411 412 class_id = qe->class;413 break;414 }415 case SCHED_FLOWC: {416 struct ch_sched_flowc *fe = (struct ch_sched_flowc *)arg;417 418 class_id = fe->class;419 break;420 }421 default:422 return -ENOTSUPP;423 }424 425 if (!valid_class_id(dev, class_id))426 return -EINVAL;427 428 if (class_id == SCHED_CLS_NONE)429 return -ENOTSUPP;430 431 return t4_sched_class_bind_unbind_op(pi, arg, type, true);432 433}434 435/**436 * cxgb4_sched_class_unbind - Unbind an entity from a scheduling class437 * @dev: net_device pointer438 * @arg: Entity opaque data439 * @type: Entity type (Queue)440 *441 * Unbinds an entity (queue) from a scheduling class.442 */443int cxgb4_sched_class_unbind(struct net_device *dev, void *arg,444 enum sched_bind_type type)445{446 struct port_info *pi = netdev2pinfo(dev);447 u8 class_id;448 449 if (!can_sched(dev))450 return -ENOTSUPP;451 452 if (!arg)453 return -EINVAL;454 455 switch (type) {456 case SCHED_QUEUE: {457 struct ch_sched_queue *qe = (struct ch_sched_queue *)arg;458 459 class_id = qe->class;460 break;461 }462 case SCHED_FLOWC: {463 struct ch_sched_flowc *fe = (struct ch_sched_flowc *)arg;464 465 class_id = fe->class;466 break;467 }468 default:469 return -ENOTSUPP;470 }471 472 if (!valid_class_id(dev, class_id))473 return -EINVAL;474 475 return t4_sched_class_bind_unbind_op(pi, arg, type, false);476}477 478/* If @p is NULL, fetch any available unused class */479static struct sched_class *t4_sched_class_lookup(struct port_info *pi,480 const struct ch_sched_params *p)481{482 struct sched_table *s = pi->sched_tbl;483 struct sched_class *found = NULL;484 struct sched_class *e, *end;485 486 if (!p) {487 /* Get any available unused class */488 end = &s->tab[s->sched_size];489 for (e = &s->tab[0]; e != end; ++e) {490 if (e->state == SCHED_STATE_UNUSED) {491 found = e;492 break;493 }494 }495 } else {496 /* Look for a class with matching scheduling parameters */497 struct ch_sched_params info;498 struct ch_sched_params tp;499 500 memcpy(&tp, p, sizeof(tp));501 /* Don't try to match class parameter */502 tp.u.params.class = SCHED_CLS_NONE;503 504 end = &s->tab[s->sched_size];505 for (e = &s->tab[0]; e != end; ++e) {506 if (e->state == SCHED_STATE_UNUSED)507 continue;508 509 memcpy(&info, &e->info, sizeof(info));510 /* Don't try to match class parameter */511 info.u.params.class = SCHED_CLS_NONE;512 513 if ((info.type == tp.type) &&514 (!memcmp(&info.u.params, &tp.u.params,515 sizeof(info.u.params)))) {516 found = e;517 break;518 }519 }520 }521 522 return found;523}524 525static struct sched_class *t4_sched_class_alloc(struct port_info *pi,526 struct ch_sched_params *p)527{528 struct sched_class *e = NULL;529 u8 class_id;530 int err;531 532 if (!p)533 return NULL;534 535 class_id = p->u.params.class;536 537 /* Only accept search for existing class with matching params538 * or allocation of new class with specified params539 */540 if (class_id != SCHED_CLS_NONE)541 return NULL;542 543 /* See if there's an exisiting class with same requested sched544 * params. Classes can only be shared among FLOWC types. For545 * other types, always request a new class.546 */547 if (p->u.params.mode == SCHED_CLASS_MODE_FLOW)548 e = t4_sched_class_lookup(pi, p);549 550 if (!e) {551 struct ch_sched_params np;552 553 /* Fetch any available unused class */554 e = t4_sched_class_lookup(pi, NULL);555 if (!e)556 return NULL;557 558 memcpy(&np, p, sizeof(np));559 np.u.params.class = e->idx;560 /* New class */561 err = t4_sched_class_fw_cmd(pi, &np, SCHED_FW_OP_ADD);562 if (err)563 return NULL;564 memcpy(&e->info, &np, sizeof(e->info));565 atomic_set(&e->refcnt, 0);566 e->state = SCHED_STATE_ACTIVE;567 }568 569 return e;570}571 572/**573 * cxgb4_sched_class_alloc - allocate a scheduling class574 * @dev: net_device pointer575 * @p: new scheduling class to create.576 *577 * Returns pointer to the scheduling class created. If @p is NULL, then578 * it allocates and returns any available unused scheduling class. If a579 * scheduling class with matching @p is found, then the matching class is580 * returned.581 */582struct sched_class *cxgb4_sched_class_alloc(struct net_device *dev,583 struct ch_sched_params *p)584{585 struct port_info *pi = netdev2pinfo(dev);586 u8 class_id;587 588 if (!can_sched(dev))589 return NULL;590 591 class_id = p->u.params.class;592 if (!valid_class_id(dev, class_id))593 return NULL;594 595 return t4_sched_class_alloc(pi, p);596}597 598/**599 * cxgb4_sched_class_free - free a scheduling class600 * @dev: net_device pointer601 * @classid: scheduling class id to free602 *603 * Frees a scheduling class if there are no users.604 */605void cxgb4_sched_class_free(struct net_device *dev, u8 classid)606{607 struct port_info *pi = netdev2pinfo(dev);608 struct sched_table *s = pi->sched_tbl;609 struct ch_sched_params p;610 struct sched_class *e;611 u32 speed;612 int ret;613 614 e = &s->tab[classid];615 if (!atomic_read(&e->refcnt) && e->state != SCHED_STATE_UNUSED) {616 /* Port based rate limiting needs explicit reset back617 * to max rate. But, we'll do explicit reset for all618 * types, instead of just port based type, to be on619 * the safer side.620 */621 memcpy(&p, &e->info, sizeof(p));622 /* Always reset mode to 0. Otherwise, FLOWC mode will623 * still be enabled even after resetting the traffic624 * class.625 */626 p.u.params.mode = 0;627 p.u.params.minrate = 0;628 p.u.params.pktsize = 0;629 630 ret = t4_get_link_params(pi, NULL, &speed, NULL);631 if (!ret)632 p.u.params.maxrate = speed * 1000; /* Mbps to Kbps */633 else634 p.u.params.maxrate = SCHED_MAX_RATE_KBPS;635 636 t4_sched_class_fw_cmd(pi, &p, SCHED_FW_OP_DEL);637 638 e->state = SCHED_STATE_UNUSED;639 memset(&e->info, 0, sizeof(e->info));640 }641}642 643static void t4_sched_class_free(struct net_device *dev, struct sched_class *e)644{645 struct port_info *pi = netdev2pinfo(dev);646 647 t4_sched_class_unbind_all(pi, e, e->bind_type);648 cxgb4_sched_class_free(dev, e->idx);649}650 651struct sched_table *t4_init_sched(unsigned int sched_size)652{653 struct sched_table *s;654 unsigned int i;655 656 s = kvzalloc(struct_size(s, tab, sched_size), GFP_KERNEL);657 if (!s)658 return NULL;659 660 s->sched_size = sched_size;661 662 for (i = 0; i < s->sched_size; i++) {663 memset(&s->tab[i], 0, sizeof(struct sched_class));664 s->tab[i].idx = i;665 s->tab[i].state = SCHED_STATE_UNUSED;666 INIT_LIST_HEAD(&s->tab[i].entry_list);667 atomic_set(&s->tab[i].refcnt, 0);668 }669 return s;670}671 672void t4_cleanup_sched(struct adapter *adap)673{674 struct sched_table *s;675 unsigned int j, i;676 677 for_each_port(adap, j) {678 struct port_info *pi = netdev2pinfo(adap->port[j]);679 680 s = pi->sched_tbl;681 if (!s)682 continue;683 684 for (i = 0; i < s->sched_size; i++) {685 struct sched_class *e;686 687 e = &s->tab[i];688 if (e->state == SCHED_STATE_ACTIVE)689 t4_sched_class_free(adap->port[j], e);690 }691 kvfree(s);692 }693}694