[PATCH v7 2/2] drivers/perf: riscv-iommu: protect shared state with a raw spinlock
Zong Li <[email protected]>
| Newsgroups | org.infradead.lists.linux-riscv,dev.linux.lists.iommu,org.kernel.vger.linux-kernel,org.kernel.vger.linux-perf-users |
|---|---|
| Message-ID | <[email protected]> |
Events are bound to one CPU and the interrupt is affine to it, so the perf callbacks running with interrupts disabled would be enough to exclude the handler. PCI MSI/MSI-X on IMSIC breaks that: the irqchip sets IRQCHIP_MOVE_DEFERRED, so irq_set_affinity() reports success while only recording the request, and the move is applied in interrupt context upon the next device interrupt. Until then the interrupt is still routed to the CPU IMSIC picked initially, so the first overflow interrupt can run concurrently with the perf callbacks on the CPU the events are bound to. Take a raw spinlock, with interrupts disabled so that the handler can never interrupt a holder on the same CPU, rather than depending on that irqchip behaviour. It covers the state which is reachable from both sides: - IOCOUNTINH is read-modify-written by ->start()/->stop() and is saved and restored around the whole handler. - pmu->events[] is written by ->del() and read by the handler. - hw_perf_event::prev_count is updated by both. ->add() and ->del() call the unlocked __riscv_iommu_pmu_start() and __riscv_iommu_pmu_stop() so the lock is taken once per callback. Signed-off-by: Zong Li <[email protected]> --- drivers/perf/riscv_iommu_pmu.c | 66 ++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/drivers/perf/riscv_iommu_pmu.c b/drivers/perf/riscv_iommu_pmu.c index f6acd56f2f61..ee2f6d1fbece 100644 --- a/drivers/perf/riscv_iommu_pmu.c +++ b/drivers/perf/riscv_iommu_pmu.c @@ -101,6 +101,7 @@ struct riscv_iommu_pmu { u64 event_cntr_mask; struct perf_event *events[RISCV_IOMMU_HPM_COUNTER_NUM]; DECLARE_BITMAP(used_counters, RISCV_IOMMU_HPM_COUNTER_NUM); + raw_spinlock_t lock; }; #define to_riscv_iommu_pmu(p) (container_of(p, struct riscv_iommu_pmu, pmu)) @@ -485,7 +486,8 @@ static void riscv_iommu_pmu_update(struct perf_event *event) local64_add(delta, &event->count); } -static void riscv_iommu_pmu_start(struct perf_event *event, int flags) +/* Called with pmu->lock held */ +static void __riscv_iommu_pmu_start(struct perf_event *event, int flags) { struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; @@ -500,11 +502,22 @@ static void riscv_iommu_pmu_start(struct perf_event *event, int flags) riscv_iommu_pmu_set_period(event); riscv_iommu_pmu_set_event(pmu, hwc->idx, hwc->config); riscv_iommu_pmu_enable_counter(pmu, hwc->idx); +} + +static void riscv_iommu_pmu_start(struct perf_event *event, int flags) +{ + struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); + unsigned long irqflags; + + raw_spin_lock_irqsave(&pmu->lock, irqflags); + __riscv_iommu_pmu_start(event, flags); + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); perf_event_update_userpage(event); } -static void riscv_iommu_pmu_stop(struct perf_event *event, int flags) +/* Called with pmu->lock held */ +static void __riscv_iommu_pmu_stop(struct perf_event *event, int flags) { struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; @@ -521,13 +534,26 @@ static void riscv_iommu_pmu_stop(struct perf_event *event, int flags) hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; } +static void riscv_iommu_pmu_stop(struct perf_event *event, int flags) +{ + struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); + unsigned long irqflags; + + raw_spin_lock_irqsave(&pmu->lock, irqflags); + __riscv_iommu_pmu_stop(event, flags); + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); +} + static int riscv_iommu_pmu_add(struct perf_event *event, int flags) { struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; unsigned int num_counters = pmu->num_counters; + unsigned long irqflags; unsigned int idx; + raw_spin_lock_irqsave(&pmu->lock, irqflags); + /* Reserve index zero for iohpmcycles */ if (is_cycle_event(event->attr.config)) idx = RISCV_IOMMU_HPM_CYCLE_IDX; @@ -535,8 +561,10 @@ static int riscv_iommu_pmu_add(struct perf_event *event, int flags) idx = find_next_zero_bit(pmu->used_counters, num_counters, 1); /* All event counters or cycle counter are in use */ - if (idx == num_counters || pmu->events[idx]) + if (idx == num_counters || pmu->events[idx]) { + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); return -EAGAIN; + } set_bit(idx, pmu->used_counters); @@ -546,7 +574,9 @@ static int riscv_iommu_pmu_add(struct perf_event *event, int flags) local64_set(&hwc->prev_count, 0); if (flags & PERF_EF_START) - riscv_iommu_pmu_start(event, flags); + __riscv_iommu_pmu_start(event, flags); + + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); /* Propagate changes to the userspace mapping. */ perf_event_update_userpage(event); @@ -556,18 +586,26 @@ static int riscv_iommu_pmu_add(struct perf_event *event, int flags) static void riscv_iommu_pmu_read(struct perf_event *event) { + struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); + unsigned long irqflags; + + raw_spin_lock_irqsave(&pmu->lock, irqflags); riscv_iommu_pmu_update(event); + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); } static void riscv_iommu_pmu_del(struct perf_event *event, int flags) { struct riscv_iommu_pmu *pmu = to_riscv_iommu_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; + unsigned long irqflags; int idx = hwc->idx; - riscv_iommu_pmu_stop(event, PERF_EF_UPDATE); + raw_spin_lock_irqsave(&pmu->lock, irqflags); + __riscv_iommu_pmu_stop(event, PERF_EF_UPDATE); pmu->events[idx] = NULL; clear_bit(idx, pmu->used_counters); + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); perf_event_update_userpage(event); } @@ -635,12 +673,24 @@ static irqreturn_t riscv_iommu_pmu_irq_handler(int irq, void *dev_id) { struct riscv_iommu_pmu *pmu = (struct riscv_iommu_pmu *)dev_id; DECLARE_BITMAP(ovf_bitmap, BITS_PER_TYPE(u64)); + unsigned long irqflags; u32 ovf, idx, inhibit; - /* Check whether this interrupt is for PMU */ + /* + * Check whether this interrupt is for PMU. Done outside the lock so + * that a shared interrupt line is left alone as cheaply as possible. + */ if (!(readl_relaxed(pmu->reg + RISCV_IOMMU_REG_IPSR) & RISCV_IOMMU_IPSR_PMIP)) return IRQ_NONE; + /* + * Hold the lock across the whole sequence below. Stopping the + * counters, processing them and restoring the previous inhibit state + * has to be atomic against ->start()/->stop(), otherwise a counter + * enabled in between would be inhibited again by the restore. + */ + raw_spin_lock_irqsave(&pmu->lock, irqflags); + /* Process PMU IRQ */ inhibit = riscv_iommu_pmu_stop_all(pmu); @@ -672,6 +722,8 @@ static irqreturn_t riscv_iommu_pmu_irq_handler(int irq, void *dev_id) riscv_iommu_pmu_start_all(pmu, inhibit); + raw_spin_unlock_irqrestore(&pmu->lock, irqflags); + return IRQ_HANDLED; } @@ -735,6 +787,8 @@ static int riscv_iommu_pmu_probe(struct auxiliary_device *auxdev, iommu_pmu->reg = iommu_dev->reg; + raw_spin_lock_init(&iommu_pmu->lock); + /* * Counter number and width are hardware-implemented, detect them by * writing 1s and reading back which bits stuck. -- 2.43.7 _______________________________________________ linux-riscv mailing list [email protected] http://lists.infradead.org/mailman/listinfo/linux-riscv