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 <tzimmerm...@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(-)

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__ */
-- 
2.35.1

Reply via email to