diff --git a/drivers/gpu/drm/vpu/Makefile b/drivers/gpu/drm/vpu/Makefile
index b789e3a6ed22..b2f7a6c240f7 100644
--- a/drivers/gpu/drm/vpu/Makefile
+++ b/drivers/gpu/drm/vpu/Makefile
@@ -3,6 +3,7 @@
 
 intel_vpu-y := \
 	vpu_drv.o \
+	vpu_gem.o \
 	vpu_hw_mtl.o \
 	vpu_mmu.o \
 	vpu_mmu_context.o
diff --git a/drivers/gpu/drm/vpu/vpu_drv.c b/drivers/gpu/drm/vpu/vpu_drv.c
index 75ec1fe9c3e2..0e820aeecdcc 100644
--- a/drivers/gpu/drm/vpu/vpu_drv.c
+++ b/drivers/gpu/drm/vpu/vpu_drv.c
@@ -11,8 +11,10 @@
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
+#include <drm/drm_prime.h>
 
 #include "vpu_drv.h"
+#include "vpu_gem.h"
 #include "vpu_hw.h"
 #include "vpu_mmu.h"
 #include "vpu_mmu_context.h"
@@ -187,6 +189,9 @@ static void vpu_postclose(struct drm_device *dev, struct drm_file *file)
 static const struct drm_ioctl_desc vpu_drm_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(VPU_GET_PARAM, vpu_get_param_ioctl, DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF_DRV(VPU_SET_PARAM, vpu_set_param_ioctl, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(VPU_BO_CREATE, vpu_bo_create_ioctl, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(VPU_BO_INFO, vpu_bo_info_ioctl, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(VPU_BO_USERPTR, vpu_bo_userptr_ioctl, DRM_RENDER_ALLOW),
 };
 
 DEFINE_DRM_GEM_FOPS(vpu_fops);
@@ -210,6 +215,10 @@ static const struct drm_driver driver = {
 
 	.open = vpu_open,
 	.postclose = vpu_postclose,
+	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+	.gem_prime_import = vpu_gem_prime_import,
+	.gem_prime_mmap = drm_gem_prime_mmap,
 
 	.ioctls = vpu_drm_ioctls,
 	.num_ioctls = ARRAY_SIZE(vpu_drm_ioctls),
diff --git a/drivers/gpu/drm/vpu/vpu_gem.c b/drivers/gpu/drm/vpu/vpu_gem.c
new file mode 100644
index 000000000000..12f82ab941bd
--- /dev/null
+++ b/drivers/gpu/drm/vpu/vpu_gem.c
@@ -0,0 +1,833 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright © 2020-2022 Intel Corporation
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/highmem.h>
+#include <linux/module.h>
+#include <linux/set_memory.h>
+#include <linux/xarray.h>
+
+#include <drm/drm_cache.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_file.h>
+#include <drm/drm_utils.h>
+
+#include "vpu_drv.h"
+#include "vpu_gem.h"
+#include "vpu_hw.h"
+#include "vpu_mmu.h"
+#include "vpu_mmu_context.h"
+
+MODULE_IMPORT_NS(DMA_BUF);
+
+static const struct drm_gem_object_funcs vpu_gem_funcs;
+
+static int __must_check prime_alloc_pages_locked(struct vpu_bo *bo)
+{
+	/* Pages are managed by the underlying dma-buf */
+	return 0;
+}
+
+static void prime_free_pages_locked(struct vpu_bo *bo)
+{
+	/* Pages are managed by the underlying dma-buf */
+}
+
+static int prime_map_pages_locked(struct vpu_bo *bo)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	struct sg_table *sgt;
+
+	WARN_ON(!bo->base.import_attach);
+
+	sgt = dma_buf_map_attachment(bo->base.import_attach, DMA_BIDIRECTIONAL);
+	if (IS_ERR(sgt)) {
+		vpu_err(vdev, "Failed to map attachment: %ld\n", PTR_ERR(sgt));
+		return PTR_ERR(sgt);
+	}
+
+	bo->sgt = sgt;
+	return 0;
+}
+
+static void prime_unmap_pages_locked(struct vpu_bo *bo)
+{
+	WARN_ON(!bo->base.import_attach);
+
+	dma_buf_unmap_attachment(bo->base.import_attach, bo->sgt, DMA_BIDIRECTIONAL);
+	bo->sgt = NULL;
+}
+
+static const struct vpu_bo_ops prime_ops = {
+	.type = VPU_BO_TYPE_PRIME,
+	.name = "prime",
+	.alloc_pages = prime_alloc_pages_locked,
+	.free_pages = prime_free_pages_locked,
+	.map_pages = prime_map_pages_locked,
+	.unmap_pages = prime_unmap_pages_locked,
+};
+
+static int __must_check shmem_alloc_pages_locked(struct vpu_bo *bo)
+{
+	int npages = bo->base.size >> PAGE_SHIFT;
+	struct page **pages;
+
+	pages = drm_gem_get_pages(&bo->base);
+	if (IS_ERR(pages))
+		return PTR_ERR(pages);
+
+	if (!vpu_bo_is_cached(bo))
+		set_pages_array_uc(pages, npages);
+
+	bo->pages = pages;
+	return 0;
+}
+
+static void shmem_free_pages_locked(struct vpu_bo *bo)
+{
+	if (!vpu_bo_is_cached(bo))
+		set_pages_array_wb(bo->pages, bo->base.size >> PAGE_SHIFT);
+
+	drm_gem_put_pages(&bo->base, bo->pages, true, false);
+	bo->pages = NULL;
+}
+
+static int vpu_bo_map_pages_locked(struct vpu_bo *bo)
+{
+	int npages = bo->base.size >> PAGE_SHIFT;
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	struct sg_table *sgt;
+	int ret;
+
+	sgt = drm_prime_pages_to_sg(&vdev->drm, bo->pages, npages);
+	if (IS_ERR(sgt)) {
+		vpu_err(vdev, "Failed to allocate sgtable\n");
+		return PTR_ERR(sgt);
+	}
+
+	ret = dma_map_sgtable(vdev->drm.dev, sgt, DMA_BIDIRECTIONAL, 0);
+	if (ret) {
+		vpu_err(vdev, "Failed to map BO in IOMMU: %d\n", ret);
+		goto err_free_sgt;
+	}
+
+	bo->sgt = sgt;
+	return 0;
+
+err_free_sgt:
+	kfree(sgt);
+	return ret;
+}
+
+static void vpu_bo_unmap_pages_locked(struct vpu_bo *bo)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+
+	dma_unmap_sgtable(vdev->drm.dev, bo->sgt, DMA_BIDIRECTIONAL, 0);
+	sg_free_table(bo->sgt);
+	kfree(bo->sgt);
+	bo->sgt = NULL;
+}
+
+static const struct vpu_bo_ops shmem_ops = {
+	.type = VPU_BO_TYPE_SHMEM,
+	.name = "shmem",
+	.alloc_pages = shmem_alloc_pages_locked,
+	.free_pages = shmem_free_pages_locked,
+	.map_pages = vpu_bo_map_pages_locked,
+	.unmap_pages = vpu_bo_unmap_pages_locked,
+};
+
+static int __must_check userptr_alloc_pages_locked(struct vpu_bo *bo)
+{
+	unsigned int npages = bo->base.size >> PAGE_SHIFT;
+	struct page **pages;
+	int ret;
+
+	pages = kvmalloc_array(npages, sizeof(*bo->pages), GFP_KERNEL);
+	if (!pages)
+		return -ENOMEM;
+
+	ret = pin_user_pages(bo->user_ptr & PAGE_MASK, npages,
+			     FOLL_FORCE | FOLL_WRITE | FOLL_LONGTERM, pages, NULL);
+	if (ret != npages) {
+		if (ret > 0)
+			goto err_unpin_pages;
+		goto err_free_pages;
+	}
+
+	bo->pages = pages;
+	return 0;
+
+err_unpin_pages:
+	unpin_user_pages(pages, ret);
+err_free_pages:
+	kvfree(pages);
+	return ret;
+}
+
+static void userptr_free_pages_locked(struct vpu_bo *bo)
+{
+	unpin_user_pages(bo->pages, bo->base.size >> PAGE_SHIFT);
+	kvfree(bo->pages);
+	bo->pages = NULL;
+}
+
+static const struct vpu_bo_ops userptr_ops = {
+	.type = VPU_BO_TYPE_USERPTR,
+	.name = "userptr",
+	.alloc_pages = userptr_alloc_pages_locked,
+	.free_pages = userptr_free_pages_locked,
+	.map_pages = vpu_bo_map_pages_locked,
+	.unmap_pages = vpu_bo_unmap_pages_locked,
+};
+
+static int __must_check internal_alloc_pages_locked(struct vpu_bo *bo)
+{
+	unsigned int i, npages = bo->base.size >> PAGE_SHIFT;
+	struct page **pages;
+	int ret;
+
+	pages = kvmalloc_array(npages, sizeof(*bo->pages), GFP_KERNEL);
+	if (!pages)
+		return -ENOMEM;
+
+	for (i = 0; i < npages; i++) {
+		pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
+		if (!pages[i]) {
+			ret = -ENOMEM;
+			goto err_free_pages;
+		}
+		cond_resched();
+	}
+
+	bo->pages = pages;
+	return 0;
+
+err_free_pages:
+	while (i--)
+		put_page(pages[i]);
+	kvfree(pages);
+	return ret;
+}
+
+static void internal_free_pages_locked(struct vpu_bo *bo)
+{
+	unsigned int i, npages = bo->base.size >> PAGE_SHIFT;
+
+	for (i = 0; i < npages; i++)
+		put_page(bo->pages[i]);
+
+	kvfree(bo->pages);
+	bo->pages = NULL;
+}
+
+static const struct vpu_bo_ops internal_ops = {
+	.type = VPU_BO_TYPE_INTERNAL,
+	.name = "internal",
+	.alloc_pages = internal_alloc_pages_locked,
+	.free_pages = internal_free_pages_locked,
+	.map_pages = vpu_bo_map_pages_locked,
+	.unmap_pages = vpu_bo_unmap_pages_locked,
+};
+
+static int __must_check vpu_bo_alloc_and_map_pages_locked(struct vpu_bo *bo)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	int ret;
+
+	lockdep_assert_held(&bo->lock);
+	WARN_ON(bo->sgt);
+
+	ret = bo->ops->alloc_pages(bo);
+	if (ret) {
+		vpu_err(vdev, "Failed to allocate pages for BO: %d", ret);
+		return ret;
+	}
+
+	ret = bo->ops->map_pages(bo);
+	if (ret) {
+		vpu_err(vdev, "Failed to map pages for BO: %d", ret);
+		goto err_free_pages;
+	}
+	return ret;
+
+err_free_pages:
+	bo->ops->free_pages(bo);
+	return ret;
+}
+
+static void vpu_bo_unmap_and_free_pages(struct vpu_bo *bo)
+{
+	mutex_lock(&bo->lock);
+
+	WARN_ON(!bo->sgt);
+	bo->ops->unmap_pages(bo);
+	WARN_ON(bo->sgt);
+	bo->ops->free_pages(bo);
+	WARN_ON(bo->pages);
+
+	mutex_unlock(&bo->lock);
+}
+
+/**
+ * vpu_bo_pin() - pin the backing physical pages and map them to VPU.
+ *
+ * This function pins physical memory pages, then maps the physical pages
+ * to IOMMU address space and finally updates the VPU MMU page tables
+ * to allow the VPU to translate VPU address to IOMMU address.
+ */
+int __must_check vpu_bo_pin(struct vpu_bo *bo)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	int ret = 0;
+
+	mutex_lock(&bo->lock);
+
+	if (!bo->vpu_addr) {
+		vpu_err(vdev, "vpu_addr not set for BO ctx_id: %d handle: %d\n",
+			bo->ctx->id, bo->handle);
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	if (!bo->sgt) {
+		ret = vpu_bo_alloc_and_map_pages_locked(bo);
+		if (ret)
+			goto unlock;
+	}
+
+	if (!bo->mmu_mapped) {
+		ret = vpu_mmu_context_map_sgt(vdev, bo->ctx, bo->vpu_addr, bo->sgt,
+					      vpu_bo_is_cached(bo));
+		if (ret) {
+			vpu_err(vdev, "Failed to map BO in MMU: %d\n", ret);
+			goto unlock;
+		}
+		bo->mmu_mapped = true;
+	}
+
+unlock:
+	mutex_unlock(&bo->lock);
+
+	return ret;
+}
+
+static int
+vpu_bo_alloc_vpu_addr(struct vpu_bo *bo, struct vpu_mmu_context *ctx,
+		      const struct vpu_addr_range *range)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	int ret;
+
+	if (!range) {
+		if (bo->flags & DRM_VPU_BO_HIGH_MEM)
+			range = &vdev->hw->ranges.user_high;
+		else
+			range = &vdev->hw->ranges.user_low;
+	}
+
+	mutex_lock(&ctx->lock);
+	ret = vpu_mmu_context_insert_node_locked(ctx, range, bo->base.size, &bo->mm_node);
+	if (!ret) {
+		bo->ctx = ctx;
+		bo->vpu_addr = bo->mm_node.start;
+		list_add_tail(&bo->ctx_node, &ctx->bo_list);
+	}
+	mutex_unlock(&ctx->lock);
+
+	return ret;
+}
+
+static void vpu_bo_free_vpu_addr(struct vpu_bo *bo)
+{
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	struct vpu_mmu_context *ctx = bo->ctx;
+
+	vpu_dbg(BO, "remove from ctx: ctx %d vpu_addr 0x%llx allocated %d mmu_mapped %d\n",
+		ctx->id, bo->vpu_addr, (bool)bo->sgt, bo->mmu_mapped);
+
+	mutex_lock(&bo->lock);
+
+	if (bo->mmu_mapped) {
+		WARN_ON(!bo->sgt);
+		vpu_mmu_context_unmap_sgt(vdev, ctx, bo->vpu_addr, bo->sgt);
+		bo->mmu_mapped = false;
+	}
+
+	mutex_lock(&ctx->lock);
+	list_del(&bo->ctx_node);
+	bo->vpu_addr = 0;
+	bo->ctx = NULL;
+	vpu_mmu_context_remove_node_locked(ctx, &bo->mm_node);
+	mutex_unlock(&ctx->lock);
+
+	mutex_unlock(&bo->lock);
+}
+
+void vpu_bo_remove_all_bos_from_context(struct vpu_mmu_context *ctx)
+{
+	struct vpu_bo *bo, *tmp;
+
+	list_for_each_entry_safe(bo, tmp, &ctx->bo_list, ctx_node)
+		vpu_bo_free_vpu_addr(bo);
+}
+
+static struct vpu_bo *
+vpu_bo_alloc(struct vpu_device *vdev, struct vpu_mmu_context *mmu_context,
+	     u64 unaligned_size, u32 flags, const struct vpu_bo_ops *ops,
+	     const struct vpu_addr_range *range, u64 user_ptr)
+{
+	u64 size = PAGE_ALIGN(unaligned_size);
+	struct vpu_bo *bo;
+	int ret = 0;
+
+	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+	if (!bo)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&bo->lock);
+	bo->base.funcs = &vpu_gem_funcs;
+	bo->flags = flags;
+	bo->ops = ops;
+	bo->user_ptr = user_ptr;
+
+	if (ops->type == VPU_BO_TYPE_SHMEM)
+		ret = drm_gem_object_init(&vdev->drm, &bo->base, size);
+	else
+		drm_gem_private_object_init(&vdev->drm, &bo->base, size);
+
+	if (ret) {
+		vpu_err(vdev, "Failed to initialize drm object\n");
+		goto err_free;
+	}
+
+	if (flags & DRM_VPU_BO_MAPPABLE) {
+		ret = drm_gem_create_mmap_offset(&bo->base);
+		if (ret) {
+			vpu_err(vdev, "Failed to allocate mmap offset\n");
+			goto err_release;
+		}
+	}
+
+	if (mmu_context) {
+		ret = vpu_bo_alloc_vpu_addr(bo, mmu_context, range);
+		if (ret) {
+			vpu_err(vdev, "Failed to add BO to context: %d\n", ret);
+			goto err_release;
+		}
+	}
+
+	return bo;
+
+err_release:
+	drm_gem_object_release(&bo->base);
+err_free:
+	kfree(bo);
+	return ERR_PTR(ret);
+}
+
+static void vpu_bo_free(struct drm_gem_object *obj)
+{
+	struct vpu_bo *bo = to_vpu_bo(obj);
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+
+	if (bo->ctx)
+		vpu_dbg(BO, "free: ctx %d vpu_addr 0x%llx allocated %d mmu_mapped %d\n",
+			bo->ctx->id, bo->vpu_addr, (bool)bo->sgt, bo->mmu_mapped);
+	else
+		vpu_dbg(BO, "free: ctx (released) allocated %d mmu_mapped %d\n",
+			(bool)bo->sgt, bo->mmu_mapped);
+
+	WARN_ON(!dma_resv_test_signaled(obj->resv, DMA_RESV_USAGE_READ));
+
+	vunmap(bo->kvaddr);
+
+	if (bo->ctx)
+		vpu_bo_free_vpu_addr(bo);
+
+	if (bo->sgt)
+		vpu_bo_unmap_and_free_pages(bo);
+
+	if (bo->base.import_attach)
+		drm_prime_gem_destroy(&bo->base, bo->sgt);
+
+	drm_gem_object_release(&bo->base);
+
+	mutex_destroy(&bo->lock);
+	kfree(bo);
+}
+
+static int vpu_bo_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+	struct vpu_bo *bo = to_vpu_bo(obj);
+	struct vpu_device *vdev = vpu_bo_to_vdev(bo);
+	pgprot_t vm_page_prot;
+
+	vpu_dbg(BO, "mmap: ctx %u handle %u vpu_addr 0x%llx size %zu type %s",
+		bo->ctx->id, bo->handle, bo->vpu_addr, bo->base.size, bo->ops->name);
+
+	if (obj->import_attach) {
+		/* Drop the reference drm_gem_mmap_obj() acquired.*/
+		drm_gem_object_put(obj);
+		vma->vm_private_data = NULL;
+		return dma_buf_mmap(obj->dma_buf, vma, 0);
+	}
+
+	vma->vm_flags |= VM_PFNMAP | VM_DONTEXPAND;
+
+	if (!vpu_bo_is_cached(bo)) {
+		vm_page_prot = vm_get_page_prot(vma->vm_flags);
+		vma->vm_page_prot = pgprot_noncached(vm_page_prot);
+	}
+
+	return 0;
+}
+
+static struct sg_table *vpu_bo_get_sg_table(struct drm_gem_object *obj)
+{
+	struct vpu_bo *bo = to_vpu_bo(obj);
+	loff_t npages = obj->size >> PAGE_SHIFT;
+	int ret = 0;
+
+	mutex_lock(&bo->lock);
+
+	if (!bo->sgt)
+		ret = vpu_bo_alloc_and_map_pages_locked(bo);
+
+	mutex_unlock(&bo->lock);
+
+	if (ret)
+		return ERR_PTR(ret);
+
+	return drm_prime_pages_to_sg(obj->dev, bo->pages, npages);
+}
+
+static vm_fault_t vpu_vm_fault(struct vm_fault *vmf)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct drm_gem_object *obj = vma->vm_private_data;
+	struct vpu_bo *bo = to_vpu_bo(obj);
+	loff_t npages = obj->size >> PAGE_SHIFT;
+	pgoff_t page_offset;
+	struct page *page;
+	vm_fault_t ret;
+	int err;
+
+	mutex_lock(&bo->lock);
+
+	if (!bo->sgt) {
+		err = vpu_bo_alloc_and_map_pages_locked(bo);
+		if (err) {
+			ret = vmf_error(err);
+			goto unlock;
+		}
+	}
+
+	/* We don't use vmf->pgoff since that has the fake offset */
+	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
+	if (page_offset >= npages) {
+		ret = VM_FAULT_SIGBUS;
+	} else {
+		page = bo->pages[page_offset];
+		ret = vmf_insert_pfn(vma, vmf->address, page_to_pfn(page));
+	}
+
+unlock:
+	mutex_unlock(&bo->lock);
+
+	return ret;
+}
+
+static const struct vm_operations_struct vpu_vm_ops = {
+	.fault = vpu_vm_fault,
+	.open = drm_gem_vm_open,
+	.close = drm_gem_vm_close,
+};
+
+static const struct drm_gem_object_funcs vpu_gem_funcs = {
+	.free = vpu_bo_free,
+	.mmap = vpu_bo_mmap,
+	.vm_ops = &vpu_vm_ops,
+	.get_sg_table = vpu_bo_get_sg_table,
+};
+
+int
+vpu_bo_create_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
+{
+	struct vpu_file_priv *file_priv = file->driver_priv;
+	struct vpu_device *vdev = file_priv->vdev;
+	struct drm_vpu_bo_create *args = data;
+	struct vpu_bo *bo;
+	int ret;
+
+	if (args->flags & ~DRM_VPU_BO_FLAGS)
+		return -EINVAL;
+
+	if (args->size == 0)
+		return -EINVAL;
+
+	ret = vpu_mmu_user_context_init(file_priv);
+	if (ret)
+		return ret;
+
+	bo = vpu_bo_alloc(vdev, &file_priv->ctx, args->size, args->flags,
+			  &shmem_ops, NULL, 0);
+	if (IS_ERR(bo)) {
+		vpu_err(vdev, "Failed to create BO: %pe (ctx %u size %llu flags 0x%x)",
+			bo, file_priv->ctx.id, args->size, args->flags);
+		return PTR_ERR(bo);
+	}
+
+	ret = drm_gem_handle_create(file, &bo->base, &bo->handle);
+	if (!ret) {
+		args->vpu_addr = bo->vpu_addr;
+		args->handle = bo->handle;
+	}
+
+	drm_gem_object_put(&bo->base);
+
+	vpu_dbg(BO, "alloc shmem: ctx %u vpu_addr 0x%llx size %zu flags 0x%x\n",
+		file_priv->ctx.id, bo->vpu_addr, bo->base.size, bo->flags);
+
+	return ret;
+}
+
+int
+vpu_bo_userptr_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
+{
+	struct vpu_file_priv *file_priv = file->driver_priv;
+	struct vpu_device *vdev = file_priv->vdev;
+	struct drm_vpu_bo_userptr *args = data;
+	struct vpu_bo *bo;
+	int ret;
+
+	if (args->user_ptr == 0 || !PAGE_ALIGNED(args->user_ptr))
+		return -EINVAL;
+
+	if (args->user_size == 0 || !PAGE_ALIGNED(args->user_size))
+		return -EINVAL;
+
+	if (args->flags & ~DRM_VPU_BO_HIGH_MEM)
+		return -EINVAL;
+
+	if (!access_ok((const void __user *)args->user_ptr, args->user_size))
+		return -EFAULT;
+
+	ret = vpu_mmu_user_context_init(file_priv);
+	if (ret)
+		return ret;
+
+	bo = vpu_bo_alloc(vdev, &file_priv->ctx, args->user_size, args->flags,
+			  &userptr_ops, NULL, args->user_ptr);
+	if (IS_ERR(bo)) {
+		vpu_err(vdev, "Failed to create BO: %pe (ctx %u size %llu flags 0x%x)",
+			bo, file_priv->ctx.id, args->user_size, args->flags);
+		return PTR_ERR(bo);
+	}
+
+	if (!bo)
+		return -ENOMEM;
+
+	ret = drm_gem_handle_create(file, &bo->base, &bo->handle);
+	if (!ret) {
+		args->vpu_addr = bo->vpu_addr;
+		args->handle = bo->handle;
+	}
+
+	drm_gem_object_put(&bo->base);
+
+	vpu_dbg(BO, "alloc userptr: ctx %u vpu_addr 0x%llx size %zu flags 0x%x\n",
+		file_priv->ctx.id, bo->vpu_addr, bo->base.size, args->flags);
+
+	return ret;
+}
+
+struct vpu_bo *
+vpu_bo_alloc_internal(struct vpu_device *vdev, u64 vpu_addr, u64 size, bool cached)
+{
+	const struct vpu_addr_range *range;
+	struct vpu_addr_range fixed_range;
+	pgprot_t prot = PAGE_KERNEL;
+	struct vpu_bo *bo;
+	int flags = cached ? 0 : DRM_VPU_BO_UNCACHED;
+	int ret;
+
+	WARN_ON(!PAGE_ALIGNED(vpu_addr));
+	WARN_ON(!PAGE_ALIGNED(size));
+
+	if (vpu_addr) {
+		fixed_range.start = vpu_addr;
+		fixed_range.end = vpu_addr + size;
+		range = &fixed_range;
+	} else {
+		range = &vdev->hw->ranges.global_low;
+	}
+
+	bo = vpu_bo_alloc(vdev, &vdev->gctx, size, flags, &internal_ops, range, 0);
+	if (IS_ERR(bo)) {
+		vpu_err(vdev, "Failed to create BO: %pe (vpu_addr 0x%llx size %llu cached %d)",
+			bo, vpu_addr, size, cached);
+		return NULL;
+	}
+
+	ret = vpu_bo_pin(bo);
+	if (ret)
+		goto err_put;
+
+	if (!vpu_bo_is_cached(bo)) {
+		prot = pgprot_noncached(prot);
+		drm_clflush_pages(bo->pages, bo->base.size >> PAGE_SHIFT);
+	}
+
+	bo->kvaddr = vmap(bo->pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
+	if (!bo->kvaddr) {
+		vpu_err(vdev, "Failed to map BO into kernel virtual memory\n");
+		goto err_put;
+	}
+
+	vpu_dbg(BO, "alloc internal: ctx 0 vpu_addr 0x%llx size %zu cached %d\n",
+		bo->vpu_addr, bo->base.size, cached);
+
+	return bo;
+
+err_put:
+	drm_gem_object_put(&bo->base);
+	return NULL;
+}
+
+void vpu_bo_free_internal(struct vpu_bo *bo)
+{
+	drm_gem_object_put(&bo->base);
+}
+
+int vpu_bo_vremap_internal(struct vpu_device *vdev, struct vpu_bo *bo, bool cached)
+{
+	pgprot_t prot = cached ? PAGE_KERNEL : pgprot_noncached(PAGE_KERNEL);
+
+	vunmap(bo->kvaddr);
+
+	bo->kvaddr = vmap(bo->pages, bo->base.size >> PAGE_SHIFT, VM_MAP, prot);
+	if (!bo->kvaddr) {
+		vpu_err(vdev, "Failed to remap BO into kernel virtual memory\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+struct drm_gem_object *vpu_gem_prime_import(struct drm_device *dev, struct dma_buf *buf)
+{
+	struct vpu_device *vdev = to_vpu_dev(dev);
+	struct dma_buf_attachment *attach;
+	struct vpu_bo *bo;
+
+	attach = dma_buf_attach(buf, dev->dev);
+	if (IS_ERR(attach))
+		return ERR_CAST(attach);
+
+	get_dma_buf(buf);
+
+	bo = vpu_bo_alloc(vdev, NULL, buf->size, DRM_VPU_BO_MAPPABLE, &prime_ops, NULL, 0);
+	if (IS_ERR(bo)) {
+		vpu_err(vdev, "Failed to import BO: %pe (size %lu)", bo, buf->size);
+		goto err_detach;
+	}
+
+	bo->base.import_attach = attach;
+	bo->base.resv = buf->resv;
+
+	return &bo->base;
+
+err_detach:
+	dma_buf_detach(buf, attach);
+	dma_buf_put(buf);
+	return ERR_CAST(bo);
+}
+
+int vpu_bo_info_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
+{
+	struct vpu_file_priv *file_priv = file->driver_priv;
+	struct vpu_device *vdev = to_vpu_dev(dev);
+	struct drm_vpu_bo_info *args = data;
+	struct drm_gem_object *obj;
+	struct vpu_bo *bo;
+	int ret;
+
+	obj = drm_gem_object_lookup(file, args->handle);
+	if (!obj)
+		return -ENOENT;
+
+	ret = vpu_mmu_user_context_init(file_priv);
+	if (ret)
+		goto obj_put;
+
+	bo = to_vpu_bo(obj);
+
+	mutex_lock(&bo->lock);
+
+	if (!bo->ctx) {
+		ret = vpu_bo_alloc_vpu_addr(bo, &file_priv->ctx, NULL);
+		if (ret) {
+			vpu_err(vdev, "Failed to allocate vpu_addr: %d\n", ret);
+			goto unlock;
+		}
+	}
+
+	args->flags = bo->flags;
+	args->mmap_offset = drm_vma_node_offset_addr(&obj->vma_node);
+	args->vpu_addr = bo->vpu_addr;
+	args->size = obj->size;
+unlock:
+	mutex_unlock(&bo->lock);
+obj_put:
+	drm_gem_object_put(obj);
+	return ret;
+}
+
+static void vpu_bo_print_info(struct vpu_bo *bo, struct drm_printer *p)
+{
+	unsigned long dma_refcount = 0;
+
+	if (bo->base.dma_buf && bo->base.dma_buf->file)
+		dma_refcount = atomic_long_read(&bo->base.dma_buf->file->f_count);
+
+	drm_printf(p, "%5u %6d %16llx %10lu %10u %12lu %14s\n",
+		   bo->ctx->id, bo->handle, bo->vpu_addr, bo->base.size,
+		   kref_read(&bo->base.refcount), dma_refcount, bo->ops->name);
+}
+
+void vpu_bo_list(struct drm_device *dev, struct drm_printer *p)
+{
+	struct vpu_device *vdev = to_vpu_dev(dev);
+	struct vpu_file_priv *file_priv;
+	unsigned long ctx_id;
+	struct vpu_bo *bo;
+
+	drm_printf(p, "%5s %6s %16s %10s %10s %12s %14s\n",
+		   "ctx", "handle", "vpu_addr", "size", "refcount", "dma_refcount", "type");
+
+	list_for_each_entry(bo, &vdev->gctx.bo_list, ctx_node) {
+		mutex_lock(&vdev->gctx.lock);
+		vpu_bo_print_info(bo, p);
+		mutex_unlock(&vdev->gctx.lock);
+	}
+
+	xa_for_each(&vdev->context_xa, ctx_id, file_priv) {
+		if (!file_priv)
+			continue;
+
+		mutex_lock(&file_priv->ctx.lock);
+		list_for_each_entry(bo, &file_priv->ctx.bo_list, ctx_node)
+			vpu_bo_print_info(bo, p);
+		mutex_unlock(&file_priv->ctx.lock);
+	}
+}
+
+void vpu_bo_list_print(struct drm_device *dev)
+{
+	struct drm_printer p = drm_info_printer(dev->dev);
+
+	vpu_bo_list(dev, &p);
+}
diff --git a/drivers/gpu/drm/vpu/vpu_gem.h b/drivers/gpu/drm/vpu/vpu_gem.h
new file mode 100644
index 000000000000..5afe761bc0f3
--- /dev/null
+++ b/drivers/gpu/drm/vpu/vpu_gem.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright © 2020-2022 Intel Corporation
+ */
+#ifndef __VPU_GEM_H__
+#define __VPU_GEM_H__
+
+#include <drm/drm_gem.h>
+#include <drm/drm_mm.h>
+
+struct dma_buf;
+struct vpu_bo_ops;
+struct vpu_file_priv;
+
+struct vpu_bo {
+	struct drm_gem_object base;
+	const struct vpu_bo_ops *ops;
+
+	struct vpu_mmu_context *ctx;
+	struct list_head ctx_node;
+	struct drm_mm_node mm_node;
+
+	struct mutex lock; /* Protects: pages, sgt, mmu_mapped */
+	struct sg_table *sgt;
+	struct page **pages;
+	bool mmu_mapped;
+
+	void *kvaddr;
+	u64 vpu_addr;
+	u32 handle;
+	u32 flags;
+	uintptr_t user_ptr;
+};
+
+enum vpu_bo_type {
+	VPU_BO_TYPE_SHMEM = 1,
+	VPU_BO_TYPE_USERPTR,
+	VPU_BO_TYPE_INTERNAL,
+	VPU_BO_TYPE_PRIME,
+};
+
+struct vpu_bo_ops {
+	enum vpu_bo_type type;
+	const char *name;
+	int (*alloc_pages)(struct vpu_bo *bo);
+	void (*free_pages)(struct vpu_bo *bo);
+	int (*map_pages)(struct vpu_bo *bo);
+	void (*unmap_pages)(struct vpu_bo *bo);
+};
+
+int vpu_bo_pin(struct vpu_bo *bo);
+void vpu_bo_remove_all_bos_from_context(struct vpu_mmu_context *ctx);
+void vpu_bo_list(struct drm_device *dev, struct drm_printer *p);
+void vpu_bo_list_print(struct drm_device *dev);
+
+struct vpu_bo *
+vpu_bo_alloc_internal(struct vpu_device *vdev, u64 vpu_addr, u64 size, bool cached);
+void vpu_bo_free_internal(struct vpu_bo *bo);
+int vpu_bo_vremap_internal(struct vpu_device *vdev, struct vpu_bo *bo, bool cached);
+struct drm_gem_object *vpu_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf);
+void vpu_bo_unmap_sgt_and_remove_from_context(struct vpu_bo *bo);
+
+int vpu_bo_create_ioctl(struct drm_device *dev, void *data, struct drm_file *file);
+int vpu_bo_info_ioctl(struct drm_device *dev, void *data, struct drm_file *file);
+int vpu_bo_userptr_ioctl(struct drm_device *dev, void *data, struct drm_file *file);
+int vpu_bo_wait_ioctl(struct drm_device *dev, void *data, struct drm_file *file);
+
+static inline struct vpu_bo *to_vpu_bo(struct drm_gem_object *obj)
+{
+	return container_of(obj, struct vpu_bo, base);
+}
+
+static inline struct page *vpu_bo_get_page(struct vpu_bo *bo, u64 offset)
+{
+	if (offset > bo->base.size || !bo->pages)
+		return NULL;
+
+	return bo->pages[offset / PAGE_SIZE];
+}
+
+static inline bool vpu_bo_is_cached(struct vpu_bo *bo)
+{
+	return !(bo->flags & DRM_VPU_BO_UNCACHED);
+}
+
+static inline struct vpu_device *vpu_bo_to_vdev(struct vpu_bo *bo)
+{
+	return bo->base.dev->dev_private;
+}
+
+static inline void *vpu_to_cpu_addr(struct vpu_bo *bo, u32 vpu_addr)
+{
+	if (vpu_addr < bo->vpu_addr)
+		return NULL;
+
+	if (vpu_addr >= (bo->vpu_addr + bo->base.size))
+		return NULL;
+
+	return bo->kvaddr + (vpu_addr - bo->vpu_addr);
+}
+
+static inline u32 cpu_to_vpu_addr(struct vpu_bo *bo, void *cpu_addr)
+{
+	if (cpu_addr < bo->kvaddr)
+		return 0;
+
+	if (cpu_addr >= (bo->kvaddr + bo->base.size))
+		return 0;
+
+	return bo->vpu_addr + (cpu_addr - bo->kvaddr);
+}
+
+#endif /* __VPU_GEM_H__ */
diff --git a/include/uapi/drm/vpu_drm.h b/include/uapi/drm/vpu_drm.h
index 8e3b852d78a1..8793ed06bfa9 100644
--- a/include/uapi/drm/vpu_drm.h
+++ b/include/uapi/drm/vpu_drm.h
@@ -17,6 +17,9 @@ extern "C" {
 
 #define DRM_VPU_GET_PARAM		 0x00
 #define DRM_VPU_SET_PARAM		 0x01
+#define DRM_VPU_BO_CREATE		 0x02
+#define DRM_VPU_BO_INFO			 0x03
+#define DRM_VPU_BO_USERPTR		 0x04
 
 #define DRM_IOCTL_VPU_GET_PARAM                                                                    \
 	DRM_IOWR(DRM_COMMAND_BASE + DRM_VPU_GET_PARAM, struct drm_vpu_param)
@@ -24,6 +27,15 @@ extern "C" {
 #define DRM_IOCTL_VPU_SET_PARAM                                                                    \
 	DRM_IOW(DRM_COMMAND_BASE + DRM_VPU_SET_PARAM, struct drm_vpu_param)
 
+#define DRM_IOCTL_VPU_BO_CREATE                                                                    \
+	DRM_IOWR(DRM_COMMAND_BASE + DRM_VPU_BO_CREATE, struct drm_vpu_bo_create)
+
+#define DRM_IOCTL_VPU_BO_INFO                                                                      \
+	DRM_IOWR(DRM_COMMAND_BASE + DRM_VPU_BO_INFO, struct drm_vpu_bo_info)
+
+#define DRM_IOCTL_VPU_BO_USERPTR                                                                   \
+	DRM_IOWR(DRM_COMMAND_BASE + DRM_VPU_BO_USERPTR, struct drm_vpu_bo_userptr)
+
 /**
  * DOC: contexts
  *
@@ -92,6 +104,108 @@ struct drm_vpu_param {
 	__u64 value;
 };
 
+#define DRM_VPU_BO_HIGH_MEM 0x00000001
+#define DRM_VPU_BO_MAPPABLE 0x00000002
+#define DRM_VPU_BO_UNCACHED 0x00010000
+
+#define DRM_VPU_BO_FLAGS \
+	(DRM_VPU_BO_HIGH_MEM | \
+	 DRM_VPU_BO_MAPPABLE | \
+	 DRM_VPU_BO_UNCACHED)
+
+/**
+ * struct drm_vpu_bo_create - Create BO backed by SHMEM
+ *
+ * Create GEM buffer object allocated in SHMEM memory.
+ */
+struct drm_vpu_bo_create {
+	/** @size: The size in bytes of the allocated memory */
+	__u64 size;
+
+	/**
+	 * @flags:
+	 *
+	 * Supported flags:
+	 *
+	 * %DRM_VPU_BO_HIGH_MEM:
+	 *
+	 * Allocate VPU address from >4GB range.
+	 * Buffer object with vpu address >4GB can be always accessed by the
+	 * VPU DMA engine, but some HW generation may not be able to access
+	 * this memory from then firmware running on the VPU management processor.
+	 * Suitable for input, output and some scratch buffers.
+	 *
+	 * %DRM_VPU_BO_MAPPABLE:
+	 *
+	 * Buffer object can be mapped using mmap().
+	 *
+	 * %DRM_VPU_BO_UNCACHED:
+	 *
+	 * Allocated BO will not be cached on host side and it will be snooped
+	 * on the VPU side.
+	 */
+	__u32 flags;
+
+	/** @handle: Returned GEM object handle */
+	__u32 handle;
+
+	/** @vpu_addr: Returned VPU virtual address */
+	__u64 vpu_addr;
+};
+
+/**
+ * struct drm_vpu_bo_info - Query buffer object info
+ */
+struct drm_vpu_bo_info {
+	/** @handle: Handle of the queried BO */
+	__u32 handle;
+
+	/** @flags: Returned flags used to create the BO */
+	__u32 flags;
+
+	/** @vpu_addr: Returned VPU virtual address */
+	__u64 vpu_addr;
+
+	/**
+	 * @mmap_offset:
+	 *
+	 * Returned offset to be used in mmap(). 0 in case the BO is not mappable.
+	 */
+	__u64 mmap_offset;
+
+	/** @size: Returned GEM object size, aligned to PAGE_SIZE */
+	__u64 size;
+};
+
+/**
+ * struct drm_vpu_bo_userptr - Create BO from user memory
+ *
+ * Create GEM buffer object from user allocated memory. The provided @user_ptr
+ * has to be page aligned. BOs created using this ioctl are always cacheable.
+ */
+struct drm_vpu_bo_userptr {
+	/** @user_ptr: User allocated pointer aligned to PAGE_SIZE */
+	__u64 user_ptr;
+
+	/** @user_size: The size in bytes of the allocated memory */
+	__u64 user_size;
+
+	/**
+	 * @flags:
+	 *
+	 * Supported flags:
+	 *
+	 * %DRM_VPU_BO_HIGH_MEM: see &drm_vpu_bo_create->flags
+	 */
+	__u32 flags;
+
+	/** @handle: Returned GEM object handle */
+	__u32 handle;
+
+	/** @vpu_addr: Returned VPU virtual address */
+	__u64 vpu_addr;
+};
+
 #if defined(__cplusplus)
 }
 #endif
