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. Earlier expiries are covered by that check; the first later expiry queues another check. 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 17b9929785f668487d2ec851b736d588e025fd91..a4d33f55c4e5cb6052cea6bee4c0afa46372b5c5 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -583,6 +583,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
