1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2020 Marvell International Ltd. All rights reserved */ |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/list.h> |
6 | |
7 | #include "prestera.h" |
8 | #include "prestera_acl.h" |
9 | #include "prestera_flow.h" |
10 | #include "prestera_flower.h" |
11 | #include "prestera_matchall.h" |
12 | #include "prestera_span.h" |
13 | |
14 | static LIST_HEAD(prestera_block_cb_list); |
15 | |
16 | static int prestera_flow_block_mall_cb(struct prestera_flow_block *block, |
17 | struct tc_cls_matchall_offload *f) |
18 | { |
19 | switch (f->command) { |
20 | case TC_CLSMATCHALL_REPLACE: |
21 | return prestera_mall_replace(block, f); |
22 | case TC_CLSMATCHALL_DESTROY: |
23 | prestera_mall_destroy(block); |
24 | return 0; |
25 | default: |
26 | return -EOPNOTSUPP; |
27 | } |
28 | } |
29 | |
30 | static int prestera_flow_block_flower_cb(struct prestera_flow_block *block, |
31 | struct flow_cls_offload *f) |
32 | { |
33 | switch (f->command) { |
34 | case FLOW_CLS_REPLACE: |
35 | return prestera_flower_replace(block, f); |
36 | case FLOW_CLS_DESTROY: |
37 | prestera_flower_destroy(block, f); |
38 | return 0; |
39 | case FLOW_CLS_STATS: |
40 | return prestera_flower_stats(block, f); |
41 | case FLOW_CLS_TMPLT_CREATE: |
42 | return prestera_flower_tmplt_create(block, f); |
43 | case FLOW_CLS_TMPLT_DESTROY: |
44 | prestera_flower_tmplt_destroy(block, f); |
45 | return 0; |
46 | default: |
47 | return -EOPNOTSUPP; |
48 | } |
49 | } |
50 | |
51 | static int prestera_flow_block_cb(enum tc_setup_type type, |
52 | void *type_data, void *cb_priv) |
53 | { |
54 | struct prestera_flow_block *block = cb_priv; |
55 | |
56 | switch (type) { |
57 | case TC_SETUP_CLSFLOWER: |
58 | return prestera_flow_block_flower_cb(block, f: type_data); |
59 | case TC_SETUP_CLSMATCHALL: |
60 | return prestera_flow_block_mall_cb(block, f: type_data); |
61 | default: |
62 | return -EOPNOTSUPP; |
63 | } |
64 | } |
65 | |
66 | static void prestera_flow_block_destroy(void *cb_priv) |
67 | { |
68 | struct prestera_flow_block *block = cb_priv; |
69 | |
70 | prestera_flower_template_cleanup(block); |
71 | |
72 | WARN_ON(!list_empty(&block->template_list)); |
73 | WARN_ON(!list_empty(&block->binding_list)); |
74 | |
75 | kfree(objp: block); |
76 | } |
77 | |
78 | static struct prestera_flow_block * |
79 | prestera_flow_block_create(struct prestera_switch *sw, |
80 | struct net *net, |
81 | bool ingress) |
82 | { |
83 | struct prestera_flow_block *block; |
84 | |
85 | block = kzalloc(size: sizeof(*block), GFP_KERNEL); |
86 | if (!block) |
87 | return NULL; |
88 | |
89 | INIT_LIST_HEAD(list: &block->binding_list); |
90 | INIT_LIST_HEAD(list: &block->template_list); |
91 | block->net = net; |
92 | block->sw = sw; |
93 | block->mall.prio_min = UINT_MAX; |
94 | block->mall.prio_max = 0; |
95 | block->mall.bound = false; |
96 | block->ingress = ingress; |
97 | |
98 | return block; |
99 | } |
100 | |
101 | static void prestera_flow_block_release(void *cb_priv) |
102 | { |
103 | struct prestera_flow_block *block = cb_priv; |
104 | |
105 | prestera_flow_block_destroy(cb_priv: block); |
106 | } |
107 | |
108 | static bool |
109 | prestera_flow_block_is_bound(const struct prestera_flow_block *block) |
110 | { |
111 | return block->ruleset_zero; |
112 | } |
113 | |
114 | static struct prestera_flow_block_binding * |
115 | prestera_flow_block_lookup(struct prestera_flow_block *block, |
116 | struct prestera_port *port) |
117 | { |
118 | struct prestera_flow_block_binding *binding; |
119 | |
120 | list_for_each_entry(binding, &block->binding_list, list) |
121 | if (binding->port == port) |
122 | return binding; |
123 | |
124 | return NULL; |
125 | } |
126 | |
127 | static int prestera_flow_block_bind(struct prestera_flow_block *block, |
128 | struct prestera_port *port) |
129 | { |
130 | struct prestera_flow_block_binding *binding; |
131 | int err; |
132 | |
133 | binding = kzalloc(size: sizeof(*binding), GFP_KERNEL); |
134 | if (!binding) |
135 | return -ENOMEM; |
136 | |
137 | binding->span_id = PRESTERA_SPAN_INVALID_ID; |
138 | binding->port = port; |
139 | |
140 | if (prestera_flow_block_is_bound(block)) { |
141 | err = prestera_acl_ruleset_bind(ruleset: block->ruleset_zero, port); |
142 | if (err) |
143 | goto err_ruleset_bind; |
144 | } |
145 | |
146 | list_add(new: &binding->list, head: &block->binding_list); |
147 | return 0; |
148 | |
149 | err_ruleset_bind: |
150 | kfree(objp: binding); |
151 | return err; |
152 | } |
153 | |
154 | static int prestera_flow_block_unbind(struct prestera_flow_block *block, |
155 | struct prestera_port *port) |
156 | { |
157 | struct prestera_flow_block_binding *binding; |
158 | |
159 | binding = prestera_flow_block_lookup(block, port); |
160 | if (!binding) |
161 | return -ENOENT; |
162 | |
163 | list_del(entry: &binding->list); |
164 | |
165 | if (prestera_flow_block_is_bound(block)) |
166 | prestera_acl_ruleset_unbind(ruleset: block->ruleset_zero, port); |
167 | |
168 | kfree(objp: binding); |
169 | return 0; |
170 | } |
171 | |
172 | static struct prestera_flow_block * |
173 | prestera_flow_block_get(struct prestera_switch *sw, |
174 | struct flow_block_offload *f, |
175 | bool *register_block, |
176 | bool ingress) |
177 | { |
178 | struct prestera_flow_block *block; |
179 | struct flow_block_cb *block_cb; |
180 | |
181 | block_cb = flow_block_cb_lookup(block: f->block, |
182 | cb: prestera_flow_block_cb, cb_ident: sw); |
183 | if (!block_cb) { |
184 | block = prestera_flow_block_create(sw, net: f->net, ingress); |
185 | if (!block) |
186 | return ERR_PTR(error: -ENOMEM); |
187 | |
188 | block_cb = flow_block_cb_alloc(cb: prestera_flow_block_cb, |
189 | cb_ident: sw, cb_priv: block, |
190 | release: prestera_flow_block_release); |
191 | if (IS_ERR(ptr: block_cb)) { |
192 | prestera_flow_block_destroy(cb_priv: block); |
193 | return ERR_CAST(ptr: block_cb); |
194 | } |
195 | |
196 | block->block_cb = block_cb; |
197 | *register_block = true; |
198 | } else { |
199 | block = flow_block_cb_priv(block_cb); |
200 | *register_block = false; |
201 | } |
202 | |
203 | flow_block_cb_incref(block_cb); |
204 | |
205 | return block; |
206 | } |
207 | |
208 | static void prestera_flow_block_put(struct prestera_flow_block *block) |
209 | { |
210 | struct flow_block_cb *block_cb = block->block_cb; |
211 | |
212 | if (flow_block_cb_decref(block_cb)) |
213 | return; |
214 | |
215 | flow_block_cb_free(block_cb); |
216 | prestera_flow_block_destroy(cb_priv: block); |
217 | } |
218 | |
219 | static int prestera_setup_flow_block_bind(struct prestera_port *port, |
220 | struct flow_block_offload *f, bool ingress) |
221 | { |
222 | struct prestera_switch *sw = port->sw; |
223 | struct prestera_flow_block *block; |
224 | struct flow_block_cb *block_cb; |
225 | bool register_block; |
226 | int err; |
227 | |
228 | block = prestera_flow_block_get(sw, f, register_block: ®ister_block, ingress); |
229 | if (IS_ERR(ptr: block)) |
230 | return PTR_ERR(ptr: block); |
231 | |
232 | block_cb = block->block_cb; |
233 | |
234 | err = prestera_flow_block_bind(block, port); |
235 | if (err) |
236 | goto err_block_bind; |
237 | |
238 | if (register_block) { |
239 | flow_block_cb_add(block_cb, offload: f); |
240 | list_add_tail(new: &block_cb->driver_list, head: &prestera_block_cb_list); |
241 | } |
242 | |
243 | if (ingress) |
244 | port->ingress_flow_block = block; |
245 | else |
246 | port->egress_flow_block = block; |
247 | |
248 | return 0; |
249 | |
250 | err_block_bind: |
251 | prestera_flow_block_put(block); |
252 | |
253 | return err; |
254 | } |
255 | |
256 | static void prestera_setup_flow_block_unbind(struct prestera_port *port, |
257 | struct flow_block_offload *f, bool ingress) |
258 | { |
259 | struct prestera_switch *sw = port->sw; |
260 | struct prestera_flow_block *block; |
261 | struct flow_block_cb *block_cb; |
262 | int err; |
263 | |
264 | block_cb = flow_block_cb_lookup(block: f->block, cb: prestera_flow_block_cb, cb_ident: sw); |
265 | if (!block_cb) |
266 | return; |
267 | |
268 | block = flow_block_cb_priv(block_cb); |
269 | |
270 | prestera_mall_destroy(block); |
271 | |
272 | err = prestera_flow_block_unbind(block, port); |
273 | if (err) |
274 | goto error; |
275 | |
276 | if (!flow_block_cb_decref(block_cb)) { |
277 | flow_block_cb_remove(block_cb, offload: f); |
278 | list_del(entry: &block_cb->driver_list); |
279 | } |
280 | error: |
281 | if (ingress) |
282 | port->ingress_flow_block = NULL; |
283 | else |
284 | port->egress_flow_block = NULL; |
285 | } |
286 | |
287 | static int prestera_setup_flow_block_clsact(struct prestera_port *port, |
288 | struct flow_block_offload *f, |
289 | bool ingress) |
290 | { |
291 | f->driver_block_list = &prestera_block_cb_list; |
292 | |
293 | switch (f->command) { |
294 | case FLOW_BLOCK_BIND: |
295 | return prestera_setup_flow_block_bind(port, f, ingress); |
296 | case FLOW_BLOCK_UNBIND: |
297 | prestera_setup_flow_block_unbind(port, f, ingress); |
298 | return 0; |
299 | default: |
300 | return -EOPNOTSUPP; |
301 | } |
302 | } |
303 | |
304 | int prestera_flow_block_setup(struct prestera_port *port, |
305 | struct flow_block_offload *f) |
306 | { |
307 | switch (f->binder_type) { |
308 | case FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS: |
309 | return prestera_setup_flow_block_clsact(port, f, ingress: true); |
310 | case FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS: |
311 | return prestera_setup_flow_block_clsact(port, f, ingress: false); |
312 | default: |
313 | return -EOPNOTSUPP; |
314 | } |
315 | } |
316 | |