]>
Commit | Line | Data |
---|---|---|
3d67b2d2 RBY |
1 | /* |
2 | * Copyright (c) 2014, 2015, 2016, 2017 Nicira, Inc. | |
3 | * Copyright (c) 2019 Mellanox Technologies, Ltd. | |
4 | * | |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | * you may not use this file except in compliance with the License. | |
7 | * You may obtain a copy of the License at: | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | #include <config.h> | |
3d67b2d2 RBY |
18 | |
19 | #include <rte_flow.h> | |
20 | ||
21 | #include "cmap.h" | |
22 | #include "dpif-netdev.h" | |
5fc5c50f | 23 | #include "netdev-offload-provider.h" |
3d67b2d2 RBY |
24 | #include "netdev-provider.h" |
25 | #include "openvswitch/match.h" | |
26 | #include "openvswitch/vlog.h" | |
27 | #include "packets.h" | |
28 | #include "uuid.h" | |
29 | ||
4f746d52 | 30 | VLOG_DEFINE_THIS_MODULE(netdev_offload_dpdk); |
7d6033d6 | 31 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(100, 5); |
3d67b2d2 | 32 | |
5fc5c50f IM |
33 | /* Thread-safety |
34 | * ============= | |
35 | * | |
36 | * Below API is NOT thread safe in following terms: | |
37 | * | |
38 | * - The caller must be sure that none of these functions will be called | |
39 | * simultaneously. Even for different 'netdev's. | |
40 | * | |
41 | * - The caller must be sure that 'netdev' will not be destructed/deallocated. | |
42 | * | |
43 | * - The caller must be sure that 'netdev' configuration will not be changed. | |
44 | * For example, simultaneous call of 'netdev_reconfigure()' for the same | |
45 | * 'netdev' is forbidden. | |
46 | * | |
47 | * For current implementation all above restrictions could be fulfilled by | |
48 | * taking the datapath 'port_mutex' in lib/dpif-netdev.c. */ | |
49 | ||
3d67b2d2 RBY |
50 | /* |
51 | * A mapping from ufid to dpdk rte_flow. | |
52 | */ | |
53 | static struct cmap ufid_to_rte_flow = CMAP_INITIALIZER; | |
54 | ||
55 | struct ufid_to_rte_flow_data { | |
56 | struct cmap_node node; | |
57 | ovs_u128 ufid; | |
58 | struct rte_flow *rte_flow; | |
60e778c7 | 59 | bool actions_offloaded; |
2aca29df | 60 | struct dpif_flow_stats stats; |
3d67b2d2 RBY |
61 | }; |
62 | ||
63 | /* Find rte_flow with @ufid. */ | |
34ce6bf7 EB |
64 | static struct ufid_to_rte_flow_data * |
65 | ufid_to_rte_flow_data_find(const ovs_u128 *ufid) | |
3d67b2d2 RBY |
66 | { |
67 | size_t hash = hash_bytes(ufid, sizeof *ufid, 0); | |
68 | struct ufid_to_rte_flow_data *data; | |
69 | ||
70 | CMAP_FOR_EACH_WITH_HASH (data, node, hash, &ufid_to_rte_flow) { | |
71 | if (ovs_u128_equals(*ufid, data->ufid)) { | |
34ce6bf7 | 72 | return data; |
3d67b2d2 RBY |
73 | } |
74 | } | |
75 | ||
76 | return NULL; | |
77 | } | |
78 | ||
79 | static inline void | |
80 | ufid_to_rte_flow_associate(const ovs_u128 *ufid, | |
60e778c7 | 81 | struct rte_flow *rte_flow, bool actions_offloaded) |
3d67b2d2 RBY |
82 | { |
83 | size_t hash = hash_bytes(ufid, sizeof *ufid, 0); | |
84 | struct ufid_to_rte_flow_data *data = xzalloc(sizeof *data); | |
34ce6bf7 | 85 | struct ufid_to_rte_flow_data *data_prev; |
3d67b2d2 RBY |
86 | |
87 | /* | |
88 | * We should not simply overwrite an existing rte flow. | |
89 | * We should have deleted it first before re-adding it. | |
90 | * Thus, if following assert triggers, something is wrong: | |
91 | * the rte_flow is not destroyed. | |
92 | */ | |
34ce6bf7 EB |
93 | data_prev = ufid_to_rte_flow_data_find(ufid); |
94 | if (data_prev) { | |
95 | ovs_assert(data_prev->rte_flow == NULL); | |
96 | } | |
3d67b2d2 RBY |
97 | |
98 | data->ufid = *ufid; | |
99 | data->rte_flow = rte_flow; | |
60e778c7 | 100 | data->actions_offloaded = actions_offloaded; |
3d67b2d2 RBY |
101 | |
102 | cmap_insert(&ufid_to_rte_flow, | |
103 | CONST_CAST(struct cmap_node *, &data->node), hash); | |
104 | } | |
105 | ||
106 | static inline void | |
107 | ufid_to_rte_flow_disassociate(const ovs_u128 *ufid) | |
108 | { | |
109 | size_t hash = hash_bytes(ufid, sizeof *ufid, 0); | |
110 | struct ufid_to_rte_flow_data *data; | |
111 | ||
112 | CMAP_FOR_EACH_WITH_HASH (data, node, hash, &ufid_to_rte_flow) { | |
113 | if (ovs_u128_equals(*ufid, data->ufid)) { | |
114 | cmap_remove(&ufid_to_rte_flow, | |
115 | CONST_CAST(struct cmap_node *, &data->node), hash); | |
116 | ovsrcu_postpone(free, data); | |
117 | return; | |
118 | } | |
119 | } | |
120 | ||
121 | VLOG_WARN("ufid "UUID_FMT" is not associated with an rte flow\n", | |
122 | UUID_ARGS((struct uuid *) ufid)); | |
123 | } | |
124 | ||
125 | /* | |
126 | * To avoid individual xrealloc calls for each new element, a 'curent_max' | |
127 | * is used to keep track of current allocated number of elements. Starts | |
128 | * by 8 and doubles on each xrealloc call. | |
129 | */ | |
130 | struct flow_patterns { | |
131 | struct rte_flow_item *items; | |
132 | int cnt; | |
133 | int current_max; | |
134 | }; | |
135 | ||
136 | struct flow_actions { | |
137 | struct rte_flow_action *actions; | |
138 | int cnt; | |
139 | int current_max; | |
140 | }; | |
141 | ||
142 | static void | |
7d6033d6 | 143 | dump_flow_attr(struct ds *s, const struct rte_flow_attr *attr) |
3d67b2d2 | 144 | { |
7d6033d6 EB |
145 | ds_put_format(s, |
146 | " Attributes: " | |
147 | "ingress=%d, egress=%d, prio=%d, group=%d, transfer=%d\n", | |
148 | attr->ingress, attr->egress, attr->priority, attr->group, | |
149 | attr->transfer); | |
150 | } | |
3d67b2d2 | 151 | |
7d6033d6 EB |
152 | static void |
153 | dump_flow_pattern(struct ds *s, const struct rte_flow_item *item) | |
154 | { | |
3d67b2d2 RBY |
155 | if (item->type == RTE_FLOW_ITEM_TYPE_ETH) { |
156 | const struct rte_flow_item_eth *eth_spec = item->spec; | |
157 | const struct rte_flow_item_eth *eth_mask = item->mask; | |
158 | ||
7d6033d6 | 159 | ds_put_cstr(s, "rte flow eth pattern:\n"); |
3d67b2d2 | 160 | if (eth_spec) { |
7d6033d6 | 161 | ds_put_format(s, |
3d67b2d2 RBY |
162 | " Spec: src="ETH_ADDR_FMT", dst="ETH_ADDR_FMT", " |
163 | "type=0x%04" PRIx16"\n", | |
164 | ETH_ADDR_BYTES_ARGS(eth_spec->src.addr_bytes), | |
165 | ETH_ADDR_BYTES_ARGS(eth_spec->dst.addr_bytes), | |
166 | ntohs(eth_spec->type)); | |
167 | } else { | |
7d6033d6 | 168 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
169 | } |
170 | if (eth_mask) { | |
7d6033d6 | 171 | ds_put_format(s, |
3d67b2d2 RBY |
172 | " Mask: src="ETH_ADDR_FMT", dst="ETH_ADDR_FMT", " |
173 | "type=0x%04"PRIx16"\n", | |
174 | ETH_ADDR_BYTES_ARGS(eth_mask->src.addr_bytes), | |
175 | ETH_ADDR_BYTES_ARGS(eth_mask->dst.addr_bytes), | |
34378ae4 | 176 | ntohs(eth_mask->type)); |
3d67b2d2 | 177 | } else { |
7d6033d6 | 178 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 179 | } |
7d6033d6 | 180 | } else if (item->type == RTE_FLOW_ITEM_TYPE_VLAN) { |
3d67b2d2 RBY |
181 | const struct rte_flow_item_vlan *vlan_spec = item->spec; |
182 | const struct rte_flow_item_vlan *vlan_mask = item->mask; | |
183 | ||
7d6033d6 | 184 | ds_put_cstr(s, "rte flow vlan pattern:\n"); |
3d67b2d2 | 185 | if (vlan_spec) { |
7d6033d6 | 186 | ds_put_format(s, |
3d67b2d2 RBY |
187 | " Spec: inner_type=0x%"PRIx16", tci=0x%"PRIx16"\n", |
188 | ntohs(vlan_spec->inner_type), ntohs(vlan_spec->tci)); | |
189 | } else { | |
7d6033d6 | 190 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
191 | } |
192 | ||
193 | if (vlan_mask) { | |
7d6033d6 | 194 | ds_put_format(s, |
3d67b2d2 RBY |
195 | " Mask: inner_type=0x%"PRIx16", tci=0x%"PRIx16"\n", |
196 | ntohs(vlan_mask->inner_type), ntohs(vlan_mask->tci)); | |
197 | } else { | |
7d6033d6 | 198 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 199 | } |
7d6033d6 | 200 | } else if (item->type == RTE_FLOW_ITEM_TYPE_IPV4) { |
3d67b2d2 RBY |
201 | const struct rte_flow_item_ipv4 *ipv4_spec = item->spec; |
202 | const struct rte_flow_item_ipv4 *ipv4_mask = item->mask; | |
203 | ||
7d6033d6 | 204 | ds_put_cstr(s, "rte flow ipv4 pattern:\n"); |
3d67b2d2 | 205 | if (ipv4_spec) { |
7d6033d6 | 206 | ds_put_format(s, |
3d67b2d2 RBY |
207 | " Spec: tos=0x%"PRIx8", ttl=%"PRIx8 |
208 | ", proto=0x%"PRIx8 | |
209 | ", src="IP_FMT", dst="IP_FMT"\n", | |
210 | ipv4_spec->hdr.type_of_service, | |
211 | ipv4_spec->hdr.time_to_live, | |
212 | ipv4_spec->hdr.next_proto_id, | |
213 | IP_ARGS(ipv4_spec->hdr.src_addr), | |
214 | IP_ARGS(ipv4_spec->hdr.dst_addr)); | |
215 | } else { | |
7d6033d6 | 216 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
217 | } |
218 | if (ipv4_mask) { | |
7d6033d6 | 219 | ds_put_format(s, |
3d67b2d2 RBY |
220 | " Mask: tos=0x%"PRIx8", ttl=%"PRIx8 |
221 | ", proto=0x%"PRIx8 | |
222 | ", src="IP_FMT", dst="IP_FMT"\n", | |
223 | ipv4_mask->hdr.type_of_service, | |
224 | ipv4_mask->hdr.time_to_live, | |
225 | ipv4_mask->hdr.next_proto_id, | |
226 | IP_ARGS(ipv4_mask->hdr.src_addr), | |
227 | IP_ARGS(ipv4_mask->hdr.dst_addr)); | |
228 | } else { | |
7d6033d6 | 229 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 230 | } |
7d6033d6 | 231 | } else if (item->type == RTE_FLOW_ITEM_TYPE_UDP) { |
3d67b2d2 RBY |
232 | const struct rte_flow_item_udp *udp_spec = item->spec; |
233 | const struct rte_flow_item_udp *udp_mask = item->mask; | |
234 | ||
7d6033d6 | 235 | ds_put_cstr(s, "rte flow udp pattern:\n"); |
3d67b2d2 | 236 | if (udp_spec) { |
7d6033d6 | 237 | ds_put_format(s, |
3d67b2d2 RBY |
238 | " Spec: src_port=%"PRIu16", dst_port=%"PRIu16"\n", |
239 | ntohs(udp_spec->hdr.src_port), | |
240 | ntohs(udp_spec->hdr.dst_port)); | |
241 | } else { | |
7d6033d6 | 242 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
243 | } |
244 | if (udp_mask) { | |
7d6033d6 | 245 | ds_put_format(s, |
3d67b2d2 RBY |
246 | " Mask: src_port=0x%"PRIx16 |
247 | ", dst_port=0x%"PRIx16"\n", | |
34378ae4 IM |
248 | ntohs(udp_mask->hdr.src_port), |
249 | ntohs(udp_mask->hdr.dst_port)); | |
3d67b2d2 | 250 | } else { |
7d6033d6 | 251 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 252 | } |
7d6033d6 | 253 | } else if (item->type == RTE_FLOW_ITEM_TYPE_SCTP) { |
3d67b2d2 RBY |
254 | const struct rte_flow_item_sctp *sctp_spec = item->spec; |
255 | const struct rte_flow_item_sctp *sctp_mask = item->mask; | |
256 | ||
7d6033d6 | 257 | ds_put_cstr(s, "rte flow sctp pattern:\n"); |
3d67b2d2 | 258 | if (sctp_spec) { |
7d6033d6 | 259 | ds_put_format(s, |
3d67b2d2 RBY |
260 | " Spec: src_port=%"PRIu16", dst_port=%"PRIu16"\n", |
261 | ntohs(sctp_spec->hdr.src_port), | |
262 | ntohs(sctp_spec->hdr.dst_port)); | |
263 | } else { | |
7d6033d6 | 264 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
265 | } |
266 | if (sctp_mask) { | |
7d6033d6 | 267 | ds_put_format(s, |
3d67b2d2 RBY |
268 | " Mask: src_port=0x%"PRIx16 |
269 | ", dst_port=0x%"PRIx16"\n", | |
34378ae4 IM |
270 | ntohs(sctp_mask->hdr.src_port), |
271 | ntohs(sctp_mask->hdr.dst_port)); | |
3d67b2d2 | 272 | } else { |
7d6033d6 | 273 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 274 | } |
7d6033d6 | 275 | } else if (item->type == RTE_FLOW_ITEM_TYPE_ICMP) { |
3d67b2d2 RBY |
276 | const struct rte_flow_item_icmp *icmp_spec = item->spec; |
277 | const struct rte_flow_item_icmp *icmp_mask = item->mask; | |
278 | ||
7d6033d6 | 279 | ds_put_cstr(s, "rte flow icmp pattern:\n"); |
3d67b2d2 | 280 | if (icmp_spec) { |
7d6033d6 | 281 | ds_put_format(s, |
3d67b2d2 RBY |
282 | " Spec: icmp_type=%"PRIu8", icmp_code=%"PRIu8"\n", |
283 | icmp_spec->hdr.icmp_type, | |
284 | icmp_spec->hdr.icmp_code); | |
285 | } else { | |
7d6033d6 | 286 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
287 | } |
288 | if (icmp_mask) { | |
7d6033d6 | 289 | ds_put_format(s, |
3d67b2d2 RBY |
290 | " Mask: icmp_type=0x%"PRIx8 |
291 | ", icmp_code=0x%"PRIx8"\n", | |
292 | icmp_spec->hdr.icmp_type, | |
293 | icmp_spec->hdr.icmp_code); | |
294 | } else { | |
7d6033d6 | 295 | ds_put_cstr(s, " Mask = null\n"); |
3d67b2d2 | 296 | } |
7d6033d6 | 297 | } else if (item->type == RTE_FLOW_ITEM_TYPE_TCP) { |
3d67b2d2 RBY |
298 | const struct rte_flow_item_tcp *tcp_spec = item->spec; |
299 | const struct rte_flow_item_tcp *tcp_mask = item->mask; | |
300 | ||
7d6033d6 | 301 | ds_put_cstr(s, "rte flow tcp pattern:\n"); |
3d67b2d2 | 302 | if (tcp_spec) { |
7d6033d6 | 303 | ds_put_format(s, |
3d67b2d2 RBY |
304 | " Spec: src_port=%"PRIu16", dst_port=%"PRIu16 |
305 | ", data_off=0x%"PRIx8", tcp_flags=0x%"PRIx8"\n", | |
306 | ntohs(tcp_spec->hdr.src_port), | |
307 | ntohs(tcp_spec->hdr.dst_port), | |
308 | tcp_spec->hdr.data_off, | |
309 | tcp_spec->hdr.tcp_flags); | |
310 | } else { | |
7d6033d6 | 311 | ds_put_cstr(s, " Spec = null\n"); |
3d67b2d2 RBY |
312 | } |
313 | if (tcp_mask) { | |
7d6033d6 | 314 | ds_put_format(s, |
3d67b2d2 RBY |
315 | " Mask: src_port=%"PRIx16", dst_port=%"PRIx16 |
316 | ", data_off=0x%"PRIx8", tcp_flags=0x%"PRIx8"\n", | |
34378ae4 IM |
317 | ntohs(tcp_mask->hdr.src_port), |
318 | ntohs(tcp_mask->hdr.dst_port), | |
3d67b2d2 RBY |
319 | tcp_mask->hdr.data_off, |
320 | tcp_mask->hdr.tcp_flags); | |
321 | } else { | |
7d6033d6 EB |
322 | ds_put_cstr(s, " Mask = null\n"); |
323 | } | |
324 | } else { | |
325 | ds_put_format(s, "unknown rte flow pattern (%d)\n", item->type); | |
326 | } | |
327 | } | |
328 | ||
329 | static void | |
330 | dump_flow_action(struct ds *s, const struct rte_flow_action *actions) | |
331 | { | |
332 | if (actions->type == RTE_FLOW_ACTION_TYPE_MARK) { | |
333 | const struct rte_flow_action_mark *mark = actions->conf; | |
334 | ||
335 | ds_put_cstr(s, "rte flow mark action:\n"); | |
336 | if (mark) { | |
337 | ds_put_format(s, " Mark: id=%d\n", mark->id); | |
338 | } else { | |
339 | ds_put_cstr(s, " Mark = null\n"); | |
3d67b2d2 | 340 | } |
7d6033d6 EB |
341 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_RSS) { |
342 | const struct rte_flow_action_rss *rss = actions->conf; | |
343 | ||
344 | ds_put_cstr(s, "rte flow RSS action:\n"); | |
345 | if (rss) { | |
346 | ds_put_format(s, " RSS: queue_num=%d\n", rss->queue_num); | |
347 | } else { | |
348 | ds_put_cstr(s, " RSS = null\n"); | |
349 | } | |
3c7330eb EB |
350 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_COUNT) { |
351 | const struct rte_flow_action_count *count = actions->conf; | |
352 | ||
353 | ds_put_cstr(s, "rte flow count action:\n"); | |
354 | if (count) { | |
355 | ds_put_format(s, " Count: shared=%d, id=%d\n", count->shared, | |
356 | count->id); | |
357 | } else { | |
358 | ds_put_cstr(s, " Count = null\n"); | |
359 | } | |
360 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_PORT_ID) { | |
361 | const struct rte_flow_action_port_id *port_id = actions->conf; | |
362 | ||
363 | ds_put_cstr(s, "rte flow port-id action:\n"); | |
364 | if (port_id) { | |
365 | ds_put_format(s, " Port-id: original=%d, id=%d\n", | |
366 | port_id->original, port_id->id); | |
367 | } else { | |
368 | ds_put_cstr(s, " Port-id = null\n"); | |
369 | } | |
abb288c0 EB |
370 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_DROP) { |
371 | ds_put_cstr(s, "rte flow drop action\n"); | |
ae32e08d EB |
372 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_SET_MAC_SRC || |
373 | actions->type == RTE_FLOW_ACTION_TYPE_SET_MAC_DST) { | |
374 | const struct rte_flow_action_set_mac *set_mac = actions->conf; | |
375 | ||
376 | char *dirstr = actions->type == RTE_FLOW_ACTION_TYPE_SET_MAC_DST | |
377 | ? "dst" : "src"; | |
378 | ||
379 | ds_put_format(s, "rte flow set-mac-%s action:\n", dirstr); | |
380 | if (set_mac) { | |
381 | ds_put_format(s, | |
382 | " Set-mac-%s: "ETH_ADDR_FMT"\n", dirstr, | |
383 | ETH_ADDR_BYTES_ARGS(set_mac->mac_addr)); | |
384 | } else { | |
385 | ds_put_format(s, " Set-mac-%s = null\n", dirstr); | |
386 | } | |
d9a831c3 EB |
387 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_SET_IPV4_SRC || |
388 | actions->type == RTE_FLOW_ACTION_TYPE_SET_IPV4_DST) { | |
389 | const struct rte_flow_action_set_ipv4 *set_ipv4 = actions->conf; | |
390 | char *dirstr = actions->type == RTE_FLOW_ACTION_TYPE_SET_IPV4_DST | |
391 | ? "dst" : "src"; | |
392 | ||
393 | ds_put_format(s, "rte flow set-ipv4-%s action:\n", dirstr); | |
394 | if (set_ipv4) { | |
395 | ds_put_format(s, | |
396 | " Set-ipv4-%s: "IP_FMT"\n", dirstr, | |
397 | IP_ARGS(set_ipv4->ipv4_addr)); | |
398 | } else { | |
399 | ds_put_format(s, " Set-ipv4-%s = null\n", dirstr); | |
400 | } | |
401 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_SET_TTL) { | |
402 | const struct rte_flow_action_set_ttl *set_ttl = actions->conf; | |
403 | ||
404 | ds_put_cstr(s, "rte flow set-ttl action:\n"); | |
405 | if (set_ttl) { | |
406 | ds_put_format(s, " Set-ttl: %d\n", set_ttl->ttl_value); | |
407 | } else { | |
408 | ds_put_cstr(s, " Set-ttl = null\n"); | |
409 | } | |
b9254f7b EB |
410 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_SET_TP_SRC || |
411 | actions->type == RTE_FLOW_ACTION_TYPE_SET_TP_DST) { | |
412 | const struct rte_flow_action_set_tp *set_tp = actions->conf; | |
413 | char *dirstr = actions->type == RTE_FLOW_ACTION_TYPE_SET_TP_DST | |
414 | ? "dst" : "src"; | |
415 | ||
416 | ds_put_format(s, "rte flow set-tcp/udp-port-%s action:\n", dirstr); | |
417 | if (set_tp) { | |
418 | ds_put_format(s, " Set-%s-tcp/udp-port: %"PRIu16"\n", dirstr, | |
419 | ntohs(set_tp->port)); | |
420 | } else { | |
421 | ds_put_format(s, " Set-%s-tcp/udp-port = null\n", dirstr); | |
422 | } | |
02927385 SB |
423 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN) { |
424 | const struct rte_flow_action_of_push_vlan *rte_push_vlan; | |
425 | ||
426 | rte_push_vlan = actions->conf; | |
427 | ds_put_cstr(s, "rte flow push-vlan action:\n"); | |
428 | if (rte_push_vlan) { | |
429 | ds_put_format(s, " Push-vlan: 0x%"PRIx16"\n", | |
430 | ntohs(rte_push_vlan->ethertype)); | |
431 | } else { | |
432 | ds_put_format(s, " Push-vlan = null\n"); | |
433 | } | |
434 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP) { | |
435 | const struct rte_flow_action_of_set_vlan_pcp *rte_vlan_pcp; | |
436 | ||
437 | rte_vlan_pcp = actions->conf; | |
438 | ds_put_cstr(s, "rte flow set-vlan-pcp action:\n"); | |
439 | if (rte_vlan_pcp) { | |
440 | ds_put_format(s, " Set-vlan-pcp: %"PRIu8"\n", | |
441 | rte_vlan_pcp->vlan_pcp); | |
442 | } else { | |
443 | ds_put_format(s, " Set-vlan-pcp = null\n"); | |
444 | } | |
445 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID) { | |
446 | const struct rte_flow_action_of_set_vlan_vid *rte_vlan_vid; | |
447 | ||
448 | rte_vlan_vid = actions->conf; | |
449 | ds_put_cstr(s, "rte flow set-vlan-vid action:\n"); | |
450 | if (rte_vlan_vid) { | |
451 | ds_put_format(s, " Set-vlan-vid: %"PRIu16"\n", | |
452 | ntohs(rte_vlan_vid->vlan_vid)); | |
453 | } else { | |
454 | ds_put_format(s, " Set-vlan-vid = null\n"); | |
455 | } | |
456 | } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_POP_VLAN) { | |
457 | ds_put_cstr(s, "rte flow pop-vlan action\n"); | |
7d6033d6 EB |
458 | } else { |
459 | ds_put_format(s, "unknown rte flow action (%d)\n", actions->type); | |
3d67b2d2 | 460 | } |
7d6033d6 | 461 | } |
3d67b2d2 | 462 | |
7d6033d6 EB |
463 | static struct ds * |
464 | dump_flow(struct ds *s, | |
465 | const struct rte_flow_attr *attr, | |
466 | const struct rte_flow_item *items, | |
467 | const struct rte_flow_action *actions) | |
468 | { | |
469 | if (attr) { | |
470 | dump_flow_attr(s, attr); | |
471 | } | |
472 | while (items && items->type != RTE_FLOW_ITEM_TYPE_END) { | |
473 | dump_flow_pattern(s, items++); | |
474 | } | |
475 | while (actions && actions->type != RTE_FLOW_ACTION_TYPE_END) { | |
476 | dump_flow_action(s, actions++); | |
477 | } | |
478 | return s; | |
479 | } | |
480 | ||
481 | static struct rte_flow * | |
482 | netdev_offload_dpdk_flow_create(struct netdev *netdev, | |
483 | const struct rte_flow_attr *attr, | |
484 | const struct rte_flow_item *items, | |
485 | const struct rte_flow_action *actions, | |
486 | struct rte_flow_error *error) | |
487 | { | |
488 | struct rte_flow *flow; | |
489 | struct ds s; | |
490 | ||
491 | flow = netdev_dpdk_rte_flow_create(netdev, attr, items, actions, error); | |
492 | if (flow) { | |
493 | if (!VLOG_DROP_DBG(&rl)) { | |
494 | ds_init(&s); | |
495 | dump_flow(&s, attr, items, actions); | |
496 | VLOG_DBG_RL(&rl, "%s: rte_flow 0x%"PRIxPTR" created:\n%s", | |
497 | netdev_get_name(netdev), (intptr_t) flow, ds_cstr(&s)); | |
498 | ds_destroy(&s); | |
499 | } | |
500 | } else { | |
501 | enum vlog_level level = VLL_WARN; | |
502 | ||
503 | if (error->type == RTE_FLOW_ERROR_TYPE_ACTION) { | |
504 | level = VLL_DBG; | |
505 | } | |
506 | VLOG_RL(&rl, level, "%s: rte_flow creation failed: %d (%s).", | |
507 | netdev_get_name(netdev), error->type, error->message); | |
508 | if (!vlog_should_drop(&this_module, level, &rl)) { | |
509 | ds_init(&s); | |
510 | dump_flow(&s, attr, items, actions); | |
511 | VLOG_RL(&rl, level, "Failed flow:\n%s", ds_cstr(&s)); | |
512 | ds_destroy(&s); | |
513 | } | |
514 | } | |
515 | return flow; | |
3d67b2d2 RBY |
516 | } |
517 | ||
518 | static void | |
519 | add_flow_pattern(struct flow_patterns *patterns, enum rte_flow_item_type type, | |
520 | const void *spec, const void *mask) | |
521 | { | |
522 | int cnt = patterns->cnt; | |
523 | ||
524 | if (cnt == 0) { | |
525 | patterns->current_max = 8; | |
526 | patterns->items = xcalloc(patterns->current_max, | |
527 | sizeof *patterns->items); | |
528 | } else if (cnt == patterns->current_max) { | |
529 | patterns->current_max *= 2; | |
530 | patterns->items = xrealloc(patterns->items, patterns->current_max * | |
531 | sizeof *patterns->items); | |
532 | } | |
533 | ||
534 | patterns->items[cnt].type = type; | |
535 | patterns->items[cnt].spec = spec; | |
536 | patterns->items[cnt].mask = mask; | |
537 | patterns->items[cnt].last = NULL; | |
3d67b2d2 RBY |
538 | patterns->cnt++; |
539 | } | |
540 | ||
541 | static void | |
542 | add_flow_action(struct flow_actions *actions, enum rte_flow_action_type type, | |
543 | const void *conf) | |
544 | { | |
545 | int cnt = actions->cnt; | |
546 | ||
547 | if (cnt == 0) { | |
548 | actions->current_max = 8; | |
549 | actions->actions = xcalloc(actions->current_max, | |
550 | sizeof *actions->actions); | |
551 | } else if (cnt == actions->current_max) { | |
552 | actions->current_max *= 2; | |
553 | actions->actions = xrealloc(actions->actions, actions->current_max * | |
554 | sizeof *actions->actions); | |
555 | } | |
556 | ||
557 | actions->actions[cnt].type = type; | |
558 | actions->actions[cnt].conf = conf; | |
559 | actions->cnt++; | |
560 | } | |
561 | ||
900fe007 EB |
562 | static void |
563 | free_flow_patterns(struct flow_patterns *patterns) | |
564 | { | |
565 | int i; | |
566 | ||
567 | for (i = 0; i < patterns->cnt; i++) { | |
568 | if (patterns->items[i].spec) { | |
569 | free(CONST_CAST(void *, patterns->items[i].spec)); | |
570 | } | |
571 | if (patterns->items[i].mask) { | |
572 | free(CONST_CAST(void *, patterns->items[i].mask)); | |
573 | } | |
574 | } | |
575 | free(patterns->items); | |
576 | patterns->items = NULL; | |
577 | patterns->cnt = 0; | |
578 | } | |
579 | ||
1e60e4b0 EB |
580 | static void |
581 | free_flow_actions(struct flow_actions *actions) | |
3d67b2d2 RBY |
582 | { |
583 | int i; | |
3d67b2d2 | 584 | |
1e60e4b0 EB |
585 | for (i = 0; i < actions->cnt; i++) { |
586 | if (actions->actions[i].conf) { | |
587 | free(CONST_CAST(void *, actions->actions[i].conf)); | |
588 | } | |
3d67b2d2 | 589 | } |
1e60e4b0 EB |
590 | free(actions->actions); |
591 | actions->actions = NULL; | |
592 | actions->cnt = 0; | |
3d67b2d2 RBY |
593 | } |
594 | ||
595 | static int | |
7c5b722a | 596 | parse_flow_match(struct flow_patterns *patterns, |
7c5b722a | 597 | const struct match *match) |
3d67b2d2 | 598 | { |
900fe007 | 599 | uint8_t *next_proto_mask = NULL; |
3d67b2d2 | 600 | uint8_t proto = 0; |
3d67b2d2 RBY |
601 | |
602 | /* Eth */ | |
603 | if (!eth_addr_is_zero(match->wc.masks.dl_src) || | |
604 | !eth_addr_is_zero(match->wc.masks.dl_dst)) { | |
900fe007 EB |
605 | struct rte_flow_item_eth *spec, *mask; |
606 | ||
607 | spec = xzalloc(sizeof *spec); | |
608 | mask = xzalloc(sizeof *mask); | |
609 | ||
610 | memcpy(&spec->dst, &match->flow.dl_dst, sizeof spec->dst); | |
611 | memcpy(&spec->src, &match->flow.dl_src, sizeof spec->src); | |
612 | spec->type = match->flow.dl_type; | |
3d67b2d2 | 613 | |
900fe007 EB |
614 | memcpy(&mask->dst, &match->wc.masks.dl_dst, sizeof mask->dst); |
615 | memcpy(&mask->src, &match->wc.masks.dl_src, sizeof mask->src); | |
616 | mask->type = match->wc.masks.dl_type; | |
3d67b2d2 | 617 | |
900fe007 | 618 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_ETH, spec, mask); |
3d67b2d2 RBY |
619 | } else { |
620 | /* | |
621 | * If user specifies a flow (like UDP flow) without L2 patterns, | |
622 | * OVS will at least set the dl_type. Normally, it's enough to | |
623 | * create an eth pattern just with it. Unluckily, some Intel's | |
624 | * NIC (such as XL710) doesn't support that. Below is a workaround, | |
625 | * which simply matches any L2 pkts. | |
626 | */ | |
7c5b722a | 627 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_ETH, NULL, NULL); |
3d67b2d2 RBY |
628 | } |
629 | ||
630 | /* VLAN */ | |
631 | if (match->wc.masks.vlans[0].tci && match->flow.vlans[0].tci) { | |
900fe007 EB |
632 | struct rte_flow_item_vlan *spec, *mask; |
633 | ||
634 | spec = xzalloc(sizeof *spec); | |
635 | mask = xzalloc(sizeof *mask); | |
636 | ||
637 | spec->tci = match->flow.vlans[0].tci & ~htons(VLAN_CFI); | |
638 | mask->tci = match->wc.masks.vlans[0].tci & ~htons(VLAN_CFI); | |
3d67b2d2 RBY |
639 | |
640 | /* Match any protocols. */ | |
900fe007 | 641 | mask->inner_type = 0; |
3d67b2d2 | 642 | |
900fe007 | 643 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_VLAN, spec, mask); |
3d67b2d2 RBY |
644 | } |
645 | ||
646 | /* IP v4 */ | |
647 | if (match->flow.dl_type == htons(ETH_TYPE_IP)) { | |
900fe007 EB |
648 | struct rte_flow_item_ipv4 *spec, *mask; |
649 | ||
650 | spec = xzalloc(sizeof *spec); | |
651 | mask = xzalloc(sizeof *mask); | |
652 | ||
653 | spec->hdr.type_of_service = match->flow.nw_tos; | |
654 | spec->hdr.time_to_live = match->flow.nw_ttl; | |
655 | spec->hdr.next_proto_id = match->flow.nw_proto; | |
656 | spec->hdr.src_addr = match->flow.nw_src; | |
657 | spec->hdr.dst_addr = match->flow.nw_dst; | |
3d67b2d2 | 658 | |
900fe007 EB |
659 | mask->hdr.type_of_service = match->wc.masks.nw_tos; |
660 | mask->hdr.time_to_live = match->wc.masks.nw_ttl; | |
661 | mask->hdr.next_proto_id = match->wc.masks.nw_proto; | |
662 | mask->hdr.src_addr = match->wc.masks.nw_src; | |
663 | mask->hdr.dst_addr = match->wc.masks.nw_dst; | |
3d67b2d2 | 664 | |
900fe007 | 665 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_IPV4, spec, mask); |
3d67b2d2 RBY |
666 | |
667 | /* Save proto for L4 protocol setup. */ | |
900fe007 EB |
668 | proto = spec->hdr.next_proto_id & |
669 | mask->hdr.next_proto_id; | |
670 | next_proto_mask = &mask->hdr.next_proto_id; | |
3d67b2d2 RBY |
671 | } |
672 | ||
673 | if (proto != IPPROTO_ICMP && proto != IPPROTO_UDP && | |
674 | proto != IPPROTO_SCTP && proto != IPPROTO_TCP && | |
675 | (match->wc.masks.tp_src || | |
676 | match->wc.masks.tp_dst || | |
677 | match->wc.masks.tcp_flags)) { | |
678 | VLOG_DBG("L4 Protocol (%u) not supported", proto); | |
7c5b722a | 679 | return -1; |
3d67b2d2 RBY |
680 | } |
681 | ||
682 | if ((match->wc.masks.tp_src && match->wc.masks.tp_src != OVS_BE16_MAX) || | |
683 | (match->wc.masks.tp_dst && match->wc.masks.tp_dst != OVS_BE16_MAX)) { | |
7c5b722a | 684 | return -1; |
3d67b2d2 RBY |
685 | } |
686 | ||
900fe007 EB |
687 | if (proto == IPPROTO_TCP) { |
688 | struct rte_flow_item_tcp *spec, *mask; | |
3d67b2d2 | 689 | |
900fe007 EB |
690 | spec = xzalloc(sizeof *spec); |
691 | mask = xzalloc(sizeof *mask); | |
3d67b2d2 | 692 | |
900fe007 EB |
693 | spec->hdr.src_port = match->flow.tp_src; |
694 | spec->hdr.dst_port = match->flow.tp_dst; | |
695 | spec->hdr.data_off = ntohs(match->flow.tcp_flags) >> 8; | |
696 | spec->hdr.tcp_flags = ntohs(match->flow.tcp_flags) & 0xff; | |
697 | ||
698 | mask->hdr.src_port = match->wc.masks.tp_src; | |
699 | mask->hdr.dst_port = match->wc.masks.tp_dst; | |
700 | mask->hdr.data_off = ntohs(match->wc.masks.tcp_flags) >> 8; | |
701 | mask->hdr.tcp_flags = ntohs(match->wc.masks.tcp_flags) & 0xff; | |
702 | ||
703 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_TCP, spec, mask); | |
3d67b2d2 RBY |
704 | |
705 | /* proto == TCP and ITEM_TYPE_TCP, thus no need for proto match. */ | |
900fe007 EB |
706 | if (next_proto_mask) { |
707 | *next_proto_mask = 0; | |
708 | } | |
709 | } else if (proto == IPPROTO_UDP) { | |
710 | struct rte_flow_item_udp *spec, *mask; | |
3d67b2d2 | 711 | |
900fe007 EB |
712 | spec = xzalloc(sizeof *spec); |
713 | mask = xzalloc(sizeof *mask); | |
3d67b2d2 | 714 | |
900fe007 EB |
715 | spec->hdr.src_port = match->flow.tp_src; |
716 | spec->hdr.dst_port = match->flow.tp_dst; | |
3d67b2d2 | 717 | |
900fe007 EB |
718 | mask->hdr.src_port = match->wc.masks.tp_src; |
719 | mask->hdr.dst_port = match->wc.masks.tp_dst; | |
720 | ||
721 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_UDP, spec, mask); | |
3d67b2d2 RBY |
722 | |
723 | /* proto == UDP and ITEM_TYPE_UDP, thus no need for proto match. */ | |
900fe007 EB |
724 | if (next_proto_mask) { |
725 | *next_proto_mask = 0; | |
726 | } | |
727 | } else if (proto == IPPROTO_SCTP) { | |
728 | struct rte_flow_item_sctp *spec, *mask; | |
3d67b2d2 | 729 | |
900fe007 EB |
730 | spec = xzalloc(sizeof *spec); |
731 | mask = xzalloc(sizeof *mask); | |
3d67b2d2 | 732 | |
900fe007 EB |
733 | spec->hdr.src_port = match->flow.tp_src; |
734 | spec->hdr.dst_port = match->flow.tp_dst; | |
3d67b2d2 | 735 | |
900fe007 EB |
736 | mask->hdr.src_port = match->wc.masks.tp_src; |
737 | mask->hdr.dst_port = match->wc.masks.tp_dst; | |
738 | ||
739 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_SCTP, spec, mask); | |
3d67b2d2 RBY |
740 | |
741 | /* proto == SCTP and ITEM_TYPE_SCTP, thus no need for proto match. */ | |
900fe007 EB |
742 | if (next_proto_mask) { |
743 | *next_proto_mask = 0; | |
744 | } | |
745 | } else if (proto == IPPROTO_ICMP) { | |
746 | struct rte_flow_item_icmp *spec, *mask; | |
3d67b2d2 | 747 | |
900fe007 EB |
748 | spec = xzalloc(sizeof *spec); |
749 | mask = xzalloc(sizeof *mask); | |
3d67b2d2 | 750 | |
900fe007 EB |
751 | spec->hdr.icmp_type = (uint8_t) ntohs(match->flow.tp_src); |
752 | spec->hdr.icmp_code = (uint8_t) ntohs(match->flow.tp_dst); | |
3d67b2d2 | 753 | |
900fe007 EB |
754 | mask->hdr.icmp_type = (uint8_t) ntohs(match->wc.masks.tp_src); |
755 | mask->hdr.icmp_code = (uint8_t) ntohs(match->wc.masks.tp_dst); | |
756 | ||
757 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_ICMP, spec, mask); | |
3d67b2d2 RBY |
758 | |
759 | /* proto == ICMP and ITEM_TYPE_ICMP, thus no need for proto match. */ | |
900fe007 EB |
760 | if (next_proto_mask) { |
761 | *next_proto_mask = 0; | |
762 | } | |
3d67b2d2 RBY |
763 | } |
764 | ||
7c5b722a EB |
765 | add_flow_pattern(patterns, RTE_FLOW_ITEM_TYPE_END, NULL, NULL); |
766 | ||
767 | return 0; | |
768 | } | |
769 | ||
1e60e4b0 EB |
770 | static void |
771 | add_flow_mark_rss_actions(struct flow_actions *actions, | |
772 | uint32_t flow_mark, | |
773 | const struct netdev *netdev) | |
774 | { | |
775 | struct rte_flow_action_mark *mark; | |
776 | struct action_rss_data { | |
777 | struct rte_flow_action_rss conf; | |
778 | uint16_t queue[0]; | |
779 | } *rss_data; | |
780 | BUILD_ASSERT_DECL(offsetof(struct action_rss_data, conf) == 0); | |
781 | int i; | |
782 | ||
783 | mark = xzalloc(sizeof *mark); | |
784 | ||
785 | mark->id = flow_mark; | |
786 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_MARK, mark); | |
787 | ||
788 | rss_data = xmalloc(sizeof *rss_data + | |
789 | netdev_n_rxq(netdev) * sizeof rss_data->queue[0]); | |
790 | *rss_data = (struct action_rss_data) { | |
791 | .conf = (struct rte_flow_action_rss) { | |
792 | .func = RTE_ETH_HASH_FUNCTION_DEFAULT, | |
793 | .level = 0, | |
794 | .types = 0, | |
795 | .queue_num = netdev_n_rxq(netdev), | |
796 | .queue = rss_data->queue, | |
797 | .key_len = 0, | |
798 | .key = NULL | |
799 | }, | |
800 | }; | |
801 | ||
802 | /* Override queue array with default. */ | |
803 | for (i = 0; i < netdev_n_rxq(netdev); i++) { | |
804 | rss_data->queue[i] = i; | |
805 | } | |
806 | ||
807 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_RSS, &rss_data->conf); | |
808 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_END, NULL); | |
809 | } | |
810 | ||
60e778c7 EB |
811 | static struct rte_flow * |
812 | netdev_offload_dpdk_mark_rss(struct flow_patterns *patterns, | |
813 | struct netdev *netdev, | |
814 | uint32_t flow_mark) | |
7c5b722a | 815 | { |
60e778c7 | 816 | struct flow_actions actions = { .actions = NULL, .cnt = 0 }; |
7c5b722a EB |
817 | const struct rte_flow_attr flow_attr = { |
818 | .group = 0, | |
819 | .priority = 0, | |
820 | .ingress = 1, | |
821 | .egress = 0 | |
822 | }; | |
60e778c7 | 823 | struct rte_flow_error error; |
7c5b722a | 824 | struct rte_flow *flow; |
60e778c7 EB |
825 | |
826 | add_flow_mark_rss_actions(&actions, flow_mark, netdev); | |
827 | ||
828 | flow = netdev_offload_dpdk_flow_create(netdev, &flow_attr, patterns->items, | |
829 | actions.actions, &error); | |
830 | ||
831 | free_flow_actions(&actions); | |
832 | return flow; | |
833 | } | |
834 | ||
3c7330eb EB |
835 | static void |
836 | add_count_action(struct flow_actions *actions) | |
837 | { | |
838 | struct rte_flow_action_count *count = xzalloc(sizeof *count); | |
839 | ||
840 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_COUNT, count); | |
841 | } | |
842 | ||
60e778c7 | 843 | static int |
3c7330eb EB |
844 | add_port_id_action(struct flow_actions *actions, |
845 | struct netdev *outdev) | |
846 | { | |
847 | struct rte_flow_action_port_id *port_id; | |
848 | int outdev_id; | |
849 | ||
850 | outdev_id = netdev_dpdk_get_port_id(outdev); | |
851 | if (outdev_id < 0) { | |
852 | return -1; | |
853 | } | |
854 | port_id = xzalloc(sizeof *port_id); | |
855 | port_id->id = outdev_id; | |
856 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_PORT_ID, port_id); | |
857 | return 0; | |
858 | } | |
859 | ||
860 | static int | |
861 | add_output_action(struct netdev *netdev, | |
862 | struct flow_actions *actions, | |
863 | const struct nlattr *nla, | |
864 | struct offload_info *info) | |
865 | { | |
866 | struct netdev *outdev; | |
867 | odp_port_t port; | |
868 | int ret = 0; | |
869 | ||
870 | port = nl_attr_get_odp_port(nla); | |
871 | outdev = netdev_ports_get(port, info->dpif_class); | |
872 | if (outdev == NULL) { | |
873 | VLOG_DBG_RL(&rl, "Cannot find netdev for odp port %"PRIu32, port); | |
874 | return -1; | |
875 | } | |
876 | if (!netdev_flow_api_equals(netdev, outdev) || | |
877 | add_port_id_action(actions, outdev)) { | |
878 | VLOG_DBG_RL(&rl, "%s: Output to port \'%s\' cannot be offloaded.", | |
879 | netdev_get_name(netdev), netdev_get_name(outdev)); | |
880 | ret = -1; | |
881 | } | |
882 | netdev_close(outdev); | |
883 | return ret; | |
884 | } | |
885 | ||
ae32e08d EB |
886 | static int |
887 | add_set_flow_action__(struct flow_actions *actions, | |
888 | const void *value, void *mask, | |
889 | const size_t size, const int attr) | |
890 | { | |
891 | void *spec; | |
892 | ||
893 | if (mask) { | |
894 | /* DPDK does not support partially masked set actions. In such | |
895 | * case, fail the offload. | |
896 | */ | |
897 | if (is_all_zeros(mask, size)) { | |
898 | return 0; | |
899 | } | |
900 | if (!is_all_ones(mask, size)) { | |
901 | VLOG_DBG_RL(&rl, "Partial mask is not supported"); | |
902 | return -1; | |
903 | } | |
904 | } | |
905 | ||
906 | spec = xzalloc(size); | |
907 | memcpy(spec, value, size); | |
908 | add_flow_action(actions, attr, spec); | |
909 | ||
910 | /* Clear used mask for later checking. */ | |
911 | if (mask) { | |
912 | memset(mask, 0, size); | |
913 | } | |
914 | return 0; | |
915 | } | |
916 | ||
917 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_mac) == | |
918 | MEMBER_SIZEOF(struct ovs_key_ethernet, eth_src)); | |
919 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_mac) == | |
920 | MEMBER_SIZEOF(struct ovs_key_ethernet, eth_dst)); | |
d9a831c3 EB |
921 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_ipv4) == |
922 | MEMBER_SIZEOF(struct ovs_key_ipv4, ipv4_src)); | |
923 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_ipv4) == | |
924 | MEMBER_SIZEOF(struct ovs_key_ipv4, ipv4_dst)); | |
925 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_ttl) == | |
926 | MEMBER_SIZEOF(struct ovs_key_ipv4, ipv4_ttl)); | |
b9254f7b EB |
927 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_tp) == |
928 | MEMBER_SIZEOF(struct ovs_key_tcp, tcp_src)); | |
929 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_tp) == | |
930 | MEMBER_SIZEOF(struct ovs_key_tcp, tcp_dst)); | |
931 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_tp) == | |
932 | MEMBER_SIZEOF(struct ovs_key_udp, udp_src)); | |
933 | BUILD_ASSERT_DECL(sizeof(struct rte_flow_action_set_tp) == | |
934 | MEMBER_SIZEOF(struct ovs_key_udp, udp_dst)); | |
ae32e08d EB |
935 | |
936 | static int | |
937 | parse_set_actions(struct flow_actions *actions, | |
938 | const struct nlattr *set_actions, | |
939 | const size_t set_actions_len, | |
940 | bool masked) | |
941 | { | |
942 | const struct nlattr *sa; | |
943 | unsigned int sleft; | |
944 | ||
945 | #define add_set_flow_action(field, type) \ | |
946 | if (add_set_flow_action__(actions, &key->field, \ | |
947 | mask ? CONST_CAST(void *, &mask->field) : NULL, \ | |
948 | sizeof key->field, type)) { \ | |
949 | return -1; \ | |
950 | } | |
951 | ||
952 | NL_ATTR_FOR_EACH_UNSAFE (sa, sleft, set_actions, set_actions_len) { | |
953 | if (nl_attr_type(sa) == OVS_KEY_ATTR_ETHERNET) { | |
954 | const struct ovs_key_ethernet *key = nl_attr_get(sa); | |
955 | const struct ovs_key_ethernet *mask = masked ? key + 1 : NULL; | |
956 | ||
957 | add_set_flow_action(eth_src, RTE_FLOW_ACTION_TYPE_SET_MAC_SRC); | |
958 | add_set_flow_action(eth_dst, RTE_FLOW_ACTION_TYPE_SET_MAC_DST); | |
959 | ||
960 | if (mask && !is_all_zeros(mask, sizeof *mask)) { | |
961 | VLOG_DBG_RL(&rl, "Unsupported ETHERNET set action"); | |
962 | return -1; | |
963 | } | |
d9a831c3 EB |
964 | } else if (nl_attr_type(sa) == OVS_KEY_ATTR_IPV4) { |
965 | const struct ovs_key_ipv4 *key = nl_attr_get(sa); | |
966 | const struct ovs_key_ipv4 *mask = masked ? key + 1 : NULL; | |
967 | ||
968 | add_set_flow_action(ipv4_src, RTE_FLOW_ACTION_TYPE_SET_IPV4_SRC); | |
969 | add_set_flow_action(ipv4_dst, RTE_FLOW_ACTION_TYPE_SET_IPV4_DST); | |
970 | add_set_flow_action(ipv4_ttl, RTE_FLOW_ACTION_TYPE_SET_TTL); | |
971 | ||
972 | if (mask && !is_all_zeros(mask, sizeof *mask)) { | |
973 | VLOG_DBG_RL(&rl, "Unsupported IPv4 set action"); | |
974 | return -1; | |
975 | } | |
b9254f7b EB |
976 | } else if (nl_attr_type(sa) == OVS_KEY_ATTR_TCP) { |
977 | const struct ovs_key_tcp *key = nl_attr_get(sa); | |
978 | const struct ovs_key_tcp *mask = masked ? key + 1 : NULL; | |
979 | ||
980 | add_set_flow_action(tcp_src, RTE_FLOW_ACTION_TYPE_SET_TP_SRC); | |
981 | add_set_flow_action(tcp_dst, RTE_FLOW_ACTION_TYPE_SET_TP_DST); | |
982 | ||
983 | if (mask && !is_all_zeros(mask, sizeof *mask)) { | |
984 | VLOG_DBG_RL(&rl, "Unsupported TCP set action"); | |
985 | return -1; | |
986 | } | |
987 | } else if (nl_attr_type(sa) == OVS_KEY_ATTR_UDP) { | |
988 | const struct ovs_key_udp *key = nl_attr_get(sa); | |
989 | const struct ovs_key_udp *mask = masked ? key + 1 : NULL; | |
990 | ||
991 | add_set_flow_action(udp_src, RTE_FLOW_ACTION_TYPE_SET_TP_SRC); | |
992 | add_set_flow_action(udp_dst, RTE_FLOW_ACTION_TYPE_SET_TP_DST); | |
993 | ||
994 | if (mask && !is_all_zeros(mask, sizeof *mask)) { | |
995 | VLOG_DBG_RL(&rl, "Unsupported UDP set action"); | |
996 | return -1; | |
997 | } | |
ae32e08d EB |
998 | } else { |
999 | VLOG_DBG_RL(&rl, | |
1000 | "Unsupported set action type %d", nl_attr_type(sa)); | |
1001 | return -1; | |
1002 | } | |
1003 | } | |
1004 | ||
1005 | return 0; | |
1006 | } | |
1007 | ||
02927385 SB |
1008 | static int |
1009 | parse_vlan_push_action(struct flow_actions *actions, | |
1010 | const struct ovs_action_push_vlan *vlan_push) | |
1011 | { | |
1012 | struct rte_flow_action_of_push_vlan *rte_push_vlan; | |
1013 | struct rte_flow_action_of_set_vlan_pcp *rte_vlan_pcp; | |
1014 | struct rte_flow_action_of_set_vlan_vid *rte_vlan_vid; | |
1015 | ||
1016 | rte_push_vlan = xzalloc(sizeof *rte_push_vlan); | |
1017 | rte_push_vlan->ethertype = vlan_push->vlan_tpid; | |
1018 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN, rte_push_vlan); | |
1019 | ||
1020 | rte_vlan_pcp = xzalloc(sizeof *rte_vlan_pcp); | |
1021 | rte_vlan_pcp->vlan_pcp = vlan_tci_to_pcp(vlan_push->vlan_tci); | |
1022 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP, | |
1023 | rte_vlan_pcp); | |
1024 | ||
1025 | rte_vlan_vid = xzalloc(sizeof *rte_vlan_vid); | |
1026 | rte_vlan_vid->vlan_vid = htons(vlan_tci_to_vid(vlan_push->vlan_tci)); | |
1027 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID, | |
1028 | rte_vlan_vid); | |
1029 | return 0; | |
1030 | } | |
1031 | ||
3c7330eb EB |
1032 | static int |
1033 | parse_flow_actions(struct netdev *netdev, | |
60e778c7 EB |
1034 | struct flow_actions *actions, |
1035 | struct nlattr *nl_actions, | |
1036 | size_t nl_actions_len, | |
3c7330eb | 1037 | struct offload_info *info) |
60e778c7 EB |
1038 | { |
1039 | struct nlattr *nla; | |
1040 | size_t left; | |
1041 | ||
3c7330eb | 1042 | add_count_action(actions); |
60e778c7 | 1043 | NL_ATTR_FOR_EACH_UNSAFE (nla, left, nl_actions, nl_actions_len) { |
3c7330eb EB |
1044 | if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) { |
1045 | if (add_output_action(netdev, actions, nla, info)) { | |
1046 | return -1; | |
1047 | } | |
abb288c0 EB |
1048 | } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_DROP) { |
1049 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_DROP, NULL); | |
ae32e08d EB |
1050 | } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_SET || |
1051 | nl_attr_type(nla) == OVS_ACTION_ATTR_SET_MASKED) { | |
1052 | const struct nlattr *set_actions = nl_attr_get(nla); | |
1053 | const size_t set_actions_len = nl_attr_get_size(nla); | |
1054 | bool masked = nl_attr_type(nla) == OVS_ACTION_ATTR_SET_MASKED; | |
1055 | ||
1056 | if (parse_set_actions(actions, set_actions, set_actions_len, | |
1057 | masked)) { | |
1058 | return -1; | |
1059 | } | |
02927385 SB |
1060 | } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_PUSH_VLAN) { |
1061 | const struct ovs_action_push_vlan *vlan = nl_attr_get(nla); | |
1062 | ||
1063 | if (parse_vlan_push_action(actions, vlan)) { | |
1064 | return -1; | |
1065 | } | |
1066 | } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_POP_VLAN) { | |
1067 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_POP_VLAN, NULL); | |
3c7330eb EB |
1068 | } else { |
1069 | VLOG_DBG_RL(&rl, "Unsupported action type %d", nl_attr_type(nla)); | |
1070 | return -1; | |
1071 | } | |
60e778c7 EB |
1072 | } |
1073 | ||
1074 | if (nl_actions_len == 0) { | |
1075 | VLOG_DBG_RL(&rl, "No actions provided"); | |
1076 | return -1; | |
1077 | } | |
1078 | ||
1079 | add_flow_action(actions, RTE_FLOW_ACTION_TYPE_END, NULL); | |
1080 | return 0; | |
1081 | } | |
1082 | ||
1083 | static struct rte_flow * | |
1084 | netdev_offload_dpdk_actions(struct netdev *netdev, | |
1085 | struct flow_patterns *patterns, | |
1086 | struct nlattr *nl_actions, | |
1087 | size_t actions_len, | |
1088 | struct offload_info *info) | |
1089 | { | |
1090 | const struct rte_flow_attr flow_attr = { .ingress = 1, .transfer = 1 }; | |
1091 | struct flow_actions actions = { .actions = NULL, .cnt = 0 }; | |
1092 | struct rte_flow *flow = NULL; | |
7c5b722a | 1093 | struct rte_flow_error error; |
60e778c7 EB |
1094 | int ret; |
1095 | ||
1096 | ret = parse_flow_actions(netdev, &actions, nl_actions, actions_len, info); | |
1097 | if (ret) { | |
1098 | goto out; | |
1099 | } | |
1100 | flow = netdev_offload_dpdk_flow_create(netdev, &flow_attr, patterns->items, | |
1101 | actions.actions, &error); | |
1102 | out: | |
1103 | free_flow_actions(&actions); | |
1104 | return flow; | |
1105 | } | |
1106 | ||
1107 | static int | |
1108 | netdev_offload_dpdk_add_flow(struct netdev *netdev, | |
1109 | const struct match *match, | |
1110 | struct nlattr *nl_actions, | |
1111 | size_t actions_len, | |
1112 | const ovs_u128 *ufid, | |
1113 | struct offload_info *info) | |
1114 | { | |
1115 | struct flow_patterns patterns = { .items = NULL, .cnt = 0 }; | |
1116 | bool actions_offloaded = true; | |
1117 | struct rte_flow *flow; | |
7c5b722a | 1118 | int ret = 0; |
7c5b722a | 1119 | |
900fe007 | 1120 | ret = parse_flow_match(&patterns, match); |
7c5b722a EB |
1121 | if (ret) { |
1122 | goto out; | |
1123 | } | |
3d67b2d2 | 1124 | |
60e778c7 EB |
1125 | flow = netdev_offload_dpdk_actions(netdev, &patterns, nl_actions, |
1126 | actions_len, info); | |
1127 | if (!flow) { | |
1128 | /* If we failed to offload the rule actions fallback to MARK+RSS | |
1129 | * actions. | |
1130 | */ | |
1131 | flow = netdev_offload_dpdk_mark_rss(&patterns, netdev, | |
1132 | info->flow_mark); | |
1133 | actions_offloaded = false; | |
1134 | } | |
3d67b2d2 | 1135 | |
3d67b2d2 | 1136 | if (!flow) { |
3d67b2d2 RBY |
1137 | ret = -1; |
1138 | goto out; | |
1139 | } | |
60e778c7 | 1140 | ufid_to_rte_flow_associate(ufid, flow, actions_offloaded); |
3d67b2d2 RBY |
1141 | VLOG_DBG("%s: installed flow %p by ufid "UUID_FMT"\n", |
1142 | netdev_get_name(netdev), flow, UUID_ARGS((struct uuid *)ufid)); | |
1143 | ||
1144 | out: | |
900fe007 | 1145 | free_flow_patterns(&patterns); |
3d67b2d2 RBY |
1146 | return ret; |
1147 | } | |
1148 | ||
1149 | /* | |
1150 | * Check if any unsupported flow patterns are specified. | |
1151 | */ | |
1152 | static int | |
4f746d52 | 1153 | netdev_offload_dpdk_validate_flow(const struct match *match) |
3d67b2d2 RBY |
1154 | { |
1155 | struct match match_zero_wc; | |
1156 | const struct flow *masks = &match->wc.masks; | |
1157 | ||
1158 | /* Create a wc-zeroed version of flow. */ | |
1159 | match_init(&match_zero_wc, &match->flow, &match->wc); | |
1160 | ||
1161 | if (!is_all_zeros(&match_zero_wc.flow.tunnel, | |
1162 | sizeof match_zero_wc.flow.tunnel)) { | |
1163 | goto err; | |
1164 | } | |
1165 | ||
1166 | if (masks->metadata || masks->skb_priority || | |
1167 | masks->pkt_mark || masks->dp_hash) { | |
1168 | goto err; | |
1169 | } | |
1170 | ||
1171 | /* recirc id must be zero. */ | |
1172 | if (match_zero_wc.flow.recirc_id) { | |
1173 | goto err; | |
1174 | } | |
1175 | ||
1176 | if (masks->ct_state || masks->ct_nw_proto || | |
1177 | masks->ct_zone || masks->ct_mark || | |
1178 | !ovs_u128_is_zero(masks->ct_label)) { | |
1179 | goto err; | |
1180 | } | |
1181 | ||
1182 | if (masks->conj_id || masks->actset_output) { | |
1183 | goto err; | |
1184 | } | |
1185 | ||
1186 | /* Unsupported L2. */ | |
1187 | if (!is_all_zeros(masks->mpls_lse, sizeof masks->mpls_lse)) { | |
1188 | goto err; | |
1189 | } | |
1190 | ||
1191 | /* Unsupported L3. */ | |
1192 | if (masks->ipv6_label || masks->ct_nw_src || masks->ct_nw_dst || | |
1193 | !is_all_zeros(&masks->ipv6_src, sizeof masks->ipv6_src) || | |
1194 | !is_all_zeros(&masks->ipv6_dst, sizeof masks->ipv6_dst) || | |
1195 | !is_all_zeros(&masks->ct_ipv6_src, sizeof masks->ct_ipv6_src) || | |
1196 | !is_all_zeros(&masks->ct_ipv6_dst, sizeof masks->ct_ipv6_dst) || | |
1197 | !is_all_zeros(&masks->nd_target, sizeof masks->nd_target) || | |
1198 | !is_all_zeros(&masks->nsh, sizeof masks->nsh) || | |
1199 | !is_all_zeros(&masks->arp_sha, sizeof masks->arp_sha) || | |
1200 | !is_all_zeros(&masks->arp_tha, sizeof masks->arp_tha)) { | |
1201 | goto err; | |
1202 | } | |
1203 | ||
1204 | /* If fragmented, then don't HW accelerate - for now. */ | |
1205 | if (match_zero_wc.flow.nw_frag) { | |
1206 | goto err; | |
1207 | } | |
1208 | ||
1209 | /* Unsupported L4. */ | |
1210 | if (masks->igmp_group_ip4 || masks->ct_tp_src || masks->ct_tp_dst) { | |
1211 | goto err; | |
1212 | } | |
1213 | ||
1214 | return 0; | |
1215 | ||
1216 | err: | |
1217 | VLOG_ERR("cannot HW accelerate this flow due to unsupported protocols"); | |
1218 | return -1; | |
1219 | } | |
1220 | ||
1221 | static int | |
4f746d52 | 1222 | netdev_offload_dpdk_destroy_flow(struct netdev *netdev, |
e0c58ca6 OM |
1223 | const ovs_u128 *ufid, |
1224 | struct rte_flow *rte_flow) | |
3d67b2d2 RBY |
1225 | { |
1226 | struct rte_flow_error error; | |
1227 | int ret = netdev_dpdk_rte_flow_destroy(netdev, rte_flow, &error); | |
1228 | ||
1229 | if (ret == 0) { | |
1230 | ufid_to_rte_flow_disassociate(ufid); | |
1231 | VLOG_DBG("%s: removed rte flow %p associated with ufid " UUID_FMT "\n", | |
1232 | netdev_get_name(netdev), rte_flow, | |
1233 | UUID_ARGS((struct uuid *)ufid)); | |
1234 | } else { | |
7d6033d6 EB |
1235 | VLOG_ERR("%s: Failed to destroy flow: %s (%u)\n", |
1236 | netdev_get_name(netdev), error.message, error.type); | |
3d67b2d2 RBY |
1237 | } |
1238 | ||
1239 | return ret; | |
1240 | } | |
1241 | ||
5fc5c50f | 1242 | static int |
4f746d52 | 1243 | netdev_offload_dpdk_flow_put(struct netdev *netdev, struct match *match, |
e0c58ca6 OM |
1244 | struct nlattr *actions, size_t actions_len, |
1245 | const ovs_u128 *ufid, struct offload_info *info, | |
75ad1cd6 | 1246 | struct dpif_flow_stats *stats) |
3d67b2d2 | 1247 | { |
34ce6bf7 | 1248 | struct ufid_to_rte_flow_data *rte_flow_data; |
3d67b2d2 RBY |
1249 | int ret; |
1250 | ||
1251 | /* | |
1252 | * If an old rte_flow exists, it means it's a flow modification. | |
1253 | * Here destroy the old rte flow first before adding a new one. | |
1254 | */ | |
34ce6bf7 EB |
1255 | rte_flow_data = ufid_to_rte_flow_data_find(ufid); |
1256 | if (rte_flow_data && rte_flow_data->rte_flow) { | |
1257 | ret = netdev_offload_dpdk_destroy_flow(netdev, ufid, | |
1258 | rte_flow_data->rte_flow); | |
3d67b2d2 RBY |
1259 | if (ret < 0) { |
1260 | return ret; | |
1261 | } | |
1262 | } | |
1263 | ||
4f746d52 | 1264 | ret = netdev_offload_dpdk_validate_flow(match); |
3d67b2d2 RBY |
1265 | if (ret < 0) { |
1266 | return ret; | |
1267 | } | |
1268 | ||
75ad1cd6 BP |
1269 | if (stats) { |
1270 | memset(stats, 0, sizeof *stats); | |
1271 | } | |
4f746d52 | 1272 | return netdev_offload_dpdk_add_flow(netdev, match, actions, |
e0c58ca6 | 1273 | actions_len, ufid, info); |
3d67b2d2 RBY |
1274 | } |
1275 | ||
5fc5c50f | 1276 | static int |
4f746d52 | 1277 | netdev_offload_dpdk_flow_del(struct netdev *netdev, const ovs_u128 *ufid, |
75ad1cd6 | 1278 | struct dpif_flow_stats *stats) |
3d67b2d2 | 1279 | { |
34ce6bf7 | 1280 | struct ufid_to_rte_flow_data *rte_flow_data; |
3d67b2d2 | 1281 | |
34ce6bf7 EB |
1282 | rte_flow_data = ufid_to_rte_flow_data_find(ufid); |
1283 | if (!rte_flow_data || !rte_flow_data->rte_flow) { | |
3d67b2d2 RBY |
1284 | return -1; |
1285 | } | |
1286 | ||
75ad1cd6 BP |
1287 | if (stats) { |
1288 | memset(stats, 0, sizeof *stats); | |
1289 | } | |
34ce6bf7 EB |
1290 | return netdev_offload_dpdk_destroy_flow(netdev, ufid, |
1291 | rte_flow_data->rte_flow); | |
3d67b2d2 | 1292 | } |
5fc5c50f IM |
1293 | |
1294 | static int | |
4f746d52 | 1295 | netdev_offload_dpdk_init_flow_api(struct netdev *netdev) |
5fc5c50f IM |
1296 | { |
1297 | return netdev_dpdk_flow_api_supported(netdev) ? 0 : EOPNOTSUPP; | |
1298 | } | |
1299 | ||
2aca29df EB |
1300 | static int |
1301 | netdev_offload_dpdk_flow_get(struct netdev *netdev, | |
1302 | struct match *match OVS_UNUSED, | |
1303 | struct nlattr **actions OVS_UNUSED, | |
1304 | const ovs_u128 *ufid, | |
1305 | struct dpif_flow_stats *stats, | |
1306 | struct dpif_flow_attrs *attrs, | |
1307 | struct ofpbuf *buf OVS_UNUSED) | |
1308 | { | |
1309 | struct rte_flow_query_count query = { .reset = 1 }; | |
1310 | struct ufid_to_rte_flow_data *rte_flow_data; | |
1311 | struct rte_flow_error error; | |
1312 | int ret = 0; | |
1313 | ||
1314 | rte_flow_data = ufid_to_rte_flow_data_find(ufid); | |
1315 | if (!rte_flow_data || !rte_flow_data->rte_flow) { | |
1316 | ret = -1; | |
1317 | goto out; | |
1318 | } | |
1319 | ||
1320 | attrs->offloaded = true; | |
1321 | if (!rte_flow_data->actions_offloaded) { | |
1322 | attrs->dp_layer = "ovs"; | |
1323 | memset(stats, 0, sizeof *stats); | |
1324 | goto out; | |
1325 | } | |
1326 | attrs->dp_layer = "dpdk"; | |
1327 | ret = netdev_dpdk_rte_flow_query_count(netdev, rte_flow_data->rte_flow, | |
1328 | &query, &error); | |
1329 | if (ret) { | |
1330 | VLOG_DBG_RL(&rl, "%s: Failed to query ufid "UUID_FMT" flow: %p\n", | |
1331 | netdev_get_name(netdev), UUID_ARGS((struct uuid *) ufid), | |
1332 | rte_flow_data->rte_flow); | |
1333 | goto out; | |
1334 | } | |
1335 | rte_flow_data->stats.n_packets += (query.hits_set) ? query.hits : 0; | |
1336 | rte_flow_data->stats.n_bytes += (query.bytes_set) ? query.bytes : 0; | |
1337 | if (query.hits_set && query.hits) { | |
1338 | rte_flow_data->stats.used = time_msec(); | |
1339 | } | |
1340 | memcpy(stats, &rte_flow_data->stats, sizeof *stats); | |
1341 | out: | |
d7b55c5c | 1342 | attrs->dp_extra_info = NULL; |
2aca29df EB |
1343 | return ret; |
1344 | } | |
1345 | ||
4f746d52 | 1346 | const struct netdev_flow_api netdev_offload_dpdk = { |
5fc5c50f | 1347 | .type = "dpdk_flow_api", |
4f746d52 IM |
1348 | .flow_put = netdev_offload_dpdk_flow_put, |
1349 | .flow_del = netdev_offload_dpdk_flow_del, | |
1350 | .init_flow_api = netdev_offload_dpdk_init_flow_api, | |
2aca29df | 1351 | .flow_get = netdev_offload_dpdk_flow_get, |
5fc5c50f | 1352 | }; |