/src/openvswitch/lib/odp-execute-private.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2022 Intel. |
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 | | #include <stdio.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "cpu.h" |
23 | | #include "dpdk.h" |
24 | | #include "dp-packet.h" |
25 | | #include "odp-execute.h" |
26 | | #include "odp-execute-private.h" |
27 | | #include "odp-netlink.h" |
28 | | #include "odp-util.h" |
29 | | #include "openvswitch/vlog.h" |
30 | | |
31 | | VLOG_DEFINE_THIS_MODULE(odp_execute_impl); |
32 | | static int active_action_impl_index; |
33 | | |
34 | | #if ACTION_IMPL_AVX512_CHECK |
35 | | /* Probe functions to check ISA requirements. */ |
36 | | bool |
37 | | action_avx512_isa_probe(void) |
38 | | { |
39 | | static enum ovs_cpu_isa isa_required[] = { |
40 | | OVS_CPU_ISA_X86_AVX512F, |
41 | | OVS_CPU_ISA_X86_AVX512BW, |
42 | | OVS_CPU_ISA_X86_BMI2, |
43 | | OVS_CPU_ISA_X86_AVX512VL, |
44 | | }; |
45 | | for (int i = 0; i < ARRAY_SIZE(isa_required); i++) { |
46 | | if (!cpu_has_isa(isa_required[i])) { |
47 | | return false; |
48 | | } |
49 | | } |
50 | | return true; |
51 | | } |
52 | | |
53 | | #else |
54 | | |
55 | | bool |
56 | | action_avx512_isa_probe(void) |
57 | 0 | { |
58 | 0 | return false; |
59 | 0 | } |
60 | | |
61 | | #endif |
62 | | |
63 | | #if ACTION_IMPL_AVX512_CHECK && HAVE_AVX512VBMI |
64 | | bool |
65 | | action_avx512vbmi_isa_probe(void) |
66 | | { |
67 | | return cpu_has_isa(OVS_CPU_ISA_X86_AVX512VBMI); |
68 | | } |
69 | | #else |
70 | | bool |
71 | | action_avx512vbmi_isa_probe(void) |
72 | 0 | { |
73 | 0 | return false; |
74 | 0 | } |
75 | | #endif |
76 | | |
77 | | static struct odp_execute_action_impl action_impls[] = { |
78 | | [ACTION_IMPL_AUTOVALIDATOR] = { |
79 | | .available = false, |
80 | | .name = "autovalidator", |
81 | | .init_func = action_autoval_init, |
82 | | }, |
83 | | |
84 | | [ACTION_IMPL_SCALAR] = { |
85 | | .available = false, |
86 | | .name = "scalar", |
87 | | .init_func = odp_action_scalar_init, |
88 | | }, |
89 | | |
90 | | #if ACTION_IMPL_AVX512_CHECK |
91 | | [ACTION_IMPL_AVX512] = { |
92 | | .available = false, |
93 | | .name = "avx512", |
94 | | .init_func = action_avx512_init, |
95 | | }, |
96 | | #endif |
97 | | }; |
98 | | |
99 | | static void |
100 | | action_impl_copy_funcs(struct odp_execute_action_impl *dest, |
101 | | const struct odp_execute_action_impl *src) |
102 | 0 | { |
103 | 0 | for (int i = 0; i < __OVS_ACTION_ATTR_MAX; i++) { |
104 | 0 | atomic_store_relaxed(&dest->funcs[i], src->funcs[i]); |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | | struct odp_execute_action_impl * |
109 | | odp_execute_action_set(const char *name) |
110 | 0 | { |
111 | 0 | for (int i = 0; i < ACTION_IMPL_MAX; i++) { |
112 | | /* String compare, and set ptrs atomically. */ |
113 | 0 | if (!strcmp(action_impls[i].name, name)) { |
114 | 0 | if (i != active_action_impl_index) { |
115 | 0 | active_action_impl_index = i; |
116 | 0 | VLOG_INFO("Action implementation set to %s", name); |
117 | 0 | } |
118 | 0 | return &action_impls[i]; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | return NULL; |
122 | 0 | } |
123 | | |
124 | | void |
125 | | odp_execute_action_get_info(struct ds *string) |
126 | 0 | { |
127 | 0 | ds_put_cstr(string, "Available Actions implementations:\n"); |
128 | 0 | for (int i = 0; i < ACTION_IMPL_MAX; i++) { |
129 | 0 | ds_put_format(string, " %s (available: %s, active: %s)\n", |
130 | 0 | action_impls[i].name, |
131 | 0 | action_impls[i].available ? "Yes" : "No", |
132 | 0 | i == active_action_impl_index ? "Yes" : "No"); |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | void |
137 | | odp_execute_action_init(void) |
138 | 0 | { |
139 | | /* Each impl's function array is initialized to reflect the scalar |
140 | | * implementation. This simplifies adding optimized implementations, |
141 | | * as the autovalidator can always compare all actions. |
142 | | * |
143 | | * Below will check if impl is available and copies the scalar functions |
144 | | * to all other implementations. */ |
145 | 0 | for (int i = 0; i < ACTION_IMPL_MAX; i++) { |
146 | 0 | bool avail = true; |
147 | |
|
148 | 0 | if (i != ACTION_IMPL_SCALAR) { |
149 | 0 | action_impl_copy_funcs(&action_impls[i], |
150 | 0 | &action_impls[ACTION_IMPL_SCALAR]); |
151 | 0 | } |
152 | |
|
153 | 0 | if (action_impls[i].init_func) { |
154 | | /* Return zero is success, non-zero means error. */ |
155 | 0 | avail = (action_impls[i].init_func(&action_impls[i]) == 0); |
156 | 0 | } |
157 | |
|
158 | 0 | action_impls[i].available = avail; |
159 | |
|
160 | 0 | VLOG_DBG("Actions implementation '%s' %s available.", |
161 | 0 | action_impls[i].name, avail ? "is" : "is not"); |
162 | | |
163 | | /* The following is a run-time check to make sure a scalar |
164 | | * implementation exists for the given ISA implementation. This is to |
165 | | * make sure the autovalidator works as expected. */ |
166 | 0 | if (avail && i != ACTION_IMPL_SCALAR) { |
167 | 0 | for (int j = 0; j < __OVS_ACTION_ATTR_MAX; j++) { |
168 | | /* No ovs_assert(), as it can be compiled out. */ |
169 | 0 | if (action_impls[ACTION_IMPL_SCALAR].funcs[j] == NULL |
170 | 0 | && action_impls[i].funcs[j] != NULL) { |
171 | 0 | ovs_assert_failure(OVS_SOURCE_LOCATOR, __func__, |
172 | 0 | "Missing scalar action function!"); |
173 | 0 | } |
174 | 0 | } |
175 | 0 | } |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | /* Init sequence required to be scalar first to pick up the default scalar |
180 | | * implementations, allowing over-riding of the optimized functions later. */ |
181 | | BUILD_ASSERT_DECL(ACTION_IMPL_SCALAR == 0); |
182 | | BUILD_ASSERT_DECL(ACTION_IMPL_AUTOVALIDATOR == 1); |
183 | | |
184 | | /* Loop over packets, and validate each one for the given action. */ |
185 | | static void |
186 | | action_autoval_generic(struct dp_packet_batch *batch, const struct nlattr *a) |
187 | 0 | { |
188 | 0 | struct odp_execute_action_impl *scalar = &action_impls[ACTION_IMPL_SCALAR]; |
189 | 0 | enum ovs_action_attr attr_type = nl_attr_type(a); |
190 | 0 | struct dp_packet_batch original_batch; |
191 | 0 | bool failed = false; |
192 | |
|
193 | 0 | dp_packet_batch_clone(&original_batch, batch); |
194 | |
|
195 | 0 | scalar->funcs[attr_type](batch, a); |
196 | |
|
197 | 0 | for (int impl = ACTION_IMPL_BEGIN; impl < ACTION_IMPL_MAX; impl++) { |
198 | | /* Clone original batch and execute implementation under test. */ |
199 | 0 | struct dp_packet_batch test_batch; |
200 | |
|
201 | 0 | dp_packet_batch_clone(&test_batch, &original_batch); |
202 | 0 | action_impls[impl].funcs[attr_type](&test_batch, a); |
203 | | |
204 | | /* Loop over implementations, checking each one. */ |
205 | 0 | for (int pidx = 0; pidx < original_batch.count; pidx++) { |
206 | 0 | struct dp_packet *good_pkt = batch->packets[pidx]; |
207 | 0 | struct dp_packet *test_pkt = test_batch.packets[pidx]; |
208 | |
|
209 | 0 | struct ds log_msg = DS_EMPTY_INITIALIZER; |
210 | | |
211 | | /* Compare packet length and payload contents. */ |
212 | 0 | bool eq = dp_packet_equal(good_pkt, test_pkt); |
213 | |
|
214 | 0 | if (!eq) { |
215 | 0 | ds_put_format(&log_msg, "Packet: %d\nAction : ", pidx); |
216 | 0 | format_odp_actions(&log_msg, a, a->nla_len, NULL); |
217 | 0 | ds_put_format(&log_msg, "\nGood hex:\n"); |
218 | 0 | ds_put_hex_dump(&log_msg, dp_packet_data(good_pkt), |
219 | 0 | dp_packet_size(good_pkt), 0, false); |
220 | 0 | ds_put_format(&log_msg, "Test hex:\n"); |
221 | 0 | ds_put_hex_dump(&log_msg, dp_packet_data(test_pkt), |
222 | 0 | dp_packet_size(test_pkt), 0, false); |
223 | |
|
224 | 0 | failed = true; |
225 | 0 | } |
226 | | |
227 | | /* Compare offsets and RSS */ |
228 | 0 | if (!dp_packet_compare_offsets(good_pkt, test_pkt, &log_msg)) { |
229 | 0 | failed = true; |
230 | 0 | } |
231 | |
|
232 | 0 | if (dp_packet_rss_valid(good_pkt)) { |
233 | 0 | uint32_t good_hash = dp_packet_get_rss_hash(good_pkt); |
234 | 0 | uint32_t test_hash = dp_packet_get_rss_hash(test_pkt); |
235 | |
|
236 | 0 | if (good_hash != test_hash) { |
237 | 0 | ds_put_format(&log_msg, |
238 | 0 | "Autovalidation rss hash failed\n"); |
239 | 0 | ds_put_format(&log_msg, "Good RSS hash : %u\n", good_hash); |
240 | 0 | ds_put_format(&log_msg, "Test RSS hash : %u\n", test_hash); |
241 | |
|
242 | 0 | failed = true; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | /* Compare packet metadata. */ |
247 | 0 | if (memcmp(&good_pkt->md, &test_pkt->md, sizeof good_pkt->md)) { |
248 | 0 | ds_put_format(&log_msg, "Autovalidation metadata failed\n"); |
249 | 0 | ds_put_format(&log_msg, "Good packet metadata:\n"); |
250 | 0 | ds_put_sparse_hex_dump(&log_msg, &good_pkt->md, |
251 | 0 | sizeof good_pkt->md, 0, false); |
252 | 0 | ds_put_format(&log_msg, "Test packet metadata:\n"); |
253 | 0 | ds_put_sparse_hex_dump(&log_msg, &test_pkt->md, |
254 | 0 | sizeof test_pkt->md, 0, false); |
255 | 0 | failed = true; |
256 | 0 | } |
257 | |
|
258 | 0 | if (failed) { |
259 | 0 | VLOG_ERR("Autovalidation of %s failed. Details:\n%s", |
260 | 0 | action_impls[impl].name, ds_cstr(&log_msg)); |
261 | 0 | ds_destroy(&log_msg); |
262 | 0 | failed = false; |
263 | 0 | } |
264 | 0 | } |
265 | 0 | dp_packet_delete_batch(&test_batch, true); |
266 | 0 | } |
267 | 0 | dp_packet_delete_batch(&original_batch, true); |
268 | 0 | } |
269 | | |
270 | | void |
271 | | odp_execute_scalar_action(struct dp_packet_batch *batch, |
272 | | const struct nlattr *action) |
273 | 0 | { |
274 | 0 | enum ovs_action_attr type = nl_attr_type(action); |
275 | |
|
276 | 0 | if (type <= OVS_ACTION_ATTR_MAX && |
277 | 0 | action_impls[ACTION_IMPL_SCALAR].funcs[type]) { |
278 | |
|
279 | 0 | action_impls[ACTION_IMPL_SCALAR].funcs[type](batch, action); |
280 | 0 | } |
281 | 0 | } |
282 | | |
283 | | int |
284 | | action_autoval_init(struct odp_execute_action_impl *self) |
285 | 0 | { |
286 | | /* Set function pointers for actions that can be applied directly, these |
287 | | * are identified by OVS_ACTION_ATTR_*. */ |
288 | 0 | for (int i = 0; i < __OVS_ACTION_ATTR_MAX; i++) { |
289 | 0 | if (action_impls[ACTION_IMPL_SCALAR].funcs[i]) { |
290 | 0 | self->funcs[i] = action_autoval_generic; |
291 | 0 | } |
292 | 0 | } |
293 | 0 | return 0; |
294 | 0 | } |