Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright 2020 Mellanox Technologies, Ltd
3 : : */
4 : :
5 : : #include <stdint.h>
6 : : #include <rte_malloc.h>
7 : : #include <mlx5_malloc.h>
8 : : #include <rte_ring.h>
9 : : #include <mlx5_devx_cmds.h>
10 : : #include <rte_cycles.h>
11 : : #include <rte_eal_paging.h>
12 : : #include <rte_thread.h>
13 : :
14 : : #if defined(HAVE_IBV_FLOW_DV_SUPPORT) || !defined(HAVE_INFINIBAND_VERBS_H)
15 : :
16 : : #include "mlx5_utils.h"
17 : : #include "mlx5_hws_cnt.h"
18 : :
19 : : #define HWS_CNT_CACHE_SZ_DEFAULT 511
20 : : #define HWS_CNT_CACHE_PRELOAD_DEFAULT 254
21 : : #define HWS_CNT_CACHE_FETCH_DEFAULT 254
22 : : #define HWS_CNT_CACHE_THRESHOLD_DEFAULT 254
23 : : #define HWS_CNT_ALLOC_FACTOR_DEFAULT 20
24 : :
25 : : static int
26 [ # # ]: 0 : __hws_cnt_id_load(struct mlx5_hws_cnt_pool *cpool)
27 : : {
28 : : uint32_t cnt_num = mlx5_hws_cnt_pool_get_size(cpool);
29 : : uint32_t iidx;
30 : : cnt_id_t *cnt_arr = NULL;
31 : :
32 : 0 : cnt_arr = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO,
33 : : cnt_num * sizeof(cnt_id_t), 0, SOCKET_ID_ANY);
34 [ # # ]: 0 : if (cnt_arr == NULL)
35 : : return -ENOMEM;
36 : : /*
37 : : * Counter ID order is important for tracking the max number of in used
38 : : * counter for querying, which means counter internal index order must
39 : : * be from zero to the number user configured, i.e: 0 - 8000000.
40 : : * Need to load counter ID in this order into the cache firstly,
41 : : * and then the global free list.
42 : : * In the end, user fetch the counter from minimal to the maximum.
43 : : */
44 [ # # ]: 0 : for (iidx = 0; iidx < cnt_num; iidx++) {
45 : : cnt_id_t cnt_id = mlx5_hws_cnt_id_gen(cpool, iidx);
46 : 0 : cnt_arr[iidx] = cnt_id;
47 : : }
48 [ # # # # : 0 : rte_ring_enqueue_bulk_elem(cpool->free_list, cnt_arr,
# ]
49 : : sizeof(cnt_id_t), cnt_num, NULL);
50 : 0 : mlx5_free(cnt_arr);
51 : 0 : return 0;
52 : : }
53 : :
54 : : static void
55 : 0 : __mlx5_hws_cnt_svc(struct mlx5_dev_ctx_shared *sh,
56 : : struct mlx5_hws_cnt_pool *cpool)
57 : : {
58 : 0 : struct rte_ring *reset_list = cpool->wait_reset_list;
59 : 0 : struct rte_ring *reuse_list = cpool->reuse_list;
60 : : uint32_t reset_cnt_num;
61 : : struct rte_ring_zc_data zcdr = {0};
62 : : struct rte_ring_zc_data zcdu = {0};
63 : : uint32_t ret __rte_unused;
64 : :
65 : : reset_cnt_num = rte_ring_count(reset_list);
66 : 0 : mlx5_aso_cnt_query(sh, cpool);
67 [ # # # ]: 0 : rte_atomic_fetch_add_explicit(&cpool->query_gen, 1, rte_memory_order_release);
68 : : zcdr.n1 = 0;
69 : : zcdu.n1 = 0;
70 : : ret = rte_ring_enqueue_zc_burst_elem_start(reuse_list,
71 : : sizeof(cnt_id_t),
72 : : reset_cnt_num, &zcdu,
73 : : NULL);
74 : : MLX5_ASSERT(ret == reset_cnt_num);
75 : : ret = rte_ring_dequeue_zc_burst_elem_start(reset_list,
76 : : sizeof(cnt_id_t),
77 : : reset_cnt_num, &zcdr,
78 : : NULL);
79 : : MLX5_ASSERT(ret == reset_cnt_num);
80 : : __hws_cnt_r2rcpy(&zcdu, &zcdr, reset_cnt_num);
81 : : rte_ring_dequeue_zc_elem_finish(reset_list, reset_cnt_num);
82 : : rte_ring_enqueue_zc_elem_finish(reuse_list, reset_cnt_num);
83 : :
84 [ # # ]: 0 : if (rte_log_can_log(mlx5_logtype, RTE_LOG_DEBUG)) {
85 : : reset_cnt_num = rte_ring_count(reset_list);
86 : 0 : DRV_LOG(DEBUG, "ibdev %s cpool %p wait_reset_cnt=%" PRIu32,
87 : : sh->ibdev_name, (void *)cpool, reset_cnt_num);
88 : : }
89 : 0 : }
90 : :
91 : : /**
92 : : * Release AGE parameter.
93 : : *
94 : : * @param priv
95 : : * Pointer to the port private data structure.
96 : : * @param own_cnt_index
97 : : * Counter ID to created only for this AGE to release.
98 : : * Zero means there is no such counter.
99 : : * @param age_ipool
100 : : * Pointer to AGE parameter indexed pool.
101 : : * @param idx
102 : : * Index of AGE parameter in the indexed pool.
103 : : */
104 : : static void
105 : 0 : mlx5_hws_age_param_free(struct mlx5_priv *priv, cnt_id_t own_cnt_index,
106 : : struct mlx5_indexed_pool *age_ipool, uint32_t idx)
107 : : {
108 [ # # ]: 0 : if (own_cnt_index) {
109 [ # # ]: 0 : struct mlx5_hws_cnt_pool *cpool = priv->hws_cpool;
110 : :
111 : : MLX5_ASSERT(mlx5_hws_cnt_is_shared(cpool, own_cnt_index));
112 : : mlx5_hws_cnt_shared_put(cpool, &own_cnt_index);
113 : : }
114 : 0 : mlx5_ipool_free(age_ipool, idx);
115 : 0 : }
116 : :
117 : : /**
118 : : * Check and callback event for new aged flow in the HWS counter pool.
119 : : *
120 : : * @param[in] priv
121 : : * Pointer to port private object.
122 : : * @param[in] cpool
123 : : * Pointer to current counter pool.
124 : : */
125 : : static void
126 : 0 : mlx5_hws_aging_check(struct mlx5_priv *priv, struct mlx5_hws_cnt_pool *cpool)
127 : : {
128 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
129 : 0 : struct flow_counter_stats *stats = cpool->raw_mng->raw;
130 : : struct mlx5_hws_age_param *param;
131 : : struct rte_ring *r;
132 : 0 : const uint64_t curr_time = MLX5_CURR_TIME_SEC;
133 [ # # ]: 0 : const uint32_t time_delta = curr_time - cpool->time_of_last_age_check;
134 : : uint32_t nb_alloc_cnts = mlx5_hws_cnt_pool_get_size(cpool);
135 : : uint16_t expected1 = HWS_AGE_CANDIDATE;
136 : : uint16_t expected2 = HWS_AGE_CANDIDATE_INSIDE_RING;
137 : : uint32_t i, age_idx, in_use;
138 : :
139 : 0 : cpool->time_of_last_age_check = curr_time;
140 [ # # ]: 0 : for (i = 0; i < nb_alloc_cnts; ++i) {
141 : : uint64_t hits;
142 : :
143 [ # # ]: 0 : mlx5_hws_cnt_get_all(&cpool->pool[i], &in_use, NULL, &age_idx);
144 [ # # # # ]: 0 : if (!in_use || age_idx == 0)
145 : 0 : continue;
146 : 0 : param = mlx5_ipool_get(age_info->ages_ipool, age_idx);
147 [ # # ]: 0 : if (unlikely(param == NULL)) {
148 : : /*
149 : : * When AGE which used indirect counter it is user
150 : : * responsibility not using this indirect counter
151 : : * without this AGE.
152 : : * If this counter is used after the AGE was freed, the
153 : : * AGE index is invalid and using it here will cause a
154 : : * segmentation fault.
155 : : */
156 : 0 : DRV_LOG(WARNING,
157 : : "Counter %u is lost his AGE, it is unused.", i);
158 : 0 : continue;
159 : : }
160 [ # # ]: 0 : if (param->timeout == 0)
161 : 0 : continue;
162 [ # # # # ]: 0 : switch (rte_atomic_load_explicit(¶m->state, rte_memory_order_relaxed)) {
163 : 0 : case HWS_AGE_AGED_OUT_NOT_REPORTED:
164 : : case HWS_AGE_AGED_OUT_REPORTED:
165 : : /* Already aged-out, no action is needed. */
166 : 0 : continue;
167 : : case HWS_AGE_CANDIDATE:
168 : : case HWS_AGE_CANDIDATE_INSIDE_RING:
169 : : /* This AGE candidate to be aged-out, go to checking. */
170 : : break;
171 : 0 : case HWS_AGE_FREE:
172 : : /*
173 : : * Since this check is async, we may reach a race condition
174 : : * where the age and counter are used in the same rule,
175 : : * using the same counter index,
176 : : * age was freed first, and counter was not freed yet.
177 : : * Aging check can be safely ignored in that case.
178 : : */
179 : 0 : continue;
180 : 0 : default:
181 : : MLX5_ASSERT(0);
182 : 0 : continue;
183 : : }
184 [ # # ]: 0 : hits = rte_be_to_cpu_64(stats[i].hits);
185 [ # # ]: 0 : if (param->nb_cnts == 1) {
186 [ # # ]: 0 : if (hits != param->accumulator_last_hits) {
187 : 0 : rte_atomic_store_explicit(¶m->sec_since_last_hit, 0,
188 : : rte_memory_order_relaxed);
189 : 0 : param->accumulator_last_hits = hits;
190 : 0 : continue;
191 : : }
192 : : } else {
193 : 0 : param->accumulator_hits += hits;
194 : 0 : param->accumulator_cnt++;
195 [ # # ]: 0 : if (param->accumulator_cnt < param->nb_cnts)
196 : 0 : continue;
197 : 0 : param->accumulator_cnt = 0;
198 [ # # ]: 0 : if (param->accumulator_last_hits !=
199 : : param->accumulator_hits) {
200 : 0 : rte_atomic_store_explicit(¶m->sec_since_last_hit,
201 : : 0, rte_memory_order_relaxed);
202 : 0 : param->accumulator_last_hits =
203 : 0 : param->accumulator_hits;
204 : 0 : param->accumulator_hits = 0;
205 : 0 : continue;
206 : : }
207 : 0 : param->accumulator_hits = 0;
208 : : }
209 : 0 : if (rte_atomic_fetch_add_explicit(¶m->sec_since_last_hit, time_delta,
210 : 0 : rte_memory_order_relaxed) + time_delta <=
211 [ # # ]: 0 : rte_atomic_load_explicit(¶m->timeout, rte_memory_order_relaxed))
212 : 0 : continue;
213 : : /* Prepare the relevant ring for this AGE parameter */
214 [ # # ]: 0 : if (priv->hws_strict_queue)
215 : 0 : r = age_info->hw_q_age->aged_lists[param->queue_id];
216 : : else
217 : 0 : r = age_info->hw_age.aged_list;
218 : : /* Changing the state atomically and insert it into the ring. */
219 [ # # ]: 0 : if (rte_atomic_compare_exchange_strong_explicit(¶m->state, &expected1,
220 : : HWS_AGE_AGED_OUT_NOT_REPORTED,
221 : : rte_memory_order_relaxed,
222 : : rte_memory_order_relaxed)) {
223 : : int ret = rte_ring_enqueue_burst_elem(r, &age_idx,
224 : : sizeof(uint32_t),
225 : : 1, NULL);
226 : :
227 : : /*
228 : : * The ring doesn't have enough room for this entry,
229 : : * it replace back the state for the next second.
230 : : *
231 : : * FIXME: if until next sec it get traffic, we are going
232 : : * to lose this "aged out", will be fixed later
233 : : * when optimise it to fill ring in bulks.
234 : : */
235 : : expected2 = HWS_AGE_AGED_OUT_NOT_REPORTED;
236 [ # # # # ]: 0 : if (ret == 0 &&
237 : 0 : !rte_atomic_compare_exchange_strong_explicit(¶m->state,
238 : : &expected2, expected1,
239 : : rte_memory_order_relaxed,
240 [ # # ]: 0 : rte_memory_order_relaxed) &&
241 : : expected2 == HWS_AGE_FREE)
242 : 0 : mlx5_hws_age_param_free(priv,
243 : : param->own_cnt_index,
244 : : age_info->ages_ipool,
245 : : age_idx);
246 : : /* The event is irrelevant in strict queue mode. */
247 [ # # ]: 0 : if (!priv->hws_strict_queue)
248 : 0 : MLX5_AGE_SET(age_info, MLX5_AGE_EVENT_NEW);
249 : : } else {
250 : 0 : rte_atomic_compare_exchange_strong_explicit(¶m->state, &expected2,
251 : : HWS_AGE_AGED_OUT_NOT_REPORTED,
252 : : rte_memory_order_relaxed,
253 : : rte_memory_order_relaxed);
254 : : }
255 : : }
256 : : /* The event is irrelevant in strict queue mode. */
257 [ # # ]: 0 : if (!priv->hws_strict_queue)
258 : 0 : mlx5_age_event_prepare(priv->sh);
259 : 0 : }
260 : :
261 : : static void
262 : 0 : mlx5_hws_cnt_raw_data_free(struct mlx5_dev_ctx_shared *sh,
263 : : struct mlx5_hws_cnt_raw_data_mng *mng)
264 : : {
265 [ # # ]: 0 : if (mng == NULL)
266 : : return;
267 : 0 : sh->cdev->mr_scache.dereg_mr_cb(&mng->mr);
268 : 0 : mlx5_free(mng->raw);
269 : 0 : mlx5_free(mng);
270 : : }
271 : :
272 : : __rte_unused
273 : : static struct mlx5_hws_cnt_raw_data_mng *
274 : 0 : mlx5_hws_cnt_raw_data_alloc(struct mlx5_dev_ctx_shared *sh, uint32_t n,
275 : : struct rte_flow_error *error)
276 : : {
277 : : struct mlx5_hws_cnt_raw_data_mng *mng = NULL;
278 : : int ret;
279 : 0 : size_t sz = n * sizeof(struct flow_counter_stats);
280 : 0 : size_t pgsz = rte_mem_page_size();
281 : :
282 : : MLX5_ASSERT(pgsz > 0);
283 : 0 : mng = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO, sizeof(*mng), 0,
284 : : SOCKET_ID_ANY);
285 [ # # ]: 0 : if (mng == NULL) {
286 : 0 : rte_flow_error_set(error, ENOMEM,
287 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
288 : : NULL, "failed to allocate counters memory manager");
289 : 0 : goto error;
290 : : }
291 : 0 : mng->raw = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO, sz, pgsz,
292 : : SOCKET_ID_ANY);
293 [ # # ]: 0 : if (mng->raw == NULL) {
294 : 0 : rte_flow_error_set(error, ENOMEM,
295 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
296 : : NULL, "failed to allocate raw counters memory");
297 : 0 : goto error;
298 : : }
299 : 0 : ret = sh->cdev->mr_scache.reg_mr_cb(sh->cdev->pd, mng->raw, sz,
300 : : &mng->mr);
301 [ # # ]: 0 : if (ret) {
302 : 0 : rte_flow_error_set(error, errno,
303 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
304 : : NULL, "failed to register counters memory region");
305 : 0 : goto error;
306 : : }
307 : : return mng;
308 : 0 : error:
309 : 0 : mlx5_hws_cnt_raw_data_free(sh, mng);
310 : 0 : return NULL;
311 : : }
312 : :
313 : : static uint32_t
314 : 0 : mlx5_hws_cnt_svc(void *opaque)
315 : : {
316 : : struct mlx5_dev_ctx_shared *sh =
317 : : (struct mlx5_dev_ctx_shared *)opaque;
318 : 0 : uint64_t interval =
319 : 0 : (uint64_t)sh->cnt_svc->query_interval * (US_PER_S / MS_PER_S);
320 : : struct mlx5_hws_cnt_pool *hws_cpool;
321 : : uint64_t start_cycle, query_cycle = 0;
322 : : uint64_t query_us;
323 : : uint64_t sleep_us;
324 : :
325 [ # # ]: 0 : while (sh->cnt_svc->svc_running != 0) {
326 [ # # ]: 0 : if (rte_spinlock_trylock(&sh->cpool_lock) == 0)
327 : 0 : continue;
328 : : start_cycle = rte_rdtsc();
329 : : /* 200ms for 16M counters. */
330 [ # # ]: 0 : LIST_FOREACH(hws_cpool, &sh->hws_cpool_list, next) {
331 : 0 : struct mlx5_priv *opriv = hws_cpool->priv;
332 : :
333 : 0 : __mlx5_hws_cnt_svc(sh, hws_cpool);
334 [ # # ]: 0 : if (opriv->hws_age_req)
335 : 0 : mlx5_hws_aging_check(opriv, hws_cpool);
336 : : }
337 : 0 : query_cycle = rte_rdtsc() - start_cycle;
338 : : rte_spinlock_unlock(&sh->cpool_lock);
339 : 0 : query_us = query_cycle / (rte_get_timer_hz() / US_PER_S);
340 : 0 : sleep_us = interval - query_us;
341 [ # # ]: 0 : DRV_LOG(DEBUG, "ibdev %s counter service thread: "
342 : : "interval_us=%" PRIu64 " query_us=%" PRIu64 " "
343 : : "sleep_us=%" PRIu64,
344 : : sh->ibdev_name, interval, query_us,
345 : : interval > query_us ? sleep_us : 0);
346 [ # # ]: 0 : if (interval > query_us)
347 : 0 : rte_delay_us_sleep(sleep_us);
348 : : }
349 : 0 : return 0;
350 : : }
351 : :
352 : : static void
353 : 0 : mlx5_hws_cnt_pool_deinit(struct mlx5_hws_cnt_pool * const cntp)
354 : : {
355 : : uint32_t qidx = 0;
356 [ # # ]: 0 : if (cntp == NULL)
357 : : return;
358 : 0 : rte_ring_free(cntp->free_list);
359 : 0 : rte_ring_free(cntp->wait_reset_list);
360 : 0 : rte_ring_free(cntp->reuse_list);
361 [ # # ]: 0 : if (cntp->cache) {
362 [ # # ]: 0 : for (qidx = 0; qidx < cntp->cache->q_num; qidx++)
363 : 0 : rte_ring_free(cntp->cache->qcache[qidx]);
364 : : }
365 : 0 : mlx5_free(cntp->cache);
366 : 0 : mlx5_free(cntp->raw_mng);
367 : 0 : mlx5_free(cntp->pool);
368 : 0 : mlx5_free(cntp);
369 : : }
370 : :
371 : : static bool
372 : : mlx5_hws_cnt_should_enable_cache(const struct mlx5_hws_cnt_pool_cfg *pcfg,
373 : : const struct mlx5_hws_cache_param *ccfg)
374 : : {
375 : : /*
376 : : * Enable cache if and only if there are enough counters requested
377 : : * to populate all of the caches.
378 : : */
379 : 0 : return pcfg->request_num >= ccfg->q_num * ccfg->size;
380 : : }
381 : :
382 : : static struct mlx5_hws_cnt_pool_caches *
383 : 0 : mlx5_hws_cnt_cache_init(const struct mlx5_hws_cnt_pool_cfg *pcfg,
384 : : const struct mlx5_hws_cache_param *ccfg)
385 : : {
386 : : struct mlx5_hws_cnt_pool_caches *cache;
387 : : char mz_name[RTE_MEMZONE_NAMESIZE];
388 : : uint32_t qidx;
389 : :
390 : : /* If counter pool is big enough, setup the counter pool cache. */
391 : 0 : cache = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO,
392 : 0 : sizeof(*cache) +
393 : : sizeof(((struct mlx5_hws_cnt_pool_caches *)0)->qcache[0])
394 : 0 : * ccfg->q_num, 0, SOCKET_ID_ANY);
395 [ # # ]: 0 : if (cache == NULL)
396 : : return NULL;
397 : : /* Store the necessary cache parameters. */
398 : 0 : cache->fetch_sz = ccfg->fetch_sz;
399 : 0 : cache->preload_sz = ccfg->preload_sz;
400 : 0 : cache->threshold = ccfg->threshold;
401 : 0 : cache->q_num = ccfg->q_num;
402 [ # # ]: 0 : for (qidx = 0; qidx < ccfg->q_num; qidx++) {
403 : 0 : snprintf(mz_name, sizeof(mz_name), "%s_qc/%x", pcfg->name, qidx);
404 : 0 : cache->qcache[qidx] = rte_ring_create(mz_name, ccfg->size,
405 : : SOCKET_ID_ANY,
406 : : RING_F_SP_ENQ | RING_F_SC_DEQ |
407 : : RING_F_EXACT_SZ);
408 [ # # ]: 0 : if (cache->qcache[qidx] == NULL)
409 : 0 : goto error;
410 : : }
411 : : return cache;
412 : :
413 : : error:
414 [ # # ]: 0 : while (qidx--)
415 : 0 : rte_ring_free(cache->qcache[qidx]);
416 : 0 : mlx5_free(cache);
417 : 0 : return NULL;
418 : : }
419 : :
420 : : static struct mlx5_hws_cnt_pool *
421 : 0 : mlx5_hws_cnt_pool_init(struct mlx5_dev_ctx_shared *sh,
422 : : const struct mlx5_hws_cnt_pool_cfg *pcfg,
423 : : const struct mlx5_hws_cache_param *ccfg,
424 : : struct rte_flow_error *error)
425 : : {
426 : : char mz_name[RTE_MEMZONE_NAMESIZE];
427 : : struct mlx5_hws_cnt_pool *cntp;
428 : : uint64_t cnt_num = 0;
429 : :
430 : : MLX5_ASSERT(pcfg);
431 : : MLX5_ASSERT(ccfg);
432 : 0 : cntp = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO, sizeof(*cntp), 0,
433 : : SOCKET_ID_ANY);
434 [ # # ]: 0 : if (cntp == NULL) {
435 : 0 : rte_flow_error_set(error, ENOMEM,
436 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
437 : : "failed to allocate counter pool context");
438 : 0 : return NULL;
439 : : }
440 : :
441 : 0 : cntp->cfg = *pcfg;
442 : 0 : DRV_LOG(DEBUG, "ibdev %s counter and age action %s supported on group 0",
443 : : sh->ibdev_name,
444 : : mlx5dr_action_counter_root_is_supported() ? "is" : "is not");
445 [ # # ]: 0 : if (cntp->cfg.host_cpool)
446 : : return cntp;
447 [ # # ]: 0 : if (pcfg->request_num > sh->hws_max_nb_counters) {
448 : 0 : DRV_LOG(ERR, "Counter number %u "
449 : : "is greater than the maximum supported (%u).",
450 : : pcfg->request_num, sh->hws_max_nb_counters);
451 : 0 : rte_flow_error_set(error, EINVAL,
452 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
453 : : "requested counters number exceeds supported capacity");
454 : 0 : goto error;
455 : : }
456 : 0 : cnt_num = pcfg->request_num * (100 + pcfg->alloc_factor) / 100;
457 : : if (cnt_num > UINT32_MAX) {
458 : : DRV_LOG(ERR, "counter number %"PRIu64" is out of 32bit range",
459 : : cnt_num);
460 : : rte_flow_error_set(error, EINVAL,
461 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
462 : : "counters number must fit in 32 bits");
463 : : goto error;
464 : : }
465 : : /*
466 : : * When counter request number is supported, but the factor takes it
467 : : * out of size, the factor is reduced.
468 : : */
469 : 0 : cnt_num = RTE_MIN((uint32_t)cnt_num, sh->hws_max_nb_counters);
470 : 0 : cntp->pool = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO,
471 : : sizeof(struct mlx5_hws_cnt) * cnt_num,
472 : : 0, SOCKET_ID_ANY);
473 [ # # ]: 0 : if (cntp->pool == NULL) {
474 : 0 : rte_flow_error_set(error, ENOMEM,
475 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
476 : : "failed to allocate counter pool context");
477 : 0 : goto error;
478 : : }
479 : 0 : snprintf(mz_name, sizeof(mz_name), "%s_F_RING", pcfg->name);
480 : 0 : cntp->free_list = rte_ring_create_elem(mz_name, sizeof(cnt_id_t),
481 : : (uint32_t)cnt_num, SOCKET_ID_ANY,
482 : : RING_F_MP_HTS_ENQ | RING_F_MC_HTS_DEQ |
483 : : RING_F_EXACT_SZ);
484 [ # # ]: 0 : if (cntp->free_list == NULL) {
485 : 0 : rte_flow_error_set(error, ENOMEM,
486 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
487 : : "failed to allocate free counters ring");
488 : 0 : goto error;
489 : : }
490 : 0 : snprintf(mz_name, sizeof(mz_name), "%s_R_RING", pcfg->name);
491 : 0 : cntp->wait_reset_list = rte_ring_create_elem(mz_name, sizeof(cnt_id_t),
492 : : (uint32_t)cnt_num, SOCKET_ID_ANY,
493 : : RING_F_MP_HTS_ENQ | RING_F_SC_DEQ | RING_F_EXACT_SZ);
494 [ # # ]: 0 : if (cntp->wait_reset_list == NULL) {
495 : 0 : rte_flow_error_set(error, ENOMEM,
496 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
497 : : "failed to allocate counters wait reset ring");
498 : 0 : goto error;
499 : : }
500 : 0 : snprintf(mz_name, sizeof(mz_name), "%s_U_RING", pcfg->name);
501 : 0 : cntp->reuse_list = rte_ring_create_elem(mz_name, sizeof(cnt_id_t),
502 : : (uint32_t)cnt_num, SOCKET_ID_ANY,
503 : : RING_F_MP_HTS_ENQ | RING_F_MC_HTS_DEQ | RING_F_EXACT_SZ);
504 [ # # ]: 0 : if (cntp->reuse_list == NULL) {
505 : 0 : rte_flow_error_set(error, ENOMEM,
506 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
507 : : "failed to allocate counters reuse ring");
508 : 0 : goto error;
509 : : }
510 : : /* Allocate counter cache only if needed. */
511 [ # # ]: 0 : if (mlx5_hws_cnt_should_enable_cache(pcfg, ccfg)) {
512 : 0 : cntp->cache = mlx5_hws_cnt_cache_init(pcfg, ccfg);
513 [ # # ]: 0 : if (cntp->cache == NULL) {
514 : 0 : rte_flow_error_set(error, ENOMEM,
515 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
516 : : "failed to allocate counters cache");
517 : 0 : goto error;
518 : : }
519 : : }
520 : : /* Initialize the time for aging-out calculation. */
521 : 0 : cntp->time_of_last_age_check = MLX5_CURR_TIME_SEC;
522 : 0 : return cntp;
523 : 0 : error:
524 : 0 : mlx5_hws_cnt_pool_deinit(cntp);
525 : 0 : return NULL;
526 : : }
527 : :
528 : : int
529 : 0 : mlx5_hws_cnt_service_thread_create(struct mlx5_dev_ctx_shared *sh)
530 : : {
531 : : char name[RTE_THREAD_INTERNAL_NAME_SIZE];
532 : : rte_thread_attr_t attr;
533 : : int ret;
534 : 0 : uint32_t service_core = sh->cnt_svc->service_core;
535 : :
536 : 0 : ret = rte_thread_attr_init(&attr);
537 [ # # ]: 0 : if (ret != 0)
538 : 0 : goto error;
539 [ # # ]: 0 : CPU_SET(service_core, &attr.cpuset);
540 : 0 : sh->cnt_svc->svc_running = 1;
541 : 0 : ret = rte_thread_create(&sh->cnt_svc->service_thread,
542 : : &attr, mlx5_hws_cnt_svc, sh);
543 [ # # ]: 0 : if (ret != 0)
544 : 0 : goto error;
545 : : snprintf(name, sizeof(name), "mlx5-cn%d", service_core);
546 : 0 : rte_thread_set_prefixed_name(sh->cnt_svc->service_thread, name);
547 : :
548 : 0 : return 0;
549 : 0 : error:
550 : 0 : DRV_LOG(ERR, "Failed to create HW steering's counter service thread.");
551 : 0 : return ret;
552 : : }
553 : :
554 : : void
555 : 0 : mlx5_hws_cnt_service_thread_destroy(struct mlx5_dev_ctx_shared *sh)
556 : : {
557 [ # # ]: 0 : if (sh->cnt_svc->service_thread.opaque_id == 0)
558 : : return;
559 : 0 : sh->cnt_svc->svc_running = 0;
560 : 0 : rte_thread_join(sh->cnt_svc->service_thread, NULL);
561 : 0 : sh->cnt_svc->service_thread.opaque_id = 0;
562 : : }
563 : :
564 : : static int
565 : 0 : mlx5_hws_cnt_pool_dcs_alloc(struct mlx5_dev_ctx_shared *sh,
566 : : struct mlx5_hws_cnt_pool *cpool,
567 : : struct rte_flow_error *error)
568 : : {
569 : 0 : struct mlx5_hca_attr *hca_attr = &sh->cdev->config.hca_attr;
570 [ # # ]: 0 : uint32_t max_log_bulk_sz = sh->hws_max_log_bulk_sz;
571 : : uint32_t log_bulk_sz;
572 : : uint32_t idx, alloc_candidate, alloced = 0;
573 : : unsigned int cnt_num = mlx5_hws_cnt_pool_get_size(cpool);
574 : 0 : struct mlx5_devx_counter_attr attr = {0};
575 : : struct mlx5_devx_obj *dcs;
576 : :
577 : : MLX5_ASSERT(cpool->cfg.host_cpool == NULL);
578 [ # # ]: 0 : if (hca_attr->flow_counter_bulk_log_max_alloc == 0)
579 : 0 : return rte_flow_error_set(error, ENOTSUP,
580 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
581 : : NULL, "FW doesn't support bulk log max alloc");
582 [ # # ]: 0 : cnt_num = RTE_ALIGN_CEIL(cnt_num, 4); /* minimal 4 counter in bulk. */
583 : 0 : log_bulk_sz = RTE_MIN(max_log_bulk_sz, rte_log2_u32(cnt_num));
584 : 0 : attr.pd = sh->cdev->pdn;
585 : 0 : attr.pd_valid = 1;
586 : 0 : attr.bulk_log_max_alloc = 1;
587 : 0 : attr.flow_counter_bulk_log_size = log_bulk_sz;
588 : : idx = 0;
589 : 0 : dcs = mlx5_devx_cmd_flow_counter_alloc_general(sh->cdev->ctx, &attr);
590 [ # # ]: 0 : if (dcs == NULL) {
591 : 0 : rte_flow_error_set(error, rte_errno,
592 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
593 : : NULL, "FW failed to allocate counters");
594 : 0 : goto error;
595 : : }
596 : 0 : cpool->dcs_mng.dcs[idx].obj = dcs;
597 : 0 : cpool->dcs_mng.dcs[idx].batch_sz = (1 << log_bulk_sz);
598 : 0 : cpool->dcs_mng.batch_total++;
599 : : idx++;
600 : 0 : cpool->dcs_mng.dcs[0].iidx = 0;
601 : : alloced = cpool->dcs_mng.dcs[0].batch_sz;
602 [ # # ]: 0 : if (cnt_num > cpool->dcs_mng.dcs[0].batch_sz) {
603 [ # # ]: 0 : while (idx < MLX5_HWS_CNT_DCS_NUM) {
604 : 0 : attr.flow_counter_bulk_log_size = --max_log_bulk_sz;
605 : 0 : alloc_candidate = RTE_BIT32(max_log_bulk_sz);
606 [ # # ]: 0 : if (alloced + alloc_candidate > sh->hws_max_nb_counters)
607 : 0 : continue;
608 : 0 : dcs = mlx5_devx_cmd_flow_counter_alloc_general
609 : 0 : (sh->cdev->ctx, &attr);
610 [ # # ]: 0 : if (dcs == NULL) {
611 : 0 : rte_flow_error_set(error, rte_errno,
612 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
613 : : NULL, "FW failed to allocate counters");
614 : 0 : goto error;
615 : : }
616 : 0 : cpool->dcs_mng.dcs[idx].obj = dcs;
617 : 0 : cpool->dcs_mng.dcs[idx].batch_sz = alloc_candidate;
618 : 0 : cpool->dcs_mng.dcs[idx].iidx = alloced;
619 : : alloced += cpool->dcs_mng.dcs[idx].batch_sz;
620 : 0 : cpool->dcs_mng.batch_total++;
621 [ # # ]: 0 : if (alloced >= cnt_num)
622 : : break;
623 : 0 : idx++;
624 : : }
625 : : }
626 : : return 0;
627 : 0 : error:
628 : 0 : DRV_LOG(DEBUG,
629 : : "Cannot alloc device counter, allocated[%" PRIu32 "] request[%" PRIu32 "]",
630 : : alloced, cnt_num);
631 [ # # ]: 0 : for (idx = 0; idx < cpool->dcs_mng.batch_total; idx++) {
632 : 0 : mlx5_devx_cmd_destroy(cpool->dcs_mng.dcs[idx].obj);
633 : 0 : cpool->dcs_mng.dcs[idx].obj = NULL;
634 : 0 : cpool->dcs_mng.dcs[idx].batch_sz = 0;
635 : 0 : cpool->dcs_mng.dcs[idx].iidx = 0;
636 : : }
637 : 0 : cpool->dcs_mng.batch_total = 0;
638 : 0 : return -1;
639 : : }
640 : :
641 : : static void
642 : 0 : mlx5_hws_cnt_pool_dcs_free(struct mlx5_dev_ctx_shared *sh,
643 : : struct mlx5_hws_cnt_pool *cpool)
644 : : {
645 : : uint32_t idx;
646 : :
647 [ # # ]: 0 : if (cpool == NULL)
648 : : return;
649 [ # # ]: 0 : for (idx = 0; idx < MLX5_HWS_CNT_DCS_NUM; idx++)
650 : 0 : mlx5_devx_cmd_destroy(cpool->dcs_mng.dcs[idx].obj);
651 [ # # ]: 0 : if (cpool->raw_mng) {
652 : 0 : mlx5_hws_cnt_raw_data_free(sh, cpool->raw_mng);
653 : 0 : cpool->raw_mng = NULL;
654 : : }
655 : : }
656 : :
657 : : static void
658 : 0 : mlx5_hws_cnt_pool_action_destroy(struct mlx5_hws_cnt_pool *cpool)
659 : : {
660 : : uint32_t idx;
661 : :
662 [ # # ]: 0 : for (idx = 0; idx < cpool->dcs_mng.batch_total; idx++) {
663 : : struct mlx5_hws_cnt_dcs *dcs = &cpool->dcs_mng.dcs[idx];
664 : :
665 [ # # ]: 0 : if (dcs->root_action != NULL) {
666 : 0 : mlx5dr_action_destroy(dcs->root_action);
667 : 0 : dcs->root_action = NULL;
668 : : }
669 [ # # ]: 0 : if (dcs->hws_action != NULL) {
670 : 0 : mlx5dr_action_destroy(dcs->hws_action);
671 : 0 : dcs->hws_action = NULL;
672 : : }
673 : : }
674 : 0 : }
675 : :
676 : : static int
677 [ # # ]: 0 : mlx5_hws_cnt_pool_action_create(struct mlx5_priv *priv,
678 : : struct mlx5_hws_cnt_pool *cpool)
679 : : {
680 : : struct mlx5_hws_cnt_pool *hpool = mlx5_hws_cnt_host_pool(cpool);
681 : : uint32_t idx;
682 : : int ret = 0;
683 : : uint32_t root_flags;
684 : : uint32_t hws_flags;
685 : :
686 : : root_flags = MLX5DR_ACTION_FLAG_ROOT_RX | MLX5DR_ACTION_FLAG_ROOT_TX;
687 : : hws_flags = MLX5DR_ACTION_FLAG_HWS_RX | MLX5DR_ACTION_FLAG_HWS_TX;
688 [ # # # # ]: 0 : if (priv->sh->config.dv_esw_en && priv->master) {
689 : : root_flags |= MLX5DR_ACTION_FLAG_ROOT_FDB;
690 : 0 : hws_flags |= (is_unified_fdb(priv) ?
691 : : (MLX5DR_ACTION_FLAG_HWS_FDB_RX |
692 : : MLX5DR_ACTION_FLAG_HWS_FDB_TX |
693 [ # # ]: 0 : MLX5DR_ACTION_FLAG_HWS_FDB_UNIFIED) :
694 : : MLX5DR_ACTION_FLAG_HWS_FDB);
695 : : }
696 [ # # ]: 0 : for (idx = 0; idx < hpool->dcs_mng.batch_total; idx++) {
697 : : struct mlx5_hws_cnt_dcs *hdcs = &hpool->dcs_mng.dcs[idx];
698 : : struct mlx5_hws_cnt_dcs *dcs = &cpool->dcs_mng.dcs[idx];
699 : :
700 : 0 : dcs->hws_action = mlx5dr_action_create_counter(priv->dr_ctx,
701 : 0 : (struct mlx5dr_devx_obj *)hdcs->obj,
702 : : hws_flags);
703 [ # # ]: 0 : if (dcs->hws_action == NULL) {
704 : 0 : mlx5_hws_cnt_pool_action_destroy(cpool);
705 : : ret = -ENOSYS;
706 : 0 : break;
707 : : }
708 : :
709 : 0 : if (!mlx5dr_action_counter_root_is_supported()) {
710 : 0 : dcs->root_action = NULL;
711 : : continue;
712 : : }
713 : :
714 : : dcs->root_action = mlx5dr_action_create_counter(priv->dr_ctx,
715 : : (struct mlx5dr_devx_obj *)hdcs->obj,
716 : : root_flags);
717 : : if (dcs->root_action == NULL) {
718 : : mlx5_hws_cnt_pool_action_destroy(cpool);
719 : : ret = -ENOSYS;
720 : : break;
721 : : }
722 : : }
723 : 0 : return ret;
724 : : }
725 : :
726 : : int
727 : 0 : mlx5_hws_cnt_pool_create(struct rte_eth_dev *dev,
728 : : uint32_t nb_counters, uint16_t nb_queue,
729 : : struct mlx5_hws_cnt_pool *chost,
730 : : struct rte_flow_error *error)
731 : : {
732 : : struct mlx5_hws_cnt_pool *cpool = NULL;
733 : 0 : struct mlx5_priv *priv = dev->data->dev_private;
734 : 0 : struct mlx5_hws_cache_param cparam = {0};
735 : 0 : struct mlx5_hws_cnt_pool_cfg pcfg = {0};
736 : : char *mp_name;
737 : : int ret = 0;
738 : : size_t sz;
739 : :
740 : 0 : mp_name = mlx5_malloc(MLX5_MEM_ZERO, RTE_MEMZONE_NAMESIZE, 0, SOCKET_ID_ANY);
741 [ # # ]: 0 : if (mp_name == NULL) {
742 : 0 : ret = rte_flow_error_set(error, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
743 : : "failed to allocate counter pool name prefix");
744 : 0 : goto error;
745 : : }
746 [ # # ]: 0 : snprintf(mp_name, RTE_MEMZONE_NAMESIZE, "MLX5_HWS_CNT_P_%x", dev->data->port_id);
747 : 0 : pcfg.name = mp_name;
748 : 0 : pcfg.request_num = nb_counters;
749 : 0 : pcfg.alloc_factor = HWS_CNT_ALLOC_FACTOR_DEFAULT;
750 [ # # ]: 0 : if (chost) {
751 : 0 : pcfg.host_cpool = chost;
752 : 0 : cpool = mlx5_hws_cnt_pool_init(priv->sh, &pcfg, &cparam, error);
753 [ # # ]: 0 : if (cpool == NULL) {
754 : 0 : ret = -rte_errno;
755 : 0 : goto error;
756 : : }
757 : 0 : ret = mlx5_hws_cnt_pool_action_create(priv, cpool);
758 [ # # ]: 0 : if (ret != 0) {
759 : 0 : rte_flow_error_set(error, -ret,
760 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
761 : : NULL, "failed to allocate counter actions on guest port");
762 : 0 : goto error;
763 : : }
764 : 0 : goto success;
765 : : }
766 : 0 : cparam.fetch_sz = HWS_CNT_CACHE_FETCH_DEFAULT;
767 : 0 : cparam.preload_sz = HWS_CNT_CACHE_PRELOAD_DEFAULT;
768 : 0 : cparam.q_num = nb_queue;
769 : 0 : cparam.threshold = HWS_CNT_CACHE_THRESHOLD_DEFAULT;
770 : 0 : cparam.size = HWS_CNT_CACHE_SZ_DEFAULT;
771 : 0 : cpool = mlx5_hws_cnt_pool_init(priv->sh, &pcfg, &cparam, error);
772 [ # # ]: 0 : if (cpool == NULL) {
773 : 0 : ret = -rte_errno;
774 : 0 : goto error;
775 : : }
776 : 0 : ret = mlx5_hws_cnt_pool_dcs_alloc(priv->sh, cpool, error);
777 [ # # ]: 0 : if (ret != 0)
778 : 0 : goto error;
779 : 0 : sz = RTE_ALIGN_CEIL(mlx5_hws_cnt_pool_get_size(cpool), 4);
780 : 0 : cpool->raw_mng = mlx5_hws_cnt_raw_data_alloc(priv->sh, sz, error);
781 [ # # ]: 0 : if (cpool->raw_mng == NULL) {
782 : 0 : ret = -rte_errno;
783 : 0 : goto error;
784 : : }
785 : 0 : ret = __hws_cnt_id_load(cpool);
786 [ # # ]: 0 : if (ret != 0)
787 : 0 : goto error;
788 : : /*
789 : : * Bump query gen right after pool create so the
790 : : * pre-loaded counters can be used directly
791 : : * because they already have init value no need
792 : : * to wait for query.
793 : : */
794 : 0 : rte_atomic_store_explicit(&cpool->query_gen, 1, rte_memory_order_relaxed);
795 : 0 : ret = mlx5_hws_cnt_pool_action_create(priv, cpool);
796 [ # # ]: 0 : if (ret != 0) {
797 : 0 : rte_flow_error_set(error, -ret,
798 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
799 : : NULL, "failed to allocate counter actions");
800 : 0 : goto error;
801 : : }
802 : : /* init cnt service if not. */
803 [ # # ]: 0 : if (priv->sh->cnt_svc == NULL) {
804 : 0 : ret = mlx5_hws_cnt_svc_init(priv->sh, error);
805 [ # # ]: 0 : if (ret)
806 : 0 : goto error;
807 : : }
808 : 0 : priv->sh->cnt_svc->refcnt++;
809 : 0 : cpool->priv = priv;
810 : 0 : rte_spinlock_lock(&priv->sh->cpool_lock);
811 [ # # ]: 0 : LIST_INSERT_HEAD(&priv->sh->hws_cpool_list, cpool, next);
812 : 0 : rte_spinlock_unlock(&priv->sh->cpool_lock);
813 : 0 : success:
814 : 0 : priv->hws_cpool = cpool;
815 : 0 : return 0;
816 : 0 : error:
817 : : MLX5_ASSERT(ret);
818 : 0 : mlx5_hws_cnt_pool_destroy(priv->sh, cpool);
819 : 0 : priv->hws_cpool = NULL;
820 : 0 : mlx5_free(mp_name);
821 : 0 : return ret;
822 : : }
823 : :
824 : : void
825 : 0 : mlx5_hws_cnt_pool_destroy(struct mlx5_dev_ctx_shared *sh,
826 : : struct mlx5_hws_cnt_pool *cpool)
827 : : {
828 [ # # ]: 0 : if (cpool == NULL)
829 : : return;
830 : : /*
831 : : * 16M counter consumes 200ms to finish the query.
832 : : * Maybe blocked for at most 200ms here.
833 : : */
834 : 0 : rte_spinlock_lock(&sh->cpool_lock);
835 : : /* Try to remove cpool before it was added to list caused segfault. */
836 [ # # # # ]: 0 : if (!LIST_EMPTY(&sh->hws_cpool_list) && cpool->next.le_prev)
837 [ # # ]: 0 : LIST_REMOVE(cpool, next);
838 : : rte_spinlock_unlock(&sh->cpool_lock);
839 [ # # ]: 0 : if (cpool->cfg.host_cpool == NULL) {
840 [ # # # # ]: 0 : if (sh->cnt_svc && --sh->cnt_svc->refcnt == 0)
841 : 0 : mlx5_hws_cnt_svc_deinit(sh);
842 : : }
843 : 0 : mlx5_hws_cnt_pool_action_destroy(cpool);
844 [ # # ]: 0 : if (cpool->cfg.host_cpool == NULL) {
845 : 0 : mlx5_hws_cnt_pool_dcs_free(sh, cpool);
846 : 0 : mlx5_hws_cnt_raw_data_free(sh, cpool->raw_mng);
847 : : }
848 : 0 : mlx5_free((void *)cpool->cfg.name);
849 : 0 : mlx5_hws_cnt_pool_deinit(cpool);
850 : : }
851 : :
852 : : int
853 : 0 : mlx5_hws_cnt_svc_init(struct mlx5_dev_ctx_shared *sh,
854 : : struct rte_flow_error *error)
855 : : {
856 : : int ret;
857 : :
858 : 0 : sh->cnt_svc = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO,
859 : : sizeof(*sh->cnt_svc), 0, SOCKET_ID_ANY);
860 [ # # ]: 0 : if (sh->cnt_svc == NULL)
861 : 0 : goto err;
862 : 0 : sh->cnt_svc->query_interval = sh->config.cnt_svc.cycle_time;
863 : 0 : sh->cnt_svc->service_core = sh->config.cnt_svc.service_core;
864 : 0 : ret = mlx5_aso_cnt_queue_init(sh);
865 [ # # ]: 0 : if (ret != 0) {
866 : 0 : mlx5_free(sh->cnt_svc);
867 : 0 : sh->cnt_svc = NULL;
868 : 0 : goto err;
869 : : }
870 : 0 : ret = mlx5_hws_cnt_service_thread_create(sh);
871 [ # # ]: 0 : if (ret != 0) {
872 : 0 : mlx5_aso_cnt_queue_uninit(sh);
873 : 0 : mlx5_free(sh->cnt_svc);
874 : 0 : sh->cnt_svc = NULL;
875 : : }
876 : : return 0;
877 : 0 : err:
878 : 0 : return rte_flow_error_set(error, ENOMEM,
879 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
880 : : NULL, "failed to init counters service");
881 : :
882 : : }
883 : :
884 : : void
885 : 0 : mlx5_hws_cnt_svc_deinit(struct mlx5_dev_ctx_shared *sh)
886 : : {
887 [ # # ]: 0 : if (sh->cnt_svc == NULL)
888 : : return;
889 : 0 : mlx5_hws_cnt_service_thread_destroy(sh);
890 : 0 : mlx5_aso_cnt_queue_uninit(sh);
891 : 0 : mlx5_free(sh->cnt_svc);
892 : 0 : sh->cnt_svc = NULL;
893 : : }
894 : :
895 : : /**
896 : : * Destroy AGE action.
897 : : *
898 : : * @param priv
899 : : * Pointer to the port private data structure.
900 : : * @param idx
901 : : * Index of AGE parameter.
902 : : * @param error
903 : : * Pointer to error structure.
904 : : *
905 : : * @return
906 : : * 0 on success, a negative errno value otherwise and rte_errno is set.
907 : : */
908 : : int
909 : 0 : mlx5_hws_age_action_destroy(struct mlx5_priv *priv, uint32_t idx,
910 : : struct rte_flow_error *error)
911 : : {
912 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
913 : 0 : struct mlx5_indexed_pool *ipool = age_info->ages_ipool;
914 : 0 : struct mlx5_hws_age_param *param = mlx5_ipool_get(ipool, idx);
915 : :
916 [ # # ]: 0 : if (param == NULL)
917 : 0 : return rte_flow_error_set(error, EINVAL,
918 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
919 : : "invalid AGE parameter index");
920 [ # # # ]: 0 : switch (rte_atomic_exchange_explicit(¶m->state, HWS_AGE_FREE,
921 : : rte_memory_order_relaxed)) {
922 : 0 : case HWS_AGE_CANDIDATE:
923 : : case HWS_AGE_AGED_OUT_REPORTED:
924 : 0 : mlx5_hws_age_param_free(priv, param->own_cnt_index, ipool, idx);
925 : 0 : break;
926 : : case HWS_AGE_AGED_OUT_NOT_REPORTED:
927 : : case HWS_AGE_CANDIDATE_INSIDE_RING:
928 : : /*
929 : : * In both cases AGE is inside the ring. Change the state here
930 : : * and destroy it later when it is taken out of ring.
931 : : */
932 : : break;
933 : 0 : case HWS_AGE_FREE:
934 : : /*
935 : : * If index is valid and state is FREE, it says this AGE has
936 : : * been freed for the user but not for the PMD since it is
937 : : * inside the ring.
938 : : */
939 : 0 : return rte_flow_error_set(error, EINVAL,
940 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
941 : : "this AGE has already been released");
942 : : default:
943 : : MLX5_ASSERT(0);
944 : : break;
945 : : }
946 : : return 0;
947 : : }
948 : :
949 : : /**
950 : : * Create AGE action parameter.
951 : : *
952 : : * @param[in] priv
953 : : * Pointer to the port private data structure.
954 : : * @param[in] queue_id
955 : : * Which HWS queue to be used.
956 : : * @param[in] shared
957 : : * Whether it indirect AGE action.
958 : : * @param[in] flow_idx
959 : : * Flow index from indexed pool.
960 : : * For indirect AGE action it doesn't affect.
961 : : * @param[in] age
962 : : * Pointer to the aging action configuration.
963 : : * @param[out] error
964 : : * Pointer to error structure.
965 : : *
966 : : * @return
967 : : * Index to AGE action parameter on success, 0 otherwise.
968 : : */
969 : : uint32_t
970 : 0 : mlx5_hws_age_action_create(struct mlx5_priv *priv, uint32_t queue_id,
971 : : bool shared, const struct rte_flow_action_age *age,
972 : : uint32_t flow_idx, struct rte_flow_error *error)
973 : : {
974 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
975 : 0 : struct mlx5_indexed_pool *ipool = age_info->ages_ipool;
976 : : struct mlx5_hws_age_param *param;
977 : : uint32_t age_idx;
978 : :
979 : 0 : param = mlx5_ipool_malloc(ipool, &age_idx);
980 [ # # ]: 0 : if (param == NULL) {
981 : 0 : rte_flow_error_set(error, ENOMEM,
982 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
983 : : "cannot allocate AGE parameter");
984 : 0 : return 0;
985 : : }
986 : : MLX5_ASSERT(rte_atomic_load_explicit(¶m->state,
987 : : rte_memory_order_relaxed) == HWS_AGE_FREE);
988 [ # # ]: 0 : if (shared) {
989 : 0 : param->nb_cnts = 0;
990 : 0 : param->accumulator_hits = 0;
991 : 0 : param->accumulator_cnt = 0;
992 : 0 : flow_idx = age_idx;
993 : : } else {
994 : 0 : param->nb_cnts = 1;
995 : : }
996 [ # # ]: 0 : param->context = age->context ? age->context :
997 : 0 : (void *)(uintptr_t)flow_idx;
998 : 0 : param->timeout = age->timeout;
999 : 0 : param->queue_id = queue_id;
1000 : 0 : param->accumulator_last_hits = 0;
1001 : 0 : param->own_cnt_index = 0;
1002 : 0 : param->sec_since_last_hit = 0;
1003 : 0 : param->state = HWS_AGE_CANDIDATE;
1004 : 0 : return age_idx;
1005 : : }
1006 : :
1007 : : /**
1008 : : * Update indirect AGE action parameter.
1009 : : *
1010 : : * @param[in] priv
1011 : : * Pointer to the port private data structure.
1012 : : * @param[in] idx
1013 : : * Index of AGE parameter.
1014 : : * @param[in] update
1015 : : * Update value.
1016 : : * @param[out] error
1017 : : * Pointer to error structure.
1018 : : *
1019 : : * @return
1020 : : * 0 on success, a negative errno value otherwise and rte_errno is set.
1021 : : */
1022 : : int
1023 : 0 : mlx5_hws_age_action_update(struct mlx5_priv *priv, uint32_t idx,
1024 : : const void *update, struct rte_flow_error *error)
1025 : : {
1026 : : const struct rte_flow_update_age *update_ade = update;
1027 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1028 : 0 : struct mlx5_indexed_pool *ipool = age_info->ages_ipool;
1029 : 0 : struct mlx5_hws_age_param *param = mlx5_ipool_get(ipool, idx);
1030 : : bool sec_since_last_hit_reset = false;
1031 : : bool state_update = false;
1032 : :
1033 [ # # ]: 0 : if (param == NULL)
1034 : 0 : return rte_flow_error_set(error, EINVAL,
1035 : : RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
1036 : : "invalid AGE parameter index");
1037 [ # # ]: 0 : if (update_ade->timeout_valid) {
1038 : 0 : uint32_t old_timeout = rte_atomic_exchange_explicit(¶m->timeout,
1039 : : update_ade->timeout,
1040 : : rte_memory_order_relaxed);
1041 : :
1042 [ # # ]: 0 : if (old_timeout == 0)
1043 : : sec_since_last_hit_reset = true;
1044 [ # # ]: 0 : else if (old_timeout < update_ade->timeout ||
1045 [ # # ]: 0 : update_ade->timeout == 0)
1046 : : /*
1047 : : * When timeout is increased, aged-out flows might be
1048 : : * active again and state should be updated accordingly.
1049 : : * When new timeout is 0, we update the state for not
1050 : : * reporting aged-out stopped.
1051 : : */
1052 : : state_update = true;
1053 : : }
1054 [ # # ]: 0 : if (update_ade->touch) {
1055 : : sec_since_last_hit_reset = true;
1056 : : state_update = true;
1057 : : }
1058 [ # # ]: 0 : if (sec_since_last_hit_reset)
1059 : 0 : rte_atomic_store_explicit(¶m->sec_since_last_hit, 0,
1060 : : rte_memory_order_relaxed);
1061 [ # # ]: 0 : if (state_update) {
1062 : : uint16_t expected = HWS_AGE_AGED_OUT_NOT_REPORTED;
1063 : :
1064 : : /*
1065 : : * Change states of aged-out flows to active:
1066 : : * - AGED_OUT_NOT_REPORTED -> CANDIDATE_INSIDE_RING
1067 : : * - AGED_OUT_REPORTED -> CANDIDATE
1068 : : */
1069 [ # # ]: 0 : if (!rte_atomic_compare_exchange_strong_explicit(¶m->state, &expected,
1070 : : HWS_AGE_CANDIDATE_INSIDE_RING,
1071 : : rte_memory_order_relaxed,
1072 [ # # ]: 0 : rte_memory_order_relaxed) &&
1073 : : expected == HWS_AGE_AGED_OUT_REPORTED)
1074 : 0 : rte_atomic_store_explicit(¶m->state, HWS_AGE_CANDIDATE,
1075 : : rte_memory_order_relaxed);
1076 : : }
1077 : : return 0;
1078 : : }
1079 : :
1080 : : /**
1081 : : * Get the AGE context if the aged-out index is still valid.
1082 : : *
1083 : : * @param priv
1084 : : * Pointer to the port private data structure.
1085 : : * @param idx
1086 : : * Index of AGE parameter.
1087 : : *
1088 : : * @return
1089 : : * AGE context if the index is still aged-out, NULL otherwise.
1090 : : */
1091 : : void *
1092 : 0 : mlx5_hws_age_context_get(struct mlx5_priv *priv, uint32_t idx)
1093 : : {
1094 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1095 : 0 : struct mlx5_indexed_pool *ipool = age_info->ages_ipool;
1096 : 0 : struct mlx5_hws_age_param *param = mlx5_ipool_get(ipool, idx);
1097 : : uint16_t expected = HWS_AGE_AGED_OUT_NOT_REPORTED;
1098 : :
1099 : : MLX5_ASSERT(param != NULL);
1100 [ # # ]: 0 : if (rte_atomic_compare_exchange_strong_explicit(¶m->state, &expected,
1101 : : HWS_AGE_AGED_OUT_REPORTED,
1102 : : rte_memory_order_relaxed, rte_memory_order_relaxed))
1103 : 0 : return param->context;
1104 [ # # # ]: 0 : switch (expected) {
1105 : 0 : case HWS_AGE_FREE:
1106 : : /*
1107 : : * This AGE couldn't have been destroyed since it was inside
1108 : : * the ring. Its state has updated, and now it is actually
1109 : : * destroyed.
1110 : : */
1111 : 0 : mlx5_hws_age_param_free(priv, param->own_cnt_index, ipool, idx);
1112 : 0 : break;
1113 : 0 : case HWS_AGE_CANDIDATE_INSIDE_RING:
1114 : 0 : rte_atomic_store_explicit(¶m->state, HWS_AGE_CANDIDATE,
1115 : : rte_memory_order_relaxed);
1116 : 0 : break;
1117 : : case HWS_AGE_CANDIDATE:
1118 : : /*
1119 : : * Only BG thread pushes to ring and it never pushes this state.
1120 : : * When AGE inside the ring becomes candidate, it has a special
1121 : : * state called HWS_AGE_CANDIDATE_INSIDE_RING.
1122 : : * Fall-through.
1123 : : */
1124 : : case HWS_AGE_AGED_OUT_REPORTED:
1125 : : /*
1126 : : * Only this thread (doing query) may write this state, and it
1127 : : * happens only after the query thread takes it out of the ring.
1128 : : * Fall-through.
1129 : : */
1130 : : case HWS_AGE_AGED_OUT_NOT_REPORTED:
1131 : : /*
1132 : : * In this case the compare return true and function return
1133 : : * the context immediately.
1134 : : * Fall-through.
1135 : : */
1136 : : default:
1137 : : MLX5_ASSERT(0);
1138 : : break;
1139 : : }
1140 : : return NULL;
1141 : : }
1142 : :
1143 : : #ifdef RTE_ARCH_64
1144 : : #define MLX5_HWS_AGED_OUT_RING_SIZE_MAX UINT32_MAX
1145 : : #else
1146 : : #define MLX5_HWS_AGED_OUT_RING_SIZE_MAX RTE_BIT32(8)
1147 : : #endif
1148 : :
1149 : : /**
1150 : : * Get the size of aged out ring list for each queue.
1151 : : *
1152 : : * The size is one percent of nb_counters divided by nb_queues.
1153 : : * The ring size must be power of 2, so it align up to power of 2.
1154 : : * In 32 bit systems, the size is limited by 256.
1155 : : *
1156 : : * This function is called when RTE_FLOW_PORT_FLAG_STRICT_QUEUE is on.
1157 : : *
1158 : : * @param nb_counters
1159 : : * Final number of allocated counter in the pool.
1160 : : * @param nb_queues
1161 : : * Number of HWS queues in this port.
1162 : : *
1163 : : * @return
1164 : : * Size of aged out ring per queue.
1165 : : */
1166 : : static __rte_always_inline uint32_t
1167 : : mlx5_hws_aged_out_q_ring_size_get(uint32_t nb_counters, uint32_t nb_queues)
1168 : : {
1169 : 0 : uint32_t size = rte_align32pow2((nb_counters / 100) / nb_queues);
1170 : : uint32_t max_size = MLX5_HWS_AGED_OUT_RING_SIZE_MAX;
1171 : :
1172 : : return RTE_MIN(size, max_size);
1173 : : }
1174 : :
1175 : : /**
1176 : : * Get the size of the aged out ring list.
1177 : : *
1178 : : * The size is one percent of nb_counters.
1179 : : * The ring size must be power of 2, so it align up to power of 2.
1180 : : * In 32 bit systems, the size is limited by 256.
1181 : : *
1182 : : * This function is called when RTE_FLOW_PORT_FLAG_STRICT_QUEUE is off.
1183 : : *
1184 : : * @param nb_counters
1185 : : * Final number of allocated counter in the pool.
1186 : : *
1187 : : * @return
1188 : : * Size of the aged out ring list.
1189 : : */
1190 : : static __rte_always_inline uint32_t
1191 : : mlx5_hws_aged_out_ring_size_get(uint32_t nb_counters)
1192 : : {
1193 : 0 : uint32_t size = rte_align32pow2(nb_counters / 100);
1194 : : uint32_t max_size = MLX5_HWS_AGED_OUT_RING_SIZE_MAX;
1195 : :
1196 : : return RTE_MIN(size, max_size);
1197 : : }
1198 : :
1199 : : /**
1200 : : * Initialize the shared aging list information per port.
1201 : : *
1202 : : * @param dev
1203 : : * Pointer to the rte_eth_dev structure.
1204 : : * @param nb_queues
1205 : : * Number of HWS queues.
1206 : : * @param strict_queue
1207 : : * Indicator whether is strict_queue mode.
1208 : : * @param ring_size
1209 : : * Size of aged-out ring for creation.
1210 : : *
1211 : : * @return
1212 : : * 0 on success, a negative errno value otherwise and rte_errno is set.
1213 : : */
1214 : : static int
1215 : 0 : mlx5_hws_age_info_init(struct rte_eth_dev *dev, uint16_t nb_queues,
1216 : : bool strict_queue, uint32_t ring_size)
1217 : : {
1218 : 0 : struct mlx5_priv *priv = dev->data->dev_private;
1219 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1220 : : uint32_t flags = RING_F_SP_ENQ | RING_F_SC_DEQ | RING_F_EXACT_SZ;
1221 : : char mz_name[RTE_MEMZONE_NAMESIZE];
1222 : : struct rte_ring *r = NULL;
1223 : : uint32_t qidx;
1224 : :
1225 : 0 : age_info->flags = 0;
1226 [ # # ]: 0 : if (strict_queue) {
1227 : 0 : size_t size = sizeof(*age_info->hw_q_age) +
1228 : : sizeof(struct rte_ring *) * nb_queues;
1229 : :
1230 : 0 : age_info->hw_q_age = mlx5_malloc(MLX5_MEM_ANY | MLX5_MEM_ZERO,
1231 : : size, 0, SOCKET_ID_ANY);
1232 [ # # ]: 0 : if (age_info->hw_q_age == NULL)
1233 : : return -ENOMEM;
1234 [ # # ]: 0 : for (qidx = 0; qidx < nb_queues; ++qidx) {
1235 : 0 : snprintf(mz_name, sizeof(mz_name),
1236 : : "port_%u_queue_%u_aged_out_ring",
1237 : 0 : dev->data->port_id, qidx);
1238 : 0 : r = rte_ring_create(mz_name, ring_size, SOCKET_ID_ANY,
1239 : : flags);
1240 [ # # ]: 0 : if (r == NULL) {
1241 : 0 : DRV_LOG(ERR, "\"%s\" creation failed: %s",
1242 : : mz_name, rte_strerror(rte_errno));
1243 : 0 : goto error;
1244 : : }
1245 : 0 : age_info->hw_q_age->aged_lists[qidx] = r;
1246 : 0 : DRV_LOG(DEBUG,
1247 : : "\"%s\" is successfully created (size=%u).",
1248 : : mz_name, ring_size);
1249 : : }
1250 : 0 : age_info->hw_q_age->nb_rings = nb_queues;
1251 : : } else {
1252 : 0 : snprintf(mz_name, sizeof(mz_name), "port_%u_aged_out_ring",
1253 : 0 : dev->data->port_id);
1254 : 0 : r = rte_ring_create(mz_name, ring_size, SOCKET_ID_ANY, flags);
1255 [ # # ]: 0 : if (r == NULL) {
1256 : 0 : DRV_LOG(ERR, "\"%s\" creation failed: %s", mz_name,
1257 : : rte_strerror(rte_errno));
1258 : 0 : return -rte_errno;
1259 : : }
1260 : 0 : age_info->hw_age.aged_list = r;
1261 : 0 : DRV_LOG(DEBUG, "\"%s\" is successfully created (size=%u).",
1262 : : mz_name, ring_size);
1263 : : /* In non "strict_queue" mode, initialize the event. */
1264 : 0 : MLX5_AGE_SET(age_info, MLX5_AGE_TRIGGER);
1265 : : }
1266 : : return 0;
1267 : : error:
1268 : : MLX5_ASSERT(strict_queue);
1269 [ # # ]: 0 : while (qidx--)
1270 : 0 : rte_ring_free(age_info->hw_q_age->aged_lists[qidx]);
1271 : 0 : mlx5_free(age_info->hw_q_age);
1272 : 0 : return -1;
1273 : : }
1274 : :
1275 : : /**
1276 : : * Cleanup aged-out ring before destroying.
1277 : : *
1278 : : * @param priv
1279 : : * Pointer to port private object.
1280 : : * @param r
1281 : : * Pointer to aged-out ring object.
1282 : : */
1283 : : static void
1284 : 0 : mlx5_hws_aged_out_ring_cleanup(struct mlx5_priv *priv, struct rte_ring *r)
1285 : : {
1286 : 0 : int ring_size = rte_ring_count(r);
1287 : :
1288 [ # # ]: 0 : while (ring_size > 0) {
1289 [ # # # # : 0 : uint32_t age_idx = 0;
# ]
1290 : :
1291 : : if (rte_ring_dequeue_elem(r, &age_idx, sizeof(uint32_t)) < 0)
1292 : : break;
1293 : : /* get the AGE context if the aged-out index is still valid. */
1294 : 0 : mlx5_hws_age_context_get(priv, age_idx);
1295 : 0 : ring_size--;
1296 : : }
1297 : 0 : rte_ring_free(r);
1298 : 0 : }
1299 : :
1300 : : /**
1301 : : * Destroy the shared aging list information per port.
1302 : : *
1303 : : * @param priv
1304 : : * Pointer to port private object.
1305 : : */
1306 : : static void
1307 : 0 : mlx5_hws_age_info_destroy(struct mlx5_priv *priv)
1308 : : {
1309 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1310 : 0 : uint16_t nb_queues = age_info->hw_q_age->nb_rings;
1311 : : struct rte_ring *r;
1312 : :
1313 [ # # ]: 0 : if (priv->hws_strict_queue) {
1314 : : uint32_t qidx;
1315 : :
1316 [ # # ]: 0 : for (qidx = 0; qidx < nb_queues; ++qidx) {
1317 : 0 : r = age_info->hw_q_age->aged_lists[qidx];
1318 : 0 : mlx5_hws_aged_out_ring_cleanup(priv, r);
1319 : : }
1320 : 0 : mlx5_free(age_info->hw_q_age);
1321 : : } else {
1322 : : r = age_info->hw_age.aged_list;
1323 : 0 : mlx5_hws_aged_out_ring_cleanup(priv, r);
1324 : : }
1325 : 0 : }
1326 : :
1327 : : /**
1328 : : * Initialize the aging mechanism per port.
1329 : : *
1330 : : * @param dev
1331 : : * Pointer to the rte_eth_dev structure.
1332 : : * @param attr
1333 : : * Port configuration attributes.
1334 : : * @param nb_queues
1335 : : * Number of HWS queues.
1336 : : *
1337 : : * @return
1338 : : * 0 on success, a negative errno value otherwise and rte_errno is set.
1339 : : */
1340 : : int
1341 : 0 : mlx5_hws_age_pool_init(struct rte_eth_dev *dev,
1342 : : uint32_t nb_aging_objects,
1343 : : uint16_t nb_queues,
1344 : : bool strict_queue)
1345 : : {
1346 : 0 : struct mlx5_priv *priv = dev->data->dev_private;
1347 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1348 : 0 : struct mlx5_indexed_pool_config cfg = {
1349 : : .size =
1350 : : RTE_CACHE_LINE_ROUNDUP(sizeof(struct mlx5_hws_age_param)),
1351 : : .trunk_size = 1 << 12,
1352 : : .per_core_cache = 1 << 13,
1353 : : .need_lock = 1,
1354 : 0 : .release_mem_en = !!priv->sh->config.reclaim_mode,
1355 : : .malloc = mlx5_malloc,
1356 : : .free = mlx5_free,
1357 : : .type = "mlx5_hws_age_pool",
1358 : : };
1359 : : uint32_t nb_alloc_cnts;
1360 : : uint32_t rsize;
1361 : : uint32_t nb_ages_updated;
1362 : : int ret;
1363 : :
1364 : : MLX5_ASSERT(priv->hws_cpool);
1365 [ # # ]: 0 : nb_alloc_cnts = mlx5_hws_cnt_pool_get_size(priv->hws_cpool);
1366 [ # # ]: 0 : if (strict_queue) {
1367 : 0 : rsize = mlx5_hws_aged_out_q_ring_size_get(nb_alloc_cnts,
1368 : : nb_queues);
1369 : 0 : nb_ages_updated = rsize * nb_queues + nb_aging_objects;
1370 : : } else {
1371 : : rsize = mlx5_hws_aged_out_ring_size_get(nb_alloc_cnts);
1372 : 0 : nb_ages_updated = rsize + nb_aging_objects;
1373 : : }
1374 : 0 : ret = mlx5_hws_age_info_init(dev, nb_queues, strict_queue, rsize);
1375 [ # # ]: 0 : if (ret < 0)
1376 : : return ret;
1377 : 0 : cfg.max_idx = rte_align32pow2(nb_ages_updated);
1378 [ # # ]: 0 : if (cfg.max_idx <= cfg.trunk_size) {
1379 : 0 : cfg.per_core_cache = 0;
1380 : 0 : cfg.trunk_size = cfg.max_idx;
1381 [ # # ]: 0 : } else if (cfg.max_idx <= MLX5_HW_IPOOL_SIZE_THRESHOLD) {
1382 : 0 : cfg.per_core_cache = MLX5_HW_IPOOL_CACHE_MIN;
1383 : : }
1384 : 0 : age_info->ages_ipool = mlx5_ipool_create(&cfg);
1385 [ # # ]: 0 : if (age_info->ages_ipool == NULL) {
1386 : 0 : mlx5_hws_age_info_destroy(priv);
1387 : 0 : rte_errno = ENOMEM;
1388 : 0 : return -rte_errno;
1389 : : }
1390 : 0 : priv->hws_age_req = 1;
1391 : 0 : return 0;
1392 : : }
1393 : :
1394 : : /**
1395 : : * Cleanup all aging resources per port.
1396 : : *
1397 : : * @param priv
1398 : : * Pointer to port private object.
1399 : : */
1400 : : void
1401 : 0 : mlx5_hws_age_pool_destroy(struct mlx5_priv *priv)
1402 : : {
1403 : 0 : struct mlx5_age_info *age_info = GET_PORT_AGE_INFO(priv);
1404 : :
1405 : 0 : rte_spinlock_lock(&priv->sh->cpool_lock);
1406 : : MLX5_ASSERT(priv->hws_age_req);
1407 : 0 : mlx5_hws_age_info_destroy(priv);
1408 : 0 : mlx5_ipool_destroy(age_info->ages_ipool);
1409 : 0 : age_info->ages_ipool = NULL;
1410 : 0 : priv->hws_age_req = 0;
1411 : 0 : rte_spinlock_unlock(&priv->sh->cpool_lock);
1412 : 0 : }
1413 : :
1414 : : #endif
|