/src/openvswitch/lib/dpif-netdev-lookup-generic.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017 Nicira, Inc. |
3 | | * Copyright (c) 2019, 2020 Intel Corporation. |
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 | | |
18 | | #include <config.h> |
19 | | #include "dpif-netdev.h" |
20 | | #include "dpif-netdev-lookup.h" |
21 | | |
22 | | #include "bitmap.h" |
23 | | #include "cmap.h" |
24 | | |
25 | | #include "dp-packet.h" |
26 | | #include "dpif.h" |
27 | | #include "dpif-netdev-perf.h" |
28 | | #include "dpif-provider.h" |
29 | | #include "flow.h" |
30 | | #include "ovs-thread.h" |
31 | | #include "packets.h" |
32 | | #include "pvector.h" |
33 | | |
34 | | VLOG_DEFINE_THIS_MODULE(dpif_lookup_generic); |
35 | | |
36 | | /* Lookup functions below depends on the internal structure of flowmap. */ |
37 | | BUILD_ASSERT_DECL(FLOWMAP_UNITS == 2); |
38 | | |
39 | | struct block_array { |
40 | | uint32_t count; /* Number of items allocated in 'blocks' */ |
41 | | uint64_t blocks[]; |
42 | | }; |
43 | | |
44 | | DEFINE_PER_THREAD_MALLOCED_DATA(struct block_array *, block_array); |
45 | | |
46 | | static inline uint64_t * |
47 | | get_blocks_scratch(uint32_t required_count) |
48 | 0 | { |
49 | 0 | struct block_array *array = block_array_get(); |
50 | | |
51 | | /* Check if this thread already has a large enough array allocated. |
52 | | * This is a predictable and unlikely branch, as it occurs only once at |
53 | | * startup, or if a subtable with higher block count is added. |
54 | | */ |
55 | 0 | if (OVS_UNLIKELY(!array || array->count < required_count)) { |
56 | 0 | array = xrealloc(array, sizeof *array + |
57 | 0 | (required_count * sizeof array->blocks[0])); |
58 | 0 | array->count = required_count; |
59 | 0 | block_array_set_unsafe(array); |
60 | 0 | VLOG_DBG("Block array resized to %"PRIu32, required_count); |
61 | 0 | } |
62 | |
|
63 | 0 | return &array->blocks[0]; |
64 | 0 | } |
65 | | |
66 | | static inline void |
67 | | netdev_flow_key_flatten_unit(const uint64_t *pkt_blocks, |
68 | | const uint64_t *tbl_blocks, |
69 | | const uint64_t *mf_masks, |
70 | | uint64_t *blocks_scratch, |
71 | | const uint64_t pkt_mf_bits, |
72 | | const uint32_t count) |
73 | 0 | { |
74 | 0 | uint32_t i; |
75 | |
|
76 | 0 | for (i = 0; i < count; i++) { |
77 | 0 | uint64_t mf_mask = mf_masks[i]; |
78 | | /* Calculate the block index for the packet metadata. */ |
79 | 0 | uint64_t idx_bits = mf_mask & pkt_mf_bits; |
80 | 0 | const uint32_t pkt_idx = count_1bits(idx_bits); |
81 | | |
82 | | /* Check if the packet has the subtable miniflow bit set. If yes, the |
83 | | * block at the above pkt_idx will be stored, otherwise it is masked |
84 | | * out to be zero. |
85 | | */ |
86 | 0 | uint64_t pkt_has_mf_bit = (mf_mask + 1) & pkt_mf_bits; |
87 | 0 | uint64_t no_bit = ((!pkt_has_mf_bit) > 0) - 1; |
88 | | |
89 | | /* Mask packet block by table block, and mask to zero if packet |
90 | | * doesn't actually contain this block of metadata. |
91 | | */ |
92 | 0 | blocks_scratch[i] = pkt_blocks[pkt_idx] & tbl_blocks[i] & no_bit; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | /* This function takes a packet, and subtable and writes an array of uint64_t |
97 | | * blocks. The blocks contain the metadata that the subtable matches on, in |
98 | | * the same order as the subtable, allowing linear iteration over the blocks. |
99 | | * |
100 | | * To calculate the blocks contents, the netdev_flow_key_flatten_unit function |
101 | | * is called twice, once for each "unit" of the miniflow. This call can be |
102 | | * inlined by the compiler for performance. |
103 | | * |
104 | | * Note that the u0_count and u1_count variables can be compile-time constants, |
105 | | * allowing the loop in the inlined flatten_unit() function to be compile-time |
106 | | * unrolled, or possibly removed totally by unrolling by the loop iterations. |
107 | | * The compile time optimizations enabled by this design improves performance. |
108 | | */ |
109 | | static inline void |
110 | | netdev_flow_key_flatten(const struct netdev_flow_key *key, |
111 | | const struct netdev_flow_key *mask, |
112 | | const uint64_t *mf_masks, |
113 | | uint64_t *blocks_scratch, |
114 | | const uint32_t u0_count, |
115 | | const uint32_t u1_count) |
116 | 0 | { |
117 | | /* Load mask from subtable, mask with packet mf, popcount to get idx. */ |
118 | 0 | const uint64_t *pkt_blocks = miniflow_get_values(&key->mf); |
119 | 0 | const uint64_t *tbl_blocks = miniflow_get_values(&mask->mf); |
120 | | |
121 | | /* Packet miniflow bits to be masked by pre-calculated mf_masks. */ |
122 | 0 | const uint64_t pkt_bits_u0 = key->mf.map.bits[0]; |
123 | 0 | const uint32_t pkt_bits_u0_pop = count_1bits(pkt_bits_u0); |
124 | 0 | const uint64_t pkt_bits_u1 = key->mf.map.bits[1]; |
125 | | |
126 | | /* Unit 0 flattening */ |
127 | 0 | netdev_flow_key_flatten_unit(&pkt_blocks[0], |
128 | 0 | &tbl_blocks[0], |
129 | 0 | &mf_masks[0], |
130 | 0 | &blocks_scratch[0], |
131 | 0 | pkt_bits_u0, |
132 | 0 | u0_count); |
133 | | |
134 | | /* Unit 1 flattening: |
135 | | * Move the pointers forward in the arrays based on u0 offsets, NOTE: |
136 | | * 1) pkt blocks indexed by actual popcount of u0, which is NOT always |
137 | | * the same as the amount of bits set in the subtable. |
138 | | * 2) mf_masks, tbl_block and blocks_scratch are all "flat" arrays, so |
139 | | * the index is always u0_count. |
140 | | */ |
141 | 0 | netdev_flow_key_flatten_unit(&pkt_blocks[pkt_bits_u0_pop], |
142 | 0 | &tbl_blocks[u0_count], |
143 | 0 | &mf_masks[u0_count], |
144 | 0 | &blocks_scratch[u0_count], |
145 | 0 | pkt_bits_u1, |
146 | 0 | u1_count); |
147 | 0 | } |
148 | | |
149 | | /* Compares a rule and the blocks representing a key, returns 1 on a match. */ |
150 | | static inline uint64_t |
151 | | netdev_rule_matches_key(const struct dpcls_rule *rule, |
152 | | const uint32_t mf_bits_total, |
153 | | const uint64_t *blocks_scratch) |
154 | 0 | { |
155 | 0 | const uint64_t *keyp = miniflow_get_values(&rule->flow.mf); |
156 | 0 | const uint64_t *maskp = miniflow_get_values(&rule->mask->mf); |
157 | 0 | uint64_t not_match = 0; |
158 | |
|
159 | 0 | for (int i = 0; i < mf_bits_total; i++) { |
160 | 0 | not_match |= (blocks_scratch[i] & maskp[i]) != keyp[i]; |
161 | 0 | } |
162 | | |
163 | | /* Invert result to show match as 1. */ |
164 | 0 | return !not_match; |
165 | 0 | } |
166 | | |
167 | | /* Const prop version of the function: note that mf bits total and u0 are |
168 | | * explicitly passed in here, while they're also available at runtime from the |
169 | | * subtable pointer. By making them compile time, we enable the compiler to |
170 | | * unroll loops and flatten out code-sequences based on the knowledge of the |
171 | | * mf_bits_* compile time values. This results in improved performance. |
172 | | * |
173 | | * Note: this function is marked with ALWAYS_INLINE to ensure the compiler |
174 | | * inlines the below code, and then uses the compile time constants to make |
175 | | * specialized versions of the runtime code. Without ALWAYS_INLINE, the |
176 | | * compiler might decide to not inline, and performance will suffer. |
177 | | */ |
178 | | static inline uint32_t ALWAYS_INLINE |
179 | | lookup_generic_impl(struct dpcls_subtable *subtable, |
180 | | uint32_t keys_map, |
181 | | const struct netdev_flow_key *keys[], |
182 | | struct dpcls_rule **rules, |
183 | | const uint32_t bit_count_u0, |
184 | | const uint32_t bit_count_u1) |
185 | 0 | { |
186 | 0 | const uint32_t n_pkts = count_1bits(keys_map); |
187 | 0 | ovs_assert(NETDEV_MAX_BURST >= n_pkts); |
188 | 0 | uint32_t hashes[NETDEV_MAX_BURST]; |
189 | |
|
190 | 0 | const uint32_t bit_count_total = bit_count_u0 + bit_count_u1; |
191 | 0 | const uint32_t block_count_required = bit_count_total * NETDEV_MAX_BURST; |
192 | 0 | uint64_t *mf_masks = subtable->mf_masks; |
193 | 0 | int i; |
194 | | |
195 | | /* Blocks scratch is an optimization to re-use the same packet miniflow |
196 | | * block data when doing rule-verify. This reduces work done during lookup |
197 | | * and hence improves performance. The blocks_scratch array is stored as a |
198 | | * thread local variable, as each thread requires its own blocks memory. |
199 | | */ |
200 | 0 | uint64_t *blocks_scratch = get_blocks_scratch(block_count_required); |
201 | | |
202 | | /* Flatten the packet metadata into the blocks_scratch[] using subtable. */ |
203 | 0 | ULLONG_FOR_EACH_1 (i, keys_map) { |
204 | 0 | netdev_flow_key_flatten(keys[i], |
205 | 0 | &subtable->mask, |
206 | 0 | mf_masks, |
207 | 0 | &blocks_scratch[i * bit_count_total], |
208 | 0 | bit_count_u0, |
209 | 0 | bit_count_u1); |
210 | 0 | } |
211 | | |
212 | | /* Hash the now linearized blocks of packet metadata. */ |
213 | 0 | ULLONG_FOR_EACH_1 (i, keys_map) { |
214 | 0 | uint64_t *block_ptr = &blocks_scratch[i * bit_count_total]; |
215 | 0 | uint32_t hash = hash_add_words64(0, block_ptr, bit_count_total); |
216 | 0 | hashes[i] = hash_finish(hash, bit_count_total * 8); |
217 | 0 | } |
218 | | |
219 | | /* Lookup: this returns a bitmask of packets where the hash table had |
220 | | * an entry for the given hash key. Presence of a hash key does not |
221 | | * guarantee matching the key, as there can be hash collisions. |
222 | | */ |
223 | 0 | uint32_t found_map; |
224 | 0 | const struct cmap_node *nodes[NETDEV_MAX_BURST]; |
225 | |
|
226 | 0 | found_map = cmap_find_batch(&subtable->rules, keys_map, hashes, nodes); |
227 | | |
228 | | /* Verify that packet actually matched rule. If not found, a hash |
229 | | * collision has taken place, so continue searching with the next node. |
230 | | */ |
231 | 0 | ULLONG_FOR_EACH_1 (i, found_map) { |
232 | 0 | struct dpcls_rule *rule; |
233 | |
|
234 | 0 | CMAP_NODE_FOR_EACH (rule, cmap_node, nodes[i]) { |
235 | 0 | const uint32_t cidx = i * bit_count_total; |
236 | 0 | uint32_t match = netdev_rule_matches_key(rule, bit_count_total, |
237 | 0 | &blocks_scratch[cidx]); |
238 | |
|
239 | 0 | if (OVS_LIKELY(match)) { |
240 | 0 | rules[i] = rule; |
241 | 0 | subtable->hit_cnt++; |
242 | 0 | goto next; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | /* None of the found rules was a match. Reset the i-th bit to |
247 | | * keep searching this key in the next subtable. */ |
248 | 0 | ULLONG_SET0(found_map, i); /* Did not match. */ |
249 | 0 | next: |
250 | 0 | ; /* Keep Sparse happy. */ |
251 | 0 | } |
252 | | |
253 | 0 | return found_map; |
254 | 0 | } |
255 | | |
256 | | /* Generic lookup function that uses runtime provided mf bits for iterating. */ |
257 | | static uint32_t |
258 | | dpcls_subtable_lookup_generic(struct dpcls_subtable *subtable, |
259 | | uint32_t keys_map, |
260 | | const struct netdev_flow_key *keys[], |
261 | | struct dpcls_rule **rules) |
262 | 0 | { |
263 | | /* Here the runtime subtable->mf_bits counts are used, which forces the |
264 | | * compiler to iterate normal for() loops. Due to this limitation in the |
265 | | * compilers available optimizations, this function has lower performance |
266 | | * than the below specialized functions. |
267 | | */ |
268 | 0 | return lookup_generic_impl(subtable, keys_map, keys, rules, |
269 | 0 | subtable->mf_bits_set_unit0, |
270 | 0 | subtable->mf_bits_set_unit1); |
271 | 0 | } |
272 | | |
273 | | /* Expand out specialized functions with U0 and U1 bit attributes. */ |
274 | | #define DECLARE_OPTIMIZED_LOOKUP_FUNCTION(U0, U1) \ |
275 | | static uint32_t \ |
276 | | dpcls_subtable_lookup_mf_u0w##U0##_u1w##U1( \ |
277 | | struct dpcls_subtable *subtable, \ |
278 | | uint32_t keys_map, \ |
279 | | const struct netdev_flow_key *keys[],\ |
280 | | struct dpcls_rule **rules) \ |
281 | 0 | { \ |
282 | 0 | return lookup_generic_impl(subtable, keys_map, keys, rules, U0, U1); \ |
283 | 0 | } \ Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w9_u1w4 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w9_u1w1 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w8_u1w1 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w5_u1w3 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w5_u1w2 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w5_u1w1 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w4_u1w1 Unexecuted instantiation: dpif-netdev-lookup-generic.c:dpcls_subtable_lookup_mf_u0w4_u1w0 |
284 | | |
285 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(9, 4) |
286 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(9, 1) |
287 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(8, 1) |
288 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(5, 3) |
289 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(5, 2) |
290 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(5, 1) |
291 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(4, 1) |
292 | | DECLARE_OPTIMIZED_LOOKUP_FUNCTION(4, 0) |
293 | | |
294 | | /* Check if a specialized function is valid for the required subtable. */ |
295 | | #define CHECK_LOOKUP_FUNCTION(U0, U1) \ |
296 | 0 | if (!f && u0_bits == U0 && u1_bits == U1) { \ |
297 | 0 | f = dpcls_subtable_lookup_mf_u0w##U0##_u1w##U1; \ |
298 | 0 | } |
299 | | |
300 | | /* Probe function to lookup an available specialized function. |
301 | | * If capable to run the requested miniflow fingerprint, this function returns |
302 | | * the most optimal implementation for that miniflow fingerprint. |
303 | | * @retval Non-NULL A valid function to handle the miniflow bit pattern |
304 | | * @retval NULL The requested miniflow is not supported by this implementation. |
305 | | */ |
306 | | dpcls_subtable_lookup_func |
307 | | dpcls_subtable_generic_probe(uint32_t u0_bits, uint32_t u1_bits) |
308 | 0 | { |
309 | 0 | dpcls_subtable_lookup_func f = NULL; |
310 | |
|
311 | 0 | CHECK_LOOKUP_FUNCTION(9, 4); |
312 | 0 | CHECK_LOOKUP_FUNCTION(9, 1); |
313 | 0 | CHECK_LOOKUP_FUNCTION(8, 1); |
314 | 0 | CHECK_LOOKUP_FUNCTION(5, 3); |
315 | 0 | CHECK_LOOKUP_FUNCTION(5, 2); |
316 | 0 | CHECK_LOOKUP_FUNCTION(5, 1); |
317 | 0 | CHECK_LOOKUP_FUNCTION(4, 1); |
318 | 0 | CHECK_LOOKUP_FUNCTION(4, 0); |
319 | |
|
320 | 0 | if (f) { |
321 | 0 | VLOG_DBG("Subtable using Generic Optimized for u0 %d, u1 %d\n", |
322 | 0 | u0_bits, u1_bits); |
323 | 0 | } else { |
324 | | /* Always return the generic function. */ |
325 | 0 | f = dpcls_subtable_lookup_generic; |
326 | 0 | } |
327 | |
|
328 | 0 | return f; |
329 | 0 | } |