Commit | Line | Data |
---|---|---|
fe3490e6 HV |
1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | /* Microsemi Ocelot Switch driver | |
3 | * Copyright (c) 2019 Microsemi Corporation | |
4 | */ | |
5 | ||
6 | #include <net/pkt_cls.h> | |
7 | #include <net/tc_act/tc_gact.h> | |
8 | ||
9 | #include "ocelot_ace.h" | |
10 | ||
11 | struct ocelot_port_block { | |
12 | struct ocelot_acl_block *block; | |
13 | struct ocelot_port *port; | |
14 | }; | |
15 | ||
16 | static u16 get_prio(u32 prio) | |
17 | { | |
18 | /* prio starts from 0x1000 while the ids starts from 0 */ | |
19 | return prio >> 16; | |
20 | } | |
21 | ||
f9e30088 | 22 | static int ocelot_flower_parse_action(struct flow_cls_offload *f, |
fe3490e6 HV |
23 | struct ocelot_ace_rule *rule) |
24 | { | |
25 | const struct flow_action_entry *a; | |
26 | int i; | |
27 | ||
28 | if (f->rule->action.num_entries != 1) | |
29 | return -EOPNOTSUPP; | |
30 | ||
31 | flow_action_for_each(i, a, &f->rule->action) { | |
32 | switch (a->id) { | |
33 | case FLOW_ACTION_DROP: | |
34 | rule->action = OCELOT_ACL_ACTION_DROP; | |
35 | break; | |
36 | case FLOW_ACTION_TRAP: | |
37 | rule->action = OCELOT_ACL_ACTION_TRAP; | |
38 | break; | |
39 | default: | |
40 | return -EOPNOTSUPP; | |
41 | } | |
42 | } | |
43 | ||
44 | return 0; | |
45 | } | |
46 | ||
f9e30088 | 47 | static int ocelot_flower_parse(struct flow_cls_offload *f, |
fe3490e6 HV |
48 | struct ocelot_ace_rule *ocelot_rule) |
49 | { | |
f9e30088 | 50 | struct flow_rule *rule = flow_cls_offload_flow_rule(f); |
fe3490e6 HV |
51 | struct flow_dissector *dissector = rule->match.dissector; |
52 | ||
53 | if (dissector->used_keys & | |
54 | ~(BIT(FLOW_DISSECTOR_KEY_CONTROL) | | |
55 | BIT(FLOW_DISSECTOR_KEY_BASIC) | | |
56 | BIT(FLOW_DISSECTOR_KEY_PORTS) | | |
57 | BIT(FLOW_DISSECTOR_KEY_VLAN) | | |
58 | BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | | |
59 | BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | | |
60 | BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { | |
61 | return -EOPNOTSUPP; | |
62 | } | |
63 | ||
64 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { | |
65 | struct flow_match_control match; | |
66 | ||
67 | flow_rule_match_control(rule, &match); | |
68 | } | |
69 | ||
70 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { | |
71 | struct flow_match_eth_addrs match; | |
72 | u16 proto = ntohs(f->common.protocol); | |
73 | ||
74 | /* The hw support mac matches only for MAC_ETYPE key, | |
75 | * therefore if other matches(port, tcp flags, etc) are added | |
76 | * then just bail out | |
77 | */ | |
78 | if ((dissector->used_keys & | |
79 | (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | | |
80 | BIT(FLOW_DISSECTOR_KEY_BASIC) | | |
81 | BIT(FLOW_DISSECTOR_KEY_CONTROL))) != | |
82 | (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | | |
83 | BIT(FLOW_DISSECTOR_KEY_BASIC) | | |
84 | BIT(FLOW_DISSECTOR_KEY_CONTROL))) | |
85 | return -EOPNOTSUPP; | |
86 | ||
87 | if (proto == ETH_P_IP || | |
88 | proto == ETH_P_IPV6 || | |
89 | proto == ETH_P_ARP) | |
90 | return -EOPNOTSUPP; | |
91 | ||
92 | flow_rule_match_eth_addrs(rule, &match); | |
93 | ocelot_rule->type = OCELOT_ACE_TYPE_ETYPE; | |
94 | ether_addr_copy(ocelot_rule->frame.etype.dmac.value, | |
95 | match.key->dst); | |
96 | ether_addr_copy(ocelot_rule->frame.etype.smac.value, | |
97 | match.key->src); | |
98 | ether_addr_copy(ocelot_rule->frame.etype.dmac.mask, | |
99 | match.mask->dst); | |
100 | ether_addr_copy(ocelot_rule->frame.etype.smac.mask, | |
101 | match.mask->src); | |
102 | goto finished_key_parsing; | |
103 | } | |
104 | ||
105 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { | |
106 | struct flow_match_basic match; | |
107 | ||
108 | flow_rule_match_basic(rule, &match); | |
109 | if (ntohs(match.key->n_proto) == ETH_P_IP) { | |
110 | ocelot_rule->type = OCELOT_ACE_TYPE_IPV4; | |
111 | ocelot_rule->frame.ipv4.proto.value[0] = | |
112 | match.key->ip_proto; | |
113 | ocelot_rule->frame.ipv4.proto.mask[0] = | |
114 | match.mask->ip_proto; | |
115 | } | |
116 | if (ntohs(match.key->n_proto) == ETH_P_IPV6) { | |
117 | ocelot_rule->type = OCELOT_ACE_TYPE_IPV6; | |
118 | ocelot_rule->frame.ipv6.proto.value[0] = | |
119 | match.key->ip_proto; | |
120 | ocelot_rule->frame.ipv6.proto.mask[0] = | |
121 | match.mask->ip_proto; | |
122 | } | |
123 | } | |
124 | ||
125 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) && | |
126 | ntohs(f->common.protocol) == ETH_P_IP) { | |
127 | struct flow_match_ipv4_addrs match; | |
128 | u8 *tmp; | |
129 | ||
130 | flow_rule_match_ipv4_addrs(rule, &match); | |
131 | tmp = &ocelot_rule->frame.ipv4.sip.value.addr[0]; | |
132 | memcpy(tmp, &match.key->src, 4); | |
133 | ||
134 | tmp = &ocelot_rule->frame.ipv4.sip.mask.addr[0]; | |
135 | memcpy(tmp, &match.mask->src, 4); | |
136 | ||
137 | tmp = &ocelot_rule->frame.ipv4.dip.value.addr[0]; | |
138 | memcpy(tmp, &match.key->dst, 4); | |
139 | ||
140 | tmp = &ocelot_rule->frame.ipv4.dip.mask.addr[0]; | |
141 | memcpy(tmp, &match.mask->dst, 4); | |
142 | } | |
143 | ||
144 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS) && | |
145 | ntohs(f->common.protocol) == ETH_P_IPV6) { | |
146 | return -EOPNOTSUPP; | |
147 | } | |
148 | ||
149 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { | |
150 | struct flow_match_ports match; | |
151 | ||
152 | flow_rule_match_ports(rule, &match); | |
153 | ocelot_rule->frame.ipv4.sport.value = ntohs(match.key->src); | |
154 | ocelot_rule->frame.ipv4.sport.mask = ntohs(match.mask->src); | |
155 | ocelot_rule->frame.ipv4.dport.value = ntohs(match.key->dst); | |
156 | ocelot_rule->frame.ipv4.dport.mask = ntohs(match.mask->dst); | |
157 | } | |
158 | ||
159 | if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { | |
160 | struct flow_match_vlan match; | |
161 | ||
162 | flow_rule_match_vlan(rule, &match); | |
163 | ocelot_rule->type = OCELOT_ACE_TYPE_ANY; | |
164 | ocelot_rule->vlan.vid.value = match.key->vlan_id; | |
165 | ocelot_rule->vlan.vid.mask = match.mask->vlan_id; | |
166 | ocelot_rule->vlan.pcp.value[0] = match.key->vlan_priority; | |
167 | ocelot_rule->vlan.pcp.mask[0] = match.mask->vlan_priority; | |
168 | } | |
169 | ||
170 | finished_key_parsing: | |
171 | ocelot_rule->prio = get_prio(f->common.prio); | |
172 | ocelot_rule->id = f->cookie; | |
173 | return ocelot_flower_parse_action(f, ocelot_rule); | |
174 | } | |
175 | ||
176 | static | |
f9e30088 | 177 | struct ocelot_ace_rule *ocelot_ace_rule_create(struct flow_cls_offload *f, |
fe3490e6 HV |
178 | struct ocelot_port_block *block) |
179 | { | |
180 | struct ocelot_ace_rule *rule; | |
181 | ||
182 | rule = kzalloc(sizeof(*rule), GFP_KERNEL); | |
183 | if (!rule) | |
184 | return NULL; | |
185 | ||
186 | rule->port = block->port; | |
187 | rule->chip_port = block->port->chip_port; | |
188 | return rule; | |
189 | } | |
190 | ||
f9e30088 | 191 | static int ocelot_flower_replace(struct flow_cls_offload *f, |
fe3490e6 HV |
192 | struct ocelot_port_block *port_block) |
193 | { | |
194 | struct ocelot_ace_rule *rule; | |
195 | int ret; | |
196 | ||
197 | rule = ocelot_ace_rule_create(f, port_block); | |
198 | if (!rule) | |
199 | return -ENOMEM; | |
200 | ||
201 | ret = ocelot_flower_parse(f, rule); | |
202 | if (ret) { | |
203 | kfree(rule); | |
204 | return ret; | |
205 | } | |
206 | ||
207 | ret = ocelot_ace_rule_offload_add(rule); | |
208 | if (ret) | |
209 | return ret; | |
210 | ||
211 | port_block->port->tc.offload_cnt++; | |
212 | return 0; | |
213 | } | |
214 | ||
f9e30088 | 215 | static int ocelot_flower_destroy(struct flow_cls_offload *f, |
fe3490e6 HV |
216 | struct ocelot_port_block *port_block) |
217 | { | |
218 | struct ocelot_ace_rule rule; | |
219 | int ret; | |
220 | ||
221 | rule.prio = get_prio(f->common.prio); | |
222 | rule.port = port_block->port; | |
223 | rule.id = f->cookie; | |
224 | ||
225 | ret = ocelot_ace_rule_offload_del(&rule); | |
226 | if (ret) | |
227 | return ret; | |
228 | ||
229 | port_block->port->tc.offload_cnt--; | |
230 | return 0; | |
231 | } | |
232 | ||
f9e30088 | 233 | static int ocelot_flower_stats_update(struct flow_cls_offload *f, |
fe3490e6 HV |
234 | struct ocelot_port_block *port_block) |
235 | { | |
236 | struct ocelot_ace_rule rule; | |
237 | int ret; | |
238 | ||
239 | rule.prio = get_prio(f->common.prio); | |
240 | rule.port = port_block->port; | |
241 | rule.id = f->cookie; | |
242 | ret = ocelot_ace_rule_stats_update(&rule); | |
243 | if (ret) | |
244 | return ret; | |
245 | ||
246 | flow_stats_update(&f->stats, 0x0, rule.stats.pkts, 0x0); | |
247 | return 0; | |
248 | } | |
249 | ||
f9e30088 | 250 | static int ocelot_setup_tc_cls_flower(struct flow_cls_offload *f, |
fe3490e6 HV |
251 | struct ocelot_port_block *port_block) |
252 | { | |
253 | switch (f->command) { | |
f9e30088 | 254 | case FLOW_CLS_REPLACE: |
fe3490e6 | 255 | return ocelot_flower_replace(f, port_block); |
f9e30088 | 256 | case FLOW_CLS_DESTROY: |
fe3490e6 | 257 | return ocelot_flower_destroy(f, port_block); |
f9e30088 | 258 | case FLOW_CLS_STATS: |
fe3490e6 HV |
259 | return ocelot_flower_stats_update(f, port_block); |
260 | default: | |
261 | return -EOPNOTSUPP; | |
262 | } | |
263 | } | |
264 | ||
265 | static int ocelot_setup_tc_block_cb_flower(enum tc_setup_type type, | |
266 | void *type_data, void *cb_priv) | |
267 | { | |
268 | struct ocelot_port_block *port_block = cb_priv; | |
269 | ||
270 | if (!tc_cls_can_offload_and_chain0(port_block->port->dev, type_data)) | |
271 | return -EOPNOTSUPP; | |
272 | ||
273 | switch (type) { | |
274 | case TC_SETUP_CLSFLOWER: | |
275 | return ocelot_setup_tc_cls_flower(type_data, cb_priv); | |
276 | case TC_SETUP_CLSMATCHALL: | |
277 | return 0; | |
278 | default: | |
279 | return -EOPNOTSUPP; | |
280 | } | |
281 | } | |
282 | ||
283 | static struct ocelot_port_block* | |
284 | ocelot_port_block_create(struct ocelot_port *port) | |
285 | { | |
286 | struct ocelot_port_block *port_block; | |
287 | ||
288 | port_block = kzalloc(sizeof(*port_block), GFP_KERNEL); | |
289 | if (!port_block) | |
290 | return NULL; | |
291 | ||
292 | port_block->port = port; | |
293 | ||
294 | return port_block; | |
295 | } | |
296 | ||
297 | static void ocelot_port_block_destroy(struct ocelot_port_block *block) | |
298 | { | |
299 | kfree(block); | |
300 | } | |
301 | ||
955bcb6e PNA |
302 | static void ocelot_tc_block_unbind(void *cb_priv) |
303 | { | |
304 | struct ocelot_port_block *port_block = cb_priv; | |
305 | ||
306 | ocelot_port_block_destroy(port_block); | |
307 | } | |
308 | ||
fe3490e6 | 309 | int ocelot_setup_tc_block_flower_bind(struct ocelot_port *port, |
955bcb6e | 310 | struct flow_block_offload *f) |
fe3490e6 HV |
311 | { |
312 | struct ocelot_port_block *port_block; | |
955bcb6e | 313 | struct flow_block_cb *block_cb; |
fe3490e6 HV |
314 | int ret; |
315 | ||
32f8c409 | 316 | if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS) |
fe3490e6 HV |
317 | return -EOPNOTSUPP; |
318 | ||
14bfb13f PNA |
319 | block_cb = flow_block_cb_lookup(f->block, |
320 | ocelot_setup_tc_block_cb_flower, port); | |
fe3490e6 HV |
321 | if (!block_cb) { |
322 | port_block = ocelot_port_block_create(port); | |
323 | if (!port_block) | |
324 | return -ENOMEM; | |
325 | ||
0c7294dd | 326 | block_cb = flow_block_cb_alloc(ocelot_setup_tc_block_cb_flower, |
955bcb6e PNA |
327 | port, port_block, |
328 | ocelot_tc_block_unbind); | |
fe3490e6 HV |
329 | if (IS_ERR(block_cb)) { |
330 | ret = PTR_ERR(block_cb); | |
331 | goto err_cb_register; | |
332 | } | |
955bcb6e PNA |
333 | flow_block_cb_add(block_cb, f); |
334 | list_add_tail(&block_cb->driver_list, f->driver_block_list); | |
fe3490e6 | 335 | } else { |
955bcb6e | 336 | port_block = flow_block_cb_priv(block_cb); |
fe3490e6 HV |
337 | } |
338 | ||
955bcb6e | 339 | flow_block_cb_incref(block_cb); |
fe3490e6 HV |
340 | return 0; |
341 | ||
342 | err_cb_register: | |
343 | ocelot_port_block_destroy(port_block); | |
344 | ||
345 | return ret; | |
346 | } | |
347 | ||
348 | void ocelot_setup_tc_block_flower_unbind(struct ocelot_port *port, | |
955bcb6e | 349 | struct flow_block_offload *f) |
fe3490e6 | 350 | { |
955bcb6e | 351 | struct flow_block_cb *block_cb; |
fe3490e6 | 352 | |
14bfb13f PNA |
353 | block_cb = flow_block_cb_lookup(f->block, |
354 | ocelot_setup_tc_block_cb_flower, port); | |
fe3490e6 HV |
355 | if (!block_cb) |
356 | return; | |
357 | ||
955bcb6e PNA |
358 | if (!flow_block_cb_decref(block_cb)) { |
359 | flow_block_cb_remove(block_cb, f); | |
360 | list_del(&block_cb->driver_list); | |
fe3490e6 HV |
361 | } |
362 | } |