[PATCH 5/11] staging: udlfb: revamp reference handling to insure successful shutdown

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Revamp reference handling and synchronization for unload/shutdown

Udlfb is a "virtual" framebuffer device that really exists on
two separate stacks: at the bottom of the framebuffer interface,
and on top of USB.  During unload, there's no guarantee which
one will tear down first. So reference counting must be solid
to handle all possibilities and not access anything once its gone.

Signed-off-by: Bernie Thompson <bernie@xxxxxxxxxxxx>
Index: b/drivers/staging/udlfb/udlfb.c
===================================================================
--- a/drivers/staging/udlfb/udlfb.c	2010-08-18 09:21:34.000000000 -0700
+++ b/drivers/staging/udlfb/udlfb.c	2010-08-18 09:28:31.000000000 -0700
@@ -25,6 +25,8 @@
 #include <linux/fb.h>
 #include <linux/vmalloc.h>
 #include <linux/slab.h>
+#include <linux/delay.h>
+
 
 #include "udlfb.h"
 
@@ -300,7 +302,6 @@ static int dlfb_ops_mmap(struct fb_info 
 	unsigned long size = vma->vm_end - vma->vm_start;
 	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 	unsigned long page, pos;
-	struct dlfb_data *dev = info->par;
 
 	dl_notice("MMAP: %lu %u\n", offset + size, info->fix.smem_len);
 
@@ -785,6 +786,7 @@ dlfb_ops_setcolreg(unsigned regno, unsig
 /*
  * It's common for several clients to have framebuffer open simultaneously.
  * e.g. both fbcon and X. Makes things interesting.
+ * Assumes caller is holding info->lock (for open and release at least)
  */
 static int dlfb_ops_open(struct fb_info *info, int user)
 {
@@ -794,10 +796,14 @@ static int dlfb_ops_open(struct fb_info 
  *		We could special case kernel mode clients (fbcon) here
  */
 
-	mutex_lock(&dev->fb_open_lock);
+	/* If the USB device is gone, we don't accept new opens */
+	if (dev->virtualized)
+		return -ENODEV;
 
 	dev->fb_count++;
 
+	kref_get(&dev->kref);
+
 #ifdef CONFIG_FB_DEFERRED_IO
 	if ((atomic_read(&dev->use_defio)) && (info->fbdefio == NULL)) {
 		/* enable defio */
@@ -809,32 +815,6 @@ static int dlfb_ops_open(struct fb_info 
 	dl_notice("open /dev/fb%d user=%d fb_info=%p count=%d\n",
 	    info->node, user, info, dev->fb_count);
 
-	mutex_unlock(&dev->fb_open_lock);
-
-	return 0;
-}
-
-static int dlfb_ops_release(struct fb_info *info, int user)
-{
-	struct dlfb_data *dev = info->par;
-
-	mutex_lock(&dev->fb_open_lock);
-
-	dev->fb_count--;
-
-#ifdef CONFIG_FB_DEFERRED_IO
-	if ((dev->fb_count == 0) && (info->fbdefio)) {
-		fb_deferred_io_cleanup(info);
-		info->fbdefio = NULL;
-		info->fbops->fb_mmap = dlfb_ops_mmap;
-	}
-#endif
-
-	dl_notice("release /dev/fb%d user=%d count=%d\n",
-		  info->node, user, dev->fb_count);
-
-	mutex_unlock(&dev->fb_open_lock);
-
 	return 0;
 }
 
@@ -843,25 +823,33 @@ static int dlfb_ops_release(struct fb_in
  * and all references to our device instance (dlfb_data) are released.
  * Every transaction must have a reference, so we know are fully spun down
  */
-static void dlfb_delete(struct kref *kref)
+static void dlfb_free(struct kref *kref)
 {
 	struct dlfb_data *dev = container_of(kref, struct dlfb_data, kref);
 
+	/* this function will wait for all in-flight urbs to complete */
+	if (dev->urbs.count > 0)
+		dlfb_free_urb_list(dev);
+
 	if (dev->backing_buffer)
 		vfree(dev->backing_buffer);
 
-	mutex_destroy(&dev->fb_open_lock);
+	kfree(dev->edid);
+
+	dl_warn("freeing dlfb_data %p\n", dev);
 
 	kfree(dev);
 }
 
-/*
- * Called by fbdev as last part of unregister_framebuffer() process
- * No new clients can open connections. Deallocate everything fb_info.
- */
-static void dlfb_ops_destroy(struct fb_info *info)
+
+static void dlfb_free_framebuffer_work(struct work_struct *work)
 {
-	struct dlfb_data *dev = info->par;
+	struct dlfb_data *dev = container_of(work, struct dlfb_data,
+					     free_framebuffer_work.work);
+	struct fb_info *info = dev->info;
+	int node = info->node;
+
+	unregister_framebuffer(info);
 
 	if (info->cmap.len != 0)
 		fb_dealloc_cmap(&info->cmap);
@@ -872,10 +860,45 @@ static void dlfb_ops_destroy(struct fb_i
 
 	fb_destroy_modelist(&info->modelist);
 
+	dev->info = 0;
+
+	/* Assume info structure is freed after this point */
 	framebuffer_release(info);
 
-	/* ref taken before register_framebuffer() for dlfb_data clients */
-	kref_put(&dev->kref, dlfb_delete);
+	dl_warn("fb_info for /dev/fb%d has been freed\n", node);
+
+	/* ref taken in probe() as part of registering framebfufer */
+	kref_put(&dev->kref, dlfb_free);
+}
+
+/*
+ * Assumes caller is holding info->lock mutex (for open and release at least)
+ */
+static int dlfb_ops_release(struct fb_info *info, int user)
+{
+	struct dlfb_data *dev = info->par;
+
+	dev->fb_count--;
+
+	/* We can't free fb_info here - fbmem will touch it when we return */
+	if (dev->virtualized && (dev->fb_count == 0))
+		schedule_delayed_work(&dev->free_framebuffer_work, HZ);
+
+#ifdef CONFIG_FB_DEFERRED_IO
+	if ((dev->fb_count == 0) && (info->fbdefio)) {
+		fb_deferred_io_cleanup(info);
+		kfree(info->fbdefio);
+		info->fbdefio = NULL;
+		info->fbops->fb_mmap = dlfb_ops_mmap;
+	}
+#endif
+
+	dl_warn("released /dev/fb%d user=%d count=%d\n",
+		  info->node, user, dev->fb_count);
+
+	kref_put(&dev->kref, dlfb_free);
+
+	return 0;
 }
 
 /*
@@ -1244,13 +1267,12 @@ static int dlfb_usb_probe(struct usb_int
 {
 	struct usb_device *usbdev;
 	struct dlfb_data *dev;
-	struct fb_info *info;
+	struct fb_info *info = 0;
 	int videomemorysize;
 	int i;
 	unsigned char *videomemory;
 	int retval = -ENOMEM;
 	struct fb_var_screeninfo *var;
-	int registered = 0;
 	u16 *pix_framebuffer;
 
 	/* usb initialization */
@@ -1277,8 +1299,6 @@ static int dlfb_usb_probe(struct usb_int
 		goto error;
 	}
 
-	mutex_init(&dev->fb_open_lock);
-
 	/* We don't register a new USB class. Our client interface is fbdev */
 
 	/* allocates framebuffer driver structure, not framebuffer memory */
@@ -1288,6 +1308,7 @@ static int dlfb_usb_probe(struct usb_int
 		dl_err("framebuffer_alloc failed\n");
 		goto error;
 	}
+
 	dev->info = info;
 	info->par = dev;
 	info->pseudo_palette = dev->pseudo_palette;
@@ -1343,6 +1364,9 @@ static int dlfb_usb_probe(struct usb_int
 		goto error;
 	}
 
+	INIT_DELAYED_WORK(&dev->free_framebuffer_work,
+			  dlfb_free_framebuffer_work);
+
 	/* ready to begin using device */
 
 #ifdef CONFIG_FB_DEFERRED_IO
@@ -1367,7 +1391,6 @@ static int dlfb_usb_probe(struct usb_int
 		dl_err("register_framebuffer failed %d\n", retval);
 		goto error;
 	}
-	registered = 1;
 
 	for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++)
 		device_create_file(info->dev, &fb_device_attrs[i]);
@@ -1383,15 +1406,25 @@ static int dlfb_usb_probe(struct usb_int
 
 error:
 	if (dev) {
-		if (registered) {
-			unregister_framebuffer(info);
-			dlfb_ops_destroy(info);
-		} else
-			kref_put(&dev->kref, dlfb_delete);
 
-		if (dev->urbs.count > 0)
-			dlfb_free_urb_list(dev);
-		kref_put(&dev->kref, dlfb_delete); /* last ref from kref_init */
+		if (info) {
+			if (info->cmap.len != 0)
+				fb_dealloc_cmap(&info->cmap);
+			if (info->monspecs.modedb)
+				fb_destroy_modedb(info->monspecs.modedb);
+			if (info->screen_base)
+				vfree(info->screen_base);
+
+			fb_destroy_modelist(&info->modelist);
+
+			framebuffer_release(info);
+		}
+
+		if (dev->backing_buffer)
+			vfree(dev->backing_buffer);
+
+		kref_put(&dev->kref, dlfb_free); /* ref for framebuffer */
+		kref_put(&dev->kref, dlfb_free); /* last ref from kref_init */
 
 		/* dev has been deallocated. Do not dereference */
 	}
@@ -1408,27 +1441,27 @@ static void dlfb_usb_disconnect(struct u
 	dev = usb_get_intfdata(interface);
 	info = dev->info;
 
-	/* when non-active we'll update virtual framebuffer, but no new urbs */
-	atomic_set(&dev->usb_active, 0);
+	dl_info("USB disconnect starting\n");
 
-	usb_set_intfdata(interface, NULL);
+	/* we virtualize until all fb clients release. Then we free */
+	dev->virtualized = true;
+
+	/* When non-active we'll update virtual framebuffer, but no new urbs */
+	atomic_set(&dev->usb_active, 0);
 
+	/* remove udlfb's sysfs interfaces */
 	for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++)
 		device_remove_file(info->dev, &fb_device_attrs[i]);
-
 	device_remove_bin_file(info->dev, &edid_attr);
 
-	/* this function will wait for all in-flight urbs to complete */
-	dlfb_free_urb_list(dev);
+	usb_set_intfdata(interface, NULL);
 
-	if (info) {
-		dl_notice("Detaching /dev/fb%d\n", info->node);
-		unregister_framebuffer(info);
-		dlfb_ops_destroy(info);
-	}
+	/* if clients still have us open, will be freed on last close */
+	if (dev->fb_count == 0)
+		schedule_delayed_work(&dev->free_framebuffer_work, 0);
 
 	/* release reference taken by kref_init in probe() */
-	kref_put(&dev->kref, dlfb_delete);
+	kref_put(&dev->kref, dlfb_free);
 
 	/* consider dlfb_data freed */
 
@@ -1450,8 +1483,6 @@ static int __init dlfb_module_init(void)
 	if (res)
 		err("usb_register failed. Error number %d", res);
 
-	printk(KERN_INFO "VMODES initialized\n");
-
 	return res;
 }
 
@@ -1503,12 +1534,12 @@ static void dlfb_free_urb_list(struct dl
 
 	/* keep waiting and freeing, until we've got 'em all */
 	while (count--) {
-		/* Timeout means a memory leak and/or fault */
-		ret = down_timeout(&dev->urbs.limit_sem, FREE_URB_TIMEOUT);
-		if (ret) {
-			BUG_ON(ret);
+
+		/* Getting interrupted means a leak, but ok at shutdown*/
+		ret = down_interruptible(&dev->urbs.limit_sem);
+		if (ret)
 			break;
-		}
+
 		spin_lock_irqsave(&dev->urbs.lock, flags);
 
 		node = dev->urbs.list.next; /* have reserved one with sem */
@@ -1526,8 +1557,6 @@ static void dlfb_free_urb_list(struct dl
 		kfree(node);
 	}
 
-	kref_put(&dev->kref, dlfb_delete);
-
 }
 
 static int dlfb_alloc_urb_list(struct dlfb_data *dev, int count, size_t size)
@@ -1577,8 +1606,6 @@ static int dlfb_alloc_urb_list(struct dl
 	dev->urbs.count = i;
 	dev->urbs.available = i;
 
-	kref_get(&dev->kref); /* released in free_render_urbs() */
-
 	dl_notice("allocated %d %d byte urbs\n", i, (int) size);
 
 	return i;
Index: b/drivers/staging/udlfb/udlfb.h
===================================================================
--- a/drivers/staging/udlfb/udlfb.h	2010-08-18 09:21:34.000000000 -0700
+++ b/drivers/staging/udlfb/udlfb.h	2010-08-18 09:22:50.000000000 -0700
@@ -38,9 +38,9 @@ struct dlfb_data {
 	struct urb_list urbs;
 	struct kref kref;
 	char *backing_buffer;
-	struct delayed_work deferred_work;
-	struct mutex fb_open_lock;
 	int fb_count;
+	bool virtualized; /* true when physical usb device not present */
+	struct delayed_work free_framebuffer_work;
 	atomic_t usb_active; /* 0 = update virtual buffer, but no usb traffic */
 	atomic_t lost_pixels; /* 1 = a render op failed. Need screen refresh */
 	atomic_t use_defio; /* 0 = rely on ioctls and blit/copy/fill rects */
@@ -89,12 +89,20 @@ struct dlfb_data {
 /* remove once this gets added to sysfs.h */
 #define __ATTR_RW(attr) __ATTR(attr, 0644, attr##_show, attr##_store)
 
+/*
+ * udlfb is both a usb device, and a framebuffer device.
+ * They may exist at the same time, but during various stages
+ * inactivity, teardown, or "virtual" operation, only one or the
+ * other will exist (one will outlive the other).  So we can't
+ * call the dev_*() macros, because we don't have a stable dev object.
+ */
 #define dl_err(format, arg...) \
-	dev_err(dev->gdev, "dlfb: " format, ## arg)
+	pr_err("udlfb: " format, ## arg)
 #define dl_warn(format, arg...) \
-	dev_warn(dev->gdev, "dlfb: " format, ## arg)
+	pr_warning("udlfb: " format, ## arg)
 #define dl_notice(format, arg...) \
-	dev_notice(dev->gdev, "dlfb: " format, ## arg)
+	pr_notice("udlfb: " format, ## arg)
 #define dl_info(format, arg...) \
-	dev_info(dev->gdev, "dlfb: " format, ## arg)
+	pr_info("udlfb: " format, ## arg)
+
 #endif


_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/devel


[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux