The PMU timer callback can race MTTCG execution and CSR writes while reading the event map and updating counters, OF and MIP.
Queue counter checks on the owning vCPU. The callback atomically sets pmu_timer_work_pending and queues work only if it was previously false. The vCPU clears the flag before checking counters and rebuilding the timer. Timer expirations before the flag is cleared are covered by that check; the first expiration afterward queues another check. Reviewed-by: Daniel Henrique Barboza <[email protected]> Signed-off-by: TANG Tiancheng <[email protected]> --- target/riscv/cpu.h | 1 + target/riscv/tcg/pmu.c | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 1ab6ae6937aeca524c4635eae6c000f4a6d1ac5a..abc9180e35dae9f713652038f3e677d520e2ef50 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -586,6 +586,7 @@ struct ArchCPU { QEMUTimer *pmu_timer; uint64_t pmu_timer_instret_snapshot; bool pmu_timer_stalled; + bool pmu_timer_work_pending; /* A bitmask of Available programmable counters */ uint32_t pmu_avail_ctrs; /* Mapping of events to counters */ diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c index 08298010b6d06f5792fa14ff81fe2f7a28c6476f..6286552a4ebf614df0252f84ddfadbc25d8d2258 100644 --- a/target/riscv/tcg/pmu.c +++ b/target/riscv/tcg/pmu.c @@ -663,15 +663,29 @@ void riscv_pmu_rebuild_timer(CPURISCVState *env) riscv_pmu_rebuild_timer_internal(env, false); } +static void riscv_pmu_timer_work(CPUState *cs, run_on_cpu_data data) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + + /* + * An expiry before this exchange is covered by the following counter + * check. The first expiry after it sets pmu_timer_work_pending and queues + * another check. + */ + qatomic_xchg(&cpu->pmu_timer_work_pending, false); + riscv_pmu_rebuild_timer_internal(&cpu->env, true); +} + /* Timer callback for instret and cycle counter overflow */ void riscv_pmu_timer_cb(void *priv) { RISCVCPU *cpu = priv; - riscv_pmu_rebuild_timer_internal(&cpu->env, true); + if (!qatomic_xchg(&cpu->pmu_timer_work_pending, true)) { + async_run_on_cpu(CPU(cpu), riscv_pmu_timer_work, RUN_ON_CPU_NULL); + } } - void riscv_pmu_init(RISCVCPU *cpu, Error **errp) { if (cpu->cfg.pmu_mask & (COUNTEREN_CY | COUNTEREN_TM | COUNTEREN_IR)) { -- 2.43.0
