xref: /openbmc/linux/drivers/net/ethernet/netronome/nfp/abm/qdisc.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
147330f9bSJakub Kicinski // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
247330f9bSJakub Kicinski /* Copyright (C) 2018 Netronome Systems, Inc. */
347330f9bSJakub Kicinski 
46b8417b7SJakub Kicinski #include <linux/rtnetlink.h>
547330f9bSJakub Kicinski #include <net/pkt_cls.h>
647330f9bSJakub Kicinski #include <net/pkt_sched.h>
747330f9bSJakub Kicinski #include <net/red.h>
847330f9bSJakub Kicinski 
947330f9bSJakub Kicinski #include "../nfpcore/nfp_cpp.h"
1047330f9bSJakub Kicinski #include "../nfp_app.h"
116666f545SJakub Kicinski #include "../nfp_main.h"
126666f545SJakub Kicinski #include "../nfp_net.h"
1347330f9bSJakub Kicinski #include "../nfp_port.h"
1447330f9bSJakub Kicinski #include "main.h"
1547330f9bSJakub Kicinski 
nfp_abm_qdisc_is_red(struct nfp_qdisc * qdisc)16bd3b5d46SJakub Kicinski static bool nfp_abm_qdisc_is_red(struct nfp_qdisc *qdisc)
17bd3b5d46SJakub Kicinski {
18f3d63720SJakub Kicinski 	return qdisc->type == NFP_QDISC_RED || qdisc->type == NFP_QDISC_GRED;
19bd3b5d46SJakub Kicinski }
20bd3b5d46SJakub Kicinski 
nfp_abm_qdisc_child_valid(struct nfp_qdisc * qdisc,unsigned int id)216b8417b7SJakub Kicinski static bool nfp_abm_qdisc_child_valid(struct nfp_qdisc *qdisc, unsigned int id)
226b8417b7SJakub Kicinski {
236b8417b7SJakub Kicinski 	return qdisc->children[id] &&
246b8417b7SJakub Kicinski 	       qdisc->children[id] != NFP_QDISC_UNTRACKED;
256b8417b7SJakub Kicinski }
266b8417b7SJakub Kicinski 
nfp_abm_qdisc_tree_deref_slot(void __rcu ** slot)276b8417b7SJakub Kicinski static void *nfp_abm_qdisc_tree_deref_slot(void __rcu **slot)
286b8417b7SJakub Kicinski {
296b8417b7SJakub Kicinski 	return rtnl_dereference(*slot);
306b8417b7SJakub Kicinski }
316b8417b7SJakub Kicinski 
326b8417b7SJakub Kicinski static void
nfp_abm_stats_propagate(struct nfp_alink_stats * parent,struct nfp_alink_stats * child)33bd3b5d46SJakub Kicinski nfp_abm_stats_propagate(struct nfp_alink_stats *parent,
34bd3b5d46SJakub Kicinski 			struct nfp_alink_stats *child)
35bd3b5d46SJakub Kicinski {
36bd3b5d46SJakub Kicinski 	parent->tx_pkts		+= child->tx_pkts;
37bd3b5d46SJakub Kicinski 	parent->tx_bytes	+= child->tx_bytes;
38bd3b5d46SJakub Kicinski 	parent->backlog_pkts	+= child->backlog_pkts;
39bd3b5d46SJakub Kicinski 	parent->backlog_bytes	+= child->backlog_bytes;
40bd3b5d46SJakub Kicinski 	parent->overlimits	+= child->overlimits;
41bd3b5d46SJakub Kicinski 	parent->drops		+= child->drops;
42bd3b5d46SJakub Kicinski }
43bd3b5d46SJakub Kicinski 
44bd3b5d46SJakub Kicinski static void
nfp_abm_stats_update_red(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc,unsigned int queue)45bd3b5d46SJakub Kicinski nfp_abm_stats_update_red(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc,
46bd3b5d46SJakub Kicinski 			 unsigned int queue)
47bd3b5d46SJakub Kicinski {
48bd3b5d46SJakub Kicinski 	struct nfp_cpp *cpp = alink->abm->app->cpp;
49990b50a5SJakub Kicinski 	unsigned int i;
50bd3b5d46SJakub Kicinski 	int err;
51bd3b5d46SJakub Kicinski 
52bd3b5d46SJakub Kicinski 	if (!qdisc->offloaded)
53bd3b5d46SJakub Kicinski 		return;
54bd3b5d46SJakub Kicinski 
55990b50a5SJakub Kicinski 	for (i = 0; i < qdisc->red.num_bands; i++) {
56990b50a5SJakub Kicinski 		err = nfp_abm_ctrl_read_q_stats(alink, i, queue,
57990b50a5SJakub Kicinski 						&qdisc->red.band[i].stats);
58bd3b5d46SJakub Kicinski 		if (err)
5957207696SJakub Kicinski 			nfp_err(cpp, "RED stats (%d, %d) read failed with error %d\n",
60990b50a5SJakub Kicinski 				i, queue, err);
61bd3b5d46SJakub Kicinski 
62990b50a5SJakub Kicinski 		err = nfp_abm_ctrl_read_q_xstats(alink, i, queue,
63990b50a5SJakub Kicinski 						 &qdisc->red.band[i].xstats);
64bd3b5d46SJakub Kicinski 		if (err)
6557207696SJakub Kicinski 			nfp_err(cpp, "RED xstats (%d, %d) read failed with error %d\n",
66990b50a5SJakub Kicinski 				i, queue, err);
67990b50a5SJakub Kicinski 	}
68bd3b5d46SJakub Kicinski }
69bd3b5d46SJakub Kicinski 
70bd3b5d46SJakub Kicinski static void
nfp_abm_stats_update_mq(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc)71bd3b5d46SJakub Kicinski nfp_abm_stats_update_mq(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc)
72bd3b5d46SJakub Kicinski {
73bd3b5d46SJakub Kicinski 	unsigned int i;
74bd3b5d46SJakub Kicinski 
75bd3b5d46SJakub Kicinski 	if (qdisc->type != NFP_QDISC_MQ)
76bd3b5d46SJakub Kicinski 		return;
77bd3b5d46SJakub Kicinski 
78bd3b5d46SJakub Kicinski 	for (i = 0; i < alink->total_queues; i++)
79bd3b5d46SJakub Kicinski 		if (nfp_abm_qdisc_child_valid(qdisc, i))
80bd3b5d46SJakub Kicinski 			nfp_abm_stats_update_red(alink, qdisc->children[i], i);
81bd3b5d46SJakub Kicinski }
82bd3b5d46SJakub Kicinski 
__nfp_abm_stats_update(struct nfp_abm_link * alink,u64 time_now)83bd3b5d46SJakub Kicinski static void __nfp_abm_stats_update(struct nfp_abm_link *alink, u64 time_now)
84bd3b5d46SJakub Kicinski {
85bd3b5d46SJakub Kicinski 	alink->last_stats_update = time_now;
86bd3b5d46SJakub Kicinski 	if (alink->root_qdisc)
87bd3b5d46SJakub Kicinski 		nfp_abm_stats_update_mq(alink, alink->root_qdisc);
88bd3b5d46SJakub Kicinski }
89bd3b5d46SJakub Kicinski 
nfp_abm_stats_update(struct nfp_abm_link * alink)90bd3b5d46SJakub Kicinski static void nfp_abm_stats_update(struct nfp_abm_link *alink)
91bd3b5d46SJakub Kicinski {
92bd3b5d46SJakub Kicinski 	u64 now;
93bd3b5d46SJakub Kicinski 
94bd3b5d46SJakub Kicinski 	/* Limit the frequency of updates - stats of non-leaf qdiscs are a sum
95bd3b5d46SJakub Kicinski 	 * of all their leafs, so we would read the same stat multiple times
96bd3b5d46SJakub Kicinski 	 * for every dump.
97bd3b5d46SJakub Kicinski 	 */
98bd3b5d46SJakub Kicinski 	now = ktime_get();
99bd3b5d46SJakub Kicinski 	if (now - alink->last_stats_update < NFP_ABM_STATS_REFRESH_IVAL)
100bd3b5d46SJakub Kicinski 		return;
101bd3b5d46SJakub Kicinski 
102bd3b5d46SJakub Kicinski 	__nfp_abm_stats_update(alink, now);
103bd3b5d46SJakub Kicinski }
104bd3b5d46SJakub Kicinski 
105bd3b5d46SJakub Kicinski static void
nfp_abm_qdisc_unlink_children(struct nfp_qdisc * qdisc,unsigned int start,unsigned int end)1066b8417b7SJakub Kicinski nfp_abm_qdisc_unlink_children(struct nfp_qdisc *qdisc,
1076b8417b7SJakub Kicinski 			      unsigned int start, unsigned int end)
1086b8417b7SJakub Kicinski {
1096b8417b7SJakub Kicinski 	unsigned int i;
1106b8417b7SJakub Kicinski 
1116b8417b7SJakub Kicinski 	for (i = start; i < end; i++)
1126b8417b7SJakub Kicinski 		if (nfp_abm_qdisc_child_valid(qdisc, i)) {
1136b8417b7SJakub Kicinski 			qdisc->children[i]->use_cnt--;
1146b8417b7SJakub Kicinski 			qdisc->children[i] = NULL;
1156b8417b7SJakub Kicinski 		}
1166b8417b7SJakub Kicinski }
1176b8417b7SJakub Kicinski 
1186b8417b7SJakub Kicinski static void
nfp_abm_qdisc_offload_stop(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc)119bd3b5d46SJakub Kicinski nfp_abm_qdisc_offload_stop(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc)
120bd3b5d46SJakub Kicinski {
121990b50a5SJakub Kicinski 	unsigned int i;
122990b50a5SJakub Kicinski 
123bd3b5d46SJakub Kicinski 	/* Don't complain when qdisc is getting unlinked */
124bd3b5d46SJakub Kicinski 	if (qdisc->use_cnt)
125bd3b5d46SJakub Kicinski 		nfp_warn(alink->abm->app->cpp, "Offload of '%08x' stopped\n",
126bd3b5d46SJakub Kicinski 			 qdisc->handle);
127bd3b5d46SJakub Kicinski 
128bd3b5d46SJakub Kicinski 	if (!nfp_abm_qdisc_is_red(qdisc))
129bd3b5d46SJakub Kicinski 		return;
130bd3b5d46SJakub Kicinski 
131990b50a5SJakub Kicinski 	for (i = 0; i < qdisc->red.num_bands; i++) {
132990b50a5SJakub Kicinski 		qdisc->red.band[i].stats.backlog_pkts = 0;
133990b50a5SJakub Kicinski 		qdisc->red.band[i].stats.backlog_bytes = 0;
134990b50a5SJakub Kicinski 	}
135bd3b5d46SJakub Kicinski }
136bd3b5d46SJakub Kicinski 
137bd3b5d46SJakub Kicinski static int
__nfp_abm_stats_init(struct nfp_abm_link * alink,unsigned int band,unsigned int queue,struct nfp_alink_stats * prev_stats,struct nfp_alink_xstats * prev_xstats)13857207696SJakub Kicinski __nfp_abm_stats_init(struct nfp_abm_link *alink, unsigned int band,
139bd3b5d46SJakub Kicinski 		     unsigned int queue, struct nfp_alink_stats *prev_stats,
140bd3b5d46SJakub Kicinski 		     struct nfp_alink_xstats *prev_xstats)
141bd3b5d46SJakub Kicinski {
142bd3b5d46SJakub Kicinski 	u64 backlog_pkts, backlog_bytes;
143bd3b5d46SJakub Kicinski 	int err;
144bd3b5d46SJakub Kicinski 
145bd3b5d46SJakub Kicinski 	/* Don't touch the backlog, backlog can only be reset after it has
146bd3b5d46SJakub Kicinski 	 * been reported back to the tc qdisc stats.
147bd3b5d46SJakub Kicinski 	 */
148bd3b5d46SJakub Kicinski 	backlog_pkts = prev_stats->backlog_pkts;
149bd3b5d46SJakub Kicinski 	backlog_bytes = prev_stats->backlog_bytes;
150bd3b5d46SJakub Kicinski 
15157207696SJakub Kicinski 	err = nfp_abm_ctrl_read_q_stats(alink, band, queue, prev_stats);
152bd3b5d46SJakub Kicinski 	if (err) {
153bd3b5d46SJakub Kicinski 		nfp_err(alink->abm->app->cpp,
15457207696SJakub Kicinski 			"RED stats init (%d, %d) failed with error %d\n",
15557207696SJakub Kicinski 			band, queue, err);
156bd3b5d46SJakub Kicinski 		return err;
157bd3b5d46SJakub Kicinski 	}
158bd3b5d46SJakub Kicinski 
15957207696SJakub Kicinski 	err = nfp_abm_ctrl_read_q_xstats(alink, band, queue, prev_xstats);
160bd3b5d46SJakub Kicinski 	if (err) {
161bd3b5d46SJakub Kicinski 		nfp_err(alink->abm->app->cpp,
16257207696SJakub Kicinski 			"RED xstats init (%d, %d) failed with error %d\n",
16357207696SJakub Kicinski 			band, queue, err);
164bd3b5d46SJakub Kicinski 		return err;
165bd3b5d46SJakub Kicinski 	}
166bd3b5d46SJakub Kicinski 
167bd3b5d46SJakub Kicinski 	prev_stats->backlog_pkts = backlog_pkts;
168bd3b5d46SJakub Kicinski 	prev_stats->backlog_bytes = backlog_bytes;
169bd3b5d46SJakub Kicinski 	return 0;
170bd3b5d46SJakub Kicinski }
171bd3b5d46SJakub Kicinski 
172bd3b5d46SJakub Kicinski static int
nfp_abm_stats_init(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc,unsigned int queue)173bd3b5d46SJakub Kicinski nfp_abm_stats_init(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc,
174bd3b5d46SJakub Kicinski 		   unsigned int queue)
175bd3b5d46SJakub Kicinski {
176990b50a5SJakub Kicinski 	unsigned int i;
177990b50a5SJakub Kicinski 	int err;
178990b50a5SJakub Kicinski 
179990b50a5SJakub Kicinski 	for (i = 0; i < qdisc->red.num_bands; i++) {
180990b50a5SJakub Kicinski 		err = __nfp_abm_stats_init(alink, i, queue,
181990b50a5SJakub Kicinski 					   &qdisc->red.band[i].prev_stats,
182990b50a5SJakub Kicinski 					   &qdisc->red.band[i].prev_xstats);
183990b50a5SJakub Kicinski 		if (err)
184990b50a5SJakub Kicinski 			return err;
185990b50a5SJakub Kicinski 	}
186990b50a5SJakub Kicinski 
187990b50a5SJakub Kicinski 	return 0;
188bd3b5d46SJakub Kicinski }
189bd3b5d46SJakub Kicinski 
190bd3b5d46SJakub Kicinski static void
nfp_abm_offload_compile_red(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc,unsigned int queue)191bd3b5d46SJakub Kicinski nfp_abm_offload_compile_red(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc,
192bd3b5d46SJakub Kicinski 			    unsigned int queue)
193bd3b5d46SJakub Kicinski {
194f3d63720SJakub Kicinski 	bool good_red, good_gred;
195990b50a5SJakub Kicinski 	unsigned int i;
196990b50a5SJakub Kicinski 
197f3d63720SJakub Kicinski 	good_red = qdisc->type == NFP_QDISC_RED &&
198bd3b5d46SJakub Kicinski 		   qdisc->params_ok &&
199bd3b5d46SJakub Kicinski 		   qdisc->use_cnt == 1 &&
200174ab544SJakub Kicinski 		   !alink->has_prio &&
201bd3b5d46SJakub Kicinski 		   !qdisc->children[0];
202f3d63720SJakub Kicinski 	good_gred = qdisc->type == NFP_QDISC_GRED &&
203f3d63720SJakub Kicinski 		    qdisc->params_ok &&
204f3d63720SJakub Kicinski 		    qdisc->use_cnt == 1;
205f3d63720SJakub Kicinski 	qdisc->offload_mark = good_red || good_gred;
206bd3b5d46SJakub Kicinski 
207bd3b5d46SJakub Kicinski 	/* If we are starting offload init prev_stats */
208bd3b5d46SJakub Kicinski 	if (qdisc->offload_mark && !qdisc->offloaded)
209bd3b5d46SJakub Kicinski 		if (nfp_abm_stats_init(alink, qdisc, queue))
210bd3b5d46SJakub Kicinski 			qdisc->offload_mark = false;
211bd3b5d46SJakub Kicinski 
212bd3b5d46SJakub Kicinski 	if (!qdisc->offload_mark)
213bd3b5d46SJakub Kicinski 		return;
214bd3b5d46SJakub Kicinski 
215340a4864SJakub Kicinski 	for (i = 0; i < alink->abm->num_bands; i++) {
216340a4864SJakub Kicinski 		enum nfp_abm_q_action act;
217340a4864SJakub Kicinski 
218990b50a5SJakub Kicinski 		nfp_abm_ctrl_set_q_lvl(alink, i, queue,
219990b50a5SJakub Kicinski 				       qdisc->red.band[i].threshold);
220340a4864SJakub Kicinski 		act = qdisc->red.band[i].ecn ?
221340a4864SJakub Kicinski 			NFP_ABM_ACT_MARK_DROP : NFP_ABM_ACT_DROP;
222340a4864SJakub Kicinski 		nfp_abm_ctrl_set_q_act(alink, i, queue, act);
223340a4864SJakub Kicinski 	}
224bd3b5d46SJakub Kicinski }
225bd3b5d46SJakub Kicinski 
226bd3b5d46SJakub Kicinski static void
nfp_abm_offload_compile_mq(struct nfp_abm_link * alink,struct nfp_qdisc * qdisc)227bd3b5d46SJakub Kicinski nfp_abm_offload_compile_mq(struct nfp_abm_link *alink, struct nfp_qdisc *qdisc)
228bd3b5d46SJakub Kicinski {
229bd3b5d46SJakub Kicinski 	unsigned int i;
230bd3b5d46SJakub Kicinski 
231bd3b5d46SJakub Kicinski 	qdisc->offload_mark = qdisc->type == NFP_QDISC_MQ;
232bd3b5d46SJakub Kicinski 	if (!qdisc->offload_mark)
233bd3b5d46SJakub Kicinski 		return;
234bd3b5d46SJakub Kicinski 
235bd3b5d46SJakub Kicinski 	for (i = 0; i < alink->total_queues; i++) {
236bd3b5d46SJakub Kicinski 		struct nfp_qdisc *child = qdisc->children[i];
237bd3b5d46SJakub Kicinski 
238bd3b5d46SJakub Kicinski 		if (!nfp_abm_qdisc_child_valid(qdisc, i))
239bd3b5d46SJakub Kicinski 			continue;
240bd3b5d46SJakub Kicinski 
241bd3b5d46SJakub Kicinski 		nfp_abm_offload_compile_red(alink, child, i);
242bd3b5d46SJakub Kicinski 	}
243bd3b5d46SJakub Kicinski }
244bd3b5d46SJakub Kicinski 
nfp_abm_qdisc_offload_update(struct nfp_abm_link * alink)245bd3b5d46SJakub Kicinski void nfp_abm_qdisc_offload_update(struct nfp_abm_link *alink)
246bd3b5d46SJakub Kicinski {
247bd3b5d46SJakub Kicinski 	struct nfp_abm *abm = alink->abm;
248bd3b5d46SJakub Kicinski 	struct radix_tree_iter iter;
249bd3b5d46SJakub Kicinski 	struct nfp_qdisc *qdisc;
250bd3b5d46SJakub Kicinski 	void __rcu **slot;
251bd3b5d46SJakub Kicinski 	size_t i;
252bd3b5d46SJakub Kicinski 
253bd3b5d46SJakub Kicinski 	/* Mark all thresholds as unconfigured */
254990b50a5SJakub Kicinski 	for (i = 0; i < abm->num_bands; i++)
255bd3b5d46SJakub Kicinski 		__bitmap_set(abm->threshold_undef,
256990b50a5SJakub Kicinski 			     i * NFP_NET_MAX_RX_RINGS + alink->queue_base,
257990b50a5SJakub Kicinski 			     alink->total_queues);
258bd3b5d46SJakub Kicinski 
259bd3b5d46SJakub Kicinski 	/* Clear offload marks */
260bd3b5d46SJakub Kicinski 	radix_tree_for_each_slot(slot, &alink->qdiscs, &iter, 0) {
261bd3b5d46SJakub Kicinski 		qdisc = nfp_abm_qdisc_tree_deref_slot(slot);
262bd3b5d46SJakub Kicinski 		qdisc->offload_mark = false;
263bd3b5d46SJakub Kicinski 	}
264bd3b5d46SJakub Kicinski 
265bd3b5d46SJakub Kicinski 	if (alink->root_qdisc)
266bd3b5d46SJakub Kicinski 		nfp_abm_offload_compile_mq(alink, alink->root_qdisc);
267bd3b5d46SJakub Kicinski 
268bd3b5d46SJakub Kicinski 	/* Refresh offload status */
269bd3b5d46SJakub Kicinski 	radix_tree_for_each_slot(slot, &alink->qdiscs, &iter, 0) {
270bd3b5d46SJakub Kicinski 		qdisc = nfp_abm_qdisc_tree_deref_slot(slot);
271bd3b5d46SJakub Kicinski 		if (!qdisc->offload_mark && qdisc->offloaded)
272bd3b5d46SJakub Kicinski 			nfp_abm_qdisc_offload_stop(alink, qdisc);
273bd3b5d46SJakub Kicinski 		qdisc->offloaded = qdisc->offload_mark;
274bd3b5d46SJakub Kicinski 	}
275bd3b5d46SJakub Kicinski 
276bd3b5d46SJakub Kicinski 	/* Reset the unconfigured thresholds */
277bd3b5d46SJakub Kicinski 	for (i = 0; i < abm->num_thresholds; i++)
278bd3b5d46SJakub Kicinski 		if (test_bit(i, abm->threshold_undef))
279bd3b5d46SJakub Kicinski 			__nfp_abm_ctrl_set_q_lvl(abm, i, NFP_ABM_LVL_INFINITY);
280bd3b5d46SJakub Kicinski 
281bd3b5d46SJakub Kicinski 	__nfp_abm_stats_update(alink, ktime_get());
282bd3b5d46SJakub Kicinski }
283bd3b5d46SJakub Kicinski 
284bd3b5d46SJakub Kicinski static void
nfp_abm_qdisc_clear_mq(struct net_device * netdev,struct nfp_abm_link * alink,struct nfp_qdisc * qdisc)2856b8417b7SJakub Kicinski nfp_abm_qdisc_clear_mq(struct net_device *netdev, struct nfp_abm_link *alink,
2866b8417b7SJakub Kicinski 		       struct nfp_qdisc *qdisc)
2876b8417b7SJakub Kicinski {
2886b8417b7SJakub Kicinski 	struct radix_tree_iter iter;
2896b8417b7SJakub Kicinski 	unsigned int mq_refs = 0;
2906b8417b7SJakub Kicinski 	void __rcu **slot;
2916b8417b7SJakub Kicinski 
2926b8417b7SJakub Kicinski 	if (!qdisc->use_cnt)
2936b8417b7SJakub Kicinski 		return;
2946b8417b7SJakub Kicinski 	/* MQ doesn't notify well on destruction, we need special handling of
2956b8417b7SJakub Kicinski 	 * MQ's children.
2966b8417b7SJakub Kicinski 	 */
2976b8417b7SJakub Kicinski 	if (qdisc->type == NFP_QDISC_MQ &&
2986b8417b7SJakub Kicinski 	    qdisc == alink->root_qdisc &&
2996b8417b7SJakub Kicinski 	    netdev->reg_state == NETREG_UNREGISTERING)
3006b8417b7SJakub Kicinski 		return;
3016b8417b7SJakub Kicinski 
3026b8417b7SJakub Kicinski 	/* Count refs held by MQ instances and clear pointers */
3036b8417b7SJakub Kicinski 	radix_tree_for_each_slot(slot, &alink->qdiscs, &iter, 0) {
3046b8417b7SJakub Kicinski 		struct nfp_qdisc *mq = nfp_abm_qdisc_tree_deref_slot(slot);
3056b8417b7SJakub Kicinski 		unsigned int i;
3066b8417b7SJakub Kicinski 
3076b8417b7SJakub Kicinski 		if (mq->type != NFP_QDISC_MQ || mq->netdev != netdev)
3086b8417b7SJakub Kicinski 			continue;
3096b8417b7SJakub Kicinski 		for (i = 0; i < mq->num_children; i++)
3106b8417b7SJakub Kicinski 			if (mq->children[i] == qdisc) {
3116b8417b7SJakub Kicinski 				mq->children[i] = NULL;
3126b8417b7SJakub Kicinski 				mq_refs++;
3136b8417b7SJakub Kicinski 			}
3146b8417b7SJakub Kicinski 	}
3156b8417b7SJakub Kicinski 
3166b8417b7SJakub Kicinski 	WARN(qdisc->use_cnt != mq_refs, "non-zero qdisc use count: %d (- %d)\n",
3176b8417b7SJakub Kicinski 	     qdisc->use_cnt, mq_refs);
3186b8417b7SJakub Kicinski }
3196b8417b7SJakub Kicinski 
3206666f545SJakub Kicinski static void
nfp_abm_qdisc_free(struct net_device * netdev,struct nfp_abm_link * alink,struct nfp_qdisc * qdisc)3214f5681d0SJakub Kicinski nfp_abm_qdisc_free(struct net_device *netdev, struct nfp_abm_link *alink,
3224f5681d0SJakub Kicinski 		   struct nfp_qdisc *qdisc)
32347330f9bSJakub Kicinski {
32447330f9bSJakub Kicinski 	struct nfp_port *port = nfp_port_from_netdev(netdev);
32547330f9bSJakub Kicinski 
3264f5681d0SJakub Kicinski 	if (!qdisc)
3274f5681d0SJakub Kicinski 		return;
3286b8417b7SJakub Kicinski 	nfp_abm_qdisc_clear_mq(netdev, alink, qdisc);
3294f5681d0SJakub Kicinski 	WARN_ON(radix_tree_delete(&alink->qdiscs,
3304f5681d0SJakub Kicinski 				  TC_H_MAJ(qdisc->handle)) != qdisc);
331aee7539cSJakub Kicinski 
332aee7539cSJakub Kicinski 	kfree(qdisc->children);
3334f5681d0SJakub Kicinski 	kfree(qdisc);
3344f5681d0SJakub Kicinski 
3354f5681d0SJakub Kicinski 	port->tc_offload_cnt--;
3364f5681d0SJakub Kicinski }
3374f5681d0SJakub Kicinski 
3384f5681d0SJakub Kicinski static struct nfp_qdisc *
nfp_abm_qdisc_alloc(struct net_device * netdev,struct nfp_abm_link * alink,enum nfp_qdisc_type type,u32 parent_handle,u32 handle,unsigned int children)3394f5681d0SJakub Kicinski nfp_abm_qdisc_alloc(struct net_device *netdev, struct nfp_abm_link *alink,
340aee7539cSJakub Kicinski 		    enum nfp_qdisc_type type, u32 parent_handle, u32 handle,
341aee7539cSJakub Kicinski 		    unsigned int children)
3424f5681d0SJakub Kicinski {
3434f5681d0SJakub Kicinski 	struct nfp_port *port = nfp_port_from_netdev(netdev);
3444f5681d0SJakub Kicinski 	struct nfp_qdisc *qdisc;
3454f5681d0SJakub Kicinski 	int err;
3464f5681d0SJakub Kicinski 
3474f5681d0SJakub Kicinski 	qdisc = kzalloc(sizeof(*qdisc), GFP_KERNEL);
3484f5681d0SJakub Kicinski 	if (!qdisc)
3494f5681d0SJakub Kicinski 		return NULL;
3504f5681d0SJakub Kicinski 
351f3d63720SJakub Kicinski 	if (children) {
352aee7539cSJakub Kicinski 		qdisc->children = kcalloc(children, sizeof(void *), GFP_KERNEL);
353aee7539cSJakub Kicinski 		if (!qdisc->children)
354aee7539cSJakub Kicinski 			goto err_free_qdisc;
355f3d63720SJakub Kicinski 	}
356aee7539cSJakub Kicinski 
3574f5681d0SJakub Kicinski 	qdisc->netdev = netdev;
3584f5681d0SJakub Kicinski 	qdisc->type = type;
3594f5681d0SJakub Kicinski 	qdisc->parent_handle = parent_handle;
3604f5681d0SJakub Kicinski 	qdisc->handle = handle;
361aee7539cSJakub Kicinski 	qdisc->num_children = children;
3624f5681d0SJakub Kicinski 
3634f5681d0SJakub Kicinski 	err = radix_tree_insert(&alink->qdiscs, TC_H_MAJ(qdisc->handle), qdisc);
3644f5681d0SJakub Kicinski 	if (err) {
3654f5681d0SJakub Kicinski 		nfp_err(alink->abm->app->cpp,
3664f5681d0SJakub Kicinski 			"Qdisc insertion into radix tree failed: %d\n", err);
367aee7539cSJakub Kicinski 		goto err_free_child_tbl;
3684f5681d0SJakub Kicinski 	}
3694f5681d0SJakub Kicinski 
3704f5681d0SJakub Kicinski 	port->tc_offload_cnt++;
3714f5681d0SJakub Kicinski 	return qdisc;
3724f5681d0SJakub Kicinski 
373aee7539cSJakub Kicinski err_free_child_tbl:
374aee7539cSJakub Kicinski 	kfree(qdisc->children);
3754f5681d0SJakub Kicinski err_free_qdisc:
3764f5681d0SJakub Kicinski 	kfree(qdisc);
3774f5681d0SJakub Kicinski 	return NULL;
3784f5681d0SJakub Kicinski }
3794f5681d0SJakub Kicinski 
3804f5681d0SJakub Kicinski static struct nfp_qdisc *
nfp_abm_qdisc_find(struct nfp_abm_link * alink,u32 handle)3814f5681d0SJakub Kicinski nfp_abm_qdisc_find(struct nfp_abm_link *alink, u32 handle)
3824f5681d0SJakub Kicinski {
3834f5681d0SJakub Kicinski 	return radix_tree_lookup(&alink->qdiscs, TC_H_MAJ(handle));
3844f5681d0SJakub Kicinski }
3854f5681d0SJakub Kicinski 
3864f5681d0SJakub Kicinski static int
nfp_abm_qdisc_replace(struct net_device * netdev,struct nfp_abm_link * alink,enum nfp_qdisc_type type,u32 parent_handle,u32 handle,unsigned int children,struct nfp_qdisc ** qdisc)3874f5681d0SJakub Kicinski nfp_abm_qdisc_replace(struct net_device *netdev, struct nfp_abm_link *alink,
3884f5681d0SJakub Kicinski 		      enum nfp_qdisc_type type, u32 parent_handle, u32 handle,
389aee7539cSJakub Kicinski 		      unsigned int children, struct nfp_qdisc **qdisc)
3904f5681d0SJakub Kicinski {
3914f5681d0SJakub Kicinski 	*qdisc = nfp_abm_qdisc_find(alink, handle);
3924f5681d0SJakub Kicinski 	if (*qdisc) {
3934f5681d0SJakub Kicinski 		if (WARN_ON((*qdisc)->type != type))
3944f5681d0SJakub Kicinski 			return -EINVAL;
3956c5dbda0SJakub Kicinski 		return 1;
3964f5681d0SJakub Kicinski 	}
3974f5681d0SJakub Kicinski 
398aee7539cSJakub Kicinski 	*qdisc = nfp_abm_qdisc_alloc(netdev, alink, type, parent_handle, handle,
399aee7539cSJakub Kicinski 				     children);
4004f5681d0SJakub Kicinski 	return *qdisc ? 0 : -ENOMEM;
4014f5681d0SJakub Kicinski }
4024f5681d0SJakub Kicinski 
4034f5681d0SJakub Kicinski static void
nfp_abm_qdisc_destroy(struct net_device * netdev,struct nfp_abm_link * alink,u32 handle)4044f5681d0SJakub Kicinski nfp_abm_qdisc_destroy(struct net_device *netdev, struct nfp_abm_link *alink,
4054f5681d0SJakub Kicinski 		      u32 handle)
4064f5681d0SJakub Kicinski {
4074f5681d0SJakub Kicinski 	struct nfp_qdisc *qdisc;
4084f5681d0SJakub Kicinski 
4094f5681d0SJakub Kicinski 	qdisc = nfp_abm_qdisc_find(alink, handle);
4104f5681d0SJakub Kicinski 	if (!qdisc)
4114f5681d0SJakub Kicinski 		return;
4124f5681d0SJakub Kicinski 
4136b8417b7SJakub Kicinski 	/* We don't get TC_SETUP_ROOT_QDISC w/ MQ when netdev is unregistered */
4146b8417b7SJakub Kicinski 	if (alink->root_qdisc == qdisc)
4156b8417b7SJakub Kicinski 		qdisc->use_cnt--;
4166b8417b7SJakub Kicinski 
4176b8417b7SJakub Kicinski 	nfp_abm_qdisc_unlink_children(qdisc, 0, qdisc->num_children);
4184f5681d0SJakub Kicinski 	nfp_abm_qdisc_free(netdev, alink, qdisc);
41918531258SJakub Kicinski 
420bd3b5d46SJakub Kicinski 	if (alink->root_qdisc == qdisc) {
42118531258SJakub Kicinski 		alink->root_qdisc = NULL;
422bd3b5d46SJakub Kicinski 		/* Only root change matters, other changes are acted upon on
423bd3b5d46SJakub Kicinski 		 * the graft notification.
424bd3b5d46SJakub Kicinski 		 */
425bd3b5d46SJakub Kicinski 		nfp_abm_qdisc_offload_update(alink);
426bd3b5d46SJakub Kicinski 	}
4274f5681d0SJakub Kicinski }
4284f5681d0SJakub Kicinski 
4296b8417b7SJakub Kicinski static int
nfp_abm_qdisc_graft(struct nfp_abm_link * alink,u32 handle,u32 child_handle,unsigned int id)4306b8417b7SJakub Kicinski nfp_abm_qdisc_graft(struct nfp_abm_link *alink, u32 handle, u32 child_handle,
4316b8417b7SJakub Kicinski 		    unsigned int id)
4326b8417b7SJakub Kicinski {
4336b8417b7SJakub Kicinski 	struct nfp_qdisc *parent, *child;
4346b8417b7SJakub Kicinski 
4356b8417b7SJakub Kicinski 	parent = nfp_abm_qdisc_find(alink, handle);
4366b8417b7SJakub Kicinski 	if (!parent)
4376b8417b7SJakub Kicinski 		return 0;
4386b8417b7SJakub Kicinski 
4396b8417b7SJakub Kicinski 	if (WARN(id >= parent->num_children,
4406b8417b7SJakub Kicinski 		 "graft child out of bound %d >= %d\n",
4416b8417b7SJakub Kicinski 		 id, parent->num_children))
4426b8417b7SJakub Kicinski 		return -EINVAL;
4436b8417b7SJakub Kicinski 
4446b8417b7SJakub Kicinski 	nfp_abm_qdisc_unlink_children(parent, id, id + 1);
4456b8417b7SJakub Kicinski 
4466b8417b7SJakub Kicinski 	child = nfp_abm_qdisc_find(alink, child_handle);
4476b8417b7SJakub Kicinski 	if (child)
4486b8417b7SJakub Kicinski 		child->use_cnt++;
4496b8417b7SJakub Kicinski 	else
4506b8417b7SJakub Kicinski 		child = NFP_QDISC_UNTRACKED;
4516b8417b7SJakub Kicinski 	parent->children[id] = child;
4526b8417b7SJakub Kicinski 
453bd3b5d46SJakub Kicinski 	nfp_abm_qdisc_offload_update(alink);
454bd3b5d46SJakub Kicinski 
4556b8417b7SJakub Kicinski 	return 0;
4566b8417b7SJakub Kicinski }
4576b8417b7SJakub Kicinski 
4584f5681d0SJakub Kicinski static void
nfp_abm_stats_calculate(struct nfp_alink_stats * new,struct nfp_alink_stats * old,struct gnet_stats_basic_sync * bstats,struct gnet_stats_queue * qstats)459bd3b5d46SJakub Kicinski nfp_abm_stats_calculate(struct nfp_alink_stats *new,
460bd3b5d46SJakub Kicinski 			struct nfp_alink_stats *old,
461*50dc9a85SAhmed S. Darwish 			struct gnet_stats_basic_sync *bstats,
462bd3b5d46SJakub Kicinski 			struct gnet_stats_queue *qstats)
4634f5681d0SJakub Kicinski {
464bd3b5d46SJakub Kicinski 	_bstats_update(bstats, new->tx_bytes - old->tx_bytes,
465bd3b5d46SJakub Kicinski 		       new->tx_pkts - old->tx_pkts);
466bd3b5d46SJakub Kicinski 	qstats->qlen += new->backlog_pkts - old->backlog_pkts;
467bd3b5d46SJakub Kicinski 	qstats->backlog += new->backlog_bytes - old->backlog_bytes;
468bd3b5d46SJakub Kicinski 	qstats->overlimits += new->overlimits - old->overlimits;
469bd3b5d46SJakub Kicinski 	qstats->drops += new->drops - old->drops;
47047330f9bSJakub Kicinski }
47147330f9bSJakub Kicinski 
47247330f9bSJakub Kicinski static void
nfp_abm_stats_red_calculate(struct nfp_alink_xstats * new,struct nfp_alink_xstats * old,struct red_stats * stats)473bd3b5d46SJakub Kicinski nfp_abm_stats_red_calculate(struct nfp_alink_xstats *new,
474bd3b5d46SJakub Kicinski 			    struct nfp_alink_xstats *old,
475bd3b5d46SJakub Kicinski 			    struct red_stats *stats)
47647330f9bSJakub Kicinski {
477bd3b5d46SJakub Kicinski 	stats->forced_mark += new->ecn_marked - old->ecn_marked;
478bd3b5d46SJakub Kicinski 	stats->pdrop += new->pdrop - old->pdrop;
47947330f9bSJakub Kicinski }
48047330f9bSJakub Kicinski 
48147330f9bSJakub Kicinski static int
nfp_abm_gred_stats(struct nfp_abm_link * alink,u32 handle,struct tc_gred_qopt_offload_stats * stats)482f3d63720SJakub Kicinski nfp_abm_gred_stats(struct nfp_abm_link *alink, u32 handle,
483f3d63720SJakub Kicinski 		   struct tc_gred_qopt_offload_stats *stats)
484f3d63720SJakub Kicinski {
485f3d63720SJakub Kicinski 	struct nfp_qdisc *qdisc;
486f3d63720SJakub Kicinski 	unsigned int i;
487f3d63720SJakub Kicinski 
488f3d63720SJakub Kicinski 	nfp_abm_stats_update(alink);
489f3d63720SJakub Kicinski 
490f3d63720SJakub Kicinski 	qdisc = nfp_abm_qdisc_find(alink, handle);
491f3d63720SJakub Kicinski 	if (!qdisc)
492f3d63720SJakub Kicinski 		return -EOPNOTSUPP;
493f3d63720SJakub Kicinski 	/* If the qdisc offload has stopped we may need to adjust the backlog
494f3d63720SJakub Kicinski 	 * counters back so carry on even if qdisc is not currently offloaded.
495f3d63720SJakub Kicinski 	 */
496f3d63720SJakub Kicinski 
497f3d63720SJakub Kicinski 	for (i = 0; i < qdisc->red.num_bands; i++) {
498f3d63720SJakub Kicinski 		if (!stats->xstats[i])
499f3d63720SJakub Kicinski 			continue;
500f3d63720SJakub Kicinski 
501f3d63720SJakub Kicinski 		nfp_abm_stats_calculate(&qdisc->red.band[i].stats,
502f3d63720SJakub Kicinski 					&qdisc->red.band[i].prev_stats,
503f3d63720SJakub Kicinski 					&stats->bstats[i], &stats->qstats[i]);
504f3d63720SJakub Kicinski 		qdisc->red.band[i].prev_stats = qdisc->red.band[i].stats;
505f3d63720SJakub Kicinski 
506f3d63720SJakub Kicinski 		nfp_abm_stats_red_calculate(&qdisc->red.band[i].xstats,
507f3d63720SJakub Kicinski 					    &qdisc->red.band[i].prev_xstats,
508f3d63720SJakub Kicinski 					    stats->xstats[i]);
509f3d63720SJakub Kicinski 		qdisc->red.band[i].prev_xstats = qdisc->red.band[i].xstats;
510f3d63720SJakub Kicinski 	}
511f3d63720SJakub Kicinski 
512f3d63720SJakub Kicinski 	return qdisc->offloaded ? 0 : -EOPNOTSUPP;
513f3d63720SJakub Kicinski }
514f3d63720SJakub Kicinski 
515f3d63720SJakub Kicinski static bool
nfp_abm_gred_check_params(struct nfp_abm_link * alink,struct tc_gred_qopt_offload * opt)516f3d63720SJakub Kicinski nfp_abm_gred_check_params(struct nfp_abm_link *alink,
517f3d63720SJakub Kicinski 			  struct tc_gred_qopt_offload *opt)
518f3d63720SJakub Kicinski {
519f3d63720SJakub Kicinski 	struct nfp_cpp *cpp = alink->abm->app->cpp;
520f3d63720SJakub Kicinski 	struct nfp_abm *abm = alink->abm;
521f3d63720SJakub Kicinski 	unsigned int i;
522f3d63720SJakub Kicinski 
523f3d63720SJakub Kicinski 	if (opt->set.grio_on || opt->set.wred_on) {
524f3d63720SJakub Kicinski 		nfp_warn(cpp, "GRED offload failed - GRIO and WRED not supported (p:%08x h:%08x)\n",
525f3d63720SJakub Kicinski 			 opt->parent, opt->handle);
526f3d63720SJakub Kicinski 		return false;
527f3d63720SJakub Kicinski 	}
528f3d63720SJakub Kicinski 	if (opt->set.dp_def != alink->def_band) {
529f3d63720SJakub Kicinski 		nfp_warn(cpp, "GRED offload failed - default band must be %d (p:%08x h:%08x)\n",
530f3d63720SJakub Kicinski 			 alink->def_band, opt->parent, opt->handle);
531f3d63720SJakub Kicinski 		return false;
532f3d63720SJakub Kicinski 	}
533f3d63720SJakub Kicinski 	if (opt->set.dp_cnt != abm->num_bands) {
534f3d63720SJakub Kicinski 		nfp_warn(cpp, "GRED offload failed - band count must be %d (p:%08x h:%08x)\n",
535f3d63720SJakub Kicinski 			 abm->num_bands, opt->parent, opt->handle);
536f3d63720SJakub Kicinski 		return false;
537f3d63720SJakub Kicinski 	}
538f3d63720SJakub Kicinski 
539f3d63720SJakub Kicinski 	for (i = 0; i < abm->num_bands; i++) {
540f3d63720SJakub Kicinski 		struct tc_gred_vq_qopt_offload_params *band = &opt->set.tab[i];
541f3d63720SJakub Kicinski 
542f3d63720SJakub Kicinski 		if (!band->present)
543f3d63720SJakub Kicinski 			return false;
544340a4864SJakub Kicinski 		if (!band->is_ecn && !nfp_abm_has_drop(abm)) {
545f3d63720SJakub Kicinski 			nfp_warn(cpp, "GRED offload failed - drop is not supported (ECN option required) (p:%08x h:%08x vq:%d)\n",
546f3d63720SJakub Kicinski 				 opt->parent, opt->handle, i);
547f3d63720SJakub Kicinski 			return false;
548f3d63720SJakub Kicinski 		}
549340a4864SJakub Kicinski 		if (band->is_ecn && !nfp_abm_has_mark(abm)) {
550340a4864SJakub Kicinski 			nfp_warn(cpp, "GRED offload failed - ECN marking not supported (p:%08x h:%08x vq:%d)\n",
551340a4864SJakub Kicinski 				 opt->parent, opt->handle, i);
552340a4864SJakub Kicinski 			return false;
553340a4864SJakub Kicinski 		}
554f3d63720SJakub Kicinski 		if (band->is_harddrop) {
555f3d63720SJakub Kicinski 			nfp_warn(cpp, "GRED offload failed - harddrop is not supported (p:%08x h:%08x vq:%d)\n",
556f3d63720SJakub Kicinski 				 opt->parent, opt->handle, i);
557f3d63720SJakub Kicinski 			return false;
558f3d63720SJakub Kicinski 		}
559f3d63720SJakub Kicinski 		if (band->min != band->max) {
560f3d63720SJakub Kicinski 			nfp_warn(cpp, "GRED offload failed - threshold mismatch (p:%08x h:%08x vq:%d)\n",
561f3d63720SJakub Kicinski 				 opt->parent, opt->handle, i);
562f3d63720SJakub Kicinski 			return false;
563f3d63720SJakub Kicinski 		}
564f3d63720SJakub Kicinski 		if (band->min > S32_MAX) {
565f3d63720SJakub Kicinski 			nfp_warn(cpp, "GRED offload failed - threshold too large %d > %d (p:%08x h:%08x vq:%d)\n",
566f3d63720SJakub Kicinski 				 band->min, S32_MAX, opt->parent, opt->handle,
567f3d63720SJakub Kicinski 				 i);
568f3d63720SJakub Kicinski 			return false;
569f3d63720SJakub Kicinski 		}
570f3d63720SJakub Kicinski 	}
571f3d63720SJakub Kicinski 
572f3d63720SJakub Kicinski 	return true;
573f3d63720SJakub Kicinski }
574f3d63720SJakub Kicinski 
575f3d63720SJakub Kicinski static int
nfp_abm_gred_replace(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_gred_qopt_offload * opt)576f3d63720SJakub Kicinski nfp_abm_gred_replace(struct net_device *netdev, struct nfp_abm_link *alink,
577f3d63720SJakub Kicinski 		     struct tc_gred_qopt_offload *opt)
578f3d63720SJakub Kicinski {
579f3d63720SJakub Kicinski 	struct nfp_qdisc *qdisc;
580f3d63720SJakub Kicinski 	unsigned int i;
581f3d63720SJakub Kicinski 	int ret;
582f3d63720SJakub Kicinski 
583f3d63720SJakub Kicinski 	ret = nfp_abm_qdisc_replace(netdev, alink, NFP_QDISC_GRED, opt->parent,
584f3d63720SJakub Kicinski 				    opt->handle, 0, &qdisc);
585f3d63720SJakub Kicinski 	if (ret < 0)
586f3d63720SJakub Kicinski 		return ret;
587f3d63720SJakub Kicinski 
588f3d63720SJakub Kicinski 	qdisc->params_ok = nfp_abm_gred_check_params(alink, opt);
589f3d63720SJakub Kicinski 	if (qdisc->params_ok) {
590f3d63720SJakub Kicinski 		qdisc->red.num_bands = opt->set.dp_cnt;
591340a4864SJakub Kicinski 		for (i = 0; i < qdisc->red.num_bands; i++) {
592340a4864SJakub Kicinski 			qdisc->red.band[i].ecn = opt->set.tab[i].is_ecn;
593f3d63720SJakub Kicinski 			qdisc->red.band[i].threshold = opt->set.tab[i].min;
594f3d63720SJakub Kicinski 		}
595340a4864SJakub Kicinski 	}
596f3d63720SJakub Kicinski 
597f3d63720SJakub Kicinski 	if (qdisc->use_cnt)
598f3d63720SJakub Kicinski 		nfp_abm_qdisc_offload_update(alink);
599f3d63720SJakub Kicinski 
600f3d63720SJakub Kicinski 	return 0;
601f3d63720SJakub Kicinski }
602f3d63720SJakub Kicinski 
nfp_abm_setup_tc_gred(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_gred_qopt_offload * opt)603f3d63720SJakub Kicinski int nfp_abm_setup_tc_gred(struct net_device *netdev, struct nfp_abm_link *alink,
604f3d63720SJakub Kicinski 			  struct tc_gred_qopt_offload *opt)
605f3d63720SJakub Kicinski {
606f3d63720SJakub Kicinski 	switch (opt->command) {
607f3d63720SJakub Kicinski 	case TC_GRED_REPLACE:
608f3d63720SJakub Kicinski 		return nfp_abm_gred_replace(netdev, alink, opt);
609f3d63720SJakub Kicinski 	case TC_GRED_DESTROY:
610f3d63720SJakub Kicinski 		nfp_abm_qdisc_destroy(netdev, alink, opt->handle);
611f3d63720SJakub Kicinski 		return 0;
612f3d63720SJakub Kicinski 	case TC_GRED_STATS:
613f3d63720SJakub Kicinski 		return nfp_abm_gred_stats(alink, opt->handle, &opt->stats);
614f3d63720SJakub Kicinski 	default:
615f3d63720SJakub Kicinski 		return -EOPNOTSUPP;
616f3d63720SJakub Kicinski 	}
617f3d63720SJakub Kicinski }
618f3d63720SJakub Kicinski 
619f3d63720SJakub Kicinski static int
nfp_abm_red_xstats(struct nfp_abm_link * alink,struct tc_red_qopt_offload * opt)620bd3b5d46SJakub Kicinski nfp_abm_red_xstats(struct nfp_abm_link *alink, struct tc_red_qopt_offload *opt)
62147330f9bSJakub Kicinski {
622bd3b5d46SJakub Kicinski 	struct nfp_qdisc *qdisc;
62347330f9bSJakub Kicinski 
624bd3b5d46SJakub Kicinski 	nfp_abm_stats_update(alink);
625bd3b5d46SJakub Kicinski 
626bd3b5d46SJakub Kicinski 	qdisc = nfp_abm_qdisc_find(alink, opt->handle);
627bd3b5d46SJakub Kicinski 	if (!qdisc || !qdisc->offloaded)
62847330f9bSJakub Kicinski 		return -EOPNOTSUPP;
62947330f9bSJakub Kicinski 
630990b50a5SJakub Kicinski 	nfp_abm_stats_red_calculate(&qdisc->red.band[0].xstats,
631990b50a5SJakub Kicinski 				    &qdisc->red.band[0].prev_xstats,
632bd3b5d46SJakub Kicinski 				    opt->xstats);
633990b50a5SJakub Kicinski 	qdisc->red.band[0].prev_xstats = qdisc->red.band[0].xstats;
634bd3b5d46SJakub Kicinski 	return 0;
63547330f9bSJakub Kicinski }
63647330f9bSJakub Kicinski 
637bd3b5d46SJakub Kicinski static int
nfp_abm_red_stats(struct nfp_abm_link * alink,u32 handle,struct tc_qopt_offload_stats * stats)638bd3b5d46SJakub Kicinski nfp_abm_red_stats(struct nfp_abm_link *alink, u32 handle,
639bd3b5d46SJakub Kicinski 		  struct tc_qopt_offload_stats *stats)
64047330f9bSJakub Kicinski {
641bd3b5d46SJakub Kicinski 	struct nfp_qdisc *qdisc;
64247330f9bSJakub Kicinski 
643bd3b5d46SJakub Kicinski 	nfp_abm_stats_update(alink);
6444f5681d0SJakub Kicinski 
645bd3b5d46SJakub Kicinski 	qdisc = nfp_abm_qdisc_find(alink, handle);
646bd3b5d46SJakub Kicinski 	if (!qdisc)
647bd3b5d46SJakub Kicinski 		return -EOPNOTSUPP;
648bd3b5d46SJakub Kicinski 	/* If the qdisc offload has stopped we may need to adjust the backlog
649bd3b5d46SJakub Kicinski 	 * counters back so carry on even if qdisc is not currently offloaded.
650bd3b5d46SJakub Kicinski 	 */
65147330f9bSJakub Kicinski 
652990b50a5SJakub Kicinski 	nfp_abm_stats_calculate(&qdisc->red.band[0].stats,
653990b50a5SJakub Kicinski 				&qdisc->red.band[0].prev_stats,
654bd3b5d46SJakub Kicinski 				stats->bstats, stats->qstats);
655990b50a5SJakub Kicinski 	qdisc->red.band[0].prev_stats = qdisc->red.band[0].stats;
6566666f545SJakub Kicinski 
657bd3b5d46SJakub Kicinski 	return qdisc->offloaded ? 0 : -EOPNOTSUPP;
65847330f9bSJakub Kicinski }
65947330f9bSJakub Kicinski 
660032748acSJakub Kicinski static bool
nfp_abm_red_check_params(struct nfp_abm_link * alink,struct tc_red_qopt_offload * opt)661032748acSJakub Kicinski nfp_abm_red_check_params(struct nfp_abm_link *alink,
662032748acSJakub Kicinski 			 struct tc_red_qopt_offload *opt)
663032748acSJakub Kicinski {
664032748acSJakub Kicinski 	struct nfp_cpp *cpp = alink->abm->app->cpp;
665340a4864SJakub Kicinski 	struct nfp_abm *abm = alink->abm;
666032748acSJakub Kicinski 
667340a4864SJakub Kicinski 	if (!opt->set.is_ecn && !nfp_abm_has_drop(abm)) {
668032748acSJakub Kicinski 		nfp_warn(cpp, "RED offload failed - drop is not supported (ECN option required) (p:%08x h:%08x)\n",
669032748acSJakub Kicinski 			 opt->parent, opt->handle);
670032748acSJakub Kicinski 		return false;
671032748acSJakub Kicinski 	}
672340a4864SJakub Kicinski 	if (opt->set.is_ecn && !nfp_abm_has_mark(abm)) {
673340a4864SJakub Kicinski 		nfp_warn(cpp, "RED offload failed - ECN marking not supported (p:%08x h:%08x)\n",
674340a4864SJakub Kicinski 			 opt->parent, opt->handle);
675340a4864SJakub Kicinski 		return false;
676340a4864SJakub Kicinski 	}
6776e5a716fSJakub Kicinski 	if (opt->set.is_harddrop) {
6786e5a716fSJakub Kicinski 		nfp_warn(cpp, "RED offload failed - harddrop is not supported (p:%08x h:%08x)\n",
6796e5a716fSJakub Kicinski 			 opt->parent, opt->handle);
6806e5a716fSJakub Kicinski 		return false;
6816e5a716fSJakub Kicinski 	}
682032748acSJakub Kicinski 	if (opt->set.min != opt->set.max) {
683032748acSJakub Kicinski 		nfp_warn(cpp, "RED offload failed - unsupported min/max parameters (p:%08x h:%08x)\n",
684032748acSJakub Kicinski 			 opt->parent, opt->handle);
685032748acSJakub Kicinski 		return false;
686032748acSJakub Kicinski 	}
687cae5f48eSJakub Kicinski 	if (opt->set.min > NFP_ABM_LVL_INFINITY) {
688cae5f48eSJakub Kicinski 		nfp_warn(cpp, "RED offload failed - threshold too large %d > %d (p:%08x h:%08x)\n",
689cae5f48eSJakub Kicinski 			 opt->set.min, NFP_ABM_LVL_INFINITY, opt->parent,
690cae5f48eSJakub Kicinski 			 opt->handle);
691cae5f48eSJakub Kicinski 		return false;
692cae5f48eSJakub Kicinski 	}
693032748acSJakub Kicinski 
694032748acSJakub Kicinski 	return true;
695032748acSJakub Kicinski }
696032748acSJakub Kicinski 
69747330f9bSJakub Kicinski static int
nfp_abm_red_replace(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_red_qopt_offload * opt)69847330f9bSJakub Kicinski nfp_abm_red_replace(struct net_device *netdev, struct nfp_abm_link *alink,
69947330f9bSJakub Kicinski 		    struct tc_red_qopt_offload *opt)
70047330f9bSJakub Kicinski {
7014f5681d0SJakub Kicinski 	struct nfp_qdisc *qdisc;
7024f5681d0SJakub Kicinski 	int ret;
7034f5681d0SJakub Kicinski 
7044f5681d0SJakub Kicinski 	ret = nfp_abm_qdisc_replace(netdev, alink, NFP_QDISC_RED, opt->parent,
705aee7539cSJakub Kicinski 				    opt->handle, 1, &qdisc);
706bd3b5d46SJakub Kicinski 	if (ret < 0)
707bd3b5d46SJakub Kicinski 		return ret;
7084f5681d0SJakub Kicinski 
7096c5dbda0SJakub Kicinski 	/* If limit != 0 child gets reset */
7106c5dbda0SJakub Kicinski 	if (opt->set.limit) {
7116c5dbda0SJakub Kicinski 		if (nfp_abm_qdisc_child_valid(qdisc, 0))
7126c5dbda0SJakub Kicinski 			qdisc->children[0]->use_cnt--;
7136c5dbda0SJakub Kicinski 		qdisc->children[0] = NULL;
7146c5dbda0SJakub Kicinski 	} else {
7156c5dbda0SJakub Kicinski 		/* Qdisc was just allocated without a limit will use noop_qdisc,
7166c5dbda0SJakub Kicinski 		 * i.e. a block hole.
7176c5dbda0SJakub Kicinski 		 */
7186c5dbda0SJakub Kicinski 		if (!ret)
7196c5dbda0SJakub Kicinski 			qdisc->children[0] = NFP_QDISC_UNTRACKED;
7206c5dbda0SJakub Kicinski 	}
7216c5dbda0SJakub Kicinski 
72252db4eacSJakub Kicinski 	qdisc->params_ok = nfp_abm_red_check_params(alink, opt);
723990b50a5SJakub Kicinski 	if (qdisc->params_ok) {
724990b50a5SJakub Kicinski 		qdisc->red.num_bands = 1;
725340a4864SJakub Kicinski 		qdisc->red.band[0].ecn = opt->set.is_ecn;
726990b50a5SJakub Kicinski 		qdisc->red.band[0].threshold = opt->set.min;
727990b50a5SJakub Kicinski 	}
72847330f9bSJakub Kicinski 
729bd3b5d46SJakub Kicinski 	if (qdisc->use_cnt == 1)
730bd3b5d46SJakub Kicinski 		nfp_abm_qdisc_offload_update(alink);
73147330f9bSJakub Kicinski 
73247330f9bSJakub Kicinski 	return 0;
73347330f9bSJakub Kicinski }
73447330f9bSJakub Kicinski 
nfp_abm_setup_tc_red(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_red_qopt_offload * opt)73547330f9bSJakub Kicinski int nfp_abm_setup_tc_red(struct net_device *netdev, struct nfp_abm_link *alink,
73647330f9bSJakub Kicinski 			 struct tc_red_qopt_offload *opt)
73747330f9bSJakub Kicinski {
73847330f9bSJakub Kicinski 	switch (opt->command) {
73947330f9bSJakub Kicinski 	case TC_RED_REPLACE:
74047330f9bSJakub Kicinski 		return nfp_abm_red_replace(netdev, alink, opt);
74147330f9bSJakub Kicinski 	case TC_RED_DESTROY:
742bd3b5d46SJakub Kicinski 		nfp_abm_qdisc_destroy(netdev, alink, opt->handle);
74347330f9bSJakub Kicinski 		return 0;
74447330f9bSJakub Kicinski 	case TC_RED_STATS:
745bd3b5d46SJakub Kicinski 		return nfp_abm_red_stats(alink, opt->handle, &opt->stats);
74647330f9bSJakub Kicinski 	case TC_RED_XSTATS:
74747330f9bSJakub Kicinski 		return nfp_abm_red_xstats(alink, opt);
7486b8417b7SJakub Kicinski 	case TC_RED_GRAFT:
7496b8417b7SJakub Kicinski 		return nfp_abm_qdisc_graft(alink, opt->handle,
7506b8417b7SJakub Kicinski 					   opt->child_handle, 0);
75147330f9bSJakub Kicinski 	default:
75247330f9bSJakub Kicinski 		return -EOPNOTSUPP;
75347330f9bSJakub Kicinski 	}
75447330f9bSJakub Kicinski }
75547330f9bSJakub Kicinski 
75647330f9bSJakub Kicinski static int
nfp_abm_mq_create(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_mq_qopt_offload * opt)7574f5681d0SJakub Kicinski nfp_abm_mq_create(struct net_device *netdev, struct nfp_abm_link *alink,
7584f5681d0SJakub Kicinski 		  struct tc_mq_qopt_offload *opt)
7594f5681d0SJakub Kicinski {
7604f5681d0SJakub Kicinski 	struct nfp_qdisc *qdisc;
7616c5dbda0SJakub Kicinski 	int ret;
7624f5681d0SJakub Kicinski 
7636c5dbda0SJakub Kicinski 	ret = nfp_abm_qdisc_replace(netdev, alink, NFP_QDISC_MQ,
7646c5dbda0SJakub Kicinski 				    TC_H_ROOT, opt->handle, alink->total_queues,
7656c5dbda0SJakub Kicinski 				    &qdisc);
7666c5dbda0SJakub Kicinski 	if (ret < 0)
7676c5dbda0SJakub Kicinski 		return ret;
768bd3b5d46SJakub Kicinski 
769bd3b5d46SJakub Kicinski 	qdisc->params_ok = true;
770bd3b5d46SJakub Kicinski 	qdisc->offloaded = true;
771bd3b5d46SJakub Kicinski 	nfp_abm_qdisc_offload_update(alink);
7726c5dbda0SJakub Kicinski 	return 0;
7734f5681d0SJakub Kicinski }
7744f5681d0SJakub Kicinski 
775bd3b5d46SJakub Kicinski static int
nfp_abm_mq_stats(struct nfp_abm_link * alink,u32 handle,struct tc_qopt_offload_stats * stats)776bd3b5d46SJakub Kicinski nfp_abm_mq_stats(struct nfp_abm_link *alink, u32 handle,
777bd3b5d46SJakub Kicinski 		 struct tc_qopt_offload_stats *stats)
778bd3b5d46SJakub Kicinski {
779bd3b5d46SJakub Kicinski 	struct nfp_qdisc *qdisc, *red;
780990b50a5SJakub Kicinski 	unsigned int i, j;
781bd3b5d46SJakub Kicinski 
782bd3b5d46SJakub Kicinski 	qdisc = nfp_abm_qdisc_find(alink, handle);
783bd3b5d46SJakub Kicinski 	if (!qdisc)
784bd3b5d46SJakub Kicinski 		return -EOPNOTSUPP;
785bd3b5d46SJakub Kicinski 
786bd3b5d46SJakub Kicinski 	nfp_abm_stats_update(alink);
787bd3b5d46SJakub Kicinski 
788bd3b5d46SJakub Kicinski 	/* MQ stats are summed over the children in the core, so we need
789bd3b5d46SJakub Kicinski 	 * to add up the unreported child values.
790bd3b5d46SJakub Kicinski 	 */
791bd3b5d46SJakub Kicinski 	memset(&qdisc->mq.stats, 0, sizeof(qdisc->mq.stats));
792bd3b5d46SJakub Kicinski 	memset(&qdisc->mq.prev_stats, 0, sizeof(qdisc->mq.prev_stats));
793bd3b5d46SJakub Kicinski 
794bd3b5d46SJakub Kicinski 	for (i = 0; i < qdisc->num_children; i++) {
795bd3b5d46SJakub Kicinski 		if (!nfp_abm_qdisc_child_valid(qdisc, i))
796bd3b5d46SJakub Kicinski 			continue;
797bd3b5d46SJakub Kicinski 
798bd3b5d46SJakub Kicinski 		if (!nfp_abm_qdisc_is_red(qdisc->children[i]))
799bd3b5d46SJakub Kicinski 			continue;
800bd3b5d46SJakub Kicinski 		red = qdisc->children[i];
801bd3b5d46SJakub Kicinski 
802990b50a5SJakub Kicinski 		for (j = 0; j < red->red.num_bands; j++) {
803bd3b5d46SJakub Kicinski 			nfp_abm_stats_propagate(&qdisc->mq.stats,
804990b50a5SJakub Kicinski 						&red->red.band[j].stats);
805bd3b5d46SJakub Kicinski 			nfp_abm_stats_propagate(&qdisc->mq.prev_stats,
806990b50a5SJakub Kicinski 						&red->red.band[j].prev_stats);
807990b50a5SJakub Kicinski 		}
808bd3b5d46SJakub Kicinski 	}
809bd3b5d46SJakub Kicinski 
810bd3b5d46SJakub Kicinski 	nfp_abm_stats_calculate(&qdisc->mq.stats, &qdisc->mq.prev_stats,
811bd3b5d46SJakub Kicinski 				stats->bstats, stats->qstats);
812bd3b5d46SJakub Kicinski 
813bd3b5d46SJakub Kicinski 	return qdisc->offloaded ? 0 : -EOPNOTSUPP;
814bd3b5d46SJakub Kicinski }
815bd3b5d46SJakub Kicinski 
nfp_abm_setup_tc_mq(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_mq_qopt_offload * opt)81647330f9bSJakub Kicinski int nfp_abm_setup_tc_mq(struct net_device *netdev, struct nfp_abm_link *alink,
81747330f9bSJakub Kicinski 			struct tc_mq_qopt_offload *opt)
81847330f9bSJakub Kicinski {
81947330f9bSJakub Kicinski 	switch (opt->command) {
82047330f9bSJakub Kicinski 	case TC_MQ_CREATE:
8214f5681d0SJakub Kicinski 		return nfp_abm_mq_create(netdev, alink, opt);
82247330f9bSJakub Kicinski 	case TC_MQ_DESTROY:
8234f5681d0SJakub Kicinski 		nfp_abm_qdisc_destroy(netdev, alink, opt->handle);
82447330f9bSJakub Kicinski 		return 0;
82547330f9bSJakub Kicinski 	case TC_MQ_STATS:
826bd3b5d46SJakub Kicinski 		return nfp_abm_mq_stats(alink, opt->handle, &opt->stats);
8276b8417b7SJakub Kicinski 	case TC_MQ_GRAFT:
8286b8417b7SJakub Kicinski 		return nfp_abm_qdisc_graft(alink, opt->handle,
8296b8417b7SJakub Kicinski 					   opt->graft_params.child_handle,
8306b8417b7SJakub Kicinski 					   opt->graft_params.queue);
83147330f9bSJakub Kicinski 	default:
83247330f9bSJakub Kicinski 		return -EOPNOTSUPP;
83347330f9bSJakub Kicinski 	}
83447330f9bSJakub Kicinski }
83518531258SJakub Kicinski 
nfp_abm_setup_root(struct net_device * netdev,struct nfp_abm_link * alink,struct tc_root_qopt_offload * opt)83618531258SJakub Kicinski int nfp_abm_setup_root(struct net_device *netdev, struct nfp_abm_link *alink,
83718531258SJakub Kicinski 		       struct tc_root_qopt_offload *opt)
83818531258SJakub Kicinski {
83918531258SJakub Kicinski 	if (opt->ingress)
84018531258SJakub Kicinski 		return -EOPNOTSUPP;
8416b8417b7SJakub Kicinski 	if (alink->root_qdisc)
8426b8417b7SJakub Kicinski 		alink->root_qdisc->use_cnt--;
84318531258SJakub Kicinski 	alink->root_qdisc = nfp_abm_qdisc_find(alink, opt->handle);
8446b8417b7SJakub Kicinski 	if (alink->root_qdisc)
8456b8417b7SJakub Kicinski 		alink->root_qdisc->use_cnt++;
84618531258SJakub Kicinski 
847bd3b5d46SJakub Kicinski 	nfp_abm_qdisc_offload_update(alink);
848bd3b5d46SJakub Kicinski 
84918531258SJakub Kicinski 	return 0;
85018531258SJakub Kicinski }
851