Re: [PATCH v14 11/18] irqchip: Add RISC-V incoming MSI controller early driver

Björn Töpel
2024-02-22T14:15:27Z

Anup Patel <anup@brainfault.org> writes:

> On Thu, Feb 22, 2024 at 6:43 PM Björn Töpel <bjorn@kernel.org> wrote:
>>
>> Anup Patel <apatel@ventanamicro.com> writes:
>>
>> > diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c
>> > new file mode 100644
>> > index 000000000000..0c19ffb9ca3e
>> > --- /dev/null
>> > +++ b/drivers/irqchip/irq-riscv-imsic-state.c
>> > @@ -0,0 +1,870 @@
>>
>> [...]
>>
>> > +static void __imsic_local_sync(struct imsic_local_priv *lpriv)
>> > +{
>> > + struct imsic_local_config *mlocal;
>> > + struct imsic_vector *vec, *mvec;
>> > + int i;
>> > +
>> > + lockdep_assert_held(&lpriv->lock);
>> > +
>> > + /* This pairs with the barrier in __imsic_remote_sync(). */
>> > + smp_mb();
>>
>> I'm trying to figure out why this barrier is needed? All the updates are
>> done behind the spinlocks. If there're some ordering constraints that
>> I'm missing, please document them.
>>
>> > +
>> > + for_each_set_bit(i, lpriv->dirty_bitmap, imsic->global.nr_ids + 1) {
>> > + if (!i || i == IMSIC_IPI_ID)
>> > + goto skip;
>> > + vec = &lpriv->vectors[i];
>> > +
>> > + if (READ_ONCE(vec->enable))
>> > + __imsic_id_set_enable(i);
>> > + else
>> > + __imsic_id_clear_enable(i);
>> > +
>> > + /*
>> > + * If the ID was being moved to a new ID on some other CPU
>> > + * then we can get a MSI during the movement so check the
>> > + * ID pending bit and re-trigger the new ID on other CPU
>> > + * using MMIO write.
>> > + */
>> > + mvec = READ_ONCE(vec->move);
>> > + WRITE_ONCE(vec->move, NULL);
>>
>> mvec = xchg(&vec->move, NULL) ?
>
> The __imsic_local_sync() is called with spinlock held.

Yeah, this was a readability comment.

>> > + if (mvec && mvec != vec) {
>> > + if (__imsic_id_read_clear_pending(i)) {
>> > + mlocal = per_cpu_ptr(imsic->global.local, mvec->cpu);
>> > + writel_relaxed(mvec->local_id, mlocal->msi_va);
>> > + }
>> > +
>> > + imsic_vector_free(&lpriv->vectors[i]);
>> > + }
>> > +
>> > +skip:
>> > + bitmap_clear(lpriv->dirty_bitmap, i, 1);
>> > + }
>> > +}
>> > +
>> > +void imsic_local_sync_all(void)
>> > +{
>> > + struct imsic_local_priv *lpriv = this_cpu_ptr(imsic->lpriv);
>> > + unsigned long flags;
>> > +
>> > + raw_spin_lock_irqsave(&lpriv->lock, flags);
>> > + bitmap_fill(lpriv->dirty_bitmap, imsic->global.nr_ids + 1);
>> > + __imsic_local_sync(lpriv);
>> > + raw_spin_unlock_irqrestore(&lpriv->lock, flags);
>> > +}
>> > +
>> > +void imsic_local_delivery(bool enable)
>> > +{
>> > + if (enable) {
>> > + imsic_csr_write(IMSIC_EITHRESHOLD, IMSIC_ENABLE_EITHRESHOLD);
>> > + imsic_csr_write(IMSIC_EIDELIVERY, IMSIC_ENABLE_EIDELIVERY);
>> > + return;
>> > + }
>> > +
>> > + imsic_csr_write(IMSIC_EIDELIVERY, IMSIC_DISABLE_EIDELIVERY);
>> > + imsic_csr_write(IMSIC_EITHRESHOLD, IMSIC_DISABLE_EITHRESHOLD);
>> > +}
>> > +
>> > +#ifdef CONFIG_SMP
>> > +static void imsic_local_timer_callback(struct timer_list *timer)
>> > +{
>> > + struct imsic_local_priv *lpriv = this_cpu_ptr(imsic->lpriv);
>> > + unsigned long flags;
>> > +
>> > + raw_spin_lock_irqsave(&lpriv->lock, flags);
>> > + __imsic_local_sync(lpriv);
>> > + raw_spin_unlock_irqrestore(&lpriv->lock, flags);
>> > +}
>> > +
>> > +static void __imsic_remote_sync(struct imsic_local_priv *lpriv, unsigned int cpu)
>> > +{
>> > + lockdep_assert_held(&lpriv->lock);
>> > +
>> > + /*
>> > + * Ensure that changes to vector enable, vector move and
>> > + * dirty bitmap are visible to the target CPU.
>>
>> ...which case the spinlock(s) are enough, no?
>
> spinlocks are not fences.

They are indeed, and it would be hard to use them as locks otherwise
(acq/rel -- good ol' Documentation/memory-barriers.txt).

> If the timer wheel on the target cpu is already running and we don't
> have a fence here then the target cpu might not see the changes
> done by this cpu.

Remove the smp_mb() pair, the spinlocks are enough for this scenario!

>> > + *
>> > + * This pairs with the barrier in __imsic_local_sync().
>> > + */
>> > + smp_mb();
>> > +
>> > + /*
>> > + * We schedule a timer on the target CPU if the target CPU is not
>> > + * same as the current CPU. An offline CPU will unconditionally
>> > + * synchronize IDs through imsic_starting_cpu() when the
>> > + * CPU is brought up.
>> > + */
>> > + if (cpu_online(cpu)) {
>> > + if (cpu == smp_processor_id()) {
>> > + __imsic_local_sync(lpriv);
>> > + return;
>> > + }
>>
>> Maybe move this if-clause out from the cpu_online(), and only have
>> something like
>> if (cpu_online(cpu) && !timer_pending(&lpriv->timer)) { ... }
>> inside the CONFIG_SMP guard...
>>
>> > +
>> > + if (!timer_pending(&lpriv->timer)) {
>> > + lpriv->timer.expires = jiffies + 1;
>> > + add_timer_on(&lpriv->timer, cpu);
>> > + }
>> > + }
>> > +}
>> > +#else
>> > +static void __imsic_remote_sync(struct imsic_local_priv *lpriv, unsigned int cpu)
>> > +{
>> > + lockdep_assert_held(&lpriv->lock);
>> > + __imsic_local_sync(lpriv);
>> > +}
>> > +#endif
>>
>> ...where we can get rid of this special !SMP all together for this
>> function.
>
> I failed to understand what is wrong the current code.

Oh, nothing is wrong. Just trying to get rid of some ifdeffery.

>> > +void imsic_vector_mask(struct imsic_vector *vec)
>> > +{
>> > + struct imsic_local_priv *lpriv;
>> > +
>> > + lpriv = per_cpu_ptr(imsic->lpriv, vec->cpu);
>> > + if (WARN_ON_ONCE(&lpriv->vectors[vec->local_id] != vec))
>> > + return;
>> > +
>> > + /*
>> > + * This function is called through Linux irq subsystem with
>> > + * irqs disabled so no need to save/restore irq flags.
>> > + */
>> > +
>> > + raw_spin_lock(&lpriv->lock);
>> > +
>> > + vec->enable = false;
>>
>> Should have WRITE_ONCE to make the checkers happy.
>
> Okay, I will update.
>
>>
>> > + bitmap_set(lpriv->dirty_bitmap, vec->local_id, 1);
>> > + __imsic_remote_sync(lpriv, vec->cpu);
>> > +
>> > + raw_spin_unlock(&lpriv->lock);
>> > +}
>> > +
>> > +void imsic_vector_unmask(struct imsic_vector *vec)
>> > +{
>> > + struct imsic_local_priv *lpriv;
>> > +
>> > + lpriv = per_cpu_ptr(imsic->lpriv, vec->cpu);
>> > + if (WARN_ON_ONCE(&lpriv->vectors[vec->local_id] != vec))
>> > + return;
>> > +
>> > + /*
>> > + * This function is called through Linux irq subsystem with
>> > + * irqs disabled so no need to save/restore irq flags.
>> > + */
>> > +
>> > + raw_spin_lock(&lpriv->lock);
>> > +
>> > + vec->enable = true;
>>
>> Dito.
>
> Okay, I will update.
>
>>
>> > + bitmap_set(lpriv->dirty_bitmap, vec->local_id, 1);
>> > + __imsic_remote_sync(lpriv, vec->cpu);
>> > +
>> > + raw_spin_unlock(&lpriv->lock);
>> > +}
>> > +
>> > +static bool imsic_vector_move_update(struct imsic_local_priv *lpriv, struct imsic_vector *vec,
>> > + bool new_enable, struct imsic_vector *new_move)
>> > +{
>> > + unsigned long flags;
>> > + bool enabled;
>> > +
>> > + raw_spin_lock_irqsave(&lpriv->lock, flags);
>> > +
>> > + /* Update enable and move details */
>> > + enabled = READ_ONCE(vec->enable);
>> > + WRITE_ONCE(vec->enable, new_enable);
>>
>> Again, xchg() would be easier to read.
>
> why ? spinlock is not enough?

They're enough! Just readbaility/personal taste.

I'm running tests for this series now! Would be awesome to have the
series land for 6.9!

Cheers,
Björn