dri-devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  5:59 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
@ 2026-09-18  5:59 ` Honglei Huang
  2026-09-18  6:16   ` sashiko-bot
  0 siblings, 1 reply; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  5:59 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add userptr blob objects so the guest kernel can pin an existing
userspace mapping and advertise it as CREATE_BLOB backing entries.

- New virtio_gpu_object_userptr type for userptr resources
- Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
- Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
- DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
  required; use DMA_TO_DEVICE for HINT_USERPTR_RDONLY
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Keep pages pinned until RESOURCE_UNREF is queued; drop them from
  cleanup_object() on the unref response or on create failure
- Clear userptr->pages on pin failure to avoid double-free on cleanup
- Reject unaligned or overflowing userptr ranges at create time
- Disallow PRIME export of userptr objects

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  38 +++
 drivers/gpu/drm/virtio/virtgpu_object.c  |  10 +
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 330 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 ++-
 5 files changed, 404 insertions(+), 10 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c

diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
index d2e1788a82..fe7332a621 100644
--- a/drivers/gpu/drm/virtio/Makefile
+++ b/drivers/gpu/drm/virtio/Makefile
@@ -6,6 +6,7 @@
 virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
 	virtgpu_display.o virtgpu_vq.o \
 	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
-	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
+	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
+	virtgpu_userptr.o
 
 obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 9df4c71173..e59750fc30 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
 	uint32_t blob_flags;
 	uint64_t blob_id;
 	uint32_t blob_hints;
+	uint64_t userptr;
 };
 
 struct virtio_gpu_object {
@@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
 	struct drm_mm_node vram_node;
 };
 
+struct virtio_gpu_object_userptr;
+
+struct virtio_gpu_object_userptr_ops {
+	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
+	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
+};
+
+struct virtio_gpu_object_userptr {
+	struct virtio_gpu_object base;
+	const struct virtio_gpu_object_userptr_ops *ops;
+	/* Protects pages and sgt. */
+	struct mutex lock;
+
+	uint64_t start;
+	uint32_t npages;
+	uint32_t bo_handle;
+	uint32_t flags;
+
+	struct virtio_gpu_device *vgdev;
+	struct drm_file *file;
+	struct page **pages;
+	struct sg_table *sgt;
+	bool dma_mapped;
+	enum dma_data_direction dma_dir;
+	struct mm_struct *mm;
+};
+
 #define to_virtio_gpu_shmem(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
 
 #define to_virtio_gpu_vram(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
 
+#define to_virtio_gpu_userptr(virtio_gpu_object) \
+	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
+
 struct virtio_gpu_object_array {
 	struct ww_acquire_ctx ticket;
 	struct list_head next;
@@ -562,4 +593,11 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
 int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
 				struct drm_file *file);
 
+/* virtgpu_userptr.c */
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr);
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
 #endif
diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
index 49899485be..5c40f5a034 100644
--- a/drivers/gpu/drm/virtio/virtgpu_object.c
+++ b/drivers/gpu/drm/virtio/virtgpu_object.c
@@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
 		drm_gem_free_mmap_offset(&vram->base.base.base);
 		drm_gem_object_release(&vram->base.base.base);
 		kfree(vram);
+	} else if (virtio_gpu_is_userptr(bo)) {
+		struct virtio_gpu_object_userptr *userptr =
+			to_virtio_gpu_userptr(bo);
+
+		mutex_lock(&userptr->lock);
+		userptr->ops->put_pages(userptr);
+		mutex_unlock(&userptr->lock);
+		mutex_destroy(&userptr->lock);
+		drm_gem_object_release(&userptr->base.base.base);
+		kfree(userptr);
 	} else {
 		drm_gem_object_release(&bo->base.base);
 		kfree(bo);
diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
new file mode 100644
index 0000000000..7a5870816d
--- /dev/null
+++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+#include <linux/pid.h>
+#include <linux/sched/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/vmalloc.h>
+
+#include "virtgpu_drv.h"
+#include <drm/drm_gem.h>
+
+static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
+{
+	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
+	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
+
+	/*
+	 * Keep pages pinned until RESOURCE_UNREF completes. The response
+	 * callback calls virtio_gpu_cleanup_object(), which drops them.
+	 */
+	if (bo->created) {
+		virtio_gpu_remove_from_restore_list(bo);
+		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
+		virtio_gpu_notify(vgdev);
+		return;
+	}
+
+	virtio_gpu_cleanup_object(bo);
+}
+
+static struct dma_buf *
+virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
+	.open = virtio_gpu_gem_object_open,
+	.close = virtio_gpu_gem_object_close,
+	.free = virtio_gpu_userptr_free,
+	.export = virtio_gpu_userptr_prime_export,
+};
+
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
+{
+	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
+}
+
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	struct device *dev;
+
+	if (!userptr->dma_mapped)
+		return;
+
+	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
+	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
+}
+
+static int
+virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	unsigned int flag = FOLL_LONGTERM;
+	unsigned int num_pages, pinned = 0;
+	int ret = 0;
+
+	if (userptr->pages)
+		return 0;
+
+	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
+					GFP_KERNEL);
+	if (!userptr->pages)
+		return -ENOMEM;
+
+	if (!(userptr->flags & DRM_VIRTGPU_BLOB_FLAG_HINT_USERPTR_RDONLY))
+		flag |= FOLL_WRITE;
+
+	do {
+		num_pages = userptr->npages - pinned;
+
+		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
+					  num_pages, flag,
+					  userptr->pages + pinned);
+
+		if (ret < 0) {
+			if (pinned)
+				unpin_user_pages(userptr->pages, pinned);
+			kvfree(userptr->pages);
+			userptr->pages = NULL;
+			return ret;
+		}
+
+		pinned += ret;
+
+	} while (pinned < userptr->npages);
+
+	return 0;
+}
+
+static void
+virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
+{
+	if (!userptr->mm)
+		return;
+
+	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
+	mmdrop(userptr->mm);
+	userptr->mm = NULL;
+}
+
+static void
+virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	struct drm_device *dev = userptr->base.base.base.dev;
+
+	if (userptr->sgt) {
+		if (userptr->dma_mapped)
+			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
+					  userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		sg_free_table(userptr->sgt);
+		kfree(userptr->sgt);
+		userptr->sgt = NULL;
+	}
+
+	if (userptr->pages) {
+		bool dirty = !(userptr->flags & DRM_VIRTGPU_BLOB_FLAG_HINT_USERPTR_RDONLY);
+
+		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
+					    dirty);
+		kvfree(userptr->pages);
+		userptr->pages = NULL;
+	}
+
+	virtio_gpu_userptr_unaccount(userptr);
+}
+
+static int
+virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object_userptr *userptr,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
+	struct scatterlist *sg;
+	unsigned int count;
+	int si;
+
+	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
+	if (!count)
+		return -EINVAL;
+
+	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
+	if (!*ents)
+		return -ENOMEM;
+
+	if (use_dma_api) {
+		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
+			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
+			(*ents)[si].padding = 0;
+		}
+	} else {
+		for_each_sgtable_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
+			(*ents)[si].length = cpu_to_le32(sg->length);
+			(*ents)[si].padding = 0;
+		}
+	}
+
+	*nents = count;
+	return 0;
+}
+
+static int
+virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
+			struct virtio_gpu_object_userptr *userptr,
+			struct virtio_gpu_object_params *params,
+			const struct virtio_gpu_object_userptr_ops *ops)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	userptr->start = params->userptr;
+	userptr->npages = params->size >> PAGE_SHIFT;
+	userptr->flags = params->blob_hints;
+
+	mutex_init(&userptr->lock);
+	userptr->vgdev = dev->dev_private;
+	userptr->file = file;
+	userptr->ops = ops;
+
+	/*
+	 * Allocate the resource id before GEM init so a failure here can
+	 * unwind with a plain kfree and does not need a special id=0 guard
+	 * in the shared resource_id_put helper.
+	 */
+	ret = virtio_gpu_resource_id_get(userptr->vgdev,
+					 &userptr->base.hw_res_handle);
+	if (ret) {
+		mutex_destroy(&userptr->lock);
+		return ret;
+	}
+
+	obj = &userptr->base.base.base;
+	obj->funcs = &virtio_gpu_userptr_funcs;
+
+	drm_gem_private_object_init(dev, obj, params->size);
+	INIT_LIST_HEAD(&userptr->base.restore_node);
+
+	return 0;
+}
+
+static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
+	.get_pages = virtio_gpu_userptr_get_pages,
+	.put_pages = virtio_gpu_userptr_put_pages,
+};
+
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr)
+{
+	struct virtio_gpu_object_userptr *userptr;
+	struct virtio_gpu_mem_entry *ents = NULL;
+	struct sg_table *sgt;
+	struct mm_struct *mm;
+	unsigned long lock_limit;
+	unsigned long start;
+	unsigned long end;
+	s64 new_pinned;
+	unsigned int nents;
+	int ret;
+
+	*bo_ptr = NULL;
+
+	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
+	    params->userptr != (unsigned long)params->userptr)
+		return -EINVAL;
+
+	start = params->userptr;
+	if (!IS_ALIGNED(start, PAGE_SIZE) ||
+	    check_add_overflow(start, (unsigned long)params->size, &end))
+		return -EINVAL;
+
+	if (!can_do_mlock())
+		return -EPERM;
+
+	if (params->size >> PAGE_SHIFT > INT_MAX)
+		return -E2BIG;
+
+	if (!access_ok((void __user *)start, params->size))
+		return -EFAULT;
+
+	userptr = kzalloc_obj(*userptr);
+	if (!userptr)
+		return -ENOMEM;
+
+	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
+				      &virtio_gpu_userptr_ops);
+	if (ret) {
+		kfree(userptr);
+		return ret;
+	}
+
+	mm = current->mm;
+	mmgrab(mm);
+	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
+	if (new_pinned < 0 ||
+	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
+		atomic64_sub(userptr->npages, &mm->pinned_vm);
+		mmdrop(mm);
+		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
+		goto err_cleanup;
+	}
+	userptr->mm = mm;
+
+	mutex_lock(&userptr->lock);
+	ret = userptr->ops->get_pages(userptr);
+	mutex_unlock(&userptr->lock);
+	if (ret)
+		goto err_cleanup;
+
+	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
+				    userptr->npages);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_cleanup;
+	}
+
+	userptr->sgt = sgt;
+
+	/*
+	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
+	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
+	 * that get copied back over guest pages on unmap even though the
+	 * host was given sg_phys() addresses.
+	 */
+	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
+		enum dma_data_direction dir =
+			(userptr->flags & DRM_VIRTGPU_BLOB_FLAG_HINT_USERPTR_RDONLY) ?
+			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
+
+		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
+				      dir, 0);
+		if (ret)
+			goto err_cleanup;
+
+		userptr->dma_dir = dir;
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
+	if (ret)
+		goto err_cleanup;
+
+	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
+					    nents);
+
+	*bo_ptr = &userptr->base;
+	return 0;
+
+err_cleanup:
+	virtio_gpu_cleanup_object(&userptr->base);
+	return ret;
+}
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index c02c03c10d..dcbd7bb7a6 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
-- 
2.34.1


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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  5:59 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
@ 2026-09-18  6:16   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-09-18  6:16 UTC (permalink / raw)
  To: Honglei Huang; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] drm/virtio: privilege escalation via HINT_USERPTR_RDONLY
--

commit 20ea6ca5af258a43b1dc12cab535283738613d35
Author: Honglei Huang <honghuan@amd.com>
Subject: drm/virtio: implement userptr support for zero-copy memory access

This commit adds userptr blob object support to virtio-gpu, allowing the
guest kernel to pin existing userspace mappings and use them as CREATE_BLOB
backing entries. It handles page pinning, DMA mapping if required, and
syncing the scatterlist for the device.

> diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
[ ... ]
> +static int
> +virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
> +{
> +	unsigned int flag = FOLL_LONGTERM;
> +	unsigned int num_pages, pinned = 0;
> +	int ret = 0;
[ ... ]
> +	if (!(userptr->flags & DRM_VIRTGPU_BLOB_FLAG_HINT_USERPTR_RDONLY))
> +		flag |= FOLL_WRITE;
> +
> +	do {
> +		num_pages = userptr->npages - pinned;
> +
> +		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
> +					  num_pages, flag,
> +					  userptr->pages + pinned);

[Severity: Critical]
Does this allow unprivileged userspace to bypass guest page protections?

If a malicious guest userspace maps read-only memory (such as executable
pages like libc.so) and passes it with
DRM_VIRTGPU_BLOB_FLAG_HINT_USERPTR_RDONLY, the code omits FOLL_WRITE when
calling pin_user_pages_fast. 

The pinned physical addresses are then sent to the host. If the guest has no
vIOMMU (where virtio_gpu_use_dma_api() is false), these addresses are passed
directly to the host without DMA_TO_DEVICE IOMMU protection. The userspace
process could then submit a command stream via VIRTGPU_EXECBUFFER instructing
the host GPU to write to this resource, causing the host hypervisor to write
directly into the guest's read-only physical memory.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918055917.2133574-1-honghuan@amd.com?part=3

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

* [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  6:56 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
@ 2026-09-18  6:56 ` Honglei Huang
  0 siblings, 0 replies; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  6:56 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add userptr blob objects so the guest kernel can pin an existing
userspace mapping and advertise it as CREATE_BLOB backing entries.

- New virtio_gpu_object_userptr type for userptr resources
- Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
- Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
- Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
- DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
  required; use DMA_TO_DEVICE for USE_READONLY blobs
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Keep pages pinned until RESOURCE_UNREF is queued; drop them from
  cleanup_object() on the unref response or on create failure
- Clear userptr->pages on pin failure to avoid double-free on cleanup
- Reject unaligned or overflowing userptr ranges at create time
- Disallow PRIME export of userptr objects

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  38 +++
 drivers/gpu/drm/virtio/virtgpu_object.c  |  10 +
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 330 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 ++-
 5 files changed, 404 insertions(+), 10 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c

diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
index d2e1788a82..fe7332a621 100644
--- a/drivers/gpu/drm/virtio/Makefile
+++ b/drivers/gpu/drm/virtio/Makefile
@@ -6,6 +6,7 @@
 virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
 	virtgpu_display.o virtgpu_vq.o \
 	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
-	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
+	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
+	virtgpu_userptr.o
 
 obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 9df4c71173..e59750fc30 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
 	uint32_t blob_flags;
 	uint64_t blob_id;
 	uint32_t blob_hints;
+	uint64_t userptr;
 };
 
 struct virtio_gpu_object {
@@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
 	struct drm_mm_node vram_node;
 };
 
+struct virtio_gpu_object_userptr;
+
+struct virtio_gpu_object_userptr_ops {
+	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
+	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
+};
+
+struct virtio_gpu_object_userptr {
+	struct virtio_gpu_object base;
+	const struct virtio_gpu_object_userptr_ops *ops;
+	/* Protects pages and sgt. */
+	struct mutex lock;
+
+	uint64_t start;
+	uint32_t npages;
+	uint32_t bo_handle;
+	uint32_t flags;
+
+	struct virtio_gpu_device *vgdev;
+	struct drm_file *file;
+	struct page **pages;
+	struct sg_table *sgt;
+	bool dma_mapped;
+	enum dma_data_direction dma_dir;
+	struct mm_struct *mm;
+};
+
 #define to_virtio_gpu_shmem(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
 
 #define to_virtio_gpu_vram(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
 
+#define to_virtio_gpu_userptr(virtio_gpu_object) \
+	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
+
 struct virtio_gpu_object_array {
 	struct ww_acquire_ctx ticket;
 	struct list_head next;
@@ -562,4 +593,11 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
 int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
 				struct drm_file *file);
 
+/* virtgpu_userptr.c */
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr);
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
 #endif
diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
index 49899485be..5c40f5a034 100644
--- a/drivers/gpu/drm/virtio/virtgpu_object.c
+++ b/drivers/gpu/drm/virtio/virtgpu_object.c
@@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
 		drm_gem_free_mmap_offset(&vram->base.base.base);
 		drm_gem_object_release(&vram->base.base.base);
 		kfree(vram);
+	} else if (virtio_gpu_is_userptr(bo)) {
+		struct virtio_gpu_object_userptr *userptr =
+			to_virtio_gpu_userptr(bo);
+
+		mutex_lock(&userptr->lock);
+		userptr->ops->put_pages(userptr);
+		mutex_unlock(&userptr->lock);
+		mutex_destroy(&userptr->lock);
+		drm_gem_object_release(&userptr->base.base.base);
+		kfree(userptr);
 	} else {
 		drm_gem_object_release(&bo->base.base);
 		kfree(bo);
diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
new file mode 100644
index 0000000000..2f86a95ad2
--- /dev/null
+++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+#include <linux/pid.h>
+#include <linux/sched/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/vmalloc.h>
+
+#include "virtgpu_drv.h"
+#include <drm/drm_gem.h>
+
+static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
+{
+	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
+	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
+
+	/*
+	 * Keep pages pinned until RESOURCE_UNREF completes. The response
+	 * callback calls virtio_gpu_cleanup_object(), which drops them.
+	 */
+	if (bo->created) {
+		virtio_gpu_remove_from_restore_list(bo);
+		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
+		virtio_gpu_notify(vgdev);
+		return;
+	}
+
+	virtio_gpu_cleanup_object(bo);
+}
+
+static struct dma_buf *
+virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
+	.open = virtio_gpu_gem_object_open,
+	.close = virtio_gpu_gem_object_close,
+	.free = virtio_gpu_userptr_free,
+	.export = virtio_gpu_userptr_prime_export,
+};
+
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
+{
+	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
+}
+
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	struct device *dev;
+
+	if (!userptr->dma_mapped)
+		return;
+
+	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
+	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
+}
+
+static int
+virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	unsigned int flag = FOLL_LONGTERM;
+	unsigned int num_pages, pinned = 0;
+	int ret = 0;
+
+	if (userptr->pages)
+		return 0;
+
+	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
+					GFP_KERNEL);
+	if (!userptr->pages)
+		return -ENOMEM;
+
+	if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
+		flag |= FOLL_WRITE;
+
+	do {
+		num_pages = userptr->npages - pinned;
+
+		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
+					  num_pages, flag,
+					  userptr->pages + pinned);
+
+		if (ret < 0) {
+			if (pinned)
+				unpin_user_pages(userptr->pages, pinned);
+			kvfree(userptr->pages);
+			userptr->pages = NULL;
+			return ret;
+		}
+
+		pinned += ret;
+
+	} while (pinned < userptr->npages);
+
+	return 0;
+}
+
+static void
+virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
+{
+	if (!userptr->mm)
+		return;
+
+	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
+	mmdrop(userptr->mm);
+	userptr->mm = NULL;
+}
+
+static void
+virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	struct drm_device *dev = userptr->base.base.base.dev;
+
+	if (userptr->sgt) {
+		if (userptr->dma_mapped)
+			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
+					  userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		sg_free_table(userptr->sgt);
+		kfree(userptr->sgt);
+		userptr->sgt = NULL;
+	}
+
+	if (userptr->pages) {
+		bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
+
+		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
+					    dirty);
+		kvfree(userptr->pages);
+		userptr->pages = NULL;
+	}
+
+	virtio_gpu_userptr_unaccount(userptr);
+}
+
+static int
+virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object_userptr *userptr,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
+	struct scatterlist *sg;
+	unsigned int count;
+	int si;
+
+	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
+	if (!count)
+		return -EINVAL;
+
+	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
+	if (!*ents)
+		return -ENOMEM;
+
+	if (use_dma_api) {
+		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
+			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
+			(*ents)[si].padding = 0;
+		}
+	} else {
+		for_each_sgtable_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
+			(*ents)[si].length = cpu_to_le32(sg->length);
+			(*ents)[si].padding = 0;
+		}
+	}
+
+	*nents = count;
+	return 0;
+}
+
+static int
+virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
+			struct virtio_gpu_object_userptr *userptr,
+			struct virtio_gpu_object_params *params,
+			const struct virtio_gpu_object_userptr_ops *ops)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	userptr->start = params->userptr;
+	userptr->npages = params->size >> PAGE_SHIFT;
+	userptr->flags = params->blob_flags;
+
+	mutex_init(&userptr->lock);
+	userptr->vgdev = dev->dev_private;
+	userptr->file = file;
+	userptr->ops = ops;
+
+	/*
+	 * Allocate the resource id before GEM init so a failure here can
+	 * unwind with a plain kfree and does not need a special id=0 guard
+	 * in the shared resource_id_put helper.
+	 */
+	ret = virtio_gpu_resource_id_get(userptr->vgdev,
+					 &userptr->base.hw_res_handle);
+	if (ret) {
+		mutex_destroy(&userptr->lock);
+		return ret;
+	}
+
+	obj = &userptr->base.base.base;
+	obj->funcs = &virtio_gpu_userptr_funcs;
+
+	drm_gem_private_object_init(dev, obj, params->size);
+	INIT_LIST_HEAD(&userptr->base.restore_node);
+
+	return 0;
+}
+
+static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
+	.get_pages = virtio_gpu_userptr_get_pages,
+	.put_pages = virtio_gpu_userptr_put_pages,
+};
+
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr)
+{
+	struct virtio_gpu_object_userptr *userptr;
+	struct virtio_gpu_mem_entry *ents = NULL;
+	struct sg_table *sgt;
+	struct mm_struct *mm;
+	unsigned long lock_limit;
+	unsigned long start;
+	unsigned long end;
+	s64 new_pinned;
+	unsigned int nents;
+	int ret;
+
+	*bo_ptr = NULL;
+
+	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
+	    params->userptr != (unsigned long)params->userptr)
+		return -EINVAL;
+
+	start = params->userptr;
+	if (!IS_ALIGNED(start, PAGE_SIZE) ||
+	    check_add_overflow(start, (unsigned long)params->size, &end))
+		return -EINVAL;
+
+	if (!can_do_mlock())
+		return -EPERM;
+
+	if (params->size >> PAGE_SHIFT > INT_MAX)
+		return -E2BIG;
+
+	if (!access_ok((void __user *)start, params->size))
+		return -EFAULT;
+
+	userptr = kzalloc_obj(*userptr);
+	if (!userptr)
+		return -ENOMEM;
+
+	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
+				      &virtio_gpu_userptr_ops);
+	if (ret) {
+		kfree(userptr);
+		return ret;
+	}
+
+	mm = current->mm;
+	mmgrab(mm);
+	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
+	if (new_pinned < 0 ||
+	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
+		atomic64_sub(userptr->npages, &mm->pinned_vm);
+		mmdrop(mm);
+		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
+		goto err_cleanup;
+	}
+	userptr->mm = mm;
+
+	mutex_lock(&userptr->lock);
+	ret = userptr->ops->get_pages(userptr);
+	mutex_unlock(&userptr->lock);
+	if (ret)
+		goto err_cleanup;
+
+	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
+				    userptr->npages);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_cleanup;
+	}
+
+	userptr->sgt = sgt;
+
+	/*
+	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
+	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
+	 * that get copied back over guest pages on unmap even though the
+	 * host was given sg_phys() addresses.
+	 */
+	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
+		enum dma_data_direction dir =
+			(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
+			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
+
+		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
+				      dir, 0);
+		if (ret)
+			goto err_cleanup;
+
+		userptr->dma_dir = dir;
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
+	if (ret)
+		goto err_cleanup;
+
+	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
+					    nents);
+
+	*bo_ptr = &userptr->base;
+	return 0;
+
+err_cleanup:
+	virtio_gpu_cleanup_object(&userptr->base);
+	return ret;
+}
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index c02c03c10d..dcbd7bb7a6 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
-- 
2.34.1


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

* [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads
@ 2026-09-18  7:55 Honglei Huang
  2026-09-18  7:55 ` [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability Honglei Huang
                   ` (3 more replies)
  0 siblings, 4 replies; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  7:55 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Hello,

This series adds virtio-gpu userptr support for ROCm native compute
contexts. The guest kernel pins an existing userspace mapping with
FOLL_LONGTERM and exposes it to the host as ordinary CREATE_BLOB
backing entries, avoiding a second shmem allocation and memcpy.

A non-zero userptr field selects that path. Probe
VIRTGPU_PARAM_USERPTR before using the field so an older guest kernel
rejects the request instead of silently creating a shmem blob.

GPU-readonly backing is requested with the virtio CREATE_BLOB wire
flag VIRTIO_GPU_BLOB_FLAG_USE_READONLY (also
VIRTGPU_BLOB_FLAG_USE_READONLY). The device MUST NOT write such a
resource. The guest accepts that flag and pins without FOLL_WRITE
only when the device advertised VIRTIO_GPU_F_BLOB_READONLY
(VIRTGPU_PARAM_BLOB_READONLY). Otherwise CREATE_BLOB fails, matching
the CROSS_DEVICE host-capability check.

Patches overview:
1. Add VIRTIO_GPU_CAPSET_ROCM capability for compute workloads
2. Extend DRM/virtio UAPI with userptr, PARAM_USERPTR,
   USE_READONLY, and F_BLOB_READONLY
3. Implement core userptr functionality with page management
4. Wire blob ioctl creation to userptr objects and probe the
   readonly feature

Tests:
- Full OPENCL CTS tests passed on ROCm 5.7.0 in V2000 platform.
- Near 70% percentage of OPENCL CTS tests passed on ROCm 7.0 W7900 platform.
- most HIP catch tests passed on ROCm 7.0 W7900 platform.
- Some AI applications enabled on ROCm 7.0 W7900 platform.
- latest ROCm 7.14 and ROCm 10 testing is ongoing.

V8 changes:
- Drop guest-only USERPTR ioctl flags; a non-zero userptr field
  selects the path
- Add VIRTGPU_PARAM_USERPTR so older kernels do not silently
  ignore the new field
- Request GPU-readonly backing with virtio wire flag USE_READONLY
- Accept USE_READONLY and omit FOLL_WRITE only if the host
  advertised VIRTIO_GPU_F_BLOB_READONLY

V7 changes:
- Mask guest-only DRM flags out of CREATE_BLOB wire blob_flags
- Clear userptr->pages after pin failure to avoid double-free
- DMA-map userptr SG only when virtio_gpu_use_dma_api() is required
- Use DMA_TO_DEVICE for readonly blobs
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Reject USERPTR unless blob_mem is VIRTGPU_BLOB_MEM_GUEST
- Disallow PRIME export of userptr objects
- Note that CAPSET_ROCM uses ID 8 because ID 7 is taken by VIRCL

V6 changes:
- Rebase onto drm-misc-next
- Keep USE_USERPTR / USERPTR_RDONLY as guest-only DRM flags; drop the
  virtio wire-header patch (5 patches down to 4)
- Fix userptr lifetime, DMA mapping, memlock accounting, alignment
  checks, and PRIME SG export
- Updated corresponding cover letter and commit messages

V5 changes:
    - Add VIRTIO_GPU_BLOB_FLAG_USERPTR_RDONLY definition to patch 2
    - Dropped unused VIRTIO_GPU_F_RESOURCE_USERPTR feature bit in patch 2
    - Included VIRTIO_GPU_BLOB_FLAG_USERPTR_RDONLY in VIRTGPU_BLOB_FLAG_USE_MASK in patch 5
    - Add check for userptr feature in patch 5 before creating userptr blob resource
    - Updated corresponding cover letter and commit messages

V4 changes:
    - Renamed VIRTIO_GPU_CAPSET_HSAKMT to VIRTIO_GPU_CAPSET_ROCM
    - Remove userptr feature probing cause it can reuse the guest
      blob resource code path, reduce patch count from 6 to 5
    - Updated corresponding commit messages
    - Consolidated userptr feature detection in final patch
    - Update corresponding cover letter content

V3 changes:
    - Split into focused patches for easier review
    - Removed complex interval tree userptr management
    - Simplified resource creation without deduplication
    - Added VIRTGPU_PARAM_RESOURCE_USERPTR for feature detection
    - Improved UAPI documentation and error handling
    - Enhanced code quality with proper cleanup paths
    - Removed MMU notifier dependencies for simplicity
    - Fixed resource lifecycle management issues

V2: - Split add HSAKMT context and blob userptr resource to two patches.
    - Remove MMU notifier related patches, cause use not moveable user space
      memory with MMU notifier is not a good idea.
    - Remove HSAKMT context check when create context, let all the context
      support the userptr feature.
    - Remove MMU notifier related content in cover letter.
    - Add more comments  for patch 6 in cover letter.

Previous version:
https://lore.kernel.org/dri-devel/20260917102540.1312102-1-honghuan@amd.com/

Honglei Huang (4):
  drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability
  drm/virtgpu api: add blob userptr resource
  drm/virtio: implement userptr support for zero-copy memory access
  drm/virtio: wire blob ioctl creation to userptr objects

 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_debugfs.c |   1 +
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  39 +++
 drivers/gpu/drm/virtio/virtgpu_ioctl.c   |  35 ++-
 drivers/gpu/drm/virtio/virtgpu_kms.c     |   8 +-
 drivers/gpu/drm/virtio/virtgpu_object.c  |  10 +
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 330 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 ++-
 include/uapi/drm/virtgpu_drm.h           |  13 +
 include/uapi/linux/virtio_gpu.h          |   7 +
 10 files changed, 462 insertions(+), 17 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c


base-commit: 766bfba0f3bae329f99b42dcabc3ef11fa368f0b
-- 
2.34.1

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

* [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability
  2026-09-18  7:55 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
@ 2026-09-18  7:55 ` Honglei Huang
  2026-09-18  8:07   ` sashiko-bot
  2026-09-18  7:55 ` [PATCH v8 2/4] drm/virtgpu api: add blob userptr resource Honglei Huang
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  7:55 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add a new GPU capability set VIRTIO_GPU_CAPSET_ROCM to support
ROCm compute workloads in virtualized environments.

ROCm (Radeon Open Compute) is AMD's open-source software platform
for GPU compute and HPC workloads.

ID 7 is already used by VIRCL in the virgl/virtio-gpu stack, so
ROCm uses ID 8. A matching virtio-gpu specification change is in
progress to register this assignment.

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 include/uapi/linux/virtio_gpu.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/uapi/linux/virtio_gpu.h b/include/uapi/linux/virtio_gpu.h
index 4f530d9005..3d4dfadc9d 100644
--- a/include/uapi/linux/virtio_gpu.h
+++ b/include/uapi/linux/virtio_gpu.h
@@ -321,6 +321,7 @@ struct virtio_gpu_cmd_submit {
 #define VIRTIO_GPU_CAPSET_VENUS 4
 #define VIRTIO_GPU_CAPSET_CROSS_DOMAIN 5
 #define VIRTIO_GPU_CAPSET_DRM 6
+#define VIRTIO_GPU_CAPSET_ROCM 8
 
 /* VIRTIO_GPU_CMD_GET_CAPSET_INFO */
 struct virtio_gpu_get_capset_info {
-- 
2.34.1


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

* [PATCH v8 2/4] drm/virtgpu api: add blob userptr resource
  2026-09-18  7:55 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
  2026-09-18  7:55 ` [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability Honglei Huang
@ 2026-09-18  7:55 ` Honglei Huang
  2026-09-18  7:55 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
  2026-09-18  7:56 ` [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects Honglei Huang
  3 siblings, 0 replies; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  7:55 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add a userptr address field so userspace can request a blob backed
by an existing process mapping. A non-zero userptr selects that
path; probe VIRTGPU_PARAM_USERPTR before using the field.

GPU-readonly backing is requested with the virtio CREATE_BLOB wire
flag VIRTIO_GPU_BLOB_FLAG_USE_READONLY (also
VIRTGPU_BLOB_FLAG_USE_READONLY). The device MUST NOT write such a
resource. The guest pins without FOLL_WRITE only when the device
advertised VIRTIO_GPU_F_BLOB_READONLY (VIRTGPU_PARAM_BLOB_READONLY).

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 include/uapi/drm/virtgpu_drm.h  | 13 +++++++++++++
 include/uapi/linux/virtio_gpu.h |  6 ++++++
 2 files changed, 19 insertions(+)

diff --git a/include/uapi/drm/virtgpu_drm.h b/include/uapi/drm/virtgpu_drm.h
index 95587e12ae..80f73b3276 100644
--- a/include/uapi/drm/virtgpu_drm.h
+++ b/include/uapi/drm/virtgpu_drm.h
@@ -99,6 +99,8 @@ struct drm_virtgpu_execbuffer {
 #define VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs 7 /* Bitmask of supported capability set ids */
 #define VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME 8 /* Ability to set debug name from userspace */
 #define VIRTGPU_PARAM_BLOB_ALIGNMENT 9 /* Device alignment requirements for blobs */
+#define VIRTGPU_PARAM_USERPTR 10 /* CREATE_BLOB userptr field is supported */
+#define VIRTGPU_PARAM_BLOB_READONLY 11 /* VIRTGPU_BLOB_FLAG_USE_READONLY is honored */
 
 struct drm_virtgpu_getparam {
 	__u64 param;
@@ -186,6 +188,10 @@ struct drm_virtgpu_resource_create_blob {
 #define VIRTGPU_BLOB_FLAG_USE_MAPPABLE     0x0001
 #define VIRTGPU_BLOB_FLAG_USE_SHAREABLE    0x0002
 #define VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004
+/* Wire flag: device MUST NOT write. Guest pins without FOLL_WRITE.
+ * Rejected unless VIRTGPU_PARAM_BLOB_READONLY is 1.
+ */
+#define VIRTGPU_BLOB_FLAG_USE_READONLY     0x0008
 	/* zero is invalid blob_mem */
 	__u32 blob_mem;
 	__u32 blob_flags;
@@ -205,6 +211,13 @@ struct drm_virtgpu_resource_create_blob {
 #define DRM_VIRTGPU_BLOB_FLAG_HINT_DEFER_MAPPING        0x0001
 	__u32 blob_hints;
 	__u32 pad2;
+
+	/*
+	 * Guest VA to pin as blob backing. Non-zero selects the userptr
+	 * path and is valid only with VIRTGPU_BLOB_MEM_GUEST. Must be 0
+	 * otherwise. Probe VIRTGPU_PARAM_USERPTR before using this field.
+	 */
+	__u64 userptr;
 };
 
 #define VIRTGPU_CONTEXT_PARAM_CAPSET_ID       0x0001
diff --git a/include/uapi/linux/virtio_gpu.h b/include/uapi/linux/virtio_gpu.h
index 3d4dfadc9d..33f5332e59 100644
--- a/include/uapi/linux/virtio_gpu.h
+++ b/include/uapi/linux/virtio_gpu.h
@@ -72,6 +72,10 @@
  * must be aligned to that value.
  */
 #define VIRTIO_GPU_F_BLOB_ALIGNMENT      5
+/*
+ * VIRTIO_GPU_BLOB_FLAG_USE_READONLY
+ */
+#define VIRTIO_GPU_F_BLOB_READONLY       6
 
 enum virtio_gpu_ctrl_type {
 	VIRTIO_GPU_UNDEFINED = 0,
@@ -415,6 +419,8 @@ struct virtio_gpu_resource_create_blob {
 #define VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE     0x0001
 #define VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE    0x0002
 #define VIRTIO_GPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004
+/* Device MUST NOT write. Driver may pin read-only guest pages. */
+#define VIRTIO_GPU_BLOB_FLAG_USE_READONLY     0x0008
 	/* zero is invalid blob mem */
 	__le32 blob_mem;
 	__le32 blob_flags;
-- 
2.34.1


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

* [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  7:55 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
  2026-09-18  7:55 ` [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability Honglei Huang
  2026-09-18  7:55 ` [PATCH v8 2/4] drm/virtgpu api: add blob userptr resource Honglei Huang
@ 2026-09-18  7:55 ` Honglei Huang
  2026-09-18  8:17   ` sashiko-bot
  2026-09-18  7:56 ` [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects Honglei Huang
  3 siblings, 1 reply; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  7:55 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add userptr blob objects so the guest kernel can pin an existing
userspace mapping and advertise it as CREATE_BLOB backing entries.

- New virtio_gpu_object_userptr type for userptr resources
- Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
- Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
- Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
- DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
  required; use DMA_TO_DEVICE for USE_READONLY blobs
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Keep pages pinned until RESOURCE_UNREF is queued; drop them from
  cleanup_object() on the unref response or on create failure
- Clear userptr->pages on pin failure to avoid double-free on cleanup
- Reject unaligned or overflowing userptr ranges at create time
- Disallow PRIME export of userptr objects

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  39 +++
 drivers/gpu/drm/virtio/virtgpu_object.c  |  10 +
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 330 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 ++-
 5 files changed, 405 insertions(+), 10 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c

diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
index d2e1788a82..fe7332a621 100644
--- a/drivers/gpu/drm/virtio/Makefile
+++ b/drivers/gpu/drm/virtio/Makefile
@@ -6,6 +6,7 @@
 virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
 	virtgpu_display.o virtgpu_vq.o \
 	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
-	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
+	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
+	virtgpu_userptr.o
 
 obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 9df4c71173..df1dad4ea1 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
 	uint32_t blob_flags;
 	uint64_t blob_id;
 	uint32_t blob_hints;
+	uint64_t userptr;
 };
 
 struct virtio_gpu_object {
@@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
 	struct drm_mm_node vram_node;
 };
 
+struct virtio_gpu_object_userptr;
+
+struct virtio_gpu_object_userptr_ops {
+	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
+	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
+};
+
+struct virtio_gpu_object_userptr {
+	struct virtio_gpu_object base;
+	const struct virtio_gpu_object_userptr_ops *ops;
+	/* Protects pages and sgt. */
+	struct mutex lock;
+
+	uint64_t start;
+	uint32_t npages;
+	uint32_t bo_handle;
+	uint32_t flags;
+
+	struct virtio_gpu_device *vgdev;
+	struct drm_file *file;
+	struct page **pages;
+	struct sg_table *sgt;
+	bool dma_mapped;
+	enum dma_data_direction dma_dir;
+	struct mm_struct *mm;
+};
+
 #define to_virtio_gpu_shmem(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
 
 #define to_virtio_gpu_vram(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
 
+#define to_virtio_gpu_userptr(virtio_gpu_object) \
+	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
+
 struct virtio_gpu_object_array {
 	struct ww_acquire_ctx ticket;
 	struct list_head next;
@@ -284,6 +315,7 @@ struct virtio_gpu_device {
 	bool has_host_visible;
 	bool has_context_init;
 	bool has_blob_alignment;
+	bool has_blob_readonly;
 	bool hibernated;
 	struct virtio_shm_region host_visible_region;
 	struct drm_mm host_visible_mm;
@@ -562,4 +594,11 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
 int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
 				struct drm_file *file);
 
+/* virtgpu_userptr.c */
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr);
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
 #endif
diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
index 49899485be..5c40f5a034 100644
--- a/drivers/gpu/drm/virtio/virtgpu_object.c
+++ b/drivers/gpu/drm/virtio/virtgpu_object.c
@@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
 		drm_gem_free_mmap_offset(&vram->base.base.base);
 		drm_gem_object_release(&vram->base.base.base);
 		kfree(vram);
+	} else if (virtio_gpu_is_userptr(bo)) {
+		struct virtio_gpu_object_userptr *userptr =
+			to_virtio_gpu_userptr(bo);
+
+		mutex_lock(&userptr->lock);
+		userptr->ops->put_pages(userptr);
+		mutex_unlock(&userptr->lock);
+		mutex_destroy(&userptr->lock);
+		drm_gem_object_release(&userptr->base.base.base);
+		kfree(userptr);
 	} else {
 		drm_gem_object_release(&bo->base.base);
 		kfree(bo);
diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
new file mode 100644
index 0000000000..2f86a95ad2
--- /dev/null
+++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+#include <linux/pid.h>
+#include <linux/sched/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/vmalloc.h>
+
+#include "virtgpu_drv.h"
+#include <drm/drm_gem.h>
+
+static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
+{
+	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
+	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
+
+	/*
+	 * Keep pages pinned until RESOURCE_UNREF completes. The response
+	 * callback calls virtio_gpu_cleanup_object(), which drops them.
+	 */
+	if (bo->created) {
+		virtio_gpu_remove_from_restore_list(bo);
+		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
+		virtio_gpu_notify(vgdev);
+		return;
+	}
+
+	virtio_gpu_cleanup_object(bo);
+}
+
+static struct dma_buf *
+virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
+	.open = virtio_gpu_gem_object_open,
+	.close = virtio_gpu_gem_object_close,
+	.free = virtio_gpu_userptr_free,
+	.export = virtio_gpu_userptr_prime_export,
+};
+
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
+{
+	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
+}
+
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	struct device *dev;
+
+	if (!userptr->dma_mapped)
+		return;
+
+	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
+	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
+}
+
+static int
+virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	unsigned int flag = FOLL_LONGTERM;
+	unsigned int num_pages, pinned = 0;
+	int ret = 0;
+
+	if (userptr->pages)
+		return 0;
+
+	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
+					GFP_KERNEL);
+	if (!userptr->pages)
+		return -ENOMEM;
+
+	if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
+		flag |= FOLL_WRITE;
+
+	do {
+		num_pages = userptr->npages - pinned;
+
+		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
+					  num_pages, flag,
+					  userptr->pages + pinned);
+
+		if (ret < 0) {
+			if (pinned)
+				unpin_user_pages(userptr->pages, pinned);
+			kvfree(userptr->pages);
+			userptr->pages = NULL;
+			return ret;
+		}
+
+		pinned += ret;
+
+	} while (pinned < userptr->npages);
+
+	return 0;
+}
+
+static void
+virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
+{
+	if (!userptr->mm)
+		return;
+
+	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
+	mmdrop(userptr->mm);
+	userptr->mm = NULL;
+}
+
+static void
+virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	struct drm_device *dev = userptr->base.base.base.dev;
+
+	if (userptr->sgt) {
+		if (userptr->dma_mapped)
+			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
+					  userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		sg_free_table(userptr->sgt);
+		kfree(userptr->sgt);
+		userptr->sgt = NULL;
+	}
+
+	if (userptr->pages) {
+		bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
+
+		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
+					    dirty);
+		kvfree(userptr->pages);
+		userptr->pages = NULL;
+	}
+
+	virtio_gpu_userptr_unaccount(userptr);
+}
+
+static int
+virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object_userptr *userptr,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
+	struct scatterlist *sg;
+	unsigned int count;
+	int si;
+
+	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
+	if (!count)
+		return -EINVAL;
+
+	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
+	if (!*ents)
+		return -ENOMEM;
+
+	if (use_dma_api) {
+		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
+			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
+			(*ents)[si].padding = 0;
+		}
+	} else {
+		for_each_sgtable_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
+			(*ents)[si].length = cpu_to_le32(sg->length);
+			(*ents)[si].padding = 0;
+		}
+	}
+
+	*nents = count;
+	return 0;
+}
+
+static int
+virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
+			struct virtio_gpu_object_userptr *userptr,
+			struct virtio_gpu_object_params *params,
+			const struct virtio_gpu_object_userptr_ops *ops)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	userptr->start = params->userptr;
+	userptr->npages = params->size >> PAGE_SHIFT;
+	userptr->flags = params->blob_flags;
+
+	mutex_init(&userptr->lock);
+	userptr->vgdev = dev->dev_private;
+	userptr->file = file;
+	userptr->ops = ops;
+
+	/*
+	 * Allocate the resource id before GEM init so a failure here can
+	 * unwind with a plain kfree and does not need a special id=0 guard
+	 * in the shared resource_id_put helper.
+	 */
+	ret = virtio_gpu_resource_id_get(userptr->vgdev,
+					 &userptr->base.hw_res_handle);
+	if (ret) {
+		mutex_destroy(&userptr->lock);
+		return ret;
+	}
+
+	obj = &userptr->base.base.base;
+	obj->funcs = &virtio_gpu_userptr_funcs;
+
+	drm_gem_private_object_init(dev, obj, params->size);
+	INIT_LIST_HEAD(&userptr->base.restore_node);
+
+	return 0;
+}
+
+static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
+	.get_pages = virtio_gpu_userptr_get_pages,
+	.put_pages = virtio_gpu_userptr_put_pages,
+};
+
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr)
+{
+	struct virtio_gpu_object_userptr *userptr;
+	struct virtio_gpu_mem_entry *ents = NULL;
+	struct sg_table *sgt;
+	struct mm_struct *mm;
+	unsigned long lock_limit;
+	unsigned long start;
+	unsigned long end;
+	s64 new_pinned;
+	unsigned int nents;
+	int ret;
+
+	*bo_ptr = NULL;
+
+	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
+	    params->userptr != (unsigned long)params->userptr)
+		return -EINVAL;
+
+	start = params->userptr;
+	if (!IS_ALIGNED(start, PAGE_SIZE) ||
+	    check_add_overflow(start, (unsigned long)params->size, &end))
+		return -EINVAL;
+
+	if (!can_do_mlock())
+		return -EPERM;
+
+	if (params->size >> PAGE_SHIFT > INT_MAX)
+		return -E2BIG;
+
+	if (!access_ok((void __user *)start, params->size))
+		return -EFAULT;
+
+	userptr = kzalloc_obj(*userptr);
+	if (!userptr)
+		return -ENOMEM;
+
+	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
+				      &virtio_gpu_userptr_ops);
+	if (ret) {
+		kfree(userptr);
+		return ret;
+	}
+
+	mm = current->mm;
+	mmgrab(mm);
+	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
+	if (new_pinned < 0 ||
+	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
+		atomic64_sub(userptr->npages, &mm->pinned_vm);
+		mmdrop(mm);
+		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
+		goto err_cleanup;
+	}
+	userptr->mm = mm;
+
+	mutex_lock(&userptr->lock);
+	ret = userptr->ops->get_pages(userptr);
+	mutex_unlock(&userptr->lock);
+	if (ret)
+		goto err_cleanup;
+
+	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
+				    userptr->npages);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_cleanup;
+	}
+
+	userptr->sgt = sgt;
+
+	/*
+	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
+	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
+	 * that get copied back over guest pages on unmap even though the
+	 * host was given sg_phys() addresses.
+	 */
+	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
+		enum dma_data_direction dir =
+			(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
+			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
+
+		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
+				      dir, 0);
+		if (ret)
+			goto err_cleanup;
+
+		userptr->dma_dir = dir;
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
+	if (ret)
+		goto err_cleanup;
+
+	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
+					    nents);
+
+	*bo_ptr = &userptr->base;
+	return 0;
+
+err_cleanup:
+	virtio_gpu_cleanup_object(&userptr->base);
+	return ret;
+}
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index c02c03c10d..dcbd7bb7a6 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
-- 
2.34.1


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

* [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects
  2026-09-18  7:55 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
                   ` (2 preceding siblings ...)
  2026-09-18  7:55 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
@ 2026-09-18  7:56 ` Honglei Huang
  2026-09-18  8:12   ` sashiko-bot
  3 siblings, 1 reply; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  7:56 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Integrate userptr into the blob resource creation ioctl.

- A non-zero userptr selects virtio_gpu_userptr_create()
- Reject userptr unless blob_mem is VIRTGPU_BLOB_MEM_GUEST
- Reject VIRTGPU_BLOB_FLAG_USE_READONLY unless the device
  advertised VIRTIO_GPU_F_BLOB_READONLY
- Advertise VIRTGPU_PARAM_USERPTR and VIRTGPU_PARAM_BLOB_READONLY

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/virtgpu_debugfs.c |  1 +
 drivers/gpu/drm/virtio/virtgpu_ioctl.c   | 35 ++++++++++++++++++++----
 drivers/gpu/drm/virtio/virtgpu_kms.c     |  8 ++++--
 3 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/virtio/virtgpu_debugfs.c b/drivers/gpu/drm/virtio/virtgpu_debugfs.c
index 3a68a16b58..b8b9b40584 100644
--- a/drivers/gpu/drm/virtio/virtgpu_debugfs.c
+++ b/drivers/gpu/drm/virtio/virtgpu_debugfs.c
@@ -55,6 +55,7 @@ static int virtio_gpu_features(struct seq_file *m, void *data)
 			    vgdev->has_resource_assign_uuid);
 
 	virtio_gpu_add_bool(m, "blob resources", vgdev->has_resource_blob);
+	virtio_gpu_add_bool(m, "blob readonly", vgdev->has_blob_readonly);
 	virtio_gpu_add_bool(m, "context init", vgdev->has_context_init);
 	virtio_gpu_add_int(m, "cap sets", vgdev->num_capsets);
 	virtio_gpu_add_int(m, "scanouts", vgdev->num_scanouts);
diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c
index 3d8e4ccdb7..3dc058e50e 100644
--- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c
+++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c
@@ -36,7 +36,10 @@
 
 #define VIRTGPU_BLOB_FLAG_USE_MASK (VIRTGPU_BLOB_FLAG_USE_MAPPABLE | \
 				    VIRTGPU_BLOB_FLAG_USE_SHAREABLE | \
-				    VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE)
+				    VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE | \
+				    VIRTGPU_BLOB_FLAG_USE_READONLY)
+
+#define VIRTGPU_BLOB_HINT_MASK DRM_VIRTGPU_BLOB_FLAG_HINT_DEFER_MAPPING
 
 /* Must be called with &virtio_gpu_fpriv.struct_mutex held. */
 static void virtio_gpu_create_context_locked(struct virtio_gpu_device *vgdev,
@@ -122,6 +125,12 @@ static int virtio_gpu_getparam_ioctl(struct drm_device *dev, void *data,
 			return -ENOENT;
 		value = vgdev->blob_alignment;
 		break;
+	case VIRTGPU_PARAM_USERPTR:
+		value = 1;
+		break;
+	case VIRTGPU_PARAM_BLOB_READONLY:
+		value = vgdev->has_blob_readonly ? 1 : 0;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -453,11 +462,23 @@ static int verify_blob(struct virtio_gpu_device *vgdev,
 	if (rc_blob->blob_flags & ~VIRTGPU_BLOB_FLAG_USE_MASK)
 		return -EINVAL;
 
+	if (rc_blob->blob_hints & ~VIRTGPU_BLOB_HINT_MASK)
+		return -EINVAL;
+
 	if (rc_blob->blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) {
 		if (!vgdev->has_resource_assign_uuid)
 			return -EINVAL;
 	}
 
+	if (rc_blob->blob_flags & VIRTGPU_BLOB_FLAG_USE_READONLY) {
+		if (!vgdev->has_blob_readonly)
+			return -EINVAL;
+	}
+
+	if (rc_blob->userptr &&
+	    rc_blob->blob_mem != VIRTGPU_BLOB_MEM_GUEST)
+		return -EINVAL;
+
 	switch (rc_blob->blob_mem) {
 	case VIRTGPU_BLOB_MEM_GUEST:
 		*guest_blob = true;
@@ -495,6 +516,7 @@ static int verify_blob(struct virtio_gpu_device *vgdev,
 	params->blob = true;
 	params->blob_flags = rc_blob->blob_flags;
 	params->blob_hints = rc_blob->blob_hints;
+	params->userptr = rc_blob->userptr;
 
 	if (vgdev->has_blob_alignment &&
 	    !IS_ALIGNED(params->size, vgdev->blob_alignment))
@@ -518,9 +540,10 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev,
 	struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
 	struct drm_virtgpu_resource_create_blob *rc_blob = data;
 
-	if (verify_blob(vgdev, vfpriv, &params, rc_blob,
-			&guest_blob, &host3d_blob))
-		return -EINVAL;
+	ret = verify_blob(vgdev, vfpriv, &params, rc_blob,
+			  &guest_blob, &host3d_blob);
+	if (ret)
+		return ret;
 
 	if (vgdev->has_virgl_3d)
 		virtio_gpu_create_context(dev, file);
@@ -538,7 +561,9 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev,
 				      vfpriv->ctx_id, NULL, NULL);
 	}
 
-	if (guest_blob)
+	if (guest_blob && params.userptr)
+		ret = virtio_gpu_userptr_create(vgdev, file, &params, &bo);
+	else if (guest_blob)
 		ret = virtio_gpu_object_create(vgdev, &params, &bo, NULL);
 	else if (!guest_blob && host3d_blob)
 		ret = virtio_gpu_vram_create(vgdev, &params, &bo);
diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c
index 1d4d3bf46a..06c2bded49 100644
--- a/drivers/gpu/drm/virtio/virtgpu_kms.c
+++ b/drivers/gpu/drm/virtio/virtgpu_kms.c
@@ -249,15 +249,19 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev)
 		vgdev->blob_alignment = blob_alignment;
 	}
 
+	if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_BLOB_READONLY))
+		vgdev->has_blob_readonly = true;
+
 	DRM_INFO("features: %cvirgl %cedid %cresource_blob %chost_visible",
 		 vgdev->has_virgl_3d    ? '+' : '-',
 		 vgdev->has_edid        ? '+' : '-',
 		 vgdev->has_resource_blob ? '+' : '-',
 		 vgdev->has_host_visible ? '+' : '-');
 
-	DRM_INFO("features: %ccontext_init %cblob_alignment\n",
+	DRM_INFO("features: %ccontext_init %cblob_alignment %cblob_readonly\n",
 		 vgdev->has_context_init ? '+' : '-',
-		 vgdev->has_blob_alignment ? '+' : '-');
+		 vgdev->has_blob_alignment ? '+' : '-',
+		 vgdev->has_blob_readonly ? '+' : '-');
 
 	ret = virtio_gpu_find_vqs(vgdev);
 	if (ret) {
-- 
2.34.1


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

* Re: [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability
  2026-09-18  7:55 ` [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability Honglei Huang
@ 2026-09-18  8:07   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-09-18  8:07 UTC (permalink / raw)
  To: Honglei Huang; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] virtio: premature UAPI addition and undocumented ID gap
--

commit 6c8027f028fb5e1fbc1a781cb6e95ed1a88e28f2
Author: Honglei Huang <honghuan@amd.com>

drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability

This commit adds a new GPU capability set VIRTIO_GPU_CAPSET_ROCM to support
ROCm compute workloads. It assigns ID 8 because ID 7 is used by VIRCL, and
mentions a virtio-gpu specification change is in progress.

> diff --git a/include/uapi/linux/virtio_gpu.h b/include/uapi/linux/virtio_gpu.h
> index 4f530d90058c7..3d4dfadc9d789 100644
> --- a/include/uapi/linux/virtio_gpu.h
> +++ b/include/uapi/linux/virtio_gpu.h
> @@ -321,6 +321,7 @@ struct virtio_gpu_cmd_submit {
>  #define VIRTIO_GPU_CAPSET_VENUS 4
>  #define VIRTIO_GPU_CAPSET_CROSS_DOMAIN 5
>  #define VIRTIO_GPU_CAPSET_DRM 6
> +#define VIRTIO_GPU_CAPSET_ROCM 8

[Severity: Medium]
Is it safe to introduce VIRTIO_GPU_CAPSET_ROCM (ID 8) to the immutable Linux
UAPI while the corresponding virtio specification is still in progress?

If the OASIS virtio technical committee rejects the proposal, requests
changes, or assigns a different ID during the review process, the Linux
UAPI will be permanently divergent or broken.

Additionally, should a placeholder macro or documentation be added for the
skipped ID 7? Without it in the header, future developers might assume ID 7
is available and mistakenly reallocate it, creating a potential UAPI
compatibility issue.

>  
>  /* VIRTIO_GPU_CMD_GET_CAPSET_INFO */
>  struct virtio_gpu_get_capset_info {

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918075600.2197253-1-honghuan@amd.com?part=1

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

* Re: [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects
  2026-09-18  7:56 ` [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects Honglei Huang
@ 2026-09-18  8:12   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-09-18  8:12 UTC (permalink / raw)
  To: Honglei Huang; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] drm/virtio: resource loss on resume for userptr blobs
--

commit 260e023045965d09441bfc27b3e47cf7b409d5b4
Author: Honglei Huang <honghuan@amd.com>

drm/virtio: wire blob ioctl creation to userptr objects

This commit integrates userptr into the blob resource creation ioctl.
It adds validation and wires up virtio_gpu_userptr_create() for guest
blob allocations.

> diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c
> --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c
> +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c
[ ... ]
> @@ -538,7 +561,9 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev,
>  				      vfpriv->ctx_id, NULL, NULL);
>  	}
>  
> -	if (guest_blob)
> +	if (guest_blob && params.userptr)
> +		ret = virtio_gpu_userptr_create(vgdev, file, &params, &bo);

[Severity: High]
Does this code handle system hibernation properly for userptr objects?

When virtio_gpu_userptr_create() in drivers/gpu/drm/virtio/virtgpu_userptr.c
successfully allocates the object, it returns without saving bo->params or
calling virtio_gpu_add_object_to_restore_list():

	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
					    nents);

	*bo_ptr = &userptr->base;
	return 0;

Consequently, these objects are permanently lost when the system resumes from
hibernation because their host-side resources are not re-created. Subsequent
GPU commands referencing these resources will fail.

Additionally, if these userptr objects were added to the obj_restore_list, it
seems virtio_gpu_object_restore_all() in drivers/gpu/drm/virtio/virtgpu_object.c
would crash:

	if (bo->params.blob || bo->attached) {
		ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
						   &nents);
		if (ret)
			break;
	}

This logic assumes all non-imported blobs are backed by drm_gem_shmem_object,
but userptr blobs are not.

> +	else if (guest_blob)
>  		ret = virtio_gpu_object_create(vgdev, &params, &bo, NULL);
>  	else if (!guest_blob && host3d_blob)
>  		ret = virtio_gpu_vram_create(vgdev, &params, &bo);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918075600.2197253-1-honghuan@amd.com?part=4

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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  7:55 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
@ 2026-09-18  8:17   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-09-18  8:17 UTC (permalink / raw)
  To: Honglei Huang; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] drm/virtio: suspend/resume broken for userptr blobs due to missing restore_list insertion
--

commit fa2044a8302d0462020754a25069b6a6d031a3a7
Author: Honglei Huang <honghuan@amd.com>

drm/virtio: implement userptr support for zero-copy memory access

This commit adds support for userptr blob objects in the virtio-gpu driver.
It allows the guest kernel to pin existing userspace mappings and advertise
them to the host as CREATE_BLOB backing entries.

> diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
> new file mode 100644
> index 0000000000000..2f86a95ad2e65
> --- /dev/null
> +++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c

[ ... ]

> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
> +			      struct drm_file *file,
> +			      struct virtio_gpu_object_params *params,
> +			      struct virtio_gpu_object **bo_ptr)
> +{

[ ... ]

> +	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
> +					    nents);
> +
> +	*bo_ptr = &userptr->base;
> +	return 0;

[Severity: High]
Does this code miss saving the creation parameters and adding the object to 
the restore list?

If the guest suspends and resumes, virtio_gpu_object_restore_all() needs to 
recreate resources on the host. Without saving params into the base object 
(e.g., bo->params = *params) and calling 
virtio_gpu_add_object_to_restore_list(), this userptr object might be skipped 
during system resume. 

This causes subsequent command submissions using this resource ID to be 
rejected by the host when the guest userspace resumes and tries to access 
the resource.

> +
> +err_cleanup:
> +	virtio_gpu_cleanup_object(&userptr->base);
> +	return ret;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918075600.2197253-1-honghuan@amd.com?part=3

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

* [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  8:23 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
@ 2026-09-18  8:23 ` Honglei Huang
  0 siblings, 0 replies; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  8:23 UTC (permalink / raw)
  To: dri-devel; +Cc: Honglei Huang

Add userptr blob objects so the guest kernel can pin an existing
userspace mapping and advertise it as CREATE_BLOB backing entries.

- New virtio_gpu_object_userptr type for userptr resources
- Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
- Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
- Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
- DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
  required; use DMA_TO_DEVICE for USE_READONLY blobs
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Keep pages pinned until RESOURCE_UNREF is queued; drop them from
  cleanup_object() on the unref response or on create failure
- Clear userptr->pages on pin failure to avoid double-free on cleanup
- Reject unaligned or overflowing userptr ranges at create time
- Disallow PRIME export of userptr objects
- Save CREATE_BLOB params and restore userptr resources after
  hibernation without using the shmem restore path

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  43 +++
 drivers/gpu/drm/virtio/virtgpu_object.c  |  22 ++
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 365 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 +-
 5 files changed, 456 insertions(+), 10 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c

diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
index d2e1788a82..fe7332a621 100644
--- a/drivers/gpu/drm/virtio/Makefile
+++ b/drivers/gpu/drm/virtio/Makefile
@@ -6,6 +6,7 @@
 virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
 	virtgpu_display.o virtgpu_vq.o \
 	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
-	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
+	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
+	virtgpu_userptr.o
 
 obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 9df4c71173..e0941cc187 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
 	uint32_t blob_flags;
 	uint64_t blob_id;
 	uint32_t blob_hints;
+	uint64_t userptr;
 };
 
 struct virtio_gpu_object {
@@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
 	struct drm_mm_node vram_node;
 };
 
+struct virtio_gpu_object_userptr;
+
+struct virtio_gpu_object_userptr_ops {
+	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
+	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
+};
+
+struct virtio_gpu_object_userptr {
+	struct virtio_gpu_object base;
+	const struct virtio_gpu_object_userptr_ops *ops;
+	/* Protects pages and sgt. */
+	struct mutex lock;
+
+	uint64_t start;
+	uint32_t npages;
+	uint32_t bo_handle;
+	uint32_t flags;
+
+	struct virtio_gpu_device *vgdev;
+	struct drm_file *file;
+	struct page **pages;
+	struct sg_table *sgt;
+	bool dma_mapped;
+	enum dma_data_direction dma_dir;
+	struct mm_struct *mm;
+};
+
 #define to_virtio_gpu_shmem(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
 
 #define to_virtio_gpu_vram(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
 
+#define to_virtio_gpu_userptr(virtio_gpu_object) \
+	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
+
 struct virtio_gpu_object_array {
 	struct ww_acquire_ctx ticket;
 	struct list_head next;
@@ -284,6 +315,7 @@ struct virtio_gpu_device {
 	bool has_host_visible;
 	bool has_context_init;
 	bool has_blob_alignment;
+	bool has_blob_readonly;
 	bool hibernated;
 	struct virtio_shm_region host_visible_region;
 	struct drm_mm host_visible_mm;
@@ -562,4 +594,15 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
 int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
 				struct drm_file *file);
 
+/* virtgpu_userptr.c */
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr);
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
+int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object *bo,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents);
 #endif
diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
index 49899485be..ab21494b1c 100644
--- a/drivers/gpu/drm/virtio/virtgpu_object.c
+++ b/drivers/gpu/drm/virtio/virtgpu_object.c
@@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
 		drm_gem_free_mmap_offset(&vram->base.base.base);
 		drm_gem_object_release(&vram->base.base.base);
 		kfree(vram);
+	} else if (virtio_gpu_is_userptr(bo)) {
+		struct virtio_gpu_object_userptr *userptr =
+			to_virtio_gpu_userptr(bo);
+
+		mutex_lock(&userptr->lock);
+		userptr->ops->put_pages(userptr);
+		mutex_unlock(&userptr->lock);
+		mutex_destroy(&userptr->lock);
+		drm_gem_object_release(&userptr->base.base.base);
+		kfree(userptr);
 	} else {
 		drm_gem_object_release(&bo->base.base);
 		kfree(bo);
@@ -316,6 +326,18 @@ int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev)
 			continue;
 		}
 
+		if (virtio_gpu_is_userptr(bo)) {
+			ret = virtio_gpu_userptr_restore(vgdev, bo, &ents,
+							 &nents);
+			if (ret)
+				break;
+
+			virtio_gpu_cmd_resource_create_blob(vgdev, bo,
+							    &bo->params,
+							    ents, nents);
+			continue;
+		}
+
 		if (bo->params.blob || bo->attached) {
 			ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
 							   &nents);
diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
new file mode 100644
index 0000000000..ccfd96844e
--- /dev/null
+++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+#include <linux/pid.h>
+#include <linux/sched/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/vmalloc.h>
+
+#include "virtgpu_drv.h"
+#include <drm/drm_gem.h>
+
+static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
+{
+	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
+	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
+
+	/*
+	 * Keep pages pinned until RESOURCE_UNREF completes. The response
+	 * callback calls virtio_gpu_cleanup_object(), which drops them.
+	 */
+	if (bo->created) {
+		virtio_gpu_remove_from_restore_list(bo);
+		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
+		virtio_gpu_notify(vgdev);
+		return;
+	}
+
+	virtio_gpu_cleanup_object(bo);
+}
+
+static struct dma_buf *
+virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
+	.open = virtio_gpu_gem_object_open,
+	.close = virtio_gpu_gem_object_close,
+	.free = virtio_gpu_userptr_free,
+	.export = virtio_gpu_userptr_prime_export,
+};
+
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
+{
+	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
+}
+
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	struct device *dev;
+
+	if (!userptr->dma_mapped)
+		return;
+
+	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
+	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
+}
+
+static int
+virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	unsigned int flag = FOLL_LONGTERM;
+	unsigned int num_pages, pinned = 0;
+	int ret = 0;
+
+	if (userptr->pages)
+		return 0;
+
+	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
+					GFP_KERNEL);
+	if (!userptr->pages)
+		return -ENOMEM;
+
+	if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
+		flag |= FOLL_WRITE;
+
+	do {
+		num_pages = userptr->npages - pinned;
+
+		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
+					  num_pages, flag,
+					  userptr->pages + pinned);
+
+		if (ret < 0) {
+			if (pinned)
+				unpin_user_pages(userptr->pages, pinned);
+			kvfree(userptr->pages);
+			userptr->pages = NULL;
+			return ret;
+		}
+
+		pinned += ret;
+
+	} while (pinned < userptr->npages);
+
+	return 0;
+}
+
+static void
+virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
+{
+	if (!userptr->mm)
+		return;
+
+	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
+	mmdrop(userptr->mm);
+	userptr->mm = NULL;
+}
+
+static void
+virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	struct drm_device *dev = userptr->base.base.base.dev;
+
+	if (userptr->sgt) {
+		if (userptr->dma_mapped)
+			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
+					  userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		sg_free_table(userptr->sgt);
+		kfree(userptr->sgt);
+		userptr->sgt = NULL;
+	}
+
+	if (userptr->pages) {
+		bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
+
+		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
+					    dirty);
+		kvfree(userptr->pages);
+		userptr->pages = NULL;
+	}
+
+	virtio_gpu_userptr_unaccount(userptr);
+}
+
+static int
+virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object_userptr *userptr,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
+	struct scatterlist *sg;
+	unsigned int count;
+	int si;
+
+	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
+	if (!count)
+		return -EINVAL;
+
+	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
+	if (!*ents)
+		return -ENOMEM;
+
+	if (use_dma_api) {
+		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
+			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
+			(*ents)[si].padding = 0;
+		}
+	} else {
+		for_each_sgtable_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
+			(*ents)[si].length = cpu_to_le32(sg->length);
+			(*ents)[si].padding = 0;
+		}
+	}
+
+	*nents = count;
+	return 0;
+}
+
+int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object *bo,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	int ret;
+
+	mutex_lock(&userptr->lock);
+	if (!userptr->sgt || !userptr->pages) {
+		mutex_unlock(&userptr->lock);
+		return -EINVAL;
+	}
+
+	if (userptr->dma_mapped) {
+		struct device *dev = drm_dev_dma_dev(vgdev->ddev);
+
+		dma_unmap_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		ret = dma_map_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
+		if (ret) {
+			mutex_unlock(&userptr->lock);
+			return ret;
+		}
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, ents, nents);
+	mutex_unlock(&userptr->lock);
+	return ret;
+}
+
+static int
+virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
+			struct virtio_gpu_object_userptr *userptr,
+			struct virtio_gpu_object_params *params,
+			const struct virtio_gpu_object_userptr_ops *ops)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	userptr->start = params->userptr;
+	userptr->npages = params->size >> PAGE_SHIFT;
+	userptr->flags = params->blob_flags;
+
+	mutex_init(&userptr->lock);
+	userptr->vgdev = dev->dev_private;
+	userptr->file = file;
+	userptr->ops = ops;
+
+	/*
+	 * Allocate the resource id before GEM init so a failure here can
+	 * unwind with a plain kfree and does not need a special id=0 guard
+	 * in the shared resource_id_put helper.
+	 */
+	ret = virtio_gpu_resource_id_get(userptr->vgdev,
+					 &userptr->base.hw_res_handle);
+	if (ret) {
+		mutex_destroy(&userptr->lock);
+		return ret;
+	}
+
+	obj = &userptr->base.base.base;
+	obj->funcs = &virtio_gpu_userptr_funcs;
+
+	drm_gem_private_object_init(dev, obj, params->size);
+	INIT_LIST_HEAD(&userptr->base.restore_node);
+
+	return 0;
+}
+
+static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
+	.get_pages = virtio_gpu_userptr_get_pages,
+	.put_pages = virtio_gpu_userptr_put_pages,
+};
+
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr)
+{
+	struct virtio_gpu_object_userptr *userptr;
+	struct virtio_gpu_mem_entry *ents = NULL;
+	struct sg_table *sgt;
+	struct mm_struct *mm;
+	unsigned long lock_limit;
+	unsigned long start;
+	unsigned long end;
+	s64 new_pinned;
+	unsigned int nents;
+	int ret;
+
+	*bo_ptr = NULL;
+
+	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
+	    params->userptr != (unsigned long)params->userptr)
+		return -EINVAL;
+
+	start = params->userptr;
+	if (!IS_ALIGNED(start, PAGE_SIZE) ||
+	    check_add_overflow(start, (unsigned long)params->size, &end))
+		return -EINVAL;
+
+	if (!can_do_mlock())
+		return -EPERM;
+
+	if (params->size >> PAGE_SHIFT > INT_MAX)
+		return -E2BIG;
+
+	if (!access_ok((void __user *)start, params->size))
+		return -EFAULT;
+
+	userptr = kzalloc_obj(*userptr);
+	if (!userptr)
+		return -ENOMEM;
+
+	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
+				      &virtio_gpu_userptr_ops);
+	if (ret) {
+		kfree(userptr);
+		return ret;
+	}
+
+	mm = current->mm;
+	mmgrab(mm);
+	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
+	if (new_pinned < 0 ||
+	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
+		atomic64_sub(userptr->npages, &mm->pinned_vm);
+		mmdrop(mm);
+		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
+		goto err_cleanup;
+	}
+	userptr->mm = mm;
+
+	mutex_lock(&userptr->lock);
+	ret = userptr->ops->get_pages(userptr);
+	mutex_unlock(&userptr->lock);
+	if (ret)
+		goto err_cleanup;
+
+	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
+				    userptr->npages);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_cleanup;
+	}
+
+	userptr->sgt = sgt;
+
+	/*
+	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
+	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
+	 * that get copied back over guest pages on unmap even though the
+	 * host was given sg_phys() addresses.
+	 */
+	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
+		enum dma_data_direction dir =
+			(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
+			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
+
+		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
+				      dir, 0);
+		if (ret)
+			goto err_cleanup;
+
+		userptr->dma_dir = dir;
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
+	if (ret)
+		goto err_cleanup;
+
+	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
+					    nents);
+
+	userptr->base.params = *params;
+	virtio_gpu_add_object_to_restore_list(vgdev, &userptr->base);
+
+	*bo_ptr = &userptr->base;
+	return 0;
+
+err_cleanup:
+	virtio_gpu_cleanup_object(&userptr->base);
+	return ret;
+}
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index c02c03c10d..dcbd7bb7a6 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
-- 
2.34.1


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

* [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  9:59 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
@ 2026-09-18  9:59 ` Honglei Huang
  2026-09-18 10:21   ` sashiko-bot
  2026-09-19  8:59   ` Akihiko Odaki
  0 siblings, 2 replies; 17+ messages in thread
From: Honglei Huang @ 2026-09-18  9:59 UTC (permalink / raw)
  To: Dmitry Osipenko, Akihiko Odaki, David Airlie, Gerd Hoffmann
  Cc: Gurchetan Singh, Chia-I Wu, Ray Huang, dri-devel, virtualization,
	linux-kernel, Honglei Huang

Add userptr blob objects so the guest kernel can pin an existing
userspace mapping and advertise it as CREATE_BLOB backing entries.

- New virtio_gpu_object_userptr type for userptr resources
- Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
- Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
- Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
- DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
  required; use DMA_TO_DEVICE for USE_READONLY blobs
- Sync userptr SG for the device on TRANSFER_TO_HOST
- Mark writable pages dirty when unpinning
- Keep pages pinned until RESOURCE_UNREF is queued; drop them from
  cleanup_object() on the unref response or on create failure
- Clear userptr->pages on pin failure to avoid double-free on cleanup
- Reject unaligned or overflowing userptr ranges at create time
- Disallow PRIME export of userptr objects
- Save CREATE_BLOB params and restore userptr resources after
  hibernation without using the shmem restore path

Signed-off-by: Honglei Huang <honghuan@amd.com>
---
 drivers/gpu/drm/virtio/Makefile          |   3 +-
 drivers/gpu/drm/virtio/virtgpu_drv.h     |  43 +++
 drivers/gpu/drm/virtio/virtgpu_object.c  |  22 ++
 drivers/gpu/drm/virtio/virtgpu_userptr.c | 365 +++++++++++++++++++++++
 drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 +-
 5 files changed, 456 insertions(+), 10 deletions(-)
 create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c

diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
index d2e1788a82..fe7332a621 100644
--- a/drivers/gpu/drm/virtio/Makefile
+++ b/drivers/gpu/drm/virtio/Makefile
@@ -6,6 +6,7 @@
 virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
 	virtgpu_display.o virtgpu_vq.o \
 	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
-	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
+	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
+	virtgpu_userptr.o
 
 obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 9df4c71173..e0941cc187 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
 	uint32_t blob_flags;
 	uint64_t blob_id;
 	uint32_t blob_hints;
+	uint64_t userptr;
 };
 
 struct virtio_gpu_object {
@@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
 	struct drm_mm_node vram_node;
 };
 
+struct virtio_gpu_object_userptr;
+
+struct virtio_gpu_object_userptr_ops {
+	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
+	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
+};
+
+struct virtio_gpu_object_userptr {
+	struct virtio_gpu_object base;
+	const struct virtio_gpu_object_userptr_ops *ops;
+	/* Protects pages and sgt. */
+	struct mutex lock;
+
+	uint64_t start;
+	uint32_t npages;
+	uint32_t bo_handle;
+	uint32_t flags;
+
+	struct virtio_gpu_device *vgdev;
+	struct drm_file *file;
+	struct page **pages;
+	struct sg_table *sgt;
+	bool dma_mapped;
+	enum dma_data_direction dma_dir;
+	struct mm_struct *mm;
+};
+
 #define to_virtio_gpu_shmem(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
 
 #define to_virtio_gpu_vram(virtio_gpu_object) \
 	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
 
+#define to_virtio_gpu_userptr(virtio_gpu_object) \
+	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
+
 struct virtio_gpu_object_array {
 	struct ww_acquire_ctx ticket;
 	struct list_head next;
@@ -284,6 +315,7 @@ struct virtio_gpu_device {
 	bool has_host_visible;
 	bool has_context_init;
 	bool has_blob_alignment;
+	bool has_blob_readonly;
 	bool hibernated;
 	struct virtio_shm_region host_visible_region;
 	struct drm_mm host_visible_mm;
@@ -562,4 +594,15 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
 int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
 				struct drm_file *file);
 
+/* virtgpu_userptr.c */
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr);
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
+int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object *bo,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents);
 #endif
diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
index 49899485be..ab21494b1c 100644
--- a/drivers/gpu/drm/virtio/virtgpu_object.c
+++ b/drivers/gpu/drm/virtio/virtgpu_object.c
@@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
 		drm_gem_free_mmap_offset(&vram->base.base.base);
 		drm_gem_object_release(&vram->base.base.base);
 		kfree(vram);
+	} else if (virtio_gpu_is_userptr(bo)) {
+		struct virtio_gpu_object_userptr *userptr =
+			to_virtio_gpu_userptr(bo);
+
+		mutex_lock(&userptr->lock);
+		userptr->ops->put_pages(userptr);
+		mutex_unlock(&userptr->lock);
+		mutex_destroy(&userptr->lock);
+		drm_gem_object_release(&userptr->base.base.base);
+		kfree(userptr);
 	} else {
 		drm_gem_object_release(&bo->base.base);
 		kfree(bo);
@@ -316,6 +326,18 @@ int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev)
 			continue;
 		}
 
+		if (virtio_gpu_is_userptr(bo)) {
+			ret = virtio_gpu_userptr_restore(vgdev, bo, &ents,
+							 &nents);
+			if (ret)
+				break;
+
+			virtio_gpu_cmd_resource_create_blob(vgdev, bo,
+							    &bo->params,
+							    ents, nents);
+			continue;
+		}
+
 		if (bo->params.blob || bo->attached) {
 			ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
 							   &nents);
diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
new file mode 100644
index 0000000000..ccfd96844e
--- /dev/null
+++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-mapping.h>
+#include <linux/limits.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+#include <linux/pid.h>
+#include <linux/sched/mm.h>
+#include <linux/sched/signal.h>
+#include <linux/vmalloc.h>
+
+#include "virtgpu_drv.h"
+#include <drm/drm_gem.h>
+
+static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
+{
+	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
+	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
+
+	/*
+	 * Keep pages pinned until RESOURCE_UNREF completes. The response
+	 * callback calls virtio_gpu_cleanup_object(), which drops them.
+	 */
+	if (bo->created) {
+		virtio_gpu_remove_from_restore_list(bo);
+		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
+		virtio_gpu_notify(vgdev);
+		return;
+	}
+
+	virtio_gpu_cleanup_object(bo);
+}
+
+static struct dma_buf *
+virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
+{
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
+	.open = virtio_gpu_gem_object_open,
+	.close = virtio_gpu_gem_object_close,
+	.free = virtio_gpu_userptr_free,
+	.export = virtio_gpu_userptr_prime_export,
+};
+
+bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
+{
+	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
+}
+
+void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	struct device *dev;
+
+	if (!userptr->dma_mapped)
+		return;
+
+	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
+	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
+}
+
+static int
+virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	unsigned int flag = FOLL_LONGTERM;
+	unsigned int num_pages, pinned = 0;
+	int ret = 0;
+
+	if (userptr->pages)
+		return 0;
+
+	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
+					GFP_KERNEL);
+	if (!userptr->pages)
+		return -ENOMEM;
+
+	if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
+		flag |= FOLL_WRITE;
+
+	do {
+		num_pages = userptr->npages - pinned;
+
+		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
+					  num_pages, flag,
+					  userptr->pages + pinned);
+
+		if (ret < 0) {
+			if (pinned)
+				unpin_user_pages(userptr->pages, pinned);
+			kvfree(userptr->pages);
+			userptr->pages = NULL;
+			return ret;
+		}
+
+		pinned += ret;
+
+	} while (pinned < userptr->npages);
+
+	return 0;
+}
+
+static void
+virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
+{
+	if (!userptr->mm)
+		return;
+
+	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
+	mmdrop(userptr->mm);
+	userptr->mm = NULL;
+}
+
+static void
+virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
+{
+	struct drm_device *dev = userptr->base.base.base.dev;
+
+	if (userptr->sgt) {
+		if (userptr->dma_mapped)
+			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
+					  userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		sg_free_table(userptr->sgt);
+		kfree(userptr->sgt);
+		userptr->sgt = NULL;
+	}
+
+	if (userptr->pages) {
+		bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
+
+		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
+					    dirty);
+		kvfree(userptr->pages);
+		userptr->pages = NULL;
+	}
+
+	virtio_gpu_userptr_unaccount(userptr);
+}
+
+static int
+virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object_userptr *userptr,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
+	struct scatterlist *sg;
+	unsigned int count;
+	int si;
+
+	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
+	if (!count)
+		return -EINVAL;
+
+	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
+	if (!*ents)
+		return -ENOMEM;
+
+	if (use_dma_api) {
+		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
+			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
+			(*ents)[si].padding = 0;
+		}
+	} else {
+		for_each_sgtable_sg(userptr->sgt, sg, si) {
+			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
+			(*ents)[si].length = cpu_to_le32(sg->length);
+			(*ents)[si].padding = 0;
+		}
+	}
+
+	*nents = count;
+	return 0;
+}
+
+int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
+			       struct virtio_gpu_object *bo,
+			       struct virtio_gpu_mem_entry **ents,
+			       unsigned int *nents)
+{
+	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
+	int ret;
+
+	mutex_lock(&userptr->lock);
+	if (!userptr->sgt || !userptr->pages) {
+		mutex_unlock(&userptr->lock);
+		return -EINVAL;
+	}
+
+	if (userptr->dma_mapped) {
+		struct device *dev = drm_dev_dma_dev(vgdev->ddev);
+
+		dma_unmap_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
+		userptr->dma_mapped = false;
+		ret = dma_map_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
+		if (ret) {
+			mutex_unlock(&userptr->lock);
+			return ret;
+		}
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, ents, nents);
+	mutex_unlock(&userptr->lock);
+	return ret;
+}
+
+static int
+virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
+			struct virtio_gpu_object_userptr *userptr,
+			struct virtio_gpu_object_params *params,
+			const struct virtio_gpu_object_userptr_ops *ops)
+{
+	struct drm_gem_object *obj;
+	int ret;
+
+	userptr->start = params->userptr;
+	userptr->npages = params->size >> PAGE_SHIFT;
+	userptr->flags = params->blob_flags;
+
+	mutex_init(&userptr->lock);
+	userptr->vgdev = dev->dev_private;
+	userptr->file = file;
+	userptr->ops = ops;
+
+	/*
+	 * Allocate the resource id before GEM init so a failure here can
+	 * unwind with a plain kfree and does not need a special id=0 guard
+	 * in the shared resource_id_put helper.
+	 */
+	ret = virtio_gpu_resource_id_get(userptr->vgdev,
+					 &userptr->base.hw_res_handle);
+	if (ret) {
+		mutex_destroy(&userptr->lock);
+		return ret;
+	}
+
+	obj = &userptr->base.base.base;
+	obj->funcs = &virtio_gpu_userptr_funcs;
+
+	drm_gem_private_object_init(dev, obj, params->size);
+	INIT_LIST_HEAD(&userptr->base.restore_node);
+
+	return 0;
+}
+
+static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
+	.get_pages = virtio_gpu_userptr_get_pages,
+	.put_pages = virtio_gpu_userptr_put_pages,
+};
+
+int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
+			      struct drm_file *file,
+			      struct virtio_gpu_object_params *params,
+			      struct virtio_gpu_object **bo_ptr)
+{
+	struct virtio_gpu_object_userptr *userptr;
+	struct virtio_gpu_mem_entry *ents = NULL;
+	struct sg_table *sgt;
+	struct mm_struct *mm;
+	unsigned long lock_limit;
+	unsigned long start;
+	unsigned long end;
+	s64 new_pinned;
+	unsigned int nents;
+	int ret;
+
+	*bo_ptr = NULL;
+
+	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
+	    params->userptr != (unsigned long)params->userptr)
+		return -EINVAL;
+
+	start = params->userptr;
+	if (!IS_ALIGNED(start, PAGE_SIZE) ||
+	    check_add_overflow(start, (unsigned long)params->size, &end))
+		return -EINVAL;
+
+	if (!can_do_mlock())
+		return -EPERM;
+
+	if (params->size >> PAGE_SHIFT > INT_MAX)
+		return -E2BIG;
+
+	if (!access_ok((void __user *)start, params->size))
+		return -EFAULT;
+
+	userptr = kzalloc_obj(*userptr);
+	if (!userptr)
+		return -ENOMEM;
+
+	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
+				      &virtio_gpu_userptr_ops);
+	if (ret) {
+		kfree(userptr);
+		return ret;
+	}
+
+	mm = current->mm;
+	mmgrab(mm);
+	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
+	if (new_pinned < 0 ||
+	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
+		atomic64_sub(userptr->npages, &mm->pinned_vm);
+		mmdrop(mm);
+		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
+		goto err_cleanup;
+	}
+	userptr->mm = mm;
+
+	mutex_lock(&userptr->lock);
+	ret = userptr->ops->get_pages(userptr);
+	mutex_unlock(&userptr->lock);
+	if (ret)
+		goto err_cleanup;
+
+	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
+				    userptr->npages);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto err_cleanup;
+	}
+
+	userptr->sgt = sgt;
+
+	/*
+	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
+	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
+	 * that get copied back over guest pages on unmap even though the
+	 * host was given sg_phys() addresses.
+	 */
+	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
+		enum dma_data_direction dir =
+			(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
+			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
+
+		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
+				      dir, 0);
+		if (ret)
+			goto err_cleanup;
+
+		userptr->dma_dir = dir;
+		userptr->dma_mapped = true;
+	}
+
+	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
+	if (ret)
+		goto err_cleanup;
+
+	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
+					    nents);
+
+	userptr->base.params = *params;
+	virtio_gpu_add_object_to_restore_list(vgdev, &userptr->base);
+
+	*bo_ptr = &userptr->base;
+	return 0;
+
+err_cleanup:
+	virtio_gpu_cleanup_object(&userptr->base);
+	return ret;
+}
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index c02c03c10d..dcbd7bb7a6 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
@@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
 	struct virtio_gpu_vbuffer *vbuf;
 	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
 
-	if (virtio_gpu_is_shmem(bo) && use_dma_api)
-		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
-					    bo->base.sgt, DMA_TO_DEVICE);
+	if (use_dma_api) {
+		if (virtio_gpu_is_shmem(bo))
+			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
+						    bo->base.sgt,
+						    DMA_TO_DEVICE);
+		else if (virtio_gpu_is_userptr(bo))
+			virtio_gpu_userptr_dma_sync_for_device(bo);
+	}
 
 	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
 	memset(cmd_p, 0, sizeof(*cmd_p));
-- 
2.34.1


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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  9:59 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
@ 2026-09-18 10:21   ` sashiko-bot
  2026-09-19  8:59   ` Akihiko Odaki
  1 sibling, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-09-18 10:21 UTC (permalink / raw)
  To: Honglei Huang
  Cc: virtualization, Michael S. Tsirkin, dri-devel, Eugenio Perez

> Add userptr blob objects so the guest kernel can pin an existing
> userspace mapping and advertise it as CREATE_BLOB backing entries.
> 
> - New virtio_gpu_object_userptr type for userptr resources
> - Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
> - Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
> - Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
> - DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
>   required; use DMA_TO_DEVICE for USE_READONLY blobs
> - Sync userptr SG for the device on TRANSFER_TO_HOST
> - Mark writable pages dirty when unpinning
> - Keep pages pinned until RESOURCE_UNREF is queued; drop them from
>   cleanup_object() on the unref response or on create failure
> - Clear userptr->pages on pin failure to avoid double-free on cleanup
> - Reject unaligned or overflowing userptr ranges at create time
> - Disallow PRIME export of userptr objects
> - Save CREATE_BLOB params and restore userptr resources after
>   hibernation without using the shmem restore path
> 
> Signed-off-by: Honglei Huang <honghuan@amd.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918095940.2253018-1-honghuan@amd.com?part=3


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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-18  9:59 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
  2026-09-18 10:21   ` sashiko-bot
@ 2026-09-19  8:59   ` Akihiko Odaki
  2026-09-24  9:25     ` Huang, Honglei
  1 sibling, 1 reply; 17+ messages in thread
From: Akihiko Odaki @ 2026-09-19  8:59 UTC (permalink / raw)
  To: Honglei Huang, Dmitry Osipenko, David Airlie, Gerd Hoffmann
  Cc: Gurchetan Singh, Chia-I Wu, Ray Huang, dri-devel, virtualization,
	linux-kernel

On 2026/09/18 18:59, Honglei Huang wrote:
> Add userptr blob objects so the guest kernel can pin an existing
> userspace mapping and advertise it as CREATE_BLOB backing entries.
> 
> - New virtio_gpu_object_userptr type for userptr resources
> - Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
> - Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
> - Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
> - DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
>    required; use DMA_TO_DEVICE for USE_READONLY blobs
> - Sync userptr SG for the device on TRANSFER_TO_HOST
> - Mark writable pages dirty when unpinning
> - Keep pages pinned until RESOURCE_UNREF is queued; drop them from
>    cleanup_object() on the unref response or on create failure
> - Clear userptr->pages on pin failure to avoid double-free on cleanup
> - Reject unaligned or overflowing userptr ranges at create time
> - Disallow PRIME export of userptr objects
> - Save CREATE_BLOB params and restore userptr resources after
>    hibernation without using the shmem restore path
> 
> Signed-off-by: Honglei Huang <honghuan@amd.com>
> ---
>   drivers/gpu/drm/virtio/Makefile          |   3 +-
>   drivers/gpu/drm/virtio/virtgpu_drv.h     |  43 +++
>   drivers/gpu/drm/virtio/virtgpu_object.c  |  22 ++
>   drivers/gpu/drm/virtio/virtgpu_userptr.c | 365 +++++++++++++++++++++++
>   drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 +-
>   5 files changed, 456 insertions(+), 10 deletions(-)
>   create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c
> 
> diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/Makefile
> index d2e1788a82..fe7332a621 100644
> --- a/drivers/gpu/drm/virtio/Makefile
> +++ b/drivers/gpu/drm/virtio/Makefile
> @@ -6,6 +6,7 @@
>   virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
>   	virtgpu_display.o virtgpu_vq.o \
>   	virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
> -	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
> +	virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o \
> +	virtgpu_userptr.o
>   
>   obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
> diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
> index 9df4c71173..e0941cc187 100644
> --- a/drivers/gpu/drm/virtio/virtgpu_drv.h
> +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
> @@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
>   	uint32_t blob_flags;
>   	uint64_t blob_id;
>   	uint32_t blob_hints;
> +	uint64_t userptr;
>   };
>   
>   struct virtio_gpu_object {
> @@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
>   	struct drm_mm_node vram_node;
>   };
>   
> +struct virtio_gpu_object_userptr;
> +
> +struct virtio_gpu_object_userptr_ops {
> +	int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
> +	void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
> +};
> +
> +struct virtio_gpu_object_userptr {
> +	struct virtio_gpu_object base;
> +	const struct virtio_gpu_object_userptr_ops *ops;
> +	/* Protects pages and sgt. */
> +	struct mutex lock;
> +
> +	uint64_t start;
> +	uint32_t npages;
> +	uint32_t bo_handle;
> +	uint32_t flags;
> +
> +	struct virtio_gpu_device *vgdev;
> +	struct drm_file *file;
> +	struct page **pages;
> +	struct sg_table *sgt;
> +	bool dma_mapped;
> +	enum dma_data_direction dma_dir;
> +	struct mm_struct *mm;
> +};
> +
>   #define to_virtio_gpu_shmem(virtio_gpu_object) \
>   	container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
>   
>   #define to_virtio_gpu_vram(virtio_gpu_object) \
>   	container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
>   
> +#define to_virtio_gpu_userptr(virtio_gpu_object) \
> +	container_of((virtio_gpu_object), struct virtio_gpu_object_userptr, base)
> +
>   struct virtio_gpu_object_array {
>   	struct ww_acquire_ctx ticket;
>   	struct list_head next;
> @@ -284,6 +315,7 @@ struct virtio_gpu_device {
>   	bool has_host_visible;
>   	bool has_context_init;
>   	bool has_blob_alignment;
> +	bool has_blob_readonly;
>   	bool hibernated;
>   	struct virtio_shm_region host_visible_region;
>   	struct drm_mm host_visible_mm;
> @@ -562,4 +594,15 @@ void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
>   int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
>   				struct drm_file *file);
>   
> +/* virtgpu_userptr.c */
> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
> +			      struct drm_file *file,
> +			      struct virtio_gpu_object_params *params,
> +			      struct virtio_gpu_object **bo_ptr);
> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo);
> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
> +			       struct virtio_gpu_object *bo,
> +			       struct virtio_gpu_mem_entry **ents,
> +			       unsigned int *nents);
>   #endif
> diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c
> index 49899485be..ab21494b1c 100644
> --- a/drivers/gpu/drm/virtio/virtgpu_object.c
> +++ b/drivers/gpu/drm/virtio/virtgpu_object.c
> @@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
>   		drm_gem_free_mmap_offset(&vram->base.base.base);
>   		drm_gem_object_release(&vram->base.base.base);
>   		kfree(vram);
> +	} else if (virtio_gpu_is_userptr(bo)) {
> +		struct virtio_gpu_object_userptr *userptr =
> +			to_virtio_gpu_userptr(bo);
> +
> +		mutex_lock(&userptr->lock);
> +		userptr->ops->put_pages(userptr);
> +		mutex_unlock(&userptr->lock);
> +		mutex_destroy(&userptr->lock);
> +		drm_gem_object_release(&userptr->base.base.base);
> +		kfree(userptr);
>   	} else {
>   		drm_gem_object_release(&bo->base.base);
>   		kfree(bo);
> @@ -316,6 +326,18 @@ int virtio_gpu_object_restore_all(struct virtio_gpu_device *vgdev)
>   			continue;
>   		}
>   
> +		if (virtio_gpu_is_userptr(bo)) {
> +			ret = virtio_gpu_userptr_restore(vgdev, bo, &ents,
> +							 &nents);
> +			if (ret)
> +				break;
> +
> +			virtio_gpu_cmd_resource_create_blob(vgdev, bo,
> +							    &bo->params,
> +							    ents, nents);
> +			continue;
> +		}
> +
>   		if (bo->params.blob || bo->attached) {
>   			ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
>   							   &nents);
> diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/drm/virtio/virtgpu_userptr.c
> new file mode 100644
> index 0000000000..ccfd96844e
> --- /dev/null
> +++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
> @@ -0,0 +1,365 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/dma-mapping.h>
> +#include <linux/limits.h>
> +#include <linux/mm.h>
> +#include <linux/overflow.h>
> +#include <linux/pid.h>
> +#include <linux/sched/mm.h>
> +#include <linux/sched/signal.h>
> +#include <linux/vmalloc.h>
> +
> +#include "virtgpu_drv.h"
> +#include <drm/drm_gem.h>
> +
> +static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
> +{
> +	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
> +	struct virtio_gpu_device *vgdev = obj->dev->dev_private;
> +
> +	/*
> +	 * Keep pages pinned until RESOURCE_UNREF completes. The response
> +	 * callback calls virtio_gpu_cleanup_object(), which drops them.
> +	 */
> +	if (bo->created) {
> +		virtio_gpu_remove_from_restore_list(bo);
> +		virtio_gpu_cmd_unref_resource(vgdev, bo, false);
> +		virtio_gpu_notify(vgdev);
> +		return;
> +	}
> +
> +	virtio_gpu_cleanup_object(bo);
> +}
> +
> +static struct dma_buf *
> +virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
> +{
> +	return ERR_PTR(-EINVAL);
> +}
> +
> +static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
> +	.open = virtio_gpu_gem_object_open,
> +	.close = virtio_gpu_gem_object_close,
> +	.free = virtio_gpu_userptr_free,
> +	.export = virtio_gpu_userptr_prime_export,
> +};
> +
> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
> +{
> +	return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
> +}
> +
> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object *bo)
> +{
> +	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
> +	struct device *dev;
> +
> +	if (!userptr->dma_mapped)
> +		return;
> +
> +	dev = drm_dev_dma_dev(userptr->base.base.base.dev);
> +	dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
> +}
> +
> +static int
> +virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
> +{
> +	unsigned int flag = FOLL_LONGTERM;
> +	unsigned int num_pages, pinned = 0;
> +	int ret = 0;
> +
> +	if (userptr->pages)
> +		return 0;
> +
> +	userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct page *),
> +					GFP_KERNEL);
> +	if (!userptr->pages)
> +		return -ENOMEM;
> +
> +	if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
> +		flag |= FOLL_WRITE;
> +
> +	do {
> +		num_pages = userptr->npages - pinned;
> +
> +		ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
> +					  num_pages, flag,
> +					  userptr->pages + pinned);
> +
> +		if (ret < 0) {
> +			if (pinned)
> +				unpin_user_pages(userptr->pages, pinned);
> +			kvfree(userptr->pages);
> +			userptr->pages = NULL;
> +			return ret;
> +		}
> +
> +		pinned += ret;
> +
> +	} while (pinned < userptr->npages);
> +
> +	return 0;
> +}
> +
> +static void
> +virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
> +{
> +	if (!userptr->mm)
> +		return;
> +
> +	atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
> +	mmdrop(userptr->mm);
> +	userptr->mm = NULL;
> +}
> +
> +static void
> +virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
> +{
> +	struct drm_device *dev = userptr->base.base.base.dev;
> +
> +	if (userptr->sgt) {
> +		if (userptr->dma_mapped)
> +			dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
> +					  userptr->dma_dir, 0);
> +		userptr->dma_mapped = false;
> +		sg_free_table(userptr->sgt);
> +		kfree(userptr->sgt);
> +		userptr->sgt = NULL;
> +	}
> +
> +	if (userptr->pages) {
> +		bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
> +
> +		unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
> +					    dirty);
> +		kvfree(userptr->pages);
> +		userptr->pages = NULL;
> +	}
> +
> +	virtio_gpu_userptr_unaccount(userptr);
> +}
> +
> +static int
> +virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
> +			       struct virtio_gpu_object_userptr *userptr,
> +			       struct virtio_gpu_mem_entry **ents,
> +			       unsigned int *nents)
> +{
> +	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
> +	struct scatterlist *sg;
> +	unsigned int count;
> +	int si;
> +
> +	count = use_dma_api ? userptr->sgt->nents : userptr->sgt->orig_nents;
> +	if (!count)
> +		return -EINVAL;
> +
> +	*ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
> +	if (!*ents)
> +		return -ENOMEM;
> +
> +	if (use_dma_api) {
> +		for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
> +			(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
> +			(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
> +			(*ents)[si].padding = 0;
> +		}
> +	} else {
> +		for_each_sgtable_sg(userptr->sgt, sg, si) {
> +			(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
> +			(*ents)[si].length = cpu_to_le32(sg->length);
> +			(*ents)[si].padding = 0;
> +		}
> +	}
> +
> +	*nents = count;
> +	return 0;
> +}
> +
> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
> +			       struct virtio_gpu_object *bo,
> +			       struct virtio_gpu_mem_entry **ents,
> +			       unsigned int *nents)
> +{
> +	struct virtio_gpu_object_userptr *userptr = to_virtio_gpu_userptr(bo);
> +	int ret;
> +
> +	mutex_lock(&userptr->lock);
> +	if (!userptr->sgt || !userptr->pages) {
> +		mutex_unlock(&userptr->lock);
> +		return -EINVAL;
> +	}
> +
> +	if (userptr->dma_mapped) {
> +		struct device *dev = drm_dev_dma_dev(vgdev->ddev);
> +
> +		dma_unmap_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
> +		userptr->dma_mapped = false;
> +		ret = dma_map_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);

This maps with attributes 0, allowing bounce buffers instead of
"zero-copy memory access". It is also unclear what coherence guarantee 
the UAPI is intended to provide.

> +		if (ret) {
> +			mutex_unlock(&userptr->lock);
> +			return ret;
> +		}
> +		userptr->dma_mapped = true;
> +	}
> +
> +	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, ents, nents);
> +	mutex_unlock(&userptr->lock);
> +	return ret;
> +}
> +
> +static int
> +virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
> +			struct virtio_gpu_object_userptr *userptr,
> +			struct virtio_gpu_object_params *params,
> +			const struct virtio_gpu_object_userptr_ops *ops)
> +{
> +	struct drm_gem_object *obj;
> +	int ret;
> +
> +	userptr->start = params->userptr;
> +	userptr->npages = params->size >> PAGE_SHIFT;
> +	userptr->flags = params->blob_flags;
> +
> +	mutex_init(&userptr->lock);
> +	userptr->vgdev = dev->dev_private;
> +	userptr->file = file;
> +	userptr->ops = ops;
> +
> +	/*
> +	 * Allocate the resource id before GEM init so a failure here can
> +	 * unwind with a plain kfree and does not need a special id=0 guard
> +	 * in the shared resource_id_put helper.
> +	 */
> +	ret = virtio_gpu_resource_id_get(userptr->vgdev,
> +					 &userptr->base.hw_res_handle);
> +	if (ret) {
> +		mutex_destroy(&userptr->lock);
> +		return ret;
> +	}
> +
> +	obj = &userptr->base.base.base;
> +	obj->funcs = &virtio_gpu_userptr_funcs;
> +
> +	drm_gem_private_object_init(dev, obj, params->size);
> +	INIT_LIST_HEAD(&userptr->base.restore_node);
> +
> +	return 0;
> +}
> +
> +static const struct virtio_gpu_object_userptr_ops virtio_gpu_userptr_ops = {
> +	.get_pages = virtio_gpu_userptr_get_pages,
> +	.put_pages = virtio_gpu_userptr_put_pages,
> +};
> +
> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
> +			      struct drm_file *file,
> +			      struct virtio_gpu_object_params *params,
> +			      struct virtio_gpu_object **bo_ptr)
> +{
> +	struct virtio_gpu_object_userptr *userptr;
> +	struct virtio_gpu_mem_entry *ents = NULL;
> +	struct sg_table *sgt;
> +	struct mm_struct *mm;
> +	unsigned long lock_limit;
> +	unsigned long start;
> +	unsigned long end;
> +	s64 new_pinned;
> +	unsigned int nents;
> +	int ret;
> +
> +	*bo_ptr = NULL;
> +
> +	if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
> +	    params->userptr != (unsigned long)params->userptr)
> +		return -EINVAL;
> +
> +	start = params->userptr;
> +	if (!IS_ALIGNED(start, PAGE_SIZE) ||
> +	    check_add_overflow(start, (unsigned long)params->size, &end))
> +		return -EINVAL;
> +
> +	if (!can_do_mlock())
> +		return -EPERM;
> +
> +	if (params->size >> PAGE_SHIFT > INT_MAX)
> +		return -E2BIG;
> +
> +	if (!access_ok((void __user *)start, params->size))
> +		return -EFAULT;
> +
> +	userptr = kzalloc_obj(*userptr);
> +	if (!userptr)
> +		return -ENOMEM;
> +
> +	ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
> +				      &virtio_gpu_userptr_ops);
> +	if (ret) {
> +		kfree(userptr);
> +		return ret;
> +	}
> +
> +	mm = current->mm;
> +	mmgrab(mm);
> +	lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
> +	new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
> +	if (new_pinned < 0 ||
> +	    (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
> +		atomic64_sub(userptr->npages, &mm->pinned_vm);
> +		mmdrop(mm);
> +		ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
> +		goto err_cleanup;
> +	}
> +	userptr->mm = mm;
> +
> +	mutex_lock(&userptr->lock);
> +	ret = userptr->ops->get_pages(userptr);
> +	mutex_unlock(&userptr->lock);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
> +				    userptr->npages);
> +	if (IS_ERR(sgt)) {
> +		ret = PTR_ERR(sgt);
> +		goto err_cleanup;
> +	}
> +
> +	userptr->sgt = sgt;
> +
> +	/*
> +	 * Match shmem blobs: only DMA-map when the virtio DMA API is in
> +	 * use. Mapping unconditionally can create SWIOTLB bounce buffers
> +	 * that get copied back over guest pages on unmap even though the
> +	 * host was given sg_phys() addresses.
> +	 */
> +	if (virtio_gpu_use_dma_api(vgdev->vdev)) {
> +		enum dma_data_direction dir =
> +			(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
> +			DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
> +
> +		ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
> +				      dir, 0);
> +		if (ret)
> +			goto err_cleanup;
> +
> +		userptr->dma_dir = dir;
> +		userptr->dma_mapped = true;
> +	}
> +
> +	ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
> +	if (ret)
> +		goto err_cleanup;
> +
> +	virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, params, ents,
> +					    nents);
> +
> +	userptr->base.params = *params;
> +	virtio_gpu_add_object_to_restore_list(vgdev, &userptr->base);
> +
> +	*bo_ptr = &userptr->base;
> +	return 0;
> +
> +err_cleanup:
> +	virtio_gpu_cleanup_object(&userptr->base);
> +	return ret;
> +}
> diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
> index c02c03c10d..dcbd7bb7a6 100644
> --- a/drivers/gpu/drm/virtio/virtgpu_vq.c
> +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
> @@ -781,9 +781,14 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
>   	struct virtio_gpu_vbuffer *vbuf;
>   	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>   
> -	if (virtio_gpu_is_shmem(bo) && use_dma_api)
> -		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> -					    bo->base.sgt, DMA_TO_DEVICE);
> +	if (use_dma_api) {
> +		if (virtio_gpu_is_shmem(bo))
> +			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> +						    bo->base.sgt,
> +						    DMA_TO_DEVICE);
> +		else if (virtio_gpu_is_userptr(bo))
> +			virtio_gpu_userptr_dma_sync_for_device(bo);
> +	}
>   
>   	cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, sizeof(*cmd_p));
>   	memset(cmd_p, 0, sizeof(*cmd_p));
> @@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
>   	struct virtio_gpu_vbuffer *vbuf;
>   	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>   
> -	if (virtio_gpu_is_shmem(bo) && use_dma_api)
> -		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> -					    bo->base.sgt, DMA_TO_DEVICE);
> +	if (use_dma_api) {
> +		if (virtio_gpu_is_shmem(bo))
> +			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> +						    bo->base.sgt,
> +						    DMA_TO_DEVICE);
> +		else if (virtio_gpu_is_userptr(bo))
> +			virtio_gpu_userptr_dma_sync_for_device(bo);
> +	}
>   
>   	cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
>   	memset(cmd_p, 0, sizeof(*cmd_p));
> @@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
>   	struct virtio_gpu_vbuffer *vbuf;
>   	bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>   
> -	if (virtio_gpu_is_shmem(bo) && use_dma_api)
> -		dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> -					    bo->base.sgt, DMA_TO_DEVICE);
> +	if (use_dma_api) {
> +		if (virtio_gpu_is_shmem(bo))
> +			dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
> +						    bo->base.sgt,
> +						    DMA_TO_DEVICE);
> +		else if (virtio_gpu_is_userptr(bo))
> +			virtio_gpu_userptr_dma_sync_for_device(bo);

This code is ineffective since the transfer ioctl rejects 
VIRTGPU_BLOB_MEM_GUEST.

Regards,
Akihiko Odaki

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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-19  8:59   ` Akihiko Odaki
@ 2026-09-24  9:25     ` Huang, Honglei
  2026-09-26 10:17       ` Akihiko Odaki
  0 siblings, 1 reply; 17+ messages in thread
From: Huang, Honglei @ 2026-09-24  9:25 UTC (permalink / raw)
  To: Akihiko Odaki
  Cc: Gurchetan Singh, Chia-I Wu, Ray Huang, dri-devel, virtualization,
	linux-kernel, Dmitry Osipenko, David Airlie, Gerd Hoffmann



On 9/19/2026 4:59 PM, Akihiko Odaki wrote:
> On 2026/09/18 18:59, Honglei Huang wrote:
>> Add userptr blob objects so the guest kernel can pin an existing
>> userspace mapping and advertise it as CREATE_BLOB backing entries.
>>
>> - New virtio_gpu_object_userptr type for userptr resources
>> - Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
>> - Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
>> - Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
>> - DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
>>    required; use DMA_TO_DEVICE for USE_READONLY blobs
>> - Sync userptr SG for the device on TRANSFER_TO_HOST
>> - Mark writable pages dirty when unpinning
>> - Keep pages pinned until RESOURCE_UNREF is queued; drop them from
>>    cleanup_object() on the unref response or on create failure
>> - Clear userptr->pages on pin failure to avoid double-free on cleanup
>> - Reject unaligned or overflowing userptr ranges at create time
>> - Disallow PRIME export of userptr objects
>> - Save CREATE_BLOB params and restore userptr resources after
>>    hibernation without using the shmem restore path
>>
>> Signed-off-by: Honglei Huang <honghuan@amd.com>
>> ---
>>   drivers/gpu/drm/virtio/Makefile          |   3 +-
>>   drivers/gpu/drm/virtio/virtgpu_drv.h     |  43 +++
>>   drivers/gpu/drm/virtio/virtgpu_object.c  |  22 ++
>>   drivers/gpu/drm/virtio/virtgpu_userptr.c | 365 +++++++++++++++++++++++
>>   drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 +-
>>   5 files changed, 456 insertions(+), 10 deletions(-)
>>   create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c
>>
>> diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/virtio/ 
>> Makefile
>> index d2e1788a82..fe7332a621 100644
>> --- a/drivers/gpu/drm/virtio/Makefile
>> +++ b/drivers/gpu/drm/virtio/Makefile
>> @@ -6,6 +6,7 @@
>>   virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o 
>> virtgpu_vram.o \
>>       virtgpu_display.o virtgpu_vq.o \
>>       virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o 
>> virtgpu_plane.o \
>> -    virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o 
>> virtgpu_submit.o
>> +    virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o 
>> virtgpu_submit.o \
>> +    virtgpu_userptr.o
>>   obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
>> diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/ 
>> virtio/virtgpu_drv.h
>> index 9df4c71173..e0941cc187 100644
>> --- a/drivers/gpu/drm/virtio/virtgpu_drv.h
>> +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
>> @@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
>>       uint32_t blob_flags;
>>       uint64_t blob_id;
>>       uint32_t blob_hints;
>> +    uint64_t userptr;
>>   };
>>   struct virtio_gpu_object {
>> @@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
>>       struct drm_mm_node vram_node;
>>   };
>> +struct virtio_gpu_object_userptr;
>> +
>> +struct virtio_gpu_object_userptr_ops {
>> +    int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
>> +    void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
>> +};
>> +
>> +struct virtio_gpu_object_userptr {
>> +    struct virtio_gpu_object base;
>> +    const struct virtio_gpu_object_userptr_ops *ops;
>> +    /* Protects pages and sgt. */
>> +    struct mutex lock;
>> +
>> +    uint64_t start;
>> +    uint32_t npages;
>> +    uint32_t bo_handle;
>> +    uint32_t flags;
>> +
>> +    struct virtio_gpu_device *vgdev;
>> +    struct drm_file *file;
>> +    struct page **pages;
>> +    struct sg_table *sgt;
>> +    bool dma_mapped;
>> +    enum dma_data_direction dma_dir;
>> +    struct mm_struct *mm;
>> +};
>> +
>>   #define to_virtio_gpu_shmem(virtio_gpu_object) \
>>       container_of((virtio_gpu_object), struct 
>> virtio_gpu_object_shmem, base)
>>   #define to_virtio_gpu_vram(virtio_gpu_object) \
>>       container_of((virtio_gpu_object), struct virtio_gpu_object_vram, 
>> base)
>> +#define to_virtio_gpu_userptr(virtio_gpu_object) \
>> +    container_of((virtio_gpu_object), struct 
>> virtio_gpu_object_userptr, base)
>> +
>>   struct virtio_gpu_object_array {
>>       struct ww_acquire_ctx ticket;
>>       struct list_head next;
>> @@ -284,6 +315,7 @@ struct virtio_gpu_device {
>>       bool has_host_visible;
>>       bool has_context_init;
>>       bool has_blob_alignment;
>> +    bool has_blob_readonly;
>>       bool hibernated;
>>       struct virtio_shm_region host_visible_region;
>>       struct drm_mm host_visible_mm;
>> @@ -562,4 +594,15 @@ void virtio_gpu_vram_map_deferred(struct 
>> virtio_gpu_object_vram *vram);
>>   int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
>>                   struct drm_file *file);
>> +/* virtgpu_userptr.c */
>> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
>> +                  struct drm_file *file,
>> +                  struct virtio_gpu_object_params *params,
>> +                  struct virtio_gpu_object **bo_ptr);
>> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
>> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object 
>> *bo);
>> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
>> +                   struct virtio_gpu_object *bo,
>> +                   struct virtio_gpu_mem_entry **ents,
>> +                   unsigned int *nents);
>>   #endif
>> diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/ 
>> drm/virtio/virtgpu_object.c
>> index 49899485be..ab21494b1c 100644
>> --- a/drivers/gpu/drm/virtio/virtgpu_object.c
>> +++ b/drivers/gpu/drm/virtio/virtgpu_object.c
>> @@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct 
>> virtio_gpu_object *bo)
>>           drm_gem_free_mmap_offset(&vram->base.base.base);
>>           drm_gem_object_release(&vram->base.base.base);
>>           kfree(vram);
>> +    } else if (virtio_gpu_is_userptr(bo)) {
>> +        struct virtio_gpu_object_userptr *userptr =
>> +            to_virtio_gpu_userptr(bo);
>> +
>> +        mutex_lock(&userptr->lock);
>> +        userptr->ops->put_pages(userptr);
>> +        mutex_unlock(&userptr->lock);
>> +        mutex_destroy(&userptr->lock);
>> +        drm_gem_object_release(&userptr->base.base.base);
>> +        kfree(userptr);
>>       } else {
>>           drm_gem_object_release(&bo->base.base);
>>           kfree(bo);
>> @@ -316,6 +326,18 @@ int virtio_gpu_object_restore_all(struct 
>> virtio_gpu_device *vgdev)
>>               continue;
>>           }
>> +        if (virtio_gpu_is_userptr(bo)) {
>> +            ret = virtio_gpu_userptr_restore(vgdev, bo, &ents,
>> +                             &nents);
>> +            if (ret)
>> +                break;
>> +
>> +            virtio_gpu_cmd_resource_create_blob(vgdev, bo,
>> +                                &bo->params,
>> +                                ents, nents);
>> +            continue;
>> +        }
>> +
>>           if (bo->params.blob || bo->attached) {
>>               ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
>>                                  &nents);
>> diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/ 
>> drm/virtio/virtgpu_userptr.c
>> new file mode 100644
>> index 0000000000..ccfd96844e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
>> @@ -0,0 +1,365 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <linux/dma-mapping.h>
>> +#include <linux/limits.h>
>> +#include <linux/mm.h>
>> +#include <linux/overflow.h>
>> +#include <linux/pid.h>
>> +#include <linux/sched/mm.h>
>> +#include <linux/sched/signal.h>
>> +#include <linux/vmalloc.h>
>> +
>> +#include "virtgpu_drv.h"
>> +#include <drm/drm_gem.h>
>> +
>> +static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
>> +{
>> +    struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
>> +    struct virtio_gpu_device *vgdev = obj->dev->dev_private;
>> +
>> +    /*
>> +     * Keep pages pinned until RESOURCE_UNREF completes. The response
>> +     * callback calls virtio_gpu_cleanup_object(), which drops them.
>> +     */
>> +    if (bo->created) {
>> +        virtio_gpu_remove_from_restore_list(bo);
>> +        virtio_gpu_cmd_unref_resource(vgdev, bo, false);
>> +        virtio_gpu_notify(vgdev);
>> +        return;
>> +    }
>> +
>> +    virtio_gpu_cleanup_object(bo);
>> +}
>> +
>> +static struct dma_buf *
>> +virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
>> +{
>> +    return ERR_PTR(-EINVAL);
>> +}
>> +
>> +static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
>> +    .open = virtio_gpu_gem_object_open,
>> +    .close = virtio_gpu_gem_object_close,
>> +    .free = virtio_gpu_userptr_free,
>> +    .export = virtio_gpu_userptr_prime_export,
>> +};
>> +
>> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
>> +{
>> +    return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
>> +}
>> +
>> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object 
>> *bo)
>> +{
>> +    struct virtio_gpu_object_userptr *userptr = 
>> to_virtio_gpu_userptr(bo);
>> +    struct device *dev;
>> +
>> +    if (!userptr->dma_mapped)
>> +        return;
>> +
>> +    dev = drm_dev_dma_dev(userptr->base.base.base.dev);
>> +    dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
>> +}
>> +
>> +static int
>> +virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
>> +{
>> +    unsigned int flag = FOLL_LONGTERM;
>> +    unsigned int num_pages, pinned = 0;
>> +    int ret = 0;
>> +
>> +    if (userptr->pages)
>> +        return 0;
>> +
>> +    userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct 
>> page *),
>> +                    GFP_KERNEL);
>> +    if (!userptr->pages)
>> +        return -ENOMEM;
>> +
>> +    if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
>> +        flag |= FOLL_WRITE;
>> +
>> +    do {
>> +        num_pages = userptr->npages - pinned;
>> +
>> +        ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
>> +                      num_pages, flag,
>> +                      userptr->pages + pinned);
>> +
>> +        if (ret < 0) {
>> +            if (pinned)
>> +                unpin_user_pages(userptr->pages, pinned);
>> +            kvfree(userptr->pages);
>> +            userptr->pages = NULL;
>> +            return ret;
>> +        }
>> +
>> +        pinned += ret;
>> +
>> +    } while (pinned < userptr->npages);
>> +
>> +    return 0;
>> +}
>> +
>> +static void
>> +virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
>> +{
>> +    if (!userptr->mm)
>> +        return;
>> +
>> +    atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
>> +    mmdrop(userptr->mm);
>> +    userptr->mm = NULL;
>> +}
>> +
>> +static void
>> +virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
>> +{
>> +    struct drm_device *dev = userptr->base.base.base.dev;
>> +
>> +    if (userptr->sgt) {
>> +        if (userptr->dma_mapped)
>> +            dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
>> +                      userptr->dma_dir, 0);
>> +        userptr->dma_mapped = false;
>> +        sg_free_table(userptr->sgt);
>> +        kfree(userptr->sgt);
>> +        userptr->sgt = NULL;
>> +    }
>> +
>> +    if (userptr->pages) {
>> +        bool dirty = !(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY);
>> +
>> +        unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
>> +                        dirty);
>> +        kvfree(userptr->pages);
>> +        userptr->pages = NULL;
>> +    }
>> +
>> +    virtio_gpu_userptr_unaccount(userptr);
>> +}
>> +
>> +static int
>> +virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
>> +                   struct virtio_gpu_object_userptr *userptr,
>> +                   struct virtio_gpu_mem_entry **ents,
>> +                   unsigned int *nents)
>> +{
>> +    bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>> +    struct scatterlist *sg;
>> +    unsigned int count;
>> +    int si;
>> +
>> +    count = use_dma_api ? userptr->sgt->nents : userptr->sgt- 
>> >orig_nents;
>> +    if (!count)
>> +        return -EINVAL;
>> +
>> +    *ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
>> +    if (!*ents)
>> +        return -ENOMEM;
>> +
>> +    if (use_dma_api) {
>> +        for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
>> +            (*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
>> +            (*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
>> +            (*ents)[si].padding = 0;
>> +        }
>> +    } else {
>> +        for_each_sgtable_sg(userptr->sgt, sg, si) {
>> +            (*ents)[si].addr = cpu_to_le64(sg_phys(sg));
>> +            (*ents)[si].length = cpu_to_le32(sg->length);
>> +            (*ents)[si].padding = 0;
>> +        }
>> +    }
>> +
>> +    *nents = count;
>> +    return 0;
>> +}
>> +
>> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
>> +                   struct virtio_gpu_object *bo,
>> +                   struct virtio_gpu_mem_entry **ents,
>> +                   unsigned int *nents)
>> +{
>> +    struct virtio_gpu_object_userptr *userptr = 
>> to_virtio_gpu_userptr(bo);
>> +    int ret;
>> +
>> +    mutex_lock(&userptr->lock);
>> +    if (!userptr->sgt || !userptr->pages) {
>> +        mutex_unlock(&userptr->lock);
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (userptr->dma_mapped) {
>> +        struct device *dev = drm_dev_dma_dev(vgdev->ddev);
>> +
>> +        dma_unmap_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
>> +        userptr->dma_mapped = false;
>> +        ret = dma_map_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
> 
> This maps with attributes 0, allowing bounce buffers instead of
> "zero-copy memory access". It is also unclear what coherence guarantee 
> the UAPI is intended to provide.


This is a good point, will modify the commit and patch tittle to remove
ero-copy memory access.
And in virtio_gpu_object_shmem_init it also uses dma_map_sgtable(..., 
0), so keep 0 here.

Regards,
Honglei

> 
>> +        if (ret) {
>> +            mutex_unlock(&userptr->lock);
>> +            return ret;
>> +        }
>> +        userptr->dma_mapped = true;
>> +    }
>> +
>> +    ret = virtio_gpu_userptr_get_entries(vgdev, userptr, ents, nents);
>> +    mutex_unlock(&userptr->lock);
>> +    return ret;
>> +}
>> +
>> +static int
>> +virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
>> +            struct virtio_gpu_object_userptr *userptr,
>> +            struct virtio_gpu_object_params *params,
>> +            const struct virtio_gpu_object_userptr_ops *ops)
>> +{
>> +    struct drm_gem_object *obj;
>> +    int ret;
>> +
>> +    userptr->start = params->userptr;
>> +    userptr->npages = params->size >> PAGE_SHIFT;
>> +    userptr->flags = params->blob_flags;
>> +
>> +    mutex_init(&userptr->lock);
>> +    userptr->vgdev = dev->dev_private;
>> +    userptr->file = file;
>> +    userptr->ops = ops;
>> +
>> +    /*
>> +     * Allocate the resource id before GEM init so a failure here can
>> +     * unwind with a plain kfree and does not need a special id=0 guard
>> +     * in the shared resource_id_put helper.
>> +     */
>> +    ret = virtio_gpu_resource_id_get(userptr->vgdev,
>> +                     &userptr->base.hw_res_handle);
>> +    if (ret) {
>> +        mutex_destroy(&userptr->lock);
>> +        return ret;
>> +    }
>> +
>> +    obj = &userptr->base.base.base;
>> +    obj->funcs = &virtio_gpu_userptr_funcs;
>> +
>> +    drm_gem_private_object_init(dev, obj, params->size);
>> +    INIT_LIST_HEAD(&userptr->base.restore_node);
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct virtio_gpu_object_userptr_ops 
>> virtio_gpu_userptr_ops = {
>> +    .get_pages = virtio_gpu_userptr_get_pages,
>> +    .put_pages = virtio_gpu_userptr_put_pages,
>> +};
>> +
>> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
>> +                  struct drm_file *file,
>> +                  struct virtio_gpu_object_params *params,
>> +                  struct virtio_gpu_object **bo_ptr)
>> +{
>> +    struct virtio_gpu_object_userptr *userptr;
>> +    struct virtio_gpu_mem_entry *ents = NULL;
>> +    struct sg_table *sgt;
>> +    struct mm_struct *mm;
>> +    unsigned long lock_limit;
>> +    unsigned long start;
>> +    unsigned long end;
>> +    s64 new_pinned;
>> +    unsigned int nents;
>> +    int ret;
>> +
>> +    *bo_ptr = NULL;
>> +
>> +    if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
>> +        params->userptr != (unsigned long)params->userptr)
>> +        return -EINVAL;
>> +
>> +    start = params->userptr;
>> +    if (!IS_ALIGNED(start, PAGE_SIZE) ||
>> +        check_add_overflow(start, (unsigned long)params->size, &end))
>> +        return -EINVAL;
>> +
>> +    if (!can_do_mlock())
>> +        return -EPERM;
>> +
>> +    if (params->size >> PAGE_SHIFT > INT_MAX)
>> +        return -E2BIG;
>> +
>> +    if (!access_ok((void __user *)start, params->size))
>> +        return -EFAULT;
>> +
>> +    userptr = kzalloc_obj(*userptr);
>> +    if (!userptr)
>> +        return -ENOMEM;
>> +
>> +    ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
>> +                      &virtio_gpu_userptr_ops);
>> +    if (ret) {
>> +        kfree(userptr);
>> +        return ret;
>> +    }
>> +
>> +    mm = current->mm;
>> +    mmgrab(mm);
>> +    lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
>> +    new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
>> +    if (new_pinned < 0 ||
>> +        (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
>> +        atomic64_sub(userptr->npages, &mm->pinned_vm);
>> +        mmdrop(mm);
>> +        ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
>> +        goto err_cleanup;
>> +    }
>> +    userptr->mm = mm;
>> +
>> +    mutex_lock(&userptr->lock);
>> +    ret = userptr->ops->get_pages(userptr);
>> +    mutex_unlock(&userptr->lock);
>> +    if (ret)
>> +        goto err_cleanup;
>> +
>> +    sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
>> +                    userptr->npages);
>> +    if (IS_ERR(sgt)) {
>> +        ret = PTR_ERR(sgt);
>> +        goto err_cleanup;
>> +    }
>> +
>> +    userptr->sgt = sgt;
>> +
>> +    /*
>> +     * Match shmem blobs: only DMA-map when the virtio DMA API is in
>> +     * use. Mapping unconditionally can create SWIOTLB bounce buffers
>> +     * that get copied back over guest pages on unmap even though the
>> +     * host was given sg_phys() addresses.
>> +     */
>> +    if (virtio_gpu_use_dma_api(vgdev->vdev)) {
>> +        enum dma_data_direction dir =
>> +            (userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
>> +            DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
>> +
>> +        ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
>> +                      dir, 0);
>> +        if (ret)
>> +            goto err_cleanup;
>> +
>> +        userptr->dma_dir = dir;
>> +        userptr->dma_mapped = true;
>> +    }
>> +
>> +    ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, &nents);
>> +    if (ret)
>> +        goto err_cleanup;
>> +
>> +    virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, 
>> params, ents,
>> +                        nents);
>> +
>> +    userptr->base.params = *params;
>> +    virtio_gpu_add_object_to_restore_list(vgdev, &userptr->base);
>> +
>> +    *bo_ptr = &userptr->base;
>> +    return 0;
>> +
>> +err_cleanup:
>> +    virtio_gpu_cleanup_object(&userptr->base);
>> +    return ret;
>> +}
>> diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/ 
>> virtio/virtgpu_vq.c
>> index c02c03c10d..dcbd7bb7a6 100644
>> --- a/drivers/gpu/drm/virtio/virtgpu_vq.c
>> +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
>> @@ -781,9 +781,14 @@ int 
>> virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
>>       struct virtio_gpu_vbuffer *vbuf;
>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> -                        bo->base.sgt, DMA_TO_DEVICE);
>> +    if (use_dma_api) {
>> +        if (virtio_gpu_is_shmem(bo))
>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> +                            bo->base.sgt,
>> +                            DMA_TO_DEVICE);
>> +        else if (virtio_gpu_is_userptr(bo))
>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
>> +    }
>>       cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, 
>> sizeof(*cmd_p));
>>       memset(cmd_p, 0, sizeof(*cmd_p));
>> @@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct 
>> virtio_gpu_device *vgdev,
>>       struct virtio_gpu_vbuffer *vbuf;
>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> -                        bo->base.sgt, DMA_TO_DEVICE);
>> +    if (use_dma_api) {
>> +        if (virtio_gpu_is_shmem(bo))
>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> +                            bo->base.sgt,
>> +                            DMA_TO_DEVICE);
>> +        else if (virtio_gpu_is_userptr(bo))
>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
>> +    }
>>       cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
>>       memset(cmd_p, 0, sizeof(*cmd_p));
>> @@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct 
>> virtio_gpu_device *vgdev,
>>       struct virtio_gpu_vbuffer *vbuf;
>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> -                        bo->base.sgt, DMA_TO_DEVICE);
>> +    if (use_dma_api) {
>> +        if (virtio_gpu_is_shmem(bo))
>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>> +                            bo->base.sgt,
>> +                            DMA_TO_DEVICE);
>> +        else if (virtio_gpu_is_userptr(bo))
>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
> 
> This code is ineffective since the transfer ioctl rejects 
> VIRTGPU_BLOB_MEM_GUEST.
> 
> Regards,
> Akihiko Odaki


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

* Re: [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access
  2026-09-24  9:25     ` Huang, Honglei
@ 2026-09-26 10:17       ` Akihiko Odaki
  0 siblings, 0 replies; 17+ messages in thread
From: Akihiko Odaki @ 2026-09-26 10:17 UTC (permalink / raw)
  To: Huang, Honglei
  Cc: Gurchetan Singh, Chia-I Wu, Ray Huang, dri-devel, virtualization,
	linux-kernel, Dmitry Osipenko, David Airlie, Gerd Hoffmann

On 2026/09/24 18:25, Huang, Honglei wrote:
> 
> 
> On 9/19/2026 4:59 PM, Akihiko Odaki wrote:
>> On 2026/09/18 18:59, Honglei Huang wrote:
>>> Add userptr blob objects so the guest kernel can pin an existing
>>> userspace mapping and advertise it as CREATE_BLOB backing entries.
>>>
>>> - New virtio_gpu_object_userptr type for userptr resources
>>> - Pin pages with pin_user_pages_fast() and FOLL_LONGTERM
>>> - Omit FOLL_WRITE when VIRTGPU_BLOB_FLAG_USE_READONLY is set
>>> - Charge FOLL_LONGTERM pins against RLIMIT_MEMLOCK
>>> - DMA-map the scatterlist only when virtio_gpu_use_dma_api() is
>>>    required; use DMA_TO_DEVICE for USE_READONLY blobs
>>> - Sync userptr SG for the device on TRANSFER_TO_HOST
>>> - Mark writable pages dirty when unpinning
>>> - Keep pages pinned until RESOURCE_UNREF is queued; drop them from
>>>    cleanup_object() on the unref response or on create failure
>>> - Clear userptr->pages on pin failure to avoid double-free on cleanup
>>> - Reject unaligned or overflowing userptr ranges at create time
>>> - Disallow PRIME export of userptr objects
>>> - Save CREATE_BLOB params and restore userptr resources after
>>>    hibernation without using the shmem restore path
>>>
>>> Signed-off-by: Honglei Huang <honghuan@amd.com>
>>> ---
>>>   drivers/gpu/drm/virtio/Makefile          |   3 +-
>>>   drivers/gpu/drm/virtio/virtgpu_drv.h     |  43 +++
>>>   drivers/gpu/drm/virtio/virtgpu_object.c  |  22 ++
>>>   drivers/gpu/drm/virtio/virtgpu_userptr.c | 365 +++++++++++++++++++++++
>>>   drivers/gpu/drm/virtio/virtgpu_vq.c      |  33 +-
>>>   5 files changed, 456 insertions(+), 10 deletions(-)
>>>   create mode 100644 drivers/gpu/drm/virtio/virtgpu_userptr.c
>>>
>>> diff --git a/drivers/gpu/drm/virtio/Makefile b/drivers/gpu/drm/ 
>>> virtio/ Makefile
>>> index d2e1788a82..fe7332a621 100644
>>> --- a/drivers/gpu/drm/virtio/Makefile
>>> +++ b/drivers/gpu/drm/virtio/Makefile
>>> @@ -6,6 +6,7 @@
>>>   virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o 
>>> virtgpu_vram.o \
>>>       virtgpu_display.o virtgpu_vq.o \
>>>       virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o 
>>> virtgpu_plane.o \
>>> -    virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o 
>>> virtgpu_submit.o
>>> +    virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o 
>>> virtgpu_submit.o \
>>> +    virtgpu_userptr.o
>>>   obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
>>> diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/ 
>>> virtio/virtgpu_drv.h
>>> index 9df4c71173..e0941cc187 100644
>>> --- a/drivers/gpu/drm/virtio/virtgpu_drv.h
>>> +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
>>> @@ -105,6 +105,7 @@ struct virtio_gpu_object_params {
>>>       uint32_t blob_flags;
>>>       uint64_t blob_id;
>>>       uint32_t blob_hints;
>>> +    uint64_t userptr;
>>>   };
>>>   struct virtio_gpu_object {
>>> @@ -138,12 +139,42 @@ struct virtio_gpu_object_vram {
>>>       struct drm_mm_node vram_node;
>>>   };
>>> +struct virtio_gpu_object_userptr;
>>> +
>>> +struct virtio_gpu_object_userptr_ops {
>>> +    int (*get_pages)(struct virtio_gpu_object_userptr *userptr);
>>> +    void (*put_pages)(struct virtio_gpu_object_userptr *userptr);
>>> +};
>>> +
>>> +struct virtio_gpu_object_userptr {
>>> +    struct virtio_gpu_object base;
>>> +    const struct virtio_gpu_object_userptr_ops *ops;
>>> +    /* Protects pages and sgt. */
>>> +    struct mutex lock;
>>> +
>>> +    uint64_t start;
>>> +    uint32_t npages;
>>> +    uint32_t bo_handle;
>>> +    uint32_t flags;
>>> +
>>> +    struct virtio_gpu_device *vgdev;
>>> +    struct drm_file *file;
>>> +    struct page **pages;
>>> +    struct sg_table *sgt;
>>> +    bool dma_mapped;
>>> +    enum dma_data_direction dma_dir;
>>> +    struct mm_struct *mm;
>>> +};
>>> +
>>>   #define to_virtio_gpu_shmem(virtio_gpu_object) \
>>>       container_of((virtio_gpu_object), struct 
>>> virtio_gpu_object_shmem, base)
>>>   #define to_virtio_gpu_vram(virtio_gpu_object) \
>>>       container_of((virtio_gpu_object), struct 
>>> virtio_gpu_object_vram, base)
>>> +#define to_virtio_gpu_userptr(virtio_gpu_object) \
>>> +    container_of((virtio_gpu_object), struct 
>>> virtio_gpu_object_userptr, base)
>>> +
>>>   struct virtio_gpu_object_array {
>>>       struct ww_acquire_ctx ticket;
>>>       struct list_head next;
>>> @@ -284,6 +315,7 @@ struct virtio_gpu_device {
>>>       bool has_host_visible;
>>>       bool has_context_init;
>>>       bool has_blob_alignment;
>>> +    bool has_blob_readonly;
>>>       bool hibernated;
>>>       struct virtio_shm_region host_visible_region;
>>>       struct drm_mm host_visible_mm;
>>> @@ -562,4 +594,15 @@ void virtio_gpu_vram_map_deferred(struct 
>>> virtio_gpu_object_vram *vram);
>>>   int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
>>>                   struct drm_file *file);
>>> +/* virtgpu_userptr.c */
>>> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
>>> +                  struct drm_file *file,
>>> +                  struct virtio_gpu_object_params *params,
>>> +                  struct virtio_gpu_object **bo_ptr);
>>> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo);
>>> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object 
>>> *bo);
>>> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
>>> +                   struct virtio_gpu_object *bo,
>>> +                   struct virtio_gpu_mem_entry **ents,
>>> +                   unsigned int *nents);
>>>   #endif
>>> diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/ 
>>> drm/virtio/virtgpu_object.c
>>> index 49899485be..ab21494b1c 100644
>>> --- a/drivers/gpu/drm/virtio/virtgpu_object.c
>>> +++ b/drivers/gpu/drm/virtio/virtgpu_object.c
>>> @@ -91,6 +91,16 @@ void virtio_gpu_cleanup_object(struct 
>>> virtio_gpu_object *bo)
>>>           drm_gem_free_mmap_offset(&vram->base.base.base);
>>>           drm_gem_object_release(&vram->base.base.base);
>>>           kfree(vram);
>>> +    } else if (virtio_gpu_is_userptr(bo)) {
>>> +        struct virtio_gpu_object_userptr *userptr =
>>> +            to_virtio_gpu_userptr(bo);
>>> +
>>> +        mutex_lock(&userptr->lock);
>>> +        userptr->ops->put_pages(userptr);
>>> +        mutex_unlock(&userptr->lock);
>>> +        mutex_destroy(&userptr->lock);
>>> +        drm_gem_object_release(&userptr->base.base.base);
>>> +        kfree(userptr);
>>>       } else {
>>>           drm_gem_object_release(&bo->base.base);
>>>           kfree(bo);
>>> @@ -316,6 +326,18 @@ int virtio_gpu_object_restore_all(struct 
>>> virtio_gpu_device *vgdev)
>>>               continue;
>>>           }
>>> +        if (virtio_gpu_is_userptr(bo)) {
>>> +            ret = virtio_gpu_userptr_restore(vgdev, bo, &ents,
>>> +                             &nents);
>>> +            if (ret)
>>> +                break;
>>> +
>>> +            virtio_gpu_cmd_resource_create_blob(vgdev, bo,
>>> +                                &bo->params,
>>> +                                ents, nents);
>>> +            continue;
>>> +        }
>>> +
>>>           if (bo->params.blob || bo->attached) {
>>>               ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents,
>>>                                  &nents);
>>> diff --git a/drivers/gpu/drm/virtio/virtgpu_userptr.c b/drivers/gpu/ 
>>> drm/virtio/virtgpu_userptr.c
>>> new file mode 100644
>>> index 0000000000..ccfd96844e
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/virtio/virtgpu_userptr.c
>>> @@ -0,0 +1,365 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/limits.h>
>>> +#include <linux/mm.h>
>>> +#include <linux/overflow.h>
>>> +#include <linux/pid.h>
>>> +#include <linux/sched/mm.h>
>>> +#include <linux/sched/signal.h>
>>> +#include <linux/vmalloc.h>
>>> +
>>> +#include "virtgpu_drv.h"
>>> +#include <drm/drm_gem.h>
>>> +
>>> +static void virtio_gpu_userptr_free(struct drm_gem_object *obj)
>>> +{
>>> +    struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
>>> +    struct virtio_gpu_device *vgdev = obj->dev->dev_private;
>>> +
>>> +    /*
>>> +     * Keep pages pinned until RESOURCE_UNREF completes. The response
>>> +     * callback calls virtio_gpu_cleanup_object(), which drops them.
>>> +     */
>>> +    if (bo->created) {
>>> +        virtio_gpu_remove_from_restore_list(bo);
>>> +        virtio_gpu_cmd_unref_resource(vgdev, bo, false);
>>> +        virtio_gpu_notify(vgdev);
>>> +        return;
>>> +    }
>>> +
>>> +    virtio_gpu_cleanup_object(bo);
>>> +}
>>> +
>>> +static struct dma_buf *
>>> +virtio_gpu_userptr_prime_export(struct drm_gem_object *obj, int flags)
>>> +{
>>> +    return ERR_PTR(-EINVAL);
>>> +}
>>> +
>>> +static const struct drm_gem_object_funcs virtio_gpu_userptr_funcs = {
>>> +    .open = virtio_gpu_gem_object_open,
>>> +    .close = virtio_gpu_gem_object_close,
>>> +    .free = virtio_gpu_userptr_free,
>>> +    .export = virtio_gpu_userptr_prime_export,
>>> +};
>>> +
>>> +bool virtio_gpu_is_userptr(struct virtio_gpu_object *bo)
>>> +{
>>> +    return bo->base.base.funcs == &virtio_gpu_userptr_funcs;
>>> +}
>>> +
>>> +void virtio_gpu_userptr_dma_sync_for_device(struct virtio_gpu_object 
>>> *bo)
>>> +{
>>> +    struct virtio_gpu_object_userptr *userptr = 
>>> to_virtio_gpu_userptr(bo);
>>> +    struct device *dev;
>>> +
>>> +    if (!userptr->dma_mapped)
>>> +        return;
>>> +
>>> +    dev = drm_dev_dma_dev(userptr->base.base.base.dev);
>>> +    dma_sync_sgtable_for_device(dev, userptr->sgt, DMA_TO_DEVICE);
>>> +}
>>> +
>>> +static int
>>> +virtio_gpu_userptr_get_pages(struct virtio_gpu_object_userptr *userptr)
>>> +{
>>> +    unsigned int flag = FOLL_LONGTERM;
>>> +    unsigned int num_pages, pinned = 0;
>>> +    int ret = 0;
>>> +
>>> +    if (userptr->pages)
>>> +        return 0;
>>> +
>>> +    userptr->pages = kvmalloc_array(userptr->npages, sizeof(struct 
>>> page *),
>>> +                    GFP_KERNEL);
>>> +    if (!userptr->pages)
>>> +        return -ENOMEM;
>>> +
>>> +    if (!(userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY))
>>> +        flag |= FOLL_WRITE;
>>> +
>>> +    do {
>>> +        num_pages = userptr->npages - pinned;
>>> +
>>> +        ret = pin_user_pages_fast(userptr->start + pinned * PAGE_SIZE,
>>> +                      num_pages, flag,
>>> +                      userptr->pages + pinned);
>>> +
>>> +        if (ret < 0) {
>>> +            if (pinned)
>>> +                unpin_user_pages(userptr->pages, pinned);
>>> +            kvfree(userptr->pages);
>>> +            userptr->pages = NULL;
>>> +            return ret;
>>> +        }
>>> +
>>> +        pinned += ret;
>>> +
>>> +    } while (pinned < userptr->npages);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void
>>> +virtio_gpu_userptr_unaccount(struct virtio_gpu_object_userptr *userptr)
>>> +{
>>> +    if (!userptr->mm)
>>> +        return;
>>> +
>>> +    atomic64_sub(userptr->npages, &userptr->mm->pinned_vm);
>>> +    mmdrop(userptr->mm);
>>> +    userptr->mm = NULL;
>>> +}
>>> +
>>> +static void
>>> +virtio_gpu_userptr_put_pages(struct virtio_gpu_object_userptr *userptr)
>>> +{
>>> +    struct drm_device *dev = userptr->base.base.base.dev;
>>> +
>>> +    if (userptr->sgt) {
>>> +        if (userptr->dma_mapped)
>>> +            dma_unmap_sgtable(drm_dev_dma_dev(dev), userptr->sgt,
>>> +                      userptr->dma_dir, 0);
>>> +        userptr->dma_mapped = false;
>>> +        sg_free_table(userptr->sgt);
>>> +        kfree(userptr->sgt);
>>> +        userptr->sgt = NULL;
>>> +    }
>>> +
>>> +    if (userptr->pages) {
>>> +        bool dirty = !(userptr->flags & 
>>> VIRTGPU_BLOB_FLAG_USE_READONLY);
>>> +
>>> +        unpin_user_pages_dirty_lock(userptr->pages, userptr->npages,
>>> +                        dirty);
>>> +        kvfree(userptr->pages);
>>> +        userptr->pages = NULL;
>>> +    }
>>> +
>>> +    virtio_gpu_userptr_unaccount(userptr);
>>> +}
>>> +
>>> +static int
>>> +virtio_gpu_userptr_get_entries(struct virtio_gpu_device *vgdev,
>>> +                   struct virtio_gpu_object_userptr *userptr,
>>> +                   struct virtio_gpu_mem_entry **ents,
>>> +                   unsigned int *nents)
>>> +{
>>> +    bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>>> +    struct scatterlist *sg;
>>> +    unsigned int count;
>>> +    int si;
>>> +
>>> +    count = use_dma_api ? userptr->sgt->nents : userptr->sgt- 
>>> >orig_nents;
>>> +    if (!count)
>>> +        return -EINVAL;
>>> +
>>> +    *ents = kvmalloc_array(count, sizeof(**ents), GFP_KERNEL);
>>> +    if (!*ents)
>>> +        return -ENOMEM;
>>> +
>>> +    if (use_dma_api) {
>>> +        for_each_sgtable_dma_sg(userptr->sgt, sg, si) {
>>> +            (*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
>>> +            (*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
>>> +            (*ents)[si].padding = 0;
>>> +        }
>>> +    } else {
>>> +        for_each_sgtable_sg(userptr->sgt, sg, si) {
>>> +            (*ents)[si].addr = cpu_to_le64(sg_phys(sg));
>>> +            (*ents)[si].length = cpu_to_le32(sg->length);
>>> +            (*ents)[si].padding = 0;
>>> +        }
>>> +    }
>>> +
>>> +    *nents = count;
>>> +    return 0;
>>> +}
>>> +
>>> +int virtio_gpu_userptr_restore(struct virtio_gpu_device *vgdev,
>>> +                   struct virtio_gpu_object *bo,
>>> +                   struct virtio_gpu_mem_entry **ents,
>>> +                   unsigned int *nents)
>>> +{
>>> +    struct virtio_gpu_object_userptr *userptr = 
>>> to_virtio_gpu_userptr(bo);
>>> +    int ret;
>>> +
>>> +    mutex_lock(&userptr->lock);
>>> +    if (!userptr->sgt || !userptr->pages) {
>>> +        mutex_unlock(&userptr->lock);
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    if (userptr->dma_mapped) {
>>> +        struct device *dev = drm_dev_dma_dev(vgdev->ddev);
>>> +
>>> +        dma_unmap_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
>>> +        userptr->dma_mapped = false;
>>> +        ret = dma_map_sgtable(dev, userptr->sgt, userptr->dma_dir, 0);
>>
>> This maps with attributes 0, allowing bounce buffers instead of
>> "zero-copy memory access". It is also unclear what coherence guarantee 
>> the UAPI is intended to provide.
> 
> 
> This is a good point, will modify the commit and patch tittle to remove
> ero-copy memory access.
> And in virtio_gpu_object_shmem_init it also uses dma_map_sgtable(..., 
> 0), so keep 0 here.

Removing the zero-copy claim addresses the wording, but the coherence
question remains. The required guarantees depend on how ROCm accesses
this memory. Using the same DMA mapping attributes as the shmem path
does not establish that those guarantees are met.

Regards,
Akihiko Odaki

> 
> Regards,
> Honglei
> 
>>
>>> +        if (ret) {
>>> +            mutex_unlock(&userptr->lock);
>>> +            return ret;
>>> +        }
>>> +        userptr->dma_mapped = true;
>>> +    }
>>> +
>>> +    ret = virtio_gpu_userptr_get_entries(vgdev, userptr, ents, nents);
>>> +    mutex_unlock(&userptr->lock);
>>> +    return ret;
>>> +}
>>> +
>>> +static int
>>> +virtio_gpu_userptr_init(struct drm_device *dev, struct drm_file *file,
>>> +            struct virtio_gpu_object_userptr *userptr,
>>> +            struct virtio_gpu_object_params *params,
>>> +            const struct virtio_gpu_object_userptr_ops *ops)
>>> +{
>>> +    struct drm_gem_object *obj;
>>> +    int ret;
>>> +
>>> +    userptr->start = params->userptr;
>>> +    userptr->npages = params->size >> PAGE_SHIFT;
>>> +    userptr->flags = params->blob_flags;
>>> +
>>> +    mutex_init(&userptr->lock);
>>> +    userptr->vgdev = dev->dev_private;
>>> +    userptr->file = file;
>>> +    userptr->ops = ops;
>>> +
>>> +    /*
>>> +     * Allocate the resource id before GEM init so a failure here can
>>> +     * unwind with a plain kfree and does not need a special id=0 guard
>>> +     * in the shared resource_id_put helper.
>>> +     */
>>> +    ret = virtio_gpu_resource_id_get(userptr->vgdev,
>>> +                     &userptr->base.hw_res_handle);
>>> +    if (ret) {
>>> +        mutex_destroy(&userptr->lock);
>>> +        return ret;
>>> +    }
>>> +
>>> +    obj = &userptr->base.base.base;
>>> +    obj->funcs = &virtio_gpu_userptr_funcs;
>>> +
>>> +    drm_gem_private_object_init(dev, obj, params->size);
>>> +    INIT_LIST_HEAD(&userptr->base.restore_node);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct virtio_gpu_object_userptr_ops 
>>> virtio_gpu_userptr_ops = {
>>> +    .get_pages = virtio_gpu_userptr_get_pages,
>>> +    .put_pages = virtio_gpu_userptr_put_pages,
>>> +};
>>> +
>>> +int virtio_gpu_userptr_create(struct virtio_gpu_device *vgdev,
>>> +                  struct drm_file *file,
>>> +                  struct virtio_gpu_object_params *params,
>>> +                  struct virtio_gpu_object **bo_ptr)
>>> +{
>>> +    struct virtio_gpu_object_userptr *userptr;
>>> +    struct virtio_gpu_mem_entry *ents = NULL;
>>> +    struct sg_table *sgt;
>>> +    struct mm_struct *mm;
>>> +    unsigned long lock_limit;
>>> +    unsigned long start;
>>> +    unsigned long end;
>>> +    s64 new_pinned;
>>> +    unsigned int nents;
>>> +    int ret;
>>> +
>>> +    *bo_ptr = NULL;
>>> +
>>> +    if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE) ||
>>> +        params->userptr != (unsigned long)params->userptr)
>>> +        return -EINVAL;
>>> +
>>> +    start = params->userptr;
>>> +    if (!IS_ALIGNED(start, PAGE_SIZE) ||
>>> +        check_add_overflow(start, (unsigned long)params->size, &end))
>>> +        return -EINVAL;
>>> +
>>> +    if (!can_do_mlock())
>>> +        return -EPERM;
>>> +
>>> +    if (params->size >> PAGE_SHIFT > INT_MAX)
>>> +        return -E2BIG;
>>> +
>>> +    if (!access_ok((void __user *)start, params->size))
>>> +        return -EFAULT;
>>> +
>>> +    userptr = kzalloc_obj(*userptr);
>>> +    if (!userptr)
>>> +        return -ENOMEM;
>>> +
>>> +    ret = virtio_gpu_userptr_init(vgdev->ddev, file, userptr, params,
>>> +                      &virtio_gpu_userptr_ops);
>>> +    if (ret) {
>>> +        kfree(userptr);
>>> +        return ret;
>>> +    }
>>> +
>>> +    mm = current->mm;
>>> +    mmgrab(mm);
>>> +    lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
>>> +    new_pinned = atomic64_add_return(userptr->npages, &mm->pinned_vm);
>>> +    if (new_pinned < 0 ||
>>> +        (new_pinned > lock_limit && !capable(CAP_IPC_LOCK))) {
>>> +        atomic64_sub(userptr->npages, &mm->pinned_vm);
>>> +        mmdrop(mm);
>>> +        ret = new_pinned < 0 ? -EOVERFLOW : -ENOMEM;
>>> +        goto err_cleanup;
>>> +    }
>>> +    userptr->mm = mm;
>>> +
>>> +    mutex_lock(&userptr->lock);
>>> +    ret = userptr->ops->get_pages(userptr);
>>> +    mutex_unlock(&userptr->lock);
>>> +    if (ret)
>>> +        goto err_cleanup;
>>> +
>>> +    sgt = drm_prime_pages_to_sg(vgdev->ddev, userptr->pages,
>>> +                    userptr->npages);
>>> +    if (IS_ERR(sgt)) {
>>> +        ret = PTR_ERR(sgt);
>>> +        goto err_cleanup;
>>> +    }
>>> +
>>> +    userptr->sgt = sgt;
>>> +
>>> +    /*
>>> +     * Match shmem blobs: only DMA-map when the virtio DMA API is in
>>> +     * use. Mapping unconditionally can create SWIOTLB bounce buffers
>>> +     * that get copied back over guest pages on unmap even though the
>>> +     * host was given sg_phys() addresses.
>>> +     */
>>> +    if (virtio_gpu_use_dma_api(vgdev->vdev)) {
>>> +        enum dma_data_direction dir =
>>> +            (userptr->flags & VIRTGPU_BLOB_FLAG_USE_READONLY) ?
>>> +            DMA_TO_DEVICE : DMA_BIDIRECTIONAL;
>>> +
>>> +        ret = dma_map_sgtable(drm_dev_dma_dev(vgdev->ddev), sgt,
>>> +                      dir, 0);
>>> +        if (ret)
>>> +            goto err_cleanup;
>>> +
>>> +        userptr->dma_dir = dir;
>>> +        userptr->dma_mapped = true;
>>> +    }
>>> +
>>> +    ret = virtio_gpu_userptr_get_entries(vgdev, userptr, &ents, 
>>> &nents);
>>> +    if (ret)
>>> +        goto err_cleanup;
>>> +
>>> +    virtio_gpu_cmd_resource_create_blob(vgdev, &userptr->base, 
>>> params, ents,
>>> +                        nents);
>>> +
>>> +    userptr->base.params = *params;
>>> +    virtio_gpu_add_object_to_restore_list(vgdev, &userptr->base);
>>> +
>>> +    *bo_ptr = &userptr->base;
>>> +    return 0;
>>> +
>>> +err_cleanup:
>>> +    virtio_gpu_cleanup_object(&userptr->base);
>>> +    return ret;
>>> +}
>>> diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/ 
>>> virtio/virtgpu_vq.c
>>> index c02c03c10d..dcbd7bb7a6 100644
>>> --- a/drivers/gpu/drm/virtio/virtgpu_vq.c
>>> +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
>>> @@ -781,9 +781,14 @@ int 
>>> virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device 
>>> *vgdev,
>>>       struct virtio_gpu_vbuffer *vbuf;
>>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> -                        bo->base.sgt, DMA_TO_DEVICE);
>>> +    if (use_dma_api) {
>>> +        if (virtio_gpu_is_shmem(bo))
>>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> +                            bo->base.sgt,
>>> +                            DMA_TO_DEVICE);
>>> +        else if (virtio_gpu_is_userptr(bo))
>>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
>>> +    }
>>>       cmd_p = virtio_gpu_panic_alloc_cmd_resp(vgdev, &vbuf, 
>>> sizeof(*cmd_p));
>>>       memset(cmd_p, 0, sizeof(*cmd_p));
>>> @@ -812,9 +817,14 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct 
>>> virtio_gpu_device *vgdev,
>>>       struct virtio_gpu_vbuffer *vbuf;
>>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> -                        bo->base.sgt, DMA_TO_DEVICE);
>>> +    if (use_dma_api) {
>>> +        if (virtio_gpu_is_shmem(bo))
>>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> +                            bo->base.sgt,
>>> +                            DMA_TO_DEVICE);
>>> +        else if (virtio_gpu_is_userptr(bo))
>>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
>>> +    }
>>>       cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p));
>>>       memset(cmd_p, 0, sizeof(*cmd_p));
>>> @@ -1245,9 +1255,14 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct 
>>> virtio_gpu_device *vgdev,
>>>       struct virtio_gpu_vbuffer *vbuf;
>>>       bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev);
>>> -    if (virtio_gpu_is_shmem(bo) && use_dma_api)
>>> -        dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> -                        bo->base.sgt, DMA_TO_DEVICE);
>>> +    if (use_dma_api) {
>>> +        if (virtio_gpu_is_shmem(bo))
>>> +            dma_sync_sgtable_for_device(vgdev->vdev->dev.parent,
>>> +                            bo->base.sgt,
>>> +                            DMA_TO_DEVICE);
>>> +        else if (virtio_gpu_is_userptr(bo))
>>> +            virtio_gpu_userptr_dma_sync_for_device(bo);
>>
>> This code is ineffective since the transfer ioctl rejects 
>> VIRTGPU_BLOB_MEM_GUEST.
>>
>> Regards,
>> Akihiko Odaki
> 


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

end of thread, other threads:[~2026-09-28  8:43 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-18  7:55 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
2026-09-18  7:55 ` [PATCH v8 1/4] drm/virtio-gpu: Add VIRTIO_GPU_CAPSET_ROCM capability Honglei Huang
2026-09-18  8:07   ` sashiko-bot
2026-09-18  7:55 ` [PATCH v8 2/4] drm/virtgpu api: add blob userptr resource Honglei Huang
2026-09-18  7:55 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
2026-09-18  8:17   ` sashiko-bot
2026-09-18  7:56 ` [PATCH v8 4/4] drm/virtio: wire blob ioctl creation to userptr objects Honglei Huang
2026-09-18  8:12   ` sashiko-bot
  -- strict thread matches above, loose matches on Subject: below --
2026-09-18  9:59 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
2026-09-18  9:59 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
2026-09-18 10:21   ` sashiko-bot
2026-09-19  8:59   ` Akihiko Odaki
2026-09-24  9:25     ` Huang, Honglei
2026-09-26 10:17       ` Akihiko Odaki
2026-09-18  8:23 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
2026-09-18  8:23 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
2026-09-18  6:56 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
2026-09-18  6:56 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
2026-09-18  5:59 [PATCH v8 0/4] virtio-gpu: Add userptr support for compute workloads Honglei Huang
2026-09-18  5:59 ` [PATCH v8 3/4] drm/virtio: implement userptr support for zero-copy memory access Honglei Huang
2026-09-18  6:16   ` sashiko-bot

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