[PATCH v2 5/9] bpf: simplify callback inuse tracking to boolean flag
Konstantin Ananyev
konstantin.v.ananyev at yandex.ru
Mon Oct 19 22:50:58 CEST 2026
> The even/odd usage counter semantics in struct bpf_eth_cbi was
> originally used to detect when in-flight packet callbacks complete.
> However, with at most one thread running the callback per port/queue
> pair and no risk of the ABA problem, a boolean flag (true for in use,
> false for idle) suffices.
>
> Change member use to bool and replace the counter increment in the
> datapath with stores of true and false. Simplify bpf_eth_cbi_wait()
> to a direct pause loop waiting until the flag is false, removing the
> now unnecessary RTE_WAIT_UNTIL_MASKED macro and casts. Update the
> documentation and synchronization sequence diagram accordingly.
As I explained in previous mail, it is not about even/odd.
It is about waiting while current 'quiscent' period will end.
By 'simplifying' that to use just boll values (0/1) you introduce
a live-lock in CP: it can be blocked for very long time waiting
while DP thread will keep processing packets.
So mu vote is: NACK.
As I said before - probably the best option to improving current BPF CB
mechanism,
would be to make ethedev CB to obey RCU-like behavior.
Konstantin
>
> Signed-off-by: Marat Khalili <qm2k21 at gmail.com>
> ---
> lib/bpf/bpf_pkt.c | 49 ++++++++++++++++++-----------------------------
> 1 file changed, 19 insertions(+), 30 deletions(-)
>
> diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
> index b1e25e4e7c..429feb76ad 100644
> --- a/lib/bpf/bpf_pkt.c
> +++ b/lib/bpf/bpf_pkt.c
> @@ -6,6 +6,7 @@
> #include <string.h>
> #include <errno.h>
> #include <stdint.h>
> +#include <stdbool.h>
>
> #include <sys/queue.h>
>
> @@ -36,12 +37,12 @@
> * 2. Member `cb` of struct `bpf_eth_cbi`;
> * 3. Member `use` of struct `bpf_eth_cbi`;
> *
> - * BPF program is only executed if the first two are set, and increments `use` at the start and
> - * end of the callback execution so that in the safe state its value is even.
> + * BPF program is only executed if the first two are set, and sets `use` to true at the start and
> + * to false at the end of the callback execution.
> * This relies on only one thread calling the per-port/queue callback at a time.
> *
> * Unload processing starts with clearing the first two entities,
> - * then waits for `use` to become even before destroying the BPF program.
> + * then waits for `use` to become false before destroying the BPF program.
> * The whole operation is protected by a control-plane-only spinlock (not shown on the diagram).
> *
> * Typical sequence of synchronization actions between CP and DP on unload is shown below:
> @@ -52,7 +53,7 @@
> * | | | | | |
> * | |<---------------------- fetch callback -----------------------|
> * | | | | | |
> - * | | |<------------- increment to odd --------------|
> + * | | |<------------ store true ---------------------|
> * | | | | | |
> * |-- remove callback -->| | | | rte_smp_mb()
> * | | | | | |
> @@ -62,17 +63,17 @@
> * | | | | | |
> * rte_smp_mb() | | | | |
> * | | | | | |
> - * +-if use is odd | | | | if cb not NULL-+
> + * +-while use is true | | | | if cb not NULL-+
> * | | | | | | | |
> - * | +------------ fetch odd ------------>| | |<-- execute --+ |
> + * | +----------- fetch true ------------>| | |<-- execute --+ |
> * | | | | | | BPF program | |
> - * | +-while use is same | | | | +-+
> - * | | | | | | | |
> - * | | +---------- fetch odd ------------>| | | rte_smp_rmb()
> - * | | | | | | | |
> - * +-+-+ | |<------------- increment to even -------------|
> + * | | | | | | +-+
> + * | | | | | | |
> + * | | | | | | rte_smp_rmb()
> + * | | | | | | |
> + * +-+ | |<------------ store false --------------------|
> * | | | | | |
> - * |------------- fetch even ------------>| | | |
> + * |------------ fetch false ------------>| | | |
> * | | | | | |
> * |----------------- destroy BPF program and cleanup ----------------->| |
> * | | | | | |
> @@ -87,7 +88,7 @@
> */
> struct __rte_cache_aligned bpf_eth_cbi {
> /* used by both data & control path */
> - RTE_ATOMIC(uint32_t) use; /*usage counter */
> + RTE_ATOMIC(bool) use; /* usage flag: true when in use, false otherwise */
> const struct rte_eth_rxtx_callback *cb; /* callback handle */
> struct rte_bpf *bpf;
> struct rte_bpf_jit jit;
> @@ -97,12 +98,6 @@ struct __rte_cache_aligned bpf_eth_cbi {
> uint16_t queue;
> };
>
> -/*
> - * Odd number means that callback is used by datapath.
> - * Even number means that callback is not used by datapath.
> - */
> -#define BPF_ETH_CBI_INUSE 1
> -
> /*
> * List to manage RX/TX installed callbacks.
> */
> @@ -141,20 +136,20 @@ static struct bpf_eth_cbh tx_cbh = {
> static __rte_always_inline void
> bpf_eth_cbi_inuse(struct bpf_eth_cbi *cbi)
> {
> - cbi->use++;
> + cbi->use = true;
> /* make sure no store/load reordering could happen */
> rte_smp_mb();
> }
>
> /*
> - * Marks given callback list as not used by datapath.
> + * Marks given callback as not used by datapath.
> */
> static __rte_always_inline void
> bpf_eth_cbi_unuse(struct bpf_eth_cbi *cbi)
> {
> /* make sure all previous loads are completed */
> rte_smp_rmb();
> - cbi->use++;
> + cbi->use = false;
> }
>
> /*
> @@ -163,18 +158,12 @@ bpf_eth_cbi_unuse(struct bpf_eth_cbi *cbi)
> static void
> bpf_eth_cbi_wait(const struct bpf_eth_cbi *cbi)
> {
> - uint32_t puse;
> -
> /* make sure all previous loads and stores are completed */
> rte_smp_mb();
>
> - puse = cbi->use;
> -
> /* in use, busy wait till current RX/TX iteration is finished */
> - if ((puse & BPF_ETH_CBI_INUSE) != 0) {
> - RTE_WAIT_UNTIL_MASKED((__rte_atomic uint32_t *)(uintptr_t)&cbi->use,
> - UINT32_MAX, !=, puse, rte_memory_order_relaxed);
> - }
> + while (cbi->use)
> + rte_pause();
> }
>
> static void
More information about the dev
mailing list