diff mbox series

[8/9] drm/gem-shmem: Implement fbdev dumb buffer and mmap helpers

Message ID 20220303205839.28484-9-tzimmermann@suse.de
State New
Headers show
Series drm: Support GEM SHMEM fbdev without shadow FB | expand

Commit Message

Thomas Zimmermann March 3, 2022, 8:58 p.m. UTC
Implement struct drm_driver.dumb_create_fbdev for GEM SHMEM. The
created buffer object returned by this function implements deferred
I/O for its mmap operation.

Add this feature to a number of drivers that use GEM SHMEM helpers
as shadow planes over their regular video memory. The new macro
DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES sets the regular GEM
functions and dumb_create_fbdev in struct drm_driver. Fbdev emulation
on these drivers will now mmap the GEM SHMEM pages directly with
deferred I/O without an intermediate shadow buffer.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/drm_gem_shmem_helper.c  | 197 +++++++++++++++++++++---
 drivers/gpu/drm/gud/gud_drv.c           |   2 +-
 drivers/gpu/drm/hyperv/hyperv_drm_drv.c |   2 +-
 drivers/gpu/drm/mgag200/mgag200_drv.c   |   2 +-
 drivers/gpu/drm/tiny/cirrus.c           |   2 +-
 drivers/gpu/drm/tiny/gm12u320.c         |   2 +-
 drivers/gpu/drm/tiny/simpledrm.c        |   2 +-
 drivers/gpu/drm/udl/udl_drv.c           |   2 +-
 drivers/gpu/drm/vkms/vkms_drv.c         |   2 +-
 include/drm/drm_gem_shmem_helper.h      |  63 +++++++-
 10 files changed, 240 insertions(+), 36 deletions(-)

Comments

Javier Martinez Canillas March 8, 2022, 7:29 p.m. UTC | #1
On 3/3/22 21:58, Thomas Zimmermann wrote:
> Implement struct drm_driver.dumb_create_fbdev for GEM SHMEM. The
> created buffer object returned by this function implements deferred
> I/O for its mmap operation.
> 
> Add this feature to a number of drivers that use GEM SHMEM helpers
> as shadow planes over their regular video memory. The new macro
> DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES sets the regular GEM
> functions and dumb_create_fbdev in struct drm_driver. Fbdev emulation
> on these drivers will now mmap the GEM SHMEM pages directly with
> deferred I/O without an intermediate shadow buffer.
> 
> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
> ---

[snip]

> @@ -49,8 +50,20 @@ static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
>  	.vm_ops = &drm_gem_shmem_vm_ops,
>  };
>  
> +static const struct drm_gem_object_funcs drm_gem_shmem_funcs_fbdev = {
> +	.free = drm_gem_shmem_object_free,
> +	.print_info = drm_gem_shmem_object_print_info,
> +	.pin = drm_gem_shmem_object_pin,
> +	.unpin = drm_gem_shmem_object_unpin,
> +	.get_sg_table = drm_gem_shmem_object_get_sg_table,
> +	.vmap = drm_gem_shmem_object_vmap,
> +	.vunmap = drm_gem_shmem_object_vunmap,
> +	.mmap = drm_gem_shmem_object_mmap_fbdev,
> +	.vm_ops = &drm_gem_shmem_vm_ops_fbdev,

The drm_gem_shmem_funcs_fbdev is the same than drm_gem_shmem_funcs but
.mmap and .vm_ops callbacks. Maybe adding a macro to declare these two
struct drm_gem_object_funcs could make easier to maintain it long term ?

> +};
> +
>  static struct drm_gem_shmem_object *
> -__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
> +__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, bool fbdev)
>  {
>  	struct drm_gem_shmem_object *shmem;
>  	struct drm_gem_object *obj;
> @@ -70,8 +83,12 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
>  		obj = &shmem->base;
>  	}
>  
> -	if (!obj->funcs)
> -		obj->funcs = &drm_gem_shmem_funcs;
> +	if (!obj->funcs) {
> +		if (fbdev)

Same question than in patch #6, maybe

                if (defined(CONFIG_DRM_FBDEV_EMULATION) && fbdev) ?

[snip]

> + */
> +int drm_gem_shmem_dumb_create_fbdev(struct drm_file *file, struct drm_device *dev,
> +				    struct drm_mode_create_dumb *args)
> +{
> +#if defined(CONFIG_DRM_FBDEV_EMULATION)
> +	return __drm_gem_shmem_dumb_create(file, dev, true, args);
> +#else
> +	return -ENOSYS;
> +#endif
> +}

I believe the correct errno code here should be -ENOTSUPP.

[snip]

> +	/* We don't use vmf->pgoff since that has the fake offset */
> +	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;

I see this (vmf->address - vma->vm_start) calculation in many places of
this series. Maybe we could add a macro to calculate the offset instead ?

Reviewed-by: Javier Martinez Canillas <javierm@redhat.com>
Thomas Zimmermann March 9, 2022, 8:47 a.m. UTC | #2
Hi

Am 08.03.22 um 20:29 schrieb Javier Martinez Canillas:
> On 3/3/22 21:58, Thomas Zimmermann wrote:
>> Implement struct drm_driver.dumb_create_fbdev for GEM SHMEM. The
>> created buffer object returned by this function implements deferred
>> I/O for its mmap operation.
>>
>> Add this feature to a number of drivers that use GEM SHMEM helpers
>> as shadow planes over their regular video memory. The new macro
>> DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES sets the regular GEM
>> functions and dumb_create_fbdev in struct drm_driver. Fbdev emulation
>> on these drivers will now mmap the GEM SHMEM pages directly with
>> deferred I/O without an intermediate shadow buffer.
>>
>> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
>> ---
> 
> [snip]
> 
>> @@ -49,8 +50,20 @@ static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
>>   	.vm_ops = &drm_gem_shmem_vm_ops,
>>   };
>>   
>> +static const struct drm_gem_object_funcs drm_gem_shmem_funcs_fbdev = {
>> +	.free = drm_gem_shmem_object_free,
>> +	.print_info = drm_gem_shmem_object_print_info,
>> +	.pin = drm_gem_shmem_object_pin,
>> +	.unpin = drm_gem_shmem_object_unpin,
>> +	.get_sg_table = drm_gem_shmem_object_get_sg_table,
>> +	.vmap = drm_gem_shmem_object_vmap,
>> +	.vunmap = drm_gem_shmem_object_vunmap,
>> +	.mmap = drm_gem_shmem_object_mmap_fbdev,
>> +	.vm_ops = &drm_gem_shmem_vm_ops_fbdev,
> 
> The drm_gem_shmem_funcs_fbdev is the same than drm_gem_shmem_funcs but
> .mmap and .vm_ops callbacks. Maybe adding a macro to declare these two
> struct drm_gem_object_funcs could make easier to maintain it long term ?

I see you point, but I'm undecided about this. Such macros also add some 
amount of obfuscation. I'm not sure if it's worth it for an internal 
constant. And since the fbdev buffer is never exported, we could remove 
some of the callbacks. AFAICT we never call pin, unpin or get_sg_table.

Best regards
Thomas

> 
>> +};
>> +
>>   static struct drm_gem_shmem_object *
>> -__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
>> +__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, bool fbdev)
>>   {
>>   	struct drm_gem_shmem_object *shmem;
>>   	struct drm_gem_object *obj;
>> @@ -70,8 +83,12 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
>>   		obj = &shmem->base;
>>   	}
>>   
>> -	if (!obj->funcs)
>> -		obj->funcs = &drm_gem_shmem_funcs;
>> +	if (!obj->funcs) {
>> +		if (fbdev)
> 
> Same question than in patch #6, maybe
> 
>                  if (defined(CONFIG_DRM_FBDEV_EMULATION) && fbdev) ?
> 
> [snip]
> 
>> + */
>> +int drm_gem_shmem_dumb_create_fbdev(struct drm_file *file, struct drm_device *dev,
>> +				    struct drm_mode_create_dumb *args)
>> +{
>> +#if defined(CONFIG_DRM_FBDEV_EMULATION)
>> +	return __drm_gem_shmem_dumb_create(file, dev, true, args);
>> +#else
>> +	return -ENOSYS;
>> +#endif
>> +}
> 
> I believe the correct errno code here should be -ENOTSUPP.
> 
> [snip]
> 
>> +	/* We don't use vmf->pgoff since that has the fake offset */
>> +	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
> 
> I see this (vmf->address - vma->vm_start) calculation in many places of
> this series. Maybe we could add a macro to calculate the offset instead ?
> 
> Reviewed-by: Javier Martinez Canillas <javierm@redhat.com>
>
Javier Martinez Canillas March 9, 2022, 11:25 a.m. UTC | #3
On 3/9/22 09:47, Thomas Zimmermann wrote:

[snip]

>>>   
>>> +static const struct drm_gem_object_funcs drm_gem_shmem_funcs_fbdev = {
>>> +	.free = drm_gem_shmem_object_free,
>>> +	.print_info = drm_gem_shmem_object_print_info,
>>> +	.pin = drm_gem_shmem_object_pin,
>>> +	.unpin = drm_gem_shmem_object_unpin,
>>> +	.get_sg_table = drm_gem_shmem_object_get_sg_table,
>>> +	.vmap = drm_gem_shmem_object_vmap,
>>> +	.vunmap = drm_gem_shmem_object_vunmap,
>>> +	.mmap = drm_gem_shmem_object_mmap_fbdev,
>>> +	.vm_ops = &drm_gem_shmem_vm_ops_fbdev,
>>
>> The drm_gem_shmem_funcs_fbdev is the same than drm_gem_shmem_funcs but
>> .mmap and .vm_ops callbacks. Maybe adding a macro to declare these two
>> struct drm_gem_object_funcs could make easier to maintain it long term ?
> 
> I see you point, but I'm undecided about this. Such macros also add some 
> amount of obfuscation. I'm not sure if it's worth it for an internal 
> constant. And since the fbdev buffer is never exported, we could remove 
> some of the callbacks. AFAICT we never call pin, unpin or get_sg_table.
>

Yeah, that's true too. It was just a suggestion, I'm OK with patch as is.
 
> Best regards
> Thomas
> 
>>
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index 8ad0e02991ca..ab7cb7d896c3 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -19,6 +19,7 @@ 
 #include <drm/drm.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
 #include <drm/drm_gem_shmem_helper.h>
 #include <drm/drm_prime.h>
 #include <drm/drm_print.h>
@@ -49,8 +50,20 @@  static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
 	.vm_ops = &drm_gem_shmem_vm_ops,
 };
 
+static const struct drm_gem_object_funcs drm_gem_shmem_funcs_fbdev = {
+	.free = drm_gem_shmem_object_free,
+	.print_info = drm_gem_shmem_object_print_info,
+	.pin = drm_gem_shmem_object_pin,
+	.unpin = drm_gem_shmem_object_unpin,
+	.get_sg_table = drm_gem_shmem_object_get_sg_table,
+	.vmap = drm_gem_shmem_object_vmap,
+	.vunmap = drm_gem_shmem_object_vunmap,
+	.mmap = drm_gem_shmem_object_mmap_fbdev,
+	.vm_ops = &drm_gem_shmem_vm_ops_fbdev,
+};
+
 static struct drm_gem_shmem_object *
-__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
+__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private, bool fbdev)
 {
 	struct drm_gem_shmem_object *shmem;
 	struct drm_gem_object *obj;
@@ -70,8 +83,12 @@  __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
 		obj = &shmem->base;
 	}
 
-	if (!obj->funcs)
-		obj->funcs = &drm_gem_shmem_funcs;
+	if (!obj->funcs) {
+		if (fbdev)
+			obj->funcs = &drm_gem_shmem_funcs_fbdev;
+		else
+			obj->funcs = &drm_gem_shmem_funcs;
+	}
 
 	if (private) {
 		drm_gem_private_object_init(dev, obj, size);
@@ -124,7 +141,7 @@  __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
  */
 struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size)
 {
-	return __drm_gem_shmem_create(dev, size, false);
+	return __drm_gem_shmem_create(dev, size, false, false);
 }
 EXPORT_SYMBOL_GPL(drm_gem_shmem_create);
 
@@ -415,12 +432,12 @@  EXPORT_SYMBOL(drm_gem_shmem_vunmap);
 static struct drm_gem_shmem_object *
 drm_gem_shmem_create_with_handle(struct drm_file *file_priv,
 				 struct drm_device *dev, size_t size,
-				 uint32_t *handle)
+				 bool fbdev, uint32_t *handle)
 {
 	struct drm_gem_shmem_object *shmem;
 	int ret;
 
-	shmem = drm_gem_shmem_create(dev, size);
+	shmem = __drm_gem_shmem_create(dev, size, false, fbdev);
 	if (IS_ERR(shmem))
 		return shmem;
 
@@ -496,6 +513,29 @@  bool drm_gem_shmem_purge(struct drm_gem_shmem_object *shmem)
 }
 EXPORT_SYMBOL(drm_gem_shmem_purge);
 
+static int __drm_gem_shmem_dumb_create(struct drm_file *file, struct drm_device *dev,
+				       bool fbdev, struct drm_mode_create_dumb *args)
+{
+	u32 min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	struct drm_gem_shmem_object *shmem;
+
+	if (!args->pitch || !args->size) {
+		args->pitch = min_pitch;
+		args->size = PAGE_ALIGN(args->pitch * args->height);
+	} else {
+		/* ensure sane minimum values */
+		if (args->pitch < min_pitch)
+			args->pitch = min_pitch;
+		if (args->size < args->pitch * args->height)
+			args->size = PAGE_ALIGN(args->pitch * args->height);
+	}
+
+	shmem = drm_gem_shmem_create_with_handle(file, dev, args->size, fbdev,
+						 &args->handle);
+
+	return PTR_ERR_OR_ZERO(shmem);
+}
+
 /**
  * drm_gem_shmem_dumb_create - Create a dumb shmem buffer object
  * @file: DRM file structure to create the dumb buffer for
@@ -516,26 +556,38 @@  EXPORT_SYMBOL(drm_gem_shmem_purge);
 int drm_gem_shmem_dumb_create(struct drm_file *file, struct drm_device *dev,
 			      struct drm_mode_create_dumb *args)
 {
-	u32 min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
-	struct drm_gem_shmem_object *shmem;
-
-	if (!args->pitch || !args->size) {
-		args->pitch = min_pitch;
-		args->size = PAGE_ALIGN(args->pitch * args->height);
-	} else {
-		/* ensure sane minimum values */
-		if (args->pitch < min_pitch)
-			args->pitch = min_pitch;
-		if (args->size < args->pitch * args->height)
-			args->size = PAGE_ALIGN(args->pitch * args->height);
-	}
-
-	shmem = drm_gem_shmem_create_with_handle(file, dev, args->size, &args->handle);
-
-	return PTR_ERR_OR_ZERO(shmem);
+	return __drm_gem_shmem_dumb_create(file, dev, false, args);
 }
 EXPORT_SYMBOL_GPL(drm_gem_shmem_dumb_create);
 
+/**
+ * drm_gem_shmem_dumb_create_fbdev - Create a dumb shmem buffer object for fbdev
+ * @file: DRM file structure to create the dumb buffer for
+ * @dev: DRM device
+ * @args: IOCTL data
+ *
+ * This function computes the pitch of the dumb buffer and rounds it up to an
+ * integer number of bytes per pixel. Drivers for hardware that doesn't have
+ * any additional restrictions on the pitch can directly use this function as
+ * their &drm_driver.dumb_create_fbdev callback.
+ *
+ * For hardware with additional restrictions, drivers can adjust the fields
+ * set up by userspace before calling into this function.
+ *
+ * Returns:
+ * 0 on success or a negative error code on failure.
+ */
+int drm_gem_shmem_dumb_create_fbdev(struct drm_file *file, struct drm_device *dev,
+				    struct drm_mode_create_dumb *args)
+{
+#if defined(CONFIG_DRM_FBDEV_EMULATION)
+	return __drm_gem_shmem_dumb_create(file, dev, true, args);
+#else
+	return -ENOSYS;
+#endif
+}
+EXPORT_SYMBOL_GPL(drm_gem_shmem_dumb_create_fbdev);
+
 static vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf)
 {
 	struct vm_area_struct *vma = vmf->vma;
@@ -635,6 +687,103 @@  int drm_gem_shmem_mmap(struct drm_gem_shmem_object *shmem, struct vm_area_struct
 }
 EXPORT_SYMBOL_GPL(drm_gem_shmem_mmap);
 
+#if defined(CONFIG_DRM_FBDEV_EMULATION)
+static vm_fault_t drm_gem_shmem_fault_fbdev(struct vm_fault *vmf)
+{
+	struct vm_area_struct *vma = vmf->vma;
+	struct drm_gem_object *obj = vma->vm_private_data;
+	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
+	loff_t num_pages = obj->size >> PAGE_SHIFT;
+	struct drm_device *dev = obj->dev;
+	vm_fault_t ret;
+	struct page *page;
+	pgoff_t page_offset;
+
+	/* We don't use vmf->pgoff since that has the fake offset */
+	page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
+
+	mutex_lock(&shmem->pages_lock);
+
+	if (page_offset >= num_pages || WARN_ON_ONCE(!shmem->pages) || shmem->madv < 0) {
+		ret = VM_FAULT_SIGBUS;
+		goto err_mutex_unlock;
+	}
+
+	page = shmem->pages[page_offset];
+
+	get_page(page);
+
+	if (vmf->vma->vm_file)
+		page->mapping = vmf->vma->vm_file->f_mapping;
+	else
+		drm_err(dev, "no mapping available\n");
+
+	if (drm_WARN_ON_ONCE(dev, !page->mapping)) {
+		ret = VM_FAULT_SIGBUS;
+		goto err_put_page;
+	}
+
+	/* for page_mkclean(); include the fake offset in the page index */
+	page->index = vmf->pgoff;
+
+	vmf->page = page;
+
+	mutex_unlock(&shmem->pages_lock);
+
+	return 0;
+
+err_put_page:
+	put_page(page);
+err_mutex_unlock:
+	mutex_unlock(&shmem->pages_lock);
+	return ret;
+}
+
+static vm_fault_t drm_gem_shmem_vm_page_mkwrite_fbdev(struct vm_fault *vmf)
+{
+	struct drm_gem_object *obj = vmf->vma->vm_private_data;
+
+	return drm_fb_helper_vm_page_mkwrite(obj->dev->fb_helper, vmf);
+}
+
+const struct vm_operations_struct drm_gem_shmem_vm_ops_fbdev = {
+	.open = drm_gem_shmem_vm_open,
+	.close = drm_gem_shmem_vm_close,
+	.fault = drm_gem_shmem_fault_fbdev,
+	.page_mkwrite = drm_gem_shmem_vm_page_mkwrite_fbdev,
+};
+EXPORT_SYMBOL_GPL(drm_gem_shmem_vm_ops_fbdev);
+
+int drm_gem_shmem_mmap_fbdev(struct drm_gem_shmem_object *shmem, struct vm_area_struct *vma)
+{
+	struct drm_gem_object *obj = &shmem->base;
+	int ret;
+
+	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);
+	}
+
+	ret = drm_gem_shmem_get_pages(shmem);
+	if (ret) {
+		drm_gem_vm_close(vma);
+		return ret;
+	}
+
+	vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
+	vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
+	if (shmem->map_wc)
+		vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_gem_shmem_mmap_fbdev);
+#endif
+
 /**
  * drm_gem_shmem_print_info() - Print &drm_gem_shmem_object info for debugfs
  * @shmem: shmem GEM object
@@ -751,7 +900,7 @@  drm_gem_shmem_prime_import_sg_table(struct drm_device *dev,
 	size_t size = PAGE_ALIGN(attach->dmabuf->size);
 	struct drm_gem_shmem_object *shmem;
 
-	shmem = __drm_gem_shmem_create(dev, size, true);
+	shmem = __drm_gem_shmem_create(dev, size, true, false);
 	if (IS_ERR(shmem))
 		return ERR_CAST(shmem);
 
diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c
index 3f9d4b9a1e3d..1ac1ff1b2f81 100644
--- a/drivers/gpu/drm/gud/gud_drv.c
+++ b/drivers/gpu/drm/gud/gud_drv.c
@@ -382,7 +382,7 @@  DEFINE_DRM_GEM_FOPS(gud_fops);
 static const struct drm_driver gud_drm_driver = {
 	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
 	.fops			= &gud_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 	.gem_prime_import	= gud_gem_prime_import,
 	.debugfs_init		= gud_debugfs_init,
 
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
index 4a8941fa0815..2701664c127b 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_drv.c
@@ -38,7 +38,7 @@  static struct drm_driver hyperv_driver = {
 	.minor		 = DRIVER_MINOR,
 
 	.fops		 = &hv_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 };
 
 static int hyperv_pci_probe(struct pci_dev *pdev,
diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.c b/drivers/gpu/drm/mgag200/mgag200_drv.c
index 217844d71ab5..57dd5511e118 100644
--- a/drivers/gpu/drm/mgag200/mgag200_drv.c
+++ b/drivers/gpu/drm/mgag200/mgag200_drv.c
@@ -38,7 +38,7 @@  static const struct drm_driver mgag200_driver = {
 	.major = DRIVER_MAJOR,
 	.minor = DRIVER_MINOR,
 	.patchlevel = DRIVER_PATCHLEVEL,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 };
 
 /*
diff --git a/drivers/gpu/drm/tiny/cirrus.c b/drivers/gpu/drm/tiny/cirrus.c
index c8e791840862..17d8ca07af94 100644
--- a/drivers/gpu/drm/tiny/cirrus.c
+++ b/drivers/gpu/drm/tiny/cirrus.c
@@ -542,7 +542,7 @@  static const struct drm_driver cirrus_driver = {
 	.minor		 = DRIVER_MINOR,
 
 	.fops		 = &cirrus_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 };
 
 static int cirrus_pci_probe(struct pci_dev *pdev,
diff --git a/drivers/gpu/drm/tiny/gm12u320.c b/drivers/gpu/drm/tiny/gm12u320.c
index 648e585d40a8..97946f0637f3 100644
--- a/drivers/gpu/drm/tiny/gm12u320.c
+++ b/drivers/gpu/drm/tiny/gm12u320.c
@@ -620,7 +620,7 @@  static const struct drm_driver gm12u320_drm_driver = {
 	.minor		 = DRIVER_MINOR,
 
 	.fops		 = &gm12u320_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 	.gem_prime_import = gm12u320_gem_prime_import,
 };
 
diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c
index 768242a78e2b..562d09627330 100644
--- a/drivers/gpu/drm/tiny/simpledrm.c
+++ b/drivers/gpu/drm/tiny/simpledrm.c
@@ -871,7 +871,7 @@  simpledrm_device_create(struct drm_driver *drv, struct platform_device *pdev)
 DEFINE_DRM_GEM_FOPS(simpledrm_fops);
 
 static struct drm_driver simpledrm_driver = {
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 	.name			= DRIVER_NAME,
 	.desc			= DRIVER_DESC,
 	.date			= DRIVER_DATE,
diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c
index 5703277c6f52..87f7648e73a5 100644
--- a/drivers/gpu/drm/udl/udl_drv.c
+++ b/drivers/gpu/drm/udl/udl_drv.c
@@ -55,7 +55,7 @@  static const struct drm_driver driver = {
 
 	/* GEM hooks */
 	.fops = &udl_driver_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 	.gem_prime_import = udl_driver_gem_prime_import,
 
 	.name = DRIVER_NAME,
diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c
index 0ffe5f0e33f7..645b92149b8b 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.c
+++ b/drivers/gpu/drm/vkms/vkms_drv.c
@@ -116,7 +116,7 @@  static const struct drm_driver vkms_driver = {
 	.driver_features	= DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
 	.release		= vkms_release,
 	.fops			= &vkms_driver_fops,
-	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES,
 
 	.debugfs_init           = vkms_config_debugfs_init,
 
diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h
index d0a57853c188..16b0f4b60d33 100644
--- a/include/drm/drm_gem_shmem_helper.h
+++ b/include/drm/drm_gem_shmem_helper.h
@@ -139,6 +139,11 @@  void drm_gem_shmem_print_info(const struct drm_gem_shmem_object *shmem,
 
 extern const struct vm_operations_struct drm_gem_shmem_vm_ops;
 
+#if defined(CONFIG_DRM_FBDEV_EMULATION)
+extern const struct vm_operations_struct drm_gem_shmem_vm_ops_fbdev;
+int drm_gem_shmem_mmap_fbdev(struct drm_gem_shmem_object *shmem, struct vm_area_struct *vma);
+#endif
+
 /*
  * GEM object functions
  */
@@ -272,6 +277,27 @@  static inline int drm_gem_shmem_object_mmap(struct drm_gem_object *obj, struct v
 	return drm_gem_shmem_mmap(shmem, vma);
 }
 
+#if defined(CONFIG_DRM_FBDEV_EMULATION)
+/**
+ * drm_gem_shmem_object_mmap_fbdev - GEM object function for drm_gem_shmem_mmap_fbdev()
+ * @obj: GEM object
+ * @vma: VMA for the area to be mapped
+ *
+ * This function wraps drm_gem_shmem_mmap(). Drivers that employ the shmem helpers should
+ * use it as their &drm_gem_object_funcs.mmap handler.
+ *
+ * Returns:
+ * 0 on success or a negative error code on failure.
+ */
+static inline int drm_gem_shmem_object_mmap_fbdev(struct drm_gem_object *obj,
+						  struct vm_area_struct *vma)
+{
+	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
+
+	return drm_gem_shmem_mmap_fbdev(shmem, vma);
+}
+#endif
+
 /*
  * Driver ops
  */
@@ -282,18 +308,47 @@  drm_gem_shmem_prime_import_sg_table(struct drm_device *dev,
 				    struct sg_table *sgt);
 int drm_gem_shmem_dumb_create(struct drm_file *file, struct drm_device *dev,
 			      struct drm_mode_create_dumb *args);
+int drm_gem_shmem_dumb_create_fbdev(struct drm_file *file, struct drm_device *dev,
+				    struct drm_mode_create_dumb *args);
 
 /**
- * DRM_GEM_SHMEM_DRIVER_OPS - Default shmem GEM operations
+ * DRM_GEM_SHMEM_DRIVER_OPS_WITH_DUMB_CREATE - Default shmem GEM operations
+ * @dumb_create_func: callback function for .dumb_create
+ * @dumb_create_fbdev_func: callback function for .dumb_create_fbdev
  *
  * This macro provides a shortcut for setting the shmem GEM operations in
- * the &drm_driver structure.
+ * the &drm_driver structure. The callbacks for creating dumb buffers are
+ * given as parameters. Use DRM_GEM_SHMEM_DRIVER_OPS or
+ * DRM_GEM_SHMEM_OPS_WITH_SHADOW_PLANES if possible.
  */
-#define DRM_GEM_SHMEM_DRIVER_OPS \
+#define DRM_GEM_SHMEM_DRIVER_OPS_WITH_DUMB_CREATE(dumb_create_func, dumb_create_fbdev_func) \
 	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd, \
 	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle, \
 	.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table, \
 	.gem_prime_mmap		= drm_gem_prime_mmap, \
-	.dumb_create		= drm_gem_shmem_dumb_create
+	.dumb_create		= dumb_create_func, \
+	.dumb_create_fbdev	= dumb_create_fbdev_func
+
+/**
+ * DRM_GEM_SHMEM_DRIVER_OPS - Default shmem GEM operations
+ *
+ * This macro provides a shortcut for setting the shmem GEM operations in
+ * the &drm_driver structure.
+ */
+#define DRM_GEM_SHMEM_DRIVER_OPS \
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_DUMB_CREATE(drm_gem_shmem_dumb_create, NULL)
+
+/**
+ * DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES - Default shmem GEM operations
+ *                                               for drivers that use shadow
+ *                                               planes
+ *
+ * This macro provides a shortcut for setting the shmem GEM operations in
+ * the &drm_driver structure. Drivers that employ shmem GEM for shadow
+ * buffering should use this macro.
+ */
+#define DRM_GEM_SHMEM_DRIVER_OPS_WITH_SHADOW_PLANES \
+	DRM_GEM_SHMEM_DRIVER_OPS_WITH_DUMB_CREATE(drm_gem_shmem_dumb_create, \
+						  drm_gem_shmem_dumb_create_fbdev)
 
 #endif /* __DRM_GEM_SHMEM_HELPER_H__ */