| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip Sparx5 Switch driver |
| * |
| * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include <net/pkt_cls.h> |
| #include <net/pkt_sched.h> |
| |
| #include "sparx5_tc.h" |
| #include "sparx5_main.h" |
| #include "sparx5_qos.h" |
| |
| /* tc block handling */ |
| static LIST_HEAD(sparx5_block_cb_list); |
| |
| static int sparx5_tc_block_cb(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv, bool ingress) |
| { |
| struct net_device *ndev = cb_priv; |
| |
| switch (type) { |
| case TC_SETUP_CLSMATCHALL: |
| return sparx5_tc_matchall(ndev, type_data, ingress); |
| case TC_SETUP_CLSFLOWER: |
| return sparx5_tc_flower(ndev, type_data, ingress); |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int sparx5_tc_block_cb_ingress(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv) |
| { |
| return sparx5_tc_block_cb(type, type_data, cb_priv, true); |
| } |
| |
| static int sparx5_tc_block_cb_egress(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv) |
| { |
| return sparx5_tc_block_cb(type, type_data, cb_priv, false); |
| } |
| |
| static int sparx5_tc_setup_block(struct net_device *ndev, |
| struct flow_block_offload *fbo) |
| { |
| flow_setup_cb_t *cb; |
| |
| if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS) |
| cb = sparx5_tc_block_cb_ingress; |
| else if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS) |
| cb = sparx5_tc_block_cb_egress; |
| else |
| return -EOPNOTSUPP; |
| |
| return flow_block_cb_setup_simple(fbo, &sparx5_block_cb_list, |
| cb, ndev, ndev, false); |
| } |
| |
| static void sparx5_tc_get_layer_and_idx(u32 parent, u32 portno, u32 *layer, |
| u32 *idx) |
| { |
| if (parent == TC_H_ROOT) { |
| *layer = 2; |
| *idx = portno; |
| } else { |
| u32 queue = TC_H_MIN(parent) - 1; |
| *layer = 0; |
| *idx = SPX5_HSCH_L0_GET_IDX(portno, queue); |
| } |
| } |
| |
| static int sparx5_tc_setup_qdisc_mqprio(struct net_device *ndev, |
| struct tc_mqprio_qopt_offload *m) |
| { |
| m->qopt.hw = TC_MQPRIO_HW_OFFLOAD_TCS; |
| |
| if (m->qopt.num_tc == 0) |
| return sparx5_tc_mqprio_del(ndev); |
| else |
| return sparx5_tc_mqprio_add(ndev, m->qopt.num_tc); |
| } |
| |
| static int sparx5_tc_setup_qdisc_tbf(struct net_device *ndev, |
| struct tc_tbf_qopt_offload *qopt) |
| { |
| struct sparx5_port *port = netdev_priv(ndev); |
| u32 layer, se_idx; |
| |
| sparx5_tc_get_layer_and_idx(qopt->parent, port->portno, &layer, |
| &se_idx); |
| |
| switch (qopt->command) { |
| case TC_TBF_REPLACE: |
| return sparx5_tc_tbf_add(port, &qopt->replace_params, layer, |
| se_idx); |
| case TC_TBF_DESTROY: |
| return sparx5_tc_tbf_del(port, layer, se_idx); |
| case TC_TBF_STATS: |
| return -EOPNOTSUPP; |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return -EOPNOTSUPP; |
| } |
| |
| static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev, |
| struct tc_ets_qopt_offload *qopt) |
| { |
| struct tc_ets_qopt_offload_replace_params *params = |
| &qopt->replace_params; |
| struct sparx5_port *port = netdev_priv(ndev); |
| int i; |
| |
| /* Only allow ets on ports */ |
| if (qopt->parent != TC_H_ROOT) |
| return -EOPNOTSUPP; |
| |
| switch (qopt->command) { |
| case TC_ETS_REPLACE: |
| |
| /* We support eight priorities */ |
| if (params->bands != SPX5_PRIOS) |
| return -EOPNOTSUPP; |
| |
| /* Sanity checks */ |
| for (i = 0; i < SPX5_PRIOS; ++i) { |
| /* Priority map is *always* reverse e.g: 7 6 5 .. 0 */ |
| if (params->priomap[i] != (7 - i)) |
| return -EOPNOTSUPP; |
| /* Throw an error if we receive zero weights by tc */ |
| if (params->quanta[i] && params->weights[i] == 0) { |
| pr_err("Invalid ets configuration; band %d has weight zero", |
| i); |
| return -EINVAL; |
| } |
| } |
| |
| return sparx5_tc_ets_add(port, params); |
| case TC_ETS_DESTROY: |
| |
| return sparx5_tc_ets_del(port); |
| case TC_ETS_GRAFT: |
| return -EOPNOTSUPP; |
| |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return -EOPNOTSUPP; |
| } |
| |
| int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type, |
| void *type_data) |
| { |
| switch (type) { |
| case TC_SETUP_BLOCK: |
| return sparx5_tc_setup_block(ndev, type_data); |
| case TC_SETUP_QDISC_MQPRIO: |
| return sparx5_tc_setup_qdisc_mqprio(ndev, type_data); |
| case TC_SETUP_QDISC_TBF: |
| return sparx5_tc_setup_qdisc_tbf(ndev, type_data); |
| case TC_SETUP_QDISC_ETS: |
| return sparx5_tc_setup_qdisc_ets(ndev, type_data); |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |