1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/ethernet/marvell/octeontx2/nic/qos.c
Hariprasad Kelam 04fb71cc5f octeontx2-pf: Reuse Transmit queue/Send queue index of HTB class
Real number of Transmit queues are incremented when user enables HTB
class and vice versa. Depending on SKB priority driver returns transmit
queue (Txq). Transmit queues and Send queues are one-to-one mapped.

In few scenarios, Driver is returning transmit queue value which is
greater than real number of transmit queue and Stack detects this as
error and overwrites transmit queue value.

For example
user has added two classes and real number of queues are incremented
accordingly
- tc class add dev eth1 parent 1: classid 1:1 htb
      rate 100Mbit ceil 100Mbit prio 1 quantum 1024
- tc class add dev eth1 parent 1: classid 1:2 htb
      rate 100Mbit ceil 200Mbit prio 7 quantum 1024

now if user deletes the class with id 1:1, driver decrements the real
number of queues
- tc class del dev eth1 classid 1:1

But for the class with id 1:2, driver is returning transmit queue
value which is higher than real number of transmit queue leading
to below error

eth1 selects TX queue x, but real number of TX queues is x

This patch solves the problem by assigning deleted class transmit
queue/send queue to active class.

Signed-off-by: Hariprasad Kelam <hkelam@marvell.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://lore.kernel.org/r/20240508070935.11501-1-hkelam@marvell.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2024-05-10 18:49:15 -07:00

1768 lines
43 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Marvell RVU Ethernet driver
*
* Copyright (C) 2023 Marvell.
*
*/
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/bitfield.h>
#include "otx2_common.h"
#include "cn10k.h"
#include "qos.h"
#define OTX2_QOS_QID_INNER 0xFFFFU
#define OTX2_QOS_QID_NONE 0xFFFEU
#define OTX2_QOS_ROOT_CLASSID 0xFFFFFFFF
#define OTX2_QOS_CLASS_NONE 0
#define OTX2_QOS_DEFAULT_PRIO 0xF
#define OTX2_QOS_INVALID_SQ 0xFFFF
#define OTX2_QOS_INVALID_TXSCHQ_IDX 0xFFFF
#define CN10K_MAX_RR_WEIGHT GENMASK_ULL(13, 0)
#define OTX2_MAX_RR_QUANTUM GENMASK_ULL(23, 0)
static void otx2_qos_update_tx_netdev_queues(struct otx2_nic *pfvf)
{
struct otx2_hw *hw = &pfvf->hw;
int tx_queues, qos_txqs, err;
qos_txqs = bitmap_weight(pfvf->qos.qos_sq_bmap,
OTX2_QOS_MAX_LEAF_NODES);
tx_queues = hw->tx_queues + qos_txqs;
err = netif_set_real_num_tx_queues(pfvf->netdev, tx_queues);
if (err) {
netdev_err(pfvf->netdev,
"Failed to set no of Tx queues: %d\n", tx_queues);
return;
}
}
static void otx2_qos_get_regaddr(struct otx2_qos_node *node,
struct nix_txschq_config *cfg,
int index)
{
if (node->level == NIX_TXSCH_LVL_SMQ) {
cfg->reg[index++] = NIX_AF_MDQX_PARENT(node->schq);
cfg->reg[index++] = NIX_AF_MDQX_SCHEDULE(node->schq);
cfg->reg[index++] = NIX_AF_MDQX_PIR(node->schq);
cfg->reg[index] = NIX_AF_MDQX_CIR(node->schq);
} else if (node->level == NIX_TXSCH_LVL_TL4) {
cfg->reg[index++] = NIX_AF_TL4X_PARENT(node->schq);
cfg->reg[index++] = NIX_AF_TL4X_SCHEDULE(node->schq);
cfg->reg[index++] = NIX_AF_TL4X_PIR(node->schq);
cfg->reg[index] = NIX_AF_TL4X_CIR(node->schq);
} else if (node->level == NIX_TXSCH_LVL_TL3) {
cfg->reg[index++] = NIX_AF_TL3X_PARENT(node->schq);
cfg->reg[index++] = NIX_AF_TL3X_SCHEDULE(node->schq);
cfg->reg[index++] = NIX_AF_TL3X_PIR(node->schq);
cfg->reg[index] = NIX_AF_TL3X_CIR(node->schq);
} else if (node->level == NIX_TXSCH_LVL_TL2) {
cfg->reg[index++] = NIX_AF_TL2X_PARENT(node->schq);
cfg->reg[index++] = NIX_AF_TL2X_SCHEDULE(node->schq);
cfg->reg[index++] = NIX_AF_TL2X_PIR(node->schq);
cfg->reg[index] = NIX_AF_TL2X_CIR(node->schq);
}
}
static int otx2_qos_quantum_to_dwrr_weight(struct otx2_nic *pfvf, u32 quantum)
{
u32 weight;
weight = quantum / pfvf->hw.dwrr_mtu;
if (quantum % pfvf->hw.dwrr_mtu)
weight += 1;
return weight;
}
static void otx2_config_sched_shaping(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct nix_txschq_config *cfg,
int *num_regs)
{
u32 rr_weight;
u32 quantum;
u64 maxrate;
otx2_qos_get_regaddr(node, cfg, *num_regs);
/* configure parent txschq */
cfg->regval[*num_regs] = node->parent->schq << 16;
(*num_regs)++;
/* configure prio/quantum */
if (node->qid == OTX2_QOS_QID_NONE) {
cfg->regval[*num_regs] = node->prio << 24 |
mtu_to_dwrr_weight(pfvf, pfvf->tx_max_pktlen);
(*num_regs)++;
return;
}
/* configure priority/quantum */
if (node->is_static) {
cfg->regval[*num_regs] =
(node->schq - node->parent->prio_anchor) << 24;
} else {
quantum = node->quantum ?
node->quantum : pfvf->tx_max_pktlen;
rr_weight = otx2_qos_quantum_to_dwrr_weight(pfvf, quantum);
cfg->regval[*num_regs] = node->parent->child_dwrr_prio << 24 |
rr_weight;
}
(*num_regs)++;
/* configure PIR */
maxrate = (node->rate > node->ceil) ? node->rate : node->ceil;
cfg->regval[*num_regs] =
otx2_get_txschq_rate_regval(pfvf, maxrate, 65536);
(*num_regs)++;
/* Don't configure CIR when both CIR+PIR not supported
* On 96xx, CIR + PIR + RED_ALGO=STALL causes deadlock
*/
if (!test_bit(QOS_CIR_PIR_SUPPORT, &pfvf->hw.cap_flag))
return;
cfg->regval[*num_regs] =
otx2_get_txschq_rate_regval(pfvf, node->rate, 65536);
(*num_regs)++;
}
static void __otx2_qos_txschq_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct nix_txschq_config *cfg)
{
struct otx2_hw *hw = &pfvf->hw;
int num_regs = 0;
u8 level;
level = node->level;
/* program txschq registers */
if (level == NIX_TXSCH_LVL_SMQ) {
cfg->reg[num_regs] = NIX_AF_SMQX_CFG(node->schq);
cfg->regval[num_regs] = ((u64)pfvf->tx_max_pktlen << 8) |
OTX2_MIN_MTU;
cfg->regval[num_regs] |= (0x20ULL << 51) | (0x80ULL << 39) |
(0x2ULL << 36);
num_regs++;
otx2_config_sched_shaping(pfvf, node, cfg, &num_regs);
} else if (level == NIX_TXSCH_LVL_TL4) {
otx2_config_sched_shaping(pfvf, node, cfg, &num_regs);
} else if (level == NIX_TXSCH_LVL_TL3) {
/* configure link cfg */
if (level == pfvf->qos.link_cfg_lvl) {
cfg->reg[num_regs] = NIX_AF_TL3_TL2X_LINKX_CFG(node->schq, hw->tx_link);
cfg->regval[num_regs] = BIT_ULL(13) | BIT_ULL(12);
num_regs++;
}
otx2_config_sched_shaping(pfvf, node, cfg, &num_regs);
} else if (level == NIX_TXSCH_LVL_TL2) {
/* configure link cfg */
if (level == pfvf->qos.link_cfg_lvl) {
cfg->reg[num_regs] = NIX_AF_TL3_TL2X_LINKX_CFG(node->schq, hw->tx_link);
cfg->regval[num_regs] = BIT_ULL(13) | BIT_ULL(12);
num_regs++;
}
/* check if node is root */
if (node->qid == OTX2_QOS_QID_INNER && !node->parent) {
cfg->reg[num_regs] = NIX_AF_TL2X_SCHEDULE(node->schq);
cfg->regval[num_regs] = TXSCH_TL1_DFLT_RR_PRIO << 24 |
mtu_to_dwrr_weight(pfvf,
pfvf->tx_max_pktlen);
num_regs++;
goto txschq_cfg_out;
}
otx2_config_sched_shaping(pfvf, node, cfg, &num_regs);
}
txschq_cfg_out:
cfg->num_regs = num_regs;
}
static int otx2_qos_txschq_set_parent_topology(struct otx2_nic *pfvf,
struct otx2_qos_node *parent)
{
struct mbox *mbox = &pfvf->mbox;
struct nix_txschq_config *cfg;
int rc;
if (parent->level == NIX_TXSCH_LVL_MDQ)
return 0;
mutex_lock(&mbox->lock);
cfg = otx2_mbox_alloc_msg_nix_txschq_cfg(&pfvf->mbox);
if (!cfg) {
mutex_unlock(&mbox->lock);
return -ENOMEM;
}
cfg->lvl = parent->level;
if (parent->level == NIX_TXSCH_LVL_TL4)
cfg->reg[0] = NIX_AF_TL4X_TOPOLOGY(parent->schq);
else if (parent->level == NIX_TXSCH_LVL_TL3)
cfg->reg[0] = NIX_AF_TL3X_TOPOLOGY(parent->schq);
else if (parent->level == NIX_TXSCH_LVL_TL2)
cfg->reg[0] = NIX_AF_TL2X_TOPOLOGY(parent->schq);
else if (parent->level == NIX_TXSCH_LVL_TL1)
cfg->reg[0] = NIX_AF_TL1X_TOPOLOGY(parent->schq);
cfg->regval[0] = (u64)parent->prio_anchor << 32;
cfg->regval[0] |= ((parent->child_dwrr_prio != OTX2_QOS_DEFAULT_PRIO) ?
parent->child_dwrr_prio : 0) << 1;
cfg->num_regs++;
rc = otx2_sync_mbox_msg(&pfvf->mbox);
mutex_unlock(&mbox->lock);
return rc;
}
static void otx2_qos_free_hw_node_schq(struct otx2_nic *pfvf,
struct otx2_qos_node *parent)
{
struct otx2_qos_node *node;
list_for_each_entry_reverse(node, &parent->child_schq_list, list)
otx2_txschq_free_one(pfvf, node->level, node->schq);
}
static void otx2_qos_free_hw_node(struct otx2_nic *pfvf,
struct otx2_qos_node *parent)
{
struct otx2_qos_node *node, *tmp;
list_for_each_entry_safe(node, tmp, &parent->child_list, list) {
otx2_qos_free_hw_node(pfvf, node);
otx2_qos_free_hw_node_schq(pfvf, node);
otx2_txschq_free_one(pfvf, node->level, node->schq);
}
}
static void otx2_qos_free_hw_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
mutex_lock(&pfvf->qos.qos_lock);
/* free child node hw mappings */
otx2_qos_free_hw_node(pfvf, node);
otx2_qos_free_hw_node_schq(pfvf, node);
/* free node hw mappings */
otx2_txschq_free_one(pfvf, node->level, node->schq);
mutex_unlock(&pfvf->qos.qos_lock);
}
static void otx2_qos_sw_node_delete(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
hash_del_rcu(&node->hlist);
if (node->qid != OTX2_QOS_QID_INNER && node->qid != OTX2_QOS_QID_NONE) {
__clear_bit(node->qid, pfvf->qos.qos_sq_bmap);
otx2_qos_update_tx_netdev_queues(pfvf);
}
list_del(&node->list);
kfree(node);
}
static void otx2_qos_free_sw_node_schq(struct otx2_nic *pfvf,
struct otx2_qos_node *parent)
{
struct otx2_qos_node *node, *tmp;
list_for_each_entry_safe(node, tmp, &parent->child_schq_list, list) {
list_del(&node->list);
kfree(node);
}
}
static void __otx2_qos_free_sw_node(struct otx2_nic *pfvf,
struct otx2_qos_node *parent)
{
struct otx2_qos_node *node, *tmp;
list_for_each_entry_safe(node, tmp, &parent->child_list, list) {
__otx2_qos_free_sw_node(pfvf, node);
otx2_qos_free_sw_node_schq(pfvf, node);
otx2_qos_sw_node_delete(pfvf, node);
}
}
static void otx2_qos_free_sw_node(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
mutex_lock(&pfvf->qos.qos_lock);
__otx2_qos_free_sw_node(pfvf, node);
otx2_qos_free_sw_node_schq(pfvf, node);
otx2_qos_sw_node_delete(pfvf, node);
mutex_unlock(&pfvf->qos.qos_lock);
}
static void otx2_qos_destroy_node(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
otx2_qos_free_hw_cfg(pfvf, node);
otx2_qos_free_sw_node(pfvf, node);
}
static void otx2_qos_fill_cfg_schq(struct otx2_qos_node *parent,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *node;
list_for_each_entry(node, &parent->child_schq_list, list)
cfg->schq[node->level]++;
}
static void otx2_qos_fill_cfg_tl(struct otx2_qos_node *parent,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *node;
list_for_each_entry(node, &parent->child_list, list) {
otx2_qos_fill_cfg_tl(node, cfg);
otx2_qos_fill_cfg_schq(node, cfg);
}
/* Assign the required number of transmit schedular queues under the
* given class
*/
cfg->schq_contig[parent->level - 1] += parent->child_dwrr_cnt +
parent->max_static_prio + 1;
}
static void otx2_qos_prepare_txschq_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *parent,
struct otx2_qos_cfg *cfg)
{
mutex_lock(&pfvf->qos.qos_lock);
otx2_qos_fill_cfg_tl(parent, cfg);
mutex_unlock(&pfvf->qos.qos_lock);
}
static void otx2_qos_read_txschq_cfg_schq(struct otx2_qos_node *parent,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *node;
int cnt;
list_for_each_entry(node, &parent->child_schq_list, list) {
cnt = cfg->dwrr_node_pos[node->level];
cfg->schq_list[node->level][cnt] = node->schq;
cfg->schq[node->level]++;
cfg->dwrr_node_pos[node->level]++;
}
}
static void otx2_qos_read_txschq_cfg_tl(struct otx2_qos_node *parent,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *node;
int cnt;
list_for_each_entry(node, &parent->child_list, list) {
otx2_qos_read_txschq_cfg_tl(node, cfg);
cnt = cfg->static_node_pos[node->level];
cfg->schq_contig_list[node->level][cnt] = node->schq;
cfg->schq_index_used[node->level][cnt] = true;
cfg->schq_contig[node->level]++;
cfg->static_node_pos[node->level]++;
otx2_qos_read_txschq_cfg_schq(node, cfg);
}
}
static void otx2_qos_read_txschq_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
mutex_lock(&pfvf->qos.qos_lock);
otx2_qos_read_txschq_cfg_tl(node, cfg);
mutex_unlock(&pfvf->qos.qos_lock);
}
static struct otx2_qos_node *
otx2_qos_alloc_root(struct otx2_nic *pfvf)
{
struct otx2_qos_node *node;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
return ERR_PTR(-ENOMEM);
node->parent = NULL;
if (!is_otx2_vf(pfvf->pcifunc)) {
node->level = NIX_TXSCH_LVL_TL1;
} else {
node->level = NIX_TXSCH_LVL_TL2;
node->child_dwrr_prio = OTX2_QOS_DEFAULT_PRIO;
}
WRITE_ONCE(node->qid, OTX2_QOS_QID_INNER);
node->classid = OTX2_QOS_ROOT_CLASSID;
hash_add_rcu(pfvf->qos.qos_hlist, &node->hlist, node->classid);
list_add_tail(&node->list, &pfvf->qos.qos_tree);
INIT_LIST_HEAD(&node->child_list);
INIT_LIST_HEAD(&node->child_schq_list);
return node;
}
static int otx2_qos_add_child_node(struct otx2_qos_node *parent,
struct otx2_qos_node *node)
{
struct list_head *head = &parent->child_list;
struct otx2_qos_node *tmp_node;
struct list_head *tmp;
if (node->prio > parent->max_static_prio)
parent->max_static_prio = node->prio;
for (tmp = head->next; tmp != head; tmp = tmp->next) {
tmp_node = list_entry(tmp, struct otx2_qos_node, list);
if (tmp_node->prio == node->prio &&
tmp_node->is_static)
return -EEXIST;
if (tmp_node->prio > node->prio) {
list_add_tail(&node->list, tmp);
return 0;
}
}
list_add_tail(&node->list, head);
return 0;
}
static int otx2_qos_alloc_txschq_node(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
struct otx2_qos_node *txschq_node, *parent, *tmp;
int lvl;
parent = node;
for (lvl = node->level - 1; lvl >= NIX_TXSCH_LVL_MDQ; lvl--) {
txschq_node = kzalloc(sizeof(*txschq_node), GFP_KERNEL);
if (!txschq_node)
goto err_out;
txschq_node->parent = parent;
txschq_node->level = lvl;
txschq_node->classid = OTX2_QOS_CLASS_NONE;
WRITE_ONCE(txschq_node->qid, OTX2_QOS_QID_NONE);
txschq_node->rate = 0;
txschq_node->ceil = 0;
txschq_node->prio = 0;
txschq_node->quantum = 0;
txschq_node->is_static = true;
txschq_node->child_dwrr_prio = OTX2_QOS_DEFAULT_PRIO;
txschq_node->txschq_idx = OTX2_QOS_INVALID_TXSCHQ_IDX;
mutex_lock(&pfvf->qos.qos_lock);
list_add_tail(&txschq_node->list, &node->child_schq_list);
mutex_unlock(&pfvf->qos.qos_lock);
INIT_LIST_HEAD(&txschq_node->child_list);
INIT_LIST_HEAD(&txschq_node->child_schq_list);
parent = txschq_node;
}
return 0;
err_out:
list_for_each_entry_safe(txschq_node, tmp, &node->child_schq_list,
list) {
list_del(&txschq_node->list);
kfree(txschq_node);
}
return -ENOMEM;
}
static struct otx2_qos_node *
otx2_qos_sw_create_leaf_node(struct otx2_nic *pfvf,
struct otx2_qos_node *parent,
u16 classid, u32 prio, u64 rate, u64 ceil,
u32 quantum, u16 qid, bool static_cfg)
{
struct otx2_qos_node *node;
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
return ERR_PTR(-ENOMEM);
node->parent = parent;
node->level = parent->level - 1;
node->classid = classid;
WRITE_ONCE(node->qid, qid);
node->rate = otx2_convert_rate(rate);
node->ceil = otx2_convert_rate(ceil);
node->prio = prio;
node->quantum = quantum;
node->is_static = static_cfg;
node->child_dwrr_prio = OTX2_QOS_DEFAULT_PRIO;
node->txschq_idx = OTX2_QOS_INVALID_TXSCHQ_IDX;
__set_bit(qid, pfvf->qos.qos_sq_bmap);
hash_add_rcu(pfvf->qos.qos_hlist, &node->hlist, classid);
mutex_lock(&pfvf->qos.qos_lock);
err = otx2_qos_add_child_node(parent, node);
if (err) {
mutex_unlock(&pfvf->qos.qos_lock);
return ERR_PTR(err);
}
mutex_unlock(&pfvf->qos.qos_lock);
INIT_LIST_HEAD(&node->child_list);
INIT_LIST_HEAD(&node->child_schq_list);
err = otx2_qos_alloc_txschq_node(pfvf, node);
if (err) {
otx2_qos_sw_node_delete(pfvf, node);
return ERR_PTR(-ENOMEM);
}
return node;
}
static struct otx2_qos_node
*otx2_sw_node_find_by_qid(struct otx2_nic *pfvf, u16 qid)
{
struct otx2_qos_node *node = NULL;
int bkt;
hash_for_each(pfvf->qos.qos_hlist, bkt, node, hlist) {
if (node->qid == qid)
break;
}
return node;
}
static struct otx2_qos_node *
otx2_sw_node_find(struct otx2_nic *pfvf, u32 classid)
{
struct otx2_qos_node *node = NULL;
hash_for_each_possible(pfvf->qos.qos_hlist, node, hlist, classid) {
if (node->classid == classid)
break;
}
return node;
}
static struct otx2_qos_node *
otx2_sw_node_find_rcu(struct otx2_nic *pfvf, u32 classid)
{
struct otx2_qos_node *node = NULL;
hash_for_each_possible_rcu(pfvf->qos.qos_hlist, node, hlist, classid) {
if (node->classid == classid)
break;
}
return node;
}
int otx2_get_txq_by_classid(struct otx2_nic *pfvf, u16 classid)
{
struct otx2_qos_node *node;
u16 qid;
int res;
node = otx2_sw_node_find_rcu(pfvf, classid);
if (!node) {
res = -ENOENT;
goto out;
}
qid = READ_ONCE(node->qid);
if (qid == OTX2_QOS_QID_INNER) {
res = -EINVAL;
goto out;
}
res = pfvf->hw.tx_queues + qid;
out:
return res;
}
static int
otx2_qos_txschq_config(struct otx2_nic *pfvf, struct otx2_qos_node *node)
{
struct mbox *mbox = &pfvf->mbox;
struct nix_txschq_config *req;
int rc;
mutex_lock(&mbox->lock);
req = otx2_mbox_alloc_msg_nix_txschq_cfg(&pfvf->mbox);
if (!req) {
mutex_unlock(&mbox->lock);
return -ENOMEM;
}
req->lvl = node->level;
__otx2_qos_txschq_cfg(pfvf, node, req);
rc = otx2_sync_mbox_msg(&pfvf->mbox);
mutex_unlock(&mbox->lock);
return rc;
}
static int otx2_qos_txschq_alloc(struct otx2_nic *pfvf,
struct otx2_qos_cfg *cfg)
{
struct nix_txsch_alloc_req *req;
struct nix_txsch_alloc_rsp *rsp;
struct mbox *mbox = &pfvf->mbox;
int lvl, rc, schq;
mutex_lock(&mbox->lock);
req = otx2_mbox_alloc_msg_nix_txsch_alloc(&pfvf->mbox);
if (!req) {
mutex_unlock(&mbox->lock);
return -ENOMEM;
}
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
req->schq[lvl] = cfg->schq[lvl];
req->schq_contig[lvl] = cfg->schq_contig[lvl];
}
rc = otx2_sync_mbox_msg(&pfvf->mbox);
if (rc) {
mutex_unlock(&mbox->lock);
return rc;
}
rsp = (struct nix_txsch_alloc_rsp *)
otx2_mbox_get_rsp(&pfvf->mbox.mbox, 0, &req->hdr);
if (IS_ERR(rsp)) {
rc = PTR_ERR(rsp);
goto out;
}
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
for (schq = 0; schq < rsp->schq_contig[lvl]; schq++) {
cfg->schq_contig_list[lvl][schq] =
rsp->schq_contig_list[lvl][schq];
}
}
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
for (schq = 0; schq < rsp->schq[lvl]; schq++) {
cfg->schq_list[lvl][schq] =
rsp->schq_list[lvl][schq];
}
}
pfvf->qos.link_cfg_lvl = rsp->link_cfg_lvl;
pfvf->hw.txschq_aggr_lvl_rr_prio = rsp->aggr_lvl_rr_prio;
out:
mutex_unlock(&mbox->lock);
return rc;
}
static void otx2_qos_free_unused_txschq(struct otx2_nic *pfvf,
struct otx2_qos_cfg *cfg)
{
int lvl, idx, schq;
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
for (idx = 0; idx < cfg->schq_contig[lvl]; idx++) {
if (!cfg->schq_index_used[lvl][idx]) {
schq = cfg->schq_contig_list[lvl][idx];
otx2_txschq_free_one(pfvf, lvl, schq);
}
}
}
}
static void otx2_qos_txschq_fill_cfg_schq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *tmp;
int cnt;
list_for_each_entry(tmp, &node->child_schq_list, list) {
cnt = cfg->dwrr_node_pos[tmp->level];
tmp->schq = cfg->schq_list[tmp->level][cnt];
cfg->dwrr_node_pos[tmp->level]++;
}
}
static void otx2_qos_txschq_fill_cfg_tl(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *tmp;
int cnt;
list_for_each_entry(tmp, &node->child_list, list) {
otx2_qos_txschq_fill_cfg_tl(pfvf, tmp, cfg);
cnt = cfg->static_node_pos[tmp->level];
tmp->schq = cfg->schq_contig_list[tmp->level][tmp->txschq_idx];
cfg->schq_index_used[tmp->level][tmp->txschq_idx] = true;
if (cnt == 0)
node->prio_anchor =
cfg->schq_contig_list[tmp->level][0];
cfg->static_node_pos[tmp->level]++;
otx2_qos_txschq_fill_cfg_schq(pfvf, tmp, cfg);
}
}
static void otx2_qos_txschq_fill_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
mutex_lock(&pfvf->qos.qos_lock);
otx2_qos_txschq_fill_cfg_tl(pfvf, node, cfg);
otx2_qos_txschq_fill_cfg_schq(pfvf, node, cfg);
otx2_qos_free_unused_txschq(pfvf, cfg);
mutex_unlock(&pfvf->qos.qos_lock);
}
static void __otx2_qos_assign_base_idx_tl(struct otx2_nic *pfvf,
struct otx2_qos_node *tmp,
unsigned long *child_idx_bmap,
int child_cnt)
{
int idx;
if (tmp->txschq_idx != OTX2_QOS_INVALID_TXSCHQ_IDX)
return;
/* assign static nodes 1:1 prio mapping first, then remaining nodes */
for (idx = 0; idx < child_cnt; idx++) {
if (tmp->is_static && tmp->prio == idx &&
!test_bit(idx, child_idx_bmap)) {
tmp->txschq_idx = idx;
set_bit(idx, child_idx_bmap);
return;
} else if (!tmp->is_static && idx >= tmp->prio &&
!test_bit(idx, child_idx_bmap)) {
tmp->txschq_idx = idx;
set_bit(idx, child_idx_bmap);
return;
}
}
}
static int otx2_qos_assign_base_idx_tl(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
unsigned long *child_idx_bmap;
struct otx2_qos_node *tmp;
int child_cnt;
list_for_each_entry(tmp, &node->child_list, list)
tmp->txschq_idx = OTX2_QOS_INVALID_TXSCHQ_IDX;
/* allocate child index array */
child_cnt = node->child_dwrr_cnt + node->max_static_prio + 1;
child_idx_bmap = kcalloc(BITS_TO_LONGS(child_cnt),
sizeof(unsigned long),
GFP_KERNEL);
if (!child_idx_bmap)
return -ENOMEM;
list_for_each_entry(tmp, &node->child_list, list)
otx2_qos_assign_base_idx_tl(pfvf, tmp);
/* assign base index of static priority children first */
list_for_each_entry(tmp, &node->child_list, list) {
if (!tmp->is_static)
continue;
__otx2_qos_assign_base_idx_tl(pfvf, tmp, child_idx_bmap,
child_cnt);
}
/* assign base index of dwrr priority children */
list_for_each_entry(tmp, &node->child_list, list)
__otx2_qos_assign_base_idx_tl(pfvf, tmp, child_idx_bmap,
child_cnt);
kfree(child_idx_bmap);
return 0;
}
static int otx2_qos_assign_base_idx(struct otx2_nic *pfvf,
struct otx2_qos_node *node)
{
int ret = 0;
mutex_lock(&pfvf->qos.qos_lock);
ret = otx2_qos_assign_base_idx_tl(pfvf, node);
mutex_unlock(&pfvf->qos.qos_lock);
return ret;
}
static int otx2_qos_txschq_push_cfg_schq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *tmp;
int ret;
list_for_each_entry(tmp, &node->child_schq_list, list) {
ret = otx2_qos_txschq_config(pfvf, tmp);
if (ret)
return -EIO;
ret = otx2_qos_txschq_set_parent_topology(pfvf, tmp->parent);
if (ret)
return -EIO;
}
return 0;
}
static int otx2_qos_txschq_push_cfg_tl(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
struct otx2_qos_node *tmp;
int ret;
list_for_each_entry(tmp, &node->child_list, list) {
ret = otx2_qos_txschq_push_cfg_tl(pfvf, tmp, cfg);
if (ret)
return -EIO;
ret = otx2_qos_txschq_config(pfvf, tmp);
if (ret)
return -EIO;
ret = otx2_qos_txschq_push_cfg_schq(pfvf, tmp, cfg);
if (ret)
return -EIO;
}
ret = otx2_qos_txschq_set_parent_topology(pfvf, node);
if (ret)
return -EIO;
return 0;
}
static int otx2_qos_txschq_push_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
int ret;
mutex_lock(&pfvf->qos.qos_lock);
ret = otx2_qos_txschq_push_cfg_tl(pfvf, node, cfg);
if (ret)
goto out;
ret = otx2_qos_txschq_push_cfg_schq(pfvf, node, cfg);
out:
mutex_unlock(&pfvf->qos.qos_lock);
return ret;
}
static int otx2_qos_txschq_update_config(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
otx2_qos_txschq_fill_cfg(pfvf, node, cfg);
return otx2_qos_txschq_push_cfg(pfvf, node, cfg);
}
static int otx2_qos_txschq_update_root_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *root,
struct otx2_qos_cfg *cfg)
{
root->schq = cfg->schq_list[root->level][0];
return otx2_qos_txschq_config(pfvf, root);
}
static void otx2_qos_free_cfg(struct otx2_nic *pfvf, struct otx2_qos_cfg *cfg)
{
int lvl, idx, schq;
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
for (idx = 0; idx < cfg->schq[lvl]; idx++) {
schq = cfg->schq_list[lvl][idx];
otx2_txschq_free_one(pfvf, lvl, schq);
}
}
for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) {
for (idx = 0; idx < cfg->schq_contig[lvl]; idx++) {
if (cfg->schq_index_used[lvl][idx]) {
schq = cfg->schq_contig_list[lvl][idx];
otx2_txschq_free_one(pfvf, lvl, schq);
}
}
}
}
static void otx2_qos_enadis_sq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
u16 qid)
{
if (pfvf->qos.qid_to_sqmap[qid] != OTX2_QOS_INVALID_SQ)
otx2_qos_disable_sq(pfvf, qid);
pfvf->qos.qid_to_sqmap[qid] = node->schq;
otx2_qos_txschq_config(pfvf, node);
otx2_qos_enable_sq(pfvf, qid);
}
static void otx2_qos_update_smq_schq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
bool action)
{
struct otx2_qos_node *tmp;
if (node->qid == OTX2_QOS_QID_INNER)
return;
list_for_each_entry(tmp, &node->child_schq_list, list) {
if (tmp->level == NIX_TXSCH_LVL_MDQ) {
if (action == QOS_SMQ_FLUSH)
otx2_smq_flush(pfvf, tmp->schq);
else
otx2_qos_enadis_sq(pfvf, tmp, node->qid);
}
}
}
static void __otx2_qos_update_smq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
bool action)
{
struct otx2_qos_node *tmp;
list_for_each_entry(tmp, &node->child_list, list) {
__otx2_qos_update_smq(pfvf, tmp, action);
if (tmp->qid == OTX2_QOS_QID_INNER)
continue;
if (tmp->level == NIX_TXSCH_LVL_MDQ) {
if (action == QOS_SMQ_FLUSH)
otx2_smq_flush(pfvf, tmp->schq);
else
otx2_qos_enadis_sq(pfvf, tmp, tmp->qid);
} else {
otx2_qos_update_smq_schq(pfvf, tmp, action);
}
}
}
static void otx2_qos_update_smq(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
bool action)
{
mutex_lock(&pfvf->qos.qos_lock);
__otx2_qos_update_smq(pfvf, node, action);
otx2_qos_update_smq_schq(pfvf, node, action);
mutex_unlock(&pfvf->qos.qos_lock);
}
static int otx2_qos_push_txschq_cfg(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
int ret;
ret = otx2_qos_txschq_alloc(pfvf, cfg);
if (ret)
return -ENOSPC;
ret = otx2_qos_assign_base_idx(pfvf, node);
if (ret)
return -ENOMEM;
if (!(pfvf->netdev->flags & IFF_UP)) {
otx2_qos_txschq_fill_cfg(pfvf, node, cfg);
return 0;
}
ret = otx2_qos_txschq_update_config(pfvf, node, cfg);
if (ret) {
otx2_qos_free_cfg(pfvf, cfg);
return -EIO;
}
otx2_qos_update_smq(pfvf, node, QOS_CFG_SQ);
return 0;
}
static int otx2_qos_update_tree(struct otx2_nic *pfvf,
struct otx2_qos_node *node,
struct otx2_qos_cfg *cfg)
{
otx2_qos_prepare_txschq_cfg(pfvf, node->parent, cfg);
return otx2_qos_push_txschq_cfg(pfvf, node->parent, cfg);
}
static int otx2_qos_root_add(struct otx2_nic *pfvf, u16 htb_maj_id, u16 htb_defcls,
struct netlink_ext_ack *extack)
{
struct otx2_qos_cfg *new_cfg;
struct otx2_qos_node *root;
int err;
netdev_dbg(pfvf->netdev,
"TC_HTB_CREATE: handle=0x%x defcls=0x%x\n",
htb_maj_id, htb_defcls);
root = otx2_qos_alloc_root(pfvf);
if (IS_ERR(root)) {
err = PTR_ERR(root);
return err;
}
/* allocate txschq queue */
new_cfg = kzalloc(sizeof(*new_cfg), GFP_KERNEL);
if (!new_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
err = -ENOMEM;
goto free_root_node;
}
/* allocate htb root node */
new_cfg->schq[root->level] = 1;
err = otx2_qos_txschq_alloc(pfvf, new_cfg);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "Error allocating txschq");
goto free_root_node;
}
/* Update TL1 RR PRIO */
if (root->level == NIX_TXSCH_LVL_TL1) {
root->child_dwrr_prio = pfvf->hw.txschq_aggr_lvl_rr_prio;
netdev_dbg(pfvf->netdev,
"TL1 DWRR Priority %d\n", root->child_dwrr_prio);
}
if (!(pfvf->netdev->flags & IFF_UP) ||
root->level == NIX_TXSCH_LVL_TL1) {
root->schq = new_cfg->schq_list[root->level][0];
goto out;
}
/* update the txschq configuration in hw */
err = otx2_qos_txschq_update_root_cfg(pfvf, root, new_cfg);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Error updating txschq configuration");
goto txschq_free;
}
out:
WRITE_ONCE(pfvf->qos.defcls, htb_defcls);
/* Pairs with smp_load_acquire() in ndo_select_queue */
smp_store_release(&pfvf->qos.maj_id, htb_maj_id);
kfree(new_cfg);
return 0;
txschq_free:
otx2_qos_free_cfg(pfvf, new_cfg);
free_root_node:
kfree(new_cfg);
otx2_qos_sw_node_delete(pfvf, root);
return err;
}
static int otx2_qos_root_destroy(struct otx2_nic *pfvf)
{
struct otx2_qos_node *root;
netdev_dbg(pfvf->netdev, "TC_HTB_DESTROY\n");
/* find root node */
root = otx2_sw_node_find(pfvf, OTX2_QOS_ROOT_CLASSID);
if (!root)
return -ENOENT;
/* free the hw mappings */
otx2_qos_destroy_node(pfvf, root);
return 0;
}
static int otx2_qos_validate_quantum(struct otx2_nic *pfvf, u32 quantum)
{
u32 rr_weight = otx2_qos_quantum_to_dwrr_weight(pfvf, quantum);
int err = 0;
/* Max Round robin weight supported by octeontx2 and CN10K
* is different. Validate accordingly
*/
if (is_dev_otx2(pfvf->pdev))
err = (rr_weight > OTX2_MAX_RR_QUANTUM) ? -EINVAL : 0;
else if (rr_weight > CN10K_MAX_RR_WEIGHT)
err = -EINVAL;
return err;
}
static int otx2_qos_validate_dwrr_cfg(struct otx2_qos_node *parent,
struct netlink_ext_ack *extack,
struct otx2_nic *pfvf,
u64 prio, u64 quantum)
{
int err;
err = otx2_qos_validate_quantum(pfvf, quantum);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "Unsupported quantum value");
return err;
}
if (parent->child_dwrr_prio == OTX2_QOS_DEFAULT_PRIO) {
parent->child_dwrr_prio = prio;
} else if (prio != parent->child_dwrr_prio) {
NL_SET_ERR_MSG_MOD(extack, "Only one DWRR group is allowed");
return -EOPNOTSUPP;
}
return 0;
}
static int otx2_qos_validate_configuration(struct otx2_qos_node *parent,
struct netlink_ext_ack *extack,
struct otx2_nic *pfvf,
u64 prio, bool static_cfg)
{
if (prio == parent->child_dwrr_prio && static_cfg) {
NL_SET_ERR_MSG_MOD(extack, "DWRR child group with same priority exists");
return -EEXIST;
}
if (static_cfg && test_bit(prio, parent->prio_bmap)) {
NL_SET_ERR_MSG_MOD(extack,
"Static priority child with same priority exists");
return -EEXIST;
}
return 0;
}
static void otx2_reset_dwrr_prio(struct otx2_qos_node *parent, u64 prio)
{
/* For PF, root node dwrr priority is static */
if (parent->level == NIX_TXSCH_LVL_TL1)
return;
if (parent->child_dwrr_prio != OTX2_QOS_DEFAULT_PRIO) {
parent->child_dwrr_prio = OTX2_QOS_DEFAULT_PRIO;
clear_bit(prio, parent->prio_bmap);
}
}
static bool is_qos_node_dwrr(struct otx2_qos_node *parent,
struct otx2_nic *pfvf,
u64 prio)
{
struct otx2_qos_node *node;
bool ret = false;
if (parent->child_dwrr_prio == prio)
return true;
mutex_lock(&pfvf->qos.qos_lock);
list_for_each_entry(node, &parent->child_list, list) {
if (prio == node->prio) {
if (parent->child_dwrr_prio != OTX2_QOS_DEFAULT_PRIO &&
parent->child_dwrr_prio != prio)
continue;
if (otx2_qos_validate_quantum(pfvf, node->quantum)) {
netdev_err(pfvf->netdev,
"Unsupported quantum value for existing classid=0x%x quantum=%d prio=%d",
node->classid, node->quantum,
node->prio);
break;
}
/* mark old node as dwrr */
node->is_static = false;
parent->child_dwrr_cnt++;
parent->child_static_cnt--;
ret = true;
break;
}
}
mutex_unlock(&pfvf->qos.qos_lock);
return ret;
}
static int otx2_qos_leaf_alloc_queue(struct otx2_nic *pfvf, u16 classid,
u32 parent_classid, u64 rate, u64 ceil,
u64 prio, u32 quantum,
struct netlink_ext_ack *extack)
{
struct otx2_qos_cfg *old_cfg, *new_cfg;
struct otx2_qos_node *node, *parent;
int qid, ret, err;
bool static_cfg;
netdev_dbg(pfvf->netdev,
"TC_HTB_LEAF_ALLOC_QUEUE: classid=0x%x parent_classid=0x%x rate=%lld ceil=%lld prio=%lld quantum=%d\n",
classid, parent_classid, rate, ceil, prio, quantum);
if (prio > OTX2_QOS_MAX_PRIO) {
NL_SET_ERR_MSG_MOD(extack, "Valid priority range 0 to 7");
ret = -EOPNOTSUPP;
goto out;
}
if (!quantum || quantum > INT_MAX) {
NL_SET_ERR_MSG_MOD(extack, "Invalid quantum, range 1 - 2147483647 bytes");
ret = -EOPNOTSUPP;
goto out;
}
/* get parent node */
parent = otx2_sw_node_find(pfvf, parent_classid);
if (!parent) {
NL_SET_ERR_MSG_MOD(extack, "parent node not found");
ret = -ENOENT;
goto out;
}
if (parent->level == NIX_TXSCH_LVL_MDQ) {
NL_SET_ERR_MSG_MOD(extack, "HTB qos max levels reached");
ret = -EOPNOTSUPP;
goto out;
}
static_cfg = !is_qos_node_dwrr(parent, pfvf, prio);
ret = otx2_qos_validate_configuration(parent, extack, pfvf, prio,
static_cfg);
if (ret)
goto out;
if (!static_cfg) {
ret = otx2_qos_validate_dwrr_cfg(parent, extack, pfvf, prio,
quantum);
if (ret)
goto out;
}
if (static_cfg)
parent->child_static_cnt++;
else
parent->child_dwrr_cnt++;
set_bit(prio, parent->prio_bmap);
/* read current txschq configuration */
old_cfg = kzalloc(sizeof(*old_cfg), GFP_KERNEL);
if (!old_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
ret = -ENOMEM;
goto reset_prio;
}
otx2_qos_read_txschq_cfg(pfvf, parent, old_cfg);
/* allocate a new sq */
qid = otx2_qos_get_qid(pfvf);
if (qid < 0) {
NL_SET_ERR_MSG_MOD(extack, "Reached max supported QOS SQ's");
ret = -ENOMEM;
goto free_old_cfg;
}
/* Actual SQ mapping will be updated after SMQ alloc */
pfvf->qos.qid_to_sqmap[qid] = OTX2_QOS_INVALID_SQ;
/* allocate and initialize a new child node */
node = otx2_qos_sw_create_leaf_node(pfvf, parent, classid, prio, rate,
ceil, quantum, qid, static_cfg);
if (IS_ERR(node)) {
NL_SET_ERR_MSG_MOD(extack, "Unable to allocate leaf node");
ret = PTR_ERR(node);
goto free_old_cfg;
}
/* push new txschq config to hw */
new_cfg = kzalloc(sizeof(*new_cfg), GFP_KERNEL);
if (!new_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
ret = -ENOMEM;
goto free_node;
}
ret = otx2_qos_update_tree(pfvf, node, new_cfg);
if (ret) {
NL_SET_ERR_MSG_MOD(extack, "HTB HW configuration error");
kfree(new_cfg);
otx2_qos_sw_node_delete(pfvf, node);
/* restore the old qos tree */
err = otx2_qos_txschq_update_config(pfvf, parent, old_cfg);
if (err) {
netdev_err(pfvf->netdev,
"Failed to restore txcshq configuration");
goto free_old_cfg;
}
otx2_qos_update_smq(pfvf, parent, QOS_CFG_SQ);
goto free_old_cfg;
}
/* update tx_real_queues */
otx2_qos_update_tx_netdev_queues(pfvf);
/* free new txschq config */
kfree(new_cfg);
/* free old txschq config */
otx2_qos_free_cfg(pfvf, old_cfg);
kfree(old_cfg);
return pfvf->hw.tx_queues + qid;
free_node:
otx2_qos_sw_node_delete(pfvf, node);
free_old_cfg:
kfree(old_cfg);
reset_prio:
if (static_cfg)
parent->child_static_cnt--;
else
parent->child_dwrr_cnt--;
clear_bit(prio, parent->prio_bmap);
out:
return ret;
}
static int otx2_qos_leaf_to_inner(struct otx2_nic *pfvf, u16 classid,
u16 child_classid, u64 rate, u64 ceil, u64 prio,
u32 quantum, struct netlink_ext_ack *extack)
{
struct otx2_qos_cfg *old_cfg, *new_cfg;
struct otx2_qos_node *node, *child;
bool static_cfg;
int ret, err;
u16 qid;
netdev_dbg(pfvf->netdev,
"TC_HTB_LEAF_TO_INNER classid %04x, child %04x, rate %llu, ceil %llu\n",
classid, child_classid, rate, ceil);
if (prio > OTX2_QOS_MAX_PRIO) {
NL_SET_ERR_MSG_MOD(extack, "Valid priority range 0 to 7");
ret = -EOPNOTSUPP;
goto out;
}
if (!quantum || quantum > INT_MAX) {
NL_SET_ERR_MSG_MOD(extack, "Invalid quantum, range 1 - 2147483647 bytes");
ret = -EOPNOTSUPP;
goto out;
}
/* find node related to classid */
node = otx2_sw_node_find(pfvf, classid);
if (!node) {
NL_SET_ERR_MSG_MOD(extack, "HTB node not found");
ret = -ENOENT;
goto out;
}
/* check max qos txschq level */
if (node->level == NIX_TXSCH_LVL_MDQ) {
NL_SET_ERR_MSG_MOD(extack, "HTB qos level not supported");
ret = -EOPNOTSUPP;
goto out;
}
static_cfg = !is_qos_node_dwrr(node, pfvf, prio);
if (!static_cfg) {
ret = otx2_qos_validate_dwrr_cfg(node, extack, pfvf, prio,
quantum);
if (ret)
goto out;
}
if (static_cfg)
node->child_static_cnt++;
else
node->child_dwrr_cnt++;
set_bit(prio, node->prio_bmap);
/* store the qid to assign to leaf node */
qid = node->qid;
/* read current txschq configuration */
old_cfg = kzalloc(sizeof(*old_cfg), GFP_KERNEL);
if (!old_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
ret = -ENOMEM;
goto reset_prio;
}
otx2_qos_read_txschq_cfg(pfvf, node, old_cfg);
/* delete the txschq nodes allocated for this node */
otx2_qos_free_sw_node_schq(pfvf, node);
/* mark this node as htb inner node */
WRITE_ONCE(node->qid, OTX2_QOS_QID_INNER);
/* allocate and initialize a new child node */
child = otx2_qos_sw_create_leaf_node(pfvf, node, child_classid,
prio, rate, ceil, quantum,
qid, static_cfg);
if (IS_ERR(child)) {
NL_SET_ERR_MSG_MOD(extack, "Unable to allocate leaf node");
ret = PTR_ERR(child);
goto free_old_cfg;
}
/* push new txschq config to hw */
new_cfg = kzalloc(sizeof(*new_cfg), GFP_KERNEL);
if (!new_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
ret = -ENOMEM;
goto free_node;
}
ret = otx2_qos_update_tree(pfvf, child, new_cfg);
if (ret) {
NL_SET_ERR_MSG_MOD(extack, "HTB HW configuration error");
kfree(new_cfg);
otx2_qos_sw_node_delete(pfvf, child);
/* restore the old qos tree */
WRITE_ONCE(node->qid, qid);
err = otx2_qos_alloc_txschq_node(pfvf, node);
if (err) {
netdev_err(pfvf->netdev,
"Failed to restore old leaf node");
goto free_old_cfg;
}
err = otx2_qos_txschq_update_config(pfvf, node, old_cfg);
if (err) {
netdev_err(pfvf->netdev,
"Failed to restore txcshq configuration");
goto free_old_cfg;
}
otx2_qos_update_smq(pfvf, node, QOS_CFG_SQ);
goto free_old_cfg;
}
/* free new txschq config */
kfree(new_cfg);
/* free old txschq config */
otx2_qos_free_cfg(pfvf, old_cfg);
kfree(old_cfg);
return 0;
free_node:
otx2_qos_sw_node_delete(pfvf, child);
free_old_cfg:
kfree(old_cfg);
reset_prio:
if (static_cfg)
node->child_static_cnt--;
else
node->child_dwrr_cnt--;
clear_bit(prio, node->prio_bmap);
out:
return ret;
}
static int otx2_qos_cur_leaf_nodes(struct otx2_nic *pfvf)
{
int last = find_last_bit(pfvf->qos.qos_sq_bmap, pfvf->hw.tc_tx_queues);
return last == pfvf->hw.tc_tx_queues ? 0 : last + 1;
}
static void otx2_reset_qdisc(struct net_device *dev, u16 qid)
{
struct netdev_queue *dev_queue = netdev_get_tx_queue(dev, qid);
struct Qdisc *qdisc = rtnl_dereference(dev_queue->qdisc_sleeping);
if (!qdisc)
return;
spin_lock_bh(qdisc_lock(qdisc));
qdisc_reset(qdisc);
spin_unlock_bh(qdisc_lock(qdisc));
}
static void otx2_cfg_smq(struct otx2_nic *pfvf, struct otx2_qos_node *node,
int qid)
{
struct otx2_qos_node *tmp;
list_for_each_entry(tmp, &node->child_schq_list, list)
if (tmp->level == NIX_TXSCH_LVL_MDQ) {
otx2_qos_txschq_config(pfvf, tmp);
pfvf->qos.qid_to_sqmap[qid] = tmp->schq;
}
}
static int otx2_qos_leaf_del(struct otx2_nic *pfvf, u16 *classid,
struct netlink_ext_ack *extack)
{
struct otx2_qos_node *node, *parent;
int dwrr_del_node = false;
u16 qid, moved_qid;
u64 prio;
netdev_dbg(pfvf->netdev, "TC_HTB_LEAF_DEL classid %04x\n", *classid);
/* find node related to classid */
node = otx2_sw_node_find(pfvf, *classid);
if (!node) {
NL_SET_ERR_MSG_MOD(extack, "HTB node not found");
return -ENOENT;
}
parent = node->parent;
prio = node->prio;
qid = node->qid;
if (!node->is_static)
dwrr_del_node = true;
otx2_qos_disable_sq(pfvf, node->qid);
otx2_qos_destroy_node(pfvf, node);
pfvf->qos.qid_to_sqmap[qid] = OTX2_QOS_INVALID_SQ;
if (dwrr_del_node) {
parent->child_dwrr_cnt--;
} else {
parent->child_static_cnt--;
clear_bit(prio, parent->prio_bmap);
}
/* Reset DWRR priority if all dwrr nodes are deleted */
if (!parent->child_dwrr_cnt)
otx2_reset_dwrr_prio(parent, prio);
if (!parent->child_static_cnt)
parent->max_static_prio = 0;
moved_qid = otx2_qos_cur_leaf_nodes(pfvf);
/* last node just deleted */
if (moved_qid == 0 || moved_qid == qid)
return 0;
moved_qid--;
node = otx2_sw_node_find_by_qid(pfvf, moved_qid);
if (!node)
return 0;
/* stop traffic to the old queue and disable
* SQ associated with it
*/
node->qid = OTX2_QOS_QID_INNER;
__clear_bit(moved_qid, pfvf->qos.qos_sq_bmap);
otx2_qos_disable_sq(pfvf, moved_qid);
otx2_reset_qdisc(pfvf->netdev, pfvf->hw.tx_queues + moved_qid);
/* enable SQ associated with qid and
* update the node
*/
otx2_cfg_smq(pfvf, node, qid);
otx2_qos_enable_sq(pfvf, qid);
__set_bit(qid, pfvf->qos.qos_sq_bmap);
node->qid = qid;
*classid = node->classid;
return 0;
}
static int otx2_qos_leaf_del_last(struct otx2_nic *pfvf, u16 classid, bool force,
struct netlink_ext_ack *extack)
{
struct otx2_qos_node *node, *parent;
struct otx2_qos_cfg *new_cfg;
int dwrr_del_node = false;
u64 prio;
int err;
u16 qid;
netdev_dbg(pfvf->netdev,
"TC_HTB_LEAF_DEL_LAST classid %04x\n", classid);
/* find node related to classid */
node = otx2_sw_node_find(pfvf, classid);
if (!node) {
NL_SET_ERR_MSG_MOD(extack, "HTB node not found");
return -ENOENT;
}
/* save qid for use by parent */
qid = node->qid;
prio = node->prio;
parent = otx2_sw_node_find(pfvf, node->parent->classid);
if (!parent) {
NL_SET_ERR_MSG_MOD(extack, "parent node not found");
return -ENOENT;
}
if (!node->is_static)
dwrr_del_node = true;
/* destroy the leaf node */
otx2_qos_destroy_node(pfvf, node);
pfvf->qos.qid_to_sqmap[qid] = OTX2_QOS_INVALID_SQ;
if (dwrr_del_node) {
parent->child_dwrr_cnt--;
} else {
parent->child_static_cnt--;
clear_bit(prio, parent->prio_bmap);
}
/* Reset DWRR priority if all dwrr nodes are deleted */
if (!parent->child_dwrr_cnt)
otx2_reset_dwrr_prio(parent, prio);
if (!parent->child_static_cnt)
parent->max_static_prio = 0;
/* create downstream txschq entries to parent */
err = otx2_qos_alloc_txschq_node(pfvf, parent);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "HTB failed to create txsch configuration");
return err;
}
WRITE_ONCE(parent->qid, qid);
__set_bit(qid, pfvf->qos.qos_sq_bmap);
/* push new txschq config to hw */
new_cfg = kzalloc(sizeof(*new_cfg), GFP_KERNEL);
if (!new_cfg) {
NL_SET_ERR_MSG_MOD(extack, "Memory allocation error");
return -ENOMEM;
}
/* fill txschq cfg and push txschq cfg to hw */
otx2_qos_fill_cfg_schq(parent, new_cfg);
err = otx2_qos_push_txschq_cfg(pfvf, parent, new_cfg);
if (err) {
NL_SET_ERR_MSG_MOD(extack, "HTB HW configuration error");
kfree(new_cfg);
return err;
}
kfree(new_cfg);
/* update tx_real_queues */
otx2_qos_update_tx_netdev_queues(pfvf);
return 0;
}
void otx2_clean_qos_queues(struct otx2_nic *pfvf)
{
struct otx2_qos_node *root;
root = otx2_sw_node_find(pfvf, OTX2_QOS_ROOT_CLASSID);
if (!root)
return;
otx2_qos_update_smq(pfvf, root, QOS_SMQ_FLUSH);
}
void otx2_qos_config_txschq(struct otx2_nic *pfvf)
{
struct otx2_qos_node *root;
int err;
root = otx2_sw_node_find(pfvf, OTX2_QOS_ROOT_CLASSID);
if (!root)
return;
if (root->level != NIX_TXSCH_LVL_TL1) {
err = otx2_qos_txschq_config(pfvf, root);
if (err) {
netdev_err(pfvf->netdev, "Error update txschq configuration\n");
goto root_destroy;
}
}
err = otx2_qos_txschq_push_cfg_tl(pfvf, root, NULL);
if (err) {
netdev_err(pfvf->netdev, "Error update txschq configuration\n");
goto root_destroy;
}
otx2_qos_update_smq(pfvf, root, QOS_CFG_SQ);
return;
root_destroy:
netdev_err(pfvf->netdev, "Failed to update Scheduler/Shaping config in Hardware\n");
/* Free resources allocated */
otx2_qos_root_destroy(pfvf);
}
int otx2_setup_tc_htb(struct net_device *ndev, struct tc_htb_qopt_offload *htb)
{
struct otx2_nic *pfvf = netdev_priv(ndev);
int res;
switch (htb->command) {
case TC_HTB_CREATE:
return otx2_qos_root_add(pfvf, htb->parent_classid,
htb->classid, htb->extack);
case TC_HTB_DESTROY:
return otx2_qos_root_destroy(pfvf);
case TC_HTB_LEAF_ALLOC_QUEUE:
res = otx2_qos_leaf_alloc_queue(pfvf, htb->classid,
htb->parent_classid,
htb->rate, htb->ceil,
htb->prio, htb->quantum,
htb->extack);
if (res < 0)
return res;
htb->qid = res;
return 0;
case TC_HTB_LEAF_TO_INNER:
return otx2_qos_leaf_to_inner(pfvf, htb->parent_classid,
htb->classid, htb->rate,
htb->ceil, htb->prio,
htb->quantum, htb->extack);
case TC_HTB_LEAF_DEL:
return otx2_qos_leaf_del(pfvf, &htb->classid, htb->extack);
case TC_HTB_LEAF_DEL_LAST:
case TC_HTB_LEAF_DEL_LAST_FORCE:
return otx2_qos_leaf_del_last(pfvf, htb->classid,
htb->command == TC_HTB_LEAF_DEL_LAST_FORCE,
htb->extack);
case TC_HTB_LEAF_QUERY_QUEUE:
res = otx2_get_txq_by_classid(pfvf, htb->classid);
htb->qid = res;
return 0;
case TC_HTB_NODE_MODIFY:
fallthrough;
default:
return -EOPNOTSUPP;
}
}