1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2017-2020 Mellanox Technologies. All rights reserved */ |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/slab.h> |
6 | #include <linux/errno.h> |
7 | #include <linux/list.h> |
8 | #include <net/net_namespace.h> |
9 | |
10 | #include "spectrum.h" |
11 | |
12 | struct mlxsw_sp_flow_block * |
13 | mlxsw_sp_flow_block_create(struct mlxsw_sp *mlxsw_sp, struct net *net) |
14 | { |
15 | struct mlxsw_sp_flow_block *block; |
16 | |
17 | block = kzalloc(size: sizeof(*block), GFP_KERNEL); |
18 | if (!block) |
19 | return NULL; |
20 | INIT_LIST_HEAD(list: &block->binding_list); |
21 | INIT_LIST_HEAD(list: &block->mall.list); |
22 | block->mlxsw_sp = mlxsw_sp; |
23 | block->net = net; |
24 | return block; |
25 | } |
26 | |
27 | void mlxsw_sp_flow_block_destroy(struct mlxsw_sp_flow_block *block) |
28 | { |
29 | WARN_ON(!list_empty(&block->binding_list)); |
30 | kfree(objp: block); |
31 | } |
32 | |
33 | static struct mlxsw_sp_flow_block_binding * |
34 | mlxsw_sp_flow_block_lookup(struct mlxsw_sp_flow_block *block, |
35 | struct mlxsw_sp_port *mlxsw_sp_port, bool ingress) |
36 | { |
37 | struct mlxsw_sp_flow_block_binding *binding; |
38 | |
39 | list_for_each_entry(binding, &block->binding_list, list) |
40 | if (binding->mlxsw_sp_port == mlxsw_sp_port && |
41 | binding->ingress == ingress) |
42 | return binding; |
43 | return NULL; |
44 | } |
45 | |
46 | static bool |
47 | mlxsw_sp_flow_block_ruleset_bound(const struct mlxsw_sp_flow_block *block) |
48 | { |
49 | return block->ruleset_zero; |
50 | } |
51 | |
52 | static int mlxsw_sp_flow_block_bind(struct mlxsw_sp *mlxsw_sp, |
53 | struct mlxsw_sp_flow_block *block, |
54 | struct mlxsw_sp_port *mlxsw_sp_port, |
55 | bool ingress, |
56 | struct netlink_ext_ack *extack) |
57 | { |
58 | struct mlxsw_sp_flow_block_binding *binding; |
59 | int err; |
60 | |
61 | if (WARN_ON(mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress))) |
62 | return -EEXIST; |
63 | |
64 | if (ingress && block->ingress_blocker_rule_count) { |
65 | NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to ingress because it contains unsupported rules" ); |
66 | return -EOPNOTSUPP; |
67 | } |
68 | |
69 | if (!ingress && block->egress_blocker_rule_count) { |
70 | NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to egress because it contains unsupported rules" ); |
71 | return -EOPNOTSUPP; |
72 | } |
73 | |
74 | err = mlxsw_sp_mall_port_bind(block, mlxsw_sp_port, extack); |
75 | if (err) |
76 | return err; |
77 | |
78 | binding = kzalloc(size: sizeof(*binding), GFP_KERNEL); |
79 | if (!binding) { |
80 | err = -ENOMEM; |
81 | goto err_binding_alloc; |
82 | } |
83 | binding->mlxsw_sp_port = mlxsw_sp_port; |
84 | binding->ingress = ingress; |
85 | |
86 | if (mlxsw_sp_flow_block_ruleset_bound(block)) { |
87 | err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, block, binding); |
88 | if (err) |
89 | goto err_ruleset_bind; |
90 | } |
91 | |
92 | if (ingress) |
93 | block->ingress_binding_count++; |
94 | else |
95 | block->egress_binding_count++; |
96 | list_add(new: &binding->list, head: &block->binding_list); |
97 | return 0; |
98 | |
99 | err_ruleset_bind: |
100 | kfree(objp: binding); |
101 | err_binding_alloc: |
102 | mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port); |
103 | |
104 | return err; |
105 | } |
106 | |
107 | static int mlxsw_sp_flow_block_unbind(struct mlxsw_sp *mlxsw_sp, |
108 | struct mlxsw_sp_flow_block *block, |
109 | struct mlxsw_sp_port *mlxsw_sp_port, |
110 | bool ingress) |
111 | { |
112 | struct mlxsw_sp_flow_block_binding *binding; |
113 | |
114 | binding = mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress); |
115 | if (!binding) |
116 | return -ENOENT; |
117 | |
118 | list_del(entry: &binding->list); |
119 | |
120 | if (ingress) |
121 | block->ingress_binding_count--; |
122 | else |
123 | block->egress_binding_count--; |
124 | |
125 | if (mlxsw_sp_flow_block_ruleset_bound(block)) |
126 | mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, block, binding); |
127 | |
128 | kfree(objp: binding); |
129 | |
130 | mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port); |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static int mlxsw_sp_flow_block_mall_cb(struct mlxsw_sp_flow_block *flow_block, |
136 | struct tc_cls_matchall_offload *f) |
137 | { |
138 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(block: flow_block); |
139 | |
140 | switch (f->command) { |
141 | case TC_CLSMATCHALL_REPLACE: |
142 | return mlxsw_sp_mall_replace(mlxsw_sp, block: flow_block, f); |
143 | case TC_CLSMATCHALL_DESTROY: |
144 | mlxsw_sp_mall_destroy(block: flow_block, f); |
145 | return 0; |
146 | default: |
147 | return -EOPNOTSUPP; |
148 | } |
149 | } |
150 | |
151 | static int mlxsw_sp_flow_block_flower_cb(struct mlxsw_sp_flow_block *flow_block, |
152 | struct flow_cls_offload *f) |
153 | { |
154 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(block: flow_block); |
155 | |
156 | switch (f->command) { |
157 | case FLOW_CLS_REPLACE: |
158 | return mlxsw_sp_flower_replace(mlxsw_sp, block: flow_block, f); |
159 | case FLOW_CLS_DESTROY: |
160 | mlxsw_sp_flower_destroy(mlxsw_sp, block: flow_block, f); |
161 | return 0; |
162 | case FLOW_CLS_STATS: |
163 | return mlxsw_sp_flower_stats(mlxsw_sp, block: flow_block, f); |
164 | case FLOW_CLS_TMPLT_CREATE: |
165 | return mlxsw_sp_flower_tmplt_create(mlxsw_sp, block: flow_block, f); |
166 | case FLOW_CLS_TMPLT_DESTROY: |
167 | mlxsw_sp_flower_tmplt_destroy(mlxsw_sp, block: flow_block, f); |
168 | return 0; |
169 | default: |
170 | return -EOPNOTSUPP; |
171 | } |
172 | } |
173 | |
174 | static int mlxsw_sp_flow_block_cb(enum tc_setup_type type, |
175 | void *type_data, void *cb_priv) |
176 | { |
177 | struct mlxsw_sp_flow_block *flow_block = cb_priv; |
178 | |
179 | if (mlxsw_sp_flow_block_disabled(block: flow_block)) |
180 | return -EOPNOTSUPP; |
181 | |
182 | switch (type) { |
183 | case TC_SETUP_CLSMATCHALL: |
184 | return mlxsw_sp_flow_block_mall_cb(flow_block, f: type_data); |
185 | case TC_SETUP_CLSFLOWER: |
186 | return mlxsw_sp_flow_block_flower_cb(flow_block, f: type_data); |
187 | default: |
188 | return -EOPNOTSUPP; |
189 | } |
190 | } |
191 | |
192 | static void mlxsw_sp_tc_block_release(void *cb_priv) |
193 | { |
194 | struct mlxsw_sp_flow_block *flow_block = cb_priv; |
195 | |
196 | mlxsw_sp_flow_block_destroy(block: flow_block); |
197 | } |
198 | |
199 | static LIST_HEAD(mlxsw_sp_block_cb_list); |
200 | |
201 | static int mlxsw_sp_setup_tc_block_bind(struct mlxsw_sp_port *mlxsw_sp_port, |
202 | struct flow_block_offload *f, |
203 | bool ingress) |
204 | { |
205 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
206 | struct mlxsw_sp_flow_block *flow_block; |
207 | struct flow_block_cb *block_cb; |
208 | bool register_block = false; |
209 | int err; |
210 | |
211 | block_cb = flow_block_cb_lookup(block: f->block, cb: mlxsw_sp_flow_block_cb, |
212 | cb_ident: mlxsw_sp); |
213 | if (!block_cb) { |
214 | flow_block = mlxsw_sp_flow_block_create(mlxsw_sp, net: f->net); |
215 | if (!flow_block) |
216 | return -ENOMEM; |
217 | block_cb = flow_block_cb_alloc(cb: mlxsw_sp_flow_block_cb, |
218 | cb_ident: mlxsw_sp, cb_priv: flow_block, |
219 | release: mlxsw_sp_tc_block_release); |
220 | if (IS_ERR(ptr: block_cb)) { |
221 | mlxsw_sp_flow_block_destroy(block: flow_block); |
222 | return PTR_ERR(ptr: block_cb); |
223 | } |
224 | register_block = true; |
225 | } else { |
226 | flow_block = flow_block_cb_priv(block_cb); |
227 | } |
228 | flow_block_cb_incref(block_cb); |
229 | err = mlxsw_sp_flow_block_bind(mlxsw_sp, block: flow_block, |
230 | mlxsw_sp_port, ingress, extack: f->extack); |
231 | if (err) |
232 | goto err_block_bind; |
233 | |
234 | if (ingress) |
235 | mlxsw_sp_port->ing_flow_block = flow_block; |
236 | else |
237 | mlxsw_sp_port->eg_flow_block = flow_block; |
238 | |
239 | if (register_block) { |
240 | flow_block_cb_add(block_cb, offload: f); |
241 | list_add_tail(new: &block_cb->driver_list, head: &mlxsw_sp_block_cb_list); |
242 | } |
243 | |
244 | return 0; |
245 | |
246 | err_block_bind: |
247 | if (!flow_block_cb_decref(block_cb)) |
248 | flow_block_cb_free(block_cb); |
249 | return err; |
250 | } |
251 | |
252 | static void mlxsw_sp_setup_tc_block_unbind(struct mlxsw_sp_port *mlxsw_sp_port, |
253 | struct flow_block_offload *f, |
254 | bool ingress) |
255 | { |
256 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
257 | struct mlxsw_sp_flow_block *flow_block; |
258 | struct flow_block_cb *block_cb; |
259 | int err; |
260 | |
261 | block_cb = flow_block_cb_lookup(block: f->block, cb: mlxsw_sp_flow_block_cb, |
262 | cb_ident: mlxsw_sp); |
263 | if (!block_cb) |
264 | return; |
265 | |
266 | if (ingress) |
267 | mlxsw_sp_port->ing_flow_block = NULL; |
268 | else |
269 | mlxsw_sp_port->eg_flow_block = NULL; |
270 | |
271 | flow_block = flow_block_cb_priv(block_cb); |
272 | err = mlxsw_sp_flow_block_unbind(mlxsw_sp, block: flow_block, |
273 | mlxsw_sp_port, ingress); |
274 | if (!err && !flow_block_cb_decref(block_cb)) { |
275 | flow_block_cb_remove(block_cb, offload: f); |
276 | list_del(entry: &block_cb->driver_list); |
277 | } |
278 | } |
279 | |
280 | int mlxsw_sp_setup_tc_block_clsact(struct mlxsw_sp_port *mlxsw_sp_port, |
281 | struct flow_block_offload *f, |
282 | bool ingress) |
283 | { |
284 | f->driver_block_list = &mlxsw_sp_block_cb_list; |
285 | |
286 | switch (f->command) { |
287 | case FLOW_BLOCK_BIND: |
288 | return mlxsw_sp_setup_tc_block_bind(mlxsw_sp_port, f, ingress); |
289 | case FLOW_BLOCK_UNBIND: |
290 | mlxsw_sp_setup_tc_block_unbind(mlxsw_sp_port, f, ingress); |
291 | return 0; |
292 | default: |
293 | return -EOPNOTSUPP; |
294 | } |
295 | } |
296 | |