X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;ds=sidebyside;f=drivers%2Fvideo%2Fconsole%2Ffbcon.c;h=661685cc823dffea40138cfbfce028be0ab71a05;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=2fba5cfcbcee66c3b53a13872970545c32ae1f7d;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/drivers/video/console/fbcon.c b/drivers/video/console/fbcon.c index 2fba5cfcb..661685cc8 100644 --- a/drivers/video/console/fbcon.c +++ b/drivers/video/console/fbcon.c @@ -77,7 +77,7 @@ #include #include #include - +#include /* For counting font checksums */ #include #include #include @@ -100,11 +100,20 @@ # define DPRINTK(fmt, args...) #endif +enum { + FBCON_LOGO_CANSHOW = -1, /* the logo can be shown */ + FBCON_LOGO_DRAW = -2, /* draw the logo to a console */ + FBCON_LOGO_DONTSHOW = -3 /* do not show the logo */ +}; + struct display fb_display[MAX_NR_CONSOLES]; signed char con2fb_map[MAX_NR_CONSOLES]; +signed char con2fb_map_boot[MAX_NR_CONSOLES]; static int logo_height; static int logo_lines; -static int logo_shown = -1; +/* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO + enums. */ +static int logo_shown = FBCON_LOGO_CANSHOW; /* Software scrollback */ int fbcon_softback_size = 32768; static unsigned long softback_buf, softback_curr; @@ -119,13 +128,7 @@ static int fbcon_is_default = 1; static char fontname[40]; /* current fb_info */ -static int info_idx = -1; - -#define REFCOUNT(fd) (((int *)(fd))[-1]) -#define FNTSIZE(fd) (((int *)(fd))[-2]) -#define FNTCHARCNT(fd) (((int *)(fd))[-3]) -#define FNTSUM(fd) (((int *)(fd))[-4]) -#define FONT_EXTRA_WORDS 4 +static int info_idx = -1; #define CM_SOFTBACK (8) @@ -158,6 +161,7 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height, 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 int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, int count); @@ -167,9 +171,6 @@ static int fbcon_switch(struct vc_data *vc); static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch); static int fbcon_set_palette(struct vc_data *vc, unsigned char *table); static int fbcon_scrolldelta(struct vc_data *vc, int lines); -void accel_clear_margins(struct vc_data *vc, struct fb_info *info, - int bottom_only); - /* * Internal routines @@ -182,6 +183,7 @@ static __inline__ void ypan_down(struct vc_data *vc, int count); static void fbcon_bmove_rec(struct vc_data *vc, struct 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 vc_data *vc); +static void fbcon_preset_disp(struct fb_info *info, int unit); static void fbcon_redraw_move(struct vc_data *vc, struct display *p, int line, int count, int dy); @@ -198,21 +200,85 @@ static irqreturn_t fb_vbl_detect(int irq, void *dummy, struct pt_regs *fp) } #endif +static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) +{ + return (info->state != FBINFO_STATE_RUNNING || + vt_cons[vc->vc_num]->vc_mode != KD_TEXT); +} + +static inline int get_color(struct vc_data *vc, struct fb_info *info, + u16 c, int is_fg) +{ + int depth = fb_get_color_depth(info); + int color = 0; + + if (console_blanked) { + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + + c = vc->vc_video_erase_char & charmask; + } + + if (depth != 1) + color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c) + : attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c); + + switch (depth) { + case 1: + { + /* 0 or 1 */ + int fg = (info->fix.visual != FB_VISUAL_MONO01) ? 1 : 0; + int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : 1; + + if (console_blanked) + fg = bg; + + color = (is_fg) ? fg : bg; + break; + } + case 2: + /* + * Scale down 16-colors to 4 colors. Default 4-color palette + * is grayscale. + */ + color /= 4; + break; + case 3: + /* + * Last 8 entries of default 16-color palette is a more intense + * version of the first 8 (i.e., same chrominance, different + * luminance). + */ + color &= 7; + break; + } + + + return color; +} + static void fb_flashcursor(void *private) { - struct fb_info *info = (struct fb_info *) private; + struct fb_info *info = private; + struct fbcon_ops *ops = info->fbcon_par; + struct display *p; struct vc_data *vc = NULL; + int c; + int mode; - if (info->currcon != -1) - vc = vc_cons[info->currcon].d; + if (ops->currcon != -1) + vc = vc_cons[ops->currcon].d; - if (info->state != FBINFO_STATE_RUNNING || - info->cursor.rop == ROP_COPY || !vc || !CON_IS_VISIBLE(vc) - || registered_fb[(int) con2fb_map[vc->vc_num]] != info) + if (!vc || !CON_IS_VISIBLE(vc) || + fbcon_is_inactive(vc, info) || + registered_fb[con2fb_map[vc->vc_num]] != info) return; acquire_console_sem(); - info->cursor.enable ^= 1; - info->fbops->fb_cursor(info, &info->cursor); + p = &fb_display[vc->vc_num]; + c = scr_readw((u16 *) vc->vc_pos); + mode = (!ops->cursor_flash || ops->cursor_state.enable) ? + CM_ERASE : CM_DRAW; + ops->cursor(vc, info, p, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); release_console_sem(); } @@ -233,9 +299,10 @@ static irqreturn_t fb_vbl_handler(int irq, void *dev_id, struct pt_regs *fp) static void cursor_timer_handler(unsigned long dev_addr) { struct fb_info *info = (struct fb_info *) dev_addr; - + struct fbcon_ops *ops = info->fbcon_par; + schedule_work(&info->queue); - mod_timer(&info->cursor_timer, jiffies + HZ/5); + mod_timer(&ops->cursor_timer, jiffies + HZ/5); } int __init fb_console_setup(char *this_opt) @@ -271,7 +338,8 @@ int __init fb_console_setup(char *this_opt) for (i = 0, j = 0; i < MAX_NR_CONSOLES; i++) { if (!options[j]) j = 0; - con2fb_map[i] = (options[j++]-'0') % FB_MAX; + con2fb_map_boot[i] = + (options[j++]-'0') % FB_MAX; } return 0; } @@ -294,33 +362,36 @@ __setup("fbcon=", fb_console_setup); static int search_fb_in_map(int idx) { - int i; + int i, retval = 0; for (i = 0; i < MAX_NR_CONSOLES; i++) { if (con2fb_map[i] == idx) - return 1; + retval = 1; } - return 0; + return retval; } static int search_for_mapped_con(void) { - int i; + int i, retval = 0; for (i = 0; i < MAX_NR_CONSOLES; i++) { if (con2fb_map[i] != -1) - return 1; + retval = 1; } - return 0; + return retval; } -static int fbcon_takeover(void) +static int fbcon_takeover(int show_logo) { int err, i; if (!num_registered_fb) return -ENODEV; + if (!show_logo) + logo_shown = FBCON_LOGO_DONTSHOW; + for (i = first_fb_vc; i <= last_fb_vc; i++) con2fb_map[i] = info_idx; @@ -336,338 +407,271 @@ static int fbcon_takeover(void) return err; } -/** - * set_con2fb_map - map console to frame buffer device - * @unit: virtual console number to map - * @newidx: frame buffer index to map virtual console to - * - * Maps a virtual console @unit to a frame buffer device - * @newidx. - */ -static int set_con2fb_map(int unit, int newidx) +static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, + int cols, int rows, int new_cols, int new_rows) { - struct vc_data *vc = vc_cons[unit].d; - int oldidx = con2fb_map[unit]; - struct fb_info *info = registered_fb[newidx]; - struct fb_info *oldinfo = NULL; - int found; - - if (oldidx == newidx) - return 0; - - if (!vc) - return -ENODEV; + /* Need to make room for the logo */ + int cnt, erase = vc->vc_video_erase_char, step; + unsigned short *save = NULL, *r, *q; - if (!search_for_mapped_con()) { - info_idx = newidx; - return fbcon_takeover(); + /* + * remove underline attribute from erase character + * if black and white framebuffer. + */ + if (fb_get_color_depth(info) == 1) + erase &= ~0x400; + logo_height = fb_prepare_logo(info); + logo_lines = (logo_height + vc->vc_font.height - 1) / + vc->vc_font.height; + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * rows); + step = logo_lines * cols; + for (r = q - logo_lines * cols; r < q; r++) + if (scr_readw(r) != vc->vc_video_erase_char) + break; + if (r != q && new_rows >= rows + logo_lines) { + save = kmalloc(logo_lines * new_cols * 2, GFP_KERNEL); + if (save) { + int i = cols < new_cols ? cols : new_cols; + scr_memsetw(save, erase, logo_lines * new_cols * 2); + r = q - step; + for (cnt = 0; cnt < logo_lines; cnt++, r += i) + scr_memcpyw(save + cnt * new_cols, r, 2 * i); + r = q; + } } - - if (oldidx != -1) - oldinfo = registered_fb[oldidx]; - - found = search_fb_in_map(newidx); - - acquire_console_sem(); - con2fb_map[unit] = newidx; - if (!found) { - if (!try_module_get(info->fbops->owner)) { - con2fb_map[unit] = oldidx; - release_console_sem(); - return -ENODEV; + if (r == q) { + /* We can scroll screen down */ + r = q - step - cols; + for (cnt = rows - logo_lines; cnt > 0; cnt--) { + scr_memcpyw(r + step, r, vc->vc_size_row); + r -= cols; } - if (info->fbops->fb_open && info->fbops->fb_open(info, 0)) { - module_put(info->fbops->owner); - con2fb_map[unit] = oldidx; - release_console_sem(); - return -ENODEV; + if (!save) { + vc->vc_y += logo_lines; + vc->vc_pos += logo_lines * vc->vc_size_row; } } + scr_memsetw((unsigned short *) vc->vc_origin, + erase, + vc->vc_size_row * logo_lines); - /* - * If old fb is not mapped to any of the consoles, - * fbcon should release it. - */ - if (oldinfo && !search_fb_in_map(oldidx)) { - if (oldinfo->fbops->fb_release && - oldinfo->fbops->fb_release(oldinfo, 0)) { - con2fb_map[unit] = oldidx; - if (!found && info->fbops->fb_release) - info->fbops->fb_release(info, 0); - if (!found) - module_put(info->fbops->owner); - release_console_sem(); - return -ENODEV; - } - if (oldinfo->queue.func == fb_flashcursor) - del_timer_sync(&oldinfo->cursor_timer); - module_put(oldinfo->fbops->owner); + if (CON_IS_VISIBLE(vc) && vt_cons[vc->vc_num]->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); + update_screen(vc->vc_num); } - info->currcon = -1; - if (!found) { - if (!info->queue.func || info->queue.func == fb_flashcursor) { - if (!info->queue.func) - INIT_WORK(&info->queue, fb_flashcursor, info); - - init_timer(&info->cursor_timer); - info->cursor_timer.function = cursor_timer_handler; - info->cursor_timer.expires = jiffies + HZ / 5; - info->cursor_timer.data = (unsigned long ) info; - add_timer(&info->cursor_timer); - } + + if (save) { + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * + rows); + scr_memcpyw(q, save, logo_lines * new_cols * 2); + vc->vc_y += logo_lines; + vc->vc_pos += logo_lines * vc->vc_size_row; + kfree(save); + } + + if (logo_lines > vc->vc_bottom) { + logo_shown = FBCON_LOGO_CANSHOW; + printk(KERN_INFO + "fbcon_init: disable boot-logo (boot-logo bigger than screen).\n"); + } else if (logo_shown != FBCON_LOGO_DONTSHOW) { + logo_shown = FBCON_LOGO_DRAW; + vc->vc_top = logo_lines; } - if (info->fbops->fb_set_par) - info->fbops->fb_set_par(info); - fbcon_set_disp(info, vc); - release_console_sem(); - return 0; } -/* - * Accelerated handlers. - */ -static inline int get_color(struct vc_data *vc, struct fb_info *info, - u16 c, int is_fg) +#ifdef CONFIG_FB_TILEBLITTING +static void set_blitting_type(struct vc_data *vc, struct fb_info *info, + struct display *p) { - int depth = fb_get_color_depth(info); - int color = 0; + struct fbcon_ops *ops = info->fbcon_par; - if (depth != 1) - color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c) - : attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c); + if ((info->flags & FBINFO_MISC_TILEBLITTING)) + fbcon_set_tileops(vc, info, p, ops); + else + fbcon_set_bitops(ops); +} +#else +static void set_blitting_type(struct vc_data *vc, struct fb_info *info, + struct display *p) +{ + struct fbcon_ops *ops = info->fbcon_par; - switch (depth) { - case 1: - { - /* 0 or 1 */ - int fg = (info->fix.visual != FB_VISUAL_MONO01) ? 1 : 0; - int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : 1; + info->flags &= ~FBINFO_MISC_TILEBLITTING; + fbcon_set_bitops(ops); +} +#endif /* CONFIG_MISC_TILEBLITTING */ - color = (is_fg) ? fg : bg; - break; + +static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info, + int unit, int oldidx) +{ + struct fbcon_ops *ops = NULL; + int err = 0; + + if (!try_module_get(info->fbops->owner)) + err = -ENODEV; + + if (!err && info->fbops->fb_open && + info->fbops->fb_open(info, 0)) + err = -ENODEV; + + if (!err) { + ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL); + if (!ops) + err = -ENOMEM; } - case 2: - /* - * Scale down 16-colors to 4 colors. Default 4-color palette - * is grayscale. - */ - color /= 4; - break; - case 3: - /* - * Last 8 entries of default 16-color palette is a more intense - * version of the first 8 (i.e., same chrominance, different - * luminance). - */ - color &= 7; - break; + + if (!err) { + memset(ops, 0, sizeof(struct fbcon_ops)); + info->fbcon_par = ops; + set_blitting_type(vc, info, NULL); } - return color; -} + if (err) { + con2fb_map[unit] = oldidx; + module_put(info->fbops->owner); + } -#define FBCON_ATTRIBUTE_UNDERLINE 1 -#define FBCON_ATTRIBUTE_REVERSE 2 -#define FBCON_ATTRIBUTE_BOLD 4 + return err; +} -static inline int get_attribute(struct fb_info *info, u16 c) +static int con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo, + struct fb_info *newinfo, int unit, + int oldidx, int found) { - int attribute = 0; + struct fbcon_ops *ops = oldinfo->fbcon_par; + int err = 0; + + 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) { + if (oldinfo->queue.func == fb_flashcursor) + del_timer_sync(&ops->cursor_timer); - if (fb_get_color_depth(info) == 1) { - if (attr_underline(c)) - attribute |= FBCON_ATTRIBUTE_UNDERLINE; - if (attr_reverse(c)) - attribute |= FBCON_ATTRIBUTE_REVERSE; - if (attr_bold(c)) - attribute |= FBCON_ATTRIBUTE_BOLD; + kfree(ops->cursor_state.mask); + kfree(ops->cursor_data); + kfree(oldinfo->fbcon_par); + oldinfo->fbcon_par = NULL; + module_put(oldinfo->fbops->owner); } - return attribute; + return err; } -static inline void update_attr(u8 *dst, u8 *src, int attribute, - struct vc_data *vc) +static void con2fb_init_newinfo(struct fb_info *info) { - int i, offset = (vc->vc_font.height < 10) ? 1 : 2; - int width = (vc->vc_font.width + 7) >> 3; - unsigned int cellsize = vc->vc_font.height * width; - u8 c; + if (!info->queue.func || info->queue.func == fb_flashcursor) { + struct fbcon_ops *ops = info->fbcon_par; + + if (!info->queue.func) + INIT_WORK(&info->queue, fb_flashcursor, info); - offset = cellsize - (offset * width); - for (i = 0; i < cellsize; i++) { - c = src[i]; - if (attribute & FBCON_ATTRIBUTE_UNDERLINE && i >= offset) - c = 0xff; - if (attribute & FBCON_ATTRIBUTE_BOLD) - c |= c >> 1; - if (attribute & FBCON_ATTRIBUTE_REVERSE) - c = ~c; - dst[i] = c; + init_timer(&ops->cursor_timer); + ops->cursor_timer.function = cursor_timer_handler; + ops->cursor_timer.expires = jiffies + HZ / 5; + ops->cursor_timer.data = (unsigned long ) info; + add_timer(&ops->cursor_timer); } } -void accel_bmove(struct vc_data *vc, struct fb_info *info, int sy, - int sx, int dy, int dx, int height, int width) +static void con2fb_init_display(struct vc_data *vc, struct fb_info *info, + int unit, int show_logo) { - struct fb_copyarea area; + struct fbcon_ops *ops = info->fbcon_par; - area.sx = sx * vc->vc_font.width; - area.sy = sy * vc->vc_font.height; - area.dx = dx * vc->vc_font.width; - area.dy = dy * vc->vc_font.height; - area.height = height * vc->vc_font.height; - area.width = width * vc->vc_font.width; + ops->currcon = fg_console; - info->fbops->fb_copyarea(info, &area); + if (info->fbops->fb_set_par) + info->fbops->fb_set_par(info); + + if (vc) + fbcon_set_disp(info, vc); + else + fbcon_preset_disp(info, 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_prepare_logo(fg_vc, fg_info, fg_vc->vc_cols, + fg_vc->vc_rows, fg_vc->vc_cols, + fg_vc->vc_rows); + } + + switch_screen(fg_console); } -void accel_clear(struct vc_data *vc, struct fb_info *info, int sy, - int sx, int height, int width) +/** + * set_con2fb_map - map console to frame buffer device + * @unit: virtual console number to map + * @newidx: frame buffer index to map virtual console to + * @user: user request + * + * Maps a virtual console @unit to a frame buffer device + * @newidx. + */ +static int set_con2fb_map(int unit, int newidx, int user) { - int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; - struct fb_fillrect region; + struct vc_data *vc = vc_cons[unit].d; + int oldidx = con2fb_map[unit]; + struct fb_info *info = registered_fb[newidx]; + struct fb_info *oldinfo = NULL; + int found, err = 0; - region.color = attr_bgcol_ec(bgshift, vc); - region.dx = sx * vc->vc_font.width; - region.dy = sy * vc->vc_font.height; - region.width = width * vc->vc_font.width; - region.height = height * vc->vc_font.height; - region.rop = ROP_COPY; + if (oldidx == newidx) + return 0; - info->fbops->fb_fillrect(info, ®ion); -} + if (!info) + err = -EINVAL; -void accel_putcs(struct vc_data *vc, struct fb_info *info, - const unsigned short *s, int count, int yy, int xx) -{ - void (*move_unaligned)(struct fb_info *info, struct fb_pixmap *buf, - u8 *dst, u32 d_pitch, u8 *src, u32 idx, - u32 height, u32 shift_high, u32 shift_low, - u32 mod); - void (*move_aligned)(struct fb_info *info, struct fb_pixmap *buf, - u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch, - u32 height); - unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; - unsigned int width = (vc->vc_font.width + 7) >> 3; - unsigned int cellsize = vc->vc_font.height * width; - unsigned int maxcnt = info->pixmap.size/cellsize; - unsigned int scan_align = info->pixmap.scan_align - 1; - unsigned int buf_align = info->pixmap.buf_align - 1; - unsigned int shift_low = 0, mod = vc->vc_font.width % 8; - unsigned int shift_high = 8, pitch, cnt, size, k; - unsigned int idx = vc->vc_font.width >> 3; - unsigned int attribute = get_attribute(info, scr_readw(s)); - struct fb_image image; - u8 *src, *dst, *buf = NULL; - - if (attribute) { - buf = kmalloc(cellsize, GFP_KERNEL); - if (!buf) - return; - } - - image.fg_color = get_color(vc, info, scr_readw(s), 1); - image.bg_color = get_color(vc, info, scr_readw(s), 0); - - image.dx = xx * vc->vc_font.width; - image.dy = yy * vc->vc_font.height; - image.height = vc->vc_font.height; - image.depth = 1; - - if (info->pixmap.outbuf && info->pixmap.inbuf) { - move_aligned = fb_iomove_buf_aligned; - move_unaligned = fb_iomove_buf_unaligned; - } else { - move_aligned = fb_sysmove_buf_aligned; - move_unaligned = fb_sysmove_buf_unaligned; + if (!err && !search_for_mapped_con()) { + info_idx = newidx; + return fbcon_takeover(0); } - while (count) { - if (count > maxcnt) - cnt = k = maxcnt; - else - cnt = k = count; - - image.width = vc->vc_font.width * cnt; - pitch = ((image.width + 7) >> 3) + scan_align; - pitch &= ~scan_align; - size = pitch * image.height + buf_align; - size &= ~buf_align; - dst = fb_get_buffer_offset(info, &info->pixmap, size); - image.data = dst; - if (mod) { - while (k--) { - src = vc->vc_font.data + (scr_readw(s++)& - charmask)*cellsize; - - if (attribute) { - update_attr(buf, src, attribute, vc); - src = buf; - } - move_unaligned(info, &info->pixmap, dst, pitch, - src, idx, image.height, - shift_high, shift_low, mod); - shift_low += mod; - dst += (shift_low >= 8) ? width : width - 1; - shift_low &= 7; - shift_high = 8 - shift_low; - } - } else { - while (k--) { - src = vc->vc_font.data + (scr_readw(s++)& - charmask)*cellsize; + if (oldidx != -1) + oldinfo = registered_fb[oldidx]; - if (attribute) { - update_attr(buf, src, attribute, vc); - src = buf; - } + found = search_fb_in_map(newidx); - move_aligned(info, &info->pixmap, dst, pitch, - src, idx, image.height); - dst += width; - } - } - info->fbops->fb_imageblit(info, &image); - image.dx += cnt * vc->vc_font.width; - count -= cnt; - } + acquire_console_sem(); + con2fb_map[unit] = newidx; + if (!err && !found) + err = con2fb_acquire_newinfo(vc, info, unit, oldidx); - if (buf) - kfree(buf); -} -void accel_clear_margins(struct vc_data *vc, struct fb_info *info, - int bottom_only) -{ - int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; - unsigned int cw = vc->vc_font.width; - unsigned int ch = vc->vc_font.height; - unsigned int rw = info->var.xres - (vc->vc_cols*cw); - unsigned int bh = info->var.yres - (vc->vc_rows*ch); - unsigned int rs = info->var.xres - rw; - unsigned int bs = info->var.yres - bh; - struct fb_fillrect region; + /* + * 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); - region.color = attr_bgcol_ec(bgshift, vc); - region.rop = ROP_COPY; + if (!err) { + int show_logo = (fg_console == 0 && !user && + logo_shown != FBCON_LOGO_DONTSHOW); - if (rw && !bottom_only) { - region.dx = info->var.xoffset + rs; - region.dy = 0; - region.width = rw; - region.height = info->var.yres_virtual; - info->fbops->fb_fillrect(info, ®ion); + if (!found) + con2fb_init_newinfo(info); + con2fb_map_boot[unit] = newidx; + con2fb_init_display(vc, info, unit, show_logo); } - if (bh) { - region.dx = info->var.xoffset; - region.dy = info->var.yoffset + bs; - region.width = rs; - region.height = bh; - info->fbops->fb_fillrect(info, ®ion); - } -} + release_console_sem(); + return err; +} /* * Low Level Operations @@ -722,6 +726,7 @@ static const char *fbcon_startup(void) struct font_desc *font = NULL; struct module *owner; struct fb_info *info = NULL; + struct fbcon_ops *ops; int rows, cols; int irqres; @@ -739,7 +744,6 @@ static const char *fbcon_startup(void) info = registered_fb[info_idx]; if (!info) return NULL; - info->currcon = -1; owner = info->fbops->owner; if (!try_module_get(owner)) @@ -748,6 +752,18 @@ static const char *fbcon_startup(void) module_put(owner); return NULL; } + + ops = kmalloc(sizeof(struct fbcon_ops), GFP_KERNEL); + if (!ops) { + module_put(owner); + return NULL; + } + + memset(ops, 0, sizeof(struct fbcon_ops)); + ops->currcon = -1; + info->fbcon_par = ops; + set_blitting_type(vc, info, NULL); + if (info->fix.type != FB_TYPE_TEXT) { if (fbcon_softback_size) { if (!softback_buf) { @@ -777,7 +793,7 @@ static const char *fbcon_startup(void) if (!p->fontdata) { if (!fontname[0] || !(font = find_font(fontname))) font = get_default_font(info->var.xres, - info->var.yres); + info->var.yres); vc->vc_font.width = font->width; vc->vc_font.height = font->height; vc->vc_font.data = p->fontdata = font->data; @@ -793,7 +809,6 @@ static const char *fbcon_startup(void) DPRINTK("res: %dx%d-%d\n", info->var.xres, info->var.yres, info->var.bits_per_pixel); - con_set_default_unimap(vc->vc_num); #ifdef CONFIG_ATARI if (MACH_IS_ATARI) { @@ -860,28 +875,28 @@ static const char *fbcon_startup(void) if (!info->queue.func) { INIT_WORK(&info->queue, fb_flashcursor, info); - init_timer(&info->cursor_timer); - info->cursor_timer.function = cursor_timer_handler; - info->cursor_timer.expires = jiffies + HZ / 5; - info->cursor_timer.data = (unsigned long ) info; - add_timer(&info->cursor_timer); + init_timer(&ops->cursor_timer); + ops->cursor_timer.function = cursor_timer_handler; + ops->cursor_timer.expires = jiffies + HZ / 5; + ops->cursor_timer.data = (unsigned long ) info; + add_timer(&ops->cursor_timer); } return display_desc; } static void fbcon_init(struct vc_data *vc, int init) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct vc_data **default_mode = vc->vc_display_fg; + struct vc_data *svc = *default_mode; struct display *t, *p = &fb_display[vc->vc_num]; int display_fg = (*default_mode)->vc_num; int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256; - unsigned short *save = NULL, *r, *q; int cap = info->flags; if (info_idx == -1 || info == NULL) return; - if (vc->vc_num != display_fg || (info->flags & FBINFO_MODULE) || + if (vc->vc_num != display_fg || logo_shown == FBCON_LOGO_DONTSHOW || (info->fix.type == FB_TYPE_TEXT)) logo = 0; @@ -900,7 +915,6 @@ static void fbcon_init(struct vc_data *vc, int init) p->userfont = t->userfont; if (p->userfont) REFCOUNT(p->fontdata)++; - con_copy_unimap(vc->vc_num, display_fg); } if (p->userfont) charcnt = FNTCHARCNT(p->fontdata); @@ -913,6 +927,12 @@ static void fbcon_init(struct vc_data *vc, int init) if (vc->vc_can_do_color) vc->vc_complement_mask <<= 1; } + + if (!*svc->vc_uni_pagedir_loc) + con_set_default_unimap(display_fg); + if (!*vc->vc_uni_pagedir_loc) + con_copy_unimap(vc->vc_num, display_fg); + cols = vc->vc_cols; rows = vc->vc_rows; new_cols = info->var.xres / vc->vc_font.width; @@ -945,75 +965,8 @@ static void fbcon_init(struct vc_data *vc, int init) vc->vc_rows = new_rows; } - if (logo) { - /* Need to make room for the logo */ - int cnt, erase = vc->vc_video_erase_char; - int step; - - /* - * remove underline attribute from erase character - * if black and white framebuffer. - */ - if (fb_get_color_depth(info) == 1) - erase &= ~0x400; - logo_height = fb_prepare_logo(info); - logo_lines = (logo_height + vc->vc_font.height - 1) / - vc->vc_font.height; - q = (unsigned short *) (vc->vc_origin + - vc->vc_size_row * rows); - step = logo_lines * cols; - for (r = q - logo_lines * cols; r < q; r++) - if (scr_readw(r) != vc->vc_video_erase_char) - break; - if (r != q && new_rows >= rows + logo_lines) { - save = kmalloc(logo_lines * new_cols * 2, GFP_KERNEL); - if (save) { - int i = cols < new_cols ? cols : new_cols; - scr_memsetw(save, erase, logo_lines * new_cols * 2); - r = q - step; - for (cnt = 0; cnt < logo_lines; cnt++, r += i) - scr_memcpyw(save + cnt * new_cols, r, 2 * i); - r = q; - } - } - if (r == q) { - /* We can scroll screen down */ - r = q - step - cols; - for (cnt = rows - logo_lines; cnt > 0; cnt--) { - scr_memcpyw(r + step, r, vc->vc_size_row); - r -= cols; - } - if (!save) { - vc->vc_y += logo_lines; - vc->vc_pos += logo_lines * vc->vc_size_row; - } - } - scr_memsetw((unsigned short *) vc->vc_origin, - erase, - vc->vc_size_row * logo_lines); - - if (CON_IS_VISIBLE(vc) && vt_cons[vc->vc_num]->vc_mode == KD_TEXT) { - accel_clear_margins(vc, info, 0); - update_screen(vc->vc_num); - } - if (save) { - q = (unsigned short *) (vc->vc_origin + - vc->vc_size_row * - rows); - scr_memcpyw(q, save, logo_lines * new_cols * 2); - vc->vc_y += logo_lines; - vc->vc_pos += logo_lines * vc->vc_size_row; - kfree(save); - } - if (logo_lines > vc->vc_bottom) { - logo_shown = -1; - printk(KERN_INFO - "fbcon_init: disable boot-logo (boot-logo bigger than screen).\n"); - } else { - logo_shown = -2; - vc->vc_top = logo_lines; - } - } + if (logo) + fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); if (vc->vc_num == display_fg && softback_buf) { int l = fbcon_softback_size / vc->vc_size_row; @@ -1043,7 +996,7 @@ static void fbcon_deinit(struct vc_data *vc) * This system is now divided into two levels because of complications * caused by hardware scrolling. Top level functions: * - * fbcon_bmove(), fbcon_clear(), fbcon_putc() + * fbcon_bmove(), fbcon_clear(), fbcon_putc(), fbcon_clear_margins() * * handles y values in range [0, scr_height-1] that correspond to real * screen positions. y_wrap shift means that first line of bitmap may be @@ -1073,14 +1026,13 @@ static __inline__ int real_y(struct display *p, int ypos) static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height, int width) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; - + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_ops *ops = info->fbcon_par; + struct display *p = &fb_display[vc->vc_num]; u_int y_break; - if (!info->fbops->fb_blank && console_blanked) - return; - if (info->state != FBINFO_STATE_RUNNING) + if (fbcon_is_inactive(vc, info)) return; if (!height || !width) @@ -1091,166 +1043,71 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height, y_break = p->vrows - p->yscroll; if (sy < y_break && sy + height - 1 >= y_break) { u_int b = y_break - sy; - accel_clear(vc, info, real_y(p, sy), sx, b, width); - accel_clear(vc, info, real_y(p, sy + b), sx, height - b, + ops->clear(vc, info, real_y(p, sy), sx, b, width); + ops->clear(vc, info, real_y(p, sy + b), sx, height - b, width); } else - accel_clear(vc, info, real_y(p, sy), sx, height, width); + ops->clear(vc, info, real_y(p, sy), sx, height, width); } static void fbcon_putcs(struct vc_data *vc, const unsigned short *s, int count, int ypos, int xpos) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; - if (!info->fbops->fb_blank && console_blanked) - return; - if (info->state != FBINFO_STATE_RUNNING) - return; + if (!fbcon_is_inactive(vc, info)) + ops->putcs(vc, info, s, count, real_y(p, ypos), xpos, + get_color(vc, info, scr_readw(s), 1), + get_color(vc, info, scr_readw(s), 0)); +} - if (vt_cons[vc->vc_num]->vc_mode != KD_TEXT) - return; +static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos) +{ + unsigned short chr; - accel_putcs(vc, info, s, count, real_y(p, ypos), xpos); + scr_writew(c, &chr); + fbcon_putcs(vc, &chr, 1, ypos, xpos); } -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) { - fbcon_putcs(vc, (const unsigned short *) &c, 1, ypos, xpos); + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) + ops->clear_margins(vc, info, bottom_only); } static void fbcon_cursor(struct vc_data *vc, int mode) { - struct fb_cursor cursor; - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; - unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_ops *ops = info->fbcon_par; struct display *p = &fb_display[vc->vc_num]; - int w = (vc->vc_font.width + 7) >> 3, c; - int y = real_y(p, vc->vc_y), fg, bg; - int attribute; - u8 *src; + int y = real_y(p, vc->vc_y); + int c = scr_readw((u16 *) vc->vc_pos); + if (fbcon_is_inactive(vc, info)) + return; + + ops->cursor_flash = 1; if (mode & CM_SOFTBACK) { mode &= ~CM_SOFTBACK; if (softback_lines) { - if (y + softback_lines >= vc->vc_rows) + if (y + softback_lines >= vc->vc_rows) { mode = CM_ERASE; + ops->cursor_flash = 0; + } else y += softback_lines; } } else if (softback_lines) fbcon_set_origin(vc); - c = scr_readw((u16 *) vc->vc_pos); - attribute = get_attribute(info, c); - src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); - if (attribute) { - u8 *dst; - - dst = kmalloc(w * vc->vc_font.height, GFP_ATOMIC); - if (!dst) - return; - if (info->cursor.data) - kfree(info->cursor.data); - info->cursor.data = dst; - update_attr(dst, src, attribute, vc); - src = dst; - } - - cursor.image.data = src; - cursor.set = FB_CUR_SETCUR; - cursor.image.depth = 1; - - switch (mode) { - case CM_ERASE: - if (info->cursor.rop == ROP_XOR) { - info->cursor.enable = 0; - info->cursor.rop = ROP_COPY; - info->fbops->fb_cursor(info, &cursor); - } - break; - case CM_MOVE: - case CM_DRAW: - info->cursor.enable = 1; - fg = get_color(vc, info, c, 1); - bg = get_color(vc, info, c, 0); - - if (info->cursor.image.fg_color != fg || - info->cursor.image.bg_color != bg) { - cursor.image.fg_color = fg; - cursor.image.bg_color = bg; - cursor.set |= FB_CUR_SETCMAP; - } - - if ((info->cursor.image.dx != (vc->vc_font.width * vc->vc_x)) || - (info->cursor.image.dy != (vc->vc_font.height * y))) { - cursor.image.dx = vc->vc_font.width * vc->vc_x; - cursor.image.dy = vc->vc_font.height * y; - cursor.set |= FB_CUR_SETPOS; - } - - if (info->cursor.image.height != vc->vc_font.height || - info->cursor.image.width != vc->vc_font.width) { - cursor.image.height = vc->vc_font.height; - cursor.image.width = vc->vc_font.width; - cursor.set |= FB_CUR_SETSIZE; - } - - if (info->cursor.hot.x || info->cursor.hot.y) { - cursor.hot.x = cursor.hot.y = 0; - cursor.set |= FB_CUR_SETHOT; - } - - if ((cursor.set & FB_CUR_SETSIZE) || - ((vc->vc_cursor_type & 0x0f) != p->cursor_shape) - || info->cursor.mask == NULL) { - char *mask = kmalloc(w*vc->vc_font.height, GFP_ATOMIC); - int cur_height, size, i = 0; - u8 msk = 0xff; - - if (!mask) - return; - - if (info->cursor.mask) - kfree(info->cursor.mask); - info->cursor.mask = mask; - p->cursor_shape = vc->vc_cursor_type & 0x0f; - cursor.set |= FB_CUR_SETSHAPE; - - switch (vc->vc_cursor_type & 0x0f) { - case CUR_NONE: - cur_height = 0; - break; - case CUR_UNDERLINE: - cur_height = (vc->vc_font.height < 10) ? 1 : 2; - break; - case CUR_LOWER_THIRD: - cur_height = vc->vc_font.height/3; - break; - case CUR_LOWER_HALF: - cur_height = vc->vc_font.height >> 1; - break; - case CUR_TWO_THIRDS: - cur_height = (vc->vc_font.height << 1)/3; - break; - case CUR_BLOCK: - default: - cur_height = vc->vc_font.height; - break; - } - size = (vc->vc_font.height - cur_height) * w; - while (size--) - mask[i++] = ~msk; - size = cur_height * w; - while (size--) - mask[i++] = msk; - } - info->cursor.rop = ROP_XOR; - info->fbops->fb_cursor(info, &cursor); - vbl_cursor_cnt = CURSOR_DRAW_DELAY; - break; - } + ops->cursor(vc, info, p, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + vbl_cursor_cnt = CURSOR_DRAW_DELAY; } static int scrollback_phys_max = 0; @@ -1259,15 +1116,34 @@ static int scrollback_current = 0; int update_var(int con, struct fb_info *info) { - if (con == info->currcon) + if (con == ((struct fbcon_ops *)info->fbcon_par)->currcon) return fb_pan_display(info, &info->var); return 0; } +/* + * If no vc is existent yet, just set struct display + */ +static void fbcon_preset_disp(struct fb_info *info, int unit) +{ + struct display *p = &fb_display[unit]; + struct display *t = &fb_display[fg_console]; + + info->var.xoffset = info->var.yoffset = p->yscroll = 0; + if (var_to_display(p, &info->var, info)) + return; + + p->fontdata = t->fontdata; + p->userfont = t->userfont; + if (p->userfont) + REFCOUNT(p->fontdata)++; +} + static void fbcon_set_disp(struct fb_info *info, struct vc_data *vc) { struct display *p = &fb_display[vc->vc_num], *t; struct vc_data **default_mode = vc->vc_display_fg; + struct vc_data *svc = *default_mode; int display_fg = (*default_mode)->vc_num; int rows, cols, charcnt = 256; @@ -1282,7 +1158,6 @@ static void fbcon_set_disp(struct fb_info *info, struct vc_data *vc) p->userfont = t->userfont; if (p->userfont) REFCOUNT(p->fontdata)++; - con_copy_unimap(vc->vc_num, display_fg); } if (p->userfont) charcnt = FNTCHARCNT(p->fontdata); @@ -1296,6 +1171,12 @@ static void fbcon_set_disp(struct fb_info *info, struct vc_data *vc) if (vc->vc_can_do_color) vc->vc_complement_mask <<= 1; } + + if (!*svc->vc_uni_pagedir_loc) + con_set_default_unimap(display_fg); + if (!*vc->vc_uni_pagedir_loc) + con_copy_unimap(vc->vc_num, display_fg); + cols = info->var.xres / vc->vc_font.width; rows = info->var.yres / vc->vc_font.height; vc_resize(vc->vc_num, cols, rows); @@ -1314,12 +1195,11 @@ static void fbcon_set_disp(struct fb_info *info, struct vc_data *vc) } } } - switch_screen(fg_console); } static __inline__ void ywrap_up(struct vc_data *vc, int count) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; p->yscroll += count; @@ -1337,7 +1217,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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; p->yscroll -= count; @@ -1355,20 +1235,21 @@ 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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; - + struct fbcon_ops *ops = info->fbcon_par; + p->yscroll += count; if (p->yscroll > p->vrows - vc->vc_rows) { - accel_bmove(vc, info, p->vrows - vc->vc_rows, - 0, 0, 0, vc->vc_rows, vc->vc_cols); + ops->bmove(vc, info, p->vrows - vc->vc_rows, + 0, 0, 0, vc->vc_rows, vc->vc_cols); p->yscroll -= p->vrows - vc->vc_rows; } info->var.xoffset = 0; info->var.yoffset = p->yscroll * vc->vc_font.height; info->var.vmode &= ~FB_VMODE_YWRAP; update_var(vc->vc_num, info); - accel_clear_margins(vc, info, 1); + fbcon_clear_margins(vc, 1); scrollback_max += count; if (scrollback_max > scrollback_phys_max) scrollback_max = scrollback_phys_max; @@ -1377,7 +1258,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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; int redraw = 0; @@ -1393,7 +1274,7 @@ static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count) if (redraw) fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t); update_var(vc->vc_num, info); - accel_clear_margins(vc, info, 1); + fbcon_clear_margins(vc, 1); scrollback_max += count; if (scrollback_max > scrollback_phys_max) scrollback_max = scrollback_phys_max; @@ -1402,20 +1283,21 @@ 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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; p->yscroll -= count; if (p->yscroll < 0) { - accel_bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, - 0, vc->vc_rows, vc->vc_cols); + ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, + 0, vc->vc_rows, vc->vc_cols); p->yscroll += p->vrows - vc->vc_rows; } info->var.xoffset = 0; info->var.yoffset = p->yscroll * vc->vc_font.height; info->var.vmode &= ~FB_VMODE_YWRAP; update_var(vc->vc_num, info); - accel_clear_margins(vc, info, 1); + fbcon_clear_margins(vc, 1); scrollback_max -= count; if (scrollback_max < 0) scrollback_max = 0; @@ -1424,7 +1306,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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; int redraw = 0; @@ -1439,7 +1321,7 @@ static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) if (redraw) fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count); update_var(vc->vc_num, info); - accel_clear_margins(vc, info, 1); + fbcon_clear_margins(vc, 1); scrollback_max -= count; if (scrollback_max < 0) scrollback_max = 0; @@ -1449,7 +1331,6 @@ static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) static void fbcon_redraw_softback(struct vc_data *vc, struct display *p, long delta) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; int count = vc->vc_rows; unsigned short *d, *s; unsigned long n; @@ -1506,16 +1387,16 @@ static void fbcon_redraw_softback(struct vc_data *vc, struct display *p, if (attr != (c & 0xff00)) { attr = c & 0xff00; if (s > start) { - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, + line, x); x += s - start; start = s; } } if (c == scr_readw(d)) { if (s > start) { - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, + line, x); x += s - start + 1; start = s + 1; } else { @@ -1527,8 +1408,7 @@ static void fbcon_redraw_softback(struct vc_data *vc, struct display *p, d++; } while (s < le); if (s > start) - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, line, x); line++; if (d == (u16 *) softback_end) d = (u16 *) softback_buf; @@ -1544,7 +1424,6 @@ static void fbcon_redraw_softback(struct vc_data *vc, struct display *p, static void fbcon_redraw_move(struct vc_data *vc, struct display *p, int line, int count, int dy) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; unsigned short *s = (unsigned short *) (vc->vc_origin + vc->vc_size_row * line); @@ -1560,8 +1439,8 @@ static void fbcon_redraw_move(struct vc_data *vc, struct display *p, if (attr != (c & 0xff00)) { attr = c & 0xff00; if (s > start) { - accel_putcs(vc, info, start, s - start, - real_y(p, dy), x); + fbcon_putcs(vc, start, s - start, + dy, x); x += s - start; start = s; } @@ -1570,8 +1449,7 @@ static void fbcon_redraw_move(struct vc_data *vc, struct display *p, s++; } while (s < le); if (s > start) - accel_putcs(vc, info, start, s - start, - real_y(p, dy), x); + fbcon_putcs(vc, start, s - start, dy, x); console_conditional_schedule(); dy++; } @@ -1582,7 +1460,6 @@ static void fbcon_redraw(struct vc_data *vc, struct display *p, { unsigned short *d = (unsigned short *) (vc->vc_origin + vc->vc_size_row * line); - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; unsigned short *s = d + offset; while (count--) { @@ -1597,16 +1474,16 @@ static void fbcon_redraw(struct vc_data *vc, struct display *p, if (attr != (c & 0xff00)) { attr = c & 0xff00; if (s > start) { - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, + line, x); x += s - start; start = s; } } if (c == scr_readw(d)) { if (s > start) { - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, + line, x); x += s - start + 1; start = s + 1; } else { @@ -1620,8 +1497,7 @@ static void fbcon_redraw(struct vc_data *vc, struct display *p, d++; } while (s < le); if (s > start) - accel_putcs(vc, info, start, s - start, - real_y(p, line), x); + fbcon_putcs(vc, start, s - start, line, x); console_conditional_schedule(); if (offset > 0) line++; @@ -1662,15 +1538,13 @@ static inline void fbcon_softback_note(struct vc_data *vc, int t, static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, int count) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK; - if (!info->fbops->fb_blank && console_blanked) - return 0; - - if (!count || vt_cons[vc->vc_num]->vc_mode != KD_TEXT) - return 0; + if (fbcon_is_inactive(vc, info)) + return -EINVAL; fbcon_cursor(vc, CM_ERASE); @@ -1690,10 +1564,10 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, goto redraw_up; switch (p->scrollmode) { case SCROLL_MOVE: - accel_bmove(vc, info, t + count, 0, t, 0, - b - t - count, vc->vc_cols); - accel_clear(vc, info, b - count, 0, count, - vc->vc_cols); + ops->bmove(vc, info, t + count, 0, t, 0, + b - t - count, vc->vc_cols); + ops->clear(vc, info, b - count, 0, count, + vc->vc_cols); break; case SCROLL_WRAP_MOVE: @@ -1759,8 +1633,7 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, redraw_up: fbcon_redraw(vc, p, t, b - t - count, count * vc->vc_cols); - accel_clear(vc, info, real_y(p, b - count), 0, - count, vc->vc_cols); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); scr_memsetw((unsigned short *) (vc->vc_origin + vc->vc_size_row * (b - count)), @@ -1775,9 +1648,9 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, count = vc->vc_rows; switch (p->scrollmode) { case SCROLL_MOVE: - accel_bmove(vc, info, t, 0, t + count, 0, - b - t - count, vc->vc_cols); - accel_clear(vc, info, t, 0, count, vc->vc_cols); + ops->bmove(vc, info, t, 0, t + count, 0, + b - t - count, vc->vc_cols); + ops->clear(vc, info, t, 0, count, vc->vc_cols); break; case SCROLL_WRAP_MOVE: @@ -1841,8 +1714,7 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, redraw_down: fbcon_redraw(vc, p, b - 1, b - t - count, -count * vc->vc_cols); - accel_clear(vc, info, real_y(p, t), 0, count, - vc->vc_cols); + fbcon_clear(vc, t, 0, count, vc->vc_cols); scr_memsetw((unsigned short *) (vc->vc_origin + vc->vc_size_row * t), @@ -1858,10 +1730,10 @@ static int fbcon_scroll(struct vc_data *vc, int t, int b, int dir, 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[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; - if (!info->fbops->fb_blank && console_blanked) + if (fbcon_is_inactive(vc, info)) return; if (!width || !height) @@ -1881,7 +1753,8 @@ static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, static void fbcon_bmove_rec(struct vc_data *vc, struct display *p, int sy, int sx, int dy, int dx, int height, int width, u_int y_break) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + 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) { @@ -1915,8 +1788,8 @@ static void fbcon_bmove_rec(struct vc_data *vc, struct display *p, int sy, int s } return; } - accel_bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, - height, width); + ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, + height, width); } static __inline__ void updatescrollmode(struct display *p, struct fb_info *info, @@ -1958,7 +1831,7 @@ static __inline__ void updatescrollmode(struct display *p, struct fb_info *info, static int fbcon_resize(struct vc_data *vc, unsigned int width, unsigned int height) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; struct fb_var_screeninfo var = info->var; int x_diff, y_diff; @@ -2010,10 +1883,12 @@ static int fbcon_resize(struct vc_data *vc, unsigned int width, static int fbcon_switch(struct vc_data *vc) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info; struct display *p = &fb_display[vc->vc_num]; struct fb_var_screeninfo var; - int i; + int i, prev_console, do_set_par = 0; + + info = registered_fb[con2fb_map[vc->vc_num]]; if (softback_top) { int l = fbcon_softback_size / vc->vc_size_row; @@ -2030,15 +1905,18 @@ static int fbcon_switch(struct vc_data *vc) softback_top = 0; } } + if (logo_shown >= 0) { struct vc_data *conp2 = vc_cons[logo_shown].d; if (conp2->vc_top == logo_lines && conp2->vc_bottom == conp2->vc_rows) conp2->vc_top = 0; - logo_shown = -1; + logo_shown = FBCON_LOGO_CANSHOW; } + prev_console = ((struct fbcon_ops *)info->fbcon_par)->currcon; + /* * FIXME: If we have multiple fbdev's loaded, we need to * update all info->currcon. Perhaps, we can place this @@ -2048,12 +1926,13 @@ static int fbcon_switch(struct vc_data *vc) * info->currcon = vc->vc_num; */ for (i = 0; i < FB_MAX; i++) { - if (registered_fb[i] != NULL) - registered_fb[i]->currcon = vc->vc_num; - } + if (registered_fb[i] != NULL && registered_fb[i]->fbcon_par) { + struct fbcon_ops *ops = registered_fb[i]->fbcon_par; + ops->currcon = vc->vc_num; + } + } memset(&var, 0, sizeof(struct fb_var_screeninfo)); - fb_videomode_to_var(&var, p->mode); display_to_var(&var, p); var.activate = FB_ACTIVATE_NOW; @@ -2065,12 +1944,19 @@ static int fbcon_switch(struct vc_data *vc) info->var.yoffset = info->var.xoffset = p->yscroll = 0; fb_set_var(info, &var); - if (info->flags & FBINFO_MISC_MODESWITCH) { + if (prev_console != -1 && + registered_fb[con2fb_map[prev_console]] != info) + do_set_par = 1; + + if (do_set_par || info->flags & FBINFO_MISC_MODESWITCH) { if (info->fbops->fb_set_par) info->fbops->fb_set_par(info); info->flags &= ~FBINFO_MISC_MODESWITCH; } + set_blitting_type(vc, info, p); + ((struct fbcon_ops *)info->fbcon_par)->cursor_reset = 1; + vc->vc_can_do_color = (fb_get_color_depth(info) != 1); vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; updatescrollmode(p, info, vc); @@ -2094,10 +1980,10 @@ static int fbcon_switch(struct vc_data *vc) update_var(vc->vc_num, info); fbcon_set_palette(vc, color_table); + fbcon_clear_margins(vc, 0); + + if (logo_shown == FBCON_LOGO_DRAW) { - if (vt_cons[vc->vc_num]->vc_mode == KD_TEXT) - accel_clear_margins(vc, info, 0); - if (logo_shown == -2) { logo_shown = fg_console; /* This is protected above by initmem_freed */ fb_show_logo(info); @@ -2110,15 +1996,29 @@ static int fbcon_switch(struct vc_data *vc) return 1; } +static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info, + int blank) +{ + if (blank) { + unsigned short charmask = vc->vc_hi_font_mask ? + 0x1ff : 0xff; + unsigned short oldc; + + oldc = vc->vc_video_erase_char; + vc->vc_video_erase_char &= charmask; + fbcon_clear(vc, 0, 0, vc->vc_rows, vc->vc_cols); + vc->vc_video_erase_char = oldc; + } +} + static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch) { - unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; - struct display *p = &fb_display[vc->vc_num]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; + struct fbcon_ops *ops = info->fbcon_par; + int active = !fbcon_is_inactive(vc, info); if (mode_switch) { struct fb_var_screeninfo var = info->var; - /* * HACK ALERT: Some hardware will require reinitializion at this stage, * others will require it to be done as late as possible. @@ -2131,44 +2031,27 @@ static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch) if (info->flags & FBINFO_MISC_MODESWITCHLATE) info->flags |= FBINFO_MISC_MODESWITCH; - if (blank) { - fbcon_cursor(vc, CM_ERASE); - return 0; - } - - if (!(info->flags & FBINFO_MISC_MODESWITCHLATE)) { + if (!blank && !(info->flags & FBINFO_MISC_MODESWITCHLATE)) { var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; fb_set_var(info, &var); } } - fbcon_cursor(vc, blank ? CM_ERASE : CM_DRAW); - - if (!info->fbops->fb_blank) { - if (blank) { - unsigned short oldc; - u_int height; - u_int y_break; - - oldc = vc->vc_video_erase_char; - vc->vc_video_erase_char &= charmask; - height = vc->vc_rows; - y_break = p->vrows - p->yscroll; - if (height > y_break) { - accel_clear(vc, info, real_y(p, 0), - 0, y_break, vc->vc_cols); - accel_clear(vc, info, real_y(p, y_break), - 0, height - y_break, - vc->vc_cols); - } else - accel_clear(vc, info, real_y(p, 0), - 0, height, vc->vc_cols); - vc->vc_video_erase_char = oldc; - } else - update_screen(vc->vc_num); - return 0; - } else - return fb_blank(info, blank); + if (active) { + if (ops->blank_state != blank) { + ops->blank_state = blank; + fbcon_cursor(vc, blank ? CM_ERASE : CM_DRAW); + ops->cursor_flash = (!blank); + + if (fb_blank(info, blank)) + fbcon_generic_blank(vc, info, blank); + } + + if (!blank) + update_screen(vc->vc_num); + } + + return 0; } static void fbcon_free_font(struct display *p) @@ -2233,7 +2116,7 @@ static int fbcon_get_font(struct vc_data *vc, struct console_font *font) static int fbcon_do_set_font(struct vc_data *vc, int w, int h, u8 * data, int userfont) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct display *p = &fb_display[vc->vc_num]; int resize; int cnt; @@ -2329,7 +2212,7 @@ static int fbcon_do_set_font(struct vc_data *vc, int w, int h, } } else if (CON_IS_VISIBLE(vc) && vt_cons[vc->vc_num]->vc_mode == KD_TEXT) { - accel_clear_margins(vc, info, 0); + fbcon_clear_margins(vc, 0); update_screen(vc->vc_num); } @@ -2352,6 +2235,12 @@ static int fbcon_copy_font(struct vc_data *vc, int con) * User asked to set font; we are guaranteed that * a) width and height are in range 1..32 * b) charcount does not exceed 512 + * but lets not assume that, since someone might someday want to use larger + * fonts. And charcount of 512 is small for unicode support. + * + * However, user space gives the font in 32 rows , regardless of + * actual font height. So a new API is needed if support for larger fonts + * is ever implemented. */ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigned flags) @@ -2359,20 +2248,17 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigne unsigned charcount = font->charcount; int w = font->width; int h = font->height; - int size = h; - int i, k; - u8 *new_data, *data = font->data, *p; + int size; + int i, csum; + u8 *new_data, *data = font->data; + int pitch = (font->width+7) >> 3; + /* Is there a reason why fbconsole couldn't handle any charcount >256? + * If not this check should be changed to charcount < 256 */ if (charcount != 256 && charcount != 512) return -EINVAL; - if (w > 8) { - if (w <= 16) - size *= 2; - else - size *= 4; - } - size *= charcount; + size = h * pitch * charcount; new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size, GFP_USER); @@ -2383,55 +2269,22 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigne FNTSIZE(new_data) = size; FNTCHARCNT(new_data) = charcount; REFCOUNT(new_data) = 0; /* usage counter */ - p = new_data; - if (w <= 8) { - for (i = 0; i < charcount; i++) { - memcpy(p, data, h); - data += 32; - p += h; - } - } else if (w <= 16) { - h *= 2; - for (i = 0; i < charcount; i++) { - memcpy(p, data, h); - data += 64; - p += h; - } - } else if (w <= 24) { - for (i = 0; i < charcount; i++) { - int j; - for (j = 0; j < h; j++) { - memcpy(p, data, 3); - p[3] = 0; - data += 3; - p += sizeof(u32); - } - data += 3 * (32 - h); - } - } else { - h *= 4; - for (i = 0; i < charcount; i++) { - memcpy(p, data, h); - data += 128; - p += h; - } - } - /* we can do it in u32 chunks because of charcount is 256 or 512, so - font length must be multiple of 256, at least. And 256 is multiple - of 4 */ - k = 0; - while (p > new_data) { - p = (u8 *)((u32 *)p - 1); - k += *(u32 *) p; + for (i=0; i< charcount; i++) { + memcpy(new_data + i*h*pitch, data + i*32*pitch, h*pitch); } - FNTSUM(new_data) = k; + + /* Since linux has a nice crc32 function use it for counting font + * checksums. */ + csum = crc32(0, new_data, size); + + FNTSUM(new_data) = csum; /* Check if the same font is on some other console already */ for (i = 0; i < MAX_NR_CONSOLES; i++) { struct vc_data *tmp = vc_cons[i].d; if (fb_display[i].userfont && fb_display[i].fontdata && - FNTSUM(fb_display[i].fontdata) == k && + FNTSUM(fb_display[i].fontdata) == csum && FNTSIZE(fb_display[i].fontdata) == size && tmp->vc_font.width == w && !memcmp(fb_display[i].fontdata, new_data, size)) { @@ -2445,7 +2298,7 @@ static int fbcon_set_font(struct vc_data *vc, struct console_font *font, unsigne static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, char *name) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; struct font_desc *f; if (!name) @@ -2468,12 +2321,13 @@ static struct fb_cmap palette_cmap = { static int fbcon_set_palette(struct vc_data *vc, unsigned char *table) { - struct fb_info *info = registered_fb[(int) con2fb_map[vc->vc_num]]; + struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]]; int i, j, k, depth; u8 val; - if (!info->fbops->fb_blank && console_blanked) + if (fbcon_is_inactive(vc, info)) return -EINVAL; + depth = fb_get_color_depth(info); if (depth > 3) { for (i = j = 0; i < 16; i++) { @@ -2577,7 +2431,7 @@ static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt) static int fbcon_scrolldelta(struct vc_data *vc, int lines) { - struct fb_info *info = registered_fb[(int) con2fb_map[fg_console]]; + struct fb_info *info = registered_fb[con2fb_map[fg_console]]; struct display *p = &fb_display[fg_console]; int offset, limit, scrollback_old; @@ -2613,7 +2467,7 @@ static int fbcon_scrolldelta(struct vc_data *vc, int lines) update_region(vc->vc_num, vc->vc_origin, logo_lines * vc->vc_cols); } - logo_shown = -1; + logo_shown = FBCON_LOGO_CANSHOW; } fbcon_cursor(vc, CM_ERASE | CM_SOFTBACK); fbcon_redraw_softback(vc, p, lines); @@ -2633,10 +2487,9 @@ static int fbcon_scrolldelta(struct vc_data *vc, int lines) if (scrollback_current == scrollback_old) return 0; - if (!info->fbops->fb_blank && - (console_blanked || vt_cons[vc->vc_num]->vc_mode != KD_TEXT - || !lines)) + if (fbcon_is_inactive(vc, info)) return 0; + fbcon_cursor(vc, CM_ERASE); offset = p->yscroll - scrollback_current; @@ -2665,38 +2518,49 @@ static int fbcon_scrolldelta(struct vc_data *vc, int lines) static int fbcon_set_origin(struct vc_data *vc) { - if (softback_lines && !console_blanked) + if (softback_lines) fbcon_scrolldelta(vc, softback_lines); return 0; } static void fbcon_suspended(struct fb_info *info) { + struct vc_data *vc = NULL; + struct fbcon_ops *ops = info->fbcon_par; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + /* Clear cursor, restore saved data */ - info->cursor.enable = 0; - info->fbops->fb_cursor(info, &info->cursor); + fbcon_cursor(vc, CM_ERASE); } static void fbcon_resumed(struct fb_info *info) { struct vc_data *vc; + struct fbcon_ops *ops = info->fbcon_par; - if (info->currcon < 0) + if (!ops || ops->currcon < 0) return; - vc = vc_cons[info->currcon].d; + vc = vc_cons[ops->currcon].d; update_screen(vc->vc_num); } static void fbcon_modechanged(struct fb_info *info) { - struct vc_data *vc = vc_cons[info->currcon].d; + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; struct display *p; int rows, cols; - if (info->currcon < 0 || vt_cons[info->currcon]->vc_mode != - KD_TEXT) + if (!ops || ops->currcon < 0 || vt_cons[ops->currcon]->vc_mode != + KD_TEXT || registered_fb[con2fb_map[ops->currcon]] != info) return; + + vc = vc_cons[ops->currcon].d; + p = &fb_display[vc->vc_num]; info->var.xoffset = info->var.yoffset = p->yscroll = 0; @@ -2734,7 +2598,7 @@ static int fbcon_mode_deleted(struct fb_info *info, /* before deletion, ensure that mode is not in use */ for (i = first_fb_vc; i <= last_fb_vc; i++) { - j = (int) con2fb_map[i]; + j = con2fb_map[i]; if (j == -1) continue; fb_info = registered_fb[j]; @@ -2753,20 +2617,56 @@ static int fbcon_mode_deleted(struct fb_info *info, static int fbcon_fb_registered(int idx) { - int ret = 0; + int ret = 0, i; if (info_idx == -1) { - info_idx = idx; - ret = fbcon_takeover(); + for (i = 0; i < MAX_NR_CONSOLES; i++) { + if (con2fb_map_boot[i] == idx) { + info_idx = idx; + break; + } + } + if (info_idx != -1) + ret = fbcon_takeover(1); + } else { + for (i = 0; i < MAX_NR_CONSOLES; i++) { + if (con2fb_map_boot[i] == idx) + set_con2fb_map(i, idx, 0); + } } return ret; } +static void fbcon_fb_blanked(struct fb_info *info, int blank) +{ + struct fbcon_ops *ops = info->fbcon_par; + int valid = 1; + + if (!ops || ops->currcon < 0 || + vt_cons[ops->currcon]->vc_mode != KD_TEXT || + registered_fb[con2fb_map[ops->currcon]] != info) + valid = 0; + + if (valid) { + struct vc_data *vc; + + vc = vc_cons[ops->currcon].d; + + if (CON_IS_VISIBLE(vc)) { + if (blank) + do_blank_screen(0); + else + do_unblank_screen(0); + } + ops->blank_state = blank; + } +} + static int fbcon_event_notify(struct notifier_block *self, unsigned long action, void *data) { - struct fb_event *event = (struct fb_event *) data; + struct fb_event *event = data; struct fb_info *info = event->info; struct fb_videomode *mode; struct fb_con2fbmap *con2fb; @@ -2783,20 +2683,24 @@ static int fbcon_event_notify(struct notifier_block *self, fbcon_modechanged(info); break; case FB_EVENT_MODE_DELETE: - mode = (struct fb_videomode *) event->data; + mode = event->data; ret = fbcon_mode_deleted(info, mode); break; case FB_EVENT_FB_REGISTERED: ret = fbcon_fb_registered(info->node); break; case FB_EVENT_SET_CONSOLE_MAP: - con2fb = (struct fb_con2fbmap *) event->data; - ret = set_con2fb_map(con2fb->console - 1, con2fb->framebuffer); + con2fb = event->data; + ret = set_con2fb_map(con2fb->console - 1, + con2fb->framebuffer, 1); break; case FB_EVENT_GET_CONSOLE_MAP: - con2fb = (struct fb_con2fbmap *) event->data; + con2fb = event->data; con2fb->framebuffer = con2fb_map[con2fb->console - 1]; break; + case FB_EVENT_BLANK: + fbcon_fb_blanked(info, *(int *)event->data); + break; } return ret; @@ -2854,7 +2758,7 @@ int __init fb_console_init(void) break; } } - fbcon_takeover(); + fbcon_takeover(0); } return 0; @@ -2880,7 +2784,6 @@ module_exit(fb_console_exit); * Visible symbols for modules */ -EXPORT_SYMBOL(fb_display); EXPORT_SYMBOL(fb_con); MODULE_LICENSE("GPL");