/src/openvswitch/lib/dpif-offload-dummy.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2025 Red Hat, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | #include <errno.h> |
19 | | |
20 | | #include "dpif.h" |
21 | | #include "dpif-offload.h" |
22 | | #include "dpif-offload-provider.h" |
23 | | #include "dummy.h" |
24 | | #include "id-fpool.h" |
25 | | #include "netdev-native-tnl.h" |
26 | | #include "netdev-provider.h" |
27 | | #include "odp-util.h" |
28 | | #include "util.h" |
29 | | #include "uuid.h" |
30 | | |
31 | | #include "openvswitch/json.h" |
32 | | #include "openvswitch/match.h" |
33 | | #include "openvswitch/vlog.h" |
34 | | |
35 | | VLOG_DEFINE_THIS_MODULE(dpif_offload_dummy); |
36 | | |
37 | | struct pmd_id_data { |
38 | | struct hmap_node node; |
39 | | void *flow_reference; |
40 | | unsigned pmd_id; |
41 | | }; |
42 | | |
43 | | struct dummy_offloaded_flow { |
44 | | struct hmap_node node; |
45 | | struct match match; |
46 | | const struct nlattr *actions; |
47 | | size_t actions_len; |
48 | | ovs_u128 ufid; |
49 | | uint32_t mark; |
50 | | struct dpif_flow_stats stats; |
51 | | |
52 | | /* The pmd_id_map below is also protected by the port_mutex. */ |
53 | | struct hmap pmd_id_map; |
54 | | }; |
55 | | |
56 | | struct dummy_offload { |
57 | | struct dpif_offload offload; |
58 | | struct id_fpool *flow_mark_pool; |
59 | | dpif_offload_flow_unreference_cb *unreference_cb; |
60 | | |
61 | | /* Configuration specific variables. */ |
62 | | struct ovsthread_once once_enable; /* Track first-time enablement. */ |
63 | | }; |
64 | | |
65 | | struct dummy_offload_port { |
66 | | struct dpif_offload_port pm_port; |
67 | | |
68 | | struct ovs_mutex port_mutex; /* Protect all below members. */ |
69 | | struct hmap offloaded_flows OVS_GUARDED; |
70 | | struct ovs_list hw_recv_queue OVS_GUARDED; |
71 | | |
72 | | /* Some simulated offload statistics. */ |
73 | | uint64_t rx_offload_partial OVS_GUARDED; /* Match found, CPU continues. */ |
74 | | uint64_t rx_offload_full OVS_GUARDED; /* Fully offloaded, CPU bypassed. */ |
75 | | uint64_t rx_offload_miss OVS_GUARDED; /* No HW offload rule matched. */ |
76 | | uint64_t rx_offload_pipe_abort OVS_GUARDED; /* Pipeline abort. */ |
77 | | }; |
78 | | |
79 | | struct hw_pkt_node { |
80 | | struct dp_packet *pkt; |
81 | | int queue_id; |
82 | | struct ovs_list list_node; |
83 | | }; |
84 | | |
85 | | static void dummy_flow_unreference(struct dummy_offload *, unsigned pmd_id, |
86 | | void *flow_reference); |
87 | | |
88 | | static uint32_t |
89 | | dummy_allocate_flow_mark(struct dummy_offload *offload) |
90 | 0 | { |
91 | 0 | static struct ovsthread_once init_once = OVSTHREAD_ONCE_INITIALIZER; |
92 | 0 | uint32_t flow_mark; |
93 | |
|
94 | 0 | if (ovsthread_once_start(&init_once)) { |
95 | | /* Haven't initiated yet, do it here. */ |
96 | 0 | offload->flow_mark_pool = id_fpool_create(1, 1, UINT32_MAX - 1); |
97 | 0 | ovsthread_once_done(&init_once); |
98 | 0 | } |
99 | |
|
100 | 0 | if (id_fpool_new_id(offload->flow_mark_pool, 0, &flow_mark)) { |
101 | 0 | return flow_mark; |
102 | 0 | } |
103 | | |
104 | 0 | return INVALID_FLOW_MARK; |
105 | 0 | } |
106 | | |
107 | | static void |
108 | | dummy_free_flow_mark(struct dummy_offload *offload, uint32_t flow_mark) |
109 | 0 | { |
110 | 0 | if (flow_mark != INVALID_FLOW_MARK) { |
111 | 0 | id_fpool_free_id(offload->flow_mark_pool, 0, flow_mark); |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | static struct dummy_offload_port * |
116 | | dummy_offload_port_cast(struct dpif_offload_port *port) |
117 | 0 | { |
118 | 0 | return CONTAINER_OF(port, struct dummy_offload_port, pm_port); |
119 | 0 | } |
120 | | |
121 | | static struct dummy_offload * |
122 | | dummy_offload_cast(const struct dpif_offload *offload) |
123 | 0 | { |
124 | 0 | return CONTAINER_OF(offload, struct dummy_offload, offload); |
125 | 0 | } |
126 | | |
127 | | static uint32_t |
128 | | dummy_flow_hash(const ovs_u128 *ufid) |
129 | 0 | { |
130 | 0 | return ufid->u32[0]; |
131 | 0 | } |
132 | | |
133 | | static struct pmd_id_data * |
134 | | dummy_find_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED, |
135 | | struct dummy_offloaded_flow *off_flow, |
136 | | unsigned pmd_id) |
137 | | OVS_REQUIRES(port->port_mutex) |
138 | 0 | { |
139 | 0 | size_t hash = hash_int(pmd_id, 0); |
140 | 0 | struct pmd_id_data *data; |
141 | |
|
142 | 0 | HMAP_FOR_EACH_WITH_HASH (data, node, hash, &off_flow->pmd_id_map) { |
143 | 0 | if (data->pmd_id == pmd_id) { |
144 | 0 | return data; |
145 | 0 | } |
146 | 0 | } |
147 | 0 | return NULL; |
148 | 0 | } |
149 | | |
150 | | static void |
151 | | dummy_add_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED, |
152 | | struct dummy_offloaded_flow *off_flow, unsigned pmd_id, |
153 | | void *flow_reference) |
154 | | OVS_REQUIRES(port->port_mutex) |
155 | 0 | { |
156 | 0 | struct pmd_id_data *pmd_data = xmalloc(sizeof *pmd_data); |
157 | |
|
158 | 0 | pmd_data->pmd_id = pmd_id; |
159 | 0 | pmd_data->flow_reference = flow_reference; |
160 | 0 | hmap_insert(&off_flow->pmd_id_map, &pmd_data->node, |
161 | 0 | hash_int(pmd_id, 0)); |
162 | 0 | } |
163 | | |
164 | | static void |
165 | | dummy_update_flow_pmd_data(struct dummy_offload_port *port, |
166 | | struct dummy_offloaded_flow *off_flow, |
167 | | unsigned pmd_id, void *flow_reference, |
168 | | void **previous_flow_reference) |
169 | | OVS_REQUIRES(port->port_mutex) |
170 | 0 | { |
171 | 0 | struct pmd_id_data *data = dummy_find_flow_pmd_data(port, off_flow, |
172 | 0 | pmd_id); |
173 | |
|
174 | 0 | if (data) { |
175 | 0 | *previous_flow_reference = data->flow_reference; |
176 | 0 | data->flow_reference = flow_reference; |
177 | 0 | } else { |
178 | 0 | dummy_add_flow_pmd_data(port, off_flow, pmd_id, flow_reference); |
179 | 0 | *previous_flow_reference = NULL; |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | | static bool |
184 | | dummy_del_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED, |
185 | | struct dummy_offloaded_flow *off_flow, unsigned pmd_id, |
186 | | void *flow_reference) |
187 | | OVS_REQUIRES(port->port_mutex) |
188 | 0 | { |
189 | 0 | size_t hash = hash_int(pmd_id, 0); |
190 | 0 | struct pmd_id_data *data; |
191 | |
|
192 | 0 | HMAP_FOR_EACH_WITH_HASH (data, node, hash, &off_flow->pmd_id_map) { |
193 | 0 | if (data->pmd_id == pmd_id && data->flow_reference == flow_reference) { |
194 | 0 | hmap_remove(&off_flow->pmd_id_map, &data->node); |
195 | 0 | free(data); |
196 | 0 | return true; |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | 0 | return false; |
201 | 0 | } |
202 | | |
203 | | static void |
204 | | dummy_cleanup_flow_pmd_data(struct dummy_offload *offload, |
205 | | struct dummy_offload_port *port OVS_UNUSED, |
206 | | struct dummy_offloaded_flow *off_flow) |
207 | | OVS_REQUIRES(port->port_mutex) |
208 | 0 | { |
209 | 0 | struct pmd_id_data *data; |
210 | |
|
211 | 0 | HMAP_FOR_EACH_SAFE (data, node, &off_flow->pmd_id_map) { |
212 | 0 | hmap_remove(&off_flow->pmd_id_map, &data->node); |
213 | |
|
214 | 0 | dummy_flow_unreference(offload, data->pmd_id, data->flow_reference); |
215 | 0 | free(data); |
216 | 0 | } |
217 | 0 | } |
218 | | |
219 | | static struct dummy_offloaded_flow * |
220 | | dummy_add_flow(struct dummy_offload_port *port, const ovs_u128 *ufid, |
221 | | unsigned pmd_id, void *flow_reference, uint32_t mark) |
222 | | OVS_REQUIRES(port->port_mutex) |
223 | 0 | { |
224 | 0 | struct dummy_offloaded_flow *off_flow = xzalloc(sizeof *off_flow); |
225 | |
|
226 | 0 | off_flow->mark = mark; |
227 | 0 | memcpy(&off_flow->ufid, ufid, sizeof off_flow->ufid); |
228 | 0 | hmap_init(&off_flow->pmd_id_map); |
229 | 0 | dummy_add_flow_pmd_data(port, off_flow, pmd_id, flow_reference); |
230 | |
|
231 | 0 | hmap_insert(&port->offloaded_flows, &off_flow->node, |
232 | 0 | dummy_flow_hash(ufid)); |
233 | |
|
234 | 0 | return off_flow; |
235 | 0 | } |
236 | | |
237 | | static void |
238 | | dummy_free_flow(struct dummy_offload_port *port, |
239 | | struct dummy_offloaded_flow *off_flow, bool remove_from_port) |
240 | | OVS_REQUIRES(port->port_mutex) |
241 | 0 | { |
242 | 0 | if (remove_from_port) { |
243 | 0 | hmap_remove(&port->offloaded_flows, &off_flow->node); |
244 | 0 | } |
245 | 0 | ovs_assert(!hmap_count(&off_flow->pmd_id_map)); |
246 | |
|
247 | 0 | hmap_destroy(&off_flow->pmd_id_map); |
248 | 0 | free(CONST_CAST(struct nlattr *, off_flow->actions)); |
249 | 0 | free(off_flow); |
250 | 0 | } |
251 | | |
252 | | static struct dummy_offloaded_flow * |
253 | | dummy_find_offloaded_flow(struct dummy_offload_port *port, |
254 | | const ovs_u128 *ufid) |
255 | | OVS_REQUIRES(port->port_mutex) |
256 | 0 | { |
257 | 0 | uint32_t hash = dummy_flow_hash(ufid); |
258 | 0 | struct dummy_offloaded_flow *data; |
259 | |
|
260 | 0 | HMAP_FOR_EACH_WITH_HASH (data, node, hash, &port->offloaded_flows) { |
261 | 0 | if (ovs_u128_equals(*ufid, data->ufid)) { |
262 | 0 | return data; |
263 | 0 | } |
264 | 0 | } |
265 | | |
266 | 0 | return NULL; |
267 | 0 | } |
268 | | |
269 | | static struct dummy_offloaded_flow * |
270 | | dummy_find_offloaded_flow_and_update(struct dummy_offload_port *port, |
271 | | const ovs_u128 *ufid, unsigned pmd_id, |
272 | | void *new_flow_reference, |
273 | | void **previous_flow_reference) |
274 | | OVS_REQUIRES(port->port_mutex) |
275 | 0 | { |
276 | 0 | struct dummy_offloaded_flow *off_flow; |
277 | |
|
278 | 0 | off_flow = dummy_find_offloaded_flow(port, ufid); |
279 | 0 | if (!off_flow) { |
280 | 0 | return NULL; |
281 | 0 | } |
282 | | |
283 | 0 | dummy_update_flow_pmd_data(port, off_flow, pmd_id, new_flow_reference, |
284 | 0 | previous_flow_reference); |
285 | |
|
286 | 0 | return off_flow; |
287 | 0 | } |
288 | | |
289 | | static void |
290 | | dummy_offload_enable(struct dpif_offload *dpif_offload, |
291 | | struct dpif_offload_port *port) |
292 | 0 | { |
293 | 0 | atomic_store_relaxed(&port->netdev->hw_info.post_process_api_supported, |
294 | 0 | true); |
295 | 0 | dpif_offload_set_netdev_offload(port->netdev, dpif_offload); |
296 | 0 | } |
297 | | |
298 | | static void |
299 | | dummy_offload_cleanup(struct dpif_offload_port *port) |
300 | 0 | { |
301 | 0 | dpif_offload_set_netdev_offload(port->netdev, NULL); |
302 | 0 | } |
303 | | |
304 | | static void |
305 | | dummy_free_port__(struct dummy_offload *offload, |
306 | | struct dummy_offload_port *port, bool close_netdev) |
307 | 0 | { |
308 | 0 | struct dummy_offloaded_flow *off_flow; |
309 | 0 | struct hw_pkt_node *pkt; |
310 | |
|
311 | 0 | ovs_mutex_lock(&port->port_mutex); |
312 | 0 | HMAP_FOR_EACH_POP (off_flow, node, &port->offloaded_flows) { |
313 | 0 | dummy_cleanup_flow_pmd_data(offload, port, off_flow); |
314 | 0 | dummy_free_flow(port, off_flow, false); |
315 | 0 | } |
316 | 0 | hmap_destroy(&port->offloaded_flows); |
317 | |
|
318 | 0 | LIST_FOR_EACH_POP (pkt, list_node, &port->hw_recv_queue) { |
319 | 0 | dp_packet_delete(pkt->pkt); |
320 | 0 | free(pkt); |
321 | 0 | } |
322 | |
|
323 | 0 | ovs_mutex_unlock(&port->port_mutex); |
324 | 0 | ovs_mutex_destroy(&port->port_mutex); |
325 | 0 | if (close_netdev) { |
326 | 0 | netdev_close(port->pm_port.netdev); |
327 | 0 | } |
328 | 0 | free(port); |
329 | 0 | } |
330 | | |
331 | | struct free_port_rcu { |
332 | | struct dummy_offload *offload; |
333 | | struct dummy_offload_port *port; |
334 | | }; |
335 | | |
336 | | static void |
337 | | dummy_free_port_rcu(struct free_port_rcu *fpc) |
338 | 0 | { |
339 | 0 | dummy_free_port__(fpc->offload, fpc->port, true); |
340 | 0 | free(fpc); |
341 | 0 | } |
342 | | |
343 | | static void |
344 | | dummy_free_port(struct dummy_offload *offload, struct dummy_offload_port *port) |
345 | 0 | { |
346 | 0 | struct free_port_rcu *fpc = xmalloc(sizeof *fpc); |
347 | |
|
348 | 0 | fpc->offload = offload; |
349 | 0 | fpc->port = port; |
350 | 0 | ovsrcu_postpone(dummy_free_port_rcu, fpc); |
351 | 0 | } |
352 | | |
353 | | static int |
354 | | dummy_offload_port_add(struct dpif_offload *dpif_offload, |
355 | | struct netdev *netdev, odp_port_t port_no) |
356 | 0 | { |
357 | 0 | struct dummy_offload *offload = dummy_offload_cast(dpif_offload); |
358 | 0 | struct dummy_offload_port *port = xzalloc(sizeof *port); |
359 | |
|
360 | 0 | ovs_mutex_init(&port->port_mutex); |
361 | 0 | ovs_mutex_lock(&port->port_mutex); |
362 | 0 | hmap_init(&port->offloaded_flows); |
363 | 0 | ovs_list_init(&port->hw_recv_queue); |
364 | 0 | ovs_mutex_unlock(&port->port_mutex); |
365 | |
|
366 | 0 | if (dpif_offload_port_mgr_add(dpif_offload, &port->pm_port, netdev, |
367 | 0 | port_no, false)) { |
368 | |
|
369 | 0 | if (dpif_offload_enabled()) { |
370 | 0 | dummy_offload_enable(dpif_offload, &port->pm_port); |
371 | 0 | } |
372 | 0 | return 0; |
373 | 0 | } |
374 | | |
375 | 0 | dummy_free_port__(offload, port, false); |
376 | 0 | return EEXIST; |
377 | 0 | } |
378 | | |
379 | | static int |
380 | | dummy_offload_port_del(struct dpif_offload *dpif_offload, odp_port_t port_no) |
381 | 0 | { |
382 | 0 | struct dummy_offload *offload = dummy_offload_cast(dpif_offload); |
383 | 0 | struct dpif_offload_port *port; |
384 | |
|
385 | 0 | port = dpif_offload_port_mgr_remove(dpif_offload, port_no); |
386 | 0 | if (port) { |
387 | 0 | struct dummy_offload_port *dummy_port; |
388 | |
|
389 | 0 | dummy_port = dummy_offload_port_cast(port); |
390 | 0 | if (dpif_offload_enabled()) { |
391 | 0 | dummy_offload_cleanup(port); |
392 | 0 | } |
393 | 0 | dummy_free_port(offload, dummy_port); |
394 | 0 | } |
395 | 0 | return 0; |
396 | 0 | } |
397 | | |
398 | | static struct netdev * |
399 | | dummy_offload_get_netdev(const struct dpif_offload *dpif_offload, |
400 | | odp_port_t port_no) |
401 | 0 | { |
402 | 0 | struct dpif_offload_port *port; |
403 | |
|
404 | 0 | port = dpif_offload_port_mgr_find_by_odp_port(dpif_offload, port_no); |
405 | 0 | if (!port) { |
406 | 0 | return NULL; |
407 | 0 | } |
408 | | |
409 | 0 | return port->netdev; |
410 | 0 | } |
411 | | |
412 | | static int |
413 | | dummy_offload_open(const struct dpif_offload_class *offload_class, |
414 | | struct dpif *dpif, struct dpif_offload **dpif_offload) |
415 | 0 | { |
416 | 0 | struct dummy_offload *offload; |
417 | |
|
418 | 0 | offload = xmalloc(sizeof *offload); |
419 | |
|
420 | 0 | dpif_offload_init(&offload->offload, offload_class, dpif); |
421 | 0 | offload->once_enable = (struct ovsthread_once) OVSTHREAD_ONCE_INITIALIZER; |
422 | 0 | offload->flow_mark_pool = NULL; |
423 | 0 | offload->unreference_cb = NULL; |
424 | |
|
425 | 0 | *dpif_offload = &offload->offload; |
426 | 0 | return 0; |
427 | 0 | } |
428 | | |
429 | | static void |
430 | | dummy_offload_close(struct dpif_offload *dpif_offload) |
431 | 0 | { |
432 | 0 | struct dummy_offload *offload = dummy_offload_cast(dpif_offload); |
433 | 0 | struct dpif_offload_port *port; |
434 | | |
435 | | /* The ofproto layer may not call dpif_port_del() for all ports, |
436 | | * especially internal ones, so we need to clean up any remaining ports. */ |
437 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, dpif_offload) { |
438 | 0 | dummy_offload_port_del(dpif_offload, port->port_no); |
439 | 0 | } |
440 | |
|
441 | 0 | if (offload->flow_mark_pool) { |
442 | 0 | id_fpool_destroy(offload->flow_mark_pool); |
443 | 0 | } |
444 | 0 | ovsthread_once_destroy(&offload->once_enable); |
445 | 0 | dpif_offload_destroy(dpif_offload); |
446 | 0 | free(offload); |
447 | 0 | } |
448 | | |
449 | | static void |
450 | | dummy_offload_set_config(struct dpif_offload *dpif_offload, |
451 | | const struct smap *other_cfg) |
452 | 0 | { |
453 | 0 | struct dummy_offload *offload = dummy_offload_cast(dpif_offload); |
454 | |
|
455 | 0 | if (smap_get_bool(other_cfg, "hw-offload", false)) { |
456 | 0 | if (ovsthread_once_start(&offload->once_enable)) { |
457 | 0 | struct dpif_offload_port *port; |
458 | |
|
459 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port, dpif_offload) { |
460 | 0 | dummy_offload_enable(dpif_offload, port); |
461 | 0 | } |
462 | |
|
463 | 0 | ovsthread_once_done(&offload->once_enable); |
464 | 0 | } |
465 | 0 | } |
466 | 0 | } |
467 | | |
468 | | static void |
469 | | dummy_offload_get_debug(const struct dpif_offload *offload, struct ds *ds, |
470 | | struct json *json) |
471 | 0 | { |
472 | 0 | if (json) { |
473 | 0 | struct json *json_ports = json_object_create(); |
474 | 0 | struct dpif_offload_port *port_; |
475 | |
|
476 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) { |
477 | 0 | struct dummy_offload_port *port = dummy_offload_port_cast(port_); |
478 | 0 | struct json *json_port = json_object_create(); |
479 | |
|
480 | 0 | json_object_put(json_port, "port_no", |
481 | 0 | json_integer_create(odp_to_u32(port_->port_no))); |
482 | |
|
483 | 0 | ovs_mutex_lock(&port->port_mutex); |
484 | 0 | json_object_put(json_port, "rx_offload_partial", |
485 | 0 | json_integer_create(port->rx_offload_partial)); |
486 | 0 | json_object_put(json_port, "rx_offload_full", |
487 | 0 | json_integer_create(port->rx_offload_full)); |
488 | 0 | json_object_put(json_port, "rx_offload_miss", |
489 | 0 | json_integer_create(port->rx_offload_miss)); |
490 | 0 | json_object_put(json_port, "rx_offload_pipe_abort", |
491 | 0 | json_integer_create(port->rx_offload_pipe_abort)); |
492 | 0 | ovs_mutex_unlock(&port->port_mutex); |
493 | |
|
494 | 0 | json_object_put(json_ports, netdev_get_name(port_->netdev), |
495 | 0 | json_port); |
496 | 0 | } |
497 | |
|
498 | 0 | if (!json_object_is_empty(json_ports)) { |
499 | 0 | json_object_put(json, "ports", json_ports); |
500 | 0 | } else { |
501 | 0 | json_destroy(json_ports); |
502 | 0 | } |
503 | 0 | } else if (ds) { |
504 | 0 | struct dpif_offload_port *port_; |
505 | |
|
506 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) { |
507 | 0 | struct dummy_offload_port *port = dummy_offload_port_cast(port_); |
508 | |
|
509 | 0 | ovs_mutex_lock(&port->port_mutex); |
510 | 0 | ds_put_format(ds, |
511 | 0 | " - %s: port_no: %u\n" |
512 | 0 | " rx_offload_partial : %" PRIu64 "\n" |
513 | 0 | " rx_offload_full : %" PRIu64 "\n" |
514 | 0 | " rx_offload_miss : %" PRIu64 "\n" |
515 | 0 | " rx_offload_pipe_abort: %" PRIu64 "\n", |
516 | 0 | netdev_get_name(port_->netdev), port_->port_no, |
517 | 0 | port->rx_offload_partial, port->rx_offload_full, |
518 | 0 | port->rx_offload_miss, port->rx_offload_pipe_abort); |
519 | 0 | ovs_mutex_unlock(&port->port_mutex); |
520 | 0 | } |
521 | 0 | } |
522 | 0 | } |
523 | | |
524 | | static int |
525 | | dummy_offload_get_global_stats(const struct dpif_offload *offload, |
526 | | struct netdev_custom_stats *stats) |
527 | 0 | { |
528 | | /* Add a single counter telling how many ports we are servicing. */ |
529 | 0 | stats->label = xstrdup(dpif_offload_name(offload)); |
530 | 0 | stats->size = 1; |
531 | 0 | stats->counters = xmalloc(sizeof(struct netdev_custom_counter) * 1); |
532 | 0 | stats->counters[0].value = dpif_offload_port_mgr_port_count(offload); |
533 | 0 | ovs_strzcpy(stats->counters[0].name, "Offloaded port count", |
534 | 0 | sizeof stats->counters[0].name); |
535 | |
|
536 | 0 | return 0; |
537 | 0 | } |
538 | | |
539 | | static bool |
540 | | dummy_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED, |
541 | | struct netdev *netdev) |
542 | 0 | { |
543 | 0 | return is_dummy_netdev_class(netdev->netdev_class); |
544 | 0 | } |
545 | | |
546 | | static void |
547 | | dummy_offload_log_operation(const char *op, int error, const ovs_u128 *ufid) |
548 | 0 | { |
549 | 0 | VLOG_DBG("%s to %s netdev flow "UUID_FMT, |
550 | 0 | error == 0 ? "succeed" : "failed", op, |
551 | 0 | UUID_ARGS((struct uuid *) ufid)); |
552 | 0 | } |
553 | | |
554 | | static struct dummy_offload_port * |
555 | | dummy_offload_get_port_by_netdev(const struct dpif_offload *offload, |
556 | | struct netdev *netdev) |
557 | 0 | { |
558 | 0 | struct dpif_offload_port *port; |
559 | |
|
560 | 0 | port = dpif_offload_port_mgr_find_by_netdev(offload, netdev); |
561 | 0 | if (!port) { |
562 | 0 | return NULL; |
563 | 0 | } |
564 | 0 | return dummy_offload_port_cast(port); |
565 | 0 | } |
566 | | |
567 | | static struct dummy_offload_port * |
568 | | dummy_offload_get_port_by_odp_port(const struct dpif_offload *offload_, |
569 | | odp_port_t port_no) |
570 | 0 | { |
571 | 0 | struct dpif_offload_port *port; |
572 | |
|
573 | 0 | port = dpif_offload_port_mgr_find_by_odp_port(offload_, port_no); |
574 | 0 | if (!port) { |
575 | 0 | return NULL; |
576 | 0 | } |
577 | 0 | return dummy_offload_port_cast(port); |
578 | 0 | } |
579 | | |
580 | | static int |
581 | | dummy_offload_hw_post_process(const struct dpif_offload *offload_, |
582 | | struct netdev *netdev, unsigned pmd_id, |
583 | | struct dp_packet *packet, void **flow_reference_) |
584 | 0 | { |
585 | 0 | struct dummy_offloaded_flow *off_flow; |
586 | 0 | struct dummy_offload_port *port; |
587 | 0 | void *flow_reference = NULL; |
588 | 0 | uint32_t flow_mark; |
589 | |
|
590 | 0 | port = dummy_offload_get_port_by_netdev(offload_, netdev); |
591 | 0 | if (!port || !dp_packet_has_flow_mark(packet, &flow_mark)) { |
592 | 0 | *flow_reference_ = NULL; |
593 | 0 | return 0; |
594 | 0 | } |
595 | | |
596 | 0 | ovs_mutex_lock(&port->port_mutex); |
597 | 0 | HMAP_FOR_EACH (off_flow, node, &port->offloaded_flows) { |
598 | 0 | struct pmd_id_data *pmd_data; |
599 | |
|
600 | 0 | if (flow_mark == off_flow->mark) { |
601 | 0 | pmd_data = dummy_find_flow_pmd_data(port, off_flow, pmd_id); |
602 | 0 | if (pmd_data) { |
603 | 0 | flow_reference = pmd_data->flow_reference; |
604 | 0 | } |
605 | 0 | break; |
606 | 0 | } |
607 | 0 | } |
608 | 0 | ovs_mutex_unlock(&port->port_mutex); |
609 | |
|
610 | 0 | *flow_reference_ = flow_reference; |
611 | 0 | return 0; |
612 | 0 | } |
613 | | |
614 | | static ovs_be16 |
615 | | dummy_offload_udp_tnl_get_src_port__(struct dp_packet *packet) |
616 | 0 | { |
617 | | /* Use FNV-1a hash to ensure consistent results across all platforms. The |
618 | | * standard OVS hash functions have architecture-specific implementations |
619 | | * (SSE4.2, ARM64 optimizations, etc.) that produce different outputs for |
620 | | * identical inputs, making tests non-deterministic. */ |
621 | 0 | const uint8_t *data = dp_packet_data(packet); |
622 | 0 | size_t len = dp_packet_size(packet); |
623 | 0 | uint32_t hash = 2166136261U; |
624 | 0 | uint32_t prime = 16777619U; |
625 | |
|
626 | 0 | for (size_t i = 0; i < len; i++) { |
627 | 0 | hash ^= data[i]; |
628 | 0 | hash *= prime; |
629 | 0 | } |
630 | 0 | return htons((uint16_t) hash); |
631 | 0 | } |
632 | | |
633 | | static bool |
634 | | dummy_offload_udp_tnl_get_src_port( |
635 | | const struct dpif_offload *offload OVS_UNUSED, |
636 | | const struct netdev *ingress_netdev OVS_UNUSED, |
637 | | struct dp_packet *packet, ovs_be16 *src_port) |
638 | 0 | { |
639 | 0 | *src_port = dummy_offload_udp_tnl_get_src_port__(packet); |
640 | 0 | return true; |
641 | 0 | } |
642 | | |
643 | | static bool |
644 | | dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_, |
645 | | odp_port_t in_odp, |
646 | | const struct nlattr *actions, |
647 | | size_t actions_len) |
648 | 0 | { |
649 | 0 | const struct nlattr *nla; |
650 | 0 | size_t left; |
651 | | |
652 | | /* Can we fully offload this flow? For now, only output actions are |
653 | | * supported, and only to dummy-pmd netdevs where the egress port differs |
654 | | * from the ingress port. The latter restriction ensures that the partial |
655 | | * offload test cases pass. |
656 | | * |
657 | | * The reason for supporting only dummy-pmd netdevs as output targets is |
658 | | * that they provide full protection when calling netdev_send() from any |
659 | | * thread, via a netdev-level mutex. */ |
660 | 0 | NL_ATTR_FOR_EACH (nla, left, actions, actions_len) { |
661 | 0 | enum ovs_action_attr action = nl_attr_type(nla); |
662 | |
|
663 | 0 | switch (action) { |
664 | 0 | case OVS_ACTION_ATTR_OUTPUT: { |
665 | 0 | odp_port_t out_odp = nl_attr_get_odp_port(nla); |
666 | 0 | struct dummy_offload_port *out_port; |
667 | |
|
668 | 0 | out_port = dummy_offload_get_port_by_odp_port(offload_, out_odp); |
669 | 0 | if (out_odp == in_odp || !out_port |
670 | 0 | || strcmp("dummy-pmd", |
671 | 0 | netdev_get_type(out_port->pm_port.netdev))) { |
672 | 0 | return false; |
673 | 0 | } |
674 | 0 | break; |
675 | 0 | } |
676 | | |
677 | 0 | case OVS_ACTION_ATTR_TUNNEL_PUSH: { |
678 | | /* We only support UDP tunnels, i.e. VXLAN and Geneve. */ |
679 | 0 | const struct ovs_action_push_tnl *data = nl_attr_get(nla); |
680 | |
|
681 | 0 | if (data->tnl_type != OVS_VPORT_TYPE_VXLAN |
682 | 0 | && data->tnl_type != OVS_VPORT_TYPE_GENEVE) { |
683 | 0 | return false; |
684 | 0 | } |
685 | 0 | break; |
686 | 0 | } |
687 | | |
688 | 0 | case OVS_ACTION_ATTR_UNSPEC: |
689 | 0 | case OVS_ACTION_ATTR_USERSPACE: |
690 | 0 | case OVS_ACTION_ATTR_SET: |
691 | 0 | case OVS_ACTION_ATTR_PUSH_VLAN: |
692 | 0 | case OVS_ACTION_ATTR_POP_VLAN: |
693 | 0 | case OVS_ACTION_ATTR_SAMPLE: |
694 | 0 | case OVS_ACTION_ATTR_RECIRC: |
695 | 0 | case OVS_ACTION_ATTR_HASH: |
696 | 0 | case OVS_ACTION_ATTR_PUSH_MPLS: |
697 | 0 | case OVS_ACTION_ATTR_POP_MPLS: |
698 | 0 | case OVS_ACTION_ATTR_SET_MASKED: |
699 | 0 | case OVS_ACTION_ATTR_CT: |
700 | 0 | case OVS_ACTION_ATTR_TRUNC: |
701 | 0 | case OVS_ACTION_ATTR_PUSH_ETH: |
702 | 0 | case OVS_ACTION_ATTR_POP_ETH: |
703 | 0 | case OVS_ACTION_ATTR_CT_CLEAR: |
704 | 0 | case OVS_ACTION_ATTR_PUSH_NSH: |
705 | 0 | case OVS_ACTION_ATTR_POP_NSH: |
706 | 0 | case OVS_ACTION_ATTR_METER: |
707 | 0 | case OVS_ACTION_ATTR_CLONE: |
708 | 0 | case OVS_ACTION_ATTR_CHECK_PKT_LEN: |
709 | 0 | case OVS_ACTION_ATTR_ADD_MPLS: |
710 | 0 | case OVS_ACTION_ATTR_DEC_TTL: |
711 | 0 | case OVS_ACTION_ATTR_DROP: |
712 | 0 | case OVS_ACTION_ATTR_PSAMPLE: |
713 | 0 | case OVS_ACTION_ATTR_TUNNEL_POP: |
714 | 0 | case OVS_ACTION_ATTR_LB_OUTPUT: |
715 | 0 | case __OVS_ACTION_ATTR_MAX: |
716 | 0 | default: |
717 | 0 | return false; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | return true; |
721 | 0 | } |
722 | | |
723 | | static bool |
724 | | dummy_offload_hw_process_pkt(const struct dpif_offload *offload_, |
725 | | struct dummy_offloaded_flow *flow, |
726 | | struct dp_packet *pkt) |
727 | 0 | { |
728 | 0 | uint32_t hash = dp_packet_get_rss_hash(pkt); |
729 | 0 | uint32_t pkt_size = dp_packet_size(pkt); |
730 | 0 | const struct nlattr *nla; |
731 | 0 | size_t left; |
732 | |
|
733 | 0 | if (!flow->actions) { |
734 | 0 | return false; |
735 | 0 | } |
736 | | |
737 | 0 | NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) { |
738 | 0 | bool last_action = (left <= NLA_ALIGN(nla->nla_len)); |
739 | 0 | enum ovs_action_attr action = nl_attr_type(nla); |
740 | |
|
741 | 0 | switch (action) { |
742 | 0 | case OVS_ACTION_ATTR_OUTPUT: { |
743 | 0 | odp_port_t odp_port = nl_attr_get_odp_port(nla); |
744 | 0 | struct dummy_offload_port *port; |
745 | 0 | struct dp_packet_batch batch; |
746 | 0 | int n_txq; |
747 | |
|
748 | 0 | port = dummy_offload_get_port_by_odp_port(offload_, odp_port); |
749 | 0 | if (!port) { |
750 | 0 | return false; |
751 | 0 | } |
752 | | |
753 | 0 | n_txq = netdev_n_txq(port->pm_port.netdev); |
754 | 0 | dp_packet_batch_init_packet(&batch, last_action |
755 | 0 | ? pkt |
756 | 0 | : dp_packet_clone(pkt)); |
757 | | /* As the tx-steering option is not exposed to hardware offload, |
758 | | * for now we assume hash steering based on the number of queues |
759 | | * configured for the dummy-netdev. */ |
760 | 0 | netdev_send(port->pm_port.netdev, hash % n_txq, &batch, false); |
761 | 0 | break; |
762 | 0 | } |
763 | 0 | case OVS_ACTION_ATTR_TUNNEL_PUSH: { |
764 | 0 | const struct ovs_action_push_tnl *data = nl_attr_get(nla); |
765 | 0 | struct udp_header *udp; |
766 | 0 | struct flow ovs_flow; |
767 | 0 | ovs_be16 src_port; |
768 | |
|
769 | 0 | src_port = dummy_offload_udp_tnl_get_src_port__(pkt); |
770 | 0 | netdev_tnl_push_udp_header(NULL, NULL, pkt, data); |
771 | |
|
772 | 0 | flow_extract(pkt, &ovs_flow); |
773 | 0 | udp = dp_packet_l4(pkt); |
774 | 0 | ovs_assert(ovs_flow.nw_proto == IPPROTO_UDP && udp); |
775 | |
|
776 | 0 | udp->udp_src = src_port; |
777 | 0 | break; |
778 | 0 | } |
779 | | |
780 | 0 | case OVS_ACTION_ATTR_UNSPEC: |
781 | 0 | case OVS_ACTION_ATTR_USERSPACE: |
782 | 0 | case OVS_ACTION_ATTR_SET: |
783 | 0 | case OVS_ACTION_ATTR_PUSH_VLAN: |
784 | 0 | case OVS_ACTION_ATTR_POP_VLAN: |
785 | 0 | case OVS_ACTION_ATTR_SAMPLE: |
786 | 0 | case OVS_ACTION_ATTR_RECIRC: |
787 | 0 | case OVS_ACTION_ATTR_HASH: |
788 | 0 | case OVS_ACTION_ATTR_PUSH_MPLS: |
789 | 0 | case OVS_ACTION_ATTR_POP_MPLS: |
790 | 0 | case OVS_ACTION_ATTR_SET_MASKED: |
791 | 0 | case OVS_ACTION_ATTR_CT: |
792 | 0 | case OVS_ACTION_ATTR_TRUNC: |
793 | 0 | case OVS_ACTION_ATTR_PUSH_ETH: |
794 | 0 | case OVS_ACTION_ATTR_POP_ETH: |
795 | 0 | case OVS_ACTION_ATTR_CT_CLEAR: |
796 | 0 | case OVS_ACTION_ATTR_PUSH_NSH: |
797 | 0 | case OVS_ACTION_ATTR_POP_NSH: |
798 | 0 | case OVS_ACTION_ATTR_METER: |
799 | 0 | case OVS_ACTION_ATTR_CLONE: |
800 | 0 | case OVS_ACTION_ATTR_CHECK_PKT_LEN: |
801 | 0 | case OVS_ACTION_ATTR_ADD_MPLS: |
802 | 0 | case OVS_ACTION_ATTR_DEC_TTL: |
803 | 0 | case OVS_ACTION_ATTR_DROP: |
804 | 0 | case OVS_ACTION_ATTR_PSAMPLE: |
805 | 0 | case OVS_ACTION_ATTR_TUNNEL_POP: |
806 | 0 | case OVS_ACTION_ATTR_LB_OUTPUT: |
807 | 0 | case __OVS_ACTION_ATTR_MAX: |
808 | 0 | default: |
809 | 0 | OVS_NOT_REACHED(); |
810 | 0 | } |
811 | 0 | } |
812 | | |
813 | 0 | flow->stats.n_bytes += pkt_size; |
814 | 0 | flow->stats.n_packets++; |
815 | 0 | flow->stats.used = time_msec(); |
816 | 0 | return true; |
817 | 0 | } |
818 | | |
819 | | static int |
820 | | dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev, |
821 | | struct dpif_offload_flow_put *put, |
822 | | void **previous_flow_reference) |
823 | 0 | { |
824 | 0 | struct dummy_offload *offload = dummy_offload_cast(offload_); |
825 | 0 | struct dummy_offloaded_flow *off_flow; |
826 | 0 | struct dummy_offload_port *port; |
827 | 0 | bool modify = true; |
828 | 0 | bool full_offload; |
829 | 0 | int error = 0; |
830 | |
|
831 | 0 | port = dummy_offload_get_port_by_netdev(offload_, netdev); |
832 | 0 | if (!port) { |
833 | 0 | error = ENODEV; |
834 | 0 | goto exit; |
835 | 0 | } |
836 | | |
837 | 0 | full_offload = dummy_offload_are_all_actions_supported( |
838 | 0 | offload_, put->match->flow.in_port.odp_port, |
839 | 0 | put->actions, put->actions_len); |
840 | |
|
841 | 0 | ovs_mutex_lock(&port->port_mutex); |
842 | |
|
843 | 0 | off_flow = dummy_find_offloaded_flow_and_update( |
844 | 0 | port, put->ufid, put->pmd_id, put->flow_reference, |
845 | 0 | previous_flow_reference); |
846 | |
|
847 | 0 | if (!off_flow) { |
848 | | /* Create new offloaded flow. */ |
849 | 0 | uint32_t mark = dummy_allocate_flow_mark(offload); |
850 | |
|
851 | 0 | if (mark == INVALID_FLOW_MARK) { |
852 | 0 | error = ENOSPC; |
853 | 0 | goto exit_unlock; |
854 | 0 | } |
855 | | |
856 | 0 | off_flow = dummy_add_flow(port, put->ufid, put->pmd_id, |
857 | 0 | put->flow_reference, mark); |
858 | 0 | modify = false; |
859 | 0 | *previous_flow_reference = NULL; |
860 | 0 | } |
861 | 0 | memcpy(&off_flow->match, put->match, sizeof *put->match); |
862 | 0 | free(CONST_CAST(struct nlattr *, off_flow->actions)); |
863 | 0 | if (full_offload) { |
864 | 0 | off_flow->actions = xmemdup(put->actions, put->actions_len); |
865 | 0 | off_flow->actions_len = put->actions_len; |
866 | 0 | } else { |
867 | 0 | off_flow->actions = NULL; |
868 | 0 | off_flow->actions_len = 0; |
869 | 0 | } |
870 | | |
871 | | /* As we have per-netdev 'offloaded_flows', we don't need to match |
872 | | * the 'in_port' for received packets. This will also allow offloading |
873 | | * for packets passed to 'receive' command without specifying the |
874 | | * 'in_port'. */ |
875 | 0 | off_flow->match.wc.masks.in_port.odp_port = 0; |
876 | |
|
877 | 0 | if (VLOG_IS_DBG_ENABLED()) { |
878 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
879 | |
|
880 | 0 | ds_put_format(&ds, "%s: flow put[%s]: ", netdev_get_name(netdev), |
881 | 0 | modify ? "modify" : "create"); |
882 | 0 | odp_format_ufid(put->ufid, &ds); |
883 | 0 | ds_put_cstr(&ds, " flow match: "); |
884 | 0 | match_format(put->match, NULL, &ds, OFP_DEFAULT_PRIORITY); |
885 | 0 | ds_put_format(&ds, ", mark: %"PRIu32, off_flow->mark); |
886 | |
|
887 | 0 | VLOG_DBG("%s", ds_cstr(&ds)); |
888 | 0 | ds_destroy(&ds); |
889 | 0 | } |
890 | |
|
891 | 0 | exit_unlock: |
892 | 0 | if (put->stats) { |
893 | 0 | *put->stats = off_flow->stats; |
894 | 0 | } |
895 | |
|
896 | 0 | ovs_mutex_unlock(&port->port_mutex); |
897 | |
|
898 | 0 | exit: |
899 | 0 | dummy_offload_log_operation(modify ? "modify" : "add", error, put->ufid); |
900 | 0 | return error; |
901 | 0 | } |
902 | | |
903 | | static int |
904 | | dummy_flow_del(const struct dpif_offload *offload_, struct netdev *netdev, |
905 | | struct dpif_offload_flow_del *del) |
906 | 0 | { |
907 | 0 | struct dummy_offload *offload = dummy_offload_cast(offload_); |
908 | 0 | struct dummy_offloaded_flow *off_flow; |
909 | 0 | uint32_t mark = INVALID_FLOW_MARK; |
910 | 0 | struct dummy_offload_port *port; |
911 | 0 | const char *error = NULL; |
912 | |
|
913 | 0 | port = dummy_offload_get_port_by_netdev(offload_, netdev); |
914 | 0 | if (!port) { |
915 | 0 | error = "No such (net)device."; |
916 | 0 | goto exit; |
917 | 0 | } |
918 | | |
919 | 0 | ovs_mutex_lock(&port->port_mutex); |
920 | |
|
921 | 0 | off_flow = dummy_find_offloaded_flow(port, del->ufid); |
922 | 0 | if (!off_flow) { |
923 | 0 | error = "No such flow."; |
924 | 0 | goto exit_unlock; |
925 | 0 | } |
926 | | |
927 | 0 | if (!dummy_del_flow_pmd_data(port, off_flow, del->pmd_id, |
928 | 0 | del->flow_reference)) { |
929 | 0 | error = "No such flow with pmd_id and reference."; |
930 | 0 | goto exit_unlock; |
931 | 0 | } |
932 | | |
933 | 0 | if (del->stats) { |
934 | 0 | memcpy(del->stats, &off_flow->stats, sizeof *del->stats); |
935 | 0 | } |
936 | |
|
937 | 0 | mark = off_flow->mark; |
938 | 0 | if (!hmap_count(&off_flow->pmd_id_map)) { |
939 | 0 | dummy_free_flow_mark(offload, mark); |
940 | 0 | dummy_free_flow(port, off_flow, true); |
941 | 0 | } |
942 | |
|
943 | 0 | exit_unlock: |
944 | 0 | ovs_mutex_unlock(&port->port_mutex); |
945 | |
|
946 | 0 | exit: |
947 | 0 | if (error || VLOG_IS_DBG_ENABLED()) { |
948 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
949 | |
|
950 | 0 | ds_put_format(&ds, "%s: ", netdev_get_name(netdev)); |
951 | 0 | if (error) { |
952 | 0 | ds_put_cstr(&ds, "failed to "); |
953 | 0 | } |
954 | 0 | ds_put_cstr(&ds, "flow del: "); |
955 | 0 | odp_format_ufid(del->ufid, &ds); |
956 | 0 | if (error) { |
957 | 0 | ds_put_format(&ds, " error: %s", error); |
958 | 0 | } else { |
959 | 0 | ds_put_format(&ds, " mark: %"PRIu32, mark); |
960 | 0 | } |
961 | 0 | VLOG(error ? VLL_WARN : VLL_DBG, "%s", ds_cstr(&ds)); |
962 | 0 | ds_destroy(&ds); |
963 | 0 | } |
964 | |
|
965 | 0 | dummy_offload_log_operation("delete", error ? -1 : 0, del->ufid); |
966 | 0 | return error ? ENOENT : 0; |
967 | 0 | } |
968 | | |
969 | | static bool |
970 | | dummy_flow_stats(const struct dpif_offload *offload_, struct netdev *netdev, |
971 | | const ovs_u128 *ufid, struct dpif_flow_stats *stats, |
972 | | struct dpif_flow_attrs *attrs) |
973 | 0 | { |
974 | 0 | struct dummy_offloaded_flow *off_flow = NULL; |
975 | 0 | struct dummy_offload_port *port; |
976 | |
|
977 | 0 | port = dummy_offload_get_port_by_netdev(offload_, netdev); |
978 | 0 | if (!port) { |
979 | 0 | return false; |
980 | 0 | } |
981 | | |
982 | 0 | ovs_mutex_lock(&port->port_mutex); |
983 | 0 | off_flow = dummy_find_offloaded_flow(port, ufid); |
984 | 0 | if (off_flow) { |
985 | 0 | memcpy(stats, &off_flow->stats, sizeof *stats); |
986 | 0 | attrs->dp_layer = off_flow->actions ? "dummy" : "ovs"; |
987 | 0 | attrs->dp_extra_info = NULL; |
988 | 0 | attrs->offloaded = true; |
989 | 0 | } |
990 | 0 | ovs_mutex_unlock(&port->port_mutex); |
991 | |
|
992 | 0 | if (!off_flow) { |
993 | 0 | return false; |
994 | 0 | } |
995 | | |
996 | 0 | return true; |
997 | 0 | } |
998 | | |
999 | | static void |
1000 | | dummy_register_flow_unreference_cb(const struct dpif_offload *offload_, |
1001 | | dpif_offload_flow_unreference_cb *cb) |
1002 | 0 | { |
1003 | 0 | struct dummy_offload *offload = dummy_offload_cast(offload_); |
1004 | |
|
1005 | 0 | offload->unreference_cb = cb; |
1006 | 0 | } |
1007 | | |
1008 | | static void |
1009 | | dummy_flow_unreference(struct dummy_offload *offload, unsigned pmd_id, |
1010 | | void *flow_reference) |
1011 | 0 | { |
1012 | 0 | if (offload->unreference_cb) { |
1013 | 0 | offload->unreference_cb(pmd_id, flow_reference); |
1014 | 0 | } |
1015 | 0 | } |
1016 | | |
1017 | | bool |
1018 | | dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet *packet, |
1019 | | int queue_id, struct flow *flow) |
1020 | 0 | { |
1021 | 0 | const struct dpif_offload *offload = ovsrcu_get( |
1022 | 0 | const struct dpif_offload *, &netdev->dpif_offload); |
1023 | 0 | struct dummy_offloaded_flow *data; |
1024 | 0 | struct dummy_offload_port *port; |
1025 | 0 | bool packet_stolen = false; |
1026 | 0 | struct flow packet_flow; |
1027 | 0 | bool offloaded = false; |
1028 | |
|
1029 | 0 | if (!dpif_offload_enabled() || !offload |
1030 | 0 | || strcmp(dpif_offload_type(offload), "dummy")) { |
1031 | 0 | return false; |
1032 | 0 | } |
1033 | | |
1034 | 0 | port = dummy_offload_get_port_by_netdev(offload, netdev); |
1035 | 0 | if (!port) { |
1036 | 0 | return false; |
1037 | 0 | } |
1038 | | |
1039 | 0 | if (!flow) { |
1040 | 0 | flow = &packet_flow; |
1041 | 0 | flow_extract(packet, flow); |
1042 | 0 | } |
1043 | |
|
1044 | 0 | ovs_mutex_lock(&port->port_mutex); |
1045 | 0 | HMAP_FOR_EACH (data, node, &port->offloaded_flows) { |
1046 | 0 | if (flow_equal_except(flow, &data->match.flow, &data->match.wc)) { |
1047 | |
|
1048 | 0 | dp_packet_set_flow_mark(packet, data->mark); |
1049 | |
|
1050 | 0 | if (VLOG_IS_DBG_ENABLED()) { |
1051 | 0 | struct ds ds = DS_EMPTY_INITIALIZER; |
1052 | |
|
1053 | 0 | ds_put_format(&ds, "%s: packet: ", |
1054 | 0 | netdev_get_name(netdev)); |
1055 | | /* 'flow' does not contain proper port number here. |
1056 | | * Let's just clear it as it's wildcarded anyway. */ |
1057 | 0 | flow->in_port.ofp_port = 0; |
1058 | 0 | flow_format(&ds, flow, NULL); |
1059 | |
|
1060 | 0 | ds_put_cstr(&ds, " matches with flow: "); |
1061 | 0 | odp_format_ufid(&data->ufid, &ds); |
1062 | 0 | ds_put_cstr(&ds, " "); |
1063 | 0 | match_format(&data->match, NULL, &ds, OFP_DEFAULT_PRIORITY); |
1064 | 0 | ds_put_format(&ds, " with mark: %"PRIu32, data->mark); |
1065 | |
|
1066 | 0 | VLOG_DBG("%s", ds_cstr(&ds)); |
1067 | 0 | ds_destroy(&ds); |
1068 | 0 | } |
1069 | |
|
1070 | 0 | if (data->actions) { |
1071 | | /* Perform hardware offload simulation. The packet is stolen |
1072 | | * here and handed off to the PMD thread callback for |
1073 | | * processing. */ |
1074 | 0 | struct hw_pkt_node *pkt_node = xmalloc(sizeof *pkt_node); |
1075 | |
|
1076 | 0 | pkt_node->pkt = packet; |
1077 | 0 | pkt_node->queue_id = queue_id; |
1078 | 0 | ovs_list_push_back(&port->hw_recv_queue, &pkt_node->list_node); |
1079 | 0 | packet_stolen = true; |
1080 | 0 | port->rx_offload_full++; |
1081 | 0 | } else { |
1082 | 0 | port->rx_offload_partial++; |
1083 | 0 | } |
1084 | |
|
1085 | 0 | offloaded = true; |
1086 | 0 | break; |
1087 | 0 | } |
1088 | 0 | } |
1089 | |
|
1090 | 0 | if (!offloaded) { |
1091 | 0 | port->rx_offload_miss++; |
1092 | 0 | } |
1093 | |
|
1094 | 0 | ovs_mutex_unlock(&port->port_mutex); |
1095 | 0 | return packet_stolen; |
1096 | 0 | } |
1097 | | |
1098 | | void |
1099 | | dummy_netdev_hw_offload_run(struct netdev *netdev) |
1100 | 0 | { |
1101 | 0 | const struct dpif_offload *offload = ovsrcu_get( |
1102 | 0 | const struct dpif_offload *, &netdev->dpif_offload); |
1103 | 0 | struct dpif_offload_port *port_; |
1104 | |
|
1105 | 0 | if (!dpif_offload_enabled() || !offload |
1106 | 0 | || strcmp(dpif_offload_type(offload), "dummy")) { |
1107 | 0 | return; |
1108 | 0 | } |
1109 | | |
1110 | 0 | DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) { |
1111 | 0 | struct dummy_offload_port *port; |
1112 | 0 | struct hw_pkt_node *pkt_node; |
1113 | |
|
1114 | 0 | port = dummy_offload_port_cast(port_); |
1115 | |
|
1116 | 0 | if (ovs_mutex_trylock(&port->port_mutex)) { |
1117 | 0 | continue; |
1118 | 0 | } |
1119 | | |
1120 | 0 | LIST_FOR_EACH_POP (pkt_node, list_node, &port->hw_recv_queue) { |
1121 | 0 | struct dummy_offloaded_flow *offloaded_flow; |
1122 | 0 | struct dp_packet *pkt = pkt_node->pkt; |
1123 | 0 | bool processed = false; |
1124 | 0 | struct flow flow; |
1125 | |
|
1126 | 0 | flow_extract(pkt, &flow); |
1127 | 0 | HMAP_FOR_EACH (offloaded_flow, node, &port->offloaded_flows) { |
1128 | 0 | if (flow_equal_except(&flow, &offloaded_flow->match.flow, |
1129 | 0 | &offloaded_flow->match.wc)) { |
1130 | |
|
1131 | 0 | processed = dummy_offload_hw_process_pkt( |
1132 | 0 | offload, offloaded_flow, pkt); |
1133 | 0 | break; |
1134 | 0 | } |
1135 | 0 | } |
1136 | |
|
1137 | 0 | if (!processed) { |
1138 | 0 | VLOG_DBG("Failed HW pipeline, sent to sw!"); |
1139 | 0 | port->rx_offload_pipe_abort++; |
1140 | 0 | netdev_dummy_queue_simulate_offload_packet( |
1141 | 0 | port->pm_port.netdev, pkt, pkt_node->queue_id); |
1142 | 0 | } |
1143 | 0 | free(pkt_node); |
1144 | 0 | } |
1145 | 0 | ovs_mutex_unlock(&port->port_mutex); |
1146 | 0 | } |
1147 | 0 | } |
1148 | | |
1149 | | #define DEFINE_DPIF_DUMMY_CLASS(NAME, TYPE_STR) \ |
1150 | | struct dpif_offload_class NAME = { \ |
1151 | | .type = TYPE_STR, \ |
1152 | | .impl_type = DPIF_OFFLOAD_IMPL_FLOWS_DPIF_SYNCED, \ |
1153 | | .supported_dpif_types = (const char *const[]) {"dummy", NULL}, \ |
1154 | | .open = dummy_offload_open, \ |
1155 | | .close = dummy_offload_close, \ |
1156 | | .set_config = dummy_offload_set_config, \ |
1157 | | .get_debug = dummy_offload_get_debug, \ |
1158 | | .get_global_stats = dummy_offload_get_global_stats, \ |
1159 | | .can_offload = dummy_can_offload, \ |
1160 | | .port_add = dummy_offload_port_add, \ |
1161 | | .port_del = dummy_offload_port_del, \ |
1162 | | .get_netdev = dummy_offload_get_netdev, \ |
1163 | | .netdev_hw_post_process = dummy_offload_hw_post_process, \ |
1164 | | .netdev_udp_tnl_get_src_port = dummy_offload_udp_tnl_get_src_port, \ |
1165 | | .netdev_flow_put = dummy_flow_put, \ |
1166 | | .netdev_flow_del = dummy_flow_del, \ |
1167 | | .netdev_flow_stats = dummy_flow_stats, \ |
1168 | | .register_flow_unreference_cb = dummy_register_flow_unreference_cb, \ |
1169 | | } |
1170 | | |
1171 | | DEFINE_DPIF_DUMMY_CLASS(dpif_offload_dummy_class, "dummy"); |
1172 | | DEFINE_DPIF_DUMMY_CLASS(dpif_offload_dummy_x_class, "dummy_x"); |