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@xxxxxxxxxxx> --- 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; +} + +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@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel