diff options
author | Bruno Prémont <bonbons@linux-vserver.org> | 2010-06-28 22:33:27 +0200 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2010-07-12 18:07:36 +0200 |
commit | 225b4590062008c9de22ed6e3a200f832d9bcdc8 (patch) | |
tree | 8a566370c1a31457fc9c9fb961d8e61d71f76ecf | |
parent | 365f1fcd0d5a40f933bed55e515fce2077c40e9a (diff) | |
download | linux-3.10-225b4590062008c9de22ed6e3a200f832d9bcdc8.tar.gz linux-3.10-225b4590062008c9de22ed6e3a200f832d9bcdc8.tar.bz2 linux-3.10-225b4590062008c9de22ed6e3a200f832d9bcdc8.zip |
HID: picolcd: implement refcounting of framebuffer
As our device may be hot-unplugged and framebuffer cannot handle
this case by itself we need to keep track of usage count so as
to release fb_info and framebuffer memory only after the last user
has closed framebuffer.
We need to do the freeing in a scheduled work as fb_release()
is called with fb_info lock held.
Signed-off-by: Bruno Prémont <bonbons@linux-vserver.org>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | drivers/hid/hid-picolcd.c | 110 |
1 files changed, 103 insertions, 7 deletions
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index f7204541591..346f0e34987 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -439,6 +439,9 @@ static void picolcd_fb_update(struct picolcd_data *data) int chip, tile, n; unsigned long flags; + if (!data) + return; + spin_lock_irqsave(&data->lock, flags); if (!(data->status & PICOLCD_READY_FB)) { spin_unlock_irqrestore(&data->lock, flags); @@ -461,6 +464,8 @@ static void picolcd_fb_update(struct picolcd_data *data) data->fb_bitmap, data->fb_bpp, chip, tile) || data->fb_force) { n += 2; + if (!data->fb_info->par) + return; /* device lost! */ if (n >= HID_OUTPUT_FIFO_SIZE / 2) { usbhid_wait_io(data->hdev); n = 0; @@ -531,11 +536,23 @@ static int picolcd_fb_blank(int blank, struct fb_info *info) static void picolcd_fb_destroy(struct fb_info *info) { struct picolcd_data *data = info->par; + u32 *ref_cnt = info->pseudo_palette; + int may_release; + info->par = NULL; if (data) data->fb_info = NULL; fb_deferred_io_cleanup(info); - framebuffer_release(info); + + ref_cnt--; + mutex_lock(&info->lock); + (*ref_cnt)--; + may_release = !ref_cnt; + mutex_unlock(&info->lock); + if (may_release) { + framebuffer_release(info); + vfree((u8 *)info->fix.smem_start); + } } static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) @@ -564,6 +581,8 @@ static int picolcd_set_par(struct fb_info *info) { struct picolcd_data *data = info->par; u8 *tmp_fb, *o_fb; + if (!data) + return -ENODEV; if (info->var.bits_per_pixel == data->fb_bpp) return 0; /* switch between 1/8 bit depths */ @@ -603,10 +622,77 @@ static int picolcd_set_par(struct fb_info *info) return 0; } +/* Do refcounting on our FB and cleanup per worker if FB is + * closed after unplug of our device + * (fb_release holds info->lock and still touches info after + * we return so we can't release it immediately. + */ +struct picolcd_fb_cleanup_item { + struct fb_info *info; + struct picolcd_fb_cleanup_item *next; +}; +static struct picolcd_fb_cleanup_item *fb_pending; +DEFINE_SPINLOCK(fb_pending_lock); + +static void picolcd_fb_do_cleanup(struct work_struct *data) +{ + struct picolcd_fb_cleanup_item *item; + unsigned long flags; + + do { + spin_lock_irqsave(&fb_pending_lock, flags); + item = fb_pending; + fb_pending = item ? item->next : NULL; + spin_unlock_irqrestore(&fb_pending_lock, flags); + + if (item) { + u8 *fb = (u8 *)item->info->fix.smem_start; + /* make sure we do not race against fb core when + * releasing */ + mutex_lock(&item->info->lock); + mutex_unlock(&item->info->lock); + framebuffer_release(item->info); + vfree(fb); + } + } while (item); +} + +DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); + +static int picolcd_fb_open(struct fb_info *info, int u) +{ + u32 *ref_cnt = info->pseudo_palette; + ref_cnt--; + + (*ref_cnt)++; + return 0; +} + +static int picolcd_fb_release(struct fb_info *info, int u) +{ + u32 *ref_cnt = info->pseudo_palette; + ref_cnt--; + + (*ref_cnt)++; + if (!*ref_cnt) { + unsigned long flags; + struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt; + item--; + spin_lock_irqsave(&fb_pending_lock, flags); + item->next = fb_pending; + fb_pending = item; + spin_unlock_irqrestore(&fb_pending_lock, flags); + schedule_work(&picolcd_fb_cleanup); + } + return 0; +} + /* Note this can't be const because of struct fb_info definition */ static struct fb_ops picolcdfb_ops = { .owner = THIS_MODULE, .fb_destroy = picolcd_fb_destroy, + .fb_open = picolcd_fb_open, + .fb_release = picolcd_fb_release, .fb_read = fb_sys_read, .fb_write = picolcd_fb_write, .fb_blank = picolcd_fb_blank, @@ -708,13 +794,13 @@ static int picolcd_init_framebuffer(struct picolcd_data *data) * - u32 for ref_count * - 256*u32 for pseudo_palette */ - info = framebuffer_alloc(257 * sizeof(u32), dev); + info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev); if (info == NULL) { dev_err(dev, "failed to allocate a framebuffer\n"); goto err_nomem; } - palette = info->par; + palette = info->par + sizeof(struct picolcd_fb_cleanup_item); *palette = 1; palette++; for (i = 0; i < 256; i++) @@ -775,18 +861,17 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data) { struct fb_info *info = data->fb_info; u8 *fb_vbitmap = data->fb_vbitmap; - u8 *fb_bitmap = data->fb_bitmap; if (!info) return; + info->par = NULL; + device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); + unregister_framebuffer(info); data->fb_vbitmap = NULL; data->fb_bitmap = NULL; data->fb_bpp = 0; data->fb_info = NULL; - device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); - unregister_framebuffer(info); - vfree(fb_bitmap); kfree(fb_vbitmap); } @@ -2603,6 +2688,13 @@ static void picolcd_remove(struct hid_device *hdev) spin_lock_irqsave(&data->lock, flags); data->status |= PICOLCD_FAILED; spin_unlock_irqrestore(&data->lock, flags); +#ifdef CONFIG_HID_PICOLCD_FB + /* short-circuit FB as early as possible in order to + * avoid long delays if we host console. + */ + if (data->fb_info) + data->fb_info->par = NULL; +#endif picolcd_exit_devfs(data); device_remove_file(&hdev->dev, &dev_attr_operation_mode); @@ -2660,6 +2752,10 @@ static int __init picolcd_init(void) static void __exit picolcd_exit(void) { hid_unregister_driver(&picolcd_driver); +#ifdef CONFIG_HID_PICOLCD_FB + flush_scheduled_work(); + WARN_ON(fb_pending); +#endif } module_init(picolcd_init); |