staging: udlfb: revamp reference handling to insure successful shutdown
[pandora-kernel.git] / drivers / staging / udlfb / udlfb.c
index b027a1e..c9ac687 100644 (file)
@@ -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 *info, struct vm_area_struct *vma)
        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, unsigned red, unsigned green,
 /*
  * 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 *info, int user)
  *             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 *info, int user)
        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_info *info, int user)
  * 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_info *info)
 
        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_interface *interface,
 {
        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_interface *interface,
                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_interface *interface,
                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_interface *interface,
                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_interface *interface,
                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_interface *interface,
 
 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 usb_interface *interface)
        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 dlfb_data *dev)
 
        /* 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 dlfb_data *dev)
                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 dlfb_data *dev, int count, size_t size)
        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;