1174ab544SJakub Kicinski // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2174ab544SJakub Kicinski /* Copyright (C) 2018 Netronome Systems, Inc. */
3174ab544SJakub Kicinski
4174ab544SJakub Kicinski #include <linux/bitfield.h>
5174ab544SJakub Kicinski #include <net/pkt_cls.h>
6174ab544SJakub Kicinski
7174ab544SJakub Kicinski #include "../nfpcore/nfp_cpp.h"
8174ab544SJakub Kicinski #include "../nfp_app.h"
9174ab544SJakub Kicinski #include "../nfp_net_repr.h"
10174ab544SJakub Kicinski #include "main.h"
11174ab544SJakub Kicinski
12174ab544SJakub Kicinski struct nfp_abm_u32_match {
13174ab544SJakub Kicinski u32 handle;
14174ab544SJakub Kicinski u32 band;
15174ab544SJakub Kicinski u8 mask;
16174ab544SJakub Kicinski u8 val;
17174ab544SJakub Kicinski struct list_head list;
18174ab544SJakub Kicinski };
19174ab544SJakub Kicinski
20174ab544SJakub Kicinski static bool
nfp_abm_u32_check_knode(struct nfp_abm * abm,struct tc_cls_u32_knode * knode,__be16 proto,struct netlink_ext_ack * extack)21174ab544SJakub Kicinski nfp_abm_u32_check_knode(struct nfp_abm *abm, struct tc_cls_u32_knode *knode,
22174ab544SJakub Kicinski __be16 proto, struct netlink_ext_ack *extack)
23174ab544SJakub Kicinski {
24174ab544SJakub Kicinski struct tc_u32_key *k;
25174ab544SJakub Kicinski unsigned int tos_off;
26174ab544SJakub Kicinski
27174ab544SJakub Kicinski if (knode->exts && tcf_exts_has_actions(knode->exts)) {
28174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "action offload not supported");
29174ab544SJakub Kicinski return false;
30174ab544SJakub Kicinski }
31174ab544SJakub Kicinski if (knode->link_handle) {
32174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "linking not supported");
33174ab544SJakub Kicinski return false;
34174ab544SJakub Kicinski }
35174ab544SJakub Kicinski if (knode->sel->flags != TC_U32_TERMINAL) {
36174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack,
37174ab544SJakub Kicinski "flags must be equal to TC_U32_TERMINAL");
38174ab544SJakub Kicinski return false;
39174ab544SJakub Kicinski }
40174ab544SJakub Kicinski if (knode->sel->off || knode->sel->offshift || knode->sel->offmask ||
41174ab544SJakub Kicinski knode->sel->offoff || knode->fshift) {
42d003d772SColin Ian King NL_SET_ERR_MSG_MOD(extack, "variable offsetting not supported");
43174ab544SJakub Kicinski return false;
44174ab544SJakub Kicinski }
45174ab544SJakub Kicinski if (knode->sel->hoff || knode->sel->hmask) {
46174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "hashing not supported");
47174ab544SJakub Kicinski return false;
48174ab544SJakub Kicinski }
49174ab544SJakub Kicinski if (knode->val || knode->mask) {
50174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "matching on mark not supported");
51174ab544SJakub Kicinski return false;
52174ab544SJakub Kicinski }
53174ab544SJakub Kicinski if (knode->res && knode->res->class) {
54174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "setting non-0 class not supported");
55174ab544SJakub Kicinski return false;
56174ab544SJakub Kicinski }
57174ab544SJakub Kicinski if (knode->res && knode->res->classid >= abm->num_bands) {
58174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack,
59174ab544SJakub Kicinski "classid higher than number of bands");
60174ab544SJakub Kicinski return false;
61174ab544SJakub Kicinski }
62174ab544SJakub Kicinski if (knode->sel->nkeys != 1) {
63174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "exactly one key required");
64174ab544SJakub Kicinski return false;
65174ab544SJakub Kicinski }
66174ab544SJakub Kicinski
67174ab544SJakub Kicinski switch (proto) {
68174ab544SJakub Kicinski case htons(ETH_P_IP):
69174ab544SJakub Kicinski tos_off = 16;
70174ab544SJakub Kicinski break;
71174ab544SJakub Kicinski case htons(ETH_P_IPV6):
72174ab544SJakub Kicinski tos_off = 20;
73174ab544SJakub Kicinski break;
74174ab544SJakub Kicinski default:
75174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "only IP and IPv6 supported as filter protocol");
76174ab544SJakub Kicinski return false;
77174ab544SJakub Kicinski }
78174ab544SJakub Kicinski
79174ab544SJakub Kicinski k = &knode->sel->keys[0];
80174ab544SJakub Kicinski if (k->offmask) {
81d003d772SColin Ian King NL_SET_ERR_MSG_MOD(extack, "offset mask - variable offsetting not supported");
82174ab544SJakub Kicinski return false;
83174ab544SJakub Kicinski }
84174ab544SJakub Kicinski if (k->off) {
85174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "only DSCP fields can be matched");
86174ab544SJakub Kicinski return false;
87174ab544SJakub Kicinski }
88174ab544SJakub Kicinski if (k->val & ~k->mask) {
89174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "mask does not cover the key");
90174ab544SJakub Kicinski return false;
91174ab544SJakub Kicinski }
92174ab544SJakub Kicinski if (be32_to_cpu(k->mask) >> tos_off & ~abm->dscp_mask) {
93174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "only high DSCP class selector bits can be used");
94174ab544SJakub Kicinski nfp_err(abm->app->cpp,
95174ab544SJakub Kicinski "u32 offload: requested mask %x FW can support only %x\n",
96174ab544SJakub Kicinski be32_to_cpu(k->mask) >> tos_off, abm->dscp_mask);
97174ab544SJakub Kicinski return false;
98174ab544SJakub Kicinski }
99174ab544SJakub Kicinski
100174ab544SJakub Kicinski return true;
101174ab544SJakub Kicinski }
102174ab544SJakub Kicinski
103174ab544SJakub Kicinski /* This filter list -> map conversion is O(n * m), we expect single digit or
104174ab544SJakub Kicinski * low double digit number of prios and likewise for the filters. Also u32
105174ab544SJakub Kicinski * doesn't report stats, so it's really only setup time cost.
106174ab544SJakub Kicinski */
107174ab544SJakub Kicinski static unsigned int
nfp_abm_find_band_for_prio(struct nfp_abm_link * alink,unsigned int prio)108174ab544SJakub Kicinski nfp_abm_find_band_for_prio(struct nfp_abm_link *alink, unsigned int prio)
109174ab544SJakub Kicinski {
110174ab544SJakub Kicinski struct nfp_abm_u32_match *iter;
111174ab544SJakub Kicinski
112174ab544SJakub Kicinski list_for_each_entry(iter, &alink->dscp_map, list)
113174ab544SJakub Kicinski if ((prio & iter->mask) == iter->val)
114174ab544SJakub Kicinski return iter->band;
115174ab544SJakub Kicinski
116174ab544SJakub Kicinski return alink->def_band;
117174ab544SJakub Kicinski }
118174ab544SJakub Kicinski
nfp_abm_update_band_map(struct nfp_abm_link * alink)119174ab544SJakub Kicinski static int nfp_abm_update_band_map(struct nfp_abm_link *alink)
120174ab544SJakub Kicinski {
121174ab544SJakub Kicinski unsigned int i, bits_per_prio, prios_per_word, base_shift;
122174ab544SJakub Kicinski struct nfp_abm *abm = alink->abm;
123174ab544SJakub Kicinski u32 field_mask;
124174ab544SJakub Kicinski
125174ab544SJakub Kicinski alink->has_prio = !list_empty(&alink->dscp_map);
126174ab544SJakub Kicinski
127174ab544SJakub Kicinski bits_per_prio = roundup_pow_of_two(order_base_2(abm->num_bands));
128174ab544SJakub Kicinski field_mask = (1 << bits_per_prio) - 1;
129174ab544SJakub Kicinski prios_per_word = sizeof(u32) * BITS_PER_BYTE / bits_per_prio;
130174ab544SJakub Kicinski
131174ab544SJakub Kicinski /* FW mask applies from top bits */
132174ab544SJakub Kicinski base_shift = 8 - order_base_2(abm->num_prios);
133174ab544SJakub Kicinski
134174ab544SJakub Kicinski for (i = 0; i < abm->num_prios; i++) {
135174ab544SJakub Kicinski unsigned int offset;
136174ab544SJakub Kicinski u32 *word;
137174ab544SJakub Kicinski u8 band;
138174ab544SJakub Kicinski
139174ab544SJakub Kicinski word = &alink->prio_map[i / prios_per_word];
140174ab544SJakub Kicinski offset = (i % prios_per_word) * bits_per_prio;
141174ab544SJakub Kicinski
142174ab544SJakub Kicinski band = nfp_abm_find_band_for_prio(alink, i << base_shift);
143174ab544SJakub Kicinski
144174ab544SJakub Kicinski *word &= ~(field_mask << offset);
145174ab544SJakub Kicinski *word |= band << offset;
146174ab544SJakub Kicinski }
147174ab544SJakub Kicinski
148174ab544SJakub Kicinski /* Qdisc offload status may change if has_prio changed */
149174ab544SJakub Kicinski nfp_abm_qdisc_offload_update(alink);
150174ab544SJakub Kicinski
151174ab544SJakub Kicinski return nfp_abm_ctrl_prio_map_update(alink, alink->prio_map);
152174ab544SJakub Kicinski }
153174ab544SJakub Kicinski
154174ab544SJakub Kicinski static void
nfp_abm_u32_knode_delete(struct nfp_abm_link * alink,struct tc_cls_u32_knode * knode)155174ab544SJakub Kicinski nfp_abm_u32_knode_delete(struct nfp_abm_link *alink,
156174ab544SJakub Kicinski struct tc_cls_u32_knode *knode)
157174ab544SJakub Kicinski {
158174ab544SJakub Kicinski struct nfp_abm_u32_match *iter;
159174ab544SJakub Kicinski
160174ab544SJakub Kicinski list_for_each_entry(iter, &alink->dscp_map, list)
161174ab544SJakub Kicinski if (iter->handle == knode->handle) {
162174ab544SJakub Kicinski list_del(&iter->list);
163174ab544SJakub Kicinski kfree(iter);
164174ab544SJakub Kicinski nfp_abm_update_band_map(alink);
165174ab544SJakub Kicinski return;
166174ab544SJakub Kicinski }
167174ab544SJakub Kicinski }
168174ab544SJakub Kicinski
169174ab544SJakub Kicinski static int
nfp_abm_u32_knode_replace(struct nfp_abm_link * alink,struct tc_cls_u32_knode * knode,__be16 proto,struct netlink_ext_ack * extack)170174ab544SJakub Kicinski nfp_abm_u32_knode_replace(struct nfp_abm_link *alink,
171174ab544SJakub Kicinski struct tc_cls_u32_knode *knode,
172174ab544SJakub Kicinski __be16 proto, struct netlink_ext_ack *extack)
173174ab544SJakub Kicinski {
174174ab544SJakub Kicinski struct nfp_abm_u32_match *match = NULL, *iter;
175174ab544SJakub Kicinski unsigned int tos_off;
176174ab544SJakub Kicinski u8 mask, val;
177174ab544SJakub Kicinski int err;
178174ab544SJakub Kicinski
179*1d1997dbSJakub Kicinski if (!nfp_abm_u32_check_knode(alink->abm, knode, proto, extack))
180174ab544SJakub Kicinski goto err_delete;
181174ab544SJakub Kicinski
182174ab544SJakub Kicinski tos_off = proto == htons(ETH_P_IP) ? 16 : 20;
183174ab544SJakub Kicinski
184174ab544SJakub Kicinski /* Extract the DSCP Class Selector bits */
185174ab544SJakub Kicinski val = be32_to_cpu(knode->sel->keys[0].val) >> tos_off & 0xff;
186174ab544SJakub Kicinski mask = be32_to_cpu(knode->sel->keys[0].mask) >> tos_off & 0xff;
187174ab544SJakub Kicinski
188174ab544SJakub Kicinski /* Check if there is no conflicting mapping and find match by handle */
189174ab544SJakub Kicinski list_for_each_entry(iter, &alink->dscp_map, list) {
190174ab544SJakub Kicinski u32 cmask;
191174ab544SJakub Kicinski
192174ab544SJakub Kicinski if (iter->handle == knode->handle) {
193174ab544SJakub Kicinski match = iter;
194174ab544SJakub Kicinski continue;
195174ab544SJakub Kicinski }
196174ab544SJakub Kicinski
197174ab544SJakub Kicinski cmask = iter->mask & mask;
198174ab544SJakub Kicinski if ((iter->val & cmask) == (val & cmask) &&
199174ab544SJakub Kicinski iter->band != knode->res->classid) {
200174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(extack, "conflict with already offloaded filter");
201174ab544SJakub Kicinski goto err_delete;
202174ab544SJakub Kicinski }
203174ab544SJakub Kicinski }
204174ab544SJakub Kicinski
205174ab544SJakub Kicinski if (!match) {
206174ab544SJakub Kicinski match = kzalloc(sizeof(*match), GFP_KERNEL);
207*1d1997dbSJakub Kicinski if (!match)
208*1d1997dbSJakub Kicinski return -ENOMEM;
209174ab544SJakub Kicinski list_add(&match->list, &alink->dscp_map);
210174ab544SJakub Kicinski }
211174ab544SJakub Kicinski match->handle = knode->handle;
212174ab544SJakub Kicinski match->band = knode->res->classid;
213174ab544SJakub Kicinski match->mask = mask;
214174ab544SJakub Kicinski match->val = val;
215174ab544SJakub Kicinski
216174ab544SJakub Kicinski err = nfp_abm_update_band_map(alink);
217174ab544SJakub Kicinski if (err)
218174ab544SJakub Kicinski goto err_delete;
219174ab544SJakub Kicinski
220174ab544SJakub Kicinski return 0;
221174ab544SJakub Kicinski
222174ab544SJakub Kicinski err_delete:
223174ab544SJakub Kicinski nfp_abm_u32_knode_delete(alink, knode);
224*1d1997dbSJakub Kicinski return -EOPNOTSUPP;
225174ab544SJakub Kicinski }
226174ab544SJakub Kicinski
nfp_abm_setup_tc_block_cb(enum tc_setup_type type,void * type_data,void * cb_priv)227174ab544SJakub Kicinski static int nfp_abm_setup_tc_block_cb(enum tc_setup_type type,
228174ab544SJakub Kicinski void *type_data, void *cb_priv)
229174ab544SJakub Kicinski {
230174ab544SJakub Kicinski struct tc_cls_u32_offload *cls_u32 = type_data;
231174ab544SJakub Kicinski struct nfp_repr *repr = cb_priv;
232174ab544SJakub Kicinski struct nfp_abm_link *alink;
233174ab544SJakub Kicinski
234174ab544SJakub Kicinski alink = repr->app_priv;
235174ab544SJakub Kicinski
236174ab544SJakub Kicinski if (type != TC_SETUP_CLSU32) {
237174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
238174ab544SJakub Kicinski "only offload of u32 classifier supported");
239174ab544SJakub Kicinski return -EOPNOTSUPP;
240174ab544SJakub Kicinski }
241174ab544SJakub Kicinski if (!tc_cls_can_offload_and_chain0(repr->netdev, &cls_u32->common))
242174ab544SJakub Kicinski return -EOPNOTSUPP;
243174ab544SJakub Kicinski
244174ab544SJakub Kicinski if (cls_u32->common.protocol != htons(ETH_P_IP) &&
245174ab544SJakub Kicinski cls_u32->common.protocol != htons(ETH_P_IPV6)) {
246174ab544SJakub Kicinski NL_SET_ERR_MSG_MOD(cls_u32->common.extack,
247174ab544SJakub Kicinski "only IP and IPv6 supported as filter protocol");
248174ab544SJakub Kicinski return -EOPNOTSUPP;
249174ab544SJakub Kicinski }
250174ab544SJakub Kicinski
251174ab544SJakub Kicinski switch (cls_u32->command) {
252174ab544SJakub Kicinski case TC_CLSU32_NEW_KNODE:
253174ab544SJakub Kicinski case TC_CLSU32_REPLACE_KNODE:
254174ab544SJakub Kicinski return nfp_abm_u32_knode_replace(alink, &cls_u32->knode,
255174ab544SJakub Kicinski cls_u32->common.protocol,
256174ab544SJakub Kicinski cls_u32->common.extack);
257174ab544SJakub Kicinski case TC_CLSU32_DELETE_KNODE:
258174ab544SJakub Kicinski nfp_abm_u32_knode_delete(alink, &cls_u32->knode);
259174ab544SJakub Kicinski return 0;
260174ab544SJakub Kicinski default:
261174ab544SJakub Kicinski return -EOPNOTSUPP;
262174ab544SJakub Kicinski }
263174ab544SJakub Kicinski }
264174ab544SJakub Kicinski
265955bcb6eSPablo Neira Ayuso static LIST_HEAD(nfp_abm_block_cb_list);
266955bcb6eSPablo Neira Ayuso
nfp_abm_setup_cls_block(struct net_device * netdev,struct nfp_repr * repr,struct flow_block_offload * f)267174ab544SJakub Kicinski int nfp_abm_setup_cls_block(struct net_device *netdev, struct nfp_repr *repr,
268955bcb6eSPablo Neira Ayuso struct flow_block_offload *f)
269174ab544SJakub Kicinski {
270955bcb6eSPablo Neira Ayuso return flow_block_cb_setup_simple(f, &nfp_abm_block_cb_list,
271955bcb6eSPablo Neira Ayuso nfp_abm_setup_tc_block_cb,
2724e95bc26SPablo Neira Ayuso repr, repr, true);
273174ab544SJakub Kicinski }
274