Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(c) 2020 Intel Corporation
3 : : */
4 : : #include <string.h>
5 : : #include <stdio.h>
6 : : #include <errno.h>
7 : :
8 : : #include <eal_export.h>
9 : : #include <rte_common.h>
10 : : #include <rte_cycles.h>
11 : : #include <rte_prefetch.h>
12 : : #include <rte_jhash.h>
13 : : #include <rte_hash_crc.h>
14 : :
15 : : #include "rte_swx_keycmp.h"
16 : : #include "rte_swx_table_learner.h"
17 : :
18 : : #ifndef RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES
19 : : #define RTE_SWX_TABLE_LEARNER_USE_HUGE_PAGES 1
20 : : #endif
21 : :
22 : : #ifndef RTE_SWX_TABLE_SELECTOR_HUGE_PAGES_DISABLE
23 : :
24 : : #include <rte_malloc.h>
25 : :
26 : : static void *
27 : : env_calloc(size_t size, size_t alignment, int numa_node)
28 : : {
29 : 0 : return rte_zmalloc_socket(NULL, size, alignment, numa_node);
30 : : }
31 : :
32 : : static void
33 : : env_free(void *start, size_t size __rte_unused)
34 : : {
35 : 0 : rte_free(start);
36 : 0 : }
37 : :
38 : : #else
39 : :
40 : : #include <numa.h>
41 : :
42 : : static void *
43 : : env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
44 : : {
45 : : void *start;
46 : :
47 : : if (numa_available() == -1)
48 : : return NULL;
49 : :
50 : : start = numa_alloc_onnode(size, numa_node);
51 : : if (!start)
52 : : return NULL;
53 : :
54 : : memset(start, 0, size);
55 : : return start;
56 : : }
57 : :
58 : : static void
59 : : env_free(void *start, size_t size)
60 : : {
61 : : if ((numa_available() == -1) || !start)
62 : : return;
63 : :
64 : : numa_free(start, size);
65 : : }
66 : :
67 : : #endif
68 : :
69 : : static void
70 : : table_keycpy(void *dst, void *src, uint32_t n_bytes)
71 : : {
72 : : memcpy(dst, src, n_bytes);
73 : : }
74 : :
75 : : #define TABLE_KEYS_PER_BUCKET 4
76 : : #define TABLE_KEYS_PER_BUCKET_LOG2 2
77 : :
78 : : #define TABLE_BUCKET_USEFUL_SIZE \
79 : : (TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t)))
80 : :
81 : : #define TABLE_BUCKET_PAD_SIZE \
82 : : (RTE_CACHE_LINE_SIZE - TABLE_BUCKET_USEFUL_SIZE)
83 : :
84 : : struct table_bucket {
85 : : uint32_t time[TABLE_KEYS_PER_BUCKET];
86 : : uint32_t sig[TABLE_KEYS_PER_BUCKET];
87 : : uint8_t key_timeout_id[TABLE_KEYS_PER_BUCKET];
88 : : uint8_t pad[TABLE_BUCKET_PAD_SIZE];
89 : : uint8_t key[];
90 : : };
91 : :
92 : : struct table_params {
93 : : /* The real key size. Must be non-zero. */
94 : : size_t key_size;
95 : :
96 : : /* The key size upgrated to the next power of 2. */
97 : : size_t key_size_pow2;
98 : :
99 : : /* log2(key_size_pow2). Purpose: avoid multiplication with non-power-of-2 numbers. */
100 : : size_t key_size_log2;
101 : :
102 : : /* The key offset within the key buffer. */
103 : : size_t key_offset;
104 : :
105 : : /* The real action data size. */
106 : : size_t action_data_size;
107 : :
108 : : /* The data size, i.e. the 8-byte action_id field plus the action data size, upgraded to the
109 : : * next power of 2.
110 : : */
111 : : size_t data_size_pow2;
112 : :
113 : : /* log2(data_size_pow2). Purpose: avoid multiplication with non-power of 2 numbers. */
114 : : size_t data_size_log2;
115 : :
116 : : /* Number of buckets. Must be a power of 2 to avoid modulo with non-power-of-2 numbers. */
117 : : size_t n_buckets;
118 : :
119 : : /* Bucket mask. Purpose: replace modulo with bitmask and operation. */
120 : : size_t bucket_mask;
121 : :
122 : : /* Total number of key bytes in the bucket, including the key padding bytes. There are
123 : : * (key_size_pow2 - key_size) padding bytes for each key in the bucket.
124 : : */
125 : : size_t bucket_key_all_size;
126 : :
127 : : /* Bucket size. Must be a power of 2 to avoid multiplication with non-power-of-2 number. */
128 : : size_t bucket_size;
129 : :
130 : : /* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
131 : : size_t bucket_size_log2;
132 : :
133 : : /* Hash function. */
134 : : rte_swx_hash_func_t hash_func;
135 : :
136 : : /* Key comparison function. */
137 : : rte_swx_keycmp_func_t keycmp_func;
138 : :
139 : : /* Set of all possible key timeout values measured in CPU clock cycles. */
140 : : uint64_t key_timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
141 : :
142 : : /* Number of key timeout values. */
143 : : uint32_t n_key_timeouts;
144 : :
145 : : /* Total memory size. */
146 : : size_t total_size;
147 : : };
148 : :
149 : : struct __rte_cache_aligned table {
150 : : /* Table parameters. */
151 : : struct table_params params;
152 : :
153 : : /* Table buckets. */
154 : : uint8_t buckets[];
155 : : };
156 : :
157 : : /* The timeout (in cycles) is stored in the table as a 32-bit value by truncating its least
158 : : * significant 32 bits. Therefore, to make sure the time is always advancing when adding the timeout
159 : : * value on top of the current time, the minimum timeout value is 1^32 cycles, which is 2 seconds on
160 : : * a 2 GHz CPU.
161 : : */
162 : : static uint64_t
163 : : timeout_convert(uint32_t timeout_in_seconds)
164 : : {
165 : 0 : uint64_t timeout_in_cycles = timeout_in_seconds * rte_get_tsc_hz();
166 : :
167 [ # # # # ]: 0 : if (!(timeout_in_cycles >> 32))
168 : : timeout_in_cycles = 1LLU << 32;
169 : :
170 : : return timeout_in_cycles;
171 : : }
172 : :
173 : : static int
174 : 0 : table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
175 : : {
176 : : uint32_t i;
177 : :
178 : : /* Check input parameters. */
179 [ # # ]: 0 : if (!params ||
180 [ # # ]: 0 : !params->key_size ||
181 [ # # # # ]: 0 : !params->n_keys_max ||
182 : 0 : (params->n_keys_max > 1U << 31) ||
183 [ # # ]: 0 : !params->key_timeout ||
184 [ # # # # ]: 0 : !params->n_key_timeouts ||
185 : : (params->n_key_timeouts > RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX))
186 : : return -EINVAL;
187 : :
188 [ # # ]: 0 : if (params->key_mask0) {
189 [ # # ]: 0 : for (i = 0; i < params->key_size; i++)
190 [ # # ]: 0 : if (params->key_mask0[i] != 0xFF)
191 : : break;
192 : :
193 [ # # ]: 0 : if (i < params->key_size)
194 : : return -EINVAL;
195 : : }
196 : :
197 [ # # ]: 0 : for (i = 0; i < params->n_key_timeouts; i++)
198 [ # # ]: 0 : if (!params->key_timeout[i])
199 : : return -EINVAL;
200 : :
201 : : /* Key. */
202 [ # # ]: 0 : p->key_size = params->key_size;
203 : :
204 [ # # ]: 0 : p->key_size_pow2 = rte_align64pow2(p->key_size);
205 : :
206 : 0 : p->key_size_log2 = rte_ctz64(p->key_size_pow2);
207 : :
208 : 0 : p->key_offset = params->key_offset;
209 : :
210 : : /* Data. */
211 : 0 : p->action_data_size = params->action_data_size;
212 : :
213 : 0 : p->data_size_pow2 = rte_align64pow2(sizeof(uint64_t) + p->action_data_size);
214 : :
215 [ # # ]: 0 : p->data_size_log2 = rte_ctz64(p->data_size_pow2);
216 : :
217 : : /* Buckets. */
218 : 0 : p->n_buckets = rte_align32pow2(params->n_keys_max);
219 : :
220 : 0 : p->bucket_mask = p->n_buckets - 1;
221 : :
222 : 0 : p->bucket_key_all_size = TABLE_KEYS_PER_BUCKET * p->key_size_pow2;
223 : :
224 : 0 : p->bucket_size = rte_align64pow2(sizeof(struct table_bucket) +
225 : 0 : p->bucket_key_all_size +
226 : 0 : TABLE_KEYS_PER_BUCKET * p->data_size_pow2);
227 : :
228 : 0 : p->bucket_size_log2 = rte_ctz64(p->bucket_size);
229 : :
230 [ # # ]: 0 : p->hash_func = params->hash_func ? params->hash_func : rte_hash_crc;
231 : :
232 : 0 : p->keycmp_func = rte_swx_keycmp_func_get(params->key_size);
233 : :
234 : : /* Timeout. */
235 [ # # ]: 0 : for (i = 0; i < params->n_key_timeouts; i++)
236 : 0 : p->key_timeout[i] = timeout_convert(params->key_timeout[i]);
237 : :
238 : 0 : p->n_key_timeouts = rte_align32pow2(params->n_key_timeouts);
239 : :
240 [ # # ]: 0 : for ( ; i < p->n_key_timeouts; i++)
241 : 0 : p->key_timeout[i] = p->key_timeout[0];
242 : :
243 : : /* Total size. */
244 : 0 : p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
245 : :
246 : 0 : return 0;
247 : : }
248 : :
249 : : static inline struct table_bucket *
250 : : table_bucket_get(struct table *t, size_t bucket_id)
251 : : {
252 : 0 : return (struct table_bucket *)&t->buckets[bucket_id << t->params.bucket_size_log2];
253 : : }
254 : :
255 : : static inline uint8_t *
256 : : table_bucket_key_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
257 : : {
258 : 0 : return &b->key[bucket_key_pos << t->params.key_size_log2];
259 : : }
260 : :
261 : : static inline uint64_t *
262 : : table_bucket_data_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
263 : : {
264 : 0 : return (uint64_t *)&b->key[t->params.bucket_key_all_size +
265 : 0 : (bucket_key_pos << t->params.data_size_log2)];
266 : : }
267 : :
268 : : static inline size_t
269 : : table_entry_id_get(struct table *t, struct table_bucket *b, size_t bucket_key_pos)
270 : : {
271 : 0 : size_t bucket_id = ((uint8_t *)b - t->buckets) >> t->params.bucket_size_log2;
272 : :
273 : 0 : return (bucket_id << TABLE_KEYS_PER_BUCKET_LOG2) + bucket_key_pos;
274 : : }
275 : :
276 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_footprint_get, 21.11)
277 : : uint64_t
278 : 0 : rte_swx_table_learner_footprint_get(struct rte_swx_table_learner_params *params)
279 : : {
280 : : struct table_params p;
281 : : int status;
282 : :
283 : 0 : status = table_params_get(&p, params);
284 : :
285 [ # # ]: 0 : return status ? 0 : p.total_size;
286 : : }
287 : :
288 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_create, 21.11)
289 : : void *
290 : 0 : rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node)
291 : : {
292 : : struct table_params p;
293 : : struct table *t;
294 : : int status;
295 : :
296 : : /* Check and process the input parameters. */
297 : 0 : status = table_params_get(&p, params);
298 [ # # ]: 0 : if (status)
299 : : return NULL;
300 : :
301 : : /* Memory allocation. */
302 : 0 : t = env_calloc(p.total_size, RTE_CACHE_LINE_SIZE, numa_node);
303 [ # # ]: 0 : if (!t)
304 : : return NULL;
305 : :
306 : : /* Memory initialization. */
307 : 0 : memcpy(&t->params, &p, sizeof(struct table_params));
308 : :
309 : 0 : return t;
310 : : }
311 : :
312 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_free, 21.11)
313 : : void
314 : 0 : rte_swx_table_learner_free(void *table)
315 : : {
316 : : struct table *t = table;
317 : :
318 [ # # ]: 0 : if (!t)
319 : : return;
320 : :
321 : : env_free(t, t->params.total_size);
322 : : }
323 : :
324 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_timeout_update, 22.07)
325 : : int
326 : 0 : rte_swx_table_learner_timeout_update(void *table,
327 : : uint32_t key_timeout_id,
328 : : uint32_t key_timeout)
329 : : {
330 : : struct table *t = table;
331 : :
332 [ # # ]: 0 : if (!t ||
333 [ # # # # ]: 0 : (key_timeout_id >= t->params.n_key_timeouts) ||
334 : : !key_timeout)
335 : : return -EINVAL;
336 : :
337 : 0 : t->params.key_timeout[key_timeout_id] = timeout_convert(key_timeout);
338 : :
339 : 0 : return 0;
340 : : }
341 : :
342 : : struct mailbox {
343 : : /* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
344 : : struct table_bucket *bucket;
345 : :
346 : : /* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
347 : : uint32_t input_sig;
348 : :
349 : : /* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
350 : : uint8_t *input_key;
351 : :
352 : : /* Writer: lookup state 1. Reader(s): add(). Values: 0 = miss; 1 = hit. */
353 : : uint32_t hit;
354 : :
355 : : /* Writer: lookup state 1. Reader(s): add(). Valid only when hit is non-zero. */
356 : : size_t bucket_key_pos;
357 : :
358 : : /* State. */
359 : : int state;
360 : : };
361 : :
362 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_mailbox_size_get, 21.11)
363 : : uint64_t
364 : 0 : rte_swx_table_learner_mailbox_size_get(void)
365 : : {
366 : 0 : return sizeof(struct mailbox);
367 : : }
368 : :
369 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_lookup, 21.11)
370 : : int
371 : 0 : rte_swx_table_learner_lookup(void *table,
372 : : void *mailbox,
373 : : uint64_t input_time,
374 : : uint8_t **key,
375 : : uint64_t *action_id,
376 : : uint8_t **action_data,
377 : : size_t *entry_id,
378 : : int *hit)
379 : : {
380 : : struct table *t = table;
381 : : struct mailbox *m = mailbox;
382 : :
383 [ # # # ]: 0 : switch (m->state) {
384 : 0 : case 0: {
385 : : uint8_t *input_key;
386 : : struct table_bucket *b;
387 : : size_t bucket_id;
388 : : uint32_t input_sig;
389 : :
390 : 0 : input_key = &(*key)[t->params.key_offset];
391 : 0 : input_sig = t->params.hash_func(input_key, t->params.key_size, 0);
392 : 0 : bucket_id = input_sig & t->params.bucket_mask;
393 : : b = table_bucket_get(t, bucket_id);
394 : :
395 : : rte_prefetch0(b);
396 : 0 : rte_prefetch0(&b->key[0]);
397 : 0 : rte_prefetch0(&b->key[RTE_CACHE_LINE_SIZE]);
398 : :
399 : 0 : m->bucket = b;
400 : 0 : m->input_key = input_key;
401 : 0 : m->input_sig = input_sig | 1;
402 : 0 : m->state = 1;
403 : 0 : return 0;
404 : : }
405 : :
406 : 0 : case 1: {
407 : 0 : struct table_bucket *b = m->bucket;
408 : : uint32_t i;
409 : :
410 : : /* Search the input key through the bucket keys. */
411 [ # # ]: 0 : for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
412 : 0 : uint64_t time = b->time[i];
413 : 0 : uint32_t sig = b->sig[i];
414 : 0 : uint8_t *key = table_bucket_key_get(t, b, i);
415 : :
416 : 0 : time <<= 32;
417 : :
418 [ # # ]: 0 : if ((time > input_time) &&
419 [ # # # # ]: 0 : (sig == m->input_sig) &&
420 : 0 : t->params.keycmp_func(key, m->input_key, t->params.key_size)) {
421 : : uint64_t *data = table_bucket_data_get(t, b, i);
422 : :
423 : : /* Hit. */
424 : : rte_prefetch0(data);
425 : :
426 : 0 : m->hit = 1;
427 : 0 : m->bucket_key_pos = i;
428 : 0 : m->state = 0;
429 : :
430 : 0 : *action_id = data[0];
431 : 0 : *action_data = (uint8_t *)&data[1];
432 : 0 : *entry_id = table_entry_id_get(t, b, i);
433 : 0 : *hit = 1;
434 : 0 : return 1;
435 : : }
436 : : }
437 : :
438 : : /* Miss. */
439 : 0 : m->hit = 0;
440 : 0 : m->state = 0;
441 : :
442 : 0 : *hit = 0;
443 : 0 : return 1;
444 : : }
445 : :
446 : 0 : default:
447 : : /* This state should never be reached. Miss. */
448 : 0 : m->hit = 0;
449 : 0 : m->state = 0;
450 : :
451 : 0 : *hit = 0;
452 : 0 : return 1;
453 : : }
454 : : }
455 : :
456 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_rearm, 22.07)
457 : : void
458 : 0 : rte_swx_table_learner_rearm(void *table,
459 : : void *mailbox,
460 : : uint64_t input_time)
461 : : {
462 : : struct table *t = table;
463 : : struct mailbox *m = mailbox;
464 : : struct table_bucket *b;
465 : : size_t bucket_key_pos;
466 : : uint64_t key_timeout;
467 : : uint32_t key_timeout_id;
468 : :
469 [ # # ]: 0 : if (!m->hit)
470 : : return;
471 : :
472 : 0 : b = m->bucket;
473 : 0 : bucket_key_pos = m->bucket_key_pos;
474 : :
475 : 0 : key_timeout_id = b->key_timeout_id[bucket_key_pos];
476 : 0 : key_timeout = t->params.key_timeout[key_timeout_id];
477 : 0 : b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
478 : : }
479 : :
480 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_rearm_new, 22.07)
481 : : void
482 : 0 : rte_swx_table_learner_rearm_new(void *table,
483 : : void *mailbox,
484 : : uint64_t input_time,
485 : : uint32_t key_timeout_id)
486 : : {
487 : : struct table *t = table;
488 : : struct mailbox *m = mailbox;
489 : : struct table_bucket *b;
490 : : size_t bucket_key_pos;
491 : : uint64_t key_timeout;
492 : :
493 [ # # ]: 0 : if (!m->hit)
494 : : return;
495 : :
496 : 0 : b = m->bucket;
497 : 0 : bucket_key_pos = m->bucket_key_pos;
498 : :
499 : 0 : key_timeout_id &= t->params.n_key_timeouts - 1;
500 : 0 : key_timeout = t->params.key_timeout[key_timeout_id];
501 : 0 : b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
502 : 0 : b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
503 : : }
504 : :
505 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_add, 21.11)
506 : : uint32_t
507 : 0 : rte_swx_table_learner_add(void *table,
508 : : void *mailbox,
509 : : uint64_t input_time,
510 : : uint64_t action_id,
511 : : uint8_t *action_data,
512 : : uint32_t key_timeout_id)
513 : : {
514 : : struct table *t = table;
515 : : struct mailbox *m = mailbox;
516 : 0 : struct table_bucket *b = m->bucket;
517 : : uint64_t key_timeout;
518 : : uint32_t i;
519 : :
520 : : /* Adjust the key timeout ID to fit the valid range. */
521 : 0 : key_timeout_id &= t->params.n_key_timeouts - 1;
522 : 0 : key_timeout = t->params.key_timeout[key_timeout_id];
523 : :
524 : : /* Lookup hit: The following bucket fields need to be updated:
525 : : * - key (key, sig): NO (already correctly set).
526 : : * - key timeout (key_timeout_id, time): YES.
527 : : * - key data (data): YES.
528 : : */
529 [ # # ]: 0 : if (m->hit) {
530 : 0 : size_t bucket_key_pos = m->bucket_key_pos;
531 : : uint64_t *data = table_bucket_data_get(t, b, bucket_key_pos);
532 : :
533 : : /* Install the key timeout. */
534 : 0 : b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
535 : 0 : b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
536 : :
537 : : /* Install the key data. */
538 : 0 : data[0] = action_id;
539 [ # # # # ]: 0 : if (t->params.action_data_size && action_data)
540 : 0 : memcpy(&data[1], action_data, t->params.action_data_size);
541 : :
542 : 0 : return 0;
543 : : }
544 : :
545 : : /* Lookup miss: Search for a free position in the current bucket and install the key. */
546 [ # # ]: 0 : for (i = 0; i < TABLE_KEYS_PER_BUCKET; i++) {
547 : 0 : uint64_t time = b->time[i];
548 : :
549 : 0 : time <<= 32;
550 : :
551 : : /* Free position: Either there was never a key installed here, so the key time is
552 : : * set to zero (the init value), which is always less than the current time, or this
553 : : * position was used before, but the key expired (the key time is in the past).
554 : : */
555 [ # # ]: 0 : if (time < input_time) {
556 : 0 : uint8_t *key = table_bucket_key_get(t, b, i);
557 : : uint64_t *data = table_bucket_data_get(t, b, i);
558 : :
559 : : /* Install the key and the key timeout. */
560 : 0 : b->time[i] = (input_time + key_timeout) >> 32;
561 : 0 : b->sig[i] = m->input_sig;
562 : 0 : b->key_timeout_id[i] = (uint8_t)key_timeout_id;
563 [ # # ]: 0 : table_keycpy(key, m->input_key, t->params.key_size);
564 : :
565 : : /* Install the key data. */
566 : 0 : data[0] = action_id;
567 [ # # # # ]: 0 : if (t->params.action_data_size && action_data)
568 : 0 : memcpy(&data[1], action_data, t->params.action_data_size);
569 : :
570 : : /* Mailbox. */
571 : 0 : m->hit = 1;
572 : 0 : m->bucket_key_pos = i;
573 : :
574 : 0 : return 0;
575 : : }
576 : : }
577 : :
578 : : /* Bucket full. */
579 : : return 1;
580 : : }
581 : :
582 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_swx_table_learner_delete, 21.11)
583 : : void
584 : 0 : rte_swx_table_learner_delete(void *table __rte_unused,
585 : : void *mailbox)
586 : : {
587 : : struct mailbox *m = mailbox;
588 : :
589 [ # # ]: 0 : if (m->hit) {
590 : 0 : struct table_bucket *b = m->bucket;
591 : :
592 : : /* Expire the key. */
593 : 0 : b->time[m->bucket_key_pos] = 0;
594 : :
595 : : /* Mailbox. */
596 : 0 : m->hit = 0;
597 : : }
598 : 0 : }
|