diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-24 00:44:58 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-24 00:44:58 +0200 |
commit | 06e386a1db54ab6a671e103e929b590f7a88f0e3 (patch) | |
tree | 40891a97d85cb4fcbef142b031965c242c1905f3 /drivers/video/fbdev/udlfb.c | |
parent | Merge tag 'sound-fix-4.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/g... (diff) | |
parent | Documentation/fb: corrections for fbcon.txt (diff) | |
download | linux-06e386a1db54ab6a671e103e929b590f7a88f0e3.tar.xz linux-06e386a1db54ab6a671e103e929b590f7a88f0e3.zip |
Merge tag 'fbdev-v4.19' of https://github.com/bzolnier/linux
Pull fbdev updates from Bartlomiej Zolnierkiewicz:
"Mostly small fixes and cleanups for fb drivers (the biggest updates
are for udlfb and pxafb drivers). This also adds deferred console
takeover support to the console code and efifb driver.
Summary:
- add support for deferred console takeover, when enabled defers
fbcon taking over the console from the dummy console until the
first text is displayed on the console - together with the "quiet"
kernel commandline option this allows fbcon to still be used
together with a smooth graphical bootup (Hans de Goede)
- improve console locking debugging code (Thomas Zimmermann)
- copy the ACPI BGRT boot graphics to the framebuffer when deferred
console takeover support is used in efifb driver (Hans de Goede)
- update udlfb driver - fix lost console when the user unplugs a USB
adapter, fix the screen corruption issue, fix locking and add some
performance optimizations (Mikulas Patocka)
- update pxafb driver - fix using uninitialized memory, switch to
devm_* API, handle initialization errors and add support for
lcd-supply regulator (Daniel Mack)
- add support for boards booted with a DeviceTree in pxa3xx_gcu
driver (Daniel Mack)
- rename omap2 module to omap2fb.ko to avoid conflicts with omap1
driver (Arnd Bergmann)
- enable ACPI-based enumeration for goldfishfb driver (Yu Ning)
- fix goldfishfb driver to make user space Android code use 60 fps
(Christoffer Dall)
- print big fat warning when nomodeset kernel parameter is used in
vgacon driver (Lyude Paul)
- remove VLA usage from fsl-diu-fb driver (Kees Cook)
- misc fixes (Julia Lawall, Geert Uytterhoeven, Fredrik Noring,
Yisheng Xie, Dan Carpenter, Daniel Vetter, Anton Vasilyev, Randy
Dunlap, Gustavo A. R. Silva, Colin Ian King, Fengguang Wu)
- misc cleanups (Roman Kiryanov, Yisheng Xie, Colin Ian King)"
* tag 'fbdev-v4.19' of https://github.com/bzolnier/linux: (54 commits)
Documentation/fb: corrections for fbcon.txt
fbcon: Do not takeover the console from atomic context
dummycon: Stop exporting dummycon_[un]register_output_notifier
fbcon: Only defer console takeover if the current console driver is the dummycon
fbcon: Only allow FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER if fbdev is builtin
fbdev: omap2: omapfb: fix ifnullfree.cocci warnings
fbdev: omap2: omapfb: fix bugon.cocci warnings
fbdev: omap2: omapfb: fix boolreturn.cocci warnings
fb: amifb: fix build warnings when not builtin
fbdev/core: Disable console-lock warnings when fb.lockless_register_fb is set
console: Replace #if 0 with atomic var 'ignore_console_lock_warning'
udlfb: use spin_lock_irq instead of spin_lock_irqsave
udlfb: avoid prefetch
udlfb: optimization - test the backing buffer
udlfb: allow reallocating the framebuffer
udlfb: set line_length in dlfb_ops_set_par
udlfb: handle allocation failure
udlfb: set optimal write delay
udlfb: make a local copy of fb_ops
udlfb: don't switch if we are switching to the same videomode
...
Diffstat (limited to 'drivers/video/fbdev/udlfb.c')
-rw-r--r-- | drivers/video/fbdev/udlfb.c | 230 |
1 files changed, 139 insertions, 91 deletions
diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index f365d4862015..afbd6101c78e 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -25,8 +25,8 @@ #include <linux/fb.h> #include <linux/vmalloc.h> #include <linux/slab.h> -#include <linux/prefetch.h> #include <linux/delay.h> +#include <asm/unaligned.h> #include <video/udlfb.h> #include "edid.h" @@ -72,6 +72,13 @@ static bool fb_defio = 1; /* Detect mmap writes using page faults */ static bool shadow = 1; /* Optionally disable shadow framebuffer */ static int pixel_limit; /* Optionally force a pixel resolution limit */ +struct dlfb_deferred_free { + struct list_head list; + void *mem; +}; + +static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len); + /* dlfb keeps a list of urbs for efficient bulk transfers */ static void dlfb_urb_completion(struct urb *urb); static struct urb *dlfb_get_urb(struct dlfb_data *dlfb); @@ -367,9 +374,6 @@ static int dlfb_trim_hline(const u8 *bback, const u8 **bfront, int *width_bytes) int start = width; int end = width; - prefetch((void *) front); - prefetch((void *) back); - for (j = 0; j < width; j++) { if (back[j] != front[j]) { start = j; @@ -423,7 +427,9 @@ static void dlfb_compress_hline( const uint16_t *const pixel_end, uint32_t *device_address_ptr, uint8_t **command_buffer_ptr, - const uint8_t *const cmd_buffer_end) + const uint8_t *const cmd_buffer_end, + unsigned long back_buffer_offset, + int *ident_ptr) { const uint16_t *pixel = *pixel_start_ptr; uint32_t dev_addr = *device_address_ptr; @@ -436,7 +442,13 @@ static void dlfb_compress_hline( const uint16_t *raw_pixel_start = NULL; const uint16_t *cmd_pixel_start, *cmd_pixel_end = NULL; - prefetchw((void *) cmd); /* pull in one cache line at least */ + if (back_buffer_offset && + *pixel == *(u16 *)((u8 *)pixel + back_buffer_offset)) { + pixel++; + dev_addr += BPP; + (*ident_ptr)++; + continue; + } *cmd++ = 0xAF; *cmd++ = 0x6B; @@ -450,29 +462,39 @@ static void dlfb_compress_hline( raw_pixels_count_byte = cmd++; /* we'll know this later */ raw_pixel_start = pixel; - cmd_pixel_end = pixel + min(MAX_CMD_PIXELS + 1, - min((int)(pixel_end - pixel), - (int)(cmd_buffer_end - cmd) / BPP)); + cmd_pixel_end = pixel + min3(MAX_CMD_PIXELS + 1UL, + (unsigned long)(pixel_end - pixel), + (unsigned long)(cmd_buffer_end - 1 - cmd) / BPP); - prefetch_range((void *) pixel, (cmd_pixel_end - pixel) * BPP); + if (back_buffer_offset) { + /* note: the framebuffer may change under us, so we must test for underflow */ + while (cmd_pixel_end - 1 > pixel && + *(cmd_pixel_end - 1) == *(u16 *)((u8 *)(cmd_pixel_end - 1) + back_buffer_offset)) + cmd_pixel_end--; + } while (pixel < cmd_pixel_end) { const uint16_t * const repeating_pixel = pixel; + u16 pixel_value = *pixel; - *cmd++ = *pixel >> 8; - *cmd++ = *pixel; + put_unaligned_be16(pixel_value, cmd); + if (back_buffer_offset) + *(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value; + cmd += 2; pixel++; if (unlikely((pixel < cmd_pixel_end) && - (*pixel == *repeating_pixel))) { + (*pixel == pixel_value))) { /* go back and fill in raw pixel count */ *raw_pixels_count_byte = ((repeating_pixel - raw_pixel_start) + 1) & 0xFF; - while ((pixel < cmd_pixel_end) - && (*pixel == *repeating_pixel)) { + do { + if (back_buffer_offset) + *(u16 *)((u8 *)pixel + back_buffer_offset) = pixel_value; pixel++; - } + } while ((pixel < cmd_pixel_end) && + (*pixel == pixel_value)); /* immediately after raw data is repeat byte */ *cmd++ = ((pixel - repeating_pixel) - 1) & 0xFF; @@ -486,13 +508,16 @@ static void dlfb_compress_hline( if (pixel > raw_pixel_start) { /* finalize last RAW span */ *raw_pixels_count_byte = (pixel-raw_pixel_start) & 0xFF; + } else { + /* undo unused byte */ + cmd--; } *cmd_pixels_count_byte = (pixel - cmd_pixel_start) & 0xFF; - dev_addr += (pixel - cmd_pixel_start) * BPP; + dev_addr += (u8 *)pixel - (u8 *)cmd_pixel_start; } - if (cmd_buffer_end <= MIN_RLX_CMD_BYTES + cmd) { + if (cmd_buffer_end - MIN_RLX_CMD_BYTES <= cmd) { /* Fill leftover bytes with no-ops */ if (cmd_buffer_end > cmd) memset(cmd, 0xAF, cmd_buffer_end - cmd); @@ -520,6 +545,7 @@ static int dlfb_render_hline(struct dlfb_data *dlfb, struct urb **urb_ptr, struct urb *urb = *urb_ptr; u8 *cmd = *urb_buf_ptr; u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length; + unsigned long back_buffer_offset = 0; line_start = (u8 *) (front + byte_offset); next_pixel = line_start; @@ -530,6 +556,8 @@ static int dlfb_render_hline(struct dlfb_data *dlfb, struct urb **urb_ptr, const u8 *back_start = (u8 *) (dlfb->backing_buffer + byte_offset); + back_buffer_offset = (unsigned long)back_start - (unsigned long)line_start; + *ident_ptr += dlfb_trim_hline(back_start, &next_pixel, &byte_width); @@ -538,16 +566,14 @@ static int dlfb_render_hline(struct dlfb_data *dlfb, struct urb **urb_ptr, dev_addr += offset; back_start += offset; line_start += offset; - - memcpy((char *)back_start, (char *) line_start, - byte_width); } while (next_pixel < line_end) { dlfb_compress_hline((const uint16_t **) &next_pixel, (const uint16_t *) line_end, &dev_addr, - (u8 **) &cmd, (u8 *) cmd_end); + (u8 **) &cmd, (u8 *) cmd_end, back_buffer_offset, + ident_ptr); if (cmd >= cmd_end) { int len = cmd - (u8 *) urb->transfer_buffer; @@ -610,8 +636,11 @@ static int dlfb_handle_damage(struct dlfb_data *dlfb, int x, int y, } if (cmd > (char *) urb->transfer_buffer) { + int len; + if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length) + *cmd++ = 0xAF; /* Send partial buffer remaining before exiting */ - int len = cmd - (char *) urb->transfer_buffer; + len = cmd - (char *) urb->transfer_buffer; ret = dlfb_submit_urb(dlfb, urb, len); bytes_sent += len; } else @@ -735,8 +764,11 @@ static void dlfb_dpy_deferred_io(struct fb_info *info, } if (cmd > (char *) urb->transfer_buffer) { + int len; + if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length) + *cmd++ = 0xAF; /* Send partial buffer remaining before exiting */ - int len = cmd - (char *) urb->transfer_buffer; + len = cmd - (char *) urb->transfer_buffer; dlfb_submit_urb(dlfb, urb, len); bytes_sent += len; } else @@ -917,19 +949,17 @@ static void dlfb_free(struct kref *kref) { struct dlfb_data *dlfb = container_of(kref, struct dlfb_data, kref); + while (!list_empty(&dlfb->deferred_free)) { + struct dlfb_deferred_free *d = list_entry(dlfb->deferred_free.next, struct dlfb_deferred_free, list); + list_del(&d->list); + vfree(d->mem); + kfree(d); + } vfree(dlfb->backing_buffer); kfree(dlfb->edid); kfree(dlfb); } -static void dlfb_release_urb_work(struct work_struct *work) -{ - struct urb_node *unode = container_of(work, struct urb_node, - release_urb_work.work); - - up(&unode->dlfb->urbs.limit_sem); -} - static void dlfb_free_framebuffer(struct dlfb_data *dlfb) { struct fb_info *info = dlfb->info; @@ -1018,10 +1048,6 @@ static int dlfb_ops_check_var(struct fb_var_screeninfo *var, struct fb_videomode mode; struct dlfb_data *dlfb = info->par; - /* TODO: support dynamically changing framebuffer size */ - if ((var->xres * var->yres * 2) > info->fix.smem_len) - return -EINVAL; - /* set device-specific elements of var unrelated to mode */ dlfb_var_color_format(var); @@ -1039,22 +1065,42 @@ static int dlfb_ops_set_par(struct fb_info *info) int result; u16 *pix_framebuffer; int i; + struct fb_var_screeninfo fvs; + u32 line_length = info->var.xres * (info->var.bits_per_pixel / 8); + + /* clear the activate field because it causes spurious miscompares */ + fvs = info->var; + fvs.activate = 0; + fvs.vmode &= ~FB_VMODE_SMOOTH_XPAN; + + if (!memcmp(&dlfb->current_mode, &fvs, sizeof(struct fb_var_screeninfo))) + return 0; + + result = dlfb_realloc_framebuffer(dlfb, info, info->var.yres * line_length); + if (result) + return result; result = dlfb_set_video_mode(dlfb, &info->var); - if ((result == 0) && (dlfb->fb_count == 0)) { + if (result) + return result; + + dlfb->current_mode = fvs; + info->fix.line_length = line_length; + + if (dlfb->fb_count == 0) { /* paint greenscreen */ pix_framebuffer = (u16 *) info->screen_base; for (i = 0; i < info->fix.smem_len / 2; i++) pix_framebuffer[i] = 0x37e6; - - dlfb_handle_damage(dlfb, 0, 0, info->var.xres, info->var.yres, - info->screen_base); } - return result; + dlfb_handle_damage(dlfb, 0, 0, info->var.xres, info->var.yres, + info->screen_base); + + return 0; } /* To fonzi the jukebox (e.g. make blanking changes take effect) */ @@ -1129,21 +1175,29 @@ static struct fb_ops dlfb_ops = { }; +static void dlfb_deferred_vfree(struct dlfb_data *dlfb, void *mem) +{ + struct dlfb_deferred_free *d = kmalloc(sizeof(struct dlfb_deferred_free), GFP_KERNEL); + if (!d) + return; + d->mem = mem; + list_add(&d->list, &dlfb->deferred_free); +} + /* * Assumes &info->lock held by caller * Assumes no active clients have framebuffer open */ -static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info) +static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len) { - int old_len = info->fix.smem_len; - int new_len; - unsigned char *old_fb = info->screen_base; + u32 old_len = info->fix.smem_len; + const void *old_fb = (const void __force *)info->screen_base; unsigned char *new_fb; unsigned char *new_back = NULL; - new_len = info->fix.line_length * info->var.yres; + new_len = PAGE_ALIGN(new_len); - if (PAGE_ALIGN(new_len) > old_len) { + if (new_len > old_len) { /* * Alloc system memory for virtual framebuffer */ @@ -1152,14 +1206,15 @@ static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info dev_err(info->dev, "Virtual framebuffer alloc failed\n"); return -ENOMEM; } + memset(new_fb, 0xff, new_len); if (info->screen_base) { memcpy(new_fb, old_fb, old_len); - vfree(info->screen_base); + dlfb_deferred_vfree(dlfb, (void __force *)info->screen_base); } - info->screen_base = new_fb; - info->fix.smem_len = PAGE_ALIGN(new_len); + info->screen_base = (char __iomem *)new_fb; + info->fix.smem_len = new_len; info->fix.smem_start = (unsigned long) new_fb; info->flags = udlfb_info_flags; @@ -1175,7 +1230,7 @@ static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info dev_info(info->dev, "No shadow/backing buffer allocated\n"); else { - vfree(dlfb->backing_buffer); + dlfb_deferred_vfree(dlfb, dlfb->backing_buffer); dlfb->backing_buffer = new_back; } } @@ -1327,11 +1382,6 @@ static int dlfb_setup_modes(struct dlfb_data *dlfb, * with mode size info, we can now alloc our framebuffer. */ memcpy(&info->fix, &dlfb_fix, sizeof(dlfb_fix)); - info->fix.line_length = info->var.xres * - (info->var.bits_per_pixel / 8); - - result = dlfb_realloc_framebuffer(dlfb, info); - } else result = -EINVAL; @@ -1419,7 +1469,10 @@ static ssize_t edid_store( if (!dlfb->edid || memcmp(src, dlfb->edid, src_size)) return -EINVAL; - dlfb_ops_set_par(fb_info); + ret = dlfb_ops_set_par(fb_info); + if (ret) + return ret; + return src_size; } @@ -1579,6 +1632,7 @@ static int dlfb_usb_probe(struct usb_interface *intf, } kref_init(&dlfb->kref); /* matching kref_put in usb .disconnect fn */ + INIT_LIST_HEAD(&dlfb->deferred_free); dlfb->udev = usbdev; usb_set_intfdata(intf, dlfb); @@ -1649,7 +1703,8 @@ static void dlfb_init_framebuffer_work(struct work_struct *work) dlfb->info = info; info->par = dlfb; info->pseudo_palette = dlfb->pseudo_palette; - info->fbops = &dlfb_ops; + dlfb->ops = dlfb_ops; + info->fbops = &dlfb->ops; retval = fb_alloc_cmap(&info->cmap, 256, 0); if (retval < 0) { @@ -1675,7 +1730,9 @@ static void dlfb_init_framebuffer_work(struct work_struct *work) dlfb_select_std_channel(dlfb); dlfb_ops_check_var(&info->var, info); - dlfb_ops_set_par(info); + retval = dlfb_ops_set_par(info); + if (retval) + goto error; retval = register_framebuffer(info); if (retval < 0) { @@ -1789,14 +1846,7 @@ static void dlfb_urb_completion(struct urb *urb) dlfb->urbs.available++; spin_unlock_irqrestore(&dlfb->urbs.lock, flags); - /* - * When using fb_defio, we deadlock if up() is called - * while another is waiting. So queue to another process. - */ - if (fb_defio) - schedule_delayed_work(&unode->release_urb_work, 0); - else - up(&dlfb->urbs.limit_sem); + up(&dlfb->urbs.limit_sem); } static void dlfb_free_urb_list(struct dlfb_data *dlfb) @@ -1805,23 +1855,17 @@ static void dlfb_free_urb_list(struct dlfb_data *dlfb) struct list_head *node; struct urb_node *unode; struct urb *urb; - int ret; - unsigned long flags; /* keep waiting and freeing, until we've got 'em all */ while (count--) { + down(&dlfb->urbs.limit_sem); - /* Getting interrupted means a leak, but ok at disconnect */ - ret = down_interruptible(&dlfb->urbs.limit_sem); - if (ret) - break; - - spin_lock_irqsave(&dlfb->urbs.lock, flags); + spin_lock_irq(&dlfb->urbs.lock); node = dlfb->urbs.list.next; /* have reserved one with sem */ list_del_init(node); - spin_unlock_irqrestore(&dlfb->urbs.lock, flags); + spin_unlock_irq(&dlfb->urbs.lock); unode = list_entry(node, struct urb_node, entry); urb = unode->urb; @@ -1838,25 +1882,27 @@ static void dlfb_free_urb_list(struct dlfb_data *dlfb) static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size) { - int i = 0; struct urb *urb; struct urb_node *unode; char *buf; + size_t wanted_size = count * size; spin_lock_init(&dlfb->urbs.lock); +retry: dlfb->urbs.size = size; INIT_LIST_HEAD(&dlfb->urbs.list); - while (i < count) { + sema_init(&dlfb->urbs.limit_sem, 0); + dlfb->urbs.count = 0; + dlfb->urbs.available = 0; + + while (dlfb->urbs.count * size < wanted_size) { unode = kzalloc(sizeof(*unode), GFP_KERNEL); if (!unode) break; unode->dlfb = dlfb; - INIT_DELAYED_WORK(&unode->release_urb_work, - dlfb_release_urb_work); - urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { kfree(unode); @@ -1864,11 +1910,16 @@ static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size) } unode->urb = urb; - buf = usb_alloc_coherent(dlfb->udev, MAX_TRANSFER, GFP_KERNEL, + buf = usb_alloc_coherent(dlfb->udev, size, GFP_KERNEL, &urb->transfer_dma); if (!buf) { kfree(unode); usb_free_urb(urb); + if (size > PAGE_SIZE) { + size /= 2; + dlfb_free_urb_list(dlfb); + goto retry; + } break; } @@ -1879,14 +1930,12 @@ static int dlfb_alloc_urb_list(struct dlfb_data *dlfb, int count, size_t size) list_add_tail(&unode->entry, &dlfb->urbs.list); - i++; + up(&dlfb->urbs.limit_sem); + dlfb->urbs.count++; + dlfb->urbs.available++; } - sema_init(&dlfb->urbs.limit_sem, i); - dlfb->urbs.count = i; - dlfb->urbs.available = i; - - return i; + return dlfb->urbs.count; } static struct urb *dlfb_get_urb(struct dlfb_data *dlfb) @@ -1894,7 +1943,6 @@ static struct urb *dlfb_get_urb(struct dlfb_data *dlfb) int ret; struct list_head *entry; struct urb_node *unode; - unsigned long flags; /* Wait for an in-flight buffer to complete and get re-queued */ ret = down_timeout(&dlfb->urbs.limit_sem, GET_URB_TIMEOUT); @@ -1906,14 +1954,14 @@ static struct urb *dlfb_get_urb(struct dlfb_data *dlfb) return NULL; } - spin_lock_irqsave(&dlfb->urbs.lock, flags); + spin_lock_irq(&dlfb->urbs.lock); BUG_ON(list_empty(&dlfb->urbs.list)); /* reserved one with limit_sem */ entry = dlfb->urbs.list.next; list_del_init(entry); dlfb->urbs.available--; - spin_unlock_irqrestore(&dlfb->urbs.lock, flags); + spin_unlock_irq(&dlfb->urbs.lock); unode = list_entry(entry, struct urb_node, entry); return unode->urb; |