EM-ODP 4.4.0
Event Machine on ODP
Loading...
Searching...
No Matches
em_atomic_group.c
1/*
2 * Copyright (c) 2015-2026, Nokia Solutions and Networks
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#ifndef _GNU_SOURCE
32#define _GNU_SOURCE
33#endif
34
35#ifdef HAVE_CONFIG_H
36#include "config.h"
37#endif
38
39#include <stdbool.h>
40#include <stdint.h>
41#include <string.h>
42
43#include <odp_api.h>
44
45#include <event_machine.h>
47
48#include "em_atomic_group.h"
51#include "em_error.h"
52#include "em_eo.h"
53#include "em_eo_types.h"
54#include "em_event.h"
55#include "em_event_inline.h"
56#include "em_event_state.h"
57#include "em_event_types.h"
58#include "em_mem.h"
59#include "em_queue.h"
60#include "em_queue_inline.h"
61#include "em_queue_types.h"
62#include "misc/list.h"
63#include "misc/objpool.h"
64
65/**
66 * Atomic group inits done at global init (once at startup on one core)
67 */
68em_status_t atomic_group_init(atomic_group_tbl_t *const atomic_group_tbl,
69 atomic_group_pool_t *const atomic_group_pool)
70{
71 atomic_group_elem_t *atomic_group_elem;
72 const uint32_t objpool_subpools = MIN(4, OBJSUBPOOLS_MAX);
73 int ret;
74
75 memset(atomic_group_tbl, 0, sizeof(atomic_group_tbl_t));
76 memset(atomic_group_pool, 0, sizeof(atomic_group_pool_t));
77 odp_atomic_init_u32(&em_shm->atomic_group_count, 0);
78
79 for (int i = 0; i < EM_MAX_ATOMIC_GROUPS; i++) {
80 em_atomic_group_t agrp = agrp_idx2hdl(i);
81 atomic_group_elem_t *const agrp_elem =
82 atomic_group_elem_get(agrp);
83
84 if (unlikely(!agrp_elem))
85 return EM_ERR_BAD_POINTER;
86
87 agrp_elem->atomic_group = agrp; /* store handle */
88
89 /* Init list and lock */
90 odp_ticketlock_init(&agrp_elem->lock);
91 list_init(&agrp_elem->qlist_head);
92 odp_atomic_init_u32(&agrp_elem->num_queues, 0);
93 odp_atomic_init_u32(&agrp_elem->num_hi_prio_queues, 0);
94 }
95
96 ret = objpool_init(&atomic_group_pool->objpool, objpool_subpools);
97 if (ret != 0)
98 return EM_ERR_LIB_FAILED;
99
100 for (uint32_t i = 0; i < EM_MAX_ATOMIC_GROUPS; i++) {
101 atomic_group_elem = &atomic_group_tbl->ag_elem[i];
102 objpool_add(&atomic_group_pool->objpool, i % objpool_subpools,
103 &atomic_group_elem->atomic_group_pool_elem);
104 }
105
106 return EM_OK;
107}
108
109static inline atomic_group_elem_t *
110ag_pool_elem2ag_elem(const objpool_elem_t *const atomic_group_pool_elem)
111{
112 return (atomic_group_elem_t *)((uintptr_t)atomic_group_pool_elem -
113 offsetof(atomic_group_elem_t, atomic_group_pool_elem));
114}
115
116/**
117 * Dynamic atomic group allocation
118 */
119em_atomic_group_t atomic_group_alloc(void)
120{
121 const atomic_group_elem_t *ag_elem;
122 const objpool_elem_t *ag_p_elem;
123
124 ag_p_elem = objpool_rem(&em_shm->atomic_group_pool.objpool,
125 em_core_id());
126
127 if (unlikely(ag_p_elem == NULL))
129
130 ag_elem = ag_pool_elem2ag_elem(ag_p_elem);
131
132 odp_atomic_inc_u32(&em_shm->atomic_group_count);
133 return ag_elem->atomic_group;
134}
135
136em_status_t atomic_group_free(em_atomic_group_t atomic_group)
137{
138 atomic_group_elem_t *agrp_elem = atomic_group_elem_get(atomic_group);
139
140 if (unlikely(agrp_elem == NULL))
141 return EM_ERR_BAD_ID;
142
143 objpool_add(&em_shm->atomic_group_pool.objpool,
145 &agrp_elem->atomic_group_pool_elem);
146
147 odp_atomic_dec_u32(&em_shm->atomic_group_count);
148 return EM_OK;
149}
150
151/**
152 * Called by em_queue_delete() to remove the queue from the atomic group list
153 */
154void atomic_group_remove_queue(queue_elem_t *const q_elem)
155{
156 if (!q_elem->flags.in_atomic_group)
157 return;
158
159 em_atomic_group_t atomic_group = q_elem->agrp.atomic_group;
160
161 if (!invalid_atomic_group(atomic_group)) {
162 atomic_group_elem_t *const ag_elem =
163 atomic_group_elem_get(atomic_group);
164
165 atomic_group_rem_queue_list(ag_elem, q_elem);
166 q_elem->flags.in_atomic_group = false;
168 }
169}
170
171unsigned int atomic_group_count(void)
172{
173 return odp_atomic_load_u32(&em_shm->atomic_group_count);
174}
175
176/**
177 * Handle enqueue error for the specified atomic group.
178 *
179 * Called when an error occurs during the enqueue operation for the
180 * given atomic group.
181 *
182 * @param atomic_group Atomic group handle for which the enqueue error occurred.
183 */
184static void ag_internal_enq_error(em_atomic_group_t atomic_group,
185 odp_event_t odp_evtbl[], const int num_free)
186{
187 /* num_free <= EM_SCHED_MULTI_MAX_BURST, use fixed-size (non-VLA) tables */
189 em_event_t ev_tbl[EM_SCHED_MULTI_MAX_BURST];
190
191 event_init_odp_multi(odp_evtbl, ev_tbl/*out*/, ev_hdr_tbl/*out*/,
192 num_free, true/*is_extev*/, NULL/*not a dispatch: always scan*/);
193 /* Drop events that could not be enqueued */
194 em_free_multi(ev_tbl, num_free);
195 /*
196 * Use dispatch escope since this func is called only from
197 * dispatch_round() => atomic_group_dispatch()
198 */
199 INTERNAL_ERROR(EM_ERR_OPERATION_FAILED, EM_ESCOPE_DISPATCH,
200 "Atomic group:%" PRI_AGRP " internal enqueue fails: %d events dropped",
201 atomic_group, num_free);
202}
203
204/**
205 * Enqueue atomic group events into the AG-specific internal queue (=stash).
206 *
207 * This function is run concurrently by all cores dispatching events from
208 * queues that belong to this atomic group - enqueuing is done potentially in
209 * parallel.
210 *
211 * @param ag_elem Atomic group element
212 * @param q_elem Queue element
213 * @param odp_evtbl Array of ODP events to enqueue
214 * @param num_events Number of events in the array
215 * @param priority Priority of the queue
216 */
217static void ag_internal_enq(atomic_group_elem_t *ag_elem, const queue_elem_t *q_elem,
218 odp_event_t odp_evtbl[], const int num_events,
219 const em_queue_prio_t priority)
220{
221 /* num_events <= EM_SCHED_MULTI_MAX_BURST, use fixed-size (non-VLA) table */
223 odp_stash_t stash;
224
225 const em_queue_t queue = (em_queue_t)(uintptr_t)q_elem->queue;
226 const uint16_t qidx = (uint16_t)queue_hdl2idx(queue);
227
228 for (int i = 0; i < num_events; i++)
229 entry_tbl[i] = (stash_entry_t){.qidx = qidx,
230 .evptr = (uintptr_t)odp_evtbl[i]};
231
232 if (priority == EM_QUEUE_PRIO_HIGHEST)
233 stash = ag_elem->stashes.hi_prio;
234 else
235 stash = ag_elem->stashes.lo_prio;
236
237 /*
238 * Increment the event count before enqueueing to ensure the consumer
239 * stays in the loop rather a bit too long than leaves before all events
240 * are processed.
241 */
242 (void)__atomic_fetch_add(&ag_elem->dispatch_lock.lock_and_evcnt,
243 (uint64_t)num_events, __ATOMIC_RELEASE);
244
245 /* Enqueue events to internal queue (=stash) */
246 int ret = odp_stash_put_u64(stash, &entry_tbl[0].u64, num_events);
247
248 if (unlikely(ret != num_events)) {
249 int enq_cnt = ret > 0 ? ret : 0;
250
251 (void)__atomic_fetch_sub(&ag_elem->dispatch_lock.lock_and_evcnt,
252 (uint64_t)(num_events - enq_cnt),
253 __ATOMIC_RELEASE);
254 ag_internal_enq_error(ag_elem->atomic_group,
255 &odp_evtbl[enq_cnt], num_events - enq_cnt);
256 }
257}
258
259/**
260 * Dequeue from the AG-internal queue (stash).
261 *
262 * This function attempts to dequeue up to 'num_events' entries from the atomic
263 * group internal queue (stash). Returns the number of entries actually dequeued.
264 *
265 * @param ag_elem Atomic group element.
266 * @param entry_tbl Output array for dequeued entries.
267 * @param num_events Maximum number of events to dequeue.
268 *
269 * @return Number of entries dequeued.
270 */
271static uint32_t ag_internal_deq(atomic_group_elem_t *ag_elem,
272 stash_entry_t entry_tbl[/*out*/], const int num_events)
273{
274 /*
275 * The function call_eo_receive_fn/multi() will convert to
276 * EM events with event-generation counts, if ESV is enabled,
277 * before passing the events to the user EO.
278 */
279 int32_t hi_cnt = 0;
280 int32_t lo_cnt = 0;
281 uint32_t deq_cnt = 0;
282 uint32_t max_deq = num_events;
283
284 /*
285 * hi-prio events: only touch the hi-prio stash when the atomic group
286 * actually has an EM_QUEUE_PRIO_HIGHEST queue.
287 */
288 if (odp_atomic_load_u32(&ag_elem->num_hi_prio_queues) > 0) {
289 hi_cnt = odp_stash_get_u64(ag_elem->stashes.hi_prio,
290 &entry_tbl[0].u64 /*[out]*/, max_deq);
291 if (hi_cnt > 0)
292 deq_cnt = hi_cnt;
293 }
294
295 /* ...then lo-prio events */
296 if (deq_cnt < max_deq) {
297 lo_cnt = odp_stash_get_u64(ag_elem->stashes.lo_prio,
298 &entry_tbl[deq_cnt].u64 /*[out]*/,
299 max_deq - deq_cnt);
300 if (lo_cnt > 0)
301 deq_cnt += lo_cnt;
302 }
303
304 if (unlikely(EM_CHECK_LEVEL > 1 && (hi_cnt < 0 || lo_cnt < 0))) {
305 /* Report error if dequeue failed */
306 INTERNAL_ERROR(EM_ERR_OPERATION_FAILED, EM_ESCOPE_DISPATCH,
307 "Atomic group:%" PRI_AGRP " internal dequeue fails:\n"
308 " max_deq:%d deq_cnt:%d (hi_cnt:%d lo_cnt:%d)",
309 ag_elem->atomic_group, max_deq, deq_cnt, hi_cnt, lo_cnt);
310 }
311
312 /* Decrement the event count by the number dequeued */
313 if (deq_cnt > 0)
314 (void)__atomic_fetch_sub(&ag_elem->dispatch_lock.lock_and_evcnt,
315 deq_cnt, __ATOMIC_ACQUIRE);
316
317 return deq_cnt; /* number of events dequeued */
318}
319
320/**
321 * Dispatch the dequeued atomic group events in batches
322 *
323 * Dispatch the dequeued events in batches, where each batch contains events
324 * from the same atomic queue.
325 *
326 * @param deq_entry_tbl Array of dequeued entries to be dispatched.
327 * @param deq_cnt Number of dequeued entries.
328 */
329static inline void
330ag_dispatch_batches(const stash_entry_t deq_entry_tbl[], const uint32_t deq_cnt)
331{
332 em_locm_t *const locm = &em_locm;
333 /* deq_cnt <= EM_SCHED_AG_MULTI_MAX_BURST, use fixed-size (non-VLA) table */
334 odp_event_t deq_evtbl[EM_SCHED_AG_MULTI_MAX_BURST];
335
336 for (uint32_t i = 0; i < deq_cnt; i++)
337 deq_evtbl[i] = (odp_event_t)(uintptr_t)deq_entry_tbl[i].evptr;
338
339 locm->event_burst_cnt = deq_cnt;
340 uint32_t tbl_idx = 0; /* index into ..._tbl[] */
341
342 /*
343 * Dispatch in batches of 'batch_cnt' events.
344 * Each batch contains events from the same atomic queue.
345 */
346 do {
347 const uint32_t qidx = deq_entry_tbl[tbl_idx].qidx;
348 const em_queue_t queue = queue_idx2hdl(qidx);
349 queue_elem_t *const batch_qelem = queue_elem_get(queue);
350 uint32_t batch_cnt = 1;
351
352 /* i < deq_cnt <= EM_SCHED_AG_MULTI_MAX_BURST */
353 for (uint32_t i = tbl_idx + 1; i < deq_cnt &&
354 deq_entry_tbl[i].qidx == qidx; i++) {
355 batch_cnt++;
356 }
357
358 dispatch_events(&deq_evtbl[tbl_idx],
359 batch_cnt, batch_qelem);
360 tbl_idx += batch_cnt;
361 } while (tbl_idx < deq_cnt);
362}
363
364/**
365 * Fast-path acquire of the atomic group dispatch lock.
366 *
367 * Single compare-and-swap that succeeds only when the lock is free AND the
368 * event count is zero, i.e. the internal stash is empty. On success the caller
369 * holds the lock with an empty stash and can dispatch its just-scheduled events
370 * directly, bypassing the stash entirely.
371 *
372 * Unlike ag_trylock() this never spins: any contention (lock held or events
373 * already stashed) returns false and the caller falls back to the slow path.
374 *
375 * @param ag_elem Pointer to the atomic group element.
376 *
377 * @return true if the lock was acquired with an empty stash, false otherwise.
378 */
379static bool ag_acquire_if_idle(atomic_group_elem_t *const ag_elem)
380{
381 atomic_group_dispatch_lock_t expected = {.parts = {.lock = 0,
382 .wrap = WRAP_PROTECT,
383 .evcnt = 0}};
384 atomic_group_dispatch_lock_t desired = {.parts = {.lock = 1,
385 .wrap = WRAP_PROTECT,
386 .evcnt = 0}};
387
388 return __atomic_compare_exchange_n(&ag_elem->dispatch_lock.lock_and_evcnt,
389 &expected.lock_and_evcnt,
390 desired.lock_and_evcnt,
391 false, __ATOMIC_ACQUIRE /*success*/,
392 __ATOMIC_RELAXED /*failure*/);
393}
394
395/**
396 * Try to acquire the atomic group dispatch lock.
397 *
398 * Attempts to atomically set the lock bit in the atomic group's dispatch lock.
399 * If the lock was not held, it will be acquired and the function returns true.
400 * If the lock was already held, the function returns false.
401 * Sets both the lock and the event count with a single atomic operation.
402 *
403 * Must loop to ensure that this function together with the ag_unlock() manages
404 * to handle all events and no events are left in the internal queue when
405 * returning.
406 *
407 * This function pairs with ag_unlock() to release the lock.
408 *
409 * @param ag_elem Pointer to the atomic group element.
410 *
411 * @return true if the lock was successfully acquired, false otherwise.
412 */
413static bool ag_trylock(atomic_group_elem_t *const ag_elem)
414{
415 bool success = false;
416 atomic_group_dispatch_lock_t lock_expected;
417 atomic_group_dispatch_lock_t lock_desired;
418
419 do {
420 lock_expected.lock_and_evcnt =
421 __atomic_load_n(&ag_elem->dispatch_lock.lock_and_evcnt,
422 __ATOMIC_RELAXED);
423 lock_desired.parts.lock = 1;
424 lock_desired.parts.wrap = lock_expected.parts.wrap;
425 lock_desired.parts.evcnt = lock_expected.parts.evcnt;
426
427 success = __atomic_compare_exchange_n(&ag_elem->dispatch_lock.lock_and_evcnt,
428 &lock_expected.lock_and_evcnt,
429 lock_desired.lock_and_evcnt,
430 false, __ATOMIC_ACQUIRE /*success*/,
431 __ATOMIC_RELAXED /*failure*/);
432 } while (!success);
433
434 bool lock_taken = lock_expected.parts.lock == 0 && lock_desired.parts.lock == 1;
435
436 return lock_taken;
437}
438
439/**
440 * @brief Release the atomic group dispatch lock.
441 *
442 * Normal scenario (em_atomic_processing_end() NOT called):
443 * Atomically releases the lock for the atomic group, setting the lock bit to 0.
444 * Only succeeds if the lock is currently held and the event count is zero.
445 * Clears both the lock and the event count with a single atomic operation.
446 * OR:
447 * This core called em_atomic_processing_end() during dispatch:
448 * The atomic context was dropped for the atomic group, i.e. the atomic
449 * group lock was released - another EM-core might now be processing
450 * events from the same atomic group.
451 *
452 * This function pairs with ag_trylock() to acquire the lock.
453 *
454 * @param ag_elem Pointer to the atomic group element.
455 *
456 * @return true if the lock is unlocked, false if locked.
457 */
458static bool ag_unlock(atomic_group_elem_t *const ag_elem)
459{
460 em_locm_t *const locm = &em_locm;
461
462 if (!locm->atomic_group_released) {
463 /*
464 * Normal scenario - em_atomic_processing_end() NOT called:
465 */
466 atomic_group_dispatch_lock_t unlock_expected = {.parts = {.lock = 1,
467 .wrap = WRAP_PROTECT,
468 .evcnt = 0}};
469 atomic_group_dispatch_lock_t unlock_desired = {.parts = {.lock = 0,
470 .wrap = WRAP_PROTECT,
471 .evcnt = 0}};
472 bool unlocked = __atomic_compare_exchange_n(&ag_elem->dispatch_lock.lock_and_evcnt,
473 &unlock_expected.lock_and_evcnt,
474 unlock_desired.lock_and_evcnt,
475 false, __ATOMIC_RELEASE /*success*/,
476 __ATOMIC_RELAXED /*failure*/);
477 return unlocked;
478 }
479
480 /*
481 * This core called em_atomic_processing_end() during dispatch.
482 *
483 * The atomic context was dropped for the atomic group, i.e. the atomic
484 * group lock was released - another EM-core might now be processing
485 * events from the same atomic group.
486 *
487 * atomic_group_release() set ag_elem->dispatch_lock.part.lock = 0.
488 */
489 locm->atomic_group_released = false; /* reset for next round */
490
491 bool success = false;
492 atomic_group_dispatch_lock_t lock_expected;
493 atomic_group_dispatch_lock_t lock_desired;
494
495 do {
496 lock_expected.lock_and_evcnt =
497 __atomic_load_n(&ag_elem->dispatch_lock.lock_and_evcnt,
498 __ATOMIC_RELAXED);
499
500 if (lock_expected.parts.lock == 1) {
501 /* Agrp lock taken by another core, let it handle the remaining events */
502 return true; /* unlocked = true (this core doesn’t hold the lock) */
503 }
504
505 /*
506 * lock_expected.parts.lock == 0
507 * Agrp lock not yet taken by another core:
508 */
509
510 if (lock_expected.parts.evcnt == 0 &&
511 lock_expected.parts.wrap == WRAP_PROTECT) {
512 /* No events */
513 return true; /* unlocked = true */
514 }
515
516 /* Still events, try to re-acquire lock */
517 lock_desired.parts.lock = 1;
518 lock_desired.parts.wrap = lock_expected.parts.wrap;
519 lock_desired.parts.evcnt = lock_expected.parts.evcnt;
520
521 success = __atomic_compare_exchange_n(&ag_elem->dispatch_lock.lock_and_evcnt,
522 &lock_expected.lock_and_evcnt,
523 lock_desired.lock_and_evcnt,
524 false, __ATOMIC_ACQUIRE /*success*/,
525 __ATOMIC_RELAXED /*failure*/);
526 /* 'unlocked = false' here */
527 } while (!success);
528
529 /* unlocked = false - this core has the AG lock */
530 return false;
531}
532
533/**
534 * Release the atomic group dispatch lock.
535 *
536 * Called by em_atomic_processing_end() if 'locm->event_burst_cnt == 0' since
537 * the atomic context can only be dropped for the last event (or last set of
538 * events in the case of EO multircv).
539 */
540void atomic_group_release(void)
541{
542 em_locm_t *const locm = &em_locm;
543
544 if (unlikely(locm->atomic_group_released)) {
545 /*
546 * The atomic group lock has already been released by
547 * em_atomic_processing_end() - no need to release it again.
548 */
549 return;
550 }
551
552 /*
553 * The caller, i.e. em_atomic_processing_end() has verified that
554 * locm->event_burst_cnt == 0.
555 */
556 const queue_elem_t *q_elem = locm->current.sched_q_elem;
557 em_atomic_group_t atomic_group = q_elem->agrp.atomic_group;
558 atomic_group_elem_t *const ag_elem = atomic_group_elem_get(atomic_group);
559
560 bool success = false;
561 atomic_group_dispatch_lock_t lock_expected;
562 atomic_group_dispatch_lock_t lock_desired;
563
564 do {
565 lock_expected.lock_and_evcnt =
566 __atomic_load_n(&ag_elem->dispatch_lock.lock_and_evcnt,
567 __ATOMIC_RELAXED);
568 lock_desired.parts.lock = 0;
569 lock_desired.parts.wrap = lock_expected.parts.wrap;
570 lock_desired.parts.evcnt = lock_expected.parts.evcnt;
571
572 success = __atomic_compare_exchange_n(&ag_elem->dispatch_lock.lock_and_evcnt,
573 &lock_expected.lock_and_evcnt,
574 lock_desired.lock_and_evcnt,
575 false, __ATOMIC_ACQUIRE /*success*/,
576 __ATOMIC_RELAXED /*failure*/);
577 } while (!success);
578
579 locm->atomic_group_released = true;
580}
581
582/**
583 * Drain and dispatch the atomic group's stashed events until the stash empties.
584 *
585 * The caller must hold the atomic group dispatch lock. Loops dequeuing and
586 * dispatching stashed events (added by this and other cores) in batches until
587 * ag_unlock() reports the lock released with the stash drained - or until
588 * em_atomic_processing_end() handed dispatching over to another core.
589 *
590 * @param ag_elem Atomic group element (dispatch lock held by the caller).
591 */
592static void ag_dispatch_loop(atomic_group_elem_t *const ag_elem)
593{
594 /* deq_cnt <= EM_SCHED_AG_MULTI_MAX_BURST, use fixed-size (non-VLA) table */
596
597 do {
598 uint32_t deq_cnt = ag_internal_deq(ag_elem, entry_tbl /*[out]*/,
600 /*
601 * Dispatch the dequeued events/entries in batches.
602 * Each batch contains events from the same atomic queue.
603 */
604 if (deq_cnt > 0)
605 ag_dispatch_batches(entry_tbl, deq_cnt);
606
607 /* Check if the lock can be unlocked */
608 bool unlocked = ag_unlock(ag_elem);
609
610 if (unlocked) {
611 /*
612 * The atomic group lock is released and the event count
613 * is zero or em_atomic_processing_end() was called
614 * during dispatch and another core took over dispatching.
615 * The atomic group processing (on this core) has ended.
616 */
617 return;
618 }
619 } while (true);
620}
621
622/**
623 * Dispatch function for events from queues belonging to an atomic group.
624 *
625 * Atomicity across the queues of an atomic group is guaranteed by letting only
626 * one core dispatch the group's events at a time, serialized by the per-group
627 * dispatch lock.
628 *
629 * Fast path: when the lock is free and the internal stash is empty, take the
630 * lock and dispatch the just-scheduled events directly. They have already been
631 * scheduled and, with an empty stash, are the head of the atomic group FIFO -
632 * pushing them through the stash only to immediately pull them back out is pure
633 * overhead. Events that other cores stash while this core dispatches are
634 * drained afterwards by ag_dispatch_loop().
635 *
636 * Slow path (lock held elsewhere or stash non-empty): enqueue the events into
637 * the group's internal stash - this can run in parallel on several cores - then
638 * try to become the single dispatching core.
639 *
640 * @param odp_evtbl Events received from the scheduler from an AG queue
641 * @param num_events Number of events received
642 * @param q_elem Queue element, queue is part of the atomic group
643 */
644void atomic_group_dispatch(odp_event_t odp_evtbl[], const int num_events,
645 queue_elem_t *const q_elem)
646{
647 atomic_group_elem_t *ag_elem = atomic_group_elem_get(q_elem->agrp.atomic_group);
648
649 if (ag_acquire_if_idle(ag_elem)) {
650 /*
651 * Fast path: lock taken with an empty stash. Release the source
652 * queue's ODP atomic context (the AG dispatch lock provides
653 * atomicity) and dispatch the scheduled events directly, without
654 * a stash round-trip.
655 */
656 odp_schedule_release_atomic();
657 em_locm.event_burst_cnt = num_events;
658 dispatch_events(odp_evtbl, num_events, q_elem);
659 /*
660 * dispatch_events() may have released the AG lock via
661 * em_atomic_processing_end(). ag_unlock() resolves the lock
662 * state: when it returns false this core still holds the lock
663 * and drains any events other cores stashed while it dispatched
664 * (the stash get inside ag_dispatch_loop() is only safe while
665 * holding the lock).
666 */
667 if (!ag_unlock(ag_elem))
668 ag_dispatch_loop(ag_elem);
669 return;
670 }
671
672 /*
673 * Slow path: another core holds the lock or the stash already holds
674 * events. Enqueue into the AG internal stash to preserve ordering, then
675 * try to acquire the lock - if unavailable another core is already
676 * dispatching this atomic group.
677 */
678 ag_internal_enq(ag_elem, q_elem, odp_evtbl, num_events, q_elem->priority);
679
680 if (!ag_trylock(ag_elem))
681 return; /* Lock is already held by another thread */
682
683 /* hint */
684 odp_schedule_release_atomic();
685 ag_dispatch_loop(ag_elem);
686}
687
688#define AG_INFO_HDR_STR \
689"Number of atomic groups: %d\n\n" \
690"ID Name Qgrp Q-num\n" \
691"---------------------------------------------------------\n%s\n"
692
693#define AG_INFO_LEN 58
694#define AG_INFO_FMT "%-10" PRI_AGRP "%-32s%-10" PRI_QGRP "%-5d\n"/*58 characters*/
695
696void print_atomic_group_info(void)
697{
698 unsigned int ag_num; /*atomic group number*/
699 atomic_group_elem_t *ag_elem;
700 em_atomic_group_t ag_check;
701 char ag_name[EM_ATOMIC_GROUP_NAME_LEN];
702 int len = 0;
703 int n_print = 0;
704
705 em_atomic_group_t ag = em_atomic_group_first(&ag_num);
706
707 /*
708 * ag_num might not match the actual number of atomic groups returned
709 * by iterating with func em_atomic_group_next() if atomic groups
710 * are added or removed in parallel by another core. Thus space for 10
711 * extra atomic groups is reserved. If more than 10 atomic groups are
712 * added in parallel by other cores, we print only information of the
713 * (ag_num + 10) atomic groups.
714 *
715 * The extra 1 byte is reserved for the terminating null byte.
716 */
717 const int ag_info_str_len = (ag_num + 10) * AG_INFO_LEN + 1;
718 char ag_info_str[ag_info_str_len];
719
720 while (ag != EM_ATOMIC_GROUP_UNDEF) {
721 ag_elem = atomic_group_elem_get(ag);
722
723 em_atomic_group_name(ag, ag_name, sizeof(ag_name));
724
725 ag_check = em_atomic_group_find(ag_name);
726 if (unlikely(ag_elem == NULL || ag_check != ag ||
727 !atomic_group_allocated(ag_elem))) {
729 continue;
730 }
731
732 n_print = snprintf(ag_info_str + len, ag_info_str_len - len,
733 AG_INFO_FMT, ag, ag_name, ag_elem->queue_group,
734 odp_atomic_load_u32(&ag_elem->num_queues));
735
736 /* Not enough space to hold more atomic group info */
737 if (n_print >= ag_info_str_len - len)
738 break;
739
740 len += n_print;
742 }
743
744 /* No atomic group */
745 if (len == 0) {
746 EM_PRINT("No atomic group has been created\n");
747 return;
748 }
749
750 /*
751 * To prevent printing incomplete information of the last atomic group
752 * when there is not enough space to hold all atomic group info.
753 */
754 ag_info_str[len] = '\0';
755 EM_PRINT(AG_INFO_HDR_STR, ag_num, ag_info_str);
756}
757
758#define AG_QUEUE_INFO_HDR_STR \
759"Atomic group %" PRI_AGRP "(%s) has %d queue(s):\n\n" \
760"ID Name Priority Type State Qgrp Ctx\n" \
761"-----------------------------------------------------------------------------------\n" \
762"%s\n"
763
764#define AG_Q_INFO_LEN 85
765#define AG_Q_INFO_FMT "%-10" PRI_QUEUE "%-32s%-10d%-10s%-9s%-10" PRI_QGRP "%-3c\n"
766
767void print_atomic_group_queues(em_atomic_group_t ag)
768{
769 unsigned int q_num;
770 em_queue_t ag_queue;
771 const queue_elem_t *q_elem;
772 char q_name[EM_QUEUE_NAME_LEN];
773 int len = 0;
774 int n_print = 0;
775
776 atomic_group_elem_t *ag_elem = atomic_group_elem_get(ag);
777
778 if (unlikely(ag_elem == NULL || !atomic_group_allocated(ag_elem))) {
779 EM_PRINT("Atomic group %" PRI_AGRP "is not created!\n", ag);
780 return;
781 }
782
783 ag_queue = em_atomic_group_queue_first(&q_num, ag);
784
785 /*
786 * q_num may not match the number of queues actually returned by iterating
787 * with em_atomic_group_queue_next() if queues are added or removed
788 * in parallel by another core. Thus space for 10 extra queues is reserved.
789 * If more than 10 queues are added to this atomic group by other cores
790 * in parallel, we print only information of the (q_num + 10) queues.
791 *
792 * The extra 1 byte is reserved for the terminating null byte.
793 */
794 int q_info_str_len = (q_num + 10) * AG_Q_INFO_LEN + 1;
795 char q_info_str[q_info_str_len];
796
797 while (ag_queue != EM_QUEUE_UNDEF) {
798 q_elem = queue_elem_get(ag_queue);
799
800 if (unlikely(q_elem == NULL || !queue_allocated(q_elem))) {
801 ag_queue = em_atomic_group_queue_next();
802 continue;
803 }
804
805 queue_name(q_elem, q_name, EM_QUEUE_NAME_LEN - 1);
806
807 n_print = snprintf(q_info_str + len, q_info_str_len - len,
808 AG_Q_INFO_FMT, ag_queue, q_name,
809 q_elem->priority,
810 queue_type_str(q_elem->type),
811 queue_state_str(q_elem->state),
812 q_elem->queue_group,
813 q_elem->context ? 'Y' : 'N');
814
815 /* Not enough space to hold more queue info */
816 if (n_print >= q_info_str_len - len)
817 break;
818
819 len += n_print;
820 ag_queue = em_atomic_group_queue_next();
821 }
822
823 /* Atomic group has no queue */
824 if (!len) {
825 EM_PRINT("Atomic group %" PRI_AGRP "(%s) has no queue!\n",
826 ag, ag_elem->name);
827 return;
828 }
829
830 /*
831 * To prevent printing incomplete information of the last queue when
832 * there is not enough space to hold all queue info.
833 */
834 q_info_str[len] = '\0';
835 EM_PRINT(AG_QUEUE_INFO_HDR_STR, ag, ag_elem->name, q_num, q_info_str);
836}
837
838void print_ag_elem_info(void)
839{
840 EM_PRINT("\n"
841 "EM Atomic Groups\n"
842 "----------------\n"
843 "ag-elem size: %zu B\n",
844 sizeof(atomic_group_elem_t));
845
846 DBG_PRINT("\t\toffset\tsize\n"
847 "\t\t------\t-----\n"
848 "atomic_group:\t%3zu B\t%3zu B\n"
849 "queue_group:\t%3zu B\t%3zu B\n"
850 "ag pool_elem:\t%3zu B\t%3zu B\n"
851 "stashes:\t%3zu B\t%3zu B\n"
852 "lock:\t\t%3zu B\t%3zu B\n"
853 "num_queues:\t%3zu B\t%3zu B\n"
854 "qlist_head[]:\t%3zu B\t%3zu B\n"
855 "name:\t\t%3zu B\t%3zu B\n",
856 offsetof(atomic_group_elem_t, atomic_group),
857 /* NOLINTNEXTLINE(bugprone-sizeof-expression) */
858 sizeof_field(atomic_group_elem_t, atomic_group),
859 offsetof(atomic_group_elem_t, queue_group),
860 /* NOLINTNEXTLINE(bugprone-sizeof-expression) */
861 sizeof_field(atomic_group_elem_t, queue_group),
862 offsetof(atomic_group_elem_t, atomic_group_pool_elem),
863 sizeof_field(atomic_group_elem_t, atomic_group_pool_elem),
864 offsetof(atomic_group_elem_t, stashes),
865 sizeof_field(atomic_group_elem_t, stashes),
866 offsetof(atomic_group_elem_t, lock),
867 sizeof_field(atomic_group_elem_t, lock),
868 offsetof(atomic_group_elem_t, num_queues),
869 sizeof_field(atomic_group_elem_t, num_queues),
870 offsetof(atomic_group_elem_t, qlist_head),
871 sizeof_field(atomic_group_elem_t, qlist_head),
872 offsetof(atomic_group_elem_t, name),
873 sizeof_field(atomic_group_elem_t, name));
874
875 EM_PRINT("\n");
876}
#define INTERNAL_ERROR(error, escope, fmt,...)
Definition em_error.h:58
ENV_LOCAL em_locm_t em_locm
em_shm_t * em_shm
#define EM_QUEUE_NAME_LEN
#define EM_SCHED_MULTI_MAX_BURST
#define EM_CHECK_LEVEL
#define EM_MAX_ATOMIC_GROUPS
#define EM_ATOMIC_GROUP_NAME_LEN
#define EM_SCHED_AG_MULTI_MAX_BURST
#define EM_ATOMIC_GROUP_UNDEF
#define PRI_AGRP
#define EM_QUEUE_UNDEF
em_atomic_group_t em_atomic_group_find(const char *name)
em_queue_t em_atomic_group_queue_next(void)
em_queue_t em_atomic_group_queue_first(unsigned int *num, em_atomic_group_t atomic_group)
size_t em_atomic_group_name(em_atomic_group_t atomic_group, char *name, size_t maxlen)
em_atomic_group_t em_atomic_group_next(void)
em_atomic_group_t em_atomic_group_first(unsigned int *num)
int em_core_id(void)
#define EM_OK
uint32_t em_status_t
@ EM_ERR_OPERATION_FAILED
@ EM_ERR_BAD_ID
@ EM_ERR_LIB_FAILED
@ EM_ERR_BAD_POINTER
void em_free_multi(em_event_t events[], int num)
uint32_t em_queue_prio_t
@ EM_QUEUE_PRIO_HIGHEST
#define OBJSUBPOOLS_MAX
Definition objpool.h:62
queue_elem_t * sched_q_elem
Definition em_mem.h:216
odp_atomic_u32_t num_queues
em_queue_group_t queue_group
char name[EM_ATOMIC_GROUP_NAME_LEN]
em_atomic_group_t atomic_group
struct atomic_group_elem_t::@30 stashes
odp_atomic_u32_t num_hi_prio_queues
objpool_elem_t atomic_group_pool_elem
atomic_group_elem_t ag_elem[EM_MAX_ATOMIC_GROUPS]
em_locm_current_t current
Definition em_mem.h:228
bool atomic_group_released
Definition em_mem.h:250
int event_burst_cnt
Definition em_mem.h:237
odp_atomic_u32_t atomic_group_count
Definition em_mem.h:181
uint32_t subpool_idx
Definition objpool.h:74
em_atomic_group_t atomic_group
queue_state_t state
q_elem_atomic_group_t agrp
em_queue_group_t queue_group
queue_elem_flags_t flags