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(exts: 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(head: &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, prio: 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, packed: 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(entry: &iter->list); |
163 | kfree(objp: 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(abm: alink->abm, knode, proto, extack)) |
180 | goto err_delete; |
181 | |
182 | tos_off = proto == htons(ETH_P_IP) ? 16 : 20; |
183 | |
184 | /* Extract the DSCP Class Selector bits */ |
185 | val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff; |
186 | mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff; |
187 | |
188 | /* Check if there is no conflicting mapping and find match by handle */ |
189 | list_for_each_entry(iter, &alink->dscp_map, list) { |
190 | u32 cmask; |
191 | |
192 | if (iter->handle == knode->handle) { |
193 | match = iter; |
194 | continue; |
195 | } |
196 | |
197 | cmask = iter->mask & mask; |
198 | if ((iter->val & cmask) == (val & cmask) && |
199 | iter->band != knode->res->classid) { |
200 | NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter" ); |
201 | goto err_delete; |
202 | } |
203 | } |
204 | |
205 | if (!match) { |
206 | match = kzalloc(size: sizeof(*match), GFP_KERNEL); |
207 | if (!match) |
208 | return -ENOMEM; |
209 | list_add(new: &match->list, head: &alink->dscp_map); |
210 | } |
211 | match->handle = knode->handle; |
212 | match->band = knode->res->classid; |
213 | match->mask = mask; |
214 | match->val = val; |
215 | |
216 | err = nfp_abm_update_band_map(alink); |
217 | if (err) |
218 | goto err_delete; |
219 | |
220 | return 0; |
221 | |
222 | err_delete: |
223 | nfp_abm_u32_knode_delete(alink, knode); |
224 | return -EOPNOTSUPP; |
225 | } |
226 | |
227 | static int nfp_abm_setup_tc_block_cb(enum tc_setup_type type, |
228 | void *type_data, void *cb_priv) |
229 | { |
230 | struct tc_cls_u32_offload *cls_u32 = type_data; |
231 | struct nfp_repr *repr = cb_priv; |
232 | struct nfp_abm_link *alink; |
233 | |
234 | alink = repr->app_priv; |
235 | |
236 | if (type != TC_SETUP_CLSU32) { |
237 | NL_SET_ERR_MSG_MOD(cls_u32->common.extack, |
238 | "only offload of u32 classifier supported" ); |
239 | return -EOPNOTSUPP; |
240 | } |
241 | if (!tc_cls_can_offload_and_chain0(dev: repr->netdev, common: &cls_u32->common)) |
242 | return -EOPNOTSUPP; |
243 | |
244 | if (cls_u32->common.protocol != htons(ETH_P_IP) && |
245 | cls_u32->common.protocol != htons(ETH_P_IPV6)) { |
246 | NL_SET_ERR_MSG_MOD(cls_u32->common.extack, |
247 | "only IP and IPv6 supported as filter protocol" ); |
248 | return -EOPNOTSUPP; |
249 | } |
250 | |
251 | switch (cls_u32->command) { |
252 | case TC_CLSU32_NEW_KNODE: |
253 | case TC_CLSU32_REPLACE_KNODE: |
254 | return nfp_abm_u32_knode_replace(alink, knode: &cls_u32->knode, |
255 | proto: cls_u32->common.protocol, |
256 | extack: cls_u32->common.extack); |
257 | case TC_CLSU32_DELETE_KNODE: |
258 | nfp_abm_u32_knode_delete(alink, knode: &cls_u32->knode); |
259 | return 0; |
260 | default: |
261 | return -EOPNOTSUPP; |
262 | } |
263 | } |
264 | |
265 | static LIST_HEAD(nfp_abm_block_cb_list); |
266 | |
267 | int nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr, |
268 | struct flow_block_offload *f) |
269 | { |
270 | return flow_block_cb_setup_simple(f, driver_list: &nfp_abm_block_cb_list, |
271 | cb: nfp_abm_setup_tc_block_cb, |
272 | cb_ident: repr, cb_priv: repr, ingress_only: true); |
273 | } |
274 | |