mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] RISC-V: KVM: Order IMSIC VS-file write before releasing vsfile_lock
@ 2026-10-23 14:25 Oleksii Kurochko
  2026-11-13  8:00 ` Anup Patel
  0 siblings, 1 reply; 3+ messages in thread
From: Oleksii Kurochko @ 2026-10-23 14:25 UTC (permalink / raw)
  To: Anup Patel
  Cc: Atish Patra, Paul Walmsley, Palmer Dabbelt, Albert Ou,
	Alexandre Ghiti, kvm, kvm-riscv, linux-riscv, linux-kernel,
	Oleksii Kurochko

kvm_riscv_vcpu_aia_imsic_inject() writes the interrupt identity to the
SETIPNUM register of the vCPU's IMSIC VS-file while holding vsfile_lock
for reading. The lock keeps the VS-file from being switched under the
injector: kvm_riscv_vcpu_aia_imsic_update() and
kvm_riscv_vcpu_aia_imsic_release() take it for writing to move the vCPU
to a new VS-file or to the SW-file, then read and clear the old VS-file
and free its HGEI line.

On RISC-V, writel() is followed only by mmiowb_set_pending(). The
trailing "fence o,w" is deferred to spin_unlock() by the mmiowb()
tracking, and rwlocks don't take part in that tracking:
queued_read_unlock() issues just "fence rw,w" before the AMO, which
doesn't order device output. So the SETIPNUM write can still be in
flight after another hart has observed vsfile_lock being released. If
the old VS-file is read and cleared before the write lands, the
interrupt is lost. If the HGEI line has already been handed out
again, the interrupt ends up in another vCPU's VS-file.

Add an explicit RISCV_FENCE(o, w) after the write to the VS-file, which
is what spin_unlock() would issue via mmiowb().

Fixes: db8b7e97d613 ("RISC-V: KVM: Add in-kernel virtualization of AIA IMSIC")
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Why RISCV_FENCE(o, w) and not mmiowb():

- Since the mmiowb() rework in v5.2 (e.g. commit 60ca1e5a200c
  ("mmiowb: Hook up mmiowb helpers to spinlocks and generic I/O
  accessors")), mmiowb() is an implementation detail of the spinlock
  code. Explicit invocations were removed from drivers by commit
  fb24ea52f78e ("drivers: Remove explicit invocations of mmiowb()") and
  from the documentation by commit 915530396c78 ("Documentation: Kill
  all references to mmiowb()"), so adding a new direct caller would go
  against that.

- RISCV_FENCE(o, w) states exactly the ordering needed here (device
  output before the store that releases the lock), which is fine for
  arch-specific code, and it is the same instruction mmiowb() expands to
  on RISC-V. wmb() ("fence ow,ow") would also work but is stronger than
  required.

Scope: this only orders the write before the lock release. Whether a
write that is already in global memory order is guaranteed to have
taken effect in the old VS-file before kvm_riscv_vcpu_aia_imsic_update()
or kvm_riscv_vcpu_aia_imsic_release() reads and clears it is a separate
question, see https://github.com/riscv/riscv-aia/issues/172. If it
isn't, these paths will also need to drain the old VS-file, and such a
drain depends on the ordering added here.
---
 arch/riscv/kvm/aia_imsic.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c
index f17dc559a709..807bb73a129a 100644
--- a/arch/riscv/kvm/aia_imsic.c
+++ b/arch/riscv/kvm/aia_imsic.c
@@ -1056,6 +1056,13 @@ int kvm_riscv_vcpu_aia_imsic_inject(struct kvm_vcpu *vcpu,
 
 	if (imsic->vsfile_cpu >= 0) {
 		writel(iid, imsic->vsfile_va + IMSIC_MMIO_SETIPNUM_LE);
+		/*
+		 * rwlocks don't participate in mmiowb() tracking, so order
+		 * the write to the VS-file before releasing vsfile_lock,
+		 * i.e. before the vCPU can be moved off this VS-file and
+		 * the VS-file read and cleared.
+		 */
+		RISCV_FENCE(o, w);
 	} else {
 		eix = &imsic->swfile->eix[iid / BITS_PER_TYPE(u64)];
 		set_bit(iid & (BITS_PER_TYPE(u64) - 1), eix->eip);

base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
-- 
2.56.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] RISC-V: KVM: Order IMSIC VS-file write before releasing vsfile_lock
  2026-10-23 14:25 [PATCH] RISC-V: KVM: Order IMSIC VS-file write before releasing vsfile_lock Oleksii Kurochko
@ 2026-11-13  8:00 ` Anup Patel
  2026-11-13 18:19   ` Oleksii Kurochko
  0 siblings, 1 reply; 3+ messages in thread
From: Anup Patel @ 2026-11-13  8:00 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
	Albert Ou, Alexandre Ghiti, kvm, kvm-riscv, linux-riscv,
	linux-kernel

On Fri, Oct 23, 2026 at 7:56 PM Oleksii Kurochko
<oleksii.kurochko@gmail.com> wrote:
>
> kvm_riscv_vcpu_aia_imsic_inject() writes the interrupt identity to the
> SETIPNUM register of the vCPU's IMSIC VS-file while holding vsfile_lock
> for reading. The lock keeps the VS-file from being switched under the
> injector: kvm_riscv_vcpu_aia_imsic_update() and
> kvm_riscv_vcpu_aia_imsic_release() take it for writing to move the vCPU
> to a new VS-file or to the SW-file, then read and clear the old VS-file
> and free its HGEI line.
>
> On RISC-V, writel() is followed only by mmiowb_set_pending(). The
> trailing "fence o,w" is deferred to spin_unlock() by the mmiowb()
> tracking, and rwlocks don't take part in that tracking:
> queued_read_unlock() issues just "fence rw,w" before the AMO, which
> doesn't order device output. So the SETIPNUM write can still be in
> flight after another hart has observed vsfile_lock being released. If
> the old VS-file is read and cleared before the write lands, the
> interrupt is lost. If the HGEI line has already been handed out
> again, the interrupt ends up in another vCPU's VS-file.
>
> Add an explicit RISCV_FENCE(o, w) after the write to the VS-file, which
> is what spin_unlock() would issue via mmiowb().
>
> Fixes: db8b7e97d613 ("RISC-V: KVM: Add in-kernel virtualization of AIA IMSIC")
> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
> ---
> Why RISCV_FENCE(o, w) and not mmiowb():
>
> - Since the mmiowb() rework in v5.2 (e.g. commit 60ca1e5a200c
>   ("mmiowb: Hook up mmiowb helpers to spinlocks and generic I/O
>   accessors")), mmiowb() is an implementation detail of the spinlock
>   code. Explicit invocations were removed from drivers by commit
>   fb24ea52f78e ("drivers: Remove explicit invocations of mmiowb()") and
>   from the documentation by commit 915530396c78 ("Documentation: Kill
>   all references to mmiowb()"), so adding a new direct caller would go
>   against that.
>
> - RISCV_FENCE(o, w) states exactly the ordering needed here (device
>   output before the store that releases the lock), which is fine for
>   arch-specific code, and it is the same instruction mmiowb() expands to
>   on RISC-V. wmb() ("fence ow,ow") would also work but is stronger than
>   required.
>
> Scope: this only orders the write before the lock release. Whether a
> write that is already in global memory order is guaranteed to have
> taken effect in the old VS-file before kvm_riscv_vcpu_aia_imsic_update()
> or kvm_riscv_vcpu_aia_imsic_release() reads and clears it is a separate
> question, see https://github.com/riscv/riscv-aia/issues/172. If it
> isn't, these paths will also need to drain the old VS-file, and such a
> drain depends on the ordering added here.
> ---
>  arch/riscv/kvm/aia_imsic.c | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c
> index f17dc559a709..807bb73a129a 100644
> --- a/arch/riscv/kvm/aia_imsic.c
> +++ b/arch/riscv/kvm/aia_imsic.c
> @@ -1056,6 +1056,13 @@ int kvm_riscv_vcpu_aia_imsic_inject(struct kvm_vcpu *vcpu,
>
>         if (imsic->vsfile_cpu >= 0) {
>                 writel(iid, imsic->vsfile_va + IMSIC_MMIO_SETIPNUM_LE);
> +               /*
> +                * rwlocks don't participate in mmiowb() tracking, so order
> +                * the write to the VS-file before releasing vsfile_lock,
> +                * i.e. before the vCPU can be moved off this VS-file and
> +                * the VS-file read and cleared.
> +                */
> +               RISCV_FENCE(o, w);

Another fence after every writel() is a big hammer in the path.

If the intention is to have correct contents when VS-file is read then
better place to add a fence is at the start of imsic_vsfile_local_read().

>         } else {
>                 eix = &imsic->swfile->eix[iid / BITS_PER_TYPE(u64)];
>                 set_bit(iid & (BITS_PER_TYPE(u64) - 1), eix->eip);
>
> base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
> --
> 2.56.0
>
>
> --
> kvm-riscv mailing list
> kvm-riscv@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/kvm-riscv

Regards,
Anup

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] RISC-V: KVM: Order IMSIC VS-file write before releasing vsfile_lock
  2026-11-13  8:00 ` Anup Patel
@ 2026-11-13 18:19   ` Oleksii Kurochko
  0 siblings, 0 replies; 3+ messages in thread
From: Oleksii Kurochko @ 2026-11-13 18:19 UTC (permalink / raw)
  To: Anup Patel
  Cc: Anup Patel, Atish Patra, Paul Walmsley, Palmer Dabbelt,
	Albert Ou, Alexandre Ghiti, kvm, kvm-riscv, linux-riscv,
	linux-kernel



On 11/13/26 9:00 AM, Anup Patel wrote:
> On Fri, Oct 23, 2026 at 7:56 PM Oleksii Kurochko
> <oleksii.kurochko@gmail.com> wrote:
>>
>> kvm_riscv_vcpu_aia_imsic_inject() writes the interrupt identity to the
>> SETIPNUM register of the vCPU's IMSIC VS-file while holding vsfile_lock
>> for reading. The lock keeps the VS-file from being switched under the
>> injector: kvm_riscv_vcpu_aia_imsic_update() and
>> kvm_riscv_vcpu_aia_imsic_release() take it for writing to move the vCPU
>> to a new VS-file or to the SW-file, then read and clear the old VS-file
>> and free its HGEI line.
>>
>> On RISC-V, writel() is followed only by mmiowb_set_pending(). The
>> trailing "fence o,w" is deferred to spin_unlock() by the mmiowb()
>> tracking, and rwlocks don't take part in that tracking:
>> queued_read_unlock() issues just "fence rw,w" before the AMO, which
>> doesn't order device output. So the SETIPNUM write can still be in
>> flight after another hart has observed vsfile_lock being released. If
>> the old VS-file is read and cleared before the write lands, the
>> interrupt is lost. If the HGEI line has already been handed out
>> again, the interrupt ends up in another vCPU's VS-file.
>>
>> Add an explicit RISCV_FENCE(o, w) after the write to the VS-file, which
>> is what spin_unlock() would issue via mmiowb().
>>
>> Fixes: db8b7e97d613 ("RISC-V: KVM: Add in-kernel virtualization of AIA IMSIC")
>> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
>> ---
>> Why RISCV_FENCE(o, w) and not mmiowb():
>>
>> - Since the mmiowb() rework in v5.2 (e.g. commit 60ca1e5a200c
>>    ("mmiowb: Hook up mmiowb helpers to spinlocks and generic I/O
>>    accessors")), mmiowb() is an implementation detail of the spinlock
>>    code. Explicit invocations were removed from drivers by commit
>>    fb24ea52f78e ("drivers: Remove explicit invocations of mmiowb()") and
>>    from the documentation by commit 915530396c78 ("Documentation: Kill
>>    all references to mmiowb()"), so adding a new direct caller would go
>>    against that.
>>
>> - RISCV_FENCE(o, w) states exactly the ordering needed here (device
>>    output before the store that releases the lock), which is fine for
>>    arch-specific code, and it is the same instruction mmiowb() expands to
>>    on RISC-V. wmb() ("fence ow,ow") would also work but is stronger than
>>    required.
>>
>> Scope: this only orders the write before the lock release. Whether a
>> write that is already in global memory order is guaranteed to have
>> taken effect in the old VS-file before kvm_riscv_vcpu_aia_imsic_update()
>> or kvm_riscv_vcpu_aia_imsic_release() reads and clears it is a separate
>> question, see https://github.com/riscv/riscv-aia/issues/172. If it
>> isn't, these paths will also need to drain the old VS-file, and such a
>> drain depends on the ordering added here.
>> ---
>>   arch/riscv/kvm/aia_imsic.c | 7 +++++++
>>   1 file changed, 7 insertions(+)
>>
>> diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c
>> index f17dc559a709..807bb73a129a 100644
>> --- a/arch/riscv/kvm/aia_imsic.c
>> +++ b/arch/riscv/kvm/aia_imsic.c
>> @@ -1056,6 +1056,13 @@ int kvm_riscv_vcpu_aia_imsic_inject(struct kvm_vcpu *vcpu,
>>
>>          if (imsic->vsfile_cpu >= 0) {
>>                  writel(iid, imsic->vsfile_va + IMSIC_MMIO_SETIPNUM_LE);
>> +               /*
>> +                * rwlocks don't participate in mmiowb() tracking, so order
>> +                * the write to the VS-file before releasing vsfile_lock,
>> +                * i.e. before the vCPU can be moved off this VS-file and
>> +                * the VS-file read and cleared.
>> +                */
>> +               RISCV_FENCE(o, w);
> 
> Another fence after every writel() is a big hammer in the path.
> 
> If the intention is to have correct contents when VS-file is read then
> better place to add a fence is at the start of imsic_vsfile_local_read().
Thanks for the review.

I don't think a fence in imsic_vsfile_local_read() helps here. The
problem is on the writer's side, and imsic_vsfile_local_read() runs on
a different hart.

The SETIPNUM write is done by whichever hart calls
kvm_riscv_vcpu_aia_imsic_inject(), which can be any CPU (irqfd,
ioctl, ...). imsic_vsfile_local_read() runs on the old vsfile_cpu, via
the IPI from imsic_vsfile_read(). A fence only orders the accesses of
the hart that executes it, so a fence on the reading hart can't make
another hart's MMIO store land.

Without the fence, this can happen:

   hart A (inject)            hart B (update/release)    hart C (old 
vsfile_cpu)
   read_lock()
   writel(SETIPNUM)
     (store still in flight)
   read_unlock()
                              write_lock()
                              switch VS-file
                              write_unlock()
                              IPI ------------------->
                                                        fence  <-proposed
                                                        read+clear VSfile
                              free HGEI
   SETIPNUM lands: the interrupt
   is lost, or ends up in another
   vCPU's VS-file if the HGEI was
   already reused

Hart A's unlock can become visible before its SETIPNUM write has
reached the IMSIC, because read_unlock() doesn't order device output.
Nothing that hart B or hart C does afterwards can wait for that write.
Only hart A can hold back its unlock until the write is ordered, and
that is what the fence o,w after writel() does.

With the fence:

   hart A (inject)            hart B (update/release)    hart C (old 
vsfile_cpu)
   read_lock()
   writel(SETIPNUM)
   fence o,w
   read_unlock()
                              write_lock()
                              switch VS-file
                              write_unlock()
                              IPI ------------------->
                                                       read+clear VS-file
                              free HGEI

The SETIPNUM write is now ordered before the unlock, so it is also
ordered before everything hart B does after taking the lock, and,
through the IPI, before hart C reads the old VS-file. It can no longer
arrive after the read or after the HGEI is freed.

About the cost: this is the same fence that every writel() under a
spinlock already gets. On RISC-V, writel() only sets mmiowb pending and
spin_unlock() then issues "fence o,w". We need it explicitly here only
because vsfile_lock is an rwlock. It runs once per injected MSI, and
only when the vCPU has a VS-file.

The only way I see to keep it out of the inject path is for
update/release to IPI every CPU that might be injecting and run
"fence o,w" there before the old VS-file is read. That is much heavier
and moves the cost to vCPU migration, so I'd rather keep the fence in
inject. Let me know if you'd still prefer the other way.

One more thing, not directly connected to this patch: could you please
also take a look at https://github.com/riscv/riscv-aia/issues/172?
The fence makes sure that the SETIPNUM write is ordered before hart C
reads the old VS-file, but hart C reads it through CSRs
(vsiselect/vsireg), not through memory. As far as I can tell, the spec
doesn't say whether a write that is already in global memory order is
guaranteed to be visible in eip at that point. If it isn't,
update/release will also need to drain the old VS-file before reading
it. Such a drain would build on this fence rather than replace it, since
it only works if the write is ordered before the unlock.

~ Oleksii

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-13 18:19 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23 14:25 [PATCH] RISC-V: KVM: Order IMSIC VS-file write before releasing vsfile_lock Oleksii Kurochko
2026-11-13  8:00 ` Anup Patel
2026-11-13 18:19   ` Oleksii Kurochko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®