1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Microchip Sparx5 Switch driver |
3 | * |
4 | * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. |
5 | */ |
6 | |
7 | #include <net/pkt_cls.h> |
8 | #include <net/pkt_sched.h> |
9 | |
10 | #include "sparx5_tc.h" |
11 | #include "sparx5_main.h" |
12 | #include "sparx5_qos.h" |
13 | |
14 | /* tc block handling */ |
15 | static LIST_HEAD(sparx5_block_cb_list); |
16 | |
17 | static int sparx5_tc_block_cb(enum tc_setup_type type, |
18 | void *type_data, |
19 | void *cb_priv, bool ingress) |
20 | { |
21 | struct net_device *ndev = cb_priv; |
22 | |
23 | switch (type) { |
24 | case TC_SETUP_CLSMATCHALL: |
25 | return sparx5_tc_matchall(ndev, tmo: type_data, ingress); |
26 | case TC_SETUP_CLSFLOWER: |
27 | return sparx5_tc_flower(ndev, fco: type_data, ingress); |
28 | default: |
29 | return -EOPNOTSUPP; |
30 | } |
31 | } |
32 | |
33 | static int sparx5_tc_block_cb_ingress(enum tc_setup_type type, |
34 | void *type_data, |
35 | void *cb_priv) |
36 | { |
37 | return sparx5_tc_block_cb(type, type_data, cb_priv, ingress: true); |
38 | } |
39 | |
40 | static int sparx5_tc_block_cb_egress(enum tc_setup_type type, |
41 | void *type_data, |
42 | void *cb_priv) |
43 | { |
44 | return sparx5_tc_block_cb(type, type_data, cb_priv, ingress: false); |
45 | } |
46 | |
47 | static int sparx5_tc_setup_block(struct net_device *ndev, |
48 | struct flow_block_offload *fbo) |
49 | { |
50 | flow_setup_cb_t *cb; |
51 | |
52 | if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS) |
53 | cb = sparx5_tc_block_cb_ingress; |
54 | else if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS) |
55 | cb = sparx5_tc_block_cb_egress; |
56 | else |
57 | return -EOPNOTSUPP; |
58 | |
59 | return flow_block_cb_setup_simple(f: fbo, driver_list: &sparx5_block_cb_list, |
60 | cb, cb_ident: ndev, cb_priv: ndev, ingress_only: false); |
61 | } |
62 | |
63 | static void sparx5_tc_get_layer_and_idx(u32 parent, u32 portno, u32 *layer, |
64 | u32 *idx) |
65 | { |
66 | if (parent == TC_H_ROOT) { |
67 | *layer = 2; |
68 | *idx = portno; |
69 | } else { |
70 | u32 queue = TC_H_MIN(parent) - 1; |
71 | *layer = 0; |
72 | *idx = SPX5_HSCH_L0_GET_IDX(portno, queue); |
73 | } |
74 | } |
75 | |
76 | static int sparx5_tc_setup_qdisc_mqprio(struct net_device *ndev, |
77 | struct tc_mqprio_qopt_offload *m) |
78 | { |
79 | m->qopt.hw = TC_MQPRIO_HW_OFFLOAD_TCS; |
80 | |
81 | if (m->qopt.num_tc == 0) |
82 | return sparx5_tc_mqprio_del(ndev); |
83 | else |
84 | return sparx5_tc_mqprio_add(ndev, num_tc: m->qopt.num_tc); |
85 | } |
86 | |
87 | static int sparx5_tc_setup_qdisc_tbf(struct net_device *ndev, |
88 | struct tc_tbf_qopt_offload *qopt) |
89 | { |
90 | struct sparx5_port *port = netdev_priv(dev: ndev); |
91 | u32 layer, se_idx; |
92 | |
93 | sparx5_tc_get_layer_and_idx(parent: qopt->parent, portno: port->portno, layer: &layer, |
94 | idx: &se_idx); |
95 | |
96 | switch (qopt->command) { |
97 | case TC_TBF_REPLACE: |
98 | return sparx5_tc_tbf_add(port, params: &qopt->replace_params, layer, |
99 | idx: se_idx); |
100 | case TC_TBF_DESTROY: |
101 | return sparx5_tc_tbf_del(port, layer, idx: se_idx); |
102 | case TC_TBF_STATS: |
103 | return -EOPNOTSUPP; |
104 | default: |
105 | return -EOPNOTSUPP; |
106 | } |
107 | |
108 | return -EOPNOTSUPP; |
109 | } |
110 | |
111 | static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev, |
112 | struct tc_ets_qopt_offload *qopt) |
113 | { |
114 | struct tc_ets_qopt_offload_replace_params *params = |
115 | &qopt->replace_params; |
116 | struct sparx5_port *port = netdev_priv(dev: ndev); |
117 | int i; |
118 | |
119 | /* Only allow ets on ports */ |
120 | if (qopt->parent != TC_H_ROOT) |
121 | return -EOPNOTSUPP; |
122 | |
123 | switch (qopt->command) { |
124 | case TC_ETS_REPLACE: |
125 | |
126 | /* We support eight priorities */ |
127 | if (params->bands != SPX5_PRIOS) |
128 | return -EOPNOTSUPP; |
129 | |
130 | /* Sanity checks */ |
131 | for (i = 0; i < SPX5_PRIOS; ++i) { |
132 | /* Priority map is *always* reverse e.g: 7 6 5 .. 0 */ |
133 | if (params->priomap[i] != (7 - i)) |
134 | return -EOPNOTSUPP; |
135 | /* Throw an error if we receive zero weights by tc */ |
136 | if (params->quanta[i] && params->weights[i] == 0) { |
137 | pr_err("Invalid ets configuration; band %d has weight zero" , |
138 | i); |
139 | return -EINVAL; |
140 | } |
141 | } |
142 | |
143 | return sparx5_tc_ets_add(port, params); |
144 | case TC_ETS_DESTROY: |
145 | |
146 | return sparx5_tc_ets_del(port); |
147 | case TC_ETS_GRAFT: |
148 | return -EOPNOTSUPP; |
149 | |
150 | default: |
151 | return -EOPNOTSUPP; |
152 | } |
153 | |
154 | return -EOPNOTSUPP; |
155 | } |
156 | |
157 | int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type, |
158 | void *type_data) |
159 | { |
160 | switch (type) { |
161 | case TC_SETUP_BLOCK: |
162 | return sparx5_tc_setup_block(ndev, fbo: type_data); |
163 | case TC_SETUP_QDISC_MQPRIO: |
164 | return sparx5_tc_setup_qdisc_mqprio(ndev, m: type_data); |
165 | case TC_SETUP_QDISC_TBF: |
166 | return sparx5_tc_setup_qdisc_tbf(ndev, qopt: type_data); |
167 | case TC_SETUP_QDISC_ETS: |
168 | return sparx5_tc_setup_qdisc_ets(ndev, qopt: type_data); |
169 | default: |
170 | return -EOPNOTSUPP; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |