fbcon: use lock_fb_info in fbcon_open/release
[platform/kernel/linux-starfive.git] / drivers / video / fbdev / core / fbcon.c
index 2fc1b80..cc960bf 100644 (file)
@@ -110,6 +110,18 @@ static struct fbcon_display fb_display[MAX_NR_CONSOLES];
 static signed char con2fb_map[MAX_NR_CONSOLES];
 static signed char con2fb_map_boot[MAX_NR_CONSOLES];
 
+static struct fb_info *fbcon_info_from_console(int console)
+{
+       WARN_CONSOLE_UNLOCKED();
+
+       /*
+        * Note that only con2fb_map is protected by the console lock,
+        * registered_fb is protected by a separate mutex. This lookup can
+        * therefore race.
+        */
+       return registered_fb[con2fb_map[console]];
+}
+
 static int logo_lines;
 /* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO
    enums.  */
@@ -163,39 +175,20 @@ static int fbcon_cursor_noblink;
  *  Interface used by the world
  */
 
-static const char *fbcon_startup(void);
-static void fbcon_init(struct vc_data *vc, int init);
-static void fbcon_deinit(struct vc_data *vc);
-static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
-                       int width);
-static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos);
-static void fbcon_putcs(struct vc_data *vc, const unsigned short *s,
-                       int count, int ypos, int xpos);
 static void fbcon_clear_margins(struct vc_data *vc, int bottom_only);
-static void fbcon_cursor(struct vc_data *vc, int mode);
-static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
-                       int height, int width);
-static int fbcon_switch(struct vc_data *vc);
-static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch);
 static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table);
 
 /*
  *  Internal routines
  */
-static __inline__ void ywrap_up(struct vc_data *vc, int count);
-static __inline__ void ywrap_down(struct vc_data *vc, int count);
-static __inline__ void ypan_up(struct vc_data *vc, int count);
-static __inline__ void ypan_down(struct vc_data *vc, int count);
-static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx,
-                           int dy, int dx, int height, int width, u_int y_break);
 static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
                           int unit);
 static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
                              int line, int count, int dy);
 static void fbcon_modechanged(struct fb_info *info);
 static void fbcon_set_all_vcs(struct fb_info *info);
-static void fbcon_start(void);
 static void fbcon_exit(void);
+
 static struct device *fbcon_device;
 
 #ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
@@ -218,7 +211,7 @@ static void fbcon_rotate(struct fb_info *info, u32 rotate)
        if (!ops || ops->currcon == -1)
                return;
 
-       fb_info = registered_fb[con2fb_map[ops->currcon]];
+       fb_info = fbcon_info_from_console(ops->currcon);
 
        if (info == fb_info) {
                struct fbcon_display *p = &fb_display[ops->currcon];
@@ -245,7 +238,7 @@ static void fbcon_rotate_all(struct fb_info *info, u32 rotate)
        for (i = first_fb_vc; i <= last_fb_vc; i++) {
                vc = vc_cons[i].d;
                if (!vc || vc->vc_mode != KD_TEXT ||
-                   registered_fb[con2fb_map[i]] != info)
+                   fbcon_info_from_console(i) != info)
                        continue;
 
                p = &fb_display[vc->vc_num];
@@ -357,8 +350,8 @@ static int get_color(struct vc_data *vc, struct fb_info *info,
 
 static void fb_flashcursor(struct work_struct *work)
 {
-       struct fb_info *info = container_of(work, struct fb_info, queue);
-       struct fbcon_ops *ops = info->fbcon_par;
+       struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work);
+       struct fb_info *info;
        struct vc_data *vc = NULL;
        int c;
        int mode;
@@ -371,11 +364,14 @@ static void fb_flashcursor(struct work_struct *work)
        if (ret == 0)
                return;
 
-       if (ops && ops->currcon != -1)
+       /* protected by console_lock */
+       info = ops->info;
+
+       if (ops->currcon != -1)
                vc = vc_cons[ops->currcon].d;
 
        if (!vc || !con_is_visible(vc) ||
-           registered_fb[con2fb_map[vc->vc_num]] != info ||
+           fbcon_info_from_console(vc->vc_num) != info ||
            vc->vc_deccm != 1) {
                console_unlock();
                return;
@@ -387,42 +383,25 @@ static void fb_flashcursor(struct work_struct *work)
        ops->cursor(vc, info, mode, get_color(vc, info, c, 1),
                    get_color(vc, info, c, 0));
        console_unlock();
-}
-
-static void cursor_timer_handler(struct timer_list *t)
-{
-       struct fbcon_ops *ops = from_timer(ops, t, cursor_timer);
-       struct fb_info *info = ops->info;
 
-       queue_work(system_power_efficient_wq, &info->queue);
-       mod_timer(&ops->cursor_timer, jiffies + ops->cur_blink_jiffies);
+       queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+                          ops->cur_blink_jiffies);
 }
 
-static void fbcon_add_cursor_timer(struct fb_info *info)
+static void fbcon_add_cursor_work(struct fb_info *info)
 {
        struct fbcon_ops *ops = info->fbcon_par;
 
-       if ((!info->queue.func || info->queue.func == fb_flashcursor) &&
-           !(ops->flags & FBCON_FLAGS_CURSOR_TIMER) &&
-           !fbcon_cursor_noblink) {
-               if (!info->queue.func)
-                       INIT_WORK(&info->queue, fb_flashcursor);
-
-               timer_setup(&ops->cursor_timer, cursor_timer_handler, 0);
-               mod_timer(&ops->cursor_timer, jiffies + ops->cur_blink_jiffies);
-               ops->flags |= FBCON_FLAGS_CURSOR_TIMER;
-       }
+       if (!fbcon_cursor_noblink)
+               queue_delayed_work(system_power_efficient_wq, &ops->cursor_work,
+                                  ops->cur_blink_jiffies);
 }
 
-static void fbcon_del_cursor_timer(struct fb_info *info)
+static void fbcon_del_cursor_work(struct fb_info *info)
 {
        struct fbcon_ops *ops = info->fbcon_par;
 
-       if (info->queue.func == fb_flashcursor &&
-           ops->flags & FBCON_FLAGS_CURSOR_TIMER) {
-               del_timer_sync(&ops->cursor_timer);
-               ops->flags &= ~FBCON_FLAGS_CURSOR_TIMER;
-       }
+       cancel_delayed_work_sync(&ops->cursor_work);
 }
 
 #ifndef MODULE
@@ -703,87 +682,91 @@ static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
 
 #endif /* CONFIG_MISC_TILEBLITTING */
 
+static void fbcon_release(struct fb_info *info)
+{
+       lock_fb_info(info);
+       if (info->fbops->fb_release)
+               info->fbops->fb_release(info, 0);
+       unlock_fb_info(info);
 
-static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info,
-                                 int unit, int oldidx)
+       module_put(info->fbops->owner);
+}
+
+static int fbcon_open(struct fb_info *info)
 {
-       struct fbcon_ops *ops = NULL;
-       int err = 0;
+       struct fbcon_ops *ops;
 
        if (!try_module_get(info->fbops->owner))
-               err = -ENODEV;
+               return -ENODEV;
 
-       if (!err && info->fbops->fb_open &&
-           info->fbops->fb_open(info, 0))
-               err = -ENODEV;
+       lock_fb_info(info);
+       if (info->fbops->fb_open &&
+           info->fbops->fb_open(info, 0)) {
+               unlock_fb_info(info);
+               module_put(info->fbops->owner);
+               return -ENODEV;
+       }
+       unlock_fb_info(info);
 
-       if (!err) {
-               ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
-               if (!ops)
-                       err = -ENOMEM;
+       ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
+       if (!ops) {
+               fbcon_release(info);
+               return -ENOMEM;
        }
 
-       if (!err) {
-               ops->cur_blink_jiffies = HZ / 5;
-               ops->info = info;
-               info->fbcon_par = ops;
+       INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor);
+       ops->info = info;
+       info->fbcon_par = ops;
+       ops->cur_blink_jiffies = HZ / 5;
 
-               if (vc)
-                       set_blitting_type(vc, info);
-       }
+       return 0;
+}
 
-       if (err) {
-               con2fb_map[unit] = oldidx;
-               module_put(info->fbops->owner);
-       }
+static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info,
+                                 int unit)
+{
+       int err;
+
+       err = fbcon_open(info);
+       if (err)
+               return err;
+
+       if (vc)
+               set_blitting_type(vc, info);
 
        return err;
 }
 
-static int con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo,
-                                 struct fb_info *newinfo, int unit,
-                                 int oldidx, int found)
+static void con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo,
+                                  struct fb_info *newinfo)
 {
        struct fbcon_ops *ops = oldinfo->fbcon_par;
-       int err = 0, ret;
-
-       if (oldinfo->fbops->fb_release &&
-           oldinfo->fbops->fb_release(oldinfo, 0)) {
-               con2fb_map[unit] = oldidx;
-               if (!found && newinfo->fbops->fb_release)
-                       newinfo->fbops->fb_release(newinfo, 0);
-               if (!found)
-                       module_put(newinfo->fbops->owner);
-               err = -ENODEV;
-       }
-
-       if (!err) {
-               fbcon_del_cursor_timer(oldinfo);
-               kfree(ops->cursor_state.mask);
-               kfree(ops->cursor_data);
-               kfree(ops->cursor_src);
-               kfree(ops->fontbuffer);
-               kfree(oldinfo->fbcon_par);
-               oldinfo->fbcon_par = NULL;
-               module_put(oldinfo->fbops->owner);
-               /*
-                 If oldinfo and newinfo are driving the same hardware,
-                 the fb_release() method of oldinfo may attempt to
-                 restore the hardware state.  This will leave the
-                 newinfo in an undefined state. Thus, a call to
-                 fb_set_par() may be needed for the newinfo.
-               */
-               if (newinfo && newinfo->fbops->fb_set_par) {
-                       ret = newinfo->fbops->fb_set_par(newinfo);
+       int ret;
 
-                       if (ret)
-                               printk(KERN_ERR "con2fb_release_oldinfo: "
-                                       "detected unhandled fb_set_par error, "
-                                       "error code %d\n", ret);
-               }
-       }
+       fbcon_release(oldinfo);
 
-       return err;
+       fbcon_del_cursor_work(oldinfo);
+       kfree(ops->cursor_state.mask);
+       kfree(ops->cursor_data);
+       kfree(ops->cursor_src);
+       kfree(ops->fontbuffer);
+       kfree(oldinfo->fbcon_par);
+       oldinfo->fbcon_par = NULL;
+       /*
+         If oldinfo and newinfo are driving the same hardware,
+         the fb_release() method of oldinfo may attempt to
+         restore the hardware state.  This will leave the
+         newinfo in an undefined state. Thus, a call to
+         fb_set_par() may be needed for the newinfo.
+       */
+       if (newinfo && newinfo->fbops->fb_set_par) {
+               ret = newinfo->fbops->fb_set_par(newinfo);
+
+               if (ret)
+                       printk(KERN_ERR "con2fb_release_oldinfo: "
+                               "detected unhandled fb_set_par error, "
+                               "error code %d\n", ret);
+       }
 }
 
 static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
@@ -794,7 +777,7 @@ static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
 
        ops->currcon = fg_console;
 
-       if (info->fbops->fb_set_par && !(ops->flags & FBCON_FLAGS_INIT)) {
+       if (info->fbops->fb_set_par && !ops->initialized) {
                ret = info->fbops->fb_set_par(info);
 
                if (ret)
@@ -803,14 +786,14 @@ static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
                                "error code %d\n", ret);
        }
 
-       ops->flags |= FBCON_FLAGS_INIT;
+       ops->initialized = true;
        ops->graphics = 0;
        fbcon_set_disp(info, &info->var, unit);
 
        if (show_logo) {
                struct vc_data *fg_vc = vc_cons[fg_console].d;
                struct fb_info *fg_info =
-                       registered_fb[con2fb_map[fg_console]];
+                       fbcon_info_from_console(fg_console);
 
                fbcon_prepare_logo(fg_vc, fg_info, fg_vc->vc_cols,
                                   fg_vc->vc_rows, fg_vc->vc_cols,
@@ -837,7 +820,7 @@ static int set_con2fb_map(int unit, int newidx, int user)
        int oldidx = con2fb_map[unit];
        struct fb_info *info = registered_fb[newidx];
        struct fb_info *oldinfo = NULL;
-       int found, err = 0;
+       int found, err = 0, show_logo;
 
        WARN_CONSOLE_UNLOCKED();
 
@@ -857,27 +840,26 @@ static int set_con2fb_map(int unit, int newidx, int user)
 
        found = search_fb_in_map(newidx);
 
-       con2fb_map[unit] = newidx;
-       if (!err && !found)
-               err = con2fb_acquire_newinfo(vc, info, unit, oldidx);
+       if (!err && !found) {
+               err = con2fb_acquire_newinfo(vc, info, unit);
+               if (!err)
+                       con2fb_map[unit] = newidx;
+       }
 
        /*
         * If old fb is not mapped to any of the consoles,
         * fbcon should release it.
         */
        if (!err && oldinfo && !search_fb_in_map(oldidx))
-               err = con2fb_release_oldinfo(vc, oldinfo, info, unit, oldidx,
-                                            found);
+               con2fb_release_oldinfo(vc, oldinfo, info);
 
-       if (!err) {
-               int show_logo = (fg_console == 0 && !user &&
-                                logo_shown != FBCON_LOGO_DONTSHOW);
+       show_logo = (fg_console == 0 && !user &&
+                        logo_shown != FBCON_LOGO_DONTSHOW);
 
-               if (!found)
-                       fbcon_add_cursor_timer(info);
-               con2fb_map_boot[unit] = newidx;
-               con2fb_init_display(vc, info, unit, show_logo);
-       }
+       if (!found)
+               fbcon_add_cursor_work(info);
+       con2fb_map_boot[unit] = newidx;
+       con2fb_init_display(vc, info, unit, show_logo);
 
        if (!search_fb_in_map(info_idx))
                info_idx = newidx;
@@ -938,7 +920,6 @@ static const char *fbcon_startup(void)
        struct fbcon_display *p = &fb_display[fg_console];
        struct vc_data *vc = vc_cons[fg_console].d;
        const struct font_desc *font = NULL;
-       struct module *owner;
        struct fb_info *info = NULL;
        struct fbcon_ops *ops;
        int rows, cols;
@@ -951,32 +932,19 @@ static const char *fbcon_startup(void)
                return display_desc;
        /*
         * Instead of blindly using registered_fb[0], we use info_idx, set by
-        * fb_console_init();
+        * fbcon_fb_registered();
         */
        info = registered_fb[info_idx];
        if (!info)
                return NULL;
        
-       owner = info->fbops->owner;
-       if (!try_module_get(owner))
+       if (fbcon_open(info))
                return NULL;
-       if (info->fbops->fb_open && info->fbops->fb_open(info, 0)) {
-               module_put(owner);
-               return NULL;
-       }
-
-       ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL);
-       if (!ops) {
-               module_put(owner);
-               return NULL;
-       }
 
+       ops = info->fbcon_par;
        ops->currcon = -1;
        ops->graphics = 1;
        ops->cur_rotate = -1;
-       ops->cur_blink_jiffies = HZ / 5;
-       ops->info = info;
-       info->fbcon_par = ops;
 
        p->con_rotate = initial_rotation;
        if (p->con_rotate == -1)
@@ -1013,7 +981,7 @@ static const char *fbcon_startup(void)
                 info->var.yres,
                 info->var.bits_per_pixel);
 
-       fbcon_add_cursor_timer(info);
+       fbcon_add_cursor_work(info);
        return display_desc;
 }
 
@@ -1033,7 +1001,7 @@ static void fbcon_init(struct vc_data *vc, int init)
        if (con2fb_map[vc->vc_num] == -1)
                con2fb_map[vc->vc_num] = info_idx;
 
-       info = registered_fb[con2fb_map[vc->vc_num]];
+       info = fbcon_info_from_console(vc->vc_num);
 
        if (logo_shown < 0 && console_loglevel <= CONSOLE_LOGLEVEL_QUIET)
                logo_shown = FBCON_LOGO_DONTSHOW;
@@ -1046,7 +1014,7 @@ static void fbcon_init(struct vc_data *vc, int init)
                return;
 
        if (!info->fbcon_par)
-               con2fb_acquire_newinfo(vc, info, vc->vc_num, -1);
+               con2fb_acquire_newinfo(vc, info, vc->vc_num);
 
        /* If we are not the first console on this
           fb, copy the font from that console */
@@ -1120,8 +1088,7 @@ static void fbcon_init(struct vc_data *vc, int init)
         * We need to do it in fbcon_init() to prevent screen corruption.
         */
        if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
-               if (info->fbops->fb_set_par &&
-                   !(ops->flags & FBCON_FLAGS_INIT)) {
+               if (info->fbops->fb_set_par && !ops->initialized) {
                        ret = info->fbops->fb_set_par(info);
 
                        if (ret)
@@ -1130,7 +1097,7 @@ static void fbcon_init(struct vc_data *vc, int init)
                                        "error code %d\n", ret);
                }
 
-               ops->flags |= FBCON_FLAGS_INIT;
+               ops->initialized = true;
        }
 
        ops->graphics = 0;
@@ -1201,9 +1168,9 @@ static void fbcon_deinit(struct vc_data *vc)
                goto finished;
 
        if (con_is_visible(vc))
-               fbcon_del_cursor_timer(info);
+               fbcon_del_cursor_work(info);
 
-       ops->flags &= ~FBCON_FLAGS_INIT;
+       ops->initialized = false;
 finished:
 
        fbcon_free_font(p, free_font);
@@ -1250,7 +1217,7 @@ finished:
 static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
                        int width)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
 
        struct fbcon_display *p = &fb_display[vc->vc_num];
@@ -1288,7 +1255,7 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
 static void fbcon_putcs(struct vc_data *vc, const unsigned short *s,
                        int count, int ypos, int xpos)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_display *p = &fb_display[vc->vc_num];
        struct fbcon_ops *ops = info->fbcon_par;
 
@@ -1308,7 +1275,7 @@ static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos)
 
 static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
 
        if (!fbcon_is_inactive(vc, info))
@@ -1317,7 +1284,7 @@ static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
 
 static void fbcon_cursor(struct vc_data *vc, int mode)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        int c = scr_readw((u16 *) vc->vc_pos);
 
@@ -1327,9 +1294,9 @@ static void fbcon_cursor(struct vc_data *vc, int mode)
                return;
 
        if (vc->vc_cursor_type & CUR_SW)
-               fbcon_del_cursor_timer(info);
+               fbcon_del_cursor_work(info);
        else
-               fbcon_add_cursor_timer(info);
+               fbcon_add_cursor_work(info);
 
        ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1;
 
@@ -1411,7 +1378,7 @@ static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
 
 static __inline__ void ywrap_up(struct vc_data *vc, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
 
@@ -1430,7 +1397,7 @@ static __inline__ void ywrap_up(struct vc_data *vc, int count)
 
 static __inline__ void ywrap_down(struct vc_data *vc, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
 
@@ -1449,7 +1416,7 @@ static __inline__ void ywrap_down(struct vc_data *vc, int count)
 
 static __inline__ void ypan_up(struct vc_data *vc, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_display *p = &fb_display[vc->vc_num];
        struct fbcon_ops *ops = info->fbcon_par;
 
@@ -1473,7 +1440,7 @@ static __inline__ void ypan_up(struct vc_data *vc, int count)
 
 static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
 
@@ -1497,7 +1464,7 @@ static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count)
 
 static __inline__ void ypan_down(struct vc_data *vc, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_display *p = &fb_display[vc->vc_num];
        struct fbcon_ops *ops = info->fbcon_par;
 
@@ -1521,7 +1488,7 @@ static __inline__ void ypan_down(struct vc_data *vc, int count)
 
 static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
 
@@ -1682,10 +1649,75 @@ static void fbcon_redraw(struct vc_data *vc, struct fbcon_display *p,
        }
 }
 
+static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx,
+                           int dy, int dx, int height, int width, u_int y_break)
+{
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+       struct fbcon_ops *ops = info->fbcon_par;
+       u_int b;
+
+       if (sy < y_break && sy + height > y_break) {
+               b = y_break - sy;
+               if (dy < sy) {  /* Avoid trashing self */
+                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
+                                       y_break);
+                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
+                                       height - b, width, y_break);
+               } else {
+                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
+                                       height - b, width, y_break);
+                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
+                                       y_break);
+               }
+               return;
+       }
+
+       if (dy < y_break && dy + height > y_break) {
+               b = y_break - dy;
+               if (dy < sy) {  /* Avoid trashing self */
+                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
+                                       y_break);
+                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
+                                       height - b, width, y_break);
+               } else {
+                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
+                                       height - b, width, y_break);
+                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
+                                       y_break);
+               }
+               return;
+       }
+       ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx,
+                  height, width);
+}
+
+static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
+                       int height, int width)
+{
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
+       struct fbcon_display *p = &fb_display[vc->vc_num];
+
+       if (fbcon_is_inactive(vc, info))
+               return;
+
+       if (!width || !height)
+               return;
+
+       /*  Split blits that cross physical y_wrap case.
+        *  Pathological case involves 4 blits, better to use recursive
+        *  code rather than unrolled case
+        *
+        *  Recursive invocations don't need to erase the cursor over and
+        *  over again, so we use fbcon_bmove_rec()
+        */
+       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width,
+                       p->vrows - p->yscroll);
+}
+
 static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
                enum con_scroll dir, unsigned int count)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_display *p = &fb_display[vc->vc_num];
        int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK;
 
@@ -1882,71 +1914,6 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 }
 
 
-static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
-                       int height, int width)
-{
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
-       struct fbcon_display *p = &fb_display[vc->vc_num];
-
-       if (fbcon_is_inactive(vc, info))
-               return;
-
-       if (!width || !height)
-               return;
-
-       /*  Split blits that cross physical y_wrap case.
-        *  Pathological case involves 4 blits, better to use recursive
-        *  code rather than unrolled case
-        *
-        *  Recursive invocations don't need to erase the cursor over and
-        *  over again, so we use fbcon_bmove_rec()
-        */
-       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width,
-                       p->vrows - p->yscroll);
-}
-
-static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx,
-                           int dy, int dx, int height, int width, u_int y_break)
-{
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
-       struct fbcon_ops *ops = info->fbcon_par;
-       u_int b;
-
-       if (sy < y_break && sy + height > y_break) {
-               b = y_break - sy;
-               if (dy < sy) {  /* Avoid trashing self */
-                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
-                                       y_break);
-                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
-                                       height - b, width, y_break);
-               } else {
-                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
-                                       height - b, width, y_break);
-                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
-                                       y_break);
-               }
-               return;
-       }
-
-       if (dy < y_break && dy + height > y_break) {
-               b = y_break - dy;
-               if (dy < sy) {  /* Avoid trashing self */
-                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
-                                       y_break);
-                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
-                                       height - b, width, y_break);
-               } else {
-                       fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
-                                       height - b, width, y_break);
-                       fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
-                                       y_break);
-               }
-               return;
-       }
-       ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx,
-                  height, width);
-}
-
 static void updatescrollmode_accel(struct fbcon_display *p,
                                        struct fb_info *info,
                                        struct vc_data *vc)
@@ -2015,7 +1982,7 @@ static void updatescrollmode(struct fbcon_display *p,
 static int fbcon_resize(struct vc_data *vc, unsigned int width, 
                        unsigned int height, unsigned int user)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
        struct fb_var_screeninfo var = info->var;
@@ -2084,7 +2051,7 @@ static int fbcon_switch(struct vc_data *vc)
        struct fb_var_screeninfo var;
        int i, ret, prev_console;
 
-       info = registered_fb[con2fb_map[vc->vc_num]];
+       info = fbcon_info_from_console(vc->vc_num);
        ops = info->fbcon_par;
 
        if (logo_shown >= 0) {
@@ -2098,7 +2065,7 @@ static int fbcon_switch(struct vc_data *vc)
 
        prev_console = ops->currcon;
        if (prev_console != -1)
-               old_info = registered_fb[con2fb_map[prev_console]];
+               old_info = fbcon_info_from_console(prev_console);
        /*
         * FIXME: If we have multiple fbdev's loaded, we need to
         * update all info->currcon.  Perhaps, we can place this
@@ -2139,14 +2106,14 @@ static int fbcon_switch(struct vc_data *vc)
                }
 
                if (old_info != info)
-                       fbcon_del_cursor_timer(old_info);
+                       fbcon_del_cursor_work(old_info);
        }
 
        if (fbcon_is_inactive(vc, info) ||
            ops->blank_state != FB_BLANK_UNBLANK)
-               fbcon_del_cursor_timer(info);
+               fbcon_del_cursor_work(info);
        else
-               fbcon_add_cursor_timer(info);
+               fbcon_add_cursor_work(info);
 
        set_blitting_type(vc, info);
        ops->cursor_reset = 1;
@@ -2221,7 +2188,7 @@ static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
 
 static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
 
        if (mode_switch) {
@@ -2254,16 +2221,16 @@ static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch)
 
        if (mode_switch || fbcon_is_inactive(vc, info) ||
            ops->blank_state != FB_BLANK_UNBLANK)
-               fbcon_del_cursor_timer(info);
+               fbcon_del_cursor_work(info);
        else
-               fbcon_add_cursor_timer(info);
+               fbcon_add_cursor_work(info);
 
        return 0;
 }
 
 static int fbcon_debug_enter(struct vc_data *vc)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
 
        ops->save_graphics = ops->graphics;
@@ -2276,7 +2243,7 @@ static int fbcon_debug_enter(struct vc_data *vc)
 
 static int fbcon_debug_leave(struct vc_data *vc)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
 
        ops->graphics = ops->save_graphics;
@@ -2412,7 +2379,7 @@ static void set_vc_hi_font(struct vc_data *vc, bool set)
 static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
                             const u8 * data, int userfont)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        struct fbcon_ops *ops = info->fbcon_par;
        struct fbcon_display *p = &fb_display[vc->vc_num];
        int resize;
@@ -2466,7 +2433,7 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
 static int fbcon_set_font(struct vc_data *vc, struct console_font *font,
                          unsigned int flags)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        unsigned charcount = font->charcount;
        int w = font->width;
        int h = font->height;
@@ -2530,7 +2497,7 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font,
 
 static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, char *name)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        const struct font_desc *f;
 
        if (!name)
@@ -2554,7 +2521,7 @@ static struct fb_cmap palette_cmap = {
 
 static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table)
 {
-       struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
+       struct fb_info *info = fbcon_info_from_console(vc->vc_num);
        int i, j, k, depth;
        u8 val;
 
@@ -2670,7 +2637,7 @@ static void fbcon_modechanged(struct fb_info *info)
                return;
        vc = vc_cons[ops->currcon].d;
        if (vc->vc_mode != KD_TEXT ||
-           registered_fb[con2fb_map[ops->currcon]] != info)
+           fbcon_info_from_console(ops->currcon) != info)
                return;
 
        p = &fb_display[vc->vc_num];
@@ -2710,7 +2677,7 @@ static void fbcon_set_all_vcs(struct fb_info *info)
        for (i = first_fb_vc; i <= last_fb_vc; i++) {
                vc = vc_cons[i].d;
                if (!vc || vc->vc_mode != KD_TEXT ||
-                   registered_fb[con2fb_map[i]] != info)
+                   fbcon_info_from_console(i) != info)
                        continue;
 
                if (con_is_visible(vc)) {
@@ -2786,7 +2753,7 @@ static inline void fbcon_unbind(void) {}
 /* called with console_lock held */
 void fbcon_fb_unbind(struct fb_info *info)
 {
-       int i, new_idx = -1, ret = 0;
+       int i, new_idx = -1;
        int idx = info->node;
 
        WARN_CONSOLE_UNLOCKED();
@@ -2820,13 +2787,8 @@ void fbcon_fb_unbind(struct fb_info *info)
                        if (con2fb_map[i] == idx) {
                                con2fb_map[i] = -1;
                                if (!search_fb_in_map(idx)) {
-                                       ret = con2fb_release_oldinfo(vc_cons[i].d,
-                                                                    info, NULL, i,
-                                                                    idx, 0);
-                                       if (ret) {
-                                               con2fb_map[i] = idx;
-                                               return;
-                                       }
+                                       con2fb_release_oldinfo(vc_cons[i].d,
+                                                              info, NULL);
                                }
                        }
                }
@@ -2973,7 +2935,7 @@ void fbcon_fb_blanked(struct fb_info *info, int blank)
 
        vc = vc_cons[ops->currcon].d;
        if (vc->vc_mode != KD_TEXT ||
-                       registered_fb[con2fb_map[ops->currcon]] != info)
+                       fbcon_info_from_console(ops->currcon) != info)
                return;
 
        if (con_is_visible(vc)) {
@@ -2993,7 +2955,7 @@ void fbcon_new_modelist(struct fb_info *info)
        const struct fb_videomode *mode;
 
        for (i = first_fb_vc; i <= last_fb_vc; i++) {
-               if (registered_fb[con2fb_map[i]] != info)
+               if (fbcon_info_from_console(i) != info)
                        continue;
                if (!fb_display[i].mode)
                        continue;
@@ -3188,7 +3150,7 @@ static ssize_t show_cursor_blink(struct device *device,
        if (!ops)
                goto err;
 
-       blink = (ops->flags & FBCON_FLAGS_CURSOR_TIMER) ? 1 : 0;
+       blink = delayed_work_pending(&ops->cursor_work);
 err:
        console_unlock();
        return snprintf(buf, PAGE_SIZE, "%d\n", blink);
@@ -3217,10 +3179,10 @@ static ssize_t store_cursor_blink(struct device *device,
 
        if (blink) {
                fbcon_cursor_noblink = 0;
-               fbcon_add_cursor_timer(info);
+               fbcon_add_cursor_work(info);
        } else {
                fbcon_cursor_noblink = 1;
-               fbcon_del_cursor_timer(info);
+               fbcon_del_cursor_work(info);
        }
 
 err:
@@ -3306,17 +3268,6 @@ static void fbcon_start(void)
                return;
        }
 #endif
-
-       if (num_registered_fb) {
-               int i;
-
-               for_each_registered_fb(i) {
-                       info_idx = i;
-                       break;
-               }
-
-               do_fbcon_takeover(0);
-       }
 }
 
 static void fbcon_exit(void)
@@ -3332,15 +3283,9 @@ static void fbcon_exit(void)
 #endif
 
        for_each_registered_fb(i) {
-               int pending = 0;
-
                mapped = 0;
                info = registered_fb[i];
 
-               if (info->queue.func)
-                       pending = cancel_work_sync(&info->queue);
-               pr_debug("fbcon: %s pending work\n", (pending ? "canceled" : "no"));
-
                for (j = first_fb_vc; j <= last_fb_vc; j++) {
                        if (con2fb_map[j] == i) {
                                mapped = 1;
@@ -3349,22 +3294,17 @@ static void fbcon_exit(void)
                }
 
                if (mapped) {
-                       if (info->fbops->fb_release)
-                               info->fbops->fb_release(info, 0);
-                       module_put(info->fbops->owner);
-
                        if (info->fbcon_par) {
                                struct fbcon_ops *ops = info->fbcon_par;
 
-                               fbcon_del_cursor_timer(info);
+                               fbcon_del_cursor_work(info);
                                kfree(ops->cursor_src);
                                kfree(ops->cursor_state.mask);
                                kfree(info->fbcon_par);
                                info->fbcon_par = NULL;
                        }
 
-                       if (info->queue.func == fb_flashcursor)
-                               info->queue.func = NULL;
+                       fbcon_release(info);
                }
        }
 }