diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d697c1c4a067..7e29d5340a17 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 +#include #include #include #include @@ -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. diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index b069433e7fc1..c134bbcfd2e9 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -32,6 +32,7 @@ struct drm_fb_helper; +#include #include #include #include @@ -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