Commit | Line | Data |
---|---|---|
9948a064 JP |
1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2017-2018 Mellanox Technologies. All rights reserved */ | |
96f17e07 NF |
3 | |
4 | #include <linux/kernel.h> | |
5 | #include <linux/errno.h> | |
6 | #include <linux/netdevice.h> | |
7 | #include <net/pkt_cls.h> | |
861fb829 | 8 | #include <net/red.h> |
96f17e07 NF |
9 | |
10 | #include "spectrum.h" | |
11 | #include "reg.h" | |
12 | ||
46a3615b | 13 | #define MLXSW_SP_PRIO_BAND_TO_TCLASS(band) (IEEE_8021QAZ_MAX_TCS - band - 1) |
eed4baeb NF |
14 | #define MLXSW_SP_PRIO_CHILD_TO_TCLASS(child) \ |
15 | MLXSW_SP_PRIO_BAND_TO_TCLASS((child - 1)) | |
46a3615b | 16 | |
371b437a NF |
17 | enum mlxsw_sp_qdisc_type { |
18 | MLXSW_SP_QDISC_NO_QDISC, | |
19 | MLXSW_SP_QDISC_RED, | |
46a3615b | 20 | MLXSW_SP_QDISC_PRIO, |
371b437a NF |
21 | }; |
22 | ||
562ffbc4 | 23 | struct mlxsw_sp_qdisc_ops { |
9cf6c9c7 NF |
24 | enum mlxsw_sp_qdisc_type type; |
25 | int (*check_params)(struct mlxsw_sp_port *mlxsw_sp_port, | |
26 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
27 | void *params); | |
28 | int (*replace)(struct mlxsw_sp_port *mlxsw_sp_port, | |
29 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, void *params); | |
9a37a59f NF |
30 | int (*destroy)(struct mlxsw_sp_port *mlxsw_sp_port, |
31 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc); | |
562ffbc4 NF |
32 | int (*get_stats)(struct mlxsw_sp_port *mlxsw_sp_port, |
33 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
34 | struct tc_qopt_offload_stats *stats_ptr); | |
35 | int (*get_xstats)(struct mlxsw_sp_port *mlxsw_sp_port, | |
36 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
37 | void *xstats_ptr); | |
9cf6c9c7 NF |
38 | void (*clean_stats)(struct mlxsw_sp_port *mlxsw_sp_port, |
39 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc); | |
93d8a4c1 NF |
40 | /* unoffload - to be used for a qdisc that stops being offloaded without |
41 | * being destroyed. | |
42 | */ | |
43 | void (*unoffload)(struct mlxsw_sp_port *mlxsw_sp_port, | |
44 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, void *params); | |
562ffbc4 NF |
45 | }; |
46 | ||
371b437a NF |
47 | struct mlxsw_sp_qdisc { |
48 | u32 handle; | |
d56c8955 | 49 | u8 tclass_num; |
1631ab2e | 50 | u8 prio_bitmap; |
371b437a | 51 | union { |
4d1a4b84 NF |
52 | struct red_stats red; |
53 | } xstats_base; | |
54 | struct mlxsw_sp_qdisc_stats { | |
55 | u64 tx_bytes; | |
56 | u64 tx_packets; | |
57 | u64 drops; | |
58 | u64 overlimits; | |
93d8a4c1 | 59 | u64 backlog; |
4d1a4b84 | 60 | } stats_base; |
562ffbc4 NF |
61 | |
62 | struct mlxsw_sp_qdisc_ops *ops; | |
371b437a NF |
63 | }; |
64 | ||
cba7158f NF |
65 | static bool |
66 | mlxsw_sp_qdisc_compare(struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, u32 handle, | |
67 | enum mlxsw_sp_qdisc_type type) | |
68 | { | |
9cf6c9c7 NF |
69 | return mlxsw_sp_qdisc && mlxsw_sp_qdisc->ops && |
70 | mlxsw_sp_qdisc->ops->type == type && | |
71 | mlxsw_sp_qdisc->handle == handle; | |
cba7158f NF |
72 | } |
73 | ||
eed4baeb NF |
74 | static struct mlxsw_sp_qdisc * |
75 | mlxsw_sp_qdisc_find(struct mlxsw_sp_port *mlxsw_sp_port, u32 parent, | |
76 | bool root_only) | |
77 | { | |
78 | int tclass, child_index; | |
79 | ||
80 | if (parent == TC_H_ROOT) | |
81 | return mlxsw_sp_port->root_qdisc; | |
82 | ||
83 | if (root_only || !mlxsw_sp_port->root_qdisc || | |
84 | !mlxsw_sp_port->root_qdisc->ops || | |
85 | TC_H_MAJ(parent) != mlxsw_sp_port->root_qdisc->handle || | |
86 | TC_H_MIN(parent) > IEEE_8021QAZ_MAX_TCS) | |
87 | return NULL; | |
88 | ||
89 | child_index = TC_H_MIN(parent); | |
90 | tclass = MLXSW_SP_PRIO_CHILD_TO_TCLASS(child_index); | |
91 | return &mlxsw_sp_port->tclass_qdiscs[tclass]; | |
92 | } | |
93 | ||
32dc5efc NF |
94 | static struct mlxsw_sp_qdisc * |
95 | mlxsw_sp_qdisc_find_by_handle(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle) | |
96 | { | |
97 | int i; | |
98 | ||
99 | if (mlxsw_sp_port->root_qdisc->handle == handle) | |
100 | return mlxsw_sp_port->root_qdisc; | |
101 | ||
102 | if (mlxsw_sp_port->root_qdisc->handle == TC_H_UNSPEC) | |
103 | return NULL; | |
104 | ||
105 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) | |
106 | if (mlxsw_sp_port->tclass_qdiscs[i].handle == handle) | |
107 | return &mlxsw_sp_port->tclass_qdiscs[i]; | |
108 | ||
109 | return NULL; | |
110 | } | |
111 | ||
9a37a59f NF |
112 | static int |
113 | mlxsw_sp_qdisc_destroy(struct mlxsw_sp_port *mlxsw_sp_port, | |
114 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc) | |
115 | { | |
116 | int err = 0; | |
117 | ||
118 | if (!mlxsw_sp_qdisc) | |
119 | return 0; | |
120 | ||
121 | if (mlxsw_sp_qdisc->ops && mlxsw_sp_qdisc->ops->destroy) | |
122 | err = mlxsw_sp_qdisc->ops->destroy(mlxsw_sp_port, | |
123 | mlxsw_sp_qdisc); | |
124 | ||
125 | mlxsw_sp_qdisc->handle = TC_H_UNSPEC; | |
9a37a59f NF |
126 | mlxsw_sp_qdisc->ops = NULL; |
127 | return err; | |
128 | } | |
129 | ||
9cf6c9c7 NF |
130 | static int |
131 | mlxsw_sp_qdisc_replace(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, | |
132 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
133 | struct mlxsw_sp_qdisc_ops *ops, void *params) | |
134 | { | |
135 | int err; | |
136 | ||
56202ca4 NF |
137 | if (mlxsw_sp_qdisc->ops && mlxsw_sp_qdisc->ops->type != ops->type) |
138 | /* In case this location contained a different qdisc of the | |
139 | * same type we can override the old qdisc configuration. | |
140 | * Otherwise, we need to remove the old qdisc before setting the | |
141 | * new one. | |
142 | */ | |
143 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, mlxsw_sp_qdisc); | |
9cf6c9c7 NF |
144 | err = ops->check_params(mlxsw_sp_port, mlxsw_sp_qdisc, params); |
145 | if (err) | |
146 | goto err_bad_param; | |
147 | ||
148 | err = ops->replace(mlxsw_sp_port, mlxsw_sp_qdisc, params); | |
149 | if (err) | |
150 | goto err_config; | |
151 | ||
152 | if (mlxsw_sp_qdisc->handle != handle) { | |
153 | mlxsw_sp_qdisc->ops = ops; | |
154 | if (ops->clean_stats) | |
155 | ops->clean_stats(mlxsw_sp_port, mlxsw_sp_qdisc); | |
156 | } | |
157 | ||
158 | mlxsw_sp_qdisc->handle = handle; | |
159 | return 0; | |
160 | ||
161 | err_bad_param: | |
162 | err_config: | |
93d8a4c1 NF |
163 | if (mlxsw_sp_qdisc->handle == handle && ops->unoffload) |
164 | ops->unoffload(mlxsw_sp_port, mlxsw_sp_qdisc, params); | |
165 | ||
9cf6c9c7 NF |
166 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, mlxsw_sp_qdisc); |
167 | return err; | |
168 | } | |
169 | ||
562ffbc4 NF |
170 | static int |
171 | mlxsw_sp_qdisc_get_stats(struct mlxsw_sp_port *mlxsw_sp_port, | |
172 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
173 | struct tc_qopt_offload_stats *stats_ptr) | |
174 | { | |
175 | if (mlxsw_sp_qdisc && mlxsw_sp_qdisc->ops && | |
176 | mlxsw_sp_qdisc->ops->get_stats) | |
177 | return mlxsw_sp_qdisc->ops->get_stats(mlxsw_sp_port, | |
178 | mlxsw_sp_qdisc, | |
179 | stats_ptr); | |
180 | ||
181 | return -EOPNOTSUPP; | |
182 | } | |
183 | ||
184 | static int | |
185 | mlxsw_sp_qdisc_get_xstats(struct mlxsw_sp_port *mlxsw_sp_port, | |
186 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
187 | void *xstats_ptr) | |
188 | { | |
189 | if (mlxsw_sp_qdisc && mlxsw_sp_qdisc->ops && | |
190 | mlxsw_sp_qdisc->ops->get_xstats) | |
191 | return mlxsw_sp_qdisc->ops->get_xstats(mlxsw_sp_port, | |
192 | mlxsw_sp_qdisc, | |
193 | xstats_ptr); | |
194 | ||
195 | return -EOPNOTSUPP; | |
196 | } | |
197 | ||
04cc0bf5 NF |
198 | static void |
199 | mlxsw_sp_qdisc_bstats_per_priority_get(struct mlxsw_sp_port_xstats *xstats, | |
200 | u8 prio_bitmap, u64 *tx_packets, | |
201 | u64 *tx_bytes) | |
202 | { | |
203 | int i; | |
204 | ||
205 | *tx_packets = 0; | |
206 | *tx_bytes = 0; | |
207 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { | |
208 | if (prio_bitmap & BIT(i)) { | |
209 | *tx_packets += xstats->tx_packets[i]; | |
210 | *tx_bytes += xstats->tx_bytes[i]; | |
211 | } | |
212 | } | |
213 | } | |
214 | ||
96f17e07 NF |
215 | static int |
216 | mlxsw_sp_tclass_congestion_enable(struct mlxsw_sp_port *mlxsw_sp_port, | |
217 | int tclass_num, u32 min, u32 max, | |
218 | u32 probability, bool is_ecn) | |
219 | { | |
db84924c JP |
220 | char cwtpm_cmd[MLXSW_REG_CWTPM_LEN]; |
221 | char cwtp_cmd[MLXSW_REG_CWTP_LEN]; | |
96f17e07 NF |
222 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
223 | int err; | |
224 | ||
225 | mlxsw_reg_cwtp_pack(cwtp_cmd, mlxsw_sp_port->local_port, tclass_num); | |
226 | mlxsw_reg_cwtp_profile_pack(cwtp_cmd, MLXSW_REG_CWTP_DEFAULT_PROFILE, | |
227 | roundup(min, MLXSW_REG_CWTP_MIN_VALUE), | |
228 | roundup(max, MLXSW_REG_CWTP_MIN_VALUE), | |
229 | probability); | |
230 | ||
231 | err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtp), cwtp_cmd); | |
232 | if (err) | |
233 | return err; | |
234 | ||
db84924c | 235 | mlxsw_reg_cwtpm_pack(cwtpm_cmd, mlxsw_sp_port->local_port, tclass_num, |
96f17e07 NF |
236 | MLXSW_REG_CWTP_DEFAULT_PROFILE, true, is_ecn); |
237 | ||
db84924c | 238 | return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtpm_cmd); |
96f17e07 NF |
239 | } |
240 | ||
241 | static int | |
242 | mlxsw_sp_tclass_congestion_disable(struct mlxsw_sp_port *mlxsw_sp_port, | |
243 | int tclass_num) | |
244 | { | |
245 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | |
246 | char cwtpm_cmd[MLXSW_REG_CWTPM_LEN]; | |
247 | ||
248 | mlxsw_reg_cwtpm_pack(cwtpm_cmd, mlxsw_sp_port->local_port, tclass_num, | |
249 | MLXSW_REG_CWTPM_RESET_PROFILE, false, false); | |
250 | return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtpm_cmd); | |
251 | } | |
252 | ||
861fb829 | 253 | static void |
c2ed6db7 | 254 | mlxsw_sp_setup_tc_qdisc_red_clean_stats(struct mlxsw_sp_port *mlxsw_sp_port, |
d56c8955 | 255 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc) |
861fb829 | 256 | { |
d56c8955 | 257 | u8 tclass_num = mlxsw_sp_qdisc->tclass_num; |
4d1a4b84 | 258 | struct mlxsw_sp_qdisc_stats *stats_base; |
861fb829 | 259 | struct mlxsw_sp_port_xstats *xstats; |
4d1a4b84 | 260 | struct red_stats *red_base; |
861fb829 NF |
261 | |
262 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
4d1a4b84 | 263 | stats_base = &mlxsw_sp_qdisc->stats_base; |
c2ed6db7 | 264 | red_base = &mlxsw_sp_qdisc->xstats_base.red; |
3670756f | 265 | |
04cc0bf5 NF |
266 | mlxsw_sp_qdisc_bstats_per_priority_get(xstats, |
267 | mlxsw_sp_qdisc->prio_bitmap, | |
268 | &stats_base->tx_packets, | |
269 | &stats_base->tx_bytes); | |
c2ed6db7 NF |
270 | red_base->prob_mark = xstats->ecn; |
271 | red_base->prob_drop = xstats->wred_drop[tclass_num]; | |
272 | red_base->pdrop = xstats->tail_drop[tclass_num]; | |
273 | ||
274 | stats_base->overlimits = red_base->prob_drop + red_base->prob_mark; | |
275 | stats_base->drops = red_base->prob_drop + red_base->pdrop; | |
416ef9b1 JK |
276 | |
277 | stats_base->backlog = 0; | |
861fb829 NF |
278 | } |
279 | ||
96f17e07 | 280 | static int |
cba7158f | 281 | mlxsw_sp_qdisc_red_destroy(struct mlxsw_sp_port *mlxsw_sp_port, |
d56c8955 | 282 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc) |
96f17e07 | 283 | { |
cc6e5c13 NF |
284 | struct mlxsw_sp_qdisc *root_qdisc = mlxsw_sp_port->root_qdisc; |
285 | ||
286 | if (root_qdisc != mlxsw_sp_qdisc) | |
287 | root_qdisc->stats_base.backlog -= | |
288 | mlxsw_sp_qdisc->stats_base.backlog; | |
289 | ||
9a37a59f NF |
290 | return mlxsw_sp_tclass_congestion_disable(mlxsw_sp_port, |
291 | mlxsw_sp_qdisc->tclass_num); | |
96f17e07 NF |
292 | } |
293 | ||
294 | static int | |
9cf6c9c7 NF |
295 | mlxsw_sp_qdisc_red_check_params(struct mlxsw_sp_port *mlxsw_sp_port, |
296 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
297 | void *params) | |
96f17e07 NF |
298 | { |
299 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | |
9cf6c9c7 | 300 | struct tc_red_qopt_offload_params *p = params; |
96f17e07 NF |
301 | |
302 | if (p->min > p->max) { | |
303 | dev_err(mlxsw_sp->bus_info->dev, | |
304 | "spectrum: RED: min %u is bigger then max %u\n", p->min, | |
305 | p->max); | |
9cf6c9c7 | 306 | return -EINVAL; |
96f17e07 | 307 | } |
914c4fc1 PM |
308 | if (p->max > MLXSW_CORE_RES_GET(mlxsw_sp->core, |
309 | GUARANTEED_SHARED_BUFFER)) { | |
96f17e07 NF |
310 | dev_err(mlxsw_sp->bus_info->dev, |
311 | "spectrum: RED: max value %u is too big\n", p->max); | |
9cf6c9c7 | 312 | return -EINVAL; |
96f17e07 NF |
313 | } |
314 | if (p->min == 0 || p->max == 0) { | |
315 | dev_err(mlxsw_sp->bus_info->dev, | |
316 | "spectrum: RED: 0 value is illegal for min and max\n"); | |
9cf6c9c7 | 317 | return -EINVAL; |
96f17e07 | 318 | } |
9cf6c9c7 NF |
319 | return 0; |
320 | } | |
321 | ||
322 | static int | |
323 | mlxsw_sp_qdisc_red_replace(struct mlxsw_sp_port *mlxsw_sp_port, | |
324 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
325 | void *params) | |
326 | { | |
327 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | |
328 | struct tc_red_qopt_offload_params *p = params; | |
329 | u8 tclass_num = mlxsw_sp_qdisc->tclass_num; | |
330 | u32 min, max; | |
331 | u64 prob; | |
96f17e07 NF |
332 | |
333 | /* calculate probability in percentage */ | |
334 | prob = p->probability; | |
335 | prob *= 100; | |
336 | prob = DIV_ROUND_UP(prob, 1 << 16); | |
337 | prob = DIV_ROUND_UP(prob, 1 << 16); | |
338 | min = mlxsw_sp_bytes_cells(mlxsw_sp, p->min); | |
339 | max = mlxsw_sp_bytes_cells(mlxsw_sp, p->max); | |
9cf6c9c7 NF |
340 | return mlxsw_sp_tclass_congestion_enable(mlxsw_sp_port, tclass_num, min, |
341 | max, prob, p->is_ecn); | |
96f17e07 NF |
342 | } |
343 | ||
416ef9b1 JK |
344 | static void |
345 | mlxsw_sp_qdisc_red_unoffload(struct mlxsw_sp_port *mlxsw_sp_port, | |
346 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
347 | void *params) | |
348 | { | |
349 | struct tc_red_qopt_offload_params *p = params; | |
350 | u64 backlog; | |
351 | ||
352 | backlog = mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
353 | mlxsw_sp_qdisc->stats_base.backlog); | |
354 | p->qstats->backlog -= backlog; | |
cc6e5c13 | 355 | mlxsw_sp_qdisc->stats_base.backlog = 0; |
416ef9b1 JK |
356 | } |
357 | ||
861fb829 | 358 | static int |
cba7158f | 359 | mlxsw_sp_qdisc_get_red_xstats(struct mlxsw_sp_port *mlxsw_sp_port, |
861fb829 | 360 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, |
562ffbc4 | 361 | void *xstats_ptr) |
861fb829 | 362 | { |
4d1a4b84 | 363 | struct red_stats *xstats_base = &mlxsw_sp_qdisc->xstats_base.red; |
d56c8955 | 364 | u8 tclass_num = mlxsw_sp_qdisc->tclass_num; |
861fb829 | 365 | struct mlxsw_sp_port_xstats *xstats; |
562ffbc4 | 366 | struct red_stats *res = xstats_ptr; |
f8253df5 | 367 | int early_drops, marks, pdrops; |
861fb829 | 368 | |
861fb829 NF |
369 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; |
370 | ||
f8253df5 NF |
371 | early_drops = xstats->wred_drop[tclass_num] - xstats_base->prob_drop; |
372 | marks = xstats->ecn - xstats_base->prob_mark; | |
373 | pdrops = xstats->tail_drop[tclass_num] - xstats_base->pdrop; | |
374 | ||
375 | res->pdrop += pdrops; | |
376 | res->prob_drop += early_drops; | |
377 | res->prob_mark += marks; | |
378 | ||
379 | xstats_base->pdrop += pdrops; | |
380 | xstats_base->prob_drop += early_drops; | |
381 | xstats_base->prob_mark += marks; | |
861fb829 NF |
382 | return 0; |
383 | } | |
384 | ||
3670756f | 385 | static int |
cba7158f | 386 | mlxsw_sp_qdisc_get_red_stats(struct mlxsw_sp_port *mlxsw_sp_port, |
3670756f | 387 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, |
562ffbc4 | 388 | struct tc_qopt_offload_stats *stats_ptr) |
3670756f | 389 | { |
416ef9b1 | 390 | u64 tx_bytes, tx_packets, overlimits, drops, backlog; |
d56c8955 | 391 | u8 tclass_num = mlxsw_sp_qdisc->tclass_num; |
4d1a4b84 | 392 | struct mlxsw_sp_qdisc_stats *stats_base; |
3670756f | 393 | struct mlxsw_sp_port_xstats *xstats; |
3670756f | 394 | |
3670756f | 395 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; |
4d1a4b84 | 396 | stats_base = &mlxsw_sp_qdisc->stats_base; |
3670756f | 397 | |
04cc0bf5 NF |
398 | mlxsw_sp_qdisc_bstats_per_priority_get(xstats, |
399 | mlxsw_sp_qdisc->prio_bitmap, | |
400 | &tx_packets, &tx_bytes); | |
401 | tx_bytes = tx_bytes - stats_base->tx_bytes; | |
402 | tx_packets = tx_packets - stats_base->tx_packets; | |
403 | ||
3670756f | 404 | overlimits = xstats->wred_drop[tclass_num] + xstats->ecn - |
4d1a4b84 | 405 | stats_base->overlimits; |
3670756f | 406 | drops = xstats->wred_drop[tclass_num] + xstats->tail_drop[tclass_num] - |
4d1a4b84 | 407 | stats_base->drops; |
416ef9b1 | 408 | backlog = xstats->backlog[tclass_num]; |
3670756f | 409 | |
562ffbc4 NF |
410 | _bstats_update(stats_ptr->bstats, tx_bytes, tx_packets); |
411 | stats_ptr->qstats->overlimits += overlimits; | |
412 | stats_ptr->qstats->drops += drops; | |
413 | stats_ptr->qstats->backlog += | |
416ef9b1 JK |
414 | mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, |
415 | backlog) - | |
416 | mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
417 | stats_base->backlog); | |
3670756f | 418 | |
416ef9b1 | 419 | stats_base->backlog = backlog; |
4d1a4b84 NF |
420 | stats_base->drops += drops; |
421 | stats_base->overlimits += overlimits; | |
422 | stats_base->tx_bytes += tx_bytes; | |
423 | stats_base->tx_packets += tx_packets; | |
3670756f NF |
424 | return 0; |
425 | } | |
426 | ||
96f17e07 NF |
427 | #define MLXSW_SP_PORT_DEFAULT_TCLASS 0 |
428 | ||
562ffbc4 | 429 | static struct mlxsw_sp_qdisc_ops mlxsw_sp_qdisc_ops_red = { |
9cf6c9c7 NF |
430 | .type = MLXSW_SP_QDISC_RED, |
431 | .check_params = mlxsw_sp_qdisc_red_check_params, | |
432 | .replace = mlxsw_sp_qdisc_red_replace, | |
416ef9b1 | 433 | .unoffload = mlxsw_sp_qdisc_red_unoffload, |
9a37a59f | 434 | .destroy = mlxsw_sp_qdisc_red_destroy, |
562ffbc4 NF |
435 | .get_stats = mlxsw_sp_qdisc_get_red_stats, |
436 | .get_xstats = mlxsw_sp_qdisc_get_red_xstats, | |
9cf6c9c7 | 437 | .clean_stats = mlxsw_sp_setup_tc_qdisc_red_clean_stats, |
562ffbc4 NF |
438 | }; |
439 | ||
96f17e07 NF |
440 | int mlxsw_sp_setup_tc_red(struct mlxsw_sp_port *mlxsw_sp_port, |
441 | struct tc_red_qopt_offload *p) | |
442 | { | |
443 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc; | |
96f17e07 | 444 | |
eed4baeb NF |
445 | mlxsw_sp_qdisc = mlxsw_sp_qdisc_find(mlxsw_sp_port, p->parent, false); |
446 | if (!mlxsw_sp_qdisc) | |
96f17e07 NF |
447 | return -EOPNOTSUPP; |
448 | ||
cba7158f | 449 | if (p->command == TC_RED_REPLACE) |
9cf6c9c7 NF |
450 | return mlxsw_sp_qdisc_replace(mlxsw_sp_port, p->handle, |
451 | mlxsw_sp_qdisc, | |
452 | &mlxsw_sp_qdisc_ops_red, | |
453 | &p->set); | |
cba7158f NF |
454 | |
455 | if (!mlxsw_sp_qdisc_compare(mlxsw_sp_qdisc, p->handle, | |
456 | MLXSW_SP_QDISC_RED)) | |
457 | return -EOPNOTSUPP; | |
458 | ||
459 | switch (p->command) { | |
96f17e07 | 460 | case TC_RED_DESTROY: |
9a37a59f | 461 | return mlxsw_sp_qdisc_destroy(mlxsw_sp_port, mlxsw_sp_qdisc); |
861fb829 | 462 | case TC_RED_XSTATS: |
562ffbc4 NF |
463 | return mlxsw_sp_qdisc_get_xstats(mlxsw_sp_port, mlxsw_sp_qdisc, |
464 | p->xstats); | |
3670756f | 465 | case TC_RED_STATS: |
562ffbc4 NF |
466 | return mlxsw_sp_qdisc_get_stats(mlxsw_sp_port, mlxsw_sp_qdisc, |
467 | &p->stats); | |
96f17e07 NF |
468 | default: |
469 | return -EOPNOTSUPP; | |
470 | } | |
471 | } | |
371b437a | 472 | |
46a3615b NF |
473 | static int |
474 | mlxsw_sp_qdisc_prio_destroy(struct mlxsw_sp_port *mlxsw_sp_port, | |
475 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc) | |
476 | { | |
477 | int i; | |
478 | ||
eed4baeb | 479 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { |
46a3615b NF |
480 | mlxsw_sp_port_prio_tc_set(mlxsw_sp_port, i, |
481 | MLXSW_SP_PORT_DEFAULT_TCLASS); | |
eed4baeb NF |
482 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, |
483 | &mlxsw_sp_port->tclass_qdiscs[i]); | |
1631ab2e | 484 | mlxsw_sp_port->tclass_qdiscs[i].prio_bitmap = 0; |
eed4baeb | 485 | } |
46a3615b NF |
486 | |
487 | return 0; | |
488 | } | |
489 | ||
490 | static int | |
491 | mlxsw_sp_qdisc_prio_check_params(struct mlxsw_sp_port *mlxsw_sp_port, | |
492 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
493 | void *params) | |
494 | { | |
495 | struct tc_prio_qopt_offload_params *p = params; | |
496 | ||
497 | if (p->bands > IEEE_8021QAZ_MAX_TCS) | |
498 | return -EOPNOTSUPP; | |
499 | ||
500 | return 0; | |
501 | } | |
502 | ||
503 | static int | |
504 | mlxsw_sp_qdisc_prio_replace(struct mlxsw_sp_port *mlxsw_sp_port, | |
505 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
506 | void *params) | |
507 | { | |
508 | struct tc_prio_qopt_offload_params *p = params; | |
04cc0bf5 | 509 | struct mlxsw_sp_qdisc *child_qdisc; |
cc6e5c13 | 510 | int tclass, i, band, backlog; |
04cc0bf5 | 511 | u8 old_priomap; |
46a3615b NF |
512 | int err; |
513 | ||
04cc0bf5 NF |
514 | for (band = 0; band < p->bands; band++) { |
515 | tclass = MLXSW_SP_PRIO_BAND_TO_TCLASS(band); | |
516 | child_qdisc = &mlxsw_sp_port->tclass_qdiscs[tclass]; | |
517 | old_priomap = child_qdisc->prio_bitmap; | |
518 | child_qdisc->prio_bitmap = 0; | |
519 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { | |
520 | if (p->priomap[i] == band) { | |
521 | child_qdisc->prio_bitmap |= BIT(i); | |
522 | if (BIT(i) & old_priomap) | |
523 | continue; | |
524 | err = mlxsw_sp_port_prio_tc_set(mlxsw_sp_port, | |
525 | i, tclass); | |
526 | if (err) | |
527 | return err; | |
528 | } | |
529 | } | |
530 | if (old_priomap != child_qdisc->prio_bitmap && | |
cc6e5c13 NF |
531 | child_qdisc->ops && child_qdisc->ops->clean_stats) { |
532 | backlog = child_qdisc->stats_base.backlog; | |
04cc0bf5 NF |
533 | child_qdisc->ops->clean_stats(mlxsw_sp_port, |
534 | child_qdisc); | |
cc6e5c13 NF |
535 | child_qdisc->stats_base.backlog = backlog; |
536 | } | |
46a3615b | 537 | } |
98ceb7b6 NF |
538 | for (; band < IEEE_8021QAZ_MAX_TCS; band++) { |
539 | tclass = MLXSW_SP_PRIO_BAND_TO_TCLASS(band); | |
540 | child_qdisc = &mlxsw_sp_port->tclass_qdiscs[tclass]; | |
541 | child_qdisc->prio_bitmap = 0; | |
542 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, child_qdisc); | |
543 | } | |
46a3615b NF |
544 | return 0; |
545 | } | |
546 | ||
e02f08a0 | 547 | static void |
93d8a4c1 NF |
548 | mlxsw_sp_qdisc_prio_unoffload(struct mlxsw_sp_port *mlxsw_sp_port, |
549 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
550 | void *params) | |
551 | { | |
552 | struct tc_prio_qopt_offload_params *p = params; | |
553 | u64 backlog; | |
554 | ||
555 | backlog = mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
556 | mlxsw_sp_qdisc->stats_base.backlog); | |
557 | p->qstats->backlog -= backlog; | |
558 | } | |
559 | ||
560 | static int | |
561 | mlxsw_sp_qdisc_get_prio_stats(struct mlxsw_sp_port *mlxsw_sp_port, | |
562 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
563 | struct tc_qopt_offload_stats *stats_ptr) | |
564 | { | |
565 | u64 tx_bytes, tx_packets, drops = 0, backlog = 0; | |
566 | struct mlxsw_sp_qdisc_stats *stats_base; | |
567 | struct mlxsw_sp_port_xstats *xstats; | |
568 | struct rtnl_link_stats64 *stats; | |
569 | int i; | |
570 | ||
571 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
572 | stats = &mlxsw_sp_port->periodic_hw_stats.stats; | |
573 | stats_base = &mlxsw_sp_qdisc->stats_base; | |
574 | ||
575 | tx_bytes = stats->tx_bytes - stats_base->tx_bytes; | |
576 | tx_packets = stats->tx_packets - stats_base->tx_packets; | |
577 | ||
578 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { | |
579 | drops += xstats->tail_drop[i]; | |
23f2b404 | 580 | drops += xstats->wred_drop[i]; |
93d8a4c1 NF |
581 | backlog += xstats->backlog[i]; |
582 | } | |
583 | drops = drops - stats_base->drops; | |
584 | ||
585 | _bstats_update(stats_ptr->bstats, tx_bytes, tx_packets); | |
586 | stats_ptr->qstats->drops += drops; | |
587 | stats_ptr->qstats->backlog += | |
588 | mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
589 | backlog) - | |
590 | mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
591 | stats_base->backlog); | |
592 | stats_base->backlog = backlog; | |
593 | stats_base->drops += drops; | |
594 | stats_base->tx_bytes += tx_bytes; | |
595 | stats_base->tx_packets += tx_packets; | |
596 | return 0; | |
597 | } | |
598 | ||
599 | static void | |
600 | mlxsw_sp_setup_tc_qdisc_prio_clean_stats(struct mlxsw_sp_port *mlxsw_sp_port, | |
601 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc) | |
602 | { | |
603 | struct mlxsw_sp_qdisc_stats *stats_base; | |
604 | struct mlxsw_sp_port_xstats *xstats; | |
605 | struct rtnl_link_stats64 *stats; | |
606 | int i; | |
607 | ||
608 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
609 | stats = &mlxsw_sp_port->periodic_hw_stats.stats; | |
610 | stats_base = &mlxsw_sp_qdisc->stats_base; | |
611 | ||
612 | stats_base->tx_packets = stats->tx_packets; | |
613 | stats_base->tx_bytes = stats->tx_bytes; | |
614 | ||
615 | stats_base->drops = 0; | |
23f2b404 | 616 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { |
93d8a4c1 | 617 | stats_base->drops += xstats->tail_drop[i]; |
23f2b404 NF |
618 | stats_base->drops += xstats->wred_drop[i]; |
619 | } | |
93d8a4c1 NF |
620 | |
621 | mlxsw_sp_qdisc->stats_base.backlog = 0; | |
622 | } | |
623 | ||
46a3615b NF |
624 | static struct mlxsw_sp_qdisc_ops mlxsw_sp_qdisc_ops_prio = { |
625 | .type = MLXSW_SP_QDISC_PRIO, | |
626 | .check_params = mlxsw_sp_qdisc_prio_check_params, | |
627 | .replace = mlxsw_sp_qdisc_prio_replace, | |
93d8a4c1 | 628 | .unoffload = mlxsw_sp_qdisc_prio_unoffload, |
46a3615b | 629 | .destroy = mlxsw_sp_qdisc_prio_destroy, |
93d8a4c1 NF |
630 | .get_stats = mlxsw_sp_qdisc_get_prio_stats, |
631 | .clean_stats = mlxsw_sp_setup_tc_qdisc_prio_clean_stats, | |
46a3615b NF |
632 | }; |
633 | ||
32dc5efc NF |
634 | /* Grafting is not supported in mlxsw. It will result in un-offloading of the |
635 | * grafted qdisc as well as the qdisc in the qdisc new location. | |
636 | * (However, if the graft is to the location where the qdisc is already at, it | |
637 | * will be ignored completely and won't cause un-offloading). | |
638 | */ | |
639 | static int | |
640 | mlxsw_sp_qdisc_prio_graft(struct mlxsw_sp_port *mlxsw_sp_port, | |
641 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
642 | struct tc_prio_qopt_offload_graft_params *p) | |
643 | { | |
644 | int tclass_num = MLXSW_SP_PRIO_BAND_TO_TCLASS(p->band); | |
645 | struct mlxsw_sp_qdisc *old_qdisc; | |
646 | ||
647 | /* Check if the grafted qdisc is already in its "new" location. If so - | |
648 | * nothing needs to be done. | |
649 | */ | |
650 | if (p->band < IEEE_8021QAZ_MAX_TCS && | |
651 | mlxsw_sp_port->tclass_qdiscs[tclass_num].handle == p->child_handle) | |
652 | return 0; | |
653 | ||
654 | /* See if the grafted qdisc is already offloaded on any tclass. If so, | |
655 | * unoffload it. | |
656 | */ | |
657 | old_qdisc = mlxsw_sp_qdisc_find_by_handle(mlxsw_sp_port, | |
658 | p->child_handle); | |
659 | if (old_qdisc) | |
660 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, old_qdisc); | |
661 | ||
662 | mlxsw_sp_qdisc_destroy(mlxsw_sp_port, | |
663 | &mlxsw_sp_port->tclass_qdiscs[tclass_num]); | |
664 | return -EOPNOTSUPP; | |
665 | } | |
666 | ||
46a3615b NF |
667 | int mlxsw_sp_setup_tc_prio(struct mlxsw_sp_port *mlxsw_sp_port, |
668 | struct tc_prio_qopt_offload *p) | |
669 | { | |
670 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc; | |
671 | ||
eed4baeb NF |
672 | mlxsw_sp_qdisc = mlxsw_sp_qdisc_find(mlxsw_sp_port, p->parent, true); |
673 | if (!mlxsw_sp_qdisc) | |
46a3615b NF |
674 | return -EOPNOTSUPP; |
675 | ||
46a3615b NF |
676 | if (p->command == TC_PRIO_REPLACE) |
677 | return mlxsw_sp_qdisc_replace(mlxsw_sp_port, p->handle, | |
678 | mlxsw_sp_qdisc, | |
679 | &mlxsw_sp_qdisc_ops_prio, | |
680 | &p->replace_params); | |
681 | ||
682 | if (!mlxsw_sp_qdisc_compare(mlxsw_sp_qdisc, p->handle, | |
683 | MLXSW_SP_QDISC_PRIO)) | |
684 | return -EOPNOTSUPP; | |
685 | ||
686 | switch (p->command) { | |
687 | case TC_PRIO_DESTROY: | |
688 | return mlxsw_sp_qdisc_destroy(mlxsw_sp_port, mlxsw_sp_qdisc); | |
93d8a4c1 NF |
689 | case TC_PRIO_STATS: |
690 | return mlxsw_sp_qdisc_get_stats(mlxsw_sp_port, mlxsw_sp_qdisc, | |
691 | &p->stats); | |
32dc5efc NF |
692 | case TC_PRIO_GRAFT: |
693 | return mlxsw_sp_qdisc_prio_graft(mlxsw_sp_port, mlxsw_sp_qdisc, | |
694 | &p->graft_params); | |
46a3615b NF |
695 | default: |
696 | return -EOPNOTSUPP; | |
697 | } | |
698 | } | |
699 | ||
371b437a NF |
700 | int mlxsw_sp_tc_qdisc_init(struct mlxsw_sp_port *mlxsw_sp_port) |
701 | { | |
eed4baeb NF |
702 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc; |
703 | int i; | |
371b437a | 704 | |
eed4baeb NF |
705 | mlxsw_sp_qdisc = kzalloc(sizeof(*mlxsw_sp_qdisc), GFP_KERNEL); |
706 | if (!mlxsw_sp_qdisc) | |
707 | goto err_root_qdisc_init; | |
708 | ||
709 | mlxsw_sp_port->root_qdisc = mlxsw_sp_qdisc; | |
1631ab2e | 710 | mlxsw_sp_port->root_qdisc->prio_bitmap = 0xff; |
d56c8955 NF |
711 | mlxsw_sp_port->root_qdisc->tclass_num = MLXSW_SP_PORT_DEFAULT_TCLASS; |
712 | ||
6396bb22 KC |
713 | mlxsw_sp_qdisc = kcalloc(IEEE_8021QAZ_MAX_TCS, |
714 | sizeof(*mlxsw_sp_qdisc), | |
eed4baeb NF |
715 | GFP_KERNEL); |
716 | if (!mlxsw_sp_qdisc) | |
717 | goto err_tclass_qdiscs_init; | |
718 | ||
719 | mlxsw_sp_port->tclass_qdiscs = mlxsw_sp_qdisc; | |
720 | for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) | |
721 | mlxsw_sp_port->tclass_qdiscs[i].tclass_num = i; | |
722 | ||
371b437a | 723 | return 0; |
eed4baeb NF |
724 | |
725 | err_tclass_qdiscs_init: | |
726 | kfree(mlxsw_sp_port->root_qdisc); | |
727 | err_root_qdisc_init: | |
728 | return -ENOMEM; | |
371b437a NF |
729 | } |
730 | ||
731 | void mlxsw_sp_tc_qdisc_fini(struct mlxsw_sp_port *mlxsw_sp_port) | |
732 | { | |
eed4baeb | 733 | kfree(mlxsw_sp_port->tclass_qdiscs); |
371b437a NF |
734 | kfree(mlxsw_sp_port->root_qdisc); |
735 | } |