diff options
author | Bruno Prémont <bonbons@linux-vserver.org> | 2010-06-28 16:33:27 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2010-07-12 12:07:36 -0400 |
commit | 225b4590062008c9de22ed6e3a200f832d9bcdc8 (patch) | |
tree | 8a566370c1a31457fc9c9fb961d8e61d71f76ecf /drivers/hid | |
parent | 365f1fcd0d5a40f933bed55e515fce2077c40e9a (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.c | 110 |
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) | |||
531 | static void picolcd_fb_destroy(struct fb_info *info) | 536 | static 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 | ||
541 | static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | 558 | 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) | |||
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 | */ | ||
630 | struct picolcd_fb_cleanup_item { | ||
631 | struct fb_info *info; | ||
632 | struct picolcd_fb_cleanup_item *next; | ||
633 | }; | ||
634 | static struct picolcd_fb_cleanup_item *fb_pending; | ||
635 | DEFINE_SPINLOCK(fb_pending_lock); | ||
636 | |||
637 | static 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 | |||
660 | DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); | ||
661 | |||
662 | static 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 | |||
671 | static 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 */ |
607 | static struct fb_ops picolcdfb_ops = { | 691 | static 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) | |||
2660 | static void __exit picolcd_exit(void) | 2752 | static 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 | ||
2665 | module_init(picolcd_init); | 2761 | module_init(picolcd_init); |