blob: 1e77bbf5d22a1a193602b199c90d205e219595b0 [file] [log] [blame]
// 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_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(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_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_leaf_del(struct otx2_nic *pfvf, u16 *classid,
struct netlink_ext_ack *extack)
{
struct otx2_qos_node *node, *parent;
int dwrr_del_node = false;
u64 prio;
u16 qid;
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;
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;
}
}