| /* |
| * This file is part of the Chelsio T4 Ethernet driver for Linux. |
| * |
| * Copyright (c) 2016 Chelsio Communications, Inc. All rights reserved. |
| * |
| * This software is available to you under a choice of one of two |
| * licenses. You may choose to be licensed under the terms of the GNU |
| * General Public License (GPL) Version 2, available from the file |
| * COPYING in the main directory of this source tree, or the |
| * OpenIB.org BSD license below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| |
| #include "cxgb4.h" |
| #include "sched.h" |
| |
| /* Spinlock must be held by caller */ |
| static int t4_sched_class_fw_cmd(struct port_info *pi, |
| struct ch_sched_params *p, |
| enum sched_fw_ops op) |
| { |
| struct adapter *adap = pi->adapter; |
| struct sched_table *s = pi->sched_tbl; |
| struct sched_class *e; |
| int err = 0; |
| |
| e = &s->tab[p->u.params.class]; |
| switch (op) { |
| case SCHED_FW_OP_ADD: |
| err = t4_sched_params(adap, p->type, |
| p->u.params.level, p->u.params.mode, |
| p->u.params.rateunit, |
| p->u.params.ratemode, |
| p->u.params.channel, e->idx, |
| p->u.params.minrate, p->u.params.maxrate, |
| p->u.params.weight, p->u.params.pktsize); |
| break; |
| default: |
| err = -ENOTSUPP; |
| break; |
| } |
| |
| return err; |
| } |
| |
| /* Spinlock must be held by caller */ |
| static int t4_sched_bind_unbind_op(struct port_info *pi, void *arg, |
| enum sched_bind_type type, bool bind) |
| { |
| struct adapter *adap = pi->adapter; |
| u32 fw_mnem, fw_class, fw_param; |
| unsigned int pf = adap->pf; |
| unsigned int vf = 0; |
| int err = 0; |
| |
| switch (type) { |
| case SCHED_QUEUE: { |
| struct sched_queue_entry *qe; |
| |
| qe = (struct sched_queue_entry *)arg; |
| |
| /* Create a template for the FW_PARAMS_CMD mnemonic and |
| * value (TX Scheduling Class in this case). |
| */ |
| fw_mnem = (FW_PARAMS_MNEM_V(FW_PARAMS_MNEM_DMAQ) | |
| FW_PARAMS_PARAM_X_V( |
| FW_PARAMS_PARAM_DMAQ_EQ_SCHEDCLASS_ETH)); |
| fw_class = bind ? qe->param.class : FW_SCHED_CLS_NONE; |
| fw_param = (fw_mnem | FW_PARAMS_PARAM_YZ_V(qe->cntxt_id)); |
| |
| pf = adap->pf; |
| vf = 0; |
| break; |
| } |
| default: |
| err = -ENOTSUPP; |
| goto out; |
| } |
| |
| err = t4_set_params(adap, adap->mbox, pf, vf, 1, &fw_param, &fw_class); |
| |
| out: |
| return err; |
| } |
| |
| static struct sched_class *t4_sched_queue_lookup(struct port_info *pi, |
| const unsigned int qid, |
| int *index) |
| { |
| struct sched_table *s = pi->sched_tbl; |
| struct sched_class *e, *end; |
| struct sched_class *found = NULL; |
| int i; |
| |
| /* Look for a class with matching bound queue parameters */ |
| end = &s->tab[s->sched_size]; |
| for (e = &s->tab[0]; e != end; ++e) { |
| struct sched_queue_entry *qe; |
| |
| i = 0; |
| if (e->state == SCHED_STATE_UNUSED) |
| continue; |
| |
| list_for_each_entry(qe, &e->queue_list, list) { |
| if (qe->cntxt_id == qid) { |
| found = e; |
| if (index) |
| *index = i; |
| break; |
| } |
| i++; |
| } |
| |
| if (found) |
| break; |
| } |
| |
| return found; |
| } |
| |
| static int t4_sched_queue_unbind(struct port_info *pi, struct ch_sched_queue *p) |
| { |
| struct adapter *adap = pi->adapter; |
| struct sched_class *e; |
| struct sched_queue_entry *qe = NULL; |
| struct sge_eth_txq *txq; |
| unsigned int qid; |
| int index = -1; |
| int err = 0; |
| |
| if (p->queue < 0 || p->queue >= pi->nqsets) |
| return -ERANGE; |
| |
| txq = &adap->sge.ethtxq[pi->first_qset + p->queue]; |
| qid = txq->q.cntxt_id; |
| |
| /* Find the existing class that the queue is bound to */ |
| e = t4_sched_queue_lookup(pi, qid, &index); |
| if (e && index >= 0) { |
| int i = 0; |
| |
| spin_lock(&e->lock); |
| list_for_each_entry(qe, &e->queue_list, list) { |
| if (i == index) |
| break; |
| i++; |
| } |
| err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE, |
| false); |
| if (err) { |
| spin_unlock(&e->lock); |
| goto out; |
| } |
| |
| list_del(&qe->list); |
| t4_free_mem(qe); |
| if (atomic_dec_and_test(&e->refcnt)) { |
| e->state = SCHED_STATE_UNUSED; |
| memset(&e->info, 0, sizeof(e->info)); |
| } |
| spin_unlock(&e->lock); |
| } |
| out: |
| return err; |
| } |
| |
| static int t4_sched_queue_bind(struct port_info *pi, struct ch_sched_queue *p) |
| { |
| struct adapter *adap = pi->adapter; |
| struct sched_table *s = pi->sched_tbl; |
| struct sched_class *e; |
| struct sched_queue_entry *qe = NULL; |
| struct sge_eth_txq *txq; |
| unsigned int qid; |
| int err = 0; |
| |
| if (p->queue < 0 || p->queue >= pi->nqsets) |
| return -ERANGE; |
| |
| qe = t4_alloc_mem(sizeof(struct sched_queue_entry)); |
| if (!qe) |
| return -ENOMEM; |
| |
| txq = &adap->sge.ethtxq[pi->first_qset + p->queue]; |
| qid = txq->q.cntxt_id; |
| |
| /* Unbind queue from any existing class */ |
| err = t4_sched_queue_unbind(pi, p); |
| if (err) { |
| t4_free_mem(qe); |
| goto out; |
| } |
| |
| /* Bind queue to specified class */ |
| memset(qe, 0, sizeof(*qe)); |
| qe->cntxt_id = qid; |
| memcpy(&qe->param, p, sizeof(qe->param)); |
| |
| e = &s->tab[qe->param.class]; |
| spin_lock(&e->lock); |
| err = t4_sched_bind_unbind_op(pi, (void *)qe, SCHED_QUEUE, true); |
| if (err) { |
| t4_free_mem(qe); |
| spin_unlock(&e->lock); |
| goto out; |
| } |
| |
| list_add_tail(&qe->list, &e->queue_list); |
| atomic_inc(&e->refcnt); |
| spin_unlock(&e->lock); |
| out: |
| return err; |
| } |
| |
| static void t4_sched_class_unbind_all(struct port_info *pi, |
| struct sched_class *e, |
| enum sched_bind_type type) |
| { |
| if (!e) |
| return; |
| |
| switch (type) { |
| case SCHED_QUEUE: { |
| struct sched_queue_entry *qe; |
| |
| list_for_each_entry(qe, &e->queue_list, list) |
| t4_sched_queue_unbind(pi, &qe->param); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| static int t4_sched_class_bind_unbind_op(struct port_info *pi, void *arg, |
| enum sched_bind_type type, bool bind) |
| { |
| int err = 0; |
| |
| if (!arg) |
| return -EINVAL; |
| |
| switch (type) { |
| case SCHED_QUEUE: { |
| struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; |
| |
| if (bind) |
| err = t4_sched_queue_bind(pi, qe); |
| else |
| err = t4_sched_queue_unbind(pi, qe); |
| break; |
| } |
| default: |
| err = -ENOTSUPP; |
| break; |
| } |
| |
| return err; |
| } |
| |
| /** |
| * cxgb4_sched_class_bind - Bind an entity to a scheduling class |
| * @dev: net_device pointer |
| * @arg: Entity opaque data |
| * @type: Entity type (Queue) |
| * |
| * Binds an entity (queue) to a scheduling class. If the entity |
| * is bound to another class, it will be unbound from the other class |
| * and bound to the class specified in @arg. |
| */ |
| int cxgb4_sched_class_bind(struct net_device *dev, void *arg, |
| enum sched_bind_type type) |
| { |
| struct port_info *pi = netdev2pinfo(dev); |
| struct sched_table *s; |
| int err = 0; |
| u8 class_id; |
| |
| if (!can_sched(dev)) |
| return -ENOTSUPP; |
| |
| if (!arg) |
| return -EINVAL; |
| |
| switch (type) { |
| case SCHED_QUEUE: { |
| struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; |
| |
| class_id = qe->class; |
| break; |
| } |
| default: |
| return -ENOTSUPP; |
| } |
| |
| if (!valid_class_id(dev, class_id)) |
| return -EINVAL; |
| |
| if (class_id == SCHED_CLS_NONE) |
| return -ENOTSUPP; |
| |
| s = pi->sched_tbl; |
| write_lock(&s->rw_lock); |
| err = t4_sched_class_bind_unbind_op(pi, arg, type, true); |
| write_unlock(&s->rw_lock); |
| |
| return err; |
| } |
| |
| /** |
| * cxgb4_sched_class_unbind - Unbind an entity from a scheduling class |
| * @dev: net_device pointer |
| * @arg: Entity opaque data |
| * @type: Entity type (Queue) |
| * |
| * Unbinds an entity (queue) from a scheduling class. |
| */ |
| int cxgb4_sched_class_unbind(struct net_device *dev, void *arg, |
| enum sched_bind_type type) |
| { |
| struct port_info *pi = netdev2pinfo(dev); |
| struct sched_table *s; |
| int err = 0; |
| u8 class_id; |
| |
| if (!can_sched(dev)) |
| return -ENOTSUPP; |
| |
| if (!arg) |
| return -EINVAL; |
| |
| switch (type) { |
| case SCHED_QUEUE: { |
| struct ch_sched_queue *qe = (struct ch_sched_queue *)arg; |
| |
| class_id = qe->class; |
| break; |
| } |
| default: |
| return -ENOTSUPP; |
| } |
| |
| if (!valid_class_id(dev, class_id)) |
| return -EINVAL; |
| |
| s = pi->sched_tbl; |
| write_lock(&s->rw_lock); |
| err = t4_sched_class_bind_unbind_op(pi, arg, type, false); |
| write_unlock(&s->rw_lock); |
| |
| return err; |
| } |
| |
| /* If @p is NULL, fetch any available unused class */ |
| static struct sched_class *t4_sched_class_lookup(struct port_info *pi, |
| const struct ch_sched_params *p) |
| { |
| struct sched_table *s = pi->sched_tbl; |
| struct sched_class *e, *end; |
| struct sched_class *found = NULL; |
| |
| if (!p) { |
| /* Get any available unused class */ |
| end = &s->tab[s->sched_size]; |
| for (e = &s->tab[0]; e != end; ++e) { |
| if (e->state == SCHED_STATE_UNUSED) { |
| found = e; |
| break; |
| } |
| } |
| } else { |
| /* Look for a class with matching scheduling parameters */ |
| struct ch_sched_params info; |
| struct ch_sched_params tp; |
| |
| memset(&info, 0, sizeof(info)); |
| memset(&tp, 0, sizeof(tp)); |
| |
| memcpy(&tp, p, sizeof(tp)); |
| /* Don't try to match class parameter */ |
| tp.u.params.class = SCHED_CLS_NONE; |
| |
| end = &s->tab[s->sched_size]; |
| for (e = &s->tab[0]; e != end; ++e) { |
| if (e->state == SCHED_STATE_UNUSED) |
| continue; |
| |
| memset(&info, 0, sizeof(info)); |
| memcpy(&info, &e->info, sizeof(info)); |
| /* Don't try to match class parameter */ |
| info.u.params.class = SCHED_CLS_NONE; |
| |
| if ((info.type == tp.type) && |
| (!memcmp(&info.u.params, &tp.u.params, |
| sizeof(info.u.params)))) { |
| found = e; |
| break; |
| } |
| } |
| } |
| |
| return found; |
| } |
| |
| static struct sched_class *t4_sched_class_alloc(struct port_info *pi, |
| struct ch_sched_params *p) |
| { |
| struct sched_table *s = pi->sched_tbl; |
| struct sched_class *e; |
| u8 class_id; |
| int err; |
| |
| if (!p) |
| return NULL; |
| |
| class_id = p->u.params.class; |
| |
| /* Only accept search for existing class with matching params |
| * or allocation of new class with specified params |
| */ |
| if (class_id != SCHED_CLS_NONE) |
| return NULL; |
| |
| write_lock(&s->rw_lock); |
| /* See if there's an exisiting class with same |
| * requested sched params |
| */ |
| e = t4_sched_class_lookup(pi, p); |
| if (!e) { |
| struct ch_sched_params np; |
| |
| /* Fetch any available unused class */ |
| e = t4_sched_class_lookup(pi, NULL); |
| if (!e) |
| goto out; |
| |
| memset(&np, 0, sizeof(np)); |
| memcpy(&np, p, sizeof(np)); |
| np.u.params.class = e->idx; |
| |
| spin_lock(&e->lock); |
| /* New class */ |
| err = t4_sched_class_fw_cmd(pi, &np, SCHED_FW_OP_ADD); |
| if (err) { |
| spin_unlock(&e->lock); |
| e = NULL; |
| goto out; |
| } |
| memcpy(&e->info, &np, sizeof(e->info)); |
| atomic_set(&e->refcnt, 0); |
| e->state = SCHED_STATE_ACTIVE; |
| spin_unlock(&e->lock); |
| } |
| |
| out: |
| write_unlock(&s->rw_lock); |
| return e; |
| } |
| |
| /** |
| * cxgb4_sched_class_alloc - allocate a scheduling class |
| * @dev: net_device pointer |
| * @p: new scheduling class to create. |
| * |
| * Returns pointer to the scheduling class created. If @p is NULL, then |
| * it allocates and returns any available unused scheduling class. If a |
| * scheduling class with matching @p is found, then the matching class is |
| * returned. |
| */ |
| struct sched_class *cxgb4_sched_class_alloc(struct net_device *dev, |
| struct ch_sched_params *p) |
| { |
| struct port_info *pi = netdev2pinfo(dev); |
| u8 class_id; |
| |
| if (!can_sched(dev)) |
| return NULL; |
| |
| class_id = p->u.params.class; |
| if (!valid_class_id(dev, class_id)) |
| return NULL; |
| |
| return t4_sched_class_alloc(pi, p); |
| } |
| |
| static void t4_sched_class_free(struct port_info *pi, struct sched_class *e) |
| { |
| t4_sched_class_unbind_all(pi, e, SCHED_QUEUE); |
| } |
| |
| struct sched_table *t4_init_sched(unsigned int sched_size) |
| { |
| struct sched_table *s; |
| unsigned int i; |
| |
| s = t4_alloc_mem(sizeof(*s) + sched_size * sizeof(struct sched_class)); |
| if (!s) |
| return NULL; |
| |
| s->sched_size = sched_size; |
| rwlock_init(&s->rw_lock); |
| |
| for (i = 0; i < s->sched_size; i++) { |
| memset(&s->tab[i], 0, sizeof(struct sched_class)); |
| s->tab[i].idx = i; |
| s->tab[i].state = SCHED_STATE_UNUSED; |
| INIT_LIST_HEAD(&s->tab[i].queue_list); |
| spin_lock_init(&s->tab[i].lock); |
| atomic_set(&s->tab[i].refcnt, 0); |
| } |
| return s; |
| } |
| |
| void t4_cleanup_sched(struct adapter *adap) |
| { |
| struct sched_table *s; |
| unsigned int i; |
| |
| for_each_port(adap, i) { |
| struct port_info *pi = netdev2pinfo(adap->port[i]); |
| |
| s = pi->sched_tbl; |
| for (i = 0; i < s->sched_size; i++) { |
| struct sched_class *e; |
| |
| write_lock(&s->rw_lock); |
| e = &s->tab[i]; |
| if (e->state == SCHED_STATE_ACTIVE) |
| t4_sched_class_free(pi, e); |
| write_unlock(&s->rw_lock); |
| } |
| t4_free_mem(s); |
| } |
| } |