OSDN Git Service

drm/fb-helper: Add generic fbdev emulation .fb_probe function
authorNoralf Trønnes <noralf@tronnes.org>
Tue, 3 Jul 2018 16:03:48 +0000 (18:03 +0200)
committerNoralf Trønnes <noralf@tronnes.org>
Tue, 10 Jul 2018 12:52:37 +0000 (14:52 +0200)
This is the first step in getting generic fbdev emulation.
A drm_fb_helper_funcs.fb_probe function is added which uses the
DRM client API to get a framebuffer backed by a dumb buffer.

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20180703160354.59955-3-noralf@tronnes.org
drivers/gpu/drm/drm_fb_helper.c
include/drm/drm_fb_helper.h

index d697c1c..7e29d53 100644 (file)
@@ -30,6 +30,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/console.h>
+#include <linux/dma-buf.h>
 #include <linux/kernel.h>
 #include <linux/sysrq.h>
 #include <linux/slab.h>
@@ -738,6 +739,24 @@ static void drm_fb_helper_resume_worker(struct work_struct *work)
        console_unlock();
 }
 
+static void drm_fb_helper_dirty_blit_real(struct drm_fb_helper *fb_helper,
+                                         struct drm_clip_rect *clip)
+{
+       struct drm_framebuffer *fb = fb_helper->fb;
+       unsigned int cpp = drm_format_plane_cpp(fb->format->format, 0);
+       size_t offset = clip->y1 * fb->pitches[0] + clip->x1 * cpp;
+       void *src = fb_helper->fbdev->screen_buffer + offset;
+       void *dst = fb_helper->buffer->vaddr + offset;
+       size_t len = (clip->x2 - clip->x1) * cpp;
+       unsigned int y;
+
+       for (y = clip->y1; y < clip->y2; y++) {
+               memcpy(dst, src, len);
+               src += fb->pitches[0];
+               dst += fb->pitches[0];
+       }
+}
+
 static void drm_fb_helper_dirty_work(struct work_struct *work)
 {
        struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper,
@@ -753,8 +772,12 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
        spin_unlock_irqrestore(&helper->dirty_lock, flags);
 
        /* call dirty callback only when it has been really touched */
-       if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2)
+       if (clip_copy.x1 < clip_copy.x2 && clip_copy.y1 < clip_copy.y2) {
+               /* Generic fbdev uses a shadow buffer */
+               if (helper->buffer)
+                       drm_fb_helper_dirty_blit_real(helper, &clip_copy);
                helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, &clip_copy, 1);
+       }
 }
 
 /**
@@ -2921,6 +2944,180 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_fb_helper_output_poll_changed);
 
+/* @user: 1=userspace, 0=fbcon */
+static int drm_fbdev_fb_open(struct fb_info *info, int user)
+{
+       struct drm_fb_helper *fb_helper = info->par;
+
+       if (!try_module_get(fb_helper->dev->driver->fops->owner))
+               return -ENODEV;
+
+       return 0;
+}
+
+static int drm_fbdev_fb_release(struct fb_info *info, int user)
+{
+       struct drm_fb_helper *fb_helper = info->par;
+
+       module_put(fb_helper->dev->driver->fops->owner);
+
+       return 0;
+}
+
+/*
+ * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
+ * unregister_framebuffer() or fb_release().
+ */
+static void drm_fbdev_fb_destroy(struct fb_info *info)
+{
+       struct drm_fb_helper *fb_helper = info->par;
+       struct fb_info *fbi = fb_helper->fbdev;
+       struct fb_ops *fbops = NULL;
+       void *shadow = NULL;
+
+       if (fbi->fbdefio) {
+               fb_deferred_io_cleanup(fbi);
+               shadow = fbi->screen_buffer;
+               fbops = fbi->fbops;
+       }
+
+       drm_fb_helper_fini(fb_helper);
+
+       if (shadow) {
+               vfree(shadow);
+               kfree(fbops);
+       }
+
+       drm_client_framebuffer_delete(fb_helper->buffer);
+       /*
+        * FIXME:
+        * Remove conditional when all CMA drivers have been moved over to using
+        * drm_fbdev_generic_setup().
+        */
+       if (fb_helper->client.funcs) {
+               drm_client_release(&fb_helper->client);
+               kfree(fb_helper);
+       }
+}
+
+static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+       struct drm_fb_helper *fb_helper = info->par;
+
+       if (fb_helper->dev->driver->gem_prime_mmap)
+               return fb_helper->dev->driver->gem_prime_mmap(fb_helper->buffer->gem, vma);
+       else
+               return -ENODEV;
+}
+
+static struct fb_ops drm_fbdev_fb_ops = {
+       .owner          = THIS_MODULE,
+       DRM_FB_HELPER_DEFAULT_OPS,
+       .fb_open        = drm_fbdev_fb_open,
+       .fb_release     = drm_fbdev_fb_release,
+       .fb_destroy     = drm_fbdev_fb_destroy,
+       .fb_mmap        = drm_fbdev_fb_mmap,
+       .fb_read        = drm_fb_helper_sys_read,
+       .fb_write       = drm_fb_helper_sys_write,
+       .fb_fillrect    = drm_fb_helper_sys_fillrect,
+       .fb_copyarea    = drm_fb_helper_sys_copyarea,
+       .fb_imageblit   = drm_fb_helper_sys_imageblit,
+};
+
+static struct fb_deferred_io drm_fbdev_defio = {
+       .delay          = HZ / 20,
+       .deferred_io    = drm_fb_helper_deferred_io,
+};
+
+/**
+ * drm_fb_helper_generic_probe - Generic fbdev emulation probe helper
+ * @fb_helper: fbdev helper structure
+ * @sizes: describes fbdev size and scanout surface size
+ *
+ * This function uses the client API to crate a framebuffer backed by a dumb buffer.
+ *
+ * The _sys_ versions are used for &fb_ops.fb_read, fb_write, fb_fillrect,
+ * fb_copyarea, fb_imageblit.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
+                               struct drm_fb_helper_surface_size *sizes)
+{
+       struct drm_client_dev *client = &fb_helper->client;
+       struct drm_client_buffer *buffer;
+       struct drm_framebuffer *fb;
+       struct fb_info *fbi;
+       u32 format;
+       int ret;
+
+       DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
+                     sizes->surface_width, sizes->surface_height,
+                     sizes->surface_bpp);
+
+       format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth);
+       buffer = drm_client_framebuffer_create(client, sizes->surface_width,
+                                              sizes->surface_height, format);
+       if (IS_ERR(buffer))
+               return PTR_ERR(buffer);
+
+       fb_helper->buffer = buffer;
+       fb_helper->fb = buffer->fb;
+       fb = buffer->fb;
+
+       fbi = drm_fb_helper_alloc_fbi(fb_helper);
+       if (IS_ERR(fbi)) {
+               ret = PTR_ERR(fbi);
+               goto err_free_buffer;
+       }
+
+       fbi->par = fb_helper;
+       fbi->fbops = &drm_fbdev_fb_ops;
+       fbi->screen_size = fb->height * fb->pitches[0];
+       fbi->fix.smem_len = fbi->screen_size;
+       fbi->screen_buffer = buffer->vaddr;
+       strcpy(fbi->fix.id, "DRM emulated");
+
+       drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth);
+       drm_fb_helper_fill_var(fbi, fb_helper, sizes->fb_width, sizes->fb_height);
+
+       if (fb->funcs->dirty) {
+               struct fb_ops *fbops;
+               void *shadow;
+
+               /*
+                * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per
+                * instance version is necessary.
+                */
+               fbops = kzalloc(sizeof(*fbops), GFP_KERNEL);
+               shadow = vzalloc(fbi->screen_size);
+               if (!fbops || !shadow) {
+                       kfree(fbops);
+                       vfree(shadow);
+                       ret = -ENOMEM;
+                       goto err_fb_info_destroy;
+               }
+
+               *fbops = *fbi->fbops;
+               fbi->fbops = fbops;
+               fbi->screen_buffer = shadow;
+               fbi->fbdefio = &drm_fbdev_defio;
+
+               fb_deferred_io_init(fbi);
+       }
+
+       return 0;
+
+err_fb_info_destroy:
+       drm_fb_helper_fini(fb_helper);
+err_free_buffer:
+       drm_client_framebuffer_delete(buffer);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_fb_helper_generic_probe);
+
 /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT)
  * but the module doesn't depend on any fb console symbols.  At least
  * attempt to load fbcon to avoid leaving the system without a usable console.
index b069433..c134bbc 100644 (file)
@@ -32,6 +32,7 @@
 
 struct drm_fb_helper;
 
+#include <drm/drm_client.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_device.h>
 #include <linux/kgdb.h>
@@ -154,6 +155,20 @@ struct drm_fb_helper_connector {
  * operations.
  */
 struct drm_fb_helper {
+       /**
+        * @client:
+        *
+        * DRM client used by the generic fbdev emulation.
+        */
+       struct drm_client_dev client;
+
+       /**
+        * @buffer:
+        *
+        * Framebuffer used by the generic fbdev emulation.
+        */
+       struct drm_client_buffer *buffer;
+
        struct drm_framebuffer *fb;
        struct drm_device *dev;
        int crtc_count;
@@ -234,6 +249,12 @@ struct drm_fb_helper {
        int preferred_bpp;
 };
 
+static inline struct drm_fb_helper *
+drm_fb_helper_from_client(struct drm_client_dev *client)
+{
+       return container_of(client, struct drm_fb_helper, client);
+}
+
 /**
  * define DRM_FB_HELPER_DEFAULT_OPS - helper define for drm drivers
  *
@@ -330,6 +351,9 @@ void drm_fb_helper_fbdev_teardown(struct drm_device *dev);
 
 void drm_fb_helper_lastclose(struct drm_device *dev);
 void drm_fb_helper_output_poll_changed(struct drm_device *dev);
+
+int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
+                               struct drm_fb_helper_surface_size *sizes);
 #else
 static inline void drm_fb_helper_prepare(struct drm_device *dev,
                                        struct drm_fb_helper *helper,
@@ -564,6 +588,13 @@ static inline void drm_fb_helper_output_poll_changed(struct drm_device *dev)
 {
 }
 
+static inline int
+drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
+                           struct drm_fb_helper_surface_size *sizes)
+{
+       return 0;
+}
+
 #endif
 
 static inline int