diff options
| -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); |
