diff options
author | Mikulas Patocka <mpatocka@redhat.com> | 2018-10-08 06:57:34 -0400 |
---|---|---|
committer | Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> | 2018-10-08 06:57:34 -0400 |
commit | 68a958a915ca912b8ce71b9eea7445996f6e681e (patch) | |
tree | 23476581d14a94613b449d7536f71a9511d6638f | |
parent | ad4366ad4831f9a42d86f19c43a482c7cb04cecb (diff) |
udlfb: handle unplug properly
The udlfb driver maintained an open count and cleaned up itself when the
count reached zero. But the console is also counted in the reference count
- so, if the user unplugged the device, the open count would not drop to
zero and the driver stayed loaded with console attached. If the user
re-plugged the adapter, it would create a device /dev/fb1, show green
screen and the access to the console would be lost.
The framebuffer subsystem has reference counting on its own - in order to
fix the unplug bug, we rely the framebuffer reference counting. When the
user unplugs the adapter, we call unregister_framebuffer unconditionally.
unregister_framebuffer will unbind the console, wait until all users stop
using the framebuffer and then call the fb_destroy method. The fb_destroy
cleans up the USB driver.
This patch makes the following changes:
* Drop dlfb->kref and rely on implicit framebuffer reference counting
instead.
* dlfb_usb_disconnect calls unregister_framebuffer, the rest of driver
cleanup is done in the function dlfb_ops_destroy. dlfb_ops_destroy will
be called by the framebuffer subsystem when no processes have the
framebuffer open or mapped.
* We don't use workqueue during initialization, but initialize directly
from dlfb_usb_probe. The workqueue could race with dlfb_usb_disconnect
and this racing would produce various kinds of memory corruption.
* We use usb_get_dev and usb_put_dev to make sure that the USB subsystem
doesn't free the device under us.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
cc: Dave Airlie <airlied@redhat.com>
Cc: Bernie Thompson <bernie@plugable.com>,
Cc: Ladislav Michl <ladis@linux-mips.org>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
-rw-r--r-- | drivers/video/fbdev/udlfb.c | 141 | ||||
-rw-r--r-- | include/video/udlfb.h | 3 |
2 files changed, 37 insertions, 107 deletions
diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index afbd6101c78e..070026a7e55a 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c | |||
@@ -916,8 +916,6 @@ static int dlfb_ops_open(struct fb_info *info, int user) | |||
916 | 916 | ||
917 | dlfb->fb_count++; | 917 | dlfb->fb_count++; |
918 | 918 | ||
919 | kref_get(&dlfb->kref); | ||
920 | |||
921 | if (fb_defio && (info->fbdefio == NULL)) { | 919 | if (fb_defio && (info->fbdefio == NULL)) { |
922 | /* enable defio at last moment if not disabled by client */ | 920 | /* enable defio at last moment if not disabled by client */ |
923 | 921 | ||
@@ -940,14 +938,17 @@ static int dlfb_ops_open(struct fb_info *info, int user) | |||
940 | return 0; | 938 | return 0; |
941 | } | 939 | } |
942 | 940 | ||
943 | /* | 941 | static void dlfb_ops_destroy(struct fb_info *info) |
944 | * Called when all client interfaces to start transactions have been disabled, | ||
945 | * and all references to our device instance (dlfb_data) are released. | ||
946 | * Every transaction must have a reference, so we know are fully spun down | ||
947 | */ | ||
948 | static void dlfb_free(struct kref *kref) | ||
949 | { | 942 | { |
950 | struct dlfb_data *dlfb = container_of(kref, struct dlfb_data, kref); | 943 | struct dlfb_data *dlfb = info->par; |
944 | |||
945 | if (info->cmap.len != 0) | ||
946 | fb_dealloc_cmap(&info->cmap); | ||
947 | if (info->monspecs.modedb) | ||
948 | fb_destroy_modedb(info->monspecs.modedb); | ||
949 | vfree(info->screen_base); | ||
950 | |||
951 | fb_destroy_modelist(&info->modelist); | ||
951 | 952 | ||
952 | while (!list_empty(&dlfb->deferred_free)) { | 953 | while (!list_empty(&dlfb->deferred_free)) { |
953 | struct dlfb_deferred_free *d = list_entry(dlfb->deferred_free.next, struct dlfb_deferred_free, list); | 954 | struct dlfb_deferred_free *d = list_entry(dlfb->deferred_free.next, struct dlfb_deferred_free, list); |
@@ -957,40 +958,13 @@ static void dlfb_free(struct kref *kref) | |||
957 | } | 958 | } |
958 | vfree(dlfb->backing_buffer); | 959 | vfree(dlfb->backing_buffer); |
959 | kfree(dlfb->edid); | 960 | kfree(dlfb->edid); |
961 | usb_put_dev(dlfb->udev); | ||
960 | kfree(dlfb); | 962 | kfree(dlfb); |
961 | } | ||
962 | |||
963 | static void dlfb_free_framebuffer(struct dlfb_data *dlfb) | ||
964 | { | ||
965 | struct fb_info *info = dlfb->info; | ||
966 | |||
967 | if (info) { | ||
968 | unregister_framebuffer(info); | ||
969 | |||
970 | if (info->cmap.len != 0) | ||
971 | fb_dealloc_cmap(&info->cmap); | ||
972 | if (info->monspecs.modedb) | ||
973 | fb_destroy_modedb(info->monspecs.modedb); | ||
974 | vfree(info->screen_base); | ||
975 | |||
976 | fb_destroy_modelist(&info->modelist); | ||
977 | |||
978 | dlfb->info = NULL; | ||
979 | |||
980 | /* Assume info structure is freed after this point */ | ||
981 | framebuffer_release(info); | ||
982 | } | ||
983 | 963 | ||
984 | /* ref taken in probe() as part of registering framebfufer */ | 964 | /* Assume info structure is freed after this point */ |
985 | kref_put(&dlfb->kref, dlfb_free); | 965 | framebuffer_release(info); |
986 | } | 966 | } |
987 | 967 | ||
988 | static void dlfb_free_framebuffer_work(struct work_struct *work) | ||
989 | { | ||
990 | struct dlfb_data *dlfb = container_of(work, struct dlfb_data, | ||
991 | free_framebuffer_work.work); | ||
992 | dlfb_free_framebuffer(dlfb); | ||
993 | } | ||
994 | /* | 968 | /* |
995 | * Assumes caller is holding info->lock mutex (for open and release at least) | 969 | * Assumes caller is holding info->lock mutex (for open and release at least) |
996 | */ | 970 | */ |
@@ -1000,10 +974,6 @@ static int dlfb_ops_release(struct fb_info *info, int user) | |||
1000 | 974 | ||
1001 | dlfb->fb_count--; | 975 | dlfb->fb_count--; |
1002 | 976 | ||
1003 | /* We can't free fb_info here - fbmem will touch it when we return */ | ||
1004 | if (dlfb->virtualized && (dlfb->fb_count == 0)) | ||
1005 | schedule_delayed_work(&dlfb->free_framebuffer_work, HZ); | ||
1006 | |||
1007 | if ((dlfb->fb_count == 0) && (info->fbdefio)) { | 977 | if ((dlfb->fb_count == 0) && (info->fbdefio)) { |
1008 | fb_deferred_io_cleanup(info); | 978 | fb_deferred_io_cleanup(info); |
1009 | kfree(info->fbdefio); | 979 | kfree(info->fbdefio); |
@@ -1013,8 +983,6 @@ static int dlfb_ops_release(struct fb_info *info, int user) | |||
1013 | 983 | ||
1014 | dev_dbg(info->dev, "release, user=%d count=%d\n", user, dlfb->fb_count); | 984 | dev_dbg(info->dev, "release, user=%d count=%d\n", user, dlfb->fb_count); |
1015 | 985 | ||
1016 | kref_put(&dlfb->kref, dlfb_free); | ||
1017 | |||
1018 | return 0; | 986 | return 0; |
1019 | } | 987 | } |
1020 | 988 | ||
@@ -1172,6 +1140,7 @@ static struct fb_ops dlfb_ops = { | |||
1172 | .fb_blank = dlfb_ops_blank, | 1140 | .fb_blank = dlfb_ops_blank, |
1173 | .fb_check_var = dlfb_ops_check_var, | 1141 | .fb_check_var = dlfb_ops_check_var, |
1174 | .fb_set_par = dlfb_ops_set_par, | 1142 | .fb_set_par = dlfb_ops_set_par, |
1143 | .fb_destroy = dlfb_ops_destroy, | ||
1175 | }; | 1144 | }; |
1176 | 1145 | ||
1177 | 1146 | ||
@@ -1615,12 +1584,13 @@ success: | |||
1615 | return true; | 1584 | return true; |
1616 | } | 1585 | } |
1617 | 1586 | ||
1618 | static void dlfb_init_framebuffer_work(struct work_struct *work); | ||
1619 | |||
1620 | static int dlfb_usb_probe(struct usb_interface *intf, | 1587 | static int dlfb_usb_probe(struct usb_interface *intf, |
1621 | const struct usb_device_id *id) | 1588 | const struct usb_device_id *id) |
1622 | { | 1589 | { |
1590 | int i; | ||
1591 | const struct device_attribute *attr; | ||
1623 | struct dlfb_data *dlfb; | 1592 | struct dlfb_data *dlfb; |
1593 | struct fb_info *info; | ||
1624 | int retval = -ENOMEM; | 1594 | int retval = -ENOMEM; |
1625 | struct usb_device *usbdev = interface_to_usbdev(intf); | 1595 | struct usb_device *usbdev = interface_to_usbdev(intf); |
1626 | 1596 | ||
@@ -1631,10 +1601,9 @@ static int dlfb_usb_probe(struct usb_interface *intf, | |||
1631 | goto error; | 1601 | goto error; |
1632 | } | 1602 | } |
1633 | 1603 | ||
1634 | kref_init(&dlfb->kref); /* matching kref_put in usb .disconnect fn */ | ||
1635 | INIT_LIST_HEAD(&dlfb->deferred_free); | 1604 | INIT_LIST_HEAD(&dlfb->deferred_free); |
1636 | 1605 | ||
1637 | dlfb->udev = usbdev; | 1606 | dlfb->udev = usb_get_dev(usbdev); |
1638 | usb_set_intfdata(intf, dlfb); | 1607 | usb_set_intfdata(intf, dlfb); |
1639 | 1608 | ||
1640 | dev_dbg(&intf->dev, "console enable=%d\n", console); | 1609 | dev_dbg(&intf->dev, "console enable=%d\n", console); |
@@ -1657,42 +1626,6 @@ static int dlfb_usb_probe(struct usb_interface *intf, | |||
1657 | } | 1626 | } |
1658 | 1627 | ||
1659 | 1628 | ||
1660 | if (!dlfb_alloc_urb_list(dlfb, WRITES_IN_FLIGHT, MAX_TRANSFER)) { | ||
1661 | retval = -ENOMEM; | ||
1662 | dev_err(&intf->dev, "unable to allocate urb list\n"); | ||
1663 | goto error; | ||
1664 | } | ||
1665 | |||
1666 | kref_get(&dlfb->kref); /* matching kref_put in free_framebuffer_work */ | ||
1667 | |||
1668 | /* We don't register a new USB class. Our client interface is dlfbev */ | ||
1669 | |||
1670 | /* Workitem keep things fast & simple during USB enumeration */ | ||
1671 | INIT_DELAYED_WORK(&dlfb->init_framebuffer_work, | ||
1672 | dlfb_init_framebuffer_work); | ||
1673 | schedule_delayed_work(&dlfb->init_framebuffer_work, 0); | ||
1674 | |||
1675 | return 0; | ||
1676 | |||
1677 | error: | ||
1678 | if (dlfb) { | ||
1679 | |||
1680 | kref_put(&dlfb->kref, dlfb_free); /* last ref from kref_init */ | ||
1681 | |||
1682 | /* dev has been deallocated. Do not dereference */ | ||
1683 | } | ||
1684 | |||
1685 | return retval; | ||
1686 | } | ||
1687 | |||
1688 | static void dlfb_init_framebuffer_work(struct work_struct *work) | ||
1689 | { | ||
1690 | int i, retval; | ||
1691 | struct fb_info *info; | ||
1692 | const struct device_attribute *attr; | ||
1693 | struct dlfb_data *dlfb = container_of(work, struct dlfb_data, | ||
1694 | init_framebuffer_work.work); | ||
1695 | |||
1696 | /* allocates framebuffer driver structure, not framebuffer memory */ | 1629 | /* allocates framebuffer driver structure, not framebuffer memory */ |
1697 | info = framebuffer_alloc(0, &dlfb->udev->dev); | 1630 | info = framebuffer_alloc(0, &dlfb->udev->dev); |
1698 | if (!info) { | 1631 | if (!info) { |
@@ -1706,17 +1639,22 @@ static void dlfb_init_framebuffer_work(struct work_struct *work) | |||
1706 | dlfb->ops = dlfb_ops; | 1639 | dlfb->ops = dlfb_ops; |
1707 | info->fbops = &dlfb->ops; | 1640 | info->fbops = &dlfb->ops; |
1708 | 1641 | ||
1642 | INIT_LIST_HEAD(&info->modelist); | ||
1643 | |||
1644 | if (!dlfb_alloc_urb_list(dlfb, WRITES_IN_FLIGHT, MAX_TRANSFER)) { | ||
1645 | retval = -ENOMEM; | ||
1646 | dev_err(&intf->dev, "unable to allocate urb list\n"); | ||
1647 | goto error; | ||
1648 | } | ||
1649 | |||
1650 | /* We don't register a new USB class. Our client interface is dlfbev */ | ||
1651 | |||
1709 | retval = fb_alloc_cmap(&info->cmap, 256, 0); | 1652 | retval = fb_alloc_cmap(&info->cmap, 256, 0); |
1710 | if (retval < 0) { | 1653 | if (retval < 0) { |
1711 | dev_err(info->device, "cmap allocation failed: %d\n", retval); | 1654 | dev_err(info->device, "cmap allocation failed: %d\n", retval); |
1712 | goto error; | 1655 | goto error; |
1713 | } | 1656 | } |
1714 | 1657 | ||
1715 | INIT_DELAYED_WORK(&dlfb->free_framebuffer_work, | ||
1716 | dlfb_free_framebuffer_work); | ||
1717 | |||
1718 | INIT_LIST_HEAD(&info->modelist); | ||
1719 | |||
1720 | retval = dlfb_setup_modes(dlfb, info, NULL, 0); | 1658 | retval = dlfb_setup_modes(dlfb, info, NULL, 0); |
1721 | if (retval != 0) { | 1659 | if (retval != 0) { |
1722 | dev_err(info->device, | 1660 | dev_err(info->device, |
@@ -1760,10 +1698,16 @@ static void dlfb_init_framebuffer_work(struct work_struct *work) | |||
1760 | dev_name(info->dev), info->var.xres, info->var.yres, | 1698 | dev_name(info->dev), info->var.xres, info->var.yres, |
1761 | ((dlfb->backing_buffer) ? | 1699 | ((dlfb->backing_buffer) ? |
1762 | info->fix.smem_len * 2 : info->fix.smem_len) >> 10); | 1700 | info->fix.smem_len * 2 : info->fix.smem_len) >> 10); |
1763 | return; | 1701 | return 0; |
1764 | 1702 | ||
1765 | error: | 1703 | error: |
1766 | dlfb_free_framebuffer(dlfb); | 1704 | if (dlfb->info) { |
1705 | dlfb_ops_destroy(dlfb->info); | ||
1706 | } else if (dlfb) { | ||
1707 | usb_put_dev(dlfb->udev); | ||
1708 | kfree(dlfb); | ||
1709 | } | ||
1710 | return retval; | ||
1767 | } | 1711 | } |
1768 | 1712 | ||
1769 | static void dlfb_usb_disconnect(struct usb_interface *intf) | 1713 | static void dlfb_usb_disconnect(struct usb_interface *intf) |
@@ -1791,20 +1735,9 @@ static void dlfb_usb_disconnect(struct usb_interface *intf) | |||
1791 | for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) | 1735 | for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) |
1792 | device_remove_file(info->dev, &fb_device_attrs[i]); | 1736 | device_remove_file(info->dev, &fb_device_attrs[i]); |
1793 | device_remove_bin_file(info->dev, &edid_attr); | 1737 | device_remove_bin_file(info->dev, &edid_attr); |
1794 | unlink_framebuffer(info); | ||
1795 | } | 1738 | } |
1796 | 1739 | ||
1797 | usb_set_intfdata(intf, NULL); | 1740 | unregister_framebuffer(info); |
1798 | dlfb->udev = NULL; | ||
1799 | |||
1800 | /* if clients still have us open, will be freed on last close */ | ||
1801 | if (dlfb->fb_count == 0) | ||
1802 | schedule_delayed_work(&dlfb->free_framebuffer_work, 0); | ||
1803 | |||
1804 | /* release reference taken by kref_init in probe() */ | ||
1805 | kref_put(&dlfb->kref, dlfb_free); | ||
1806 | |||
1807 | /* consider dlfb_data freed */ | ||
1808 | } | 1741 | } |
1809 | 1742 | ||
1810 | static struct usb_driver dlfb_driver = { | 1743 | static struct usb_driver dlfb_driver = { |
diff --git a/include/video/udlfb.h b/include/video/udlfb.h index 3abd327bada6..7d09e54ae54e 100644 --- a/include/video/udlfb.h +++ b/include/video/udlfb.h | |||
@@ -36,12 +36,9 @@ struct dlfb_data { | |||
36 | struct usb_device *udev; | 36 | struct usb_device *udev; |
37 | struct fb_info *info; | 37 | struct fb_info *info; |
38 | struct urb_list urbs; | 38 | struct urb_list urbs; |
39 | struct kref kref; | ||
40 | char *backing_buffer; | 39 | char *backing_buffer; |
41 | int fb_count; | 40 | int fb_count; |
42 | bool virtualized; /* true when physical usb device not present */ | 41 | bool virtualized; /* true when physical usb device not present */ |
43 | struct delayed_work init_framebuffer_work; | ||
44 | struct delayed_work free_framebuffer_work; | ||
45 | atomic_t usb_active; /* 0 = update virtual buffer, but no usb traffic */ | 42 | atomic_t usb_active; /* 0 = update virtual buffer, but no usb traffic */ |
46 | atomic_t lost_pixels; /* 1 = a render op failed. Need screen refresh */ | 43 | atomic_t lost_pixels; /* 1 = a render op failed. Need screen refresh */ |
47 | char *edid; /* null until we read edid from hw or get from sysfs */ | 44 | char *edid; /* null until we read edid from hw or get from sysfs */ |