Linux virtualization list
 help / color / mirror / Atom feed
* Re: [PATCH] vsock/virtio: fix vsockmon info leak in non-linear tap, copy
From: Arseniy Krasnov @ 2026-05-08 16:17 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: Paolo Abeni, Bobby Eshleman, netdev, linux-kernel,
	Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Yiqi Sun, kvm,
	virtualization
In-Reply-To: <af4GIeSEQ_65V5C2@sgarzare-redhat>



On 08/05/2026 18:50, Stefano Garzarella wrote:
> On Thu, May 07, 2026 at 11:03:57PM +0300, Arseniy Krasnov wrote:
>>> CCing Arseniy and Bobby.
>>
>> Thanks!
>>
>>>
>>> On Tue, May 05, 2026 at 12:26:21PM +0200, Paolo Abeni wrote:
>>>> On 4/30/26 9:11 AM, Yiqi Sun wrote:
>>>>> vsockmon mirrors packets through virtio_transport_build_skb(), which
>>>>> builds a new skb and copies the payload into it. For non-linear skbs,
>>>>> this goes through virtio_transport_copy_nonlinear_skb().
>>>>>
>>>>> Helper manually initializes a iov_iter, but leaves iov_iter.count unset.
>>>>> As a result, skb_copy_datagram_iter() sees zero writable bytes
>>>>> in the destination iterator and copies no payload data.
>>>>>
>>>>> This becomes an info leak because virtio_transport_build_skb() has
>>>>> already reserved payload_len bytes in the new skb with skb_put(). The
>>>>> skb is then returned to the tap path with that payload area still
>>>>> uninitialized, so userspace reading from a vsockmon device can observe
>>>>> heap contents and potentially kernel address.
>>>>>
>>>>> Fix it by initializing iov_iter.count to the number of bytes to copy.
>>>>>
>>>>> Fixes: 4b0bf10eb077 ("vsock/virtio: non-linear skb handling for tap")
>>>>> Signed-off-by: Yiqi Sun <sunyiqixm@gmail.com>
>>>>> ---
>>>>>  net/vmw_vsock/virtio_transport_common.c | 2 +-
>>>>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
>>>>> index 416d533f493d..6b26ee57ccab 100644
>>>>> --- a/net/vmw_vsock/virtio_transport_common.c
>>>>> +++ b/net/vmw_vsock/virtio_transport_common.c
>>>>> @@ -152,7 +152,7 @@ static void virtio_transport_copy_nonlinear_skb(const struct sk_buff *skb,
>>>>>      iov_iter.nr_segs = 1;
>>>>>
>>>>>      to_copy = min_t(size_t, len, skb->len);
>>>>> -
>>>>> +    iov_iter.count = to_copy;
>>>>>      skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)->offset,
>>>>>                     &iov_iter, to_copy);
>>>>
>>>> @Stefano, @Stefan, the patch LGTM, but sashiko pointed out to a
>>>> pre-existing issue you should probably want to address:
>>>>
>>>>>      to_copy = min_t(size_t, len, skb->len);
>>>> Does this length calculation account for the offset when a packet is
>>>> split across multiple transmissions?
>>>> If a packet is requeued, VIRTIO_VSOCK_SKB_CB(skb)->offset is increased,
>>>> but to_copy still evaluates to the full length of the skb.
>>>
>>> Yep, I just checked and vhost-vsock is the only place where we call
>>> virtio_transport_deliver_tap_pkt() wiht an offset != 0, but I agree that
>>> we should also fix it.
>>
>> Yes, looks like the only place where offset could be non zero is 'vhost_transport_do_send_pkt()'.
>> And we set valid length in header every attempt to send it:
>>
>>                /* Set the correct length in the header */
>>                hdr->len = cpu_to_le32(payload_len);
>>
>> In all other places we call 'virtio_transport_deliver_tap_pkt()' with offset == 0. And thus
>> skb->len == hdr->len.
>>
>> So for me looks ok. E.g. len in header is actual data.
>>
>>>
>>> Looking better in net/vmw_vsock/virtio_transport_common.c I think this
>>> is a regression, indeed we have this comment in
>>> virtio_transport_build_skb():
>>>
>>>     /* A packet could be split to fit the RX buffer, so we can retrieve
>>>      * the payload length from the header and the buffer pointer taking
>>>      * care of the offset in the original packet.
>>>      */
>>>     pkt_hdr = virtio_vsock_hdr(pkt);
>>>
>>> Before commit 71dc9ec9ac7d ("virtio/vsock: replace virtio_vsock_pkt with
>>> sk_buff") we read the payload lenght from the header that is always set
>>> to the right value before delivering the packet to the tap.
>>>
>>> From that commit, we don't to consider the offset anymore since we
>>> started to use `len` from the skb, so IMO we should go back to what we
>>> did before it, I mean:
>>>
>>>     payload_len = le32_to_cpu(pkt->hdr.len);
>>>
>>> @Bobby do you remember why we did that change? Or if you see any issue
>>> going back to what we did initially?
>>>
>>>
>>> Also IMO we should avoid to set all the iov_iter fields by hand and
>>> start to use iov_iter_kvec(). Plus, we can just use
>>> skb_copy_datagram_iter() in any case, like we already do in vhost-vsock,
>>> since it already handles linear vs non linear.
>>>
>>> At the end I mean something like this:
>>>
>>> @@ -171,7 +150,7 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
>>>       * care of the offset in the original packet.
>>>       */
>>>      pkt_hdr = virtio_vsock_hdr(pkt);
>>> -    payload_len = pkt->len;
>>> +    payload_len = le32_to_cpu(pkt_hdr->len);
>>>
>>>      skb = alloc_skb(sizeof(*hdr) + sizeof(*pkt_hdr) + payload_len,
>>>              GFP_ATOMIC);
>>> @@ -214,13 +193,17 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
>>>      skb_put_data(skb, pkt_hdr, sizeof(*pkt_hdr));
>>>
>>>      if (payload_len) {
>>> -        if (skb_is_nonlinear(pkt)) {
>>> -            void *data = skb_put(skb, payload_len);
>>> +        struct iov_iter iov_iter;
>>> +        struct kvec kvec;
>>> +        void *data = skb_put(skb, payload_len);
>>>
>>> -            virtio_transport_copy_nonlinear_skb(pkt, data, payload_len);
>>> -        } else {
>>> -            skb_put_data(skb, pkt->data, payload_len);
>>> -        }
>>> +        kvec.iov_base = data;
>>> +        kvec.iov_len = payload_len;
>>> +        iov_iter_kvec(&iov_iter, READ, &kvec, 1, payload_len);
>>> +
>>> +        skb_copy_datagram_iter(pkt,
>>> +                       VIRTIO_VSOCK_SKB_CB(pkt)->offset,
>>> +                       &iov_iter, payload_len);
>>>      }
>>>
>>>      return skb;
>>>
>>> And removing virtio_transport_copy_nonlinear_skb().
>>
>> Yes, this looks shorter and better.
> 
> Thanks for confirming, I'll send a series soon and CC you.
> Please review it :-)

Sure, thanks!

> 
> Thanks,
> Stefano
> 


^ permalink raw reply

* [PATCH net 0/2] vsock/virtio: fix vsockmon tap skb construction
From: Stefano Garzarella @ 2026-05-08 16:44 UTC (permalink / raw)
  To: netdev
  Cc: Yiqi Sun, Stefano Garzarella, linux-kernel, Xuan Zhuo,
	Michael S. Tsirkin, Stefan Hajnoczi, kvm, Simon Horman,
	Bobby Eshleman, Jason Wang, Jakub Kicinski, David S. Miller,
	virtualization, Eric Dumazet, Paolo Abeni, Arseniy Krasnov,
	Eugenio Pérez, Bobby Eshleman

While reviewing the patch posted by Yiqi Sun [1] to fix an issue in
virtio_transport_build_skb(), I discovered another issue related to
the offset and length of the payload to be copied in the new skb.
This was introduced when we did the skb conversion, and fixed by
patch 1.

Patch 2 fixes the issue found by Yiqi Sun in a different way: using
iov_iter_kvec() to properly initialize all the iov_iter fields and
removing the linear vs non-linear split like we alredy do in
vhost-vsock.

It could have been a single patch, but since there were two affected
commits, I decided to keep the fixes separate.

[1] https://lore.kernel.org/netdev/20260430071110.380509-1-sunyiqixm@gmail.com/

Stefano Garzarella (2):
  vsock/virtio: fix length and offset in tap skb for split packets
  vsock/virtio: fix empty payload in tap skb for non-linear buffers

 net/vmw_vsock/virtio_transport_common.c | 47 +++++++++----------------
 1 file changed, 16 insertions(+), 31 deletions(-)

-- 
2.54.0


^ permalink raw reply

* [PATCH net 1/2] vsock/virtio: fix length and offset in tap skb for split packets
From: Stefano Garzarella @ 2026-05-08 16:44 UTC (permalink / raw)
  To: netdev
  Cc: Yiqi Sun, Stefano Garzarella, linux-kernel, Xuan Zhuo,
	Michael S. Tsirkin, Stefan Hajnoczi, kvm, Simon Horman,
	Bobby Eshleman, Jason Wang, Jakub Kicinski, David S. Miller,
	virtualization, Eric Dumazet, Paolo Abeni, Arseniy Krasnov,
	Eugenio Pérez, Bobby Eshleman
In-Reply-To: <20260508164411.261440-1-sgarzare@redhat.com>

From: Stefano Garzarella <sgarzare@redhat.com>

virtio_transport_build_skb() builds a new skb to be delivered to the
vsockmon tap device. To build the new skb, it uses the original skb
data length as payload length, but as the comment notes, the original
packet stored in the skb may have been split in multiple packets, so we
need to use the length in the header, which is correctly updated before
the packet is delivered to the tap, and the offset for the data.

This was also similar to what we did before commit 71dc9ec9ac7d
("virtio/vsock: replace virtio_vsock_pkt with sk_buff") where we probably
missed something during the skb conversion.

Also update the comment above, which was left stale by the skb
conversion and still mentioned a buffer pointer that no longer exists.

Fixes: 71dc9ec9ac7d ("virtio/vsock: replace virtio_vsock_pkt with sk_buff")
Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
---
 net/vmw_vsock/virtio_transport_common.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 9b8014516f4f..a678d5d75704 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -166,12 +166,12 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
 	struct sk_buff *skb;
 	size_t payload_len;
 
-	/* A packet could be split to fit the RX buffer, so we can retrieve
-	 * the payload length from the header and the buffer pointer taking
-	 * care of the offset in the original packet.
+	/* A packet could be split to fit the RX buffer, so we use
+	 * the payload length from the header, which has been updated
+	 * by the sender to reflect the fragment size.
 	 */
 	pkt_hdr = virtio_vsock_hdr(pkt);
-	payload_len = pkt->len;
+	payload_len = le32_to_cpu(pkt_hdr->len);
 
 	skb = alloc_skb(sizeof(*hdr) + sizeof(*pkt_hdr) + payload_len,
 			GFP_ATOMIC);
@@ -219,7 +219,8 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
 
 			virtio_transport_copy_nonlinear_skb(pkt, data, payload_len);
 		} else {
-			skb_put_data(skb, pkt->data, payload_len);
+			skb_put_data(skb, pkt->data + VIRTIO_VSOCK_SKB_CB(pkt)->offset,
+				     payload_len);
 		}
 	}
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH net 2/2] vsock/virtio: fix empty payload in tap skb for non-linear buffers
From: Stefano Garzarella @ 2026-05-08 16:44 UTC (permalink / raw)
  To: netdev
  Cc: Yiqi Sun, Stefano Garzarella, linux-kernel, Xuan Zhuo,
	Michael S. Tsirkin, Stefan Hajnoczi, kvm, Simon Horman,
	Bobby Eshleman, Jason Wang, Jakub Kicinski, David S. Miller,
	virtualization, Eric Dumazet, Paolo Abeni, Arseniy Krasnov,
	Eugenio Pérez, Bobby Eshleman
In-Reply-To: <20260508164411.261440-1-sgarzare@redhat.com>

From: Stefano Garzarella <sgarzare@redhat.com>

For non-linear skbs, virtio_transport_build_skb() goes through
virtio_transport_copy_nonlinear_skb() to copy the original payload
in the new skb to be delivered to the vsockmon tap device.
This manually initializes an iov_iter but does not set iov_iter.count.
Since the iov_iter is zero-initialized, the copy length is zero and no
payload is actually copied to the monitor interface, leaving data
un-initialized.

Fix this by removing the linear vs non-linear split and using
skb_copy_datagram_iter() with iov_iter_kvec() for all cases, as
vhost-vsock already does. This handles both linear and non-linear skbs,
properly initializes the iov_iter, and removes the now unused
virtio_transport_copy_nonlinear_skb().

While touching this code, let's also check the return value of
skb_copy_datagram_iter(), even though it's unlikely to fail.

Fixes: 4b0bf10eb077 ("vsock/virtio: non-linear skb handling for tap")
Reported-by: Yiqi Sun <sunyiqixm@gmail.com>
Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
---
 net/vmw_vsock/virtio_transport_common.c | 40 ++++++++-----------------
 1 file changed, 12 insertions(+), 28 deletions(-)

diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index a678d5d75704..989cc252d3d3 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -136,27 +136,6 @@ static void virtio_transport_init_hdr(struct sk_buff *skb,
 	hdr->fwd_cnt	= cpu_to_le32(0);
 }
 
-static void virtio_transport_copy_nonlinear_skb(const struct sk_buff *skb,
-						void *dst,
-						size_t len)
-{
-	struct iov_iter iov_iter = { 0 };
-	struct kvec kvec;
-	size_t to_copy;
-
-	kvec.iov_base = dst;
-	kvec.iov_len = len;
-
-	iov_iter.iter_type = ITER_KVEC;
-	iov_iter.kvec = &kvec;
-	iov_iter.nr_segs = 1;
-
-	to_copy = min_t(size_t, len, skb->len);
-
-	skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)->offset,
-			       &iov_iter, to_copy);
-}
-
 /* Packet capture */
 static struct sk_buff *virtio_transport_build_skb(void *opaque)
 {
@@ -214,13 +193,18 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
 	skb_put_data(skb, pkt_hdr, sizeof(*pkt_hdr));
 
 	if (payload_len) {
-		if (skb_is_nonlinear(pkt)) {
-			void *data = skb_put(skb, payload_len);
-
-			virtio_transport_copy_nonlinear_skb(pkt, data, payload_len);
-		} else {
-			skb_put_data(skb, pkt->data + VIRTIO_VSOCK_SKB_CB(pkt)->offset,
-				     payload_len);
+		struct iov_iter iov_iter;
+		struct kvec kvec;
+		void *data = skb_put(skb, payload_len);
+
+		kvec.iov_base = data;
+		kvec.iov_len = payload_len;
+		iov_iter_kvec(&iov_iter, ITER_DEST, &kvec, 1, payload_len);
+
+		if (skb_copy_datagram_iter(pkt, VIRTIO_VSOCK_SKB_CB(pkt)->offset,
+					   &iov_iter, payload_len)) {
+			kfree_skb(skb);
+			return NULL;
 		}
 	}
 
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH] vsock/virtio: fix vsockmon info leak in non-linear tap copy
From: Stefano Garzarella @ 2026-05-08 16:47 UTC (permalink / raw)
  To: Yiqi Sun
  Cc: kvm, virtualization, netdev, linux-kernel, stefanha, mst,
	jasowang, xuanzhuo, eperezma, davem, edumazet, kuba, pabeni,
	horms
In-Reply-To: <20260430071110.380509-1-sunyiqixm@gmail.com>

On Thu, Apr 30, 2026 at 03:11:10PM +0800, Yiqi Sun wrote:
>vsockmon mirrors packets through virtio_transport_build_skb(), which
>builds a new skb and copies the payload into it. For non-linear skbs,
>this goes through virtio_transport_copy_nonlinear_skb().
>
>Helper manually initializes a iov_iter, but leaves iov_iter.count unset.
>As a result, skb_copy_datagram_iter() sees zero writable bytes
>in the destination iterator and copies no payload data.
>
>This becomes an info leak because virtio_transport_build_skb() has
>already reserved payload_len bytes in the new skb with skb_put(). The
>skb is then returned to the tap path with that payload area still
>uninitialized, so userspace reading from a vsockmon device can observe
>heap contents and potentially kernel address.
>
>Fix it by initializing iov_iter.count to the number of bytes to copy.
>
>Fixes: 4b0bf10eb077 ("vsock/virtio: non-linear skb handling for tap")
>Signed-off-by: Yiqi Sun <sunyiqixm@gmail.com>
>---
> net/vmw_vsock/virtio_transport_common.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)

Hi Yiqi Sun, thanks for this patch, but as I mentioned in the sub-thread 
I found another issue and also another way to fix this, please can you 
test the series I just sent: https://lore.kernel.org/netdev/20260508164411.261440-1-sgarzare@redhat.com/

Thanks,
Stefano

>
>diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
>index 416d533f493d..6b26ee57ccab 100644
>--- a/net/vmw_vsock/virtio_transport_common.c
>+++ b/net/vmw_vsock/virtio_transport_common.c
>@@ -152,7 +152,7 @@ static void virtio_transport_copy_nonlinear_skb(const struct sk_buff *skb,
> 	iov_iter.nr_segs = 1;
>
> 	to_copy = min_t(size_t, len, skb->len);
>-
>+	iov_iter.count = to_copy;
> 	skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)->offset,
> 			       &iov_iter, to_copy);
> }
>-- 
>2.34.1
>


^ permalink raw reply

* Re: [PATCH v4 3/3] vfio/pci: Replace vfio_pci_core_setup_barmap() with vfio_pci_core_get_iomap()
From: Alex Williamson @ 2026-05-08 17:45 UTC (permalink / raw)
  To: Matt Evans
  Cc: Kevin Tian, Jason Gunthorpe, Ankit Agrawal, Alistair Popple,
	Leon Romanovsky, Kees Cook, Shameer Kolothum, Yishai Hadas,
	Alexey Kardashevskiy, Eric Auger, Peter Xu, Vivek Kasireddy,
	Zhi Wang, kvm, linux-kernel, virtualization, alex
In-Reply-To: <015b1e9c-0a2d-472a-b750-9154800832ee@meta.com>

On Fri, 8 May 2026 16:30:40 +0100
Matt Evans <mattev@meta.com> wrote:

> Hi Alex,
> 
> On 07/05/2026 23:21, Alex Williamson wrote:
> > 
> > On Tue, 5 May 2026 10:38:31 -0700
> > Matt Evans <mattev@meta.com> wrote:
> >   
> >> Since "vfio/pci: Set up barmap in vfio_pci_core_enable()", the
> >> resource request and iomap for the BARs was performed early, and
> >> vfio_pci_core_setup_barmap() just checks those actions succeeded.
> >>
> >> Move this logic to a new helper that checks success and returns the
> >> iomap address, replacing the various bare vdev->barmap[] lookups.
> >> This maintains the error behaviour of the previous on-demand
> >> vfio_pci_core_setup_barmap() scheme.
> >>
> >> Signed-off-by: Matt Evans <mattev@meta.com>
> >> ---
> >>   drivers/vfio/pci/nvgrace-gpu/main.c | 11 ++++-------
> >>   drivers/vfio/pci/vfio_pci_core.c    | 11 +++++------
> >>   drivers/vfio/pci/vfio_pci_dmabuf.c  |  2 +-
> >>   drivers/vfio/pci/vfio_pci_rdwr.c    | 30 ++++++++---------------------
> >>   drivers/vfio/pci/virtio/legacy_io.c | 13 ++++++-------
> >>   include/linux/vfio_pci_core.h       | 20 ++++++++++++++++++-
> >>   6 files changed, 43 insertions(+), 44 deletions(-)
> >>
> >> diff --git a/drivers/vfio/pci/nvgrace-gpu/main.c b/drivers/vfio/pci/nvgrace-gpu/main.c
> >> index fa056b69f899..e153002258ce 100644
> >> --- a/drivers/vfio/pci/nvgrace-gpu/main.c
> >> +++ b/drivers/vfio/pci/nvgrace-gpu/main.c
> >> @@ -184,13 +184,10 @@ static int nvgrace_gpu_open_device(struct vfio_device *core_vdev)
> >>   
> >>   	/*
> >>   	 * GPU readiness is checked by reading the BAR0 registers.
> >> -	 *
> >> -	 * ioremap BAR0 to ensure that the BAR0 mapping is present before
> >> -	 * register reads on first fault before establishing any GPU
> >> -	 * memory mapping.
> >> +	 * The BAR map was just set up by vfio_pci_core_enable(), so
> >> +	 * check that was successful and bail early if not:
> >>   	 */
> >> -	ret = vfio_pci_core_setup_barmap(vdev, 0);
> >> -	if (ret)
> >> +	if (IS_ERR(vfio_pci_core_get_iomap(vdev, 0)))
> >>   		goto error_exit;  
> > 
> > Sashiko notes we're not setting ret here.  The bots are also paranoid
> > about the unreachable condition that the get_iomap below could return an
> > ERR_PTR.  Maybe head off both by adding an __iomem pointer to the
> > nvgrace_gpu_pci_core_device struct and a temporary one here.  Store the
> > iomap in the temporary variable, use it to test for IS_ERR() and
> > PTR_ERR(), then set the pointer in the structure after the last error
> > condition here.  Add one line in the close_device to set it NULL.  Then
> > just use nvdev->bar0_io below.  
> 
> Right about ret.  On the 2nd, the bots could benefit from a comment on
> the ...get_iomap() below saying that it "cannot fail" if this one
> passes, but hey.  I can add a struct member to track it (bots can then
> worry that it might be NULL, if they don't notice that
> nvgrace_gpu_check_device_ready() can't happen if
> nvgrace_gpu_open_device() didn't succeed, etc. etc.).

While I agree that there's always something that can be overlooked, it
does seem semantically cleaner that the io is tested when it's
retrieved for the driver structure and used from that structure in a
fixed lifecycle than retrieved without testing the results at time of
use.
 
> >>   
> >>   	if (nvdev->resmem.memlength) {
> >> @@ -275,7 +272,7 @@ nvgrace_gpu_check_device_ready(struct nvgrace_gpu_pci_core_device *nvdev)
> >>   	if (!__vfio_pci_memory_enabled(vdev))
> >>   		return -EIO;
> >>   
> >> -	ret = nvgrace_gpu_wait_device_ready(vdev->barmap[0]);
> >> +	ret = nvgrace_gpu_wait_device_ready(vfio_pci_core_get_iomap(vdev, 0));
> >>   	if (ret)
> >>   		return ret;
> >>   
> >> diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
> >> index 62931dc381d8..5c8bd13f10d0 100644
> >> --- a/drivers/vfio/pci/vfio_pci_core.c
> >> +++ b/drivers/vfio/pci/vfio_pci_core.c
> >> @@ -1761,7 +1761,7 @@ int vfio_pci_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma
> >>   	struct pci_dev *pdev = vdev->pdev;
> >>   	unsigned int index;
> >>   	u64 phys_len, req_len, pgoff, req_start;
> >> -	int ret;
> >> +	void __iomem *bar_io;
> >>   
> >>   	index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT);
> >>   
> >> @@ -1795,12 +1795,11 @@ int vfio_pci_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma
> >>   		return -EINVAL;
> >>   
> >>   	/*
> >> -	 * Even though we don't make use of the barmap for the mmap,
> >> -	 * we need to request the region and the barmap tracks that.
> >> +	 * Ensure the BAR resource region is reserved for use.
> >>   	 */
> >> -	ret = vfio_pci_core_setup_barmap(vdev, index);
> >> -	if (ret)
> >> -		return ret;
> >> +	bar_io = vfio_pci_core_get_iomap(vdev, index);
> >> +	if (IS_ERR(bar_io))
> >> +		return PTR_ERR(bar_io);
> >>   
> >>   	vma->vm_private_data = vdev;
> >>   	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> >> diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c
> >> index 69a5c2d511e6..46cd44b22c9c 100644
> >> --- a/drivers/vfio/pci/vfio_pci_dmabuf.c
> >> +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c
> >> @@ -248,7 +248,7 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags,
> >>   	 * else.  Check that PCI resources have been claimed for it.
> >>   	 */
> >>   	if (get_dma_buf.region_index >= VFIO_PCI_ROM_REGION_INDEX ||
> >> -	    vfio_pci_core_setup_barmap(vdev, get_dma_buf.region_index))
> >> +	    IS_ERR(vfio_pci_core_get_iomap(vdev, get_dma_buf.region_index)))
> >>   		return -ENODEV;
> >>   
> >>   	dma_ranges = memdup_array_user(&arg->dma_ranges, get_dma_buf.nr_ranges,
> >> diff --git a/drivers/vfio/pci/vfio_pci_rdwr.c b/drivers/vfio/pci/vfio_pci_rdwr.c
> >> index 3bfbb879a005..7f14dd46de17 100644
> >> --- a/drivers/vfio/pci/vfio_pci_rdwr.c
> >> +++ b/drivers/vfio/pci/vfio_pci_rdwr.c
> >> @@ -198,19 +198,6 @@ ssize_t vfio_pci_core_do_io_rw(struct vfio_pci_core_device *vdev, bool test_mem,
> >>   }
> >>   EXPORT_SYMBOL_GPL(vfio_pci_core_do_io_rw);
> >>   
> >> -/*
> >> - * The barmap is set up in vfio_pci_core_enable().  Callers use this
> >> - * function to check that the BAR resources are requested or that the
> >> - * pci_iomap() was done.
> >> - */
> >> -int vfio_pci_core_setup_barmap(struct vfio_pci_core_device *vdev, int bar)
> >> -{
> >> -	if (IS_ERR(vdev->barmap[bar]))
> >> -		return PTR_ERR(vdev->barmap[bar]);
> >> -	return 0;
> >> -}
> >> -EXPORT_SYMBOL_GPL(vfio_pci_core_setup_barmap);
> >> -
> >>   ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf,
> >>   			size_t count, loff_t *ppos, bool iswrite)
> >>   {
> >> @@ -262,13 +249,11 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, char __user *buf,
> >>   		 */
> >>   		max_width = VFIO_PCI_IO_WIDTH_4;
> >>   	} else {
> >> -		int ret = vfio_pci_core_setup_barmap(vdev, bar);
> >> -		if (ret) {
> >> -			done = ret;
> >> +		io = vfio_pci_core_get_iomap(vdev, bar);
> >> +		if (IS_ERR(io)) {
> >> +			done = PTR_ERR(io);
> >>   			goto out;
> >>   		}
> >> -
> >> -		io = vdev->barmap[bar];
> >>   	}
> >>   
> >>   	if (bar == vdev->msix_bar) {
> >> @@ -423,6 +408,7 @@ int vfio_pci_ioeventfd(struct vfio_pci_core_device *vdev, loff_t offset,
> >>   	loff_t pos = offset & VFIO_PCI_OFFSET_MASK;
> >>   	int ret, bar = VFIO_PCI_OFFSET_TO_INDEX(offset);
> >>   	struct vfio_pci_ioeventfd *ioeventfd;
> >> +	void __iomem *io;
> >>   
> >>   	/* Only support ioeventfds into BARs */
> >>   	if (bar > VFIO_PCI_BAR5_REGION_INDEX)
> >> @@ -440,9 +426,9 @@ int vfio_pci_ioeventfd(struct vfio_pci_core_device *vdev, loff_t offset,
> >>   	if (count == 8)
> >>   		return -EINVAL;
> >>   
> >> -	ret = vfio_pci_core_setup_barmap(vdev, bar);
> >> -	if (ret)
> >> -		return ret;
> >> +	io = vfio_pci_core_get_iomap(vdev, bar);
> >> +	if (IS_ERR(io))
> >> +		return PTR_ERR(io);  
> > 
> > Sashiko seems to note a real existing error here that should also be
> > pulled out to a separate fix.  Given the right offset, this could
> > generate a negative BAR value.  
> 
> Yuck, loff_t signed, yep.  Isn't the real root of this that it
> never makes sense for VFIO_PCI_OFFSET_TO_INDEX() to return a negative
> index here or anywhere else?

Yes

> I suggest instead, to also avoid this elsewhere in future, something
> like:
> 
>   #define VFIO_PCI_OFFSET_TO_INDEX(off)	((u64)(off) >> VFIO_PCI_OFFSET_SHIFT)

Sure, that has better coverage.

> > The test at the end of the previous
> > chunk should should be expanded to `if (bar < 0 || bar > ...BAR5...)`.  
> 
> Not necessary if VFIO_PCI_OFFSET_TO_INDEX() can't return < 0 (the
> magnitude would be 24b so can't overflow the `int bar` it's assigned
> into).

Yep.

> > Do you want to pick that up in this series?  I think it's the only case
> > that lets that slip through.  Thanks,  
> 
> Sure, I'll post a fix.  I don't think it needs to be part of this series
> though if it's just the macro, do you agree?

I was hoping to collect the fixes from this series for v7.1-rc
regardless, so either way.

> Do you know why drivers/gpu/drm/i915/gvt/kvmgt.c has copied
> VFIO_PCI_OFFSET_TO_INDEX() and friends?  Perhaps the shift was different
> (the reason drivers/vfio/pci/ism/main.c has its own versions).  The same
> loff_t issue seems to exist in both of those places, unfortunately.

I think because it was previously defined in a drivers/vfio/pci/ header
that couldn't be cleanly included.  The region shift is implementation,
not API, so drivers are free to define their own region spacing, see
for instance the new ISM driver that needs >40bits per region.  We're
likely going to move to a maple tree for defining regions in the future
so that we can more easily account for such large BARs as they become
more common.  Jason linked a branch with a rough draft of this not long
ago.

> PS: with minor question:
> Relatedly, I'd made `bar` an int following existing convention in
> 
>   vfio_pci_core_get_iomap(struct vfio_pci_core_device *vdev, int bar)
> 
> But I'll make this `unsigned int`, please flag if this violates taste
> and decency.  IMO any BAR/index parameter should be unsigned; most are,
> some signed remain.

Yep, I think that would make sense and avoids needing two tests to make
sure it's in range.  Thanks,

Alex

^ permalink raw reply

* Re: [PATCH v2] mm/balloon: expose per-node balloon pages in node meminfo
From: Andrew Morton @ 2026-05-08 18:31 UTC (permalink / raw)
  To: Hao Ge; +Cc: David Hildenbrand, linux-mm, virtualization, linux-kernel
In-Reply-To: <20260508094736.142467-1-hao.ge@linux.dev>

On Fri,  8 May 2026 17:47:36 +0800 Hao Ge <hao.ge@linux.dev> wrote:

> Commit 835de37603ef ("meminfo: add a per node counter for balloon
> drivers") added NR_BALLOON_PAGES and exposed it in /proc/meminfo.
> However, the per-node view at /sys/devices/system/node/nodeX/meminfo
> was not updated, even though the counter is already tracked per-node.
> 
> Add it to node_read_meminfo() so users can see balloon usage per
> NUMA node without having to parse the raw vmstat file.
> 
> ...
>
> --- a/drivers/base/node.c
> +++ b/drivers/base/node.c
> @@ -523,6 +523,7 @@ static ssize_t node_read_meminfo(struct device *dev,
>  #ifdef CONFIG_UNACCEPTED_MEMORY
>  			     "Node %d Unaccepted:     %8lu kB\n"
>  #endif
> +			     "Node %d Balloon:        %8lu kB\n"
>  			     ,
>  			     nid, K(node_page_state(pgdat, NR_FILE_DIRTY)),
>  			     nid, K(node_page_state(pgdat, NR_WRITEBACK)),
> @@ -556,6 +557,8 @@ static ssize_t node_read_meminfo(struct device *dev,
>  			     ,
>  			     nid, K(sum_zone_node_page_state(nid, NR_UNACCEPTED))
>  #endif
> +			     ,
> +			     nid, K(node_page_state(pgdat, NR_BALLOON_PAGES))
>  			    );
>  	len += hugetlb_report_node_meminfo(buf, len, nid);
>  	return len;

This was prepared against a kernel whcih didn't have 2232ba9c7931 ("mm:
add gpu active/reclaim per-node stat counters (v2)").  Which was added
in February, btw.

Please check my fixings:

--- a/drivers/base/node.c~mm-balloon-expose-per-node-balloon-pages-in-node-meminfo
+++ a/drivers/base/node.c
@@ -525,6 +525,7 @@ static ssize_t node_read_meminfo(struct
 #endif
 			     "Node %d GPUActive:      %8lu kB\n"
 			     "Node %d GPUReclaim:     %8lu kB\n"
+			     "Node %d Balloon:        %8lu kB\n"
 			     ,
 			     nid, K(node_page_state(pgdat, NR_FILE_DIRTY)),
 			     nid, K(node_page_state(pgdat, NR_WRITEBACK)),
@@ -560,7 +561,8 @@ static ssize_t node_read_meminfo(struct
 #endif
 			     ,
 			     nid, K(node_page_state(pgdat, NR_GPU_ACTIVE)),
-			     nid, K(node_page_state(pgdat, NR_GPU_RECLAIM))
+			     nid, K(node_page_state(pgdat, NR_GPU_RECLAIM)),
+			     nid, K(node_page_state(pgdat, NR_BALLOON_PAGES))
 			    );
 	len += hugetlb_report_node_meminfo(buf, len, nid);
 	return len;
_


^ permalink raw reply

* Re: [PATCH net 1/2] vsock/virtio: fix length and offset in tap skb for split packets
From: Bobby Eshleman @ 2026-05-08 22:22 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: netdev, Yiqi Sun, linux-kernel, Xuan Zhuo, Michael S. Tsirkin,
	Stefan Hajnoczi, kvm, Simon Horman, Bobby Eshleman, Jason Wang,
	Jakub Kicinski, David S. Miller, virtualization, Eric Dumazet,
	Paolo Abeni, Arseniy Krasnov, Eugenio Pérez, Bobby Eshleman
In-Reply-To: <20260508164411.261440-2-sgarzare@redhat.com>

On Fri, May 08, 2026 at 06:44:10PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
> 
> virtio_transport_build_skb() builds a new skb to be delivered to the
> vsockmon tap device. To build the new skb, it uses the original skb
> data length as payload length, but as the comment notes, the original
> packet stored in the skb may have been split in multiple packets, so we
> need to use the length in the header, which is correctly updated before
> the packet is delivered to the tap, and the offset for the data.
> 
> This was also similar to what we did before commit 71dc9ec9ac7d
> ("virtio/vsock: replace virtio_vsock_pkt with sk_buff") where we probably
> missed something during the skb conversion.
> 
> Also update the comment above, which was left stale by the skb
> conversion and still mentioned a buffer pointer that no longer exists.
> 
> Fixes: 71dc9ec9ac7d ("virtio/vsock: replace virtio_vsock_pkt with sk_buff")
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
> ---
>  net/vmw_vsock/virtio_transport_common.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)
> 
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 9b8014516f4f..a678d5d75704 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -166,12 +166,12 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
>  	struct sk_buff *skb;
>  	size_t payload_len;
>  
> -	/* A packet could be split to fit the RX buffer, so we can retrieve
> -	 * the payload length from the header and the buffer pointer taking
> -	 * care of the offset in the original packet.
> +	/* A packet could be split to fit the RX buffer, so we use
> +	 * the payload length from the header, which has been updated
> +	 * by the sender to reflect the fragment size.
>  	 */
>  	pkt_hdr = virtio_vsock_hdr(pkt);
> -	payload_len = pkt->len;
> +	payload_len = le32_to_cpu(pkt_hdr->len);
>  
>  	skb = alloc_skb(sizeof(*hdr) + sizeof(*pkt_hdr) + payload_len,
>  			GFP_ATOMIC);
> @@ -219,7 +219,8 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
>  
>  			virtio_transport_copy_nonlinear_skb(pkt, data, payload_len);
>  		} else {
> -			skb_put_data(skb, pkt->data, payload_len);
> +			skb_put_data(skb, pkt->data + VIRTIO_VSOCK_SKB_CB(pkt)->offset,
> +				     payload_len);
>  		}
>  	}
>  
> -- 
> 2.54.0
> 

Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>

^ permalink raw reply

* Re: [PATCH net 2/2] vsock/virtio: fix empty payload in tap skb for non-linear buffers
From: Bobby Eshleman @ 2026-05-08 22:30 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: netdev, Yiqi Sun, linux-kernel, Xuan Zhuo, Michael S. Tsirkin,
	Stefan Hajnoczi, kvm, Simon Horman, Bobby Eshleman, Jason Wang,
	Jakub Kicinski, David S. Miller, virtualization, Eric Dumazet,
	Paolo Abeni, Arseniy Krasnov, Eugenio Pérez, Bobby Eshleman
In-Reply-To: <20260508164411.261440-3-sgarzare@redhat.com>

On Fri, May 08, 2026 at 06:44:11PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
> 
> For non-linear skbs, virtio_transport_build_skb() goes through
> virtio_transport_copy_nonlinear_skb() to copy the original payload
> in the new skb to be delivered to the vsockmon tap device.
> This manually initializes an iov_iter but does not set iov_iter.count.
> Since the iov_iter is zero-initialized, the copy length is zero and no
> payload is actually copied to the monitor interface, leaving data
> un-initialized.
> 
> Fix this by removing the linear vs non-linear split and using
> skb_copy_datagram_iter() with iov_iter_kvec() for all cases, as
> vhost-vsock already does. This handles both linear and non-linear skbs,
> properly initializes the iov_iter, and removes the now unused
> virtio_transport_copy_nonlinear_skb().
> 
> While touching this code, let's also check the return value of
> skb_copy_datagram_iter(), even though it's unlikely to fail.
> 
> Fixes: 4b0bf10eb077 ("vsock/virtio: non-linear skb handling for tap")
> Reported-by: Yiqi Sun <sunyiqixm@gmail.com>
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
> ---
>  net/vmw_vsock/virtio_transport_common.c | 40 ++++++++-----------------
>  1 file changed, 12 insertions(+), 28 deletions(-)
> 
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index a678d5d75704..989cc252d3d3 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -136,27 +136,6 @@ static void virtio_transport_init_hdr(struct sk_buff *skb,
>  	hdr->fwd_cnt	= cpu_to_le32(0);
>  }
>  
> -static void virtio_transport_copy_nonlinear_skb(const struct sk_buff *skb,
> -						void *dst,
> -						size_t len)
> -{
> -	struct iov_iter iov_iter = { 0 };
> -	struct kvec kvec;
> -	size_t to_copy;
> -
> -	kvec.iov_base = dst;
> -	kvec.iov_len = len;
> -
> -	iov_iter.iter_type = ITER_KVEC;
> -	iov_iter.kvec = &kvec;
> -	iov_iter.nr_segs = 1;
> -
> -	to_copy = min_t(size_t, len, skb->len);
> -
> -	skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)->offset,
> -			       &iov_iter, to_copy);
> -}
> -
>  /* Packet capture */
>  static struct sk_buff *virtio_transport_build_skb(void *opaque)
>  {
> @@ -214,13 +193,18 @@ static struct sk_buff *virtio_transport_build_skb(void *opaque)
>  	skb_put_data(skb, pkt_hdr, sizeof(*pkt_hdr));
>  
>  	if (payload_len) {
> -		if (skb_is_nonlinear(pkt)) {
> -			void *data = skb_put(skb, payload_len);
> -
> -			virtio_transport_copy_nonlinear_skb(pkt, data, payload_len);
> -		} else {
> -			skb_put_data(skb, pkt->data + VIRTIO_VSOCK_SKB_CB(pkt)->offset,
> -				     payload_len);
> +		struct iov_iter iov_iter;
> +		struct kvec kvec;
> +		void *data = skb_put(skb, payload_len);
> +
> +		kvec.iov_base = data;
> +		kvec.iov_len = payload_len;
> +		iov_iter_kvec(&iov_iter, ITER_DEST, &kvec, 1, payload_len);
> +
> +		if (skb_copy_datagram_iter(pkt, VIRTIO_VSOCK_SKB_CB(pkt)->offset,
> +					   &iov_iter, payload_len)) {
> +			kfree_skb(skb);
> +			return NULL;
>  		}
>  	}
>  
> -- 
> 2.54.0
> 

Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>

^ permalink raw reply

* [PATCH v3] proc/meminfo: expose per-node balloon pages in node meminfo
From: Hao Ge @ 2026-05-09  0:56 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand
  Cc: linux-mm, virtualization, linux-kernel, Hao Ge

Commit 835de37603ef ("meminfo: add a per node counter for balloon
drivers") added NR_BALLOON_PAGES and exposed it in /proc/meminfo.
However, the per-node view at /sys/devices/system/node/nodeX/meminfo
was not updated, even though the counter is already tracked per-node.

Add it to node_read_meminfo() so users can see balloon usage per
NUMA node without having to parse the raw vmstat file.

Signed-off-by: Hao Ge <hao.ge@linux.dev>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
v3:
    Rebase on top of 2232ba9c7931 ("mm: add gpu active/reclaim
    per-node stat counters (v2)), place Balloon between Unaccepted
    and GPUActive to match /proc/meminfo ordering.

v2: Move Balloon field after Unaccepted to match /proc/meminfo ordering
    (suggested by David Hildenbrand)
---
 drivers/base/node.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/base/node.c b/drivers/base/node.c
index 126f66aa2c3e..f4d9a21cc24e 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -523,6 +523,7 @@ static ssize_t node_read_meminfo(struct device *dev,
 #ifdef CONFIG_UNACCEPTED_MEMORY
 			     "Node %d Unaccepted:     %8lu kB\n"
 #endif
+			     "Node %d Balloon:        %8lu kB\n"
 			     "Node %d GPUActive:      %8lu kB\n"
 			     "Node %d GPUReclaim:     %8lu kB\n"
 			     ,
@@ -559,6 +560,7 @@ static ssize_t node_read_meminfo(struct device *dev,
 			     nid, K(sum_zone_node_page_state(nid, NR_UNACCEPTED))
 #endif
 			     ,
+			     nid, K(node_page_state(pgdat, NR_BALLOON_PAGES)),
 			     nid, K(node_page_state(pgdat, NR_GPU_ACTIVE)),
 			     nid, K(node_page_state(pgdat, NR_GPU_RECLAIM))
 			    );
-- 
2.25.1


^ permalink raw reply related

* Re: [PATCH v2] mm/balloon: expose per-node balloon pages in node meminfo
From: SeongJae Park @ 2026-05-09  1:07 UTC (permalink / raw)
  To: Hao Ge
  Cc: SeongJae Park, Andrew Morton, David Hildenbrand, linux-mm,
	virtualization, linux-kernel
In-Reply-To: <20260508094736.142467-1-hao.ge@linux.dev>

On Fri,  8 May 2026 17:47:36 +0800 Hao Ge <hao.ge@linux.dev> wrote:

> Commit 835de37603ef ("meminfo: add a per node counter for balloon
> drivers") added NR_BALLOON_PAGES and exposed it in /proc/meminfo.
> However, the per-node view at /sys/devices/system/node/nodeX/meminfo
> was not updated, even though the counter is already tracked per-node.
> 
> Add it to node_read_meminfo() so users can see balloon usage per
> NUMA node without having to parse the raw vmstat file.

Makes sense to me.

> 
> Signed-off-by: Hao Ge <hao.ge@linux.dev>

Acked-by: SeongJae Park <sj@kernel.org>

> ---
> v2: Move Balloon field after Unaccepted to match /proc/meminfo ordering
>     (suggested by David Hildenbrand)

Adding a link to previous revision [1] would be nice.

[1] https://docs.kernel.org/process/submitting-patches.html#commentary


Thanks,
SJ

[...]

^ permalink raw reply

* Re: [PATCH v2] mm/balloon: expose per-node balloon pages in node meminfo
From: Hao Ge @ 2026-05-09  1:14 UTC (permalink / raw)
  To: Andrew Morton; +Cc: David Hildenbrand, linux-mm, virtualization, linux-kernel
In-Reply-To: <20260508113123.c8e653484d39e76cdd6e142e@linux-foundation.org>

Hi Andrew


On 2026/5/9 02:31, Andrew Morton wrote:
> On Fri,  8 May 2026 17:47:36 +0800 Hao Ge <hao.ge@linux.dev> wrote:
>
>> Commit 835de37603ef ("meminfo: add a per node counter for balloon
>> drivers") added NR_BALLOON_PAGES and exposed it in /proc/meminfo.
>> However, the per-node view at /sys/devices/system/node/nodeX/meminfo
>> was not updated, even though the counter is already tracked per-node.
>>
>> Add it to node_read_meminfo() so users can see balloon usage per
>> NUMA node without having to parse the raw vmstat file.
>>
>> ...
>>
>> --- a/drivers/base/node.c
>> +++ b/drivers/base/node.c
>> @@ -523,6 +523,7 @@ static ssize_t node_read_meminfo(struct device *dev,
>>   #ifdef CONFIG_UNACCEPTED_MEMORY
>>   			     "Node %d Unaccepted:     %8lu kB\n"
>>   #endif
>> +			     "Node %d Balloon:        %8lu kB\n"
>>   			     ,
>>   			     nid, K(node_page_state(pgdat, NR_FILE_DIRTY)),
>>   			     nid, K(node_page_state(pgdat, NR_WRITEBACK)),
>> @@ -556,6 +557,8 @@ static ssize_t node_read_meminfo(struct device *dev,
>>   			     ,
>>   			     nid, K(sum_zone_node_page_state(nid, NR_UNACCEPTED))
>>   #endif
>> +			     ,
>> +			     nid, K(node_page_state(pgdat, NR_BALLOON_PAGES))
>>   			    );
>>   	len += hugetlb_report_node_meminfo(buf, len, nid);
>>   	return len;
> This was prepared against a kernel whcih didn't have 2232ba9c7931 ("mm:
> add gpu active/reclaim per-node stat counters (v2)").  Which was added
> in February, btw.

Sorry about that — I failed to git pull the latest code before preparing 
the v2 patch,

so I didn't realize commit 2232ba9c7931 ("mm: add gpu active/reclaim 
per-node stat counters (v2)")

had already been merged in February.

I apologize for the trouble this caused you.

> Please check my fixings:
>
> --- a/drivers/base/node.c~mm-balloon-expose-per-node-balloon-pages-in-node-meminfo
> +++ a/drivers/base/node.c
> @@ -525,6 +525,7 @@ static ssize_t node_read_meminfo(struct
>   #endif
>   			     "Node %d GPUActive:      %8lu kB\n"
>   			     "Node %d GPUReclaim:     %8lu kB\n"
> +			     "Node %d Balloon:        %8lu kB\n"
>   			     ,
>   			     nid, K(node_page_state(pgdat, NR_FILE_DIRTY)),
>   			     nid, K(node_page_state(pgdat, NR_WRITEBACK)),
> @@ -560,7 +561,8 @@ static ssize_t node_read_meminfo(struct
>   #endif
>   			     ,
>   			     nid, K(node_page_state(pgdat, NR_GPU_ACTIVE)),
> -			     nid, K(node_page_state(pgdat, NR_GPU_RECLAIM))
> +			     nid, K(node_page_state(pgdat, NR_GPU_RECLAIM)),
> +			     nid, K(node_page_state(pgdat, NR_BALLOON_PAGES))
>   			    );
>   	len += hugetlb_report_node_meminfo(buf, len, nid);
>   	return len;
> _

As David suggested, the node meminfo fields should match the 
/proc/meminfo ordering.

So in the v3 I rebased on the latest tree and put Balloon between 
Unaccepted and GPUActive.

Here is the v3 patch: 
https://lore.kernel.org/all/20260509005631.17183-1-hao.ge@linux.dev/

  Sorry again for the inconvenience, Could you please replace the v2 
patch in mm-new with this v3?

Thanks

Best Regards

Hao



^ permalink raw reply

* [PATCH RFC v2 0/6] Add Rust virtio bindings and sample device
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis

Hi all, this RFC series adds Rust bindings for Virtio drivers
(frontends in virtio parlance).

As a PoC, it also adds a sample virtio-rtc driver which performs
capability discovery through the virtqueue without registering any clock.

Before I send a cleaned-up non-RFC I would like some initial feedback
(i.e. is it something the upstream wants?)

This was tested with the rust-vmm vhost-device-rtc device backend that I
wrote[^0]:

[^0]: https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-rtc

Instructions:

  Run the daemon in a separate terminal:

  $ cargo run --bin vhost-device-rtc -- -s /tmp/rtc.sock

  Then run the VM:

  $ qemu-system-aarch64 \
    -machine type=virt,virtualization=off,acpi=on \
    -cpu host \
    -smp 8 \
    -accel kvm \
    -drive if=virtio,format=qcow2,file=./debian-13-nocloud-arm64-daily.qcow2 \
    -device virtio-net-pci,netdev=unet \
    -device virtio-scsi-pci \
    -serial mon:stdio \
    -m 8192 \
    -object memory-backend-memfd,id=mem,size=8G,share=on \
    -numa node,memdev=mem \
    -display none \
    -vga none \
    -kernel /path/to/linux/build/arch/arm64/boot/Image \
    -device vhost-user-test-device,chardev=rtc,id=rtc,virtio-id=17,num_vqs=2,vq_size=1024 \
    -chardev socket,path=/tmp/rtc.sock,id=rtc \
    ...

  Example output:
    [    1.105238] rust_virtio_rtc: Probe Rust virtio driver sample.
    [    1.105645] rust_virtio_rtc: Found 1 vqs.
    [    1.136050] rust_virtio_rtc: process_requestq got buf 16 bytes
    [    1.136125] rust_virtio_rtc: Got response! Ok(RespCfg { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, num_clocks: Le16(3), reserved: [0, 0, 0, 0, 0, 0] })
    [    1.136701] rust_virtio_rtc: Got response! Ok(RespClockCap { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_type: 3, leap_second_smearing: 0, flags: 0, reserved: [0, 0, 0, 0, 0] })
    [    1.136724] rust_virtio_rtc virtio0: cannot expose clock 0 (type 3, variant 0, flags 0) to userspace
    [    1.137259] rust_virtio_rtc: Got response! Ok(RespRead { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_reading: Le64(1777890485031060388) })
    [    1.137277] rust_virtio_rtc: #0 clock reading = 1777890485031060388
    [    1.137749] rust_virtio_rtc: Got response! Ok(RespClockCap { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_type: 1, leap_second_smearing: 0, flags: 0, reserved: [0, 0, 0, 0, 0] })
    [    1.137769] rust_virtio_rtc virtio0: cannot expose clock 1 (type 1, variant 0, flags 0) to userspace
    [    1.138247] rust_virtio_rtc: Got response! Ok(RespRead { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_reading: Le64(1777890485032086075) })
    [    1.138264] rust_virtio_rtc: #1 clock reading = 1777890485032086075
    [    1.138730] rust_virtio_rtc: Got response! Ok(RespClockCap { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_type: 2, leap_second_smearing: 0, flags: 0, reserved: [0, 0, 0, 0, 0] })
    [    1.138751] rust_virtio_rtc virtio0: cannot expose clock 2 (type 2, variant 0, flags 0) to userspace
    [    1.139253] rust_virtio_rtc: Got response! Ok(RespRead { head: ReqHead { msg_type: Le16(0), reserved: [0, 0, 0, 0, 0, 0] }, clock_reading: Le64(338567896865557) })
    [    1.139270] rust_virtio_rtc: #2 clock reading = 338567896865557

Concerns - Notes - TODOs
========================

- Virtqueue lifetimes don't neatly apply to Rust as expected, so a lot
  of times we have to go through unsafe pointer dereferences (though
  which are guaranteed by Virtio subsystem to be valid, for example when
  a callback is called with the vq argument). There's a potential for
  misuse and definitely could use better thinking.
- `struct virtio_device` is not reference-counted like other implemented
  device types in rust/kernel. Maybe we need to change C API first to
  make them reference counted, assuming this doesn't break anything?
- Not sure if adding data smaller than PAGE_SIZE to virtqueue is safe
  (which current C code does a lot), so I made those allocations at
  least PAGE_SIZE.
- The sample driver obviously conflicts with the C implementation, so
  this would either need to move out of samples/ or figure out some way
  to handle this in kbuild.
- kernel::virtio module and its types need a few rustdoc examples that I
  will add in followup series
- Note that the registration of RTC clocks etc in the sample driver is
  not done, I'm putting it off until I receive some feedback first. The
  sample driver otherwise does send and receive data from the virtqueue
  as a PoC. The code and data structures can be cleaned up further
  before followup series.

PS: No LLMs used so any mistakes and goofs are solely written by me.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
Changes in v2:
- Move helper ifdefs to helper file (thanks Alice)
- Changed CONFIG checks to IS_ENABLED to allow for CONFIG_VIRTIO=m
- Split all use imports to one item per line according to style guide
- Fixed wait_for_completion_interruptible*() rustdocs
- Use Jiffy type alias in wait_for_completion_interruptible_timeout()
- Pepper and salt #[inline]s wherever appropriate as per style guide
- Split probe() into probe() and init() to allow cleaning up if init
  fails
- Remove unnecessary Send and Sync unsafe impls for
  kernel::virtio::Device
- Remove unnecessary LeSize and BeSize 
- Accept Option<_> for virtqueue callback when creating a VirtqueueInfo
- Made all vq buffer adding operations unsafe
- Use AtomicU16 instead of Cell<u16> for sample virtio driver
- Fix RespHead field types in sample virtio driver
- Fix response error checking in sample virtio driver
- Change some device contexts in method signatures
- Link to v1: https://lore.kernel.org/r/20260505-rust-virtio-v1-0-9563383909e4@pitsidianak.is

---
Manos Pitsidianakis (6):
      rust/bindings: generate virtio bindings
      rust/helpers: add virtio.c
      rust: add virtio module
      rust/scatterlist: add SGEntry::init_one
      rust: impl interruptible waits for Completion
      samples/rust: Add sample virtio-rtc driver [WIP]

 MAINTAINERS                     |   9 +
 rust/bindings/bindings_helper.h |   5 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/virtio.c           |  37 +++
 rust/kernel/lib.rs              |   2 +
 rust/kernel/scatterlist.rs      |  20 +-
 rust/kernel/sync/completion.rs  |  42 +++-
 rust/kernel/virtio.rs           | 419 ++++++++++++++++++++++++++++++++++
 rust/kernel/virtio/utils.rs     |  63 ++++++
 rust/kernel/virtio/virtqueue.rs | 241 ++++++++++++++++++++
 samples/rust/Kconfig            |  15 ++
 samples/rust/Makefile           |   1 +
 samples/rust/rust_virtio_rtc.rs | 491 ++++++++++++++++++++++++++++++++++++++++
 13 files changed, 1344 insertions(+), 2 deletions(-)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260504-rust-virtio-8523b01dfdc2

Best regards,
-- 
Manos Pitsidianakis <manos@pitsidianak.is>


^ permalink raw reply

* [PATCH RFC v2 1/6] rust/bindings: generate virtio bindings
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

Add virtio headers if CONFIG_VIRTIO is enabled.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 rust/bindings/bindings_helper.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 083cc44aa952c2b29ab82d5d481063a1cf48bccf..1cbe1a4b5647fd646c1be3c5c80fb24ae7b97a4a 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -151,3 +151,8 @@ const vm_flags_t RUST_CONST_HELPER_VM_NOHUGEPAGE = VM_NOHUGEPAGE;
 #include "../../drivers/android/binder/rust_binder_events.h"
 #include "../../drivers/android/binder/page_range_helper.h"
 #endif
+
+#if IS_ENABLED(CONFIG_VIRTIO)
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_ids.h>
+#endif /* IS_ENABLED(CONFIG_VIRTIO) */

-- 
2.47.3


^ permalink raw reply related

* [PATCH RFC v2 2/6] rust/helpers: add virtio.c
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

Some internal kernel virtio API functions are inline macros, so define
their symbols in a helper file.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 MAINTAINERS            |  6 ++++++
 rust/helpers/helpers.c |  1 +
 rust/helpers/virtio.c  | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f004da89b1aa339116908f642e894..48c9c666d90b5a256ab6fae1f42508b789a0ce50 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27930,6 +27930,12 @@ F:	include/uapi/linux/virtio_*.h
 F:	net/vmw_vsock/virtio*
 F:	tools/virtio/
 
+VIRTIO CORE API BINDINGS [RUST]
+M:	Manos Pitsidianakis <manos@pitsidianak.is>
+L:	virtualization@lists.linux.dev
+S:	Maintained
+F:	rust/helpers/virtio.c
+
 VIRTIO CRYPTO DRIVER
 M:	Gonglei <arei.gonglei@huawei.com>
 L:	virtualization@lists.linux.dev
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3c42e51f00a0990bea81ebce6e99bb397ce7533..5dc0d2f2ee6bd2ae8e6abfe4baa247c1963967f6 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -61,6 +61,7 @@
 #include "time.c"
 #include "uaccess.c"
 #include "usb.c"
+#include "virtio.c"
 #include "vmalloc.c"
 #include "wait.c"
 #include "workqueue.c"
diff --git a/rust/helpers/virtio.c b/rust/helpers/virtio.c
new file mode 100644
index 0000000000000000000000000000000000000000..92185d7b8bd97b66f7b7b825390fdd1173de313d
--- /dev/null
+++ b/rust/helpers/virtio.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#if IS_ENABLED(CONFIG_VIRTIO)
+#include <linux/virtio_config.h>
+
+__rust_helper bool
+rust_helper_virtio_has_feature(const struct virtio_device *vdev,
+			       unsigned int fbit)
+{
+	return virtio_has_feature(vdev, fbit);
+}
+__rust_helper void rust_helper_virtio_get_features(struct virtio_device *vdev,
+						   u64 *features_out)
+{
+	return virtio_get_features(vdev, features_out);
+}
+
+__rust_helper int rust_helper_virtio_find_vqs(struct virtio_device *vdev,
+					      unsigned int nvqs,
+					      struct virtqueue *vqs[],
+					      struct virtqueue_info vqs_info[],
+					      struct irq_affinity *desc)
+{
+	return virtio_find_vqs(vdev, nvqs, vqs, vqs_info, desc);
+}
+
+__rust_helper void rust_helper_virtio_device_ready(struct virtio_device *dev)
+{
+	return virtio_device_ready(dev);
+}
+
+__rust_helper bool
+rust_helper_virtio_is_little_endian(struct virtio_device *vdev)
+{
+	return virtio_is_little_endian(vdev);
+}
+#endif /* IS_ENABLED(CONFIG_VIRTIO) */

-- 
2.47.3


^ permalink raw reply related

* [PATCH RFC v2 6/6] samples/rust: Add sample virtio-rtc driver [WIP]
From: Manos Pitsidianakis @ 2026-05-09 14:47 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

While the driver queries clocks and capabilities for each clock, it
doesn't actually register them yet (TODO).

Until I implement missing functionality, there is some dead code and
some missing SAFETY comments.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 MAINTAINERS                     |   1 +
 samples/rust/Kconfig            |  15 ++
 samples/rust/Makefile           |   1 +
 samples/rust/rust_virtio_rtc.rs | 491 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 508 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e8012f708df5d4ee858c82aec3269e615fc8caad..3ed579e8d3cc64d1749cf261cd68f6338a830c4d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27937,6 +27937,7 @@ S:	Maintained
 F:	rust/helpers/virtio.c
 F:	rust/kernel/virtio.rs
 F:	rust/kernel/virtio/
+F:	samples/rust/rust_virtio_rtc.rs
 
 VIRTIO CRYPTO DRIVER
 M:	Gonglei <arei.gonglei@huawei.com>
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index c49ab910634596aea4a1a73dac87585e084f420a..96a16aecc27198fd99f4ffd0ecdf0bc0876860c6 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -179,4 +179,19 @@ config SAMPLE_RUST_HOSTPROGS
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_VIRTIO_RTC
+	tristate "Rust Virtio RTC driver"
+	depends on VIRTIO
+	depends on PTP_1588_CLOCK_OPTIONAL
+	help
+	 This driver provides current time from a Virtio RTC device. The driver
+	 provides the time through one or more clocks. The Virtio RTC PTP
+	 clocks and/or the Real Time Clock driver for Virtio RTC must be
+	 enabled to expose the clocks to userspace.
+
+	 To compile this code as a module, choose M here: the module will be
+	 called rust_virtio_rtc.
+
+	 If unsure, say M.
+
 endif # SAMPLES_RUST
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 6c0aaa58ccccfd12ef019f68ca784f6d977bc668..0142fd8656bb8cdc95b7ef54e3183b5e51358954 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX)		+= rust_driver_faux.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY)	+= rust_driver_auxiliary.o
 obj-$(CONFIG_SAMPLE_RUST_CONFIGFS)		+= rust_configfs.o
 obj-$(CONFIG_SAMPLE_RUST_SOC)			+= rust_soc.o
+obj-$(CONFIG_SAMPLE_RUST_VIRTIO_RTC)		+= rust_virtio_rtc.o
 
 rust_print-y := rust_print_main.o rust_print_events.o
 
diff --git a/samples/rust/rust_virtio_rtc.rs b/samples/rust/rust_virtio_rtc.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b12b188d4b1e91546a0c23558d747033747ff3a9
--- /dev/null
+++ b/samples/rust/rust_virtio_rtc.rs
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust virtio driver sample.
+
+use core::{
+    marker::PhantomData,
+    ptr::NonNull, //
+    sync::atomic::{
+        AtomicU16,
+        Ordering, //
+    },
+};
+
+use kernel::{
+    device::{
+        Bound,
+        Core, //
+    },
+    new_mutex,    //
+    new_spinlock, //
+    page,
+    prelude::*,
+    scatterlist::SGEntry,
+    sync::Completion,
+    sync::{
+        Mutex,
+        SpinLock, //
+    },
+    virtio::{
+        self,
+        utils::*,
+        virtqueue::*, //
+    },
+};
+
+use pin_init::stack_try_pin_init;
+
+#[pin_data]
+struct Token {
+    resp_actual_size: u32,
+    #[pin]
+    responded: Completion,
+}
+
+#[pin_data]
+struct Message<Request: Zeroable, Response: Zeroable> {
+    msg_type: u16,
+    #[pin]
+    req: KVec<u8>,
+    #[pin]
+    resp: KVec<u8>,
+    req_ptr: NonNull<Request>,
+    resp_ptr: NonNull<Response>,
+    #[pin]
+    token: Token,
+    _ph_req: PhantomData<Request>,
+    _ph_resp: PhantomData<Response>,
+}
+
+#[derive(Copy, Clone, Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_req_head")]
+struct ReqHead {
+    msg_type: Le16,
+    reserved: [u8; 6],
+}
+
+#[derive(Copy, Clone, Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_resp_head")]
+struct RespHead {
+    status: u8,
+    reserved: [u8; 7],
+}
+
+#[derive(Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_resp_cfg")]
+struct RespCfg {
+    head: RespHead,
+    /** # of clocks -> clock ids < num_clocks are valid */
+    num_clocks: Le16,
+    reserved: [u8; 6],
+}
+
+#[derive(Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_req_clock_cap")]
+struct ReqClockCap {
+    head: ReqHead,
+    clock_id: Le16,
+    reserved: [u8; 6],
+}
+
+#[derive(Copy, Clone, Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_resp_clock_cap")]
+struct RespClockCap {
+    head: RespHead,
+    clock_type: u8,
+    leap_second_smearing: u8,
+    flags: u8,
+    reserved: [u8; 5],
+}
+
+#[derive(Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_req_read")]
+struct ReqRead {
+    head: ReqHead,
+    clock_id: Le16,
+    reserved: [u8; 6],
+}
+
+#[derive(Copy, Clone, Debug, Zeroable)]
+#[repr(C)]
+#[doc(alias = "virtio_rtc_resp_read")]
+struct RespRead {
+    head: RespHead,
+    clock_reading: Le64,
+}
+
+#[repr(u8)]
+enum ClockType {
+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC")]
+    Utc = 0,
+    #[doc(alias = "VIRTIO_RTC_CLOCK_TAI")]
+    Tai = 1,
+    #[doc(alias = "VIRTIO_RTC_CLOCK_MONOTONIC")]
+    Monotonic = 2,
+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC_SMEARED")]
+    UtcSmeared = 3,
+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED")]
+    UtcMaybeSmeared = 4,
+}
+
+// SAFETY: `Message` is safe to be send to any task.
+unsafe impl<Request: Zeroable, Response: Zeroable> Send for Message<Request, Response> {}
+
+// SAFETY: `Message` is safe to be accessed concurrently.
+unsafe impl<Request: Zeroable, Response: Zeroable> Sync for Message<Request, Response> {}
+
+impl<Request: Zeroable, Response: Zeroable> Message<Request, Response> {
+    /// Create an initializer for a new [`Message`].
+    fn new(req_data: Request, msg_type: u16) -> Result<impl PinInit<Self, Error>> {
+        macro_rules! alloc_buf {
+            ($t:ty) => {{
+                let size = (core::mem::size_of::<$t>() / page::PAGE_SIZE + 1) * page::PAGE_SIZE;
+                KVec::<u8>::with_capacity(size, GFP_KERNEL)
+            }};
+        }
+        let mut req = alloc_buf!(Request)?;
+        let mut resp = alloc_buf!(Response)?;
+        let req_ptr: NonNull<Request> = NonNull::new(req.as_mut_ptr().cast()).unwrap();
+        let resp_ptr = NonNull::new(resp.as_mut_ptr().cast()).unwrap();
+        // SAFETY: `req_ptr` is a valid Request allocation
+        unsafe {
+            core::ptr::write(req_ptr.as_ptr(), req_data);
+        }
+        Ok(pin_init!(Self {
+            req,
+            resp,
+            msg_type,
+            req_ptr,
+            resp_ptr,
+            token <- pin_init!(Token {
+                resp_actual_size: 0,
+                responded <- Completion::new(),
+            }),
+            _ph_req: PhantomData,
+            _ph_resp: PhantomData,
+        }? Error))
+    }
+
+    fn get_response(&self) -> Result<&Response, Error> {
+        if self.token.resp_actual_size as usize != core::mem::size_of::<Response>() {
+            return Err(EINVAL);
+        }
+        if self.token.resp_actual_size as usize >= core::mem::size_of::<RespHead>() {
+            let head: &RespHead = unsafe { self.resp_ptr.cast().as_ref() };
+            match head.status {
+                0 => {
+                    // OK, do nothing.
+                }
+                1 => return Err(ENOTSUPP),
+                2 => return Err(ENODEV),
+                3 => return Err(EINVAL),
+                4 | 5_u8..=u8::MAX => return Err(EIO),
+            }
+        } else {
+            return Err(EINVAL);
+        }
+        Ok(unsafe { self.resp_ptr.as_ref() })
+    }
+
+    fn send(&self, vq: &SpinLock<VirtioRtcVq>, timeout_jiffies: c_ulong) -> Result {
+        let guard = vq.lock();
+
+        let mut sg_in = core::mem::MaybeUninit::zeroed();
+        let mut sg_out = core::mem::MaybeUninit::zeroed();
+        let req = unsafe {
+            SGEntry::init_one(
+                &mut sg_out,
+                self.req_ptr.cast(),
+                core::mem::size_of::<Request>() as u32,
+            )
+        };
+        let resp = unsafe {
+            SGEntry::init_one(
+                &mut sg_in,
+                self.resp_ptr.cast(),
+                core::mem::size_of::<Response>() as u32,
+            )
+        };
+        let sgs = [req, resp];
+        // SAFETY: `self` lives at least as long until `Message::send` returns
+        unsafe {
+            guard.as_ref().add_sgs(
+                &sgs,
+                1,
+                1,
+                NonNull::new((&raw const self.token).cast_mut().cast()).unwrap(),
+                GFP_ATOMIC,
+            )?;
+        }
+
+        if guard.as_ref().kick_prepare() {
+            guard.as_ref().notify();
+        }
+        drop(guard);
+
+        if timeout_jiffies > 0 {
+            self.token
+                .responded
+                .wait_for_completion_interruptible_timeout(timeout_jiffies)?;
+        } else {
+            self.token.responded.wait_for_completion_interruptible()?;
+        }
+        Ok(())
+    }
+}
+
+// TODO: use a proper enum
+
+const VIRTIO_RTC_REQ_READ: u16 = 0x0001;
+const VIRTIO_RTC_REQ_CFG: u16 = 0x1000;
+const VIRTIO_RTC_REQ_CLOCK_CAP: u16 = 0x1001;
+
+struct VirtioRtcVq {
+    ptr: NonNull<Virtqueue>,
+}
+
+// SAFETY: `VirtioRtcVq` is safe to be send to any task.
+unsafe impl Send for VirtioRtcVq {}
+
+impl VirtioRtcVq {
+    fn new(ptr: *mut Virtqueue) -> impl PinInit<SpinLock<Self>> {
+        let ptr = NonNull::new(ptr).unwrap();
+        new_spinlock!(Self { ptr })
+    }
+
+    fn as_ref(&self) -> &Virtqueue {
+        unsafe { self.ptr.as_ref() }
+    }
+}
+
+struct VirtioRtcDriver {
+    reqvq: Pin<KBox<SpinLock<VirtioRtcVq>>>,
+    alarmvq: Option<Pin<KBox<SpinLock<VirtioRtcVq>>>>,
+    num_clocks: AtomicU16,
+    registered_clocks: Pin<KBox<Mutex<KVec<()>>>>,
+}
+
+impl Drop for VirtioRtcDriver {
+    fn drop(&mut self) {
+        pr_info!("Remove Rust virtio driver sample.\n");
+    }
+}
+
+extern "C" fn vq_requestq_callback(vq: *mut kernel::bindings::virtqueue) {
+    // SAFETY: The kernel called this virtqueue callback and it must have provided a valid `vq`
+    // pointer
+    let vq = unsafe { Virtqueue::from_raw(vq) };
+    let dev: &virtio::Device<Bound> = vq.dev().expect("Could not get device");
+    let data = dev
+        .as_ref()
+        .drvdata::<VirtioRtcDriver>()
+        .expect("Could not borrow drvdata");
+    data.process_requestq();
+}
+
+impl VirtioRtcDriver {
+    /// Submit `VIRTIO_RTC_REQ_CFG` and return response (`num_clocks`)
+    fn req_cfg(&self) -> Result<u16> {
+        let head = ReqHead {
+            msg_type: VIRTIO_RTC_REQ_CFG.into(),
+            reserved: [0; 6],
+        };
+        stack_try_pin_init!(
+            let msg: Message::<ReqHead, RespCfg> =
+                Message::new(head, VIRTIO_RTC_REQ_CFG)?);
+        let msg: core::pin::Pin<&mut Message<ReqHead, RespCfg>> = msg?;
+        msg.send(&self.reqvq, 0)?;
+        pr_info!("Got response! {:?}\n", msg.get_response());
+
+        let response: &RespCfg = msg.get_response()?;
+        Ok(response.num_clocks.into())
+    }
+
+    fn process_requestq(&self) {
+        let mut cb_enabled = true;
+        loop {
+            let guard = self.reqvq.lock();
+            if cb_enabled {
+                guard.as_ref().disable_cb();
+                cb_enabled = false;
+            }
+            if let Some((token, len)) = guard.as_ref().get_buf() {
+                drop(guard);
+                pr_info!("process_requestq got buf {len} bytes\n");
+                let mut token = token.cast::<Token>();
+
+                unsafe { token.as_mut().resp_actual_size = len };
+                unsafe { token.as_mut().responded.complete_all() };
+            } else {
+                if guard.as_ref().enable_cb() {
+                    return;
+                }
+                cb_enabled = true;
+            }
+        }
+    }
+
+    fn clock_cap(&self, clock_id: u16) -> Result<RespClockCap> {
+        type ClockCapMsg = Message<ReqClockCap, RespClockCap>;
+
+        let req = ReqClockCap {
+            head: ReqHead {
+                msg_type: VIRTIO_RTC_REQ_CLOCK_CAP.into(),
+                reserved: [0; 6],
+            },
+            clock_id: clock_id.into(),
+            reserved: [0; 6],
+        };
+        stack_try_pin_init!(
+            let msg: ClockCapMsg = Message::new(req, VIRTIO_RTC_REQ_CLOCK_CAP)?
+        );
+        let msg: core::pin::Pin<&mut ClockCapMsg> = msg?;
+        msg.send(&self.reqvq, 0)?;
+        pr_info!("Got response! {:?}\n", msg.get_response());
+        let response: &RespClockCap = msg.get_response()?;
+        Ok(*response)
+    }
+
+    fn read(&self, clock_id: u16) -> Result<u64> {
+        type ReadMsg = Message<ReqRead, RespRead>;
+
+        let req = ReqRead {
+            head: ReqHead {
+                msg_type: VIRTIO_RTC_REQ_READ.into(),
+                reserved: [0; 6],
+            },
+            clock_id: clock_id.into(),
+            reserved: [0; 6],
+        };
+        stack_try_pin_init!(
+            let msg: ReadMsg = Message::new(req, VIRTIO_RTC_REQ_CLOCK_CAP)?
+        );
+        let msg: core::pin::Pin<&mut ReadMsg> = msg?;
+        msg.send(&self.reqvq, 0)?;
+        pr_info!("Got response! {:?}\n", msg.get_response());
+        let response: &RespRead = msg.get_response()?;
+        Ok(response.clock_reading.into())
+    }
+}
+
+impl virtio::Driver for VirtioRtcDriver {
+    type IdInfo = ();
+
+    /// The table of device ids supported by the driver.
+    const ID_TABLE: virtio::IdTable<Self::IdInfo> = &VIRTIO_RTC_TABLE;
+
+    fn probe(vdev: &virtio::Device<Core>) -> impl PinInit<Self, Error> {
+        const VQS_INFO: [VirtqueueInfo; 1] = [
+            VirtqueueInfo::new(c"requestq", false, Some(vq_requestq_callback)),
+            //VirtqueueInfo::new(c"alarmq", false, vq_callback),
+        ];
+        let init_fn = move |slot: *mut Self| {
+            pr_info!("Probe Rust virtio driver sample.\n");
+            let vqs = match vdev.find_vqs(&VQS_INFO) {
+                Ok(vqs) => {
+                    pr_info!("Found {} vqs.\n", vqs.len());
+                    vqs
+                }
+                Err(err) => {
+                    pr_info!("Could not find vqs: {err:?}.\n");
+
+                    return Err(err);
+                }
+            };
+            let reqvq = KBox::pin_init(VirtioRtcVq::new(vqs[0]), GFP_ATOMIC)?;
+            let registered_clocks =
+                KBox::pin_init(new_mutex!(KVec::with_capacity(0, GFP_KERNEL)?), GFP_KERNEL)?;
+            unsafe {
+                core::ptr::write(
+                    slot,
+                    Self {
+                        num_clocks: AtomicU16::new(0),
+                        reqvq,
+                        alarmvq: None,
+                        registered_clocks,
+                    },
+                )
+            };
+            Ok(())
+        };
+        unsafe { pin_init::pin_init_from_closure(init_fn) }
+    }
+
+    fn init(&self, vdev: &virtio::Device<Core>) -> Result {
+        vdev.ready();
+        self.num_clocks.store(self.req_cfg()?, Ordering::SeqCst);
+        for i in 0..(self.num_clocks.load(Ordering::SeqCst)) {
+            let mut is_exposed = false;
+
+            let resp = self.clock_cap(i)?;
+            let (clock_type, leap_second_smearing, flags) =
+                (resp.clock_type, resp.leap_second_smearing, resp.flags);
+            if cfg!(CONFIG_VIRTIO_RTC_CLASS)
+                && (clock_type == ClockType::Utc as u8
+                    || clock_type == ClockType::UtcSmeared as u8
+                    || clock_type == ClockType::UtcMaybeSmeared as u8)
+            {
+                // TODO:
+
+                // 	ret = viortc_init_rtc_class_clock(viortc, vio_clk_id,
+                // 					  clock_type, flags);
+                // 	if (ret < 0)
+                // 		return ret;
+                // 	if (ret > 0)
+                // 		is_exposed = true;
+                dev_warn!(vdev.as_ref(), "CONFIG_VIRTIO_RTC_CLASS TODO ");
+            }
+
+            if cfg!(CONFIG_VIRTIO_RTC_PTP) {
+                // TODO:
+
+                // 	ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type,
+                // 				    leap_second_smearing);
+                // 	if (ret < 0)
+                // 		return ret;
+                // 	if (ret > 0)
+                // 		is_exposed = true;
+                // todo!()
+                dev_warn!(vdev.as_ref(), "CONFIG_VIRTIO_RTC_PTP TODO ");
+            }
+
+            if !is_exposed {
+                dev_warn!(
+                    vdev.as_ref(),
+                    "cannot expose clock {i} (type {clock_type}, variant {leap_second_smearing}, \
+                    flags {flags}) to userspace\n"
+                );
+            }
+            let clock_reading = self.read(i)?;
+            pr_info!("#{i} clock reading = {clock_reading}\n");
+        }
+        Ok(())
+    }
+
+    fn remove(vdev: &virtio::Device<Core>, _this: Pin<&Self>) {
+        pr_info!("Removing Rust virtio driver sample.\n");
+        vdev.reset();
+        vdev.del_vqs();
+    }
+}
+
+kernel::virtio_device_table!(
+    VIRTIO_RTC_TABLE,
+    MODULE_VIRTIO_RTC_TABLE,
+    <VirtioRtcDriver as virtio::Driver>::IdInfo,
+    [(virtio::DeviceId::new(virtio::VirtioID::Clock), ())]
+);
+
+kernel::module_virtio_driver! {
+    type: VirtioRtcDriver,
+    name: "rust_virtio_rtc",
+    authors: ["Manos Pitsidianakis"],
+    description: "Rust virtio driver",
+    license: "GPL v2",
+}

-- 
2.47.3


^ permalink raw reply related

* [PATCH RFC v2 5/6] rust: impl interruptible waits for Completion
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

Allow Completion to wait interruptibly.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 rust/kernel/sync/completion.rs | 42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/sync/completion.rs b/rust/kernel/sync/completion.rs
index c50012a940a3c7a3e0edf302c8f833bdc4415200..17c9e48a5359c0c885be9ebf6843e74d5abe56e5 100644
--- a/rust/kernel/sync/completion.rs
+++ b/rust/kernel/sync/completion.rs
@@ -6,7 +6,12 @@
 //!
 //! C header: [`include/linux/completion.h`](srctree/include/linux/completion.h)
 
-use crate::{bindings, prelude::*, types::Opaque};
+use crate::{
+    bindings,
+    prelude::*,
+    time::Jiffies,
+    types::Opaque, //
+};
 
 /// Synchronization primitive to signal when a certain task has been completed.
 ///
@@ -109,4 +114,39 @@ pub fn wait_for_completion(&self) {
         // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`.
         unsafe { bindings::wait_for_completion(self.as_raw()) };
     }
+
+    /// Wait for completion of an interruptible task without a timeout.
+    ///
+    /// See also [`Completion::complete_all`].
+    #[inline]
+    pub fn wait_for_completion_interruptible(&self) -> Result {
+        // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`.
+        let err = unsafe { bindings::wait_for_completion_interruptible(self.as_raw()) };
+        if err < 0 {
+            Err(Error::from_errno(err))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Wait for completion of an interruptible task with a timeout.
+    ///
+    /// See also [`Completion::complete_all`].
+    #[inline]
+    pub fn wait_for_completion_interruptible_timeout(
+        &self,
+        timeout_jiffies: Jiffies,
+    ) -> Result<Jiffies> {
+        // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`.
+        let ret: c_long = unsafe {
+            bindings::wait_for_completion_interruptible_timeout(self.as_raw(), timeout_jiffies)
+        };
+        if ret == 0 {
+            return Err(ETIMEDOUT);
+        }
+        match Jiffies::try_from(ret) {
+            Ok(ret) => Ok(ret),
+            Err(_) => Err(Error::from_errno(ret as c_int)),
+        }
+    }
 }

-- 
2.47.3


^ permalink raw reply related

* [PATCH RFC v2 3/6] rust: add virtio module
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

Add module that exposes bindings for the virtio API.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 MAINTAINERS                     |   2 +
 rust/kernel/lib.rs              |   2 +
 rust/kernel/scatterlist.rs      |   2 +-
 rust/kernel/virtio.rs           | 419 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/virtio/utils.rs     |  63 ++++++
 rust/kernel/virtio/virtqueue.rs | 241 +++++++++++++++++++++++
 6 files changed, 728 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 48c9c666d90b5a256ab6fae1f42508b789a0ce50..e8012f708df5d4ee858c82aec3269e615fc8caad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27935,6 +27935,8 @@ M:	Manos Pitsidianakis <manos@pitsidianak.is>
 L:	virtualization@lists.linux.dev
 S:	Maintained
 F:	rust/helpers/virtio.c
+F:	rust/kernel/virtio.rs
+F:	rust/kernel/virtio/
 
 VIRTIO CRYPTO DRIVER
 M:	Gonglei <arei.gonglei@huawei.com>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d93292d47420f1f298a452ade5feefedce5ade86..061394f441dfa27f99939b5c4160e4161a7eaa1e 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -161,6 +161,8 @@
 pub mod uaccess;
 #[cfg(CONFIG_USB = "y")]
 pub mod usb;
+#[cfg(CONFIG_VIRTIO = "y")]
+pub mod virtio;
 pub mod workqueue;
 pub mod xarray;
 
diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs
index b83c468b5c63311f3a6d92f0f1bf05f6dfe12076..146e738cbd4351b41c11dd39a45e20f404c5cd64 100644
--- a/rust/kernel/scatterlist.rs
+++ b/rust/kernel/scatterlist.rs
@@ -75,7 +75,7 @@ unsafe fn from_raw<'a>(ptr: *mut bindings::scatterlist) -> &'a Self {
 
     /// Obtain the raw `struct scatterlist *`.
     #[inline]
-    fn as_raw(&self) -> *mut bindings::scatterlist {
+    pub(crate) fn as_raw(&self) -> *mut bindings::scatterlist {
         self.0.get()
     }
 
diff --git a/rust/kernel/virtio.rs b/rust/kernel/virtio.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c9de5d85c34e79965bd9d52d2f52f2d37cfd72d1
--- /dev/null
+++ b/rust/kernel/virtio.rs
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! VIRTIO abstraction.
+
+use crate::{
+    bindings,
+    device_id::RawDeviceId,
+    error::{
+        from_result,
+        to_result,
+        Error,
+        Result, //
+    },
+    ffi::c_uint,
+    prelude::*,
+    types::Opaque, //
+};
+
+use core::{
+    marker::PhantomData,
+    pin::Pin, //
+};
+
+pub mod utils;
+pub mod virtqueue;
+
+/// IdTable type for virtio drivers.
+pub type IdTable<T> = &'static dyn crate::device_id::IdTable<DeviceId, T>;
+
+/// A VIRTIO device id.
+///
+/// [`struct virtio_device_id`]: srctree/include/linux/mod_devicetable.h
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct DeviceId(bindings::virtio_device_id);
+
+// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct virtio_device_id` and
+// does not add additional invariants, so it's safe to transmute to `RawType`.
+unsafe impl RawDeviceId for DeviceId {
+    type RawType = bindings::virtio_device_id;
+}
+
+impl DeviceId {
+    #[inline]
+    /// Create a new device id
+    pub const fn new(device: VirtioID) -> Self {
+        Self::new_with_vendor(device, VIRTIO_DEV_ANY_ID)
+    }
+
+    /// Create a new device id with vendor
+    pub const fn new_with_vendor(device: VirtioID, vendor: u32) -> Self {
+        // Replace with `bindings::of_device_id::default()` once stabilized for `const`.
+        // SAFETY: FFI type is valid to be zero-initialized.
+        let mut ret: bindings::virtio_device_id = unsafe { core::mem::zeroed() };
+        ret.device = device as u32;
+        ret.vendor = vendor;
+        Self(ret)
+    }
+}
+
+/// Create a virtio `IdTable` with its alias for modpost.
+#[macro_export]
+macro_rules! virtio_device_table {
+    ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data:expr) => {
+        const $table_name: $crate::device_id::IdArray<
+            $crate::virtio::DeviceId,
+            $id_info_type,
+            { $table_data.len() },
+        > = $crate::device_id::IdArray::new_without_index($table_data);
+
+        $crate::module_device_table!("virtio", $module_table_name, $table_name);
+    };
+}
+
+/// Declares a kernel module that exposes a single virtio driver.
+#[macro_export]
+macro_rules! module_virtio_driver {
+($($f:tt)*) => {
+    $crate::module_driver!(<T>, $crate::virtio::Adapter<T>, { $($f)* });
+};
+}
+
+/// The Virtio driver trait.
+///
+/// Drivers must implement this trait in order to get a virtio driver registered.
+pub trait Driver: Send {
+    /// The type holding information about each device id supported by the driver.
+    // TODO: Use `associated_type_defaults` once stabilized:
+    //
+    // ```
+    // type IdInfo: 'static = ();
+    // ```
+    type IdInfo: 'static;
+
+    /// The table of device ids supported by the driver.
+    const ID_TABLE: IdTable<Self::IdInfo>;
+
+    /// virtio driver probe.
+    ///
+    /// Called when a new virtio device is added or discovered. Implementers should
+    /// attempt to initialize the device here, but not sleep, since driver data is set after this
+    /// method returns successfully.
+    fn probe(dev: &Device<crate::device::Core>) -> impl PinInit<Self, Error>;
+
+    /// virtio driver init.
+    ///
+    /// Called after a virtio device is probed successfully, can sleep.
+    fn init(&self, dev: &Device<crate::device::Core>) -> Result;
+
+    /// virtio driver remove.
+    ///
+    /// Called when a [`Device`] is removed from its [`Driver`]. Implementing this callback
+    /// is optional.
+    ///
+    /// This callback serves as a place for drivers to perform teardown operations that require a
+    /// `&Device<Core>` or `&Device<Bound>` reference. For instance, drivers may try to perform I/O
+    /// operations to gracefully tear down the device.
+    ///
+    /// Otherwise, release operations for driver resources should be performed in `Self::drop`.
+    fn remove(dev: &Device<crate::device::Core>, this: Pin<&Self>);
+}
+
+/// Abstraction for the virtio device structure (`struct virtio_device`).
+///
+/// [`struct virtio_device`]: srctree/include/linux/virtio.h
+#[repr(transparent)]
+pub struct Device<Ctx: crate::device::DeviceContext = crate::device::Normal>(
+    Opaque<bindings::virtio_device>,
+    PhantomData<Ctx>,
+);
+
+impl<Ctx: crate::device::DeviceContext> Device<Ctx> {
+    #[inline]
+    fn as_raw(&self) -> *mut bindings::virtio_device {
+        self.0.get()
+    }
+}
+
+// SAFETY: `virtio::Device` is a transparent wrapper of `struct virtio_device`.
+// The offset is guaranteed to point to a valid device field inside `virtio::Device`.
+unsafe impl<Ctx: crate::device::DeviceContext> crate::device::AsBusDevice<Ctx> for Device<Ctx> {
+    const OFFSET: usize = core::mem::offset_of!(bindings::virtio_device, dev);
+}
+
+// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
+// argument.
+kernel::impl_device_context_deref!(unsafe { Device });
+
+impl<Ctx: crate::device::DeviceContext> Device<Ctx> {
+    // TODO: return VirtioID
+    /// Returns the virtio device ID.
+    #[inline]
+    pub fn device_id(&self) -> u32 {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        unsafe { (*self.as_raw()).id.device }
+    }
+
+    /// Returns the virtio vendor ID.
+    #[inline]
+    pub fn vendor_id(&self) -> u32 {
+        // SAFETY: `self.as_raw` is a valid pointer to a `struct virtio_device`.
+        unsafe { (*self.as_raw()).id.vendor }
+    }
+
+    /// Reset device.
+    #[doc(alias = "virtio_reset_device")]
+    #[inline]
+    pub fn reset(&self) {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        unsafe { bindings::virtio_reset_device(self.as_raw()) }
+    }
+
+    /// Mark device as ready.
+    #[doc(alias = "virtio_device_ready")]
+    #[inline]
+    pub fn ready(&self) {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        unsafe { bindings::virtio_device_ready(self.as_raw()) }
+    }
+
+    /// Return virtqueues for this device.
+    #[doc(alias = "virtio_find_vqs")]
+    pub fn find_vqs(
+        &self,
+        info: &[virtqueue::VirtqueueInfo],
+    ) -> Result<KVec<*mut virtqueue::Virtqueue>> {
+        let mut vqs = KVec::with_capacity(info.len(), GFP_KERNEL)?;
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        to_result(unsafe {
+            bindings::virtio_find_vqs(
+                self.as_raw(),
+                info.len().try_into()?,
+                vqs.spare_capacity_mut().as_mut_ptr().cast(),
+                info.as_ptr().cast_mut().cast(),
+                core::ptr::null_mut(),
+            )
+        })?;
+        // SAFETY: virtio_find_vqs returned successfully so `vqs` must be populated.
+        unsafe { vqs.inc_len(info.len()) };
+        Ok(vqs)
+    }
+
+    /// Delete virtqueues from this device.
+    pub fn del_vqs(&self) {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        let config = unsafe { (*self.as_raw()).config };
+        // SAFETY: `config` points to a valid virtqueue config struct.
+        if let Some(del_vqs) = unsafe { (*config).del_vqs } {
+            // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+            // `struct virtio_device`.
+            unsafe { del_vqs(self.as_raw()) }
+        }
+    }
+
+    /// Checks if the device has a feature bit.
+    #[inline]
+    pub fn has_feature(&self, fbit: c_uint) -> bool {
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        unsafe { bindings::virtio_has_feature(self.as_raw(), fbit) }
+    }
+
+    /// Return all feature bits for this device.
+    #[inline]
+    pub fn get_features(&self) -> u64 {
+        let mut features = 0;
+        // SAFETY: By its type invariant `self.as_raw` is always a valid pointer to a
+        // `struct virtio_device`.
+        unsafe { bindings::virtio_get_features(self.as_raw(), &raw mut features) };
+        features
+    }
+}
+
+impl<Ctx: crate::device::DeviceContext> AsRef<crate::device::Device<Ctx>> for Device<Ctx> {
+    fn as_ref(&self) -> &crate::device::Device<Ctx> {
+        // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
+        // `struct virtio_device`.
+        let dev = unsafe { core::ptr::addr_of_mut!((*self.as_raw()).dev) };
+
+        // SAFETY: `dev` points to a valid `struct device`.
+        unsafe { crate::device::Device::from_raw(dev) }
+    }
+}
+
+/// An adapter for the registration of virtio drivers.
+pub struct Adapter<T: Driver>(T);
+
+// SAFETY:
+// - `bindings::virtio_driver` is a C type declared as `repr(C)`.
+// - `T` is the type of the driver's device private data.
+// - `struct virtio_driver` embeds a `struct device_driver`.
+// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`.
+unsafe impl<T: Driver + 'static> crate::driver::DriverLayout for Adapter<T> {
+    type DriverType = bindings::virtio_driver;
+    type DriverData = T;
+    const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver);
+}
+
+// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if
+// a preceding call to `register` has been successful.
+unsafe impl<T: Driver + 'static> crate::driver::RegistrationOps for Adapter<T> {
+    unsafe fn register(
+        vdrv: &Opaque<Self::DriverType>,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result {
+        // SAFETY: It's safe to set the fields of `struct virtio_driver` on initialization.
+        unsafe {
+            (*vdrv.get()).driver.name = name.as_char_ptr();
+            (*vdrv.get()).id_table = T::ID_TABLE.as_ptr();
+            (*vdrv.get()).probe = Some(Self::probe_callback);
+            (*vdrv.get()).remove = Some(Self::remove_callback);
+        }
+
+        // SAFETY: `vdrv` is guaranteed to be a valid `DriverType`.
+        to_result(unsafe { bindings::__register_virtio_driver(vdrv.get(), module.0) })
+    }
+
+    unsafe fn unregister(vdrv: &Opaque<Self::DriverType>) {
+        // SAFETY: `vdrv` is guaranteed to be a valid `DriverType`.
+        unsafe { bindings::unregister_virtio_driver(vdrv.get()) }
+    }
+}
+
+impl<T: Driver + 'static> Adapter<T> {
+    extern "C" fn probe_callback(vdev: *mut bindings::virtio_device) -> c_int {
+        // SAFETY: The kernel only ever calls the probe callback with a valid pointer to a `struct
+        // virtio_device`.
+        //
+        // INVARIANT: `vdev` is valid for the duration of `probe_callback()`.
+        let dev = unsafe { &*vdev.cast::<Device<crate::device::CoreInternal>>() };
+        from_result(|| {
+            let data = T::probe(dev);
+
+            dev.as_ref().set_drvdata(data)?;
+            // SAFETY: `Device::set_drvdata()` was just called so it's safe to borrow the data.
+            let data = unsafe { dev.as_ref().drvdata_borrow::<T>() };
+            if let Err(err) = T::init(&data, dev) {
+                // SAFETY: `Device::set_drvdata()` was just called so it's safe to re-obtain the
+                // data.
+                let data = unsafe { dev.as_ref().drvdata_obtain::<T>() }.unwrap();
+                T::remove(dev, data.as_ref());
+                drop(data);
+                return Err(err);
+            }
+            Ok(0)
+        })
+    }
+
+    extern "C" fn remove_callback(vdev: *mut bindings::virtio_device) {
+        // SAFETY: The kernel only ever calls the remove callback with a valid pointer to a `struct
+        // virtio_device`.
+        //
+        // INVARIANT: `vdev` is valid for the duration of `remove_callback()`.
+        let dev = unsafe { &*vdev.cast::<Device<crate::device::CoreInternal>>() };
+
+        // SAFETY: `remove_callback` is only ever called after a successful call to
+        // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called
+        // and stored a `Pin<KBox<T>>`.
+        let data = unsafe { dev.as_ref().drvdata_borrow::<T>() };
+
+        T::remove(dev, data);
+    }
+}
+
+/// Any vendor
+pub const VIRTIO_DEV_ANY_ID: u32 = 0xffffffff;
+
+/// Virtio IDs
+///
+/// C header: [`include/uapi/linux/virtio_ids.h`](srctree/include/uapi/linux/virtio_ids.h)
+#[repr(u32)]
+pub enum VirtioID {
+    /// virtio net
+    Net = bindings::VIRTIO_ID_NET,
+    /// virtio block
+    Block = bindings::VIRTIO_ID_BLOCK,
+    /// virtio console
+    Console = bindings::VIRTIO_ID_CONSOLE,
+    /// virtio rng
+    Rng = bindings::VIRTIO_ID_RNG,
+    /// virtio balloon
+    Balloon = bindings::VIRTIO_ID_BALLOON,
+    /// virtio ioMemory
+    IOMem = bindings::VIRTIO_ID_IOMEM,
+    /// virtio remote processor messaging
+    RPMSG = bindings::VIRTIO_ID_RPMSG,
+    /// virtio scsi
+    Scsi = bindings::VIRTIO_ID_SCSI,
+    /// 9p virtio console
+    NineP = bindings::VIRTIO_ID_9P,
+    /// virtio WLAN MAC
+    Mac80211Wlan = bindings::VIRTIO_ID_MAC80211_WLAN,
+    /// virtio remoteproc serial link
+    RPROCSerial = bindings::VIRTIO_ID_RPROC_SERIAL,
+    /// Virtio caif
+    CAIF = bindings::VIRTIO_ID_CAIF,
+    /// virtio memory balloon
+    MemoryBalloon = bindings::VIRTIO_ID_MEMORY_BALLOON,
+    /// virtio GPU
+    GPU = bindings::VIRTIO_ID_GPU,
+    /// virtio clock/timer
+    Clock = bindings::VIRTIO_ID_CLOCK,
+    /// virtio input
+    Input = bindings::VIRTIO_ID_INPUT,
+    /// virtio vsock transport
+    VSock = bindings::VIRTIO_ID_VSOCK,
+    /// virtio crypto
+    Crypto = bindings::VIRTIO_ID_CRYPTO,
+    /// virtio signal distribution device
+    SignalDist = bindings::VIRTIO_ID_SIGNAL_DIST,
+    /// virtio pstore device
+    Pstore = bindings::VIRTIO_ID_PSTORE,
+    /// virtio IOMMU
+    Iommu = bindings::VIRTIO_ID_IOMMU,
+    /// virtio mem
+    Mem = bindings::VIRTIO_ID_MEM,
+    /// virtio sound
+    Sound = bindings::VIRTIO_ID_SOUND,
+    /// virtio filesystem
+    FS = bindings::VIRTIO_ID_FS,
+    /// virtio pmem
+    PMem = bindings::VIRTIO_ID_PMEM,
+    /// virtio rpmb
+    RPMB = bindings::VIRTIO_ID_RPMB,
+    /// virtio mac80211-hwsim
+    Mac80211Hwsim = bindings::VIRTIO_ID_MAC80211_HWSIM,
+    /// virtio video encoder
+    VideoEncoder = bindings::VIRTIO_ID_VIDEO_ENCODER,
+    /// virtio video decoder
+    VideoDecoder = bindings::VIRTIO_ID_VIDEO_DECODER,
+    /// virtio SCMI
+    SCMI = bindings::VIRTIO_ID_SCMI,
+    /// virtio nitro secure module
+    NitroSecMod = bindings::VIRTIO_ID_NITRO_SEC_MOD,
+    /// virtio i2c adapter
+    I2CAdapter = bindings::VIRTIO_ID_I2C_ADAPTER,
+    /// virtio watchdog
+    Watchdog = bindings::VIRTIO_ID_WATCHDOG,
+    /// virtio can
+    CAN = bindings::VIRTIO_ID_CAN,
+    /// virtio dmabuf
+    DMABuf = bindings::VIRTIO_ID_DMABUF,
+    /// virtio parameter server
+    ParamServ = bindings::VIRTIO_ID_PARAM_SERV,
+    /// virtio audio policy
+    AudioPolicy = bindings::VIRTIO_ID_AUDIO_POLICY,
+    /// virtio bluetooth
+    BT = bindings::VIRTIO_ID_BT,
+    /// virtio gpio
+    GPIO = bindings::VIRTIO_ID_GPIO,
+    /// virtio spi
+    SPI = bindings::VIRTIO_ID_SPI,
+}
diff --git a/rust/kernel/virtio/utils.rs b/rust/kernel/virtio/utils.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bec8e0c38d7ada95584b99f70adb2328e67ecb02
--- /dev/null
+++ b/rust/kernel/virtio/utils.rs
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Helper types and utilities
+
+macro_rules! endian_type {
+    (le $old_type:ident, $new_type:ident) => {
+        endian_type!($old_type, $new_type, to_le, from_le);
+    };
+    (be $old_type:ident, $new_type:ident) => {
+        endian_type!($old_type, $new_type, to_be, from_be);
+    };
+    ($old_type:ident, $new_type:ident, $to_new:ident, $from_new:ident) => {
+        /// An unsigned integer type of with an explicit endianness.
+        #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, pin_init::Zeroable)]
+        #[repr(transparent)]
+        pub struct $new_type($old_type);
+
+        $crate::static_assert!(
+            ::core::mem::align_of::<$new_type>() == ::core::mem::align_of::<$old_type>()
+        );
+        $crate::static_assert!(
+            ::core::mem::size_of::<$new_type>() == ::core::mem::size_of::<$old_type>()
+        );
+
+        impl $new_type {
+            /// Convert to CPU/native endianness.
+            pub const fn to_cpu(self) -> $old_type {
+                $old_type::$from_new(self.0)
+            }
+        }
+
+        impl PartialEq<$old_type> for $new_type {
+            fn eq(&self, other: &$old_type) -> bool {
+                self.0 == $old_type::$to_new(*other)
+            }
+        }
+
+        impl PartialEq<$new_type> for $old_type {
+            fn eq(&self, other: &$new_type) -> bool {
+                $old_type::$to_new(other.0) == *self
+            }
+        }
+
+        impl From<$new_type> for $old_type {
+            fn from(v: $new_type) -> $old_type {
+                v.to_cpu()
+            }
+        }
+
+        impl From<$old_type> for $new_type {
+            fn from(v: $old_type) -> $new_type {
+                $new_type($old_type::$to_new(v))
+            }
+        }
+    };
+}
+
+endian_type!(u16, Le16, to_le, from_le);
+endian_type!(u32, Le32, to_le, from_le);
+endian_type!(u64, Le64, to_le, from_le);
+endian_type!(u16, Be16, to_be, from_be);
+endian_type!(u32, Be32, to_be, from_be);
+endian_type!(u64, Be64, to_be, from_be);
diff --git a/rust/kernel/virtio/virtqueue.rs b/rust/kernel/virtio/virtqueue.rs
new file mode 100644
index 0000000000000000000000000000000000000000..10186b4fa94be6786a7d203d32d4f79c121b5383
--- /dev/null
+++ b/rust/kernel/virtio/virtqueue.rs
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Virtqueue functionality.
+
+use crate::{
+    alloc::{
+        Flags, //
+    },
+    bindings,
+    device::Bound,
+    error::{
+        code::{
+            EINVAL,
+            ENOENT, //
+        },
+        to_result,
+        Result, //
+    },
+    scatterlist::SGEntry,
+    str::CStr,
+    types::Opaque,
+    virtio::Device, //
+};
+
+use core::{
+    ffi::c_uint,
+    ptr::NonNull, //
+};
+
+/// Info for a virtqueue.
+///
+/// [`struct virtqueue_info`]: srctree/include/linux/virtio_config.h
+#[doc(alias = "virtqueue_info")]
+#[repr(transparent)]
+pub struct VirtqueueInfo(Opaque<bindings::virtqueue_info>);
+
+impl VirtqueueInfo {
+    /// Create a new [`VirtqueueInfo`]
+    pub const fn new(
+        name: &'static CStr,
+        ctx: bool,
+        callback: Option<unsafe extern "C" fn(*mut bindings::virtqueue)>,
+    ) -> Self {
+        Self(Opaque::new(bindings::virtqueue_info {
+            name: name.as_ptr(),
+            ctx,
+            callback,
+        }))
+    }
+}
+
+/// An opaque handler for a virtqueue.
+///
+/// [`struct virtqueue`]: srctree/include/linux/virtio.h
+#[repr(transparent)]
+pub struct Virtqueue(Opaque<bindings::virtqueue>);
+
+impl Virtqueue {
+    /// Create a [`Virtqueue`] from a raw pointer.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that `ptr` is a properly initialized valid `virtqueue` pointer.
+    #[inline]
+    pub unsafe fn from_raw<'a>(ptr: *mut bindings::virtqueue) -> &'a Self {
+        // SAFETY: The safety requirements of this function guarantee that `ptr` is a valid
+        // pointer to a `struct virtqueue` for the duration of `'a`.
+        unsafe { &*ptr.cast() }
+    }
+
+    /// Obtain the raw `struct virtqueue *`.
+    #[inline]
+    pub(crate) fn as_raw(&self) -> *mut bindings::virtqueue {
+        self.0.get()
+    }
+
+    /// Get the [`Device`] associated with this virtqueue.
+    #[inline]
+    pub fn dev(&self) -> Result<&Device<Bound>> {
+        // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct
+        // virtqueue`.
+        if unsafe { (*self.as_raw()).vdev }.is_null() {
+            return Err(ENOENT);
+        }
+        // SAFETY: the pointer has been promised to be valid when self was created
+        Ok(unsafe { &*(&*self.as_raw()).vdev.cast::<Device<Bound>>() })
+    }
+
+    /// Get the vring size.
+    #[inline]
+    #[doc(alias = "virtqueue_get_vring_size")]
+    pub fn vring_size(&self) -> u32 {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_get_vring_size(self.as_raw()) }
+    }
+
+    /// Notify virtqueue.
+    #[inline]
+    #[doc(alias = "virtqueue_notify")]
+    pub fn notify(&self) -> bool {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_notify(self.as_raw()) }
+    }
+
+    /// Kick and prepare virtqueue.
+    #[inline]
+    #[doc(alias = "virtqueue_kick_prepare")]
+    pub fn kick_prepare(&self) -> bool {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_kick_prepare(self.as_raw()) }
+    }
+
+    /// Kick virtqueue.
+    #[inline]
+    #[doc(alias = "virtqueue_kick")]
+    pub fn kick(&self) -> bool {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_kick(self.as_raw()) }
+    }
+
+    /// Enable virtqueue's callback.
+    #[inline]
+    #[doc(alias = "virtqueue_enable_cb")]
+    pub fn enable_cb(&self) -> bool {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_enable_cb(self.as_raw()) }
+    }
+
+    /// Disable virtqueue's callback.
+    #[inline]
+    #[doc(alias = "virtqueue_disable_cb")]
+    pub fn disable_cb(&self) {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        unsafe { bindings::virtqueue_disable_cb(self.as_raw()) }
+    }
+
+    /// Get a buffer from the virtqueue, if available.
+    #[inline]
+    #[doc(alias = "virtqueue_get_buf")]
+    pub fn get_buf(&'_ self) -> Option<(NonNull<u8>, u32)> {
+        let mut len = 0;
+        // SAFETY: the pointer has been promised to be valid when self was created
+        let ptr = unsafe { bindings::virtqueue_get_buf(self.as_raw(), &mut len) };
+        Some((NonNull::new(ptr.cast())?, len))
+    }
+
+    /// Make a device write-only buffer available.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure:
+    ///
+    /// - the data pointed to by `sg` and `token` will live as long as the buffer lives in the
+    ///   virtqueue
+    /// - the driver deallocates any data pointed to by `sg` and `token` on error
+    /// - the driver deallocates any data pointed to by `sg` and `token` when it receives a
+    ///   virtqueue response
+    /// - the driver deallocates any data pointed to by `sg` and `token` when it resets the device
+    #[inline]
+    #[doc(alias = "virtqueue_add_inbuf")]
+    pub unsafe fn add_inbuf(&'_ self, sg: &SGEntry, token: NonNull<u8>, gfp: Flags) -> Result {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        to_result(unsafe {
+            bindings::virtqueue_add_inbuf(
+                self.as_raw(),
+                sg.as_raw(),
+                1,
+                token.as_ptr().cast(),
+                gfp.as_raw(),
+            )
+        })
+    }
+
+    /// Make a device read-only buffer available.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure:
+    ///
+    /// - the data pointed to by `sg` and `token` will live as long as the buffer lives in the
+    ///   virtqueue
+    /// - the driver deallocates any data pointed to by `sg` and `token` on error
+    /// - the driver deallocates any data pointed to by `sg` and `token` when it receives a
+    ///   virtqueue response
+    /// - the driver deallocates any data pointed to by `sg` and `token` when it resets the device
+    #[inline]
+    #[doc(alias = "virtqueue_add_outbuf")]
+    pub unsafe fn add_outbuf(&'_ self, sg: &SGEntry, token: NonNull<u8>, gfp: Flags) -> Result {
+        // SAFETY: the pointer has been promised to be valid when self was created
+        to_result(unsafe {
+            bindings::virtqueue_add_outbuf(
+                self.as_raw(),
+                sg.as_raw(),
+                1,
+                token.as_ptr().cast(),
+                gfp.as_raw(),
+            )
+        })
+    }
+
+    /// Add a list of scatter-gather lists to virtqueue.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure:
+    ///
+    /// - the data pointed to by `sgs` and `token` will live as long as the buffer lives in the
+    ///   virtqueue
+    /// - the driver deallocates any data pointed to by `sgs` and `token` on error
+    /// - the driver deallocates any data pointed to by `sgs` and `token` when it receives a
+    ///   virtqueue response
+    /// - the driver deallocates any data pointed to by `sgs` and `token` when it resets the device
+    #[inline]
+    #[doc(alias = "virtqueue_add_sgs")]
+    pub unsafe fn add_sgs(
+        &'_ self,
+        sgs: &[&SGEntry],
+        out_sgs: c_uint,
+        in_sgs: c_uint,
+        token: NonNull<u8>,
+        gfp: Flags,
+    ) -> Result {
+        let Some(total_size) = out_sgs.checked_add(in_sgs) else {
+            return Err(EINVAL);
+        };
+        if usize::try_from(total_size) != Ok(sgs.len()) {
+            return Err(EINVAL);
+        }
+        // SAFETY: the pointer has been promised to be valid when self was created
+        to_result(unsafe {
+            bindings::virtqueue_add_sgs(
+                self.as_raw(),
+                sgs.as_ptr().cast_mut().cast(),
+                out_sgs,
+                in_sgs,
+                token.as_ptr().cast(),
+                gfp.as_raw(),
+            )
+        })
+    }
+}

-- 
2.47.3


^ permalink raw reply related

* [PATCH RFC v2 4/6] rust/scatterlist: add SGEntry::init_one
From: Manos Pitsidianakis @ 2026-05-09 14:46 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: Manos Pitsidianakis, Peter Hilber, Stefano Garzarella,
	Stefan Hajnoczi, Viresh Kumar, Michael S. Tsirkin, Boqun Feng,
	Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
	Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux,
	Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
	linux-kernel, Manos Pitsidianakis
In-Reply-To: <20260509-rust-virtio-v2-0-c1e30ec2bd21@pitsidianak.is>

Add a method to allow creation of an SGEntry with borrowed data.
Analogous of `sg_init_one` in C.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
---
 rust/kernel/scatterlist.rs | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/rust/kernel/scatterlist.rs b/rust/kernel/scatterlist.rs
index 146e738cbd4351b41c11dd39a45e20f404c5cd64..b065762af1a734fcdd5d3acde89199f1a626fd89 100644
--- a/rust/kernel/scatterlist.rs
+++ b/rust/kernel/scatterlist.rs
@@ -95,6 +95,24 @@ pub fn dma_len(&self) -> ResourceSize {
         // SAFETY: `self.as_raw()` is a valid pointer to a `struct scatterlist`.
         unsafe { bindings::sg_dma_len(self.as_raw()) }.into()
     }
+
+    /// Initialize a new entry with borrowed data.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that:
+    /// - `buf` is a valid pointer
+    /// - `buf_len` describes a valid allocation size for this pointer
+    pub unsafe fn init_one(
+        val: &'_ mut core::mem::MaybeUninit<bindings::scatterlist>,
+        buf: NonNull<u8>,
+        buf_len: u32,
+    ) -> &'_ Self {
+        // SAFETY: `val` points to a correctly sized `struct scatterlist`.
+        unsafe { bindings::sg_init_one(val.as_mut_ptr(), buf.as_ptr().cast(), buf_len) };
+        // SAFETY: `val` points to an initialized `struct scatterlist`.
+        unsafe { Self::from_raw(val.as_mut_ptr()) }
+    }
 }
 
 /// The borrowed generic type of an [`SGTable`], representing a borrowed or externally managed

-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH net-next v11 1/4] tun/tap: add ptr_ring consume helper with netdev queue wakeup
From: Simon Schippers @ 2026-05-09 16:31 UTC (permalink / raw)
  To: willemdebruijn.kernel, jasowang, andrew+netdev, davem, edumazet,
	kuba, pabeni, mst, eperezma, leiyang, stephen, jon, tim.gebauer,
	netdev, linux-kernel, kvm, virtualization
In-Reply-To: <20260508151048.183125-2-simon.schippers@tu-dortmund.de>

On 5/8/26 17:10, Simon Schippers wrote:
> +static void tun_queue_purge(struct tun_struct *tun, struct tun_file *tfile)
>  {
>  	void *ptr;
>  
> -	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
> +	while ((ptr = tun_ring_consume(tun, tfile)) != NULL)
>  		tun_ptr_free(ptr);
>  
>  	skb_queue_purge(&tfile->sk.sk_write_queue);

Sashiko is right once again. tun_ring_consume() in tun_queue_purge()
operates on a tfile that is being torn down. Its queue_index is no
longer valid. After the swap in __tun_detach(), it points to the
netdev subqueue of a different tfile.
--> We should not wake there.

I will swap tun_ring_consume() with ptr_ring_consume() again and
submit a v12 :)


^ permalink raw reply

* Re: [PATCH net 0/2] vsock/virtio: fix vsockmon tap skb construction
From: Michael S. Tsirkin @ 2026-05-09 19:38 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: netdev, Yiqi Sun, linux-kernel, Xuan Zhuo, Stefan Hajnoczi, kvm,
	Simon Horman, Bobby Eshleman, Jason Wang, Jakub Kicinski,
	David S. Miller, virtualization, Eric Dumazet, Paolo Abeni,
	Arseniy Krasnov, Eugenio Pérez, Bobby Eshleman
In-Reply-To: <20260508164411.261440-1-sgarzare@redhat.com>

On Fri, May 08, 2026 at 06:44:09PM +0200, Stefano Garzarella wrote:
> While reviewing the patch posted by Yiqi Sun [1] to fix an issue in
> virtio_transport_build_skb(), I discovered another issue related to
> the offset and length of the payload to be copied in the new skb.
> This was introduced when we did the skb conversion, and fixed by
> patch 1.
> 
> Patch 2 fixes the issue found by Yiqi Sun in a different way: using
> iov_iter_kvec() to properly initialize all the iov_iter fields and
> removing the linear vs non-linear split like we alredy do in
> vhost-vsock.
> 
> It could have been a single patch, but since there were two affected
> commits, I decided to keep the fixes separate.
> 
> [1] https://lore.kernel.org/netdev/20260430071110.380509-1-sunyiqixm@gmail.com/


Acked-by: Michael S. Tsirkin <mst@redhat.com>

> Stefano Garzarella (2):
>   vsock/virtio: fix length and offset in tap skb for split packets
>   vsock/virtio: fix empty payload in tap skb for non-linear buffers
> 
>  net/vmw_vsock/virtio_transport_common.c | 47 +++++++++----------------
>  1 file changed, 16 insertions(+), 31 deletions(-)
> 
> -- 
> 2.54.0


^ permalink raw reply

* Re: [PATCH net-next v11 1/4] tun/tap: add ptr_ring consume helper with netdev queue wakeup
From: Michael S. Tsirkin @ 2026-05-09 22:44 UTC (permalink / raw)
  To: Simon Schippers
  Cc: willemdebruijn.kernel, jasowang, andrew+netdev, davem, edumazet,
	kuba, pabeni, eperezma, leiyang, stephen, jon, tim.gebauer,
	netdev, linux-kernel, kvm, virtualization
In-Reply-To: <dd0a3ef1-d623-4e09-a391-b56fa4bf95af@tu-dortmund.de>

On Sat, May 09, 2026 at 06:31:47PM +0200, Simon Schippers wrote:
> On 5/8/26 17:10, Simon Schippers wrote:
> > +static void tun_queue_purge(struct tun_struct *tun, struct tun_file *tfile)
> >  {
> >  	void *ptr;
> >  
> > -	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
> > +	while ((ptr = tun_ring_consume(tun, tfile)) != NULL)
> >  		tun_ptr_free(ptr);
> >  
> >  	skb_queue_purge(&tfile->sk.sk_write_queue);
> 
> Sashiko is right once again. tun_ring_consume() in tun_queue_purge()
> operates on a tfile that is being torn down. Its queue_index is no
> longer valid. After the swap in __tun_detach(), it points to the
> netdev subqueue of a different tfile.
> --> We should not wake there.

Does it not exactly point at ntfile which is what we want to wake?


> I will swap tun_ring_consume() with ptr_ring_consume() again and
> submit a v12 :)

If so then maybe
netif_tx_wake_queue(netdev_get_tx_queue(tun->dev, index));



-- 
MST


^ permalink raw reply

* [PATCH] drm/virtio: check virtio_gpu_array_lock_resv() return in cursor update
From: Deepanshu Kartikey @ 2026-05-10  5:30 UTC (permalink / raw)
  To: airlied, kraxel, dmitry.osipenko, gurchetansingh, olvaffe,
	maarten.lankhorst, mripard, tzimmermann, simona, sumit.semwal,
	christian.koenig
  Cc: dri-devel, virtualization, linux-kernel, linux-media,
	linaro-mm-sig, Deepanshu Kartikey, syzbot+72bd3dd3a5d5f39a0271,
	stable

virtio_gpu_cursor_plane_update() calls virtio_gpu_array_lock_resv()
but ignores its return value. The function can fail in two ways:

  - dma_resv_lock_interruptible() returns -ERESTARTSYS when a signal
    is delivered while waiting for the reservation lock.
  - dma_resv_reserve_fences() returns -ENOMEM if it fails to allocate
    a fence slot; in this case lock_resv unlocks before returning.

In both cases the resv lock is not held on return. The cursor path
proceeds to queue a fenced transfer command. The queue path then
walks the object array and calls dma_resv_add_fence() on the cursor
BO's reservation. dma_resv_add_fence() requires the resv lock to be
held; with lockdep enabled the missing lock trips
dma_resv_assert_held():

  WARNING: drivers/dma-buf/dma-resv.c:296 at dma_resv_add_fence+0x71e/0x840
  Call Trace:
   virtio_gpu_array_add_fence+0xcd/0x140
   virtio_gpu_queue_ctrl_sgs
   virtio_gpu_queue_fenced_ctrl_buffer+0x578/0xfb0
   virtio_gpu_cursor_plane_update+0x411/0xbc0
   drm_atomic_helper_commit_planes+0x497/0xf10
   ...
   drm_mode_cursor_ioctl+0xd4/0x110
   drm_ioctl+0x5e6/0xc60
   __x64_sys_ioctl+0x18e/0x210

Beyond the WARN, mutating the dma_resv fence list without the lock
races with concurrent readers/writers and can corrupt the list.

Check the return value of virtio_gpu_array_lock_resv(). On failure,
drop the references taken by virtio_gpu_array_add_obj() with
virtio_gpu_array_put_free() (which does not unlock, matching the
not-locked state) and return without queueing the command. A
skipped cursor frame is harmless; the WARN and the underlying race
are not.

The bug was reported by syzbot, triggered via fault injection
(fail_nth) on the DRM_IOCTL_MODE_CURSOR path, which forces the
-ENOMEM branch in dma_resv_reserve_fences().

Reported-by: syzbot+72bd3dd3a5d5f39a0271@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=72bd3dd3a5d5f39a0271
Fixes: 5cfd31c5b3a3 ("drm/virtio: fix virtio_gpu_cursor_plane_update().")
Cc: stable@vger.kernel.org
Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
---
 drivers/gpu/drm/virtio/virtgpu_plane.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c
index a126d1b25f46..ca379b08b9ec 100644
--- a/drivers/gpu/drm/virtio/virtgpu_plane.c
+++ b/drivers/gpu/drm/virtio/virtgpu_plane.c
@@ -459,7 +459,10 @@ static void virtio_gpu_cursor_plane_update(struct drm_plane *plane,
 		if (!objs)
 			return;
 		virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]);
-		virtio_gpu_array_lock_resv(objs);
+		if (virtio_gpu_array_lock_resv(objs)) {
+			virtio_gpu_array_put_free(objs);
+			return;
+		}
 		virtio_gpu_cmd_transfer_to_host_2d
 			(vgdev, 0,
 			 plane->state->crtc_w,
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net-next v11 1/4] tun/tap: add ptr_ring consume helper with netdev queue wakeup
From: Simon Schippers @ 2026-05-10  7:03 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: willemdebruijn.kernel, jasowang, andrew+netdev, davem, edumazet,
	kuba, pabeni, eperezma, leiyang, stephen, jon, tim.gebauer,
	netdev, linux-kernel, kvm, virtualization
In-Reply-To: <20260509183518-mutt-send-email-mst@kernel.org>

On 5/10/26 00:44, Michael S. Tsirkin wrote:
> On Sat, May 09, 2026 at 06:31:47PM +0200, Simon Schippers wrote:
>> On 5/8/26 17:10, Simon Schippers wrote:
>>> +static void tun_queue_purge(struct tun_struct *tun, struct tun_file *tfile)
>>>  {
>>>  	void *ptr;
>>>  
>>> -	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
>>> +	while ((ptr = tun_ring_consume(tun, tfile)) != NULL)
>>>  		tun_ptr_free(ptr);
>>>  
>>>  	skb_queue_purge(&tfile->sk.sk_write_queue);
>>
>> Sashiko is right once again. tun_ring_consume() in tun_queue_purge()
>> operates on a tfile that is being torn down. Its queue_index is no
>> longer valid. After the swap in __tun_detach(), it points to the
>> netdev subqueue of a different tfile.
>> --> We should not wake there.
> 
> Does it not exactly point at ntfile which is what we want to wake?
> 

I see your point. But calling tun_ring_consume() as done here is
wrong, because it does not wake if the tx_ring of the tfile 
(that is currently torn down) is empty. We could change
tun_ring_consume() to call __tun_wake_queue()
with consumed=0 if !ptr but I think this would slow down the consumer
path.

> 
>> I will swap tun_ring_consume() with ptr_ring_consume() again and
>> submit a v12 :)
> 
> If so then maybe
> netif_tx_wake_queue(netdev_get_tx_queue(tun->dev, index));
> 

But we should only do this if there is space in the ntfile.
My approach:

@@ -586,12 +588,18 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
 		BUG_ON(index >= tun->numqueues);
 
 		rcu_assign_pointer(tun->tfiles[index],
 				   tun->tfiles[tun->numqueues - 1]);
 		ntfile = rtnl_dereference(tun->tfiles[index]);
+		spin_lock(&ntfile->tx_ring.consumer_lock);
 		ntfile->queue_index = index;
 		ntfile->xdp_rxq.queue_index = index;
+		ntfile->cons_cnt = 0;
+		if (__ptr_ring_empty(&ntfile->tx_ring)) {
+			netif_wake_subqueue(tun->dev, index);
+		}
+		spin_unlock(&ntfile->tx_ring.consumer_lock);
 		rcu_assign_pointer(tun->tfiles[tun->numqueues - 1],
 				   NULL);

ntfile->cons_cnt is unvalid, because the new queue might not be stopped.
That is the reason why I reset it to 0.

^ permalink raw reply

* Re: [PATCH net-next v11 1/4] tun/tap: add ptr_ring consume helper with netdev queue wakeup
From: Simon Schippers @ 2026-05-10  8:55 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: willemdebruijn.kernel, jasowang, andrew+netdev, davem, edumazet,
	kuba, pabeni, eperezma, leiyang, stephen, jon, tim.gebauer,
	netdev, linux-kernel, kvm, virtualization
In-Reply-To: <a14b700a-94e8-44c0-bde0-d8307761c0f1@tu-dortmund.de>

On 5/10/26 09:03, Simon Schippers wrote:
> On 5/10/26 00:44, Michael S. Tsirkin wrote:
>> On Sat, May 09, 2026 at 06:31:47PM +0200, Simon Schippers wrote:
>>> On 5/8/26 17:10, Simon Schippers wrote:
>>>> +static void tun_queue_purge(struct tun_struct *tun, struct tun_file *tfile)
>>>>  {
>>>>  	void *ptr;
>>>>  
>>>> -	while ((ptr = ptr_ring_consume(&tfile->tx_ring)) != NULL)
>>>> +	while ((ptr = tun_ring_consume(tun, tfile)) != NULL)
>>>>  		tun_ptr_free(ptr);
>>>>  
>>>>  	skb_queue_purge(&tfile->sk.sk_write_queue);
>>>
>>> Sashiko is right once again. tun_ring_consume() in tun_queue_purge()
>>> operates on a tfile that is being torn down. Its queue_index is no
>>> longer valid. After the swap in __tun_detach(), it points to the
>>> netdev subqueue of a different tfile.
>>> --> We should not wake there.
>>
>> Does it not exactly point at ntfile which is what we want to wake?
>>
> 
> I see your point. But calling tun_ring_consume() as done here is
> wrong, because it does not wake if the tx_ring of the tfile 
> (that is currently torn down) is empty. We could change
> tun_ring_consume() to call __tun_wake_queue()
> with consumed=0 if !ptr but I think this would slow down the consumer
> path.
>

My statement is wrong:
There is no way that the tx_ring is empty and the queue is stopped
at the same time. So we do not need to touch tun_ring_consume() and
this works just fine.

>>
>>> I will swap tun_ring_consume() with ptr_ring_consume() again and
>>> submit a v12 :)
>>
>> If so then maybe
>> netif_tx_wake_queue(netdev_get_tx_queue(tun->dev, index));
>>
> 
> But we should only do this if there is space in the ntfile.
> My approach:
> 
> @@ -586,12 +588,18 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
>  		BUG_ON(index >= tun->numqueues);
>  
>  		rcu_assign_pointer(tun->tfiles[index],
>  				   tun->tfiles[tun->numqueues - 1]);
>  		ntfile = rtnl_dereference(tun->tfiles[index]);
> +		spin_lock(&ntfile->tx_ring.consumer_lock);
>  		ntfile->queue_index = index;
>  		ntfile->xdp_rxq.queue_index = index;
> +		ntfile->cons_cnt = 0;
> +		if (__ptr_ring_empty(&ntfile->tx_ring)) {
> +			netif_wake_subqueue(tun->dev, index);
> +		}
> +		spin_unlock(&ntfile->tx_ring.consumer_lock);
>  		rcu_assign_pointer(tun->tfiles[tun->numqueues - 1],
>  				   NULL);
> 
> ntfile->cons_cnt is unvalid, because the new queue might not be stopped.
> That is the reason why I reset it to 0.

However, I still prefer this approach because the code is easier to
understand.


^ permalink raw reply


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