Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

udlfb: fix sleeping inside spinlock

If a framebuffer device is used as a console, the rendering calls
(copyarea, fillrect, imageblit) may be done with the console spinlock
held. On udlfb, these function call dlfb_handle_damage that takes a
blocking semaphore before acquiring an URB.

In order to fix the bug, this patch changes the calls copyarea, fillrect
and imageblit to offload USB work to a workqueue.

A side effect of this patch is 3x improvement in console scrolling speed
because the device doesn't have to be updated after each copyarea call.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>
Cc: Ladislav Michl <ladis@linux-mips.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>

authored by

Mikulas Patocka and committed by
Bartlomiej Zolnierkiewicz
6b11f9d8 bd86b6c5

+59 -3
+53 -3
drivers/video/fbdev/udlfb.c
··· 657 657 return 0; 658 658 } 659 659 660 + static void dlfb_init_damage(struct dlfb_data *dlfb) 661 + { 662 + dlfb->damage_x = INT_MAX; 663 + dlfb->damage_x2 = 0; 664 + dlfb->damage_y = INT_MAX; 665 + dlfb->damage_y2 = 0; 666 + } 667 + 668 + static void dlfb_damage_work(struct work_struct *w) 669 + { 670 + struct dlfb_data *dlfb = container_of(w, struct dlfb_data, damage_work); 671 + int x, x2, y, y2; 672 + 673 + spin_lock_irq(&dlfb->damage_lock); 674 + x = dlfb->damage_x; 675 + x2 = dlfb->damage_x2; 676 + y = dlfb->damage_y; 677 + y2 = dlfb->damage_y2; 678 + dlfb_init_damage(dlfb); 679 + spin_unlock_irq(&dlfb->damage_lock); 680 + 681 + if (x < x2 && y < y2) 682 + dlfb_handle_damage(dlfb, x, y, x2 - x, y2 - y); 683 + } 684 + 685 + static void dlfb_offload_damage(struct dlfb_data *dlfb, int x, int y, int width, int height) 686 + { 687 + unsigned long flags; 688 + int x2 = x + width; 689 + int y2 = y + height; 690 + 691 + if (x >= x2 || y >= y2) 692 + return; 693 + 694 + spin_lock_irqsave(&dlfb->damage_lock, flags); 695 + dlfb->damage_x = min(x, dlfb->damage_x); 696 + dlfb->damage_x2 = max(x2, dlfb->damage_x2); 697 + dlfb->damage_y = min(y, dlfb->damage_y); 698 + dlfb->damage_y2 = max(y2, dlfb->damage_y2); 699 + spin_unlock_irqrestore(&dlfb->damage_lock, flags); 700 + 701 + schedule_work(&dlfb->damage_work); 702 + } 703 + 660 704 /* 661 705 * Path triggered by usermode clients who write to filesystem 662 706 * e.g. cat filename > /dev/fb1 ··· 737 693 738 694 sys_copyarea(info, area); 739 695 740 - dlfb_handle_damage(dlfb, area->dx, area->dy, 696 + dlfb_offload_damage(dlfb, area->dx, area->dy, 741 697 area->width, area->height); 742 698 } 743 699 ··· 748 704 749 705 sys_imageblit(info, image); 750 706 751 - dlfb_handle_damage(dlfb, image->dx, image->dy, 707 + dlfb_offload_damage(dlfb, image->dx, image->dy, 752 708 image->width, image->height); 753 709 } 754 710 ··· 759 715 760 716 sys_fillrect(info, rect); 761 717 762 - dlfb_handle_damage(dlfb, rect->dx, rect->dy, rect->width, 718 + dlfb_offload_damage(dlfb, rect->dx, rect->dy, rect->width, 763 719 rect->height); 764 720 } 765 721 ··· 983 939 static void dlfb_ops_destroy(struct fb_info *info) 984 940 { 985 941 struct dlfb_data *dlfb = info->par; 942 + 943 + cancel_work_sync(&dlfb->damage_work); 986 944 987 945 if (info->cmap.len != 0) 988 946 fb_dealloc_cmap(&info->cmap); ··· 1681 1635 info->pseudo_palette = dlfb->pseudo_palette; 1682 1636 dlfb->ops = dlfb_ops; 1683 1637 info->fbops = &dlfb->ops; 1638 + 1639 + dlfb_init_damage(dlfb); 1640 + spin_lock_init(&dlfb->damage_lock); 1641 + INIT_WORK(&dlfb->damage_work, dlfb_damage_work); 1684 1642 1685 1643 INIT_LIST_HEAD(&info->modelist); 1686 1644
+6
include/video/udlfb.h
··· 48 48 int base8; 49 49 u32 pseudo_palette[256]; 50 50 int blank_mode; /*one of FB_BLANK_ */ 51 + int damage_x; 52 + int damage_y; 53 + int damage_x2; 54 + int damage_y2; 55 + spinlock_t damage_lock; 56 + struct work_struct damage_work; 51 57 struct fb_ops ops; 52 58 /* blit-only rendering path metrics, exposed through sysfs */ 53 59 atomic_t bytes_rendered; /* raw pixel-bytes driver asked to render */