From mboxrd@z Thu Jan 1 00:00:00 1970 From: Bruno =?UTF-8?B?UHLDqW1vbnQ=?= Subject: Re: [PATCH 0/7] HID: picoLCD updates Date: Thu, 9 Aug 2012 20:09:47 +0200 Message-ID: <20120809200947.2194e89f@neptune.home> References: <20120730213656.0a9f6d30@neptune.home> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: QUOTED-PRINTABLE Return-path: In-Reply-To: <20120730213656.0a9f6d30@neptune.home> Sender: linux-kernel-owner@vger.kernel.org To: Tejun Heo Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Jiri Kosina , linux-fbdev@vger.kernel.org List-Id: linux-input@vger.kernel.org Hi Tejun, As you are working on workqueues and related code, could you have a loo= k at my usage of them in combination with db_defio? The delayed memory corruptions or system reboots after unbinding/unplug= ging the PicoLCD seem very much related to workqueue used to handle the defe= rred IO to framebuffer. I think things don't get cleaned-up as they should though I'm not sure where the trouble lies. =46or ease of reading, I'm inlineing below the framebuffer related code= of PicoLCD (for complete driver after this patch series apply the whole se= ries on top of 3.5 https://lkml.org/lkml/2012/7/30/375 ) Thanks, Bruno On Mon, 30 July 2012 Bruno Pr=C3=A9mont wro= te: > This series updates picoLCD driver: > - split the driver functions into separate files which get included > depending on Kconfig selection > (implementation for CIR using RC_CORE will follow later) > - drop private framebuffer refcounting in favor of refcounting added > to fb_info some time ago > - fix various bugs issues > - disabled firmware version checking in probe() as it does not work > anymore since commit 4ea5454203d991ec85264f64f89ca8855fce69b0 > [HID: Fix race condition between driver core and ll-driver] >=20 > Note: I still get weird behavior on quick unbind/bind sequences > issued via sysfs (CONFIG_SMP=3Dn system) that are triggered by frameb= uffer > support and apparently more specifically fb_defio part of it. >=20 > Unfortunately I'm out of ideas as to how to track down the problem wh= ich > shows either as SLAB corruption (detected with SLUB debugging, e.g. >=20 > [ 6383.521833] =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > [ 6383.530020] BUG kmalloc-64 (Not tainted): Object already free > [ 6383.530020] ------------------------------------------------------= ----------------------- > [ 6383.530020]=20 > [ 6383.530020] INFO: Slab 0xdde0ea20 objects=3D51 used=3D40 fp=3D0xce= f516e0 flags=3D0x40000080 > [ 6383.530020] INFO: Object 0xcef51190 @offset=3D400 fp=3D0xcef51f50 > [ 6383.530020]=20 > [ 6383.530020] Bytes b4 cef51180: cc cc cc cc d0 12 f5 ce 5a 5a 5a 5a= 5a 5a 5a 5a ........ZZZZZZZZ > [ 6383.530020] Object cef51190: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6= b 6b 6b 6b kkkkkkkkkkkkkkkk > [ 6383.530020] Object cef511a0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6= b 6b 6b 6b kkkkkkkkkkkkkkkk > [ 6383.530020] Object cef511b0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6= b 6b 6b 6b kkkkkkkkkkkkkkkk > [ 6383.530020] Object cef511c0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6= b 6b 6b a5 kkkkkkkkkkkkkkk. > [ 6383.530020] Redzone cef511d0: bb bb bb bb = .... > [ 6383.530020] Padding cef511d8: 5a 5a 5a 5a 5a 5a 5a 5a = ZZZZZZZZ > [ 6383.530020] Pid: 1922, comm: bash Not tainted 3.5.0-jupiter-00003-= g8d858b1-dirty #2 > [ 6383.530020] Call Trace: > [ 6383.530020] [] print_trailer+0x11c/0x130 > [ 6383.530020] [] object_err+0x35/0x40 > [ 6383.530020] [] free_debug_processing+0x99/0x200 > [ 6383.530020] [] __slab_free+0x2e/0x280 > [ 6383.530020] [] ? hid_submit_out+0xa4/0x120 > [ 6383.530020] [] ? __usbhid_submit_report+0xc0/0x3c0 > [ 6383.530020] [] ? kfree+0xfa/0x110 > [ 6383.530020] [] ? picolcd_debug_out_report+0x8c4/0x8e0 [= hid_picolcd] > [ 6383.530020] [] kfree+0xfa/0x110 > [ 6383.530020] [] ? hid_submit_out+0xa4/0x120 > [ 6383.530020] [] ? hid_submit_out+0xa4/0x120 > [ 6383.530020] [] ? hid_submit_out+0xa4/0x120 > [ 6383.530020] [] hid_submit_out+0xa4/0x120 > [ 6383.530020] [] __usbhid_submit_report+0x158/0x3c0 > [ 6383.530020] [] usbhid_submit_report+0x1b/0x30 > [ 6383.530020] [] picolcd_fb_reset+0xb9/0x180 [hid_picolcd= ] > [ 6383.530020] [] picolcd_init_framebuffer+0x20d/0x2e0 [hi= d_picolcd] > [ 6383.530020] [] picolcd_probe+0x3cc/0x580 [hid_picolcd] > [ 6383.530020] [] hid_device_probe+0x67/0xf0 > [ 6383.530020] [] ? driver_sysfs_add+0x57/0x80 > [ 6383.530020] [] driver_probe_device+0xbd/0x1c0 > [ 6383.530020] [] ? hid_match_device+0x7b/0x90 > [ 6383.530020] [] driver_bind+0x75/0xd0 > [ 6383.530020] [] ? driver_unbind+0x90/0x90 > [ 6383.530020] [] drv_attr_store+0x27/0x30 > [ 6383.530020] [] sysfs_write_file+0xac/0xf0 > [ 6383.530020] [] vfs_write+0x9c/0x130 > [ 6383.530020] [] ? sys_dup3+0x11f/0x160 > [ 6383.530020] [] ? sysfs_poll+0x90/0x90 > [ 6383.530020] [] sys_write+0x3d/0x70 > [ 6383.530020] [] sysenter_do_call+0x12/0x26 > [ 6383.530020] FIX kmalloc-64: Object at 0xcef51190 not freed >=20 > or worse spontaneous reboot of the system without any trace on netcon= sole or > serial console. >=20 > echo $devid > bind; echo $devid > unbind > or > echo $devid > bind; echo $devid > unbind; sleep 0.2; echo $devid > bi= nd; echo $devid > unbind >=20 > is sufficient to trigger the above issue while waiting a few seconds = between bind and unbind > shows no sign of trouble. >=20 > Suggestions as to how to debug this and fix it are welcome! =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D driver/hid/hid-pico= lcd_fb.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D /**********************************************************************= ***** * Copyright (C) 2010-2012 by Bruno Pr=C3=A9mont * * = * * Based on Logitech G13 driver (v0.4) = * * Copyright (C) 2009 by Rick L. Vinyard, Jr. * * = * * This program is free software: you can redistribute it and/or modi= fy * * it under the terms of the GNU General Public License as published = by * * the Free Software Foundation, version 2 of the License. = * * = * * This driver is distributed in the hope that it will be useful, but= * * WITHOUT ANY WARRANTY; without even the implied warranty of = * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU = * * General Public License for more details. = * * = * * You should have received a copy of the GNU General Public License = * * along with this software. If not see . * **********************************************************************= *****/ #include #include "usbhid/usbhid.h" #include #include #include #include "hid-picolcd.h" /* Framebuffer * * The PicoLCD use a Topway LCD module of 256x64 pixel * This display area is tiled over 4 controllers with 8 tiles * each. Each tile has 8x64 pixel, each data byte representing * a 1-bit wide vertical line of the tile. * * The display can be updated at a tile granularity. * * Chip 1 Chip 2 Chip 3 Chip 4 * +----------------+----------------+----------------+----------------= + * | Tile 1 | Tile 1 | Tile 1 | Tile 1 = | * +----------------+----------------+----------------+----------------= + * | Tile 2 | Tile 2 | Tile 2 | Tile 2 = | * +----------------+----------------+----------------+----------------= + * ... * +----------------+----------------+----------------+----------------= + * | Tile 8 | Tile 8 | Tile 8 | Tile 8 = | * +----------------+----------------+----------------+----------------= + */ #define PICOLCDFB_NAME "picolcdfb" #define PICOLCDFB_WIDTH (256) #define PICOLCDFB_HEIGHT (64) #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) #define PICOLCDFB_UPDATE_RATE_LIMIT 10 #define PICOLCDFB_UPDATE_RATE_DEFAULT 2 /* Framebuffer visual structures */ static const struct fb_fix_screeninfo picolcdfb_fix =3D { .id =3D PICOLCDFB_NAME, .type =3D FB_TYPE_PACKED_PIXELS, .visual =3D FB_VISUAL_MONO01, .xpanstep =3D 0, .ypanstep =3D 0, .ywrapstep =3D 0, .line_length =3D PICOLCDFB_WIDTH / 8, .accel =3D FB_ACCEL_NONE, }; static const struct fb_var_screeninfo picolcdfb_var =3D { .xres =3D PICOLCDFB_WIDTH, .yres =3D PICOLCDFB_HEIGHT, .xres_virtual =3D PICOLCDFB_WIDTH, .yres_virtual =3D PICOLCDFB_HEIGHT, .width =3D 103, .height =3D 26, .bits_per_pixel =3D 1, .grayscale =3D 1, .red =3D { .offset =3D 0, .length =3D 1, .msb_right =3D 0, }, .green =3D { .offset =3D 0, .length =3D 1, .msb_right =3D 0, }, .blue =3D { .offset =3D 0, .length =3D 1, .msb_right =3D 0, }, .transp =3D { .offset =3D 0, .length =3D 0, .msb_right =3D 0, }, }; /* Send a given tile to PicoLCD */ static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int = tile) { struct picolcd_data *data =3D hid_get_drvdata(hdev); struct hid_report *report1 =3D picolcd_out_report(REPORT_LCD_CMD_DATA,= hdev); struct hid_report *report2 =3D picolcd_out_report(REPORT_LCD_DATA, hde= v); unsigned long flags; u8 *tdata; int i; if (!report1 || report1->maxfield !=3D 1 || !report2 || report2->maxfi= eld !=3D 1) return -ENODEV; spin_lock_irqsave(&data->lock, flags); hid_set_field(report1->field[0], 0, chip << 2); hid_set_field(report1->field[0], 1, 0x02); hid_set_field(report1->field[0], 2, 0x00); hid_set_field(report1->field[0], 3, 0x00); hid_set_field(report1->field[0], 4, 0xb8 | tile); hid_set_field(report1->field[0], 5, 0x00); hid_set_field(report1->field[0], 6, 0x00); hid_set_field(report1->field[0], 7, 0x40); hid_set_field(report1->field[0], 8, 0x00); hid_set_field(report1->field[0], 9, 0x00); hid_set_field(report1->field[0], 10, 32); hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); hid_set_field(report2->field[0], 1, 0x00); hid_set_field(report2->field[0], 2, 0x00); hid_set_field(report2->field[0], 3, 32); tdata =3D data->fb_vbitmap + (tile * 4 + chip) * 64; for (i =3D 0; i < 64; i++) if (i < 32) hid_set_field(report1->field[0], 11 + i, tdata[i]); else hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); spin_unlock_irqrestore(&data->lock, flags); return 0; } /* Translate a single tile*/ static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bp= p, int chip, int tile) { int i, b, changed =3D 0; u8 tdata[64]; u8 *vdata =3D vbitmap + (tile * 4 + chip) * 64; if (bpp =3D=3D 1) { for (b =3D 7; b >=3D 0; b--) { const u8 *bdata =3D bitmap + tile * 256 + chip * 8 + b * 32; for (i =3D 0; i < 64; i++) { tdata[i] <<=3D 1; tdata[i] |=3D (bdata[i/8] >> (i % 8)) & 0x01; } } } else if (bpp =3D=3D 8) { for (b =3D 7; b >=3D 0; b--) { const u8 *bdata =3D bitmap + (tile * 256 + chip * 8 + b * 32) * 8; for (i =3D 0; i < 64; i++) { tdata[i] <<=3D 1; tdata[i] |=3D (bdata[i] & 0x80) ? 0x01 : 0x00; } } } else { /* Oops, we should never get here! */ WARN_ON(1); return 0; } for (i =3D 0; i < 64; i++) if (tdata[i] !=3D vdata[i]) { changed =3D 1; vdata[i] =3D tdata[i]; } return changed; } void picolcd_fb_refresh(struct picolcd_data *data) { if (data->fb_info) schedule_delayed_work(&data->fb_info->deferred_work, 0); } /* Reconfigure LCD display */ int picolcd_fb_reset(struct picolcd_data *data, int clear) { struct hid_report *report =3D picolcd_out_report(REPORT_LCD_CMD, data-= >hdev); int i, j; unsigned long flags; static const u8 mapcmd[8] =3D { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x= 64, 0xc0 }; if (!report || report->maxfield !=3D 1) return -ENODEV; spin_lock_irqsave(&data->lock, flags); for (i =3D 0; i < 4; i++) { for (j =3D 0; j < report->field[0]->maxusage; j++) if (j =3D=3D 0) hid_set_field(report->field[0], j, i << 2); else if (j < sizeof(mapcmd)) hid_set_field(report->field[0], j, mapcmd[j]); else hid_set_field(report->field[0], j, 0); usbhid_submit_report(data->hdev, report, USB_DIR_OUT); } data->status |=3D PICOLCD_READY_FB; spin_unlock_irqrestore(&data->lock, flags); if (data->fb_bitmap) { if (clear) { memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE); memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); } data->fb_force =3D 1; } /* schedule first output of framebuffer */ picolcd_fb_refresh(data); return 0; } /* Update fb_vbitmap from the screen_base and send changed tiles to dev= ice */ static void picolcd_fb_update(struct fb_info *info) { int chip, tile, n; unsigned long flags; struct picolcd_data *data; mutex_lock(&info->lock); data =3D info->par; if (!data) goto out; spin_lock_irqsave(&data->lock, flags); if (!(data->status & PICOLCD_READY_FB)) { spin_unlock_irqrestore(&data->lock, flags); picolcd_fb_reset(data, 0); } else { spin_unlock_irqrestore(&data->lock, flags); } /* * Translate the framebuffer into the format needed by the PicoLCD. * See display layout above. * Do this one tile after the other and push those tiles that changed. * * Wait for our IO to complete as otherwise we might flood the queue! */ n =3D 0; for (chip =3D 0; chip < 4; chip++) for (tile =3D 0; tile < 8; tile++) if (picolcd_fb_update_tile(data->fb_vbitmap, data->fb_bitmap, data->fb_bpp, chip, tile) || data->fb_force) { n +=3D 2; if (n >=3D HID_OUTPUT_FIFO_SIZE / 2) { mutex_unlock(&info->lock); usbhid_wait_io(data->hdev); mutex_lock(&info->lock); data =3D info->par; if (!data) goto out; spin_lock_irqsave(&data->lock, flags); if (data->status & PICOLCD_FAILED) { spin_unlock_irqrestore(&data->lock, flags); goto out; } spin_unlock_irqrestore(&data->lock, flags); n =3D 0; } picolcd_fb_send_tile(data->hdev, chip, tile); } data->fb_force =3D false; if (n) { mutex_unlock(&info->lock); usbhid_wait_io(data->hdev); return; } out: mutex_unlock(&info->lock); } /* Stub to call the system default and update the image on the picoLCD = */ static void picolcd_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { if (!info->par) return; sys_fillrect(info, rect); schedule_delayed_work(&info->deferred_work, 0); } /* Stub to call the system default and update the image on the picoLCD = */ static void picolcd_fb_copyarea(struct fb_info *info, const struct fb_copyarea *area) { if (!info->par) return; sys_copyarea(info, area); schedule_delayed_work(&info->deferred_work, 0); } /* Stub to call the system default and update the image on the picoLCD = */ static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_= image *image) { if (!info->par) return; sys_imageblit(info, image); schedule_delayed_work(&info->deferred_work, 0); } /* * this is the slow path from userspace. they can seek and write to * the fb. it's inefficient to do anything less than a full screen draw */ static ssize_t picolcd_fb_write(struct fb_info *info, const char __user= *buf, size_t count, loff_t *ppos) { ssize_t ret; if (!info->par) return -ENODEV; ret =3D fb_sys_write(info, buf, count, ppos); if (ret >=3D 0) schedule_delayed_work(&info->deferred_work, 0); return ret; } static int picolcd_fb_blank(int blank, struct fb_info *info) { if (!info->par) return -ENODEV; /* We let fb notification do this for us via lcd/backlight device */ return 0; } static void picolcd_fb_destroy(struct fb_info *info) { /* make sure no work is deferred */ fb_deferred_io_cleanup(info); vfree((u8 *)info->fix.smem_start); framebuffer_release(info); printk(KERN_DEBUG "picolcd_fb_destroy(%p)\n", info); } static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct f= b_info *info) { __u32 bpp =3D var->bits_per_pixel; __u32 activate =3D var->activate; /* only allow 1/8 bit depth (8-bit is grayscale) */ *var =3D picolcdfb_var; var->activate =3D activate; if (bpp >=3D 8) { var->bits_per_pixel =3D 8; var->red.length =3D 8; var->green.length =3D 8; var->blue.length =3D 8; } else { var->bits_per_pixel =3D 1; var->red.length =3D 1; var->green.length =3D 1; var->blue.length =3D 1; } return 0; } static int picolcd_set_par(struct fb_info *info) { struct picolcd_data *data =3D info->par; u8 *tmp_fb, *o_fb; if (!data) return -ENODEV; if (info->var.bits_per_pixel =3D=3D data->fb_bpp) return 0; /* switch between 1/8 bit depths */ if (info->var.bits_per_pixel !=3D 1 && info->var.bits_per_pixel !=3D 8= ) return -EINVAL; o_fb =3D data->fb_bitmap; tmp_fb =3D kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL= ); if (!tmp_fb) return -ENOMEM; /* translate FB content to new bits-per-pixel */ if (info->var.bits_per_pixel =3D=3D 1) { int i, b; for (i =3D 0; i < PICOLCDFB_SIZE; i++) { u8 p =3D 0; for (b =3D 0; b < 8; b++) { p <<=3D 1; p |=3D o_fb[i*8+b] ? 0x01 : 0x00; } tmp_fb[i] =3D p; } memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE); info->fix.visual =3D FB_VISUAL_MONO01; info->fix.line_length =3D PICOLCDFB_WIDTH / 8; } else { int i; memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE); for (i =3D 0; i < PICOLCDFB_SIZE * 8; i++) o_fb[i] =3D tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; info->fix.visual =3D FB_VISUAL_DIRECTCOLOR; info->fix.line_length =3D PICOLCDFB_WIDTH; } kfree(tmp_fb); data->fb_bpp =3D info->var.bits_per_pixel; return 0; } /* Note this can't be const because of struct fb_info definition */ static struct fb_ops picolcdfb_ops =3D { .owner =3D THIS_MODULE, .fb_destroy =3D picolcd_fb_destroy, .fb_read =3D fb_sys_read, .fb_write =3D picolcd_fb_write, .fb_blank =3D picolcd_fb_blank, .fb_fillrect =3D picolcd_fb_fillrect, .fb_copyarea =3D picolcd_fb_copyarea, .fb_imageblit =3D picolcd_fb_imageblit, .fb_check_var =3D picolcd_fb_check_var, .fb_set_par =3D picolcd_set_par, }; /* Callback from deferred IO workqueue */ static void picolcd_fb_deferred_io(struct fb_info *info, struct list_he= ad *pagelist) { picolcd_fb_update(info); } static const struct fb_deferred_io picolcd_fb_defio =3D { .delay =3D HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, .deferred_io =3D picolcd_fb_deferred_io, }; /* * The "fb_update_rate" sysfs attribute */ static ssize_t picolcd_fb_update_rate_show(struct device *dev, struct device_attribute *attr, char *buf) { struct picolcd_data *data =3D dev_get_drvdata(dev); unsigned i, fb_update_rate =3D data->fb_update_rate; size_t ret =3D 0; for (i =3D 1; i <=3D PICOLCDFB_UPDATE_RATE_LIMIT; i++) if (ret >=3D PAGE_SIZE) break; else if (i =3D=3D fb_update_rate) ret +=3D snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); else ret +=3D snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); if (ret > 0) buf[min(ret, (size_t)PAGE_SIZE)-1] =3D '\n'; return ret; } static ssize_t picolcd_fb_update_rate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct picolcd_data *data =3D dev_get_drvdata(dev); int i; unsigned u; if (count < 1 || count > 10) return -EINVAL; i =3D sscanf(buf, "%u", &u); if (i !=3D 1) return -EINVAL; if (u > PICOLCDFB_UPDATE_RATE_LIMIT) return -ERANGE; else if (u =3D=3D 0) u =3D PICOLCDFB_UPDATE_RATE_DEFAULT; data->fb_update_rate =3D u; data->fb_info->fbdefio->delay =3D HZ / data->fb_update_rate; return count; } static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, picolcd_fb_update_rate_store); /* initialize Framebuffer device */ int picolcd_init_framebuffer(struct picolcd_data *data) { struct device *dev =3D &data->hdev->dev; struct fb_info *info =3D NULL; int i, error =3D -ENOMEM; u8 *fb_vbitmap =3D NULL; u8 *fb_bitmap =3D NULL; u32 *palette; fb_bitmap =3D vmalloc(PICOLCDFB_SIZE*8); if (fb_bitmap =3D=3D NULL) { dev_err(dev, "can't get a free page for framebuffer\n"); goto err_nomem; } fb_vbitmap =3D kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); if (fb_vbitmap =3D=3D NULL) { dev_err(dev, "can't alloc vbitmap image buffer\n"); goto err_nomem; } data->fb_update_rate =3D PICOLCDFB_UPDATE_RATE_DEFAULT; /* The extra memory is: * - 256*u32 for pseudo_palette * - struct fb_deferred_io */ info =3D framebuffer_alloc(256 * sizeof(u32) + sizeof(struct fb_deferred_io), dev); if (info =3D=3D NULL) { dev_err(dev, "failed to allocate a framebuffer\n"); goto err_nomem; } info->fbdefio =3D info->par; *info->fbdefio =3D picolcd_fb_defio; palette =3D info->par + sizeof(struct fb_deferred_io); for (i =3D 0; i < 256; i++) palette[i] =3D i > 0 && i < 16 ? 0xff : 0; info->pseudo_palette =3D palette; info->screen_base =3D (char __force __iomem *)fb_bitmap; info->fbops =3D &picolcdfb_ops; info->var =3D picolcdfb_var; info->fix =3D picolcdfb_fix; info->fix.smem_len =3D PICOLCDFB_SIZE*8; info->fix.smem_start =3D (unsigned long)fb_bitmap; info->par =3D data; info->flags =3D FBINFO_FLAG_DEFAULT; data->fb_vbitmap =3D fb_vbitmap; data->fb_bitmap =3D fb_bitmap; data->fb_bpp =3D picolcdfb_var.bits_per_pixel; error =3D picolcd_fb_reset(data, 1); if (error) { dev_err(dev, "failed to configure display\n"); goto err_cleanup; } error =3D device_create_file(dev, &dev_attr_fb_update_rate); if (error) { dev_err(dev, "failed to create sysfs attributes\n"); goto err_cleanup; } fb_deferred_io_init(info); data->fb_info =3D info; error =3D register_framebuffer(info); if (error) { dev_err(dev, "failed to register framebuffer\n"); goto err_sysfs; } /* schedule first output of framebuffer */ data->fb_force =3D 1; schedule_delayed_work(&info->deferred_work, 0); return 0; err_sysfs: fb_deferred_io_cleanup(info); device_remove_file(dev, &dev_attr_fb_update_rate); err_cleanup: data->fb_vbitmap =3D NULL; data->fb_bitmap =3D NULL; data->fb_bpp =3D 0; data->fb_info =3D NULL; err_nomem: framebuffer_release(info); vfree(fb_bitmap); kfree(fb_vbitmap); return error; } void picolcd_exit_framebuffer(struct picolcd_data *data) { struct fb_info *info =3D data->fb_info; u8 *fb_vbitmap =3D data->fb_vbitmap; if (!info) return; device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); info->par =3D NULL; unregister_framebuffer(info); data->fb_vbitmap =3D NULL; data->fb_bitmap =3D NULL; data->fb_bpp =3D 0; data->fb_info =3D NULL; kfree(fb_vbitmap); }