This adds generic fbdev emulation for drivers that support the dumb buffer API. No fbdev code is necessary in the driver. Differences from drm_fb_helper: - The backing buffer is created when the first fd is opened. - Supports changing the mode from userspace. - Doesn't restore on lastclose if there is no fd/fbcon open. - Supports changing the buffer size (yres_virtual) from userspace before the fd is opened (double/trippel/... buffering). - Panning is only supported as page flipping, so no partial offset. - Supports real page flipping with FBIO_WAITFORVSYNC waiting on the actual flip. - Supports framebuffer flushing for fbcon on buffers that doesn't support fbdev deferred I/O (shmem). mmap doesn't work but fbcon does. TODO: - suspend/resume - sysrq - Look more into plane format selection/support. - Need a way for the driver to say that it wants generic fbdev emulation. The client .new hook is run in drm_dev_register() which is before drivers set up fbdev themselves. So the client can't look at drm_device->fb_helper to find out. - Do we need to support FB_VISUAL_PSEUDOCOLOR? TROUBLE: - fbcon can't handle fb_open returning an error, it just heads on. This results in a NULL deref in fbcon_init(). fbcon/vt is awful when it comes to error handling. It doesn't look to be easily fixed, so I guess a buffer has to be pre-allocated to ensure health and safety. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/gpu/drm/client/Kconfig | 16 + drivers/gpu/drm/client/Makefile | 2 + drivers/gpu/drm/client/drm_fbdev.c | 997 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1015 insertions(+) create mode 100644 drivers/gpu/drm/client/drm_fbdev.c diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig index 4bb8e4655ff7..73902ab44c75 100644 --- a/drivers/gpu/drm/client/Kconfig +++ b/drivers/gpu/drm/client/Kconfig @@ -1,4 +1,20 @@ menu "DRM Clients" depends on DRM +config DRM_CLIENT_FBDEV + tristate "Generic fbdev emulation" + depends on DRM + select FB + select FRAMEBUFFER_CONSOLE if !EXPERT + select FRAMEBUFFER_CONSOLE_DETECT_PRIMARY if FRAMEBUFFER_CONSOLE + select FB_SYS_FOPS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_DEFERRED_IO + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + Generic fbdev emulation + endmenu diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile index f66554cd5c45..3ff694429dec 100644 --- a/drivers/gpu/drm/client/Makefile +++ b/drivers/gpu/drm/client/Makefile @@ -1 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_CLIENT_FBDEV) += drm_fbdev.o diff --git a/drivers/gpu/drm/client/drm_fbdev.c b/drivers/gpu/drm/client/drm_fbdev.c new file mode 100644 index 000000000000..e28416d72de1 --- /dev/null +++ b/drivers/gpu/drm/client/drm_fbdev.c @@ -0,0 +1,997 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2018 Noralf Trønnes + +#include <linux/console.h> +#include <linux/dma-buf.h> +#include <linux/fb.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <video/videomode.h> + +#include <drm/drm_client.h> +#include <drm/drm_drv.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_modes.h> +#include <drm/drm_print.h> + +struct drm_fbdev { + struct mutex lock; + + struct drm_client_dev *client; + struct drm_client_display *display; + + unsigned int open_count; + struct drm_client_buffer *buffer; + bool page_flip_sent; + u32 curr_fb; + + struct fb_info *info; + u32 pseudo_palette[17]; + + bool flush; + bool defio_no_flushing; + struct drm_clip_rect dirty_clip; + spinlock_t dirty_lock; + struct work_struct dirty_work; +}; + +static int drm_fbdev_mode_to_fb_mode(struct drm_device *dev, + struct drm_mode_modeinfo *mode, + struct fb_videomode *fb_mode) +{ + struct drm_display_mode display_mode = { }; + struct videomode videomode = { }; + int ret; + + ret = drm_mode_convert_umode(dev, &display_mode, mode); + if (ret) + return ret; + + memset(fb_mode, 0, sizeof(*fb_mode)); + drm_display_mode_to_videomode(&display_mode, &videomode); + fb_videomode_from_videomode(&videomode, fb_mode); + + return 0; +} + +static void drm_fbdev_destroy_modelist(struct fb_info *info) +{ + struct fb_modelist *modelist, *tmp; + + list_for_each_entry_safe(modelist, tmp, &info->modelist, list) { + kfree(modelist->mode.name); + list_del(&modelist->list); + kfree(modelist); + } +} + +static void drm_fbdev_use_first_mode(struct fb_info *info) +{ + struct fb_modelist *modelist; + + modelist = list_first_entry(&info->modelist, struct fb_modelist, list); + fb_videomode_to_var(&info->var, &modelist->mode); + info->mode = &modelist->mode; +} + +static struct drm_mode_modeinfo *drm_fbdev_get_drm_mode(struct drm_fbdev *fbdev) +{ + struct drm_mode_modeinfo *mode_pos, *mode = NULL; + struct fb_info *info = fbdev->info; + struct fb_videomode tmp; + + mutex_lock(&fbdev->display->modes_lock); + drm_client_display_for_each_mode(fbdev->display, mode_pos) { + if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode_pos, &tmp)) + continue; + if (fb_mode_is_equal(info->mode, &tmp)) { + mode = mode_pos; + break; + } + } + mutex_unlock(&fbdev->display->modes_lock); + + return mode; +} + +/* Return number of modes or negative error */ +static int drm_fbdev_sync_modes(struct drm_fbdev *fbdev, bool force) +{ + struct fb_info *info = fbdev->info; + struct drm_mode_modeinfo *mode; + struct fb_videomode fb_mode; + bool changed; + + struct fb_modelist *fbdev_modelist; + int num_modes; + + num_modes = drm_client_display_update_modes(fbdev->display, &changed); + if (num_modes <= 0) + return num_modes; + + if (!info) + return num_modes; + + if (!force && !changed) + return num_modes; + + drm_fbdev_destroy_modelist(info); + + mutex_lock(&fbdev->display->modes_lock); + drm_client_display_for_each_mode(fbdev->display, mode) { + if (drm_fbdev_mode_to_fb_mode(fbdev->client->dev, mode, &fb_mode)) { + num_modes--; + continue; + } + + fbdev_modelist = kzalloc(sizeof(*fbdev_modelist), GFP_KERNEL); + if (!fbdev_modelist) { + drm_fbdev_destroy_modelist(info); + mutex_unlock(&fbdev->display->modes_lock); + return -ENOMEM; + } + + fbdev_modelist->mode = fb_mode; + fbdev_modelist->mode.name = kstrndup(mode->name, + DRM_DISPLAY_MODE_LEN, + GFP_KERNEL); + + if (mode->type & DRM_MODE_TYPE_PREFERRED) + fbdev_modelist->mode.flag |= FB_MODE_IS_FIRST; + + list_add_tail(&fbdev_modelist->list, &info->modelist); + } + mutex_unlock(&fbdev->display->modes_lock); + + if (!fbdev->open_count) + drm_fbdev_use_first_mode(info); + + return num_modes; +} + +static void drm_fbdev_format_fill_var(u32 format, struct fb_var_screeninfo *var) +{ + switch (format) { + case DRM_FORMAT_XRGB1555: + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case DRM_FORMAT_ARGB1555: + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + break; + case DRM_FORMAT_RGB565: + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case DRM_FORMAT_ARGB8888: + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + default: + WARN_ON_ONCE(1); + return; + } + + var->colorspace = 0; + var->grayscale = 0; + var->nonstd = 0; +} + +int drm_fbdev_var_to_format(struct fb_var_screeninfo *var, u32 *format) +{ + switch (var->bits_per_pixel) { + case 15: + *format = DRM_FORMAT_ARGB1555; + break; + case 16: + if (var->green.length != 5) + *format = DRM_FORMAT_RGB565; + else if (var->transp.length > 0) + *format = DRM_FORMAT_ARGB1555; + else + *format = DRM_FORMAT_XRGB1555; + break; + case 24: + *format = DRM_FORMAT_RGB888; + break; + case 32: + if (var->transp.length > 0) + *format = DRM_FORMAT_ARGB8888; + else + *format = DRM_FORMAT_XRGB8888; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void drm_fbdev_dirty_work(struct work_struct *work) +{ + struct drm_fbdev *fbdev = container_of(work, struct drm_fbdev, + dirty_work); + struct drm_clip_rect *clip = &fbdev->dirty_clip; + struct drm_clip_rect clip_copy; + unsigned long flags; + + spin_lock_irqsave(&fbdev->dirty_lock, flags); + clip_copy = *clip; + clip->x1 = clip->y1 = ~0; + clip->x2 = clip->y2 = 0; + spin_unlock_irqrestore(&fbdev->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) + drm_client_display_flush(fbdev->display, fbdev->curr_fb, + &clip_copy, 1); +} + +static void drm_fbdev_dirty(struct fb_info *info, u32 x, u32 y, + u32 width, u32 height) +{ + struct drm_fbdev *fbdev = info->par; + struct drm_clip_rect *clip = &fbdev->dirty_clip; + unsigned long flags; + + if (!fbdev->flush) + return; + + spin_lock_irqsave(&fbdev->dirty_lock, flags); + clip->x1 = min_t(u32, clip->x1, x); + clip->y1 = min_t(u32, clip->y1, y); + clip->x2 = max_t(u32, clip->x2, x + width); + clip->y2 = max_t(u32, clip->y2, y + height); + spin_unlock_irqrestore(&fbdev->dirty_lock, flags); + + schedule_work(&fbdev->dirty_work); +} + +static void drm_fbdev_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct drm_fbdev *fbdev = info->par; + unsigned long start, end, min, max; + struct page *page; + u32 y1, y2; + + /* Is userspace doing explicit pageflip flushing? */ + if (fbdev->defio_no_flushing) + return; + + min = ULONG_MAX; + max = 0; + list_for_each_entry(page, pagelist, lru) { + start = page->index << PAGE_SHIFT; + end = start + PAGE_SIZE; + min = min(min, start); + max = max(max, end); + } + + if (min < max) { + y1 = min / info->fix.line_length; + y2 = DIV_ROUND_UP(max, info->fix.line_length); + y2 = min(y2, info->var.yres); + drm_fbdev_dirty(info, 0, y1, info->var.xres, y2 - y1); + } +} + +static struct fb_deferred_io drm_fbdev_fbdefio = { + .delay = HZ / 20, + .deferred_io = drm_fbdev_deferred_io, +}; + +static int +drm_fbdev_fb_mmap_notsupp(struct fb_info *info, struct vm_area_struct *vma) +{ + return -ENOTSUPP; +} + +static void drm_fbdev_delete_buffer(struct drm_fbdev *fbdev) +{ + struct fb_info *info = fbdev->info; + + if (info->fbdefio) { + /* Stop worker and clear page->mapping */ + fb_deferred_io_cleanup(info); + info->fbdefio = NULL; + } + if (fbdev->flush) { + fbdev->flush = false; + cancel_work_sync(&fbdev->dirty_work); + } + + drm_client_buffer_rmfb(fbdev->buffer); + drm_client_buffer_delete(fbdev->buffer); + + fbdev->buffer = NULL; + fbdev->curr_fb = 0; + fbdev->page_flip_sent = false; + info->screen_buffer = NULL; + info->screen_size = 0; + info->fix.smem_len = 0; + info->fix.line_length = 0; +} + +/* Temporary hack to make tinydrm work 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_fbdev_create_buffer(struct drm_fbdev *fbdev) +{ + struct drm_client_dev *client = fbdev->client; + struct fb_info *info = fbdev->info; + struct drm_client_buffer *buffer; + struct drm_mode_modeinfo *mode; + u32 format; + int ret; + + ret = drm_fbdev_var_to_format(&info->var, &format); + if (ret) + return ret; + + buffer = drm_client_buffer_create(client, info->var.xres_virtual, + info->var.yres_virtual, format); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + mode = drm_fbdev_get_drm_mode(fbdev); + if (!mode) + return -EINVAL; + + ret = drm_client_buffer_addfb(buffer, mode); + if (ret) + goto err_free_buffer; + + fbdev->curr_fb = buffer->fb_ids[0]; + + if (drm_mode_can_dirtyfb(client->dev, fbdev->curr_fb, client->file)) { + fbdev->flush = true; +/* if (is_vmalloc_addr(buffer->vaddr)) { */ +/* Temporary hack for testing on tinydrm before it has moved to vmalloc */ + if (1) { + fbdev->dirty_clip.x1 = fbdev->dirty_clip.y1 = ~0; + fbdev->dirty_clip.x2 = fbdev->dirty_clip.y2 = 0; + info->fbdefio = &drm_fbdev_fbdefio; + + /* tinydrm hack */ + info->fix.smem_start = page_to_phys(virt_to_page(buffer->vaddr)); + + fb_deferred_io_init(info); + /* tinydrm hack */ + info->fbops->fb_mmap = drm_fbdev_cma_deferred_io_mmap; + } else { + info->fbops->fb_mmap = drm_fbdev_fb_mmap_notsupp; + } + } + + fbdev->buffer = buffer; + info->screen_buffer = buffer->vaddr; + info->screen_size = buffer->size; + info->fix.smem_len = buffer->size; + info->fix.line_length = buffer->pitch; + + return 0; + +err_free_buffer: + drm_client_buffer_delete(buffer); + + return ret; +} + +static int drm_fbdev_fb_open(struct fb_info *info, int user) +{ + struct drm_fbdev *fbdev = info->par; + int ret = 0; + + DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n"); + + mutex_lock(&fbdev->lock); + + if (!fbdev->display) { + ret = -ENODEV; + goto out_unlock; + } + + if (!fbdev->open_count) { + /* Pipeline is disabled, make sure it's forced on */ + info->var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; + ret = drm_fbdev_create_buffer(fbdev); + if (ret) + goto out_unlock; + } + + fbdev->open_count++; + +out_unlock: + mutex_unlock(&fbdev->lock); + + if (ret) + DRM_DEV_ERROR(fbdev->client->dev->dev, "fb_open failed (%d)\n", ret); + + return ret; +} + +static int drm_fbdev_fb_release(struct fb_info *info, int user) +{ + struct drm_fbdev *fbdev = info->par; + + DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n"); + mutex_lock(&fbdev->lock); + + if (--fbdev->open_count == 0) { + drm_client_display_dpms(fbdev->display, DRM_MODE_DPMS_OFF); + drm_fbdev_delete_buffer(fbdev); + } + + fbdev->defio_no_flushing = false; + + mutex_unlock(&fbdev->lock); + + return 0; +} + +static ssize_t drm_fbdev_fb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + + ret = fb_sys_write(info, buf, count, ppos); + if (ret > 0) + drm_fbdev_dirty(info, 0, 0, info->var.xres, info->var.yres); + + return ret; +} + +static void +drm_fbdev_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) +{ + sys_fillrect(info, rect); + drm_fbdev_dirty(info, rect->dx, rect->dy, rect->width, rect->height); +} + +static void +drm_fbdev_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + sys_copyarea(info, area); + drm_fbdev_dirty(info, area->dx, area->dy, area->width, area->height); +} + +static void +drm_fbdev_fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + sys_imageblit(info, image); + drm_fbdev_dirty(info, image->dx, image->dy, image->width, image->height); +} + +static int +drm_fbdev_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 new_format, old_format, yres_virtual; + struct drm_fbdev *fbdev = info->par; + const struct fb_videomode *fb_mode; + bool is_open; + int ret; + + mutex_lock(&fbdev->lock); + is_open = fbdev->open_count; + mutex_unlock(&fbdev->lock); + + if (!is_open && in_dbg_master()) + return -EINVAL; + + /* Can be called from sysfs */ + if (is_open && (var->xres_virtual > fbdev->buffer->width || + var->yres_virtual > fbdev->buffer->height)) { + DRM_DEBUG_KMS("Cannot increase virtual resolution while open\n"); + return -EBUSY; + } + + if (var->xres > var->xres_virtual || var->yres > var->yres_virtual) { + DRM_DEBUG_KMS("Requested width/height to big: %dx%d > virtual %dx%d\n", + var->xres, var->yres, var->xres_virtual, + var->yres_virtual); + return -EINVAL; + } + + ret = drm_fbdev_var_to_format(var, &new_format); + if (ret) { + DRM_DEBUG_KMS("Unsupported format\n"); + return -EINVAL; + } + + ret = drm_fbdev_var_to_format(&info->var, &old_format); + if (ret) + return ret; + + if (new_format != old_format && is_open) { + DRM_DEBUG_KMS("Cannot change format while open\n"); + return -EBUSY; + } + + drm_fbdev_format_fill_var(new_format, var); + + fb_mode = fb_find_best_mode(var, &info->modelist); + if (!fb_mode) + return -EINVAL; + + yres_virtual = var->yres_virtual; + fb_videomode_to_var(var, fb_mode); + var->yres_virtual = yres_virtual; + + return 0; +} + +static int drm_fbdev_fb_set_par(struct fb_info *info) +{ + struct drm_fbdev *fbdev = info->par; + const struct fb_videomode *fb_mode; + struct drm_mode_modeinfo *mode; + bool mode_changed; + int ret; + + mutex_lock(&fbdev->lock); + + if (!fbdev->open_count) { + ret = 0; + goto out_unlock; + } + + fb_mode = fb_match_mode(&info->var, &info->modelist); + if (!fb_mode) { + DRM_DEBUG_KMS("Couldn't find var mode\n"); + ret = -EINVAL; + goto out_unlock; + } + + mode_changed = !fb_mode_is_equal(info->mode, fb_mode); + info->mode = (struct fb_videomode *)fb_mode; + + mode = drm_fbdev_get_drm_mode(fbdev); + if (!mode) { + DRM_DEBUG_KMS("Couldn't find the matching DRM mode\n"); + ret = -EINVAL; + goto out_unlock; + } + + if (mode_changed) { + drm_client_buffer_rmfb(fbdev->buffer); + fbdev->curr_fb = 0; + ret = drm_client_buffer_addfb(fbdev->buffer, mode); + if (ret) + goto out_unlock; + + fbdev->curr_fb = fbdev->buffer->fb_ids[0]; + info->var.yoffset = 0; + } + +// info->var.width = drm_mode->width_mm; +// info->var.height = drm_mode->height_mm; + + /* Panning is only supported to do page flipping */ + info->fix.ypanstep = info->var.yres; + + ret = drm_client_display_commit_mode(fbdev->display, fbdev->curr_fb, mode); + +out_unlock: + mutex_unlock(&fbdev->lock); + + return ret; +} + +/* + * Do we need to support FB_VISUAL_PSEUDOCOLOR? + +static int drm_fbdev_fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + +} +*/ + +static int setcmap_pseudo_palette(struct fb_cmap *cmap, struct fb_info *info) +{ + u32 *palette = (u32 *)info->pseudo_palette; + int i; + + if (cmap->start + cmap->len > 16) + return -EINVAL; + + for (i = 0; i < cmap->len; ++i) { + u16 red = cmap->red[i]; + u16 green = cmap->green[i]; + u16 blue = cmap->blue[i]; + u32 value; + + red >>= 16 - info->var.red.length; + green >>= 16 - info->var.green.length; + blue >>= 16 - info->var.blue.length; + value = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + if (info->var.transp.length > 0) { + u32 mask = (1 << info->var.transp.length) - 1; + + mask <<= info->var.transp.offset; + value |= mask; + } + palette[cmap->start + i] = value; + } + + return 0; +} + +static int drm_fbdev_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + if (oops_in_progress) + return -EBUSY; + + if (info->fix.visual == FB_VISUAL_TRUECOLOR) + return setcmap_pseudo_palette(cmap, info); + + return -EINVAL; +} + +static int drm_fbdev_fb_blank(int blank, struct fb_info *info) +{ + struct drm_fbdev *fbdev = info->par; + bool is_open; + int mode; + + if (oops_in_progress) + return -EBUSY; + + mutex_lock(&fbdev->lock); + is_open = fbdev->open_count; + mutex_unlock(&fbdev->lock); + + if (!is_open) + return -EINVAL; + + if (blank == FB_BLANK_UNBLANK) + mode = DRM_MODE_DPMS_ON; + else + mode = DRM_MODE_DPMS_OFF; + + return drm_client_display_dpms(fbdev->display, mode); +} + +static int +drm_fbdev_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct drm_fbdev *fbdev = info->par; + struct drm_event *event; + unsigned int fb_idx; + int ret = 0; + + mutex_lock(&fbdev->lock); + + if (!fbdev->open_count) + goto out_unlock; + + fb_idx = var->yoffset / info->var.yres; + if (fb_idx >= fbdev->buffer->num_fbs) { + ret = -EINVAL; + goto out_unlock; + } + + /* Drain previous flip event if userspace didn't care */ + if (fbdev->page_flip_sent) { + event = drm_client_read_event(fbdev->client, false); + if (!IS_ERR(event)) + kfree(event); + fbdev->page_flip_sent = false; + } + + if (fbdev->curr_fb == fbdev->buffer->fb_ids[fb_idx]) + goto out_unlock; + + fbdev->curr_fb = fbdev->buffer->fb_ids[fb_idx]; + fbdev->defio_no_flushing = true; + + ret = drm_client_display_page_flip(fbdev->display, fbdev->curr_fb, true); + if (ret) + goto out_unlock; + + fbdev->page_flip_sent = true; + +out_unlock: + mutex_unlock(&fbdev->lock); + + return ret; +} + +static int drm_fbdev_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct drm_fbdev *fbdev = info->par; + struct drm_event *event; + bool page_flip_sent; + int ret = 0; + + switch (cmd) { +// case FBIOGET_VBLANK: +// break; + case FBIO_WAITFORVSYNC: + mutex_lock(&fbdev->lock); + page_flip_sent = fbdev->page_flip_sent; + fbdev->page_flip_sent = false; + mutex_unlock(&fbdev->lock); + + if (page_flip_sent) { + event = drm_client_read_event(fbdev->client, true); + if (IS_ERR(event)) + ret = PTR_ERR(event); + else + kfree(event); + } else { + drm_client_display_wait_vblank(fbdev->display); + } + + break; + default: + ret = -ENOTTY; + } + + return ret; +} + +static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fbdev *fbdev = info->par; + + return dma_buf_mmap(fbdev->buffer->dma_buf, vma, 0); +} + +static void drm_fbdev_fb_destroy(struct fb_info *info) +{ + struct drm_fbdev *fbdev = info->par; + + DRM_DEV_DEBUG_KMS(fbdev->client->dev->dev, "\n"); + drm_client_display_free(fbdev->display); + drm_client_free(fbdev->client); + kfree(fbdev); +} + +static struct fb_ops drm_fbdev_fb_ops = { + .owner = THIS_MODULE, + .fb_open = drm_fbdev_fb_open, + .fb_release = drm_fbdev_fb_release, + .fb_read = fb_sys_read, + .fb_write = drm_fbdev_fb_write, + .fb_check_var = drm_fbdev_fb_check_var, + .fb_set_par = drm_fbdev_fb_set_par, +// .fb_setcolreg = drm_fbdev_fb_setcolreg, + .fb_setcmap = drm_fbdev_fb_setcmap, + .fb_blank = drm_fbdev_fb_blank, + .fb_pan_display = drm_fbdev_fb_pan_display, + .fb_fillrect = drm_fbdev_fb_fillrect, + .fb_copyarea = drm_fbdev_fb_copyarea, + .fb_imageblit = drm_fbdev_fb_imageblit, + .fb_ioctl = drm_fbdev_fb_ioctl, + .fb_mmap = drm_fbdev_fb_mmap, + .fb_destroy = drm_fbdev_fb_destroy, +}; + +static int drm_fbdev_register_framebuffer(struct drm_fbdev *fbdev) +{ + struct drm_client_display *display; + struct fb_info *info; + struct fb_ops *fbops; + u32 format; + int ret; + + display = drm_client_display_get_first_enabled(fbdev->client, false); + if (IS_ERR_OR_NULL(display)) + return PTR_ERR_OR_ZERO(display); + + fbdev->display = display; + + /* + * fb_deferred_io_cleanup() clears &fbops->fb_mmap so a per instance + * version is necessary. We do it for all users since we don't know + * yet if the fb has a dirty callback. + */ + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbops) { + ret = -ENOMEM; + goto err_free; + } + + *fbops = drm_fbdev_fb_ops; + + info = framebuffer_alloc(0, fbdev->client->dev->dev); + if (!info) { + ret = -ENOMEM; + goto err_free; + } + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) + goto err_release; + + info->par = fbdev; + info->fbops = fbops; + INIT_LIST_HEAD(&info->modelist); + info->pseudo_palette = fbdev->pseudo_palette; + + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.ypanstep = info->var.yres; + + strcpy(info->fix.id, "DRM emulated"); + + fbdev->info = info; + ret = drm_fbdev_sync_modes(fbdev, true); + if (ret < 0) + goto err_free_cmap; + + info->var.bits_per_pixel = drm_client_display_preferred_depth(fbdev->display); + ret = drm_fbdev_var_to_format(&info->var, &format); + if (ret) { + DRM_WARN("Unsupported bpp, assuming x8r8g8b8 pixel format\n"); + format = DRM_FORMAT_XRGB8888; + } + drm_fbdev_format_fill_var(format, &info->var); + + info->var.xres_virtual = info->var.xres; + info->var.yres_virtual = info->var.yres; + + info->var.yres_virtual *= CONFIG_DRM_FBDEV_OVERALLOC; + info->var.yres_virtual /= 100; + + ret = register_framebuffer(info); + if (ret) + goto err_free_cmap; + + dev_info(fbdev->client->dev->dev, "fb%d: %s frame buffer device\n", + info->node, info->fix.id); + + return 0; + +err_free_cmap: + fb_dealloc_cmap(&info->cmap); +err_release: + framebuffer_release(info); +err_free: + kfree(fbops); + fbdev->info = NULL; + drm_client_display_free(fbdev->display); + fbdev->display = NULL; + + return ret; +} + +static int drm_fbdev_client_hotplug(struct drm_client_dev *client) +{ + struct drm_fbdev *fbdev = client->private; + int ret; + + if (!fbdev->info) + ret = drm_fbdev_register_framebuffer(fbdev); + else + ret = drm_fbdev_sync_modes(fbdev, false); + + return ret; +} + +static int drm_fbdev_client_new(struct drm_client_dev *client) +{ + struct drm_fbdev *fbdev; + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + mutex_init(&fbdev->lock); + spin_lock_init(&fbdev->dirty_lock); + INIT_WORK(&fbdev->dirty_work, drm_fbdev_dirty_work); + + fbdev->client = client; + client->private = fbdev; + + /* + * vc4 isn't done with it's setup when drm_dev_register() is called. + * It should have shouldn't it? + * So to keep it from crashing defer setup to hotplug... + */ + if (client->dev->mode_config.max_width) + drm_fbdev_client_hotplug(client); + + return 0; +} + +static int drm_fbdev_client_remove(struct drm_client_dev *client) +{ + struct drm_fbdev *fbdev = client->private; + + if (!fbdev->info) { + kfree(fbdev); + return 0; + } + + unregister_framebuffer(fbdev->info); + + /* drm_fbdev_fb_destroy() frees the client */ + return 1; +} + +static int drm_fbdev_client_lastclose(struct drm_client_dev *client) +{ + struct drm_fbdev *fbdev = client->private; + int ret = -ENOENT; + + if (fbdev->info) + ret = fbdev->info->fbops->fb_set_par(fbdev->info); + + return ret; +} + +static const struct drm_client_funcs drm_fbdev_client_funcs = { + .name = "drm_fbdev", + .new = drm_fbdev_client_new, + .remove = drm_fbdev_client_remove, + .lastclose = drm_fbdev_client_lastclose, + .hotplug = drm_fbdev_client_hotplug, +}; + +static int __init drm_fbdev_init(void) +{ + return drm_client_register(&drm_fbdev_client_funcs); +} +module_init(drm_fbdev_init); + +static void __exit drm_fbdev_exit(void) +{ + drm_client_unregister(&drm_fbdev_client_funcs); +} +module_exit(drm_fbdev_exit); + +MODULE_DESCRIPTION("DRM Generic fbdev emulation"); +MODULE_AUTHOR("Noralf Trønnes"); +MODULE_LICENSE("GPL"); -- 2.15.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel