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
12struct nfp_abm_u32_match {
13 u32 handle;
14 u32 band;
15 u8 mask;
16 u8 val;
17 struct list_head list;
18};
19
20static bool
21nfp_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 */
107static unsigned int
108nfp_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
119static 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
154static void
155nfp_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
169static int
170nfp_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
222err_delete:
223 nfp_abm_u32_knode_delete(alink, knode);
224 return -EOPNOTSUPP;
225}
226
227static 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
265static LIST_HEAD(nfp_abm_block_cb_list);
266
267int 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

source code of linux/drivers/net/ethernet/netronome/nfp/abm/cls.c