1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2 /* Copyright (C) 2018 Netronome Systems, Inc. */
3 
4 #include <linux/bitfield.h>
5 #include <net/pkt_cls.h>
6 
7 #include "../nfpcore/nfp_cpp.h"
8 #include "../nfp_app.h"
9 #include "../nfp_net_repr.h"
10 #include "main.h"
11 
12 struct nfp_abm_u32_match {
13 	u32 handle;
14 	u32 band;
15 	u8 mask;
16 	u8 val;
17 	struct list_head list;
18 };
19 
20 static bool
21 nfp_abm_u32_check_knode(struct nfp_abm *abm, struct tc_cls_u32_knode *knode,
22 			__be16 proto, struct netlink_ext_ack *extack)
23 {
24 	struct tc_u32_key *k;
25 	unsigned int tos_off;
26 
27 	if (knode->exts && tcf_exts_has_actions(knode->exts)) {
28 		NL_SET_ERR_MSG_MOD(extack, "action offload not supported");
29 		return false;
30 	}
31 	if (knode->link_handle) {
32 		NL_SET_ERR_MSG_MOD(extack, "linking not supported");
33 		return false;
34 	}
35 	if (knode->sel->flags != TC_U32_TERMINAL) {
36 		NL_SET_ERR_MSG_MOD(extack,
37 				   "flags must be equal to TC_U32_TERMINAL");
38 		return false;
39 	}
40 	if (knode->sel->off || knode->sel->offshift || knode->sel->offmask ||
41 	    knode->sel->offoff || knode->fshift) {
42 		NL_SET_ERR_MSG_MOD(extack, "variable offsetting not supported");
43 		return false;
44 	}
45 	if (knode->sel->hoff || knode->sel->hmask) {
46 		NL_SET_ERR_MSG_MOD(extack, "hashing not supported");
47 		return false;
48 	}
49 	if (knode->val || knode->mask) {
50 		NL_SET_ERR_MSG_MOD(extack, "matching on mark not supported");
51 		return false;
52 	}
53 	if (knode->res && knode->res->class) {
54 		NL_SET_ERR_MSG_MOD(extack, "setting non-0 class not supported");
55 		return false;
56 	}
57 	if (knode->res && knode->res->classid >= abm->num_bands) {
58 		NL_SET_ERR_MSG_MOD(extack,
59 				   "classid higher than number of bands");
60 		return false;
61 	}
62 	if (knode->sel->nkeys != 1) {
63 		NL_SET_ERR_MSG_MOD(extack, "exactly one key required");
64 		return false;
65 	}
66 
67 	switch (proto) {
68 	case htons(ETH_P_IP):
69 		tos_off = 16;
70 		break;
71 	case htons(ETH_P_IPV6):
72 		tos_off = 20;
73 		break;
74 	default:
75 		NL_SET_ERR_MSG_MOD(extack, "only IP and IPv6 supported as filter protocol");
76 		return false;
77 	}
78 
79 	k = &knode->sel->keys[0];
80 	if (k->offmask) {
81 		NL_SET_ERR_MSG_MOD(extack, "offset mask - variable offsetting not supported");
82 		return false;
83 	}
84 	if (k->off) {
85 		NL_SET_ERR_MSG_MOD(extack, "only DSCP fields can be matched");
86 		return false;
87 	}
88 	if (k->val & ~k->mask) {
89 		NL_SET_ERR_MSG_MOD(extack, "mask does not cover the key");
90 		return false;
91 	}
92 	if (be32_to_cpu(k->mask) >> tos_off & ~abm->dscp_mask) {
93 		NL_SET_ERR_MSG_MOD(extack, "only high DSCP class selector bits can be used");
94 		nfp_err(abm->app->cpp,
95 			"u32 offload: requested mask %x FW can support only %x\n",
96 			be32_to_cpu(k->mask) >> tos_off, abm->dscp_mask);
97 		return false;
98 	}
99 
100 	return true;
101 }
102 
103 /* This filter list -> map conversion is O(n * m), we expect single digit or
104  * low double digit number of prios and likewise for the filters.  Also u32
105  * doesn't report stats, so it's really only setup time cost.
106  */
107 static unsigned int
108 nfp_abm_find_band_for_prio(struct nfp_abm_link *alink, unsigned int prio)
109 {
110 	struct nfp_abm_u32_match *iter;
111 
112 	list_for_each_entry(iter, &alink->dscp_map, list)
113 		if ((prio & iter->mask) == iter->val)
114 			return iter->band;
115 
116 	return alink->def_band;
117 }
118 
119 static int nfp_abm_update_band_map(struct nfp_abm_link *alink)
120 {
121 	unsigned int i, bits_per_prio, prios_per_word, base_shift;
122 	struct nfp_abm *abm = alink->abm;
123 	u32 field_mask;
124 
125 	alink->has_prio = !list_empty(&alink->dscp_map);
126 
127 	bits_per_prio = roundup_pow_of_two(order_base_2(abm->num_bands));
128 	field_mask = (1 << bits_per_prio) - 1;
129 	prios_per_word = sizeof(u32) * BITS_PER_BYTE / bits_per_prio;
130 
131 	/* FW mask applies from top bits */
132 	base_shift = 8 - order_base_2(abm->num_prios);
133 
134 	for (i = 0; i < abm->num_prios; i++) {
135 		unsigned int offset;
136 		u32 *word;
137 		u8 band;
138 
139 		word = &alink->prio_map[i / prios_per_word];
140 		offset = (i % prios_per_word) * bits_per_prio;
141 
142 		band = nfp_abm_find_band_for_prio(alink, i << base_shift);
143 
144 		*word &= ~(field_mask << offset);
145 		*word |= band << offset;
146 	}
147 
148 	/* Qdisc offload status may change if has_prio changed */
149 	nfp_abm_qdisc_offload_update(alink);
150 
151 	return nfp_abm_ctrl_prio_map_update(alink, alink->prio_map);
152 }
153 
154 static void
155 nfp_abm_u32_knode_delete(struct nfp_abm_link *alink,
156 			 struct tc_cls_u32_knode *knode)
157 {
158 	struct nfp_abm_u32_match *iter;
159 
160 	list_for_each_entry(iter, &alink->dscp_map, list)
161 		if (iter->handle == knode->handle) {
162 			list_del(&iter->list);
163 			kfree(iter);
164 			nfp_abm_update_band_map(alink);
165 			return;
166 		}
167 }
168 
169 static int
170 nfp_abm_u32_knode_replace(struct nfp_abm_link *alink,
171 			  struct tc_cls_u32_knode *knode,
172 			  __be16 proto, struct netlink_ext_ack *extack)
173 {
174 	struct nfp_abm_u32_match *match = NULL, *iter;
175 	unsigned int tos_off;
176 	u8 mask, val;
177 	int err;
178 
179 	if (!nfp_abm_u32_check_knode(alink->abm, knode, proto, extack)) {
180 		err = -EOPNOTSUPP;
181 		goto err_delete;
182 	}
183 
184 	tos_off = proto == htons(ETH_P_IP) ? 16 : 20;
185 
186 	/* Extract the DSCP Class Selector bits */
187 	val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff;
188 	mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff;
189 
190 	/* Check if there is no conflicting mapping and find match by handle */
191 	list_for_each_entry(iter, &alink->dscp_map, list) {
192 		u32 cmask;
193 
194 		if (iter->handle == knode->handle) {
195 			match = iter;
196 			continue;
197 		}
198 
199 		cmask = iter->mask & mask;
200 		if ((iter->val & cmask) == (val & cmask) &&
201 		    iter->band != knode->res->classid) {
202 			NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter");
203 			err = -EOPNOTSUPP;
204 			goto err_delete;
205 		}
206 	}
207 
208 	if (!match) {
209 		match = kzalloc(sizeof(*match), GFP_KERNEL);
210 		if (!match) {
211 			err = -ENOMEM;
212 			goto err_delete;
213 		}
214 
215 		list_add(&match->list, &alink->dscp_map);
216 	}
217 	match->handle = knode->handle;
218 	match->band = knode->res->classid;
219 	match->mask = mask;
220 	match->val = val;
221 
222 	err = nfp_abm_update_band_map(alink);
223 	if (err)
224 		goto err_delete;
225 
226 	return 0;
227 
228 err_delete:
229 	nfp_abm_u32_knode_delete(alink, knode);
230 	return err;
231 }
232 
233 static int nfp_abm_setup_tc_block_cb(enum tc_setup_type type,
234 				     void *type_data, void *cb_priv)
235 {
236 	struct tc_cls_u32_offload *cls_u32 = type_data;
237 	struct nfp_repr *repr = cb_priv;
238 	struct nfp_abm_link *alink;
239 
240 	alink = repr->app_priv;
241 
242 	if (type != TC_SETUP_CLSU32) {
243 		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
244 				   "only offload of u32 classifier supported");
245 		return -EOPNOTSUPP;
246 	}
247 	if (!tc_cls_can_offload_and_chain0(repr->netdev, &cls_u32->common))
248 		return -EOPNOTSUPP;
249 
250 	if (cls_u32->common.protocol != htons(ETH_P_IP) &&
251 	    cls_u32->common.protocol != htons(ETH_P_IPV6)) {
252 		NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
253 				   "only IP and IPv6 supported as filter protocol");
254 		return -EOPNOTSUPP;
255 	}
256 
257 	switch (cls_u32->command) {
258 	case TC_CLSU32_NEW_KNODE:
259 	case TC_CLSU32_REPLACE_KNODE:
260 		return nfp_abm_u32_knode_replace(alink, &cls_u32->knode,
261 						 cls_u32->common.protocol,
262 						 cls_u32->common.extack);
263 	case TC_CLSU32_DELETE_KNODE:
264 		nfp_abm_u32_knode_delete(alink, &cls_u32->knode);
265 		return 0;
266 	default:
267 		return -EOPNOTSUPP;
268 	}
269 }
270 
271 static LIST_HEAD(nfp_abm_block_cb_list);
272 
273 int nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr,
274 			    struct flow_block_offload *f)
275 {
276 	return flow_block_cb_setup_simple(f, &nfp_abm_block_cb_list,
277 					  nfp_abm_setup_tc_block_cb,
278 					  repr, repr, true);
279 }
280