|
@@ -439,6 +439,9 @@ static void picolcd_fb_update(struct picolcd_data *data)
|
|
|
int chip, tile, n;
|
|
|
unsigned long flags;
|
|
|
|
|
|
+ if (!data)
|
|
|
+ return;
|
|
|
+
|
|
|
spin_lock_irqsave(&data->lock, flags);
|
|
|
if (!(data->status & PICOLCD_READY_FB)) {
|
|
|
spin_unlock_irqrestore(&data->lock, flags);
|
|
@@ -461,6 +464,8 @@ static void picolcd_fb_update(struct picolcd_data *data)
|
|
|
data->fb_bitmap, data->fb_bpp, chip, tile) ||
|
|
|
data->fb_force) {
|
|
|
n += 2;
|
|
|
+ if (!data->fb_info->par)
|
|
|
+ return; /* device lost! */
|
|
|
if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
|
|
|
usbhid_wait_io(data->hdev);
|
|
|
n = 0;
|
|
@@ -531,11 +536,23 @@ static int picolcd_fb_blank(int blank, struct fb_info *info)
|
|
|
static void picolcd_fb_destroy(struct fb_info *info)
|
|
|
{
|
|
|
struct picolcd_data *data = info->par;
|
|
|
+ u32 *ref_cnt = info->pseudo_palette;
|
|
|
+ int may_release;
|
|
|
+
|
|
|
info->par = NULL;
|
|
|
if (data)
|
|
|
data->fb_info = NULL;
|
|
|
fb_deferred_io_cleanup(info);
|
|
|
- framebuffer_release(info);
|
|
|
+
|
|
|
+ ref_cnt--;
|
|
|
+ mutex_lock(&info->lock);
|
|
|
+ (*ref_cnt)--;
|
|
|
+ may_release = !ref_cnt;
|
|
|
+ mutex_unlock(&info->lock);
|
|
|
+ if (may_release) {
|
|
|
+ framebuffer_release(info);
|
|
|
+ vfree((u8 *)info->fix.smem_start);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
{
|
|
|
struct picolcd_data *data = info->par;
|
|
|
u8 *tmp_fb, *o_fb;
|
|
|
+ if (!data)
|
|
|
+ return -ENODEV;
|
|
|
if (info->var.bits_per_pixel == data->fb_bpp)
|
|
|
return 0;
|
|
|
/* switch between 1/8 bit depths */
|
|
@@ -603,10 +622,77 @@ static int picolcd_set_par(struct fb_info *info)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* Do refcounting on our FB and cleanup per worker if FB is
|
|
|
+ * closed after unplug of our device
|
|
|
+ * (fb_release holds info->lock and still touches info after
|
|
|
+ * we return so we can't release it immediately.
|
|
|
+ */
|
|
|
+struct picolcd_fb_cleanup_item {
|
|
|
+ struct fb_info *info;
|
|
|
+ struct picolcd_fb_cleanup_item *next;
|
|
|
+};
|
|
|
+static struct picolcd_fb_cleanup_item *fb_pending;
|
|
|
+DEFINE_SPINLOCK(fb_pending_lock);
|
|
|
+
|
|
|
+static void picolcd_fb_do_cleanup(struct work_struct *data)
|
|
|
+{
|
|
|
+ struct picolcd_fb_cleanup_item *item;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ do {
|
|
|
+ spin_lock_irqsave(&fb_pending_lock, flags);
|
|
|
+ item = fb_pending;
|
|
|
+ fb_pending = item ? item->next : NULL;
|
|
|
+ spin_unlock_irqrestore(&fb_pending_lock, flags);
|
|
|
+
|
|
|
+ if (item) {
|
|
|
+ u8 *fb = (u8 *)item->info->fix.smem_start;
|
|
|
+ /* make sure we do not race against fb core when
|
|
|
+ * releasing */
|
|
|
+ mutex_lock(&item->info->lock);
|
|
|
+ mutex_unlock(&item->info->lock);
|
|
|
+ framebuffer_release(item->info);
|
|
|
+ vfree(fb);
|
|
|
+ }
|
|
|
+ } while (item);
|
|
|
+}
|
|
|
+
|
|
|
+DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup);
|
|
|
+
|
|
|
+static int picolcd_fb_open(struct fb_info *info, int u)
|
|
|
+{
|
|
|
+ u32 *ref_cnt = info->pseudo_palette;
|
|
|
+ ref_cnt--;
|
|
|
+
|
|
|
+ (*ref_cnt)++;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int picolcd_fb_release(struct fb_info *info, int u)
|
|
|
+{
|
|
|
+ u32 *ref_cnt = info->pseudo_palette;
|
|
|
+ ref_cnt--;
|
|
|
+
|
|
|
+ (*ref_cnt)++;
|
|
|
+ if (!*ref_cnt) {
|
|
|
+ unsigned long flags;
|
|
|
+ struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt;
|
|
|
+ item--;
|
|
|
+ spin_lock_irqsave(&fb_pending_lock, flags);
|
|
|
+ item->next = fb_pending;
|
|
|
+ fb_pending = item;
|
|
|
+ spin_unlock_irqrestore(&fb_pending_lock, flags);
|
|
|
+ schedule_work(&picolcd_fb_cleanup);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/* Note this can't be const because of struct fb_info definition */
|
|
|
static struct fb_ops picolcdfb_ops = {
|
|
|
.owner = THIS_MODULE,
|
|
|
.fb_destroy = picolcd_fb_destroy,
|
|
|
+ .fb_open = picolcd_fb_open,
|
|
|
+ .fb_release = picolcd_fb_release,
|
|
|
.fb_read = fb_sys_read,
|
|
|
.fb_write = picolcd_fb_write,
|
|
|
.fb_blank = picolcd_fb_blank,
|
|
@@ -708,13 +794,13 @@ static int picolcd_init_framebuffer(struct picolcd_data *data)
|
|
|
* - u32 for ref_count
|
|
|
* - 256*u32 for pseudo_palette
|
|
|
*/
|
|
|
- info = framebuffer_alloc(257 * sizeof(u32), dev);
|
|
|
+ info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev);
|
|
|
if (info == NULL) {
|
|
|
dev_err(dev, "failed to allocate a framebuffer\n");
|
|
|
goto err_nomem;
|
|
|
}
|
|
|
|
|
|
- palette = info->par;
|
|
|
+ palette = info->par + sizeof(struct picolcd_fb_cleanup_item);
|
|
|
*palette = 1;
|
|
|
palette++;
|
|
|
for (i = 0; i < 256; i++)
|
|
@@ -775,18 +861,17 @@ static void picolcd_exit_framebuffer(struct picolcd_data *data)
|
|
|
{
|
|
|
struct fb_info *info = data->fb_info;
|
|
|
u8 *fb_vbitmap = data->fb_vbitmap;
|
|
|
- u8 *fb_bitmap = data->fb_bitmap;
|
|
|
|
|
|
if (!info)
|
|
|
return;
|
|
|
|
|
|
+ info->par = NULL;
|
|
|
+ device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
|
|
|
+ unregister_framebuffer(info);
|
|
|
data->fb_vbitmap = NULL;
|
|
|
data->fb_bitmap = NULL;
|
|
|
data->fb_bpp = 0;
|
|
|
data->fb_info = NULL;
|
|
|
- device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
|
|
|
- unregister_framebuffer(info);
|
|
|
- vfree(fb_bitmap);
|
|
|
kfree(fb_vbitmap);
|
|
|
}
|
|
|
|
|
@@ -2603,6 +2688,13 @@ static void picolcd_remove(struct hid_device *hdev)
|
|
|
spin_lock_irqsave(&data->lock, flags);
|
|
|
data->status |= PICOLCD_FAILED;
|
|
|
spin_unlock_irqrestore(&data->lock, flags);
|
|
|
+#ifdef CONFIG_HID_PICOLCD_FB
|
|
|
+ /* short-circuit FB as early as possible in order to
|
|
|
+ * avoid long delays if we host console.
|
|
|
+ */
|
|
|
+ if (data->fb_info)
|
|
|
+ data->fb_info->par = NULL;
|
|
|
+#endif
|
|
|
|
|
|
picolcd_exit_devfs(data);
|
|
|
device_remove_file(&hdev->dev, &dev_attr_operation_mode);
|
|
@@ -2660,6 +2752,10 @@ static int __init picolcd_init(void)
|
|
|
static void __exit picolcd_exit(void)
|
|
|
{
|
|
|
hid_unregister_driver(&picolcd_driver);
|
|
|
+#ifdef CONFIG_HID_PICOLCD_FB
|
|
|
+ flush_scheduled_work();
|
|
|
+ WARN_ON(fb_pending);
|
|
|
+#endif
|
|
|
}
|
|
|
|
|
|
module_init(picolcd_init);
|