Commit 222ec45f authored by Thomas Zimmermann's avatar Thomas Zimmermann

drm/fb_helper: Support framebuffers in I/O memory

At least sparc64 requires I/O-specific access to framebuffers. This
patch updates the fbdev console accordingly.

For drivers with direct access to the framebuffer memory, the callback
functions in struct fb_ops test for the type of memory and call the rsp
fb_sys_ of fb_cfb_ functions. Read and write operations are implemented
internally by DRM's fbdev helper.

For drivers that employ a shadow buffer, fbdev's blit function retrieves
the framebuffer address as struct dma_buf_map, and uses dma_buf_map
interfaces to access the buffer.

The bochs driver on sparc64 uses a workaround to flag the framebuffer as
I/O memory and avoid a HW exception. With the introduction of struct
dma_buf_map, this is not required any longer. The patch removes the rsp
code from both, bochs and fbdev.

v7:
	* use min_t(size_t,) (kernel test robot)
	* return the number of bytes read/written, if any (fbdev testcase)
v5:
	* implement fb_read/fb_write internally (Daniel, Sam)
v4:
	* move dma_buf_map changes into separate patch (Daniel)
	* TODO list: comment on fbdev updates (Daniel)
Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Reviewed-by: default avatarSam Ravnborg <sam@ravnborg.org>
Tested-by: default avatarSam Ravnborg <sam@ravnborg.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20201103093015.1063-11-tzimmermann@suse.de
parent b4e7090c
...@@ -201,13 +201,28 @@ Convert drivers to use drm_fbdev_generic_setup() ...@@ -201,13 +201,28 @@ Convert drivers to use drm_fbdev_generic_setup()
------------------------------------------------ ------------------------------------------------
Most drivers can use drm_fbdev_generic_setup(). Driver have to implement Most drivers can use drm_fbdev_generic_setup(). Driver have to implement
atomic modesetting and GEM vmap support. Current generic fbdev emulation atomic modesetting and GEM vmap support. Historically, generic fbdev emulation
expects the framebuffer in system memory (or system-like memory). expected the framebuffer in system memory or system-like memory. By employing
struct dma_buf_map, drivers with frambuffers in I/O memory can be supported
as well.
Contact: Maintainer of the driver you plan to convert Contact: Maintainer of the driver you plan to convert
Level: Intermediate Level: Intermediate
Reimplement functions in drm_fbdev_fb_ops without fbdev
-------------------------------------------------------
A number of callback functions in drm_fbdev_fb_ops could benefit from
being rewritten without dependencies on the fbdev module. Some of the
helpers could further benefit from using struct dma_buf_map instead of
raw pointers.
Contact: Thomas Zimmermann <tzimmermann@suse.de>, Daniel Vetter
Level: Advanced
drm_framebuffer_funcs and drm_mode_config_funcs.fb_create cleanup drm_framebuffer_funcs and drm_mode_config_funcs.fb_create cleanup
----------------------------------------------------------------- -----------------------------------------------------------------
......
...@@ -151,7 +151,6 @@ int bochs_kms_init(struct bochs_device *bochs) ...@@ -151,7 +151,6 @@ int bochs_kms_init(struct bochs_device *bochs)
bochs->dev->mode_config.preferred_depth = 24; bochs->dev->mode_config.preferred_depth = 24;
bochs->dev->mode_config.prefer_shadow = 0; bochs->dev->mode_config.prefer_shadow = 0;
bochs->dev->mode_config.prefer_shadow_fbdev = 1; bochs->dev->mode_config.prefer_shadow_fbdev = 1;
bochs->dev->mode_config.fbdev_use_iomem = true;
bochs->dev->mode_config.quirk_addfb_prefer_host_byte_order = true; bochs->dev->mode_config.quirk_addfb_prefer_host_byte_order = true;
bochs->dev->mode_config.funcs = &bochs_mode_funcs; bochs->dev->mode_config.funcs = &bochs_mode_funcs;
......
...@@ -372,24 +372,22 @@ static void drm_fb_helper_resume_worker(struct work_struct *work) ...@@ -372,24 +372,22 @@ static void drm_fb_helper_resume_worker(struct work_struct *work)
} }
static void drm_fb_helper_dirty_blit_real(struct drm_fb_helper *fb_helper, static void drm_fb_helper_dirty_blit_real(struct drm_fb_helper *fb_helper,
struct drm_clip_rect *clip) struct drm_clip_rect *clip,
struct dma_buf_map *dst)
{ {
struct drm_framebuffer *fb = fb_helper->fb; struct drm_framebuffer *fb = fb_helper->fb;
unsigned int cpp = fb->format->cpp[0]; unsigned int cpp = fb->format->cpp[0];
size_t offset = clip->y1 * fb->pitches[0] + clip->x1 * cpp; size_t offset = clip->y1 * fb->pitches[0] + clip->x1 * cpp;
void *src = fb_helper->fbdev->screen_buffer + offset; void *src = fb_helper->fbdev->screen_buffer + offset;
void *dst = fb_helper->buffer->map.vaddr + offset;
size_t len = (clip->x2 - clip->x1) * cpp; size_t len = (clip->x2 - clip->x1) * cpp;
unsigned int y; unsigned int y;
for (y = clip->y1; y < clip->y2; y++) { dma_buf_map_incr(dst, offset); /* go to first pixel within clip rect */
if (!fb_helper->dev->mode_config.fbdev_use_iomem)
memcpy(dst, src, len);
else
memcpy_toio((void __iomem *)dst, src, len);
for (y = clip->y1; y < clip->y2; y++) {
dma_buf_map_memcpy_to(dst, src, len);
dma_buf_map_incr(dst, fb->pitches[0]);
src += fb->pitches[0]; src += fb->pitches[0];
dst += fb->pitches[0];
} }
} }
...@@ -417,8 +415,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work) ...@@ -417,8 +415,9 @@ static void drm_fb_helper_dirty_work(struct work_struct *work)
ret = drm_client_buffer_vmap(helper->buffer, &map); ret = drm_client_buffer_vmap(helper->buffer, &map);
if (ret) if (ret)
return; return;
drm_fb_helper_dirty_blit_real(helper, &clip_copy); drm_fb_helper_dirty_blit_real(helper, &clip_copy, &map);
} }
if (helper->fb->funcs->dirty) if (helper->fb->funcs->dirty)
helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, helper->fb->funcs->dirty(helper->fb, NULL, 0, 0,
&clip_copy, 1); &clip_copy, 1);
...@@ -2027,6 +2026,199 @@ static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) ...@@ -2027,6 +2026,199 @@ static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
return -ENODEV; return -ENODEV;
} }
static bool drm_fbdev_use_iomem(struct fb_info *info)
{
struct drm_fb_helper *fb_helper = info->par;
struct drm_client_buffer *buffer = fb_helper->buffer;
return !drm_fbdev_use_shadow_fb(fb_helper) && buffer->map.is_iomem;
}
static ssize_t fb_read_screen_base(struct fb_info *info, char __user *buf, size_t count,
loff_t pos)
{
const char __iomem *src = info->screen_base + pos;
size_t alloc_size = min(count, PAGE_SIZE);
ssize_t ret = 0;
int err = 0;
char *tmp;
tmp = kmalloc(alloc_size, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
while (count) {
size_t c = min_t(size_t, count, alloc_size);
memcpy_fromio(tmp, src, c);
if (copy_to_user(buf, tmp, c)) {
err = -EFAULT;
break;
}
src += c;
buf += c;
ret += c;
count -= c;
}
kfree(tmp);
return ret ? ret : err;
}
static ssize_t fb_read_screen_buffer(struct fb_info *info, char __user *buf, size_t count,
loff_t pos)
{
const char *src = info->screen_buffer + pos;
if (copy_to_user(buf, src, count))
return -EFAULT;
return count;
}
static ssize_t drm_fbdev_fb_read(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos)
{
loff_t pos = *ppos;
size_t total_size;
ssize_t ret;
if (info->screen_size)
total_size = info->screen_size;
else
total_size = info->fix.smem_len;
if (pos >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (total_size - count < pos)
count = total_size - pos;
if (drm_fbdev_use_iomem(info))
ret = fb_read_screen_base(info, buf, count, pos);
else
ret = fb_read_screen_buffer(info, buf, count, pos);
if (ret > 0)
*ppos += ret;
return ret;
}
static ssize_t fb_write_screen_base(struct fb_info *info, const char __user *buf, size_t count,
loff_t pos)
{
char __iomem *dst = info->screen_base + pos;
size_t alloc_size = min(count, PAGE_SIZE);
ssize_t ret = 0;
int err = 0;
u8 *tmp;
tmp = kmalloc(alloc_size, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
while (count) {
size_t c = min_t(size_t, count, alloc_size);
if (copy_from_user(tmp, buf, c)) {
err = -EFAULT;
break;
}
memcpy_toio(dst, tmp, c);
dst += c;
buf += c;
ret += c;
count -= c;
}
kfree(tmp);
return ret ? ret : err;
}
static ssize_t fb_write_screen_buffer(struct fb_info *info, const char __user *buf, size_t count,
loff_t pos)
{
char *dst = info->screen_buffer + pos;
if (copy_from_user(dst, buf, count))
return -EFAULT;
return count;
}
static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
loff_t pos = *ppos;
size_t total_size;
ssize_t ret;
int err = 0;
if (info->screen_size)
total_size = info->screen_size;
else
total_size = info->fix.smem_len;
if (pos > total_size)
return -EFBIG;
if (count > total_size) {
err = -EFBIG;
count = total_size;
}
if (total_size - count < pos) {
if (!err)
err = -ENOSPC;
count = total_size - pos;
}
/*
* Copy to framebuffer even if we already logged an error. Emulates
* the behavior of the original fbdev implementation.
*/
if (drm_fbdev_use_iomem(info))
ret = fb_write_screen_base(info, buf, count, pos);
else
ret = fb_write_screen_buffer(info, buf, count, pos);
if (ret > 0)
*ppos += ret;
return ret ? ret : err;
}
static void drm_fbdev_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
if (drm_fbdev_use_iomem(info))
drm_fb_helper_cfb_fillrect(info, rect);
else
drm_fb_helper_sys_fillrect(info, rect);
}
static void drm_fbdev_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
if (drm_fbdev_use_iomem(info))
drm_fb_helper_cfb_copyarea(info, area);
else
drm_fb_helper_sys_copyarea(info, area);
}
static void drm_fbdev_fb_imageblit(struct fb_info *info,
const struct fb_image *image)
{
if (drm_fbdev_use_iomem(info))
drm_fb_helper_cfb_imageblit(info, image);
else
drm_fb_helper_sys_imageblit(info, image);
}
static const struct fb_ops drm_fbdev_fb_ops = { static const struct fb_ops drm_fbdev_fb_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS, DRM_FB_HELPER_DEFAULT_OPS,
...@@ -2034,11 +2226,11 @@ static const struct fb_ops drm_fbdev_fb_ops = { ...@@ -2034,11 +2226,11 @@ static const struct fb_ops drm_fbdev_fb_ops = {
.fb_release = drm_fbdev_fb_release, .fb_release = drm_fbdev_fb_release,
.fb_destroy = drm_fbdev_fb_destroy, .fb_destroy = drm_fbdev_fb_destroy,
.fb_mmap = drm_fbdev_fb_mmap, .fb_mmap = drm_fbdev_fb_mmap,
.fb_read = drm_fb_helper_sys_read, .fb_read = drm_fbdev_fb_read,
.fb_write = drm_fb_helper_sys_write, .fb_write = drm_fbdev_fb_write,
.fb_fillrect = drm_fb_helper_sys_fillrect, .fb_fillrect = drm_fbdev_fb_fillrect,
.fb_copyarea = drm_fb_helper_sys_copyarea, .fb_copyarea = drm_fbdev_fb_copyarea,
.fb_imageblit = drm_fb_helper_sys_imageblit, .fb_imageblit = drm_fbdev_fb_imageblit,
}; };
static struct fb_deferred_io drm_fbdev_defio = { static struct fb_deferred_io drm_fbdev_defio = {
......
...@@ -877,18 +877,6 @@ struct drm_mode_config { ...@@ -877,18 +877,6 @@ struct drm_mode_config {
*/ */
bool prefer_shadow_fbdev; bool prefer_shadow_fbdev;
/**
* @fbdev_use_iomem:
*
* Set to true if framebuffer reside in iomem.
* When set to true memcpy_toio() is used when copying the framebuffer in
* drm_fb_helper.drm_fb_helper_dirty_blit_real().
*
* FIXME: This should be replaced with a per-mapping is_iomem
* flag (like ttm does), and then used everywhere in fbdev code.
*/
bool fbdev_use_iomem;
/** /**
* @quirk_addfb_prefer_xbgr_30bpp: * @quirk_addfb_prefer_xbgr_30bpp:
* *
......
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