Commit d536540f authored by Noralf Trønnes's avatar Noralf Trønnes

drm/fb-helper: Add generic fbdev emulation .fb_probe function

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: default avatarNoralf Trønnes <noralf@tronnes.org>
Reviewed-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20180703160354.59955-3-noralf@tronnes.org
parent c76f0f7c
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/console.h> #include <linux/console.h>
#include <linux/dma-buf.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/sysrq.h> #include <linux/sysrq.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -738,6 +739,24 @@ static void drm_fb_helper_resume_worker(struct work_struct *work) ...@@ -738,6 +739,24 @@ static void drm_fb_helper_resume_worker(struct work_struct *work)
console_unlock(); 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) static void drm_fb_helper_dirty_work(struct work_struct *work)
{ {
struct drm_fb_helper *helper = container_of(work, struct drm_fb_helper, 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) ...@@ -753,8 +772,12 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
spin_unlock_irqrestore(&helper->dirty_lock, flags); spin_unlock_irqrestore(&helper->dirty_lock, flags);
/* call dirty callback only when it has been really touched */ /* 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); 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) ...@@ -2921,6 +2944,180 @@ void drm_fb_helper_output_poll_changed(struct drm_device *dev)
} }
EXPORT_SYMBOL(drm_fb_helper_output_poll_changed); 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) /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT)
* but the module doesn't depend on any fb console symbols. At least * 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. * attempt to load fbcon to avoid leaving the system without a usable console.
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
struct drm_fb_helper; struct drm_fb_helper;
#include <drm/drm_client.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_device.h> #include <drm/drm_device.h>
#include <linux/kgdb.h> #include <linux/kgdb.h>
...@@ -154,6 +155,20 @@ struct drm_fb_helper_connector { ...@@ -154,6 +155,20 @@ struct drm_fb_helper_connector {
* operations. * operations.
*/ */
struct drm_fb_helper { 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_framebuffer *fb;
struct drm_device *dev; struct drm_device *dev;
int crtc_count; int crtc_count;
...@@ -234,6 +249,12 @@ struct drm_fb_helper { ...@@ -234,6 +249,12 @@ struct drm_fb_helper {
int preferred_bpp; 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 * 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); ...@@ -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_lastclose(struct drm_device *dev);
void drm_fb_helper_output_poll_changed(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 #else
static inline void drm_fb_helper_prepare(struct drm_device *dev, static inline void drm_fb_helper_prepare(struct drm_device *dev,
struct drm_fb_helper *helper, struct drm_fb_helper *helper,
...@@ -564,6 +588,13 @@ static inline void drm_fb_helper_output_poll_changed(struct drm_device *dev) ...@@ -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 #endif
static inline int static inline int
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment