// SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2019 Chelsio Communications. All rights reserved. */ #include "cxgb4.h" #include "cxgb4_tc_mqprio.h" #include "sched.h" static int cxgb4_mqprio_validate(struct net_device *dev, struct tc_mqprio_qopt_offload *mqprio) { u64 min_rate = 0, max_rate = 0, max_link_rate; struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); u32 speed, qcount = 0, qoffset = 0; u32 start_a, start_b, end_a, end_b; int ret; u8 i, j; if (!mqprio->qopt.num_tc) return 0; if (mqprio->qopt.hw != TC_MQPRIO_HW_OFFLOAD_TCS) { netdev_err(dev, "Only full TC hardware offload is supported\n"); return -EINVAL; } else if (mqprio->mode != TC_MQPRIO_MODE_CHANNEL) { netdev_err(dev, "Only channel mode offload is supported\n"); return -EINVAL; } else if (mqprio->shaper != TC_MQPRIO_SHAPER_BW_RATE) { netdev_err(dev, "Only bandwidth rate shaper supported\n"); return -EINVAL; } else if (mqprio->qopt.num_tc > adap->params.nsched_cls) { netdev_err(dev, "Only %u traffic classes supported by hardware\n", adap->params.nsched_cls); return -ERANGE; } ret = t4_get_link_params(pi, NULL, &speed, NULL); if (ret) { netdev_err(dev, "Failed to get link speed, ret: %d\n", ret); return -EINVAL; } /* Convert from Mbps to bps */ max_link_rate = (u64)speed * 1000 * 1000; for (i = 0; i < mqprio->qopt.num_tc; i++) { qoffset = max_t(u16, mqprio->qopt.offset[i], qoffset); qcount += mqprio->qopt.count[i]; start_a = mqprio->qopt.offset[i]; end_a = start_a + mqprio->qopt.count[i] - 1; for (j = i + 1; j < mqprio->qopt.num_tc; j++) { start_b = mqprio->qopt.offset[j]; end_b = start_b + mqprio->qopt.count[j] - 1; /* If queue count is 0, then the traffic * belonging to this class will not use * ETHOFLD queues. So, no need to validate * further. */ if (!mqprio->qopt.count[i]) break; if (!mqprio->qopt.count[j]) continue; if (max_t(u32, start_a, start_b) <= min_t(u32, end_a, end_b)) { netdev_err(dev, "Queues can't overlap across tc\n"); return -EINVAL; } } /* Convert byte per second to bits per second */ min_rate += (mqprio->min_rate[i] * 8); max_rate += (mqprio->max_rate[i] * 8); } if (qoffset >= adap->tids.neotids || qcount > adap->tids.neotids) return -ENOMEM; if (min_rate > max_link_rate || max_rate > max_link_rate) { netdev_err(dev, "Total Min/Max (%llu/%llu) Rate > supported (%llu)\n", min_rate, max_rate, max_link_rate); return -EINVAL; } return 0; } static int cxgb4_init_eosw_txq(struct net_device *dev, struct sge_eosw_txq *eosw_txq, u32 eotid, u32 hwqid) { struct adapter *adap = netdev2adap(dev); struct tx_sw_desc *ring; memset(eosw_txq, 0, sizeof(*eosw_txq)); ring = kcalloc(CXGB4_EOSW_TXQ_DEFAULT_DESC_NUM, sizeof(*ring), GFP_KERNEL); if (!ring) return -ENOMEM; eosw_txq->desc = ring; eosw_txq->ndesc = CXGB4_EOSW_TXQ_DEFAULT_DESC_NUM; spin_lock_init(&eosw_txq->lock); eosw_txq->state = CXGB4_EO_STATE_CLOSED; eosw_txq->eotid = eotid; eosw_txq->hwtid = adap->tids.eotid_base + eosw_txq->eotid; eosw_txq->cred = adap->params.ofldq_wr_cred; eosw_txq->hwqid = hwqid; eosw_txq->netdev = dev; tasklet_init(&eosw_txq->qresume_tsk, cxgb4_ethofld_restart, (unsigned long)eosw_txq); return 0; } static void cxgb4_clean_eosw_txq(struct net_device *dev, struct sge_eosw_txq *eosw_txq) { struct adapter *adap = netdev2adap(dev); cxgb4_eosw_txq_free_desc(adap, eosw_txq, eosw_txq->ndesc); eosw_txq->pidx = 0; eosw_txq->last_pidx = 0; eosw_txq->cidx = 0; eosw_txq->last_cidx = 0; eosw_txq->flowc_idx = 0; eosw_txq->inuse = 0; eosw_txq->cred = adap->params.ofldq_wr_cred; eosw_txq->ncompl = 0; eosw_txq->last_compl = 0; eosw_txq->state = CXGB4_EO_STATE_CLOSED; } static void cxgb4_free_eosw_txq(struct net_device *dev, struct sge_eosw_txq *eosw_txq) { spin_lock_bh(&eosw_txq->lock); cxgb4_clean_eosw_txq(dev, eosw_txq); kfree(eosw_txq->desc); spin_unlock_bh(&eosw_txq->lock); tasklet_kill(&eosw_txq->qresume_tsk); } static int cxgb4_mqprio_alloc_hw_resources(struct net_device *dev) { struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); struct sge_ofld_rxq *eorxq; struct sge_eohw_txq *eotxq; int ret, msix = 0; u32 i; /* Allocate ETHOFLD hardware queue structures if not done already */ if (!refcount_read(&adap->tc_mqprio->refcnt)) { adap->sge.eohw_rxq = kcalloc(adap->sge.eoqsets, sizeof(struct sge_ofld_rxq), GFP_KERNEL); if (!adap->sge.eohw_rxq) return -ENOMEM; adap->sge.eohw_txq = kcalloc(adap->sge.eoqsets, sizeof(struct sge_eohw_txq), GFP_KERNEL); if (!adap->sge.eohw_txq) { kfree(adap->sge.eohw_rxq); return -ENOMEM; } refcount_set(&adap->tc_mqprio->refcnt, 1); } else { refcount_inc(&adap->tc_mqprio->refcnt); } if (!(adap->flags & CXGB4_USING_MSIX)) msix = -((int)adap->sge.intrq.abs_id + 1); for (i = 0; i < pi->nqsets; i++) { eorxq = &adap->sge.eohw_rxq[pi->first_qset + i]; eotxq = &adap->sge.eohw_txq[pi->first_qset + i]; /* Allocate Rxqs for receiving ETHOFLD Tx completions */ if (msix >= 0) { msix = cxgb4_get_msix_idx_from_bmap(adap); if (msix < 0) { ret = msix; goto out_free_queues; } eorxq->msix = &adap->msix_info[msix]; snprintf(eorxq->msix->desc, sizeof(eorxq->msix->desc), "%s-eorxq%d", dev->name, i); } init_rspq(adap, &eorxq->rspq, CXGB4_EOHW_RXQ_DEFAULT_INTR_USEC, CXGB4_EOHW_RXQ_DEFAULT_PKT_CNT, CXGB4_EOHW_RXQ_DEFAULT_DESC_NUM, CXGB4_EOHW_RXQ_DEFAULT_DESC_SIZE); eorxq->fl.size = CXGB4_EOHW_FLQ_DEFAULT_DESC_NUM; ret = t4_sge_alloc_rxq(adap, &eorxq->rspq, false, dev, msix, &eorxq->fl, cxgb4_ethofld_rx_handler, NULL, 0); if (ret) goto out_free_queues; /* Allocate ETHOFLD hardware Txqs */ eotxq->q.size = CXGB4_EOHW_TXQ_DEFAULT_DESC_NUM; ret = t4_sge_alloc_ethofld_txq(adap, eotxq, dev, eorxq->rspq.cntxt_id); if (ret) goto out_free_queues; /* Allocate IRQs, set IRQ affinity, and start Rx */ if (adap->flags & CXGB4_USING_MSIX) { ret = request_irq(eorxq->msix->vec, t4_sge_intr_msix, 0, eorxq->msix->desc, &eorxq->rspq); if (ret) goto out_free_msix; cxgb4_set_msix_aff(adap, eorxq->msix->vec, &eorxq->msix->aff_mask, i); } if (adap->flags & CXGB4_FULL_INIT_DONE) cxgb4_enable_rx(adap, &eorxq->rspq); } return 0; out_free_msix: while (i-- > 0) { eorxq = &adap->sge.eohw_rxq[pi->first_qset + i]; if (adap->flags & CXGB4_FULL_INIT_DONE) cxgb4_quiesce_rx(&eorxq->rspq); if (adap->flags & CXGB4_USING_MSIX) { cxgb4_clear_msix_aff(eorxq->msix->vec, eorxq->msix->aff_mask); free_irq(eorxq->msix->vec, &eorxq->rspq); } } out_free_queues: for (i = 0; i < pi->nqsets; i++) { eorxq = &adap->sge.eohw_rxq[pi->first_qset + i]; eotxq = &adap->sge.eohw_txq[pi->first_qset + i]; if (eorxq->rspq.desc) free_rspq_fl(adap, &eorxq->rspq, &eorxq->fl); if (eorxq->msix) cxgb4_free_msix_idx_in_bmap(adap, eorxq->msix->idx); t4_sge_free_ethofld_txq(adap, eotxq); } if (refcount_dec_and_test(&adap->tc_mqprio->refcnt)) { kfree(adap->sge.eohw_txq); kfree(adap->sge.eohw_rxq); } return ret; } static void cxgb4_mqprio_free_hw_resources(struct net_device *dev) { struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); struct sge_ofld_rxq *eorxq; struct sge_eohw_txq *eotxq; u32 i; /* Return if no ETHOFLD structures have been allocated yet */ if (!refcount_read(&adap->tc_mqprio->refcnt)) return; /* Return if no hardware queues have been allocated */ if (!adap->sge.eohw_rxq[pi->first_qset].rspq.desc) return; for (i = 0; i < pi->nqsets; i++) { eorxq = &adap->sge.eohw_rxq[pi->first_qset + i]; eotxq = &adap->sge.eohw_txq[pi->first_qset + i]; /* Device removal path will already disable NAPI * before unregistering netdevice. So, only disable * NAPI if we're not in device removal path */ if (!(adap->flags & CXGB4_SHUTTING_DOWN)) cxgb4_quiesce_rx(&eorxq->rspq); if (adap->flags & CXGB4_USING_MSIX) { cxgb4_clear_msix_aff(eorxq->msix->vec, eorxq->msix->aff_mask); free_irq(eorxq->msix->vec, &eorxq->rspq); cxgb4_free_msix_idx_in_bmap(adap, eorxq->msix->idx); } free_rspq_fl(adap, &eorxq->rspq, &eorxq->fl); t4_sge_free_ethofld_txq(adap, eotxq); } /* Free up ETHOFLD structures if there are no users */ if (refcount_dec_and_test(&adap->tc_mqprio->refcnt)) { kfree(adap->sge.eohw_txq); kfree(adap->sge.eohw_rxq); } } static int cxgb4_mqprio_alloc_tc(struct net_device *dev, struct tc_mqprio_qopt_offload *mqprio) { struct ch_sched_params p = { .type = SCHED_CLASS_TYPE_PACKET, .u.params.level = SCHED_CLASS_LEVEL_CL_RL, .u.params.mode = SCHED_CLASS_MODE_FLOW, .u.params.rateunit = SCHED_CLASS_RATEUNIT_BITS, .u.params.ratemode = SCHED_CLASS_RATEMODE_ABS, .u.params.class = SCHED_CLS_NONE, .u.params.weight = 0, .u.params.pktsize = dev->mtu, }; struct cxgb4_tc_port_mqprio *tc_port_mqprio; struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); struct sched_class *e; int ret; u8 i; tc_port_mqprio = &adap->tc_mqprio->port_mqprio[pi->port_id]; p.u.params.channel = pi->tx_chan; for (i = 0; i < mqprio->qopt.num_tc; i++) { /* Convert from bytes per second to Kbps */ p.u.params.minrate = div_u64(mqprio->min_rate[i] * 8, 1000); p.u.params.maxrate = div_u64(mqprio->max_rate[i] * 8, 1000); /* Request larger burst buffer for smaller MTU, so * that hardware can work on more data per burst * cycle. */ if (dev->mtu <= ETH_DATA_LEN) p.u.params.burstsize = 8 * dev->mtu; e = cxgb4_sched_class_alloc(dev, &p); if (!e) { ret = -ENOMEM; goto out_err; } tc_port_mqprio->tc_hwtc_map[i] = e->idx; } return 0; out_err: while (i--) cxgb4_sched_class_free(dev, tc_port_mqprio->tc_hwtc_map[i]); return ret; } static void cxgb4_mqprio_free_tc(struct net_device *dev) { struct cxgb4_tc_port_mqprio *tc_port_mqprio; struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); u8 i; tc_port_mqprio = &adap->tc_mqprio->port_mqprio[pi->port_id]; for (i = 0; i < tc_port_mqprio->mqprio.qopt.num_tc; i++) cxgb4_sched_class_free(dev, tc_port_mqprio->tc_hwtc_map[i]); } static int cxgb4_mqprio_class_bind(struct net_device *dev, struct sge_eosw_txq *eosw_txq, u8 tc) { struct ch_sched_flowc fe; int ret; init_completion(&eosw_txq->completion); fe.tid = eosw_txq->eotid; fe.class = tc; ret = cxgb4_sched_class_bind(dev, &fe, SCHED_FLOWC); if (ret) return ret; ret = wait_for_completion_timeout(&eosw_txq->completion, CXGB4_FLOWC_WAIT_TIMEOUT); if (!ret) return -ETIMEDOUT; return 0; } static void cxgb4_mqprio_class_unbind(struct net_device *dev, struct sge_eosw_txq *eosw_txq, u8 tc) { struct adapter *adap = netdev2adap(dev); struct ch_sched_flowc fe; /* If we're shutting down, interrupts are disabled and no completions * come back. So, skip waiting for completions in this scenario. */ if (!(adap->flags & CXGB4_SHUTTING_DOWN)) init_completion(&eosw_txq->completion); fe.tid = eosw_txq->eotid; fe.class = tc; cxgb4_sched_class_unbind(dev, &fe, SCHED_FLOWC); if (!(adap->flags & CXGB4_SHUTTING_DOWN)) wait_for_completion_timeout(&eosw_txq->completion, CXGB4_FLOWC_WAIT_TIMEOUT); } static int cxgb4_mqprio_enable_offload(struct net_device *dev, struct tc_mqprio_qopt_offload *mqprio) { struct cxgb4_tc_port_mqprio *tc_port_mqprio; u32 qoffset, qcount, tot_qcount, qid, hwqid; struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); struct sge_eosw_txq *eosw_txq; int eotid, ret; u16 i, j; u8 hwtc; ret = cxgb4_mqprio_alloc_hw_resources(dev); if (ret) return -ENOMEM; tc_port_mqprio = &adap->tc_mqprio->port_mqprio[pi->port_id]; for (i = 0; i < mqprio->qopt.num_tc; i++) { qoffset = mqprio->qopt.offset[i]; qcount = mqprio->qopt.count[i]; for (j = 0; j < qcount; j++) { eotid = cxgb4_get_free_eotid(&adap->tids); if (eotid < 0) { ret = -ENOMEM; goto out_free_eotids; } qid = qoffset + j; hwqid = pi->first_qset + (eotid % pi->nqsets); eosw_txq = &tc_port_mqprio->eosw_txq[qid]; ret = cxgb4_init_eosw_txq(dev, eosw_txq, eotid, hwqid); if (ret) goto out_free_eotids; cxgb4_alloc_eotid(&adap->tids, eotid, eosw_txq); hwtc = tc_port_mqprio->tc_hwtc_map[i]; ret = cxgb4_mqprio_class_bind(dev, eosw_txq, hwtc); if (ret) goto out_free_eotids; } } memcpy(&tc_port_mqprio->mqprio, mqprio, sizeof(struct tc_mqprio_qopt_offload)); /* Inform the stack about the configured tc params. * * Set the correct queue map. If no queue count has been * specified, then send the traffic through default NIC * queues; instead of ETHOFLD queues. */ ret = netdev_set_num_tc(dev, mqprio->qopt.num_tc); if (ret) goto out_free_eotids; tot_qcount = pi->nqsets; for (i = 0; i < mqprio->qopt.num_tc; i++) { qcount = mqprio->qopt.count[i]; if (qcount) { qoffset = mqprio->qopt.offset[i] + pi->nqsets; } else { qcount = pi->nqsets; qoffset = 0; } ret = netdev_set_tc_queue(dev, i, qcount, qoffset); if (ret) goto out_reset_tc; tot_qcount += mqprio->qopt.count[i]; } ret = netif_set_real_num_tx_queues(dev, tot_qcount); if (ret) goto out_reset_tc; tc_port_mqprio->state = CXGB4_MQPRIO_STATE_ACTIVE; return 0; out_reset_tc: netdev_reset_tc(dev); i = mqprio->qopt.num_tc; out_free_eotids: while (i-- > 0) { qoffset = mqprio->qopt.offset[i]; qcount = mqprio->qopt.count[i]; for (j = 0; j < qcount; j++) { eosw_txq = &tc_port_mqprio->eosw_txq[qoffset + j]; hwtc = tc_port_mqprio->tc_hwtc_map[i]; cxgb4_mqprio_class_unbind(dev, eosw_txq, hwtc); cxgb4_free_eotid(&adap->tids, eosw_txq->eotid); cxgb4_free_eosw_txq(dev, eosw_txq); } } cxgb4_mqprio_free_hw_resources(dev); return ret; } static void cxgb4_mqprio_disable_offload(struct net_device *dev) { struct cxgb4_tc_port_mqprio *tc_port_mqprio; struct port_info *pi = netdev2pinfo(dev); struct adapter *adap = netdev2adap(dev); struct sge_eosw_txq *eosw_txq; u32 qoffset, qcount; u16 i, j; u8 hwtc; tc_port_mqprio = &adap->tc_mqprio->port_mqprio[pi->port_id]; if (tc_port_mqprio->state != CXGB4_MQPRIO_STATE_ACTIVE) return; netdev_reset_tc(dev); netif_set_real_num_tx_queues(dev, pi->nqsets); for (i = 0; i < tc_port_mqprio->mqprio.qopt.num_tc; i++) { qoffset = tc_port_mqprio->mqprio.qopt.offset[i]; qcount = tc_port_mqprio->mqprio.qopt.count[i]; for (j = 0; j < qcount; j++) { eosw_txq = &tc_port_mqprio->eosw_txq[qoffset + j]; hwtc = tc_port_mqprio->tc_hwtc_map[i]; cxgb4_mqprio_class_unbind(dev, eosw_txq, hwtc); cxgb4_free_eotid(&adap->tids, eosw_txq->eotid); cxgb4_free_eosw_txq(dev, eosw_txq); } } cxgb4_mqprio_free_hw_resources(dev); /* Free up the traffic classes */ cxgb4_mqprio_free_tc(dev); memset(&tc_port_mqprio->mqprio, 0, sizeof(struct tc_mqprio_qopt_offload)); tc_port_mqprio->state = CXGB4_MQPRIO_STATE_DISABLED; } int cxgb4_setup_tc_mqprio(struct net_device *dev, struct tc_mqprio_qopt_offload *mqprio) { struct adapter *adap = netdev2adap(dev); bool needs_bring_up = false; int ret; ret = cxgb4_mqprio_validate(dev, mqprio); if (ret) return ret; mutex_lock(&adap->tc_mqprio->mqprio_mutex); /* To configure tc params, the current allocated EOTIDs must * be freed up. However, they can't be freed up if there's * traffic running on the interface. So, ensure interface is * down before configuring tc params. */ if (netif_running(dev)) { cxgb_close(dev); needs_bring_up = true; } cxgb4_mqprio_disable_offload(dev); /* If requested for clear, then just return since resources are * already freed up by now. */ if (!mqprio->qopt.num_tc) goto out; /* Allocate free available traffic classes and configure * their rate parameters. */ ret = cxgb4_mqprio_alloc_tc(dev, mqprio); if (ret) goto out; ret = cxgb4_mqprio_enable_offload(dev, mqprio); if (ret) { cxgb4_mqprio_free_tc(dev); goto out; } out: if (needs_bring_up) cxgb_open(dev); mutex_unlock(&adap->tc_mqprio->mqprio_mutex); return ret; } void cxgb4_mqprio_stop_offload(struct adapter *adap) { struct cxgb4_tc_port_mqprio *tc_port_mqprio; struct net_device *dev; u8 i; if (!adap->tc_mqprio || !adap->tc_mqprio->port_mqprio) return; mutex_lock(&adap->tc_mqprio->mqprio_mutex); for_each_port(adap, i) { dev = adap->port[i]; if (!dev) continue; tc_port_mqprio = &adap->tc_mqprio->port_mqprio[i]; if (!tc_port_mqprio->mqprio.qopt.num_tc) continue; cxgb4_mqprio_disable_offload(dev); } mutex_unlock(&adap->tc_mqprio->mqprio_mutex); } int cxgb4_init_tc_mqprio(struct adapter *adap) { struct cxgb4_tc_port_mqprio *tc_port_mqprio, *port_mqprio; struct cxgb4_tc_mqprio *tc_mqprio; struct sge_eosw_txq *eosw_txq; int ret = 0; u8 i; tc_mqprio = kzalloc(sizeof(*tc_mqprio), GFP_KERNEL); if (!tc_mqprio) return -ENOMEM; tc_port_mqprio = kcalloc(adap->params.nports, sizeof(*tc_port_mqprio), GFP_KERNEL); if (!tc_port_mqprio) { ret = -ENOMEM; goto out_free_mqprio; } mutex_init(&tc_mqprio->mqprio_mutex); tc_mqprio->port_mqprio = tc_port_mqprio; for (i = 0; i < adap->params.nports; i++) { port_mqprio = &tc_mqprio->port_mqprio[i]; eosw_txq = kcalloc(adap->tids.neotids, sizeof(*eosw_txq), GFP_KERNEL); if (!eosw_txq) { ret = -ENOMEM; goto out_free_ports; } port_mqprio->eosw_txq = eosw_txq; } adap->tc_mqprio = tc_mqprio; refcount_set(&adap->tc_mqprio->refcnt, 0); return 0; out_free_ports: for (i = 0; i < adap->params.nports; i++) { port_mqprio = &tc_mqprio->port_mqprio[i]; kfree(port_mqprio->eosw_txq); } kfree(tc_port_mqprio); out_free_mqprio: kfree(tc_mqprio); return ret; } void cxgb4_cleanup_tc_mqprio(struct adapter *adap) { struct cxgb4_tc_port_mqprio *port_mqprio; u8 i; if (adap->tc_mqprio) { mutex_lock(&adap->tc_mqprio->mqprio_mutex); if (adap->tc_mqprio->port_mqprio) { for (i = 0; i < adap->params.nports; i++) { struct net_device *dev = adap->port[i]; if (dev) cxgb4_mqprio_disable_offload(dev); port_mqprio = &adap->tc_mqprio->port_mqprio[i]; kfree(port_mqprio->eosw_txq); } kfree(adap->tc_mqprio->port_mqprio); } mutex_unlock(&adap->tc_mqprio->mqprio_mutex); kfree(adap->tc_mqprio); } }