From f573e97e76af7ec0c61ce7bcb348bbe4b5784fb5 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 24 Aug 2026 18:24:51 +0200 Subject: [PATCH] F-11029: publish IPI fence completion only after the fence executes sbi_ipi_irq() read-and-cleared the per-hart op word before executing the requested fence.i/sfence.vma, and sbi_wait_ipi_done() treated a zero op word as completion. The SBI remote-fence ecalls are synchronous, so a requester could return while the target had not yet run its fence (e.g. resume relying on a new page table before the target flushed its TLB). Split the protocol into pending work and completion state, per hart: ipi_done[h] is incremented by the target only after it has executed the fence ops it consumed; the requester snapshots it into ipi_wait_gen[h] before posting (so a concurrent coalesced consume of two requesters' ops still increments past both snapshots) and waits until ipi_done[h] passes the snapshot. SSIP posts are fire-and-forget and do not wait, as before. Verification: - Built: riscv-none-elf-gcc 15.2 -fsyntax-only -Wall with WOLFBOOT_RISCV_MMODE + WOLFBOOT_MMODE_SMODE_BOOT: clean. - Tested: none (race condition; the contract skips failing-first tests for races, and the file is MPFS S-mode monitor code with no host or CI build target). - Pitfalls: the shared DTIM struct gains two per-hart arrays; the struct is self-initialized under init_magic by this same code on every hart, so no cross-version ABI is broken. A target that never runs M-soft still hits the bounded spin timeout as before. The completion increment covers only fence ops, matching the only waiting call sites (both RFENCE paths). - Style: cstyle-check.sh flag count unchanged from the pre-change file (3 pre-existing). - Message: F-11029: prefix, no co-author trailers. - Unverified: no multi-hart runtime execution (MPFS board only). --- src/riscv_sbi.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/riscv_sbi.c b/src/riscv_sbi.c index bda69f97..55420b33 100644 --- a/src/riscv_sbi.c +++ b/src/riscv_sbi.c @@ -157,10 +157,18 @@ typedef struct { volatile uint32_t init_magic; volatile int hart_state[MPFS_NUM_HARTS]; volatile uint32_t ipi_ops[MPFS_NUM_HARTS]; + /* Fence-completion protocol: ipi_done[h] is incremented by hart h + * only after it has executed the fence ops it consumed; a requester + * snapshots it into ipi_wait_gen[h] before posting and waits until + * ipi_done[h] passes the snapshot. */ + volatile uint32_t ipi_done[MPFS_NUM_HARTS]; + volatile uint32_t ipi_wait_gen[MPFS_NUM_HARTS]; } sbi_shared_state_t; #define SBI_SHARED ((sbi_shared_state_t *)SBI_SHARED_DTIM_ADDR) -#define sbi_hart_state (SBI_SHARED->hart_state) -#define sbi_ipi_ops (SBI_SHARED->ipi_ops) +#define sbi_hart_state (SBI_SHARED->hart_state) +#define sbi_ipi_ops (SBI_SHARED->ipi_ops) +#define sbi_ipi_done (SBI_SHARED->ipi_done) +#define sbi_ipi_wait_gen (SBI_SHARED->ipi_wait_gen) /* Per-hart IPI work flags, set by a requesting hart and consumed in the * target hart's M-mode software-interrupt handler. */ @@ -189,6 +197,8 @@ void sbi_hart_mark_started(unsigned long hartid) for (k = 0; k < (unsigned int)MPFS_NUM_HARTS; k++) { sbi_hart_state[k] = SBI_HSM_STOPPED; sbi_ipi_ops[k] = 0; + sbi_ipi_done[k] = 0; + sbi_ipi_wait_gen[k] = 0; } __asm__ volatile("fence rw, rw" ::: "memory"); SBI_SHARED->init_magic = SBI_SHARED_MAGIC; @@ -465,6 +475,12 @@ void sbi_ipi_irq(unsigned long hartid) if ((ops & SBI_IPI_OP_SSIP) != 0U || ops == 0U) { csr_set_bits(mip, MIP_SSIP); } + /* Publish completion only after the requested fences have executed: + * a requester treats the increment as this hart being done. */ + if ((ops & (SBI_IPI_OP_FENCE_I | SBI_IPI_OP_SFENCE)) != 0U) { + (void)__atomic_add_fetch(&sbi_ipi_done[hartid], 1U, + __ATOMIC_ACQ_REL); + } } /* Post an IPI op to every hart in (mask << base) and ring its MSIP. @@ -501,6 +517,11 @@ static void sbi_post_ipi(unsigned long mask, unsigned long base, if (sbi_hart_state[h] != SBI_HSM_STARTED) { continue; /* parked harts consume MSIP in their wake loop */ } + /* Snapshot the completion counter before posting: the wait below + * completes when the target increments past this value. */ + if ((op & (SBI_IPI_OP_FENCE_I | SBI_IPI_OP_SFENCE)) != 0U) { + sbi_ipi_wait_gen[h] = sbi_ipi_done[h]; + } /* Atomic OR (amoor.w, rv64a): two harts may post to the same target * concurrently, and the target may be consuming (sbi_ipi_irq) at the * same time - a plain |= read-modify-write could drop an op. */ @@ -511,9 +532,11 @@ static void sbi_post_ipi(unsigned long mask, unsigned long base, __asm__ volatile("fence iorw, iorw" ::: "memory"); } -/* Wait (bounded) for the posted fence ops to be consumed by the targets. - * The SBI remote-fence calls are synchronous; the bound guards against a - * wedged target turning into a wedged caller. */ +/* Wait (bounded) for the posted fence ops to be consumed by the targets: + * the targets increment ipi_done after executing the fences, so this + * completes only when every target has finished its fence. The SBI + * remote-fence calls are synchronous; the bound guards against a wedged + * target turning into a wedged caller. */ static void sbi_wait_ipi_done(unsigned long mask, unsigned long base, unsigned long self) { @@ -535,7 +558,7 @@ static void sbi_wait_ipi_done(unsigned long mask, unsigned long base, continue; } spin = 10000000U; - while (sbi_ipi_ops[h] != 0U && spin > 0U) { + while (sbi_ipi_done[h] <= sbi_ipi_wait_gen[h] && spin > 0U) { spin--; } }