mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-22 17:52:09 +00:00
As of now all transmit queues transmit packets out of same scheduler queue hierarchy. Due to this PFC frames sent by peer are not handled properly, either all transmit queues are backpressured or none. To fix this when user enables PFC for a given priority map relavant transmit queue to a different scheduler queue hierarcy, so that backpressure is applied only to the traffic egressing out of that TXQ. Signed-off-by: Suman Ghosh <sumang@marvell.com> Link: https://lore.kernel.org/r/20220830120304.158060-1-sumang@marvell.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
471 lines
11 KiB
C
471 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Marvell RVU Ethernet driver
|
|
*
|
|
* Copyright (C) 2021 Marvell.
|
|
*
|
|
*/
|
|
|
|
#include "otx2_common.h"
|
|
|
|
static int otx2_check_pfc_config(struct otx2_nic *pfvf)
|
|
{
|
|
u8 tx_queues = pfvf->hw.tx_queues, prio;
|
|
u8 pfc_en = pfvf->pfc_en;
|
|
|
|
for (prio = 0; prio < NIX_PF_PFC_PRIO_MAX; prio++) {
|
|
if ((pfc_en & (1 << prio)) &&
|
|
prio > tx_queues - 1) {
|
|
dev_warn(pfvf->dev,
|
|
"Increase number of tx queues from %d to %d to support PFC.\n",
|
|
tx_queues, prio + 1);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_pfc_txschq_config(struct otx2_nic *pfvf)
|
|
{
|
|
u8 pfc_en, pfc_bit_set;
|
|
int prio, lvl, err;
|
|
|
|
pfc_en = pfvf->pfc_en;
|
|
for (prio = 0; prio < NIX_PF_PFC_PRIO_MAX; prio++) {
|
|
pfc_bit_set = pfc_en & (1 << prio);
|
|
|
|
/* Either PFC bit is not set
|
|
* or tx scheduler is not allocated for the priority
|
|
*/
|
|
if (!pfc_bit_set || !pfvf->pfc_alloc_status[prio])
|
|
continue;
|
|
|
|
/* configure the scheduler for the tls*/
|
|
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
|
|
err = otx2_txschq_config(pfvf, lvl, prio, true);
|
|
if (err) {
|
|
dev_err(pfvf->dev,
|
|
"%s configure PFC tx schq for lvl:%d, prio:%d failed!\n",
|
|
__func__, lvl, prio);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_pfc_txschq_alloc_one(struct otx2_nic *pfvf, u8 prio)
|
|
{
|
|
struct nix_txsch_alloc_req *req;
|
|
struct nix_txsch_alloc_rsp *rsp;
|
|
int lvl, rc;
|
|
|
|
/* Get memory to put this msg */
|
|
req = otx2_mbox_alloc_msg_nix_txsch_alloc(&pfvf->mbox);
|
|
if (!req)
|
|
return -ENOMEM;
|
|
|
|
/* Request one schq per level upto max level as configured
|
|
* link config level. These rest of the scheduler can be
|
|
* same as hw.txschq_list.
|
|
*/
|
|
for (lvl = 0; lvl < pfvf->hw.txschq_link_cfg_lvl; lvl++)
|
|
req->schq[lvl] = 1;
|
|
|
|
rc = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rsp = (struct nix_txsch_alloc_rsp *)
|
|
otx2_mbox_get_rsp(&pfvf->mbox.mbox, 0, &req->hdr);
|
|
if (IS_ERR(rsp))
|
|
return PTR_ERR(rsp);
|
|
|
|
/* Setup transmit scheduler list */
|
|
for (lvl = 0; lvl < pfvf->hw.txschq_link_cfg_lvl; lvl++) {
|
|
if (!rsp->schq[lvl])
|
|
return -ENOSPC;
|
|
|
|
pfvf->pfc_schq_list[lvl][prio] = rsp->schq_list[lvl][0];
|
|
}
|
|
|
|
/* Set the Tx schedulers for rest of the levels same as
|
|
* hw.txschq_list as those will be common for all.
|
|
*/
|
|
for (; lvl < NIX_TXSCH_LVL_CNT; lvl++)
|
|
pfvf->pfc_schq_list[lvl][prio] = pfvf->hw.txschq_list[lvl][0];
|
|
|
|
pfvf->pfc_alloc_status[prio] = true;
|
|
return 0;
|
|
}
|
|
|
|
int otx2_pfc_txschq_alloc(struct otx2_nic *pfvf)
|
|
{
|
|
u8 pfc_en = pfvf->pfc_en;
|
|
u8 pfc_bit_set;
|
|
int err, prio;
|
|
|
|
for (prio = 0; prio < NIX_PF_PFC_PRIO_MAX; prio++) {
|
|
pfc_bit_set = pfc_en & (1 << prio);
|
|
|
|
if (!pfc_bit_set || pfvf->pfc_alloc_status[prio])
|
|
continue;
|
|
|
|
/* Add new scheduler to the priority */
|
|
err = otx2_pfc_txschq_alloc_one(pfvf, prio);
|
|
if (err) {
|
|
dev_err(pfvf->dev, "%s failed to allocate PFC TX schedulers\n", __func__);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_pfc_txschq_stop_one(struct otx2_nic *pfvf, u8 prio)
|
|
{
|
|
struct nix_txsch_free_req *free_req;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
/* free PFC TLx nodes */
|
|
free_req = otx2_mbox_alloc_msg_nix_txsch_free(&pfvf->mbox);
|
|
if (!free_req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
free_req->flags = TXSCHQ_FREE_ALL;
|
|
otx2_sync_mbox_msg(&pfvf->mbox);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
|
|
pfvf->pfc_alloc_status[prio] = false;
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_pfc_update_sq_smq_mapping(struct otx2_nic *pfvf, int prio)
|
|
{
|
|
struct nix_cn10k_aq_enq_req *cn10k_sq_aq;
|
|
struct net_device *dev = pfvf->netdev;
|
|
bool if_up = netif_running(dev);
|
|
struct nix_aq_enq_req *sq_aq;
|
|
|
|
if (if_up) {
|
|
if (pfvf->pfc_alloc_status[prio])
|
|
netif_tx_stop_all_queues(pfvf->netdev);
|
|
else
|
|
netif_tx_stop_queue(netdev_get_tx_queue(dev, prio));
|
|
}
|
|
|
|
if (test_bit(CN10K_LMTST, &pfvf->hw.cap_flag)) {
|
|
cn10k_sq_aq = otx2_mbox_alloc_msg_nix_cn10k_aq_enq(&pfvf->mbox);
|
|
if (!cn10k_sq_aq)
|
|
return -ENOMEM;
|
|
|
|
/* Fill AQ info */
|
|
cn10k_sq_aq->qidx = prio;
|
|
cn10k_sq_aq->ctype = NIX_AQ_CTYPE_SQ;
|
|
cn10k_sq_aq->op = NIX_AQ_INSTOP_WRITE;
|
|
|
|
/* Fill fields to update */
|
|
cn10k_sq_aq->sq.ena = 1;
|
|
cn10k_sq_aq->sq_mask.ena = 1;
|
|
cn10k_sq_aq->sq_mask.smq = GENMASK(9, 0);
|
|
cn10k_sq_aq->sq.smq = otx2_get_smq_idx(pfvf, prio);
|
|
} else {
|
|
sq_aq = otx2_mbox_alloc_msg_nix_aq_enq(&pfvf->mbox);
|
|
if (!sq_aq)
|
|
return -ENOMEM;
|
|
|
|
/* Fill AQ info */
|
|
sq_aq->qidx = prio;
|
|
sq_aq->ctype = NIX_AQ_CTYPE_SQ;
|
|
sq_aq->op = NIX_AQ_INSTOP_WRITE;
|
|
|
|
/* Fill fields to update */
|
|
sq_aq->sq.ena = 1;
|
|
sq_aq->sq_mask.ena = 1;
|
|
sq_aq->sq_mask.smq = GENMASK(8, 0);
|
|
sq_aq->sq.smq = otx2_get_smq_idx(pfvf, prio);
|
|
}
|
|
|
|
otx2_sync_mbox_msg(&pfvf->mbox);
|
|
|
|
if (if_up) {
|
|
if (pfvf->pfc_alloc_status[prio])
|
|
netif_tx_start_all_queues(pfvf->netdev);
|
|
else
|
|
netif_tx_start_queue(netdev_get_tx_queue(dev, prio));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_pfc_txschq_update(struct otx2_nic *pfvf)
|
|
{
|
|
bool if_up = netif_running(pfvf->netdev);
|
|
u8 pfc_en = pfvf->pfc_en, pfc_bit_set;
|
|
struct mbox *mbox = &pfvf->mbox;
|
|
int err, prio;
|
|
|
|
mutex_lock(&mbox->lock);
|
|
for (prio = 0; prio < NIX_PF_PFC_PRIO_MAX; prio++) {
|
|
pfc_bit_set = pfc_en & (1 << prio);
|
|
|
|
/* tx scheduler was created but user wants to disable now */
|
|
if (!pfc_bit_set && pfvf->pfc_alloc_status[prio]) {
|
|
mutex_unlock(&mbox->lock);
|
|
if (if_up)
|
|
netif_tx_stop_all_queues(pfvf->netdev);
|
|
|
|
otx2_smq_flush(pfvf, pfvf->pfc_schq_list[NIX_TXSCH_LVL_SMQ][prio]);
|
|
if (if_up)
|
|
netif_tx_start_all_queues(pfvf->netdev);
|
|
|
|
/* delete the schq */
|
|
err = otx2_pfc_txschq_stop_one(pfvf, prio);
|
|
if (err) {
|
|
dev_err(pfvf->dev,
|
|
"%s failed to stop PFC tx schedulers for priority: %d\n",
|
|
__func__, prio);
|
|
return err;
|
|
}
|
|
|
|
mutex_lock(&mbox->lock);
|
|
goto update_sq_smq_map;
|
|
}
|
|
|
|
/* Either PFC bit is not set
|
|
* or Tx scheduler is already mapped for the priority
|
|
*/
|
|
if (!pfc_bit_set || pfvf->pfc_alloc_status[prio])
|
|
continue;
|
|
|
|
/* Add new scheduler to the priority */
|
|
err = otx2_pfc_txschq_alloc_one(pfvf, prio);
|
|
if (err) {
|
|
mutex_unlock(&mbox->lock);
|
|
dev_err(pfvf->dev,
|
|
"%s failed to allocate PFC tx schedulers for priority: %d\n",
|
|
__func__, prio);
|
|
return err;
|
|
}
|
|
|
|
update_sq_smq_map:
|
|
err = otx2_pfc_update_sq_smq_mapping(pfvf, prio);
|
|
if (err) {
|
|
mutex_unlock(&mbox->lock);
|
|
dev_err(pfvf->dev, "%s failed PFC Tx schq sq:%d mapping", __func__, prio);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = otx2_pfc_txschq_config(pfvf);
|
|
mutex_unlock(&mbox->lock);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_pfc_txschq_stop(struct otx2_nic *pfvf)
|
|
{
|
|
u8 pfc_en, pfc_bit_set;
|
|
int prio, err;
|
|
|
|
pfc_en = pfvf->pfc_en;
|
|
for (prio = 0; prio < NIX_PF_PFC_PRIO_MAX; prio++) {
|
|
pfc_bit_set = pfc_en & (1 << prio);
|
|
if (!pfc_bit_set || !pfvf->pfc_alloc_status[prio])
|
|
continue;
|
|
|
|
/* Delete the existing scheduler */
|
|
err = otx2_pfc_txschq_stop_one(pfvf, prio);
|
|
if (err) {
|
|
dev_err(pfvf->dev, "%s failed to stop PFC TX schedulers\n", __func__);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_config_priority_flow_ctrl(struct otx2_nic *pfvf)
|
|
{
|
|
struct cgx_pfc_cfg *req;
|
|
struct cgx_pfc_rsp *rsp;
|
|
int err = 0;
|
|
|
|
if (is_otx2_lbkvf(pfvf->pdev))
|
|
return 0;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_cgx_prio_flow_ctrl_cfg(&pfvf->mbox);
|
|
if (!req) {
|
|
err = -ENOMEM;
|
|
goto unlock;
|
|
}
|
|
|
|
if (pfvf->pfc_en) {
|
|
req->rx_pause = true;
|
|
req->tx_pause = true;
|
|
} else {
|
|
req->rx_pause = false;
|
|
req->tx_pause = false;
|
|
}
|
|
req->pfc_en = pfvf->pfc_en;
|
|
|
|
if (!otx2_sync_mbox_msg(&pfvf->mbox)) {
|
|
rsp = (struct cgx_pfc_rsp *)
|
|
otx2_mbox_get_rsp(&pfvf->mbox.mbox, 0, &req->hdr);
|
|
if (req->rx_pause != rsp->rx_pause || req->tx_pause != rsp->tx_pause) {
|
|
dev_warn(pfvf->dev,
|
|
"Failed to config PFC\n");
|
|
err = -EPERM;
|
|
}
|
|
}
|
|
unlock:
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
void otx2_update_bpid_in_rqctx(struct otx2_nic *pfvf, int vlan_prio, int qidx,
|
|
bool pfc_enable)
|
|
{
|
|
bool if_up = netif_running(pfvf->netdev);
|
|
struct npa_aq_enq_req *npa_aq;
|
|
struct nix_aq_enq_req *aq;
|
|
int err = 0;
|
|
|
|
if (pfvf->queue_to_pfc_map[qidx] && pfc_enable) {
|
|
dev_warn(pfvf->dev,
|
|
"PFC enable not permitted as Priority %d already mapped to Queue %d\n",
|
|
pfvf->queue_to_pfc_map[qidx], qidx);
|
|
return;
|
|
}
|
|
|
|
if (if_up) {
|
|
netif_tx_stop_all_queues(pfvf->netdev);
|
|
netif_carrier_off(pfvf->netdev);
|
|
}
|
|
|
|
pfvf->queue_to_pfc_map[qidx] = vlan_prio;
|
|
|
|
aq = otx2_mbox_alloc_msg_nix_aq_enq(&pfvf->mbox);
|
|
if (!aq) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
aq->cq.bpid = pfvf->bpid[vlan_prio];
|
|
aq->cq_mask.bpid = GENMASK(8, 0);
|
|
|
|
/* Fill AQ info */
|
|
aq->qidx = qidx;
|
|
aq->ctype = NIX_AQ_CTYPE_CQ;
|
|
aq->op = NIX_AQ_INSTOP_WRITE;
|
|
|
|
otx2_sync_mbox_msg(&pfvf->mbox);
|
|
|
|
npa_aq = otx2_mbox_alloc_msg_npa_aq_enq(&pfvf->mbox);
|
|
if (!npa_aq) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
npa_aq->aura.nix0_bpid = pfvf->bpid[vlan_prio];
|
|
npa_aq->aura_mask.nix0_bpid = GENMASK(8, 0);
|
|
|
|
/* Fill NPA AQ info */
|
|
npa_aq->aura_id = qidx;
|
|
npa_aq->ctype = NPA_AQ_CTYPE_AURA;
|
|
npa_aq->op = NPA_AQ_INSTOP_WRITE;
|
|
otx2_sync_mbox_msg(&pfvf->mbox);
|
|
|
|
out:
|
|
if (if_up) {
|
|
netif_carrier_on(pfvf->netdev);
|
|
netif_tx_start_all_queues(pfvf->netdev);
|
|
}
|
|
|
|
if (err)
|
|
dev_warn(pfvf->dev,
|
|
"Updating BPIDs in CQ and Aura contexts of RQ%d failed with err %d\n",
|
|
qidx, err);
|
|
}
|
|
|
|
static int otx2_dcbnl_ieee_getpfc(struct net_device *dev, struct ieee_pfc *pfc)
|
|
{
|
|
struct otx2_nic *pfvf = netdev_priv(dev);
|
|
|
|
pfc->pfc_cap = IEEE_8021QAZ_MAX_TCS;
|
|
pfc->pfc_en = pfvf->pfc_en;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_dcbnl_ieee_setpfc(struct net_device *dev, struct ieee_pfc *pfc)
|
|
{
|
|
struct otx2_nic *pfvf = netdev_priv(dev);
|
|
int err;
|
|
|
|
/* Save PFC configuration to interface */
|
|
pfvf->pfc_en = pfc->pfc_en;
|
|
|
|
if (pfvf->hw.tx_queues >= NIX_PF_PFC_PRIO_MAX)
|
|
goto process_pfc;
|
|
|
|
/* Check if the PFC configuration can be
|
|
* supported by the tx queue configuration
|
|
*/
|
|
err = otx2_check_pfc_config(pfvf);
|
|
if (err)
|
|
return err;
|
|
|
|
process_pfc:
|
|
err = otx2_config_priority_flow_ctrl(pfvf);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Request Per channel Bpids */
|
|
if (pfc->pfc_en)
|
|
otx2_nix_config_bp(pfvf, true);
|
|
|
|
err = otx2_pfc_txschq_update(pfvf);
|
|
if (err) {
|
|
dev_err(pfvf->dev, "%s failed to update TX schedulers\n", __func__);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u8 otx2_dcbnl_getdcbx(struct net_device __always_unused *dev)
|
|
{
|
|
return DCB_CAP_DCBX_HOST | DCB_CAP_DCBX_VER_IEEE;
|
|
}
|
|
|
|
static u8 otx2_dcbnl_setdcbx(struct net_device __always_unused *dev, u8 mode)
|
|
{
|
|
return (mode != (DCB_CAP_DCBX_HOST | DCB_CAP_DCBX_VER_IEEE)) ? 1 : 0;
|
|
}
|
|
|
|
static const struct dcbnl_rtnl_ops otx2_dcbnl_ops = {
|
|
.ieee_getpfc = otx2_dcbnl_ieee_getpfc,
|
|
.ieee_setpfc = otx2_dcbnl_ieee_setpfc,
|
|
.getdcbx = otx2_dcbnl_getdcbx,
|
|
.setdcbx = otx2_dcbnl_setdcbx,
|
|
};
|
|
|
|
int otx2_dcbnl_set_ops(struct net_device *dev)
|
|
{
|
|
struct otx2_nic *pfvf = netdev_priv(dev);
|
|
|
|
pfvf->queue_to_pfc_map = devm_kzalloc(pfvf->dev, pfvf->hw.rx_queues,
|
|
GFP_KERNEL);
|
|
if (!pfvf->queue_to_pfc_map)
|
|
return -ENOMEM;
|
|
dev->dcbnl_ops = &otx2_dcbnl_ops;
|
|
|
|
return 0;
|
|
}
|