aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorBruno Prémont <bonbons@linux-vserver.org>2010-06-28 16:33:27 -0400
committerJiri Kosina <jkosina@suse.cz>2010-07-12 12:07:36 -0400
commit225b4590062008c9de22ed6e3a200f832d9bcdc8 (patch)
tree8a566370c1a31457fc9c9fb961d8e61d71f76ecf /drivers/hid
parent365f1fcd0d5a40f933bed55e515fce2077c40e9a (diff)
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>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-picolcd.c110
1 files changed, 103 insertions, 7 deletions
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index f7204541591c..346f0e34987e 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)
439 int chip, tile, n; 439 int chip, tile, n;
440 unsigned long flags; 440 unsigned long flags;
441 441
442 if (!data)
443 return;
444
442 spin_lock_irqsave(&data->lock, flags); 445 spin_lock_irqsave(&data->lock, flags);
443 if (!(data->status & PICOLCD_READY_FB)) { 446 if (!(data->status & PICOLCD_READY_FB)) {
444 spin_unlock_irqrestore(&data->lock, flags); 447 spin_unlock_irqrestore(&data->lock, flags);
@@ -461,6 +464,8 @@ static void picolcd_fb_update(struct picolcd_data *data)
461 data->fb_bitmap, data->fb_bpp, chip, tile) || 464 data->fb_bitmap, data->fb_bpp, chip, tile) ||
462 data->fb_force) { 465 data->fb_force) {
463 n += 2; 466 n += 2;
467 if (!data->fb_info->par)
468 return; /* device lost! */
464 if (n >= HID_OUTPUT_FIFO_SIZE / 2) { 469 if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
465 usbhid_wait_io(data->hdev); 470 usbhid_wait_io(data->hdev);
466 n = 0; 471 n = 0;
@@ -531,11 +536,23 @@ static int picolcd_fb_blank(int blank, struct fb_info *info)
531static void picolcd_fb_destroy(struct fb_info *info) 536static void picolcd_fb_destroy(struct fb_info *info)
532{ 537{
533 struct picolcd_data *data = info->par; 538 struct picolcd_data *data = info->par;
539 u32 *ref_cnt = info->pseudo_palette;
540 int may_release;
541
534 info->par = NULL; 542 info->par = NULL;
535 if (data) 543 if (data)
536 data->fb_info = NULL; 544 data->fb_info = NULL;
537 fb_deferred_io_cleanup(info); 545 fb_deferred_io_cleanup(info);
538 framebuffer_release(info); 546
547 ref_cnt--;
548 mutex_lock(&info->lock);
549 (*ref_cnt)--;
550 may_release = !ref_cnt;
551 mutex_unlock(&info->lock);
552 if (may_release) {
553 framebuffer_release(info);
554 vfree((u8 *)info->fix.smem_start);
555 }
539} 556}
540 557
541static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) 558static 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)
564{ 581{
565 struct picolcd_data *data = info->par; 582 struct picolcd_data *data = info->par;
566 u8 *tmp_fb, *o_fb; 583 u8 *tmp_fb, *o_fb;
584 if (!data)
585 return -ENODEV;
567 if (info->var.bits_per_pixel == data->fb_bpp) 586 if (info->var.bits_per_pixel == data->fb_bpp)
568 return 0; 587 return 0;
569 /* switch between 1/8 bit depths */ 588 /* switch between 1/8 bit depths */
@@ -603,10 +622,77 @@ static int picolcd_set_par(struct fb_info *info)
603 return 0; 622 return 0;
604} 623}
605 624
625/* Do refcounting on our FB and cleanup per worker if FB is
626 * closed after unplug of our device
627 * (fb_release holds info->lock and still touches info after
628 * we return so we can't release it immediately.
629 */
630struct picolcd_fb_cleanup_item {
631 struct fb_info *info;
632 struct picolcd_fb_cleanup_item *next;
633};
634static struct picolcd_fb_cleanup_item *fb_pending;
635DEFINE_SPINLOCK(fb_pending_lock);
636
637static void picolcd_fb_do_cleanup(struct work_struct *data)
638{
639 struct picolcd_fb_cleanup_item *item;
640 unsigned long flags;
641
642 do {
643 spin_lock_irqsave(&fb_pending_lock, flags);
644 item = fb_pending;
645 fb_pending = item ? item->next : NULL;
646 spin_unlock_irqrestore(&fb_pending_lock, flags);
647
648 if (item) {
649 u8 *fb = (u8 *)item->info->fix.smem_start;
650 /* make sure we do not race against fb core when
651 * releasing */
652 mutex_lock(&item->info->lock);
653 mutex_unlock(&item->info->lock);
654 framebuffer_release(item->info);
655 vfree(fb);
656 }
657 } while (item);
658}
659
660DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup);
661
662static int picolcd_fb_open(struct fb_info *info, int u)
663{
664 u32 *ref_cnt = info->pseudo_palette;
665 ref_cnt--;
666
667 (*ref_cnt)++;
668 return 0;
669}
670
671static int picolcd_fb_release(struct fb_info *info, int u)
672{
673 u32 *ref_cnt = info->pseudo_palette;
674 ref_cnt--;
675
676 (*ref_cnt)++;
677 if (!*ref_cnt) {
678 unsigned long flags;
679 struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt;
680 item--;
681 spin_lock_irqsave(&fb_pending_lock, flags);
682 item->next = fb_pending;
683 fb_pending = item;
684 spin_unlock_irqrestore(&fb_pending_lock, flags);
685 schedule_work(&picolcd_fb_cleanup);
686 }
687 return 0;
688}
689
606/* Note this can't be const because of struct fb_info definition */ 690/* Note this can't be const because of struct fb_info definition */
607static struct fb_ops picolcdfb_ops = { 691static struct fb_ops picolcdfb_ops = {
608 .owner = THIS_MODULE, 692 .owner = THIS_MODULE,
609 .fb_destroy = picolcd_fb_destroy, 693 .fb_destroy = picolcd_fb_destroy,
694 .fb_open = picolcd_fb_open,
695 .fb_release = picolcd_fb_release,
610 .fb_read = fb_sys_read, 696 .fb_read = fb_sys_read,
611 .fb_write = picolcd_fb_write, 697 .fb_write = picolcd_fb_write,
612 .fb_blank = picolcd_fb_blank, 698 .fb_blank = picolcd_fb_blank,
@@ -708,13 +794,13 @@ static int picolcd_init_framebuffer(struct picolcd_data *data)
708 * - u32 for ref_count 794 * - u32 for ref_count
709 * - 256*u32 for pseudo_palette 795 * - 256*u32 for pseudo_palette
710 */ 796 */
711 info = framebuffer_alloc(257 * sizeof(u32), dev); 797 info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev);
712 if (info == NULL) { 798 if (info == NULL) {
713 dev_err(dev, "failed to allocate a framebuffer\n"); 799 dev_err(dev, "failed to allocate a framebuffer\n");
714 goto err_nomem; 800 goto err_nomem;
715 } 801 }
716 802
717 palette = info->par; 803 palette = info->par + sizeof(struct picolcd_fb_cleanup_item);
718 *palette = 1; 804 *palette = 1;
719 palette++; 805 palette++;
720 for (i = 0; i < 256; i++) 806 for (i = 0; i < 256; i++)
@@ -775,18 +861,17 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data)
775{ 861{
776 struct fb_info *info = data->fb_info; 862 struct fb_info *info = data->fb_info;
777 u8 *fb_vbitmap = data->fb_vbitmap; 863 u8 *fb_vbitmap = data->fb_vbitmap;
778 u8 *fb_bitmap = data->fb_bitmap;
779 864
780 if (!info) 865 if (!info)
781 return; 866 return;
782 867
868 info->par = NULL;
869 device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
870 unregister_framebuffer(info);
783 data->fb_vbitmap = NULL; 871 data->fb_vbitmap = NULL;
784 data->fb_bitmap = NULL; 872 data->fb_bitmap = NULL;
785 data->fb_bpp = 0; 873 data->fb_bpp = 0;
786 data->fb_info = NULL; 874 data->fb_info = NULL;
787 device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
788 unregister_framebuffer(info);
789 vfree(fb_bitmap);
790 kfree(fb_vbitmap); 875 kfree(fb_vbitmap);
791} 876}
792 877
@@ -2603,6 +2688,13 @@ static void picolcd_remove(struct hid_device *hdev)
2603 spin_lock_irqsave(&data->lock, flags); 2688 spin_lock_irqsave(&data->lock, flags);
2604 data->status |= PICOLCD_FAILED; 2689 data->status |= PICOLCD_FAILED;
2605 spin_unlock_irqrestore(&data->lock, flags); 2690 spin_unlock_irqrestore(&data->lock, flags);
2691#ifdef CONFIG_HID_PICOLCD_FB
2692 /* short-circuit FB as early as possible in order to
2693 * avoid long delays if we host console.
2694 */
2695 if (data->fb_info)
2696 data->fb_info->par = NULL;
2697#endif
2606 2698
2607 picolcd_exit_devfs(data); 2699 picolcd_exit_devfs(data);
2608 device_remove_file(&hdev->dev, &dev_attr_operation_mode); 2700 device_remove_file(&hdev->dev, &dev_attr_operation_mode);
@@ -2660,6 +2752,10 @@ static int __init picolcd_init(void)
2660static void __exit picolcd_exit(void) 2752static void __exit picolcd_exit(void)
2661{ 2753{
2662 hid_unregister_driver(&picolcd_driver); 2754 hid_unregister_driver(&picolcd_driver);
2755#ifdef CONFIG_HID_PICOLCD_FB
2756 flush_scheduled_work();
2757 WARN_ON(fb_pending);
2758#endif
2663} 2759}
2664 2760
2665module_init(picolcd_init); 2761module_init(picolcd_init);