Message ID | 20180414115318.14500-23-noralf@tronnes.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Sat, Apr 14, 2018 at 01:53:15PM +0200, Noralf Trønnes wrote: > This adds generic fbdev emulation for drivers that supports > dumb buffers which they can export. > > All the driver has to do is call drm_fbdev_generic_setup(). > > Signed-off-by: Noralf Trønnes <noralf@tronnes.org> > --- > drivers/gpu/drm/drm_fb_helper.c | 255 ++++++++++++++++++++++++++++++++++++++++ > include/drm/drm_fb_helper.h | 20 ++++ > 2 files changed, 275 insertions(+) > > diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c > index b1124c08b1ed..1954de5b13e0 100644 > --- a/drivers/gpu/drm/drm_fb_helper.c > +++ b/drivers/gpu/drm/drm_fb_helper.c > @@ -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> > @@ -1995,6 +1996,260 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev) > } > EXPORT_SYMBOL(drm_fb_helper_output_poll_changed); > > +static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) > +{ > + struct drm_fb_helper *fb_helper = info->par; > + > + return dma_buf_mmap(fb_helper->buffer->dma_buf, vma, 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_ops *fbops = NULL; > + > + DRM_DEBUG("\n"); > + > + if (fb_helper->fbdev->fbdefio) > + fbops = fb_helper->fbdev->fbops; > + > + drm_fb_helper_fini(fb_helper); > + drm_client_framebuffer_delete(fb_helper->buffer); > + drm_client_free(fb_helper->client); > + kfree(fb_helper); > + kfree(fbops); > +} > + > +static struct fb_ops drm_fbdev_fb_ops = { > + /* > + * No need to set owner, this module is already pinned by the driver. > + * A reference is taken on the driver module in drm_fb_helper_fb_open() > + * to prevent the driver going away with open fd's. > + */ > + DRM_FB_HELPER_DEFAULT_OPS, > + .fb_open = drm_fb_helper_fb_open, > + .fb_release = drm_fb_helper_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, > +}; > + > +/* Hack to test tinydrm before converting to vmalloc buffers */ > +static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info, > + struct vm_area_struct *vma) > +{ > + fb_deferred_io_mmap(info, vma); > + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); > + > + return 0; > +} > + > +static 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_display_mode sizes_mode = { > + .hdisplay = sizes->surface_width, > + .vdisplay = sizes->surface_height, > + }; > + 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_mode, 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); > + > + /* > + * Drivers that set the dirty callback: > + * - Doesn't use defio: > + * i915, virtio, rockchip > + * - defio with vmalloc buffer blitted on the real one: > + * vmwgfx > + * - defio is disabled because it doesn't work with shmem: > + * udl > + * - defio with special dirty callback for fbdev, uses vmalloc for fbdev: > + * qxl > + * - defio with cma buffer, will move to vmalloc buffers: > + * tinydrm > + * > + * TODO: > + * Maybe add vmalloc shadow buffer support. > + */ > + > + if (fb->funcs->dirty) { > + struct fb_ops *fbops; > + > + /* > + * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per > + * instance version is necessary. > + */ > + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); > + if (!fbops) { > + ret = -ENOMEM; > + goto err_fb_info_destroy; > + } > + > + *fbops = *fbi->fbops; > + fbi->fbops = fbops; > + > + fbi->fbdefio = &drm_fbdev_defio; > + > + /* Hack so I can test with tinydrm */ > + fbi->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr)); > + > + fb_deferred_io_init(fbi); > + > + /* Hack so I can test with tinydrm */ > + fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap; > + } > + > + return 0; > + > +err_fb_info_destroy: > + drm_fb_helper_fini(fb_helper); > +err_free_buffer: > + drm_client_framebuffer_delete(buffer); > + > + return ret; > +} I'd split this patch into 2: - First one just adds the generic_probe callback. We could then start rolling that one out to lots of drivers, which would give all this code lots of testing. - Second part is the generic client stuff below. Again then with follow-up patches to roll it out. This way we could achieve a slightly more gradual transition of drivers. And the first step should only be replacing the fb_probe callback. -Daniel > + > +static const struct drm_fb_helper_funcs drm_fb_helper_generic_funcs = { > + .fb_probe = drm_fb_helper_generic_probe, > +}; > + > +static int drm_fbdev_client_remove(struct drm_client_dev *client) > +{ > + struct drm_fb_helper *fb_helper = client->private; > + > + if (!fb_helper->fbdev) { > + kfree(fb_helper); > + return 0; > + } > + > + unregister_framebuffer(fb_helper->fbdev); > + > + /* > + * If userspace is closed the client is now freed by > + * drm_fbdev_fb_destroy(), otherwise it will be freed on the last close. > + * Return 1 to tell that freeing is taken care of. > + */ > + > + return 1; > +} > + > +static int drm_fbdev_client_lastclose(struct drm_client_dev *client) > +{ > + struct drm_fb_helper *fb_helper = client->private; > + > + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper); > + > + return 0; > +} > + > +static int drm_fbdev_client_hotplug(struct drm_client_dev *client) > +{ > + struct drm_fb_helper *fb_helper = client->private; > + > + if (fb_helper->fbdev) > + return 0; > + > + return drm_fb_helper_fbdev_setup(client->dev, fb_helper, > + &drm_fb_helper_generic_funcs, > + fb_helper->preferred_bpp, 0); > +} > + > +static const struct drm_client_funcs drm_fbdev_client_funcs = { > + .name = "fbdev", > + .remove = drm_fbdev_client_remove, > + .lastclose = drm_fbdev_client_lastclose, > + .hotplug = drm_fbdev_client_hotplug, > +}; > + > +/** > + * drm_fb_helper_generic_fbdev_setup() - Setup generic fbdev emulation > + * @dev: DRM device > + * @preferred_bpp: Preferred bits per pixel for the device. > + * @dev->mode_config.preferred_depth is used if this is zero. > + * > + * This function sets up generic fbdev emulation for drivers that supports > + * dumb buffers which can be exported. > + * > + * Restore, hotplug events and teardown are all taken care of. Drivers that does > + * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. > + * Simple drivers might use drm_mode_config_helper_suspend(). > + * > + * Returns: > + * Zero on success or negative error code on failure. > + */ > +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) > +{ > + struct drm_fb_helper *fb_helper; > + struct drm_client_dev *client; > + > + if (!drm_fbdev_emulation) > + return 0; > + > + fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); > + if (!fb_helper) > + return -ENOMEM; > + > + client = drm_client_new(dev, &drm_fbdev_client_funcs); > + if (IS_ERR(client)) { > + kfree(fb_helper); > + return PTR_ERR(client); > + } > + > + client->private = fb_helper; > + fb_helper->client = client; > + fb_helper->preferred_bpp = preferred_bpp; > + > + drm_fbdev_client_hotplug(client); > + > + return 0; > +} > +EXPORT_SYMBOL(drm_fbdev_generic_setup); > + > /* 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. > diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h > index 330983975d5e..711da1747836 100644 > --- a/include/drm/drm_fb_helper.h > +++ b/include/drm/drm_fb_helper.h > @@ -126,6 +126,20 @@ struct drm_fb_helper { > */ > struct drm_client_display *display; > > + /** > + * @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; > + > const struct drm_fb_helper_funcs *funcs; > struct fb_info *fbdev; > u32 pseudo_palette[17]; > @@ -219,6 +233,7 @@ struct drm_fb_helper { > .fb_ioctl = drm_fb_helper_ioctl > > #ifdef CONFIG_DRM_FBDEV_EMULATION > +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp); > void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, > const struct drm_fb_helper_funcs *funcs); > int drm_fb_helper_init(struct drm_device *dev, > @@ -297,6 +312,11 @@ 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); > #else > +static inline int > +drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) > +{ > +} > + > static inline void drm_fb_helper_prepare(struct drm_device *dev, > struct drm_fb_helper *helper, > const struct drm_fb_helper_funcs *funcs) > -- > 2.15.1 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index b1124c08b1ed..1954de5b13e0 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -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> @@ -1995,6 +1996,260 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev) } EXPORT_SYMBOL(drm_fb_helper_output_poll_changed); +static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *fb_helper = info->par; + + return dma_buf_mmap(fb_helper->buffer->dma_buf, vma, 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_ops *fbops = NULL; + + DRM_DEBUG("\n"); + + if (fb_helper->fbdev->fbdefio) + fbops = fb_helper->fbdev->fbops; + + drm_fb_helper_fini(fb_helper); + drm_client_framebuffer_delete(fb_helper->buffer); + drm_client_free(fb_helper->client); + kfree(fb_helper); + kfree(fbops); +} + +static struct fb_ops drm_fbdev_fb_ops = { + /* + * No need to set owner, this module is already pinned by the driver. + * A reference is taken on the driver module in drm_fb_helper_fb_open() + * to prevent the driver going away with open fd's. + */ + DRM_FB_HELPER_DEFAULT_OPS, + .fb_open = drm_fb_helper_fb_open, + .fb_release = drm_fb_helper_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, +}; + +/* Hack to test tinydrm before converting to vmalloc buffers */ +static int drm_fbdev_cma_deferred_io_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + fb_deferred_io_mmap(info, vma); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} + +static 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_display_mode sizes_mode = { + .hdisplay = sizes->surface_width, + .vdisplay = sizes->surface_height, + }; + 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_mode, 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); + + /* + * Drivers that set the dirty callback: + * - Doesn't use defio: + * i915, virtio, rockchip + * - defio with vmalloc buffer blitted on the real one: + * vmwgfx + * - defio is disabled because it doesn't work with shmem: + * udl + * - defio with special dirty callback for fbdev, uses vmalloc for fbdev: + * qxl + * - defio with cma buffer, will move to vmalloc buffers: + * tinydrm + * + * TODO: + * Maybe add vmalloc shadow buffer support. + */ + + if (fb->funcs->dirty) { + struct fb_ops *fbops; + + /* + * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per + * instance version is necessary. + */ + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbops) { + ret = -ENOMEM; + goto err_fb_info_destroy; + } + + *fbops = *fbi->fbops; + fbi->fbops = fbops; + + fbi->fbdefio = &drm_fbdev_defio; + + /* Hack so I can test with tinydrm */ + fbi->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr)); + + fb_deferred_io_init(fbi); + + /* Hack so I can test with tinydrm */ + fbi->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap; + } + + return 0; + +err_fb_info_destroy: + drm_fb_helper_fini(fb_helper); +err_free_buffer: + drm_client_framebuffer_delete(buffer); + + return ret; +} + +static const struct drm_fb_helper_funcs drm_fb_helper_generic_funcs = { + .fb_probe = drm_fb_helper_generic_probe, +}; + +static int drm_fbdev_client_remove(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + if (!fb_helper->fbdev) { + kfree(fb_helper); + return 0; + } + + unregister_framebuffer(fb_helper->fbdev); + + /* + * If userspace is closed the client is now freed by + * drm_fbdev_fb_destroy(), otherwise it will be freed on the last close. + * Return 1 to tell that freeing is taken care of. + */ + + return 1; +} + +static int drm_fbdev_client_lastclose(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper); + + return 0; +} + +static int drm_fbdev_client_hotplug(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = client->private; + + if (fb_helper->fbdev) + return 0; + + return drm_fb_helper_fbdev_setup(client->dev, fb_helper, + &drm_fb_helper_generic_funcs, + fb_helper->preferred_bpp, 0); +} + +static const struct drm_client_funcs drm_fbdev_client_funcs = { + .name = "fbdev", + .remove = drm_fbdev_client_remove, + .lastclose = drm_fbdev_client_lastclose, + .hotplug = drm_fbdev_client_hotplug, +}; + +/** + * drm_fb_helper_generic_fbdev_setup() - Setup generic fbdev emulation + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device. + * @dev->mode_config.preferred_depth is used if this is zero. + * + * This function sets up generic fbdev emulation for drivers that supports + * dumb buffers which can be exported. + * + * Restore, hotplug events and teardown are all taken care of. Drivers that does + * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. + * Simple drivers might use drm_mode_config_helper_suspend(). + * + * Returns: + * Zero on success or negative error code on failure. + */ +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ + struct drm_fb_helper *fb_helper; + struct drm_client_dev *client; + + if (!drm_fbdev_emulation) + return 0; + + fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); + if (!fb_helper) + return -ENOMEM; + + client = drm_client_new(dev, &drm_fbdev_client_funcs); + if (IS_ERR(client)) { + kfree(fb_helper); + return PTR_ERR(client); + } + + client->private = fb_helper; + fb_helper->client = client; + fb_helper->preferred_bpp = preferred_bpp; + + drm_fbdev_client_hotplug(client); + + return 0; +} +EXPORT_SYMBOL(drm_fbdev_generic_setup); + /* 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. diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 330983975d5e..711da1747836 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -126,6 +126,20 @@ struct drm_fb_helper { */ struct drm_client_display *display; + /** + * @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; + const struct drm_fb_helper_funcs *funcs; struct fb_info *fbdev; u32 pseudo_palette[17]; @@ -219,6 +233,7 @@ struct drm_fb_helper { .fb_ioctl = drm_fb_helper_ioctl #ifdef CONFIG_DRM_FBDEV_EMULATION +int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp); void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, const struct drm_fb_helper_funcs *funcs); int drm_fb_helper_init(struct drm_device *dev, @@ -297,6 +312,11 @@ 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); #else +static inline int +drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ +} + static inline void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, const struct drm_fb_helper_funcs *funcs)
This adds generic fbdev emulation for drivers that supports dumb buffers which they can export. All the driver has to do is call drm_fbdev_generic_setup(). Signed-off-by: Noralf Trønnes <noralf@tronnes.org> --- drivers/gpu/drm/drm_fb_helper.c | 255 ++++++++++++++++++++++++++++++++++++++++ include/drm/drm_fb_helper.h | 20 ++++ 2 files changed, 275 insertions(+)