/* * linux/drivers/char/vt.c * * Copyright (C) 1991, 1992 Linus Torvalds */ /* * Hopefully this will be a rather complete VT102 implementation. * * Beeping thanks to John T Kohl. * * Virtual Consoles, Screen Blanking, Screen Dumping, Color, Graphics * Chars, and VT100 enhancements by Peter MacDonald. * * Copy and paste function by Andrew Haylett, * some enhancements by Alessandro Rubini. * * Code to check for different video-cards mostly by Galen Hunt, * <g-hunt@ee.utah.edu> * * Rudimentary ISO 10646/Unicode/UTF-8 character set support by * Markus Kuhn, <mskuhn@immd4.informatik.uni-erlangen.de>. * * Dynamic allocation of consoles, aeb@cwi.nl, May 1994 * Resizing of consoles, aeb, 940926 * * Code for xterm like mouse click reporting by Peter Orbaek 20-Jul-94 * <poe@daimi.aau.dk> * * User-defined bell sound, new setterm control sequences and printk * redirection by Martin Mares <mj@k332.feld.cvut.cz> 19-Nov-95 * * APM screenblank bug fixed Takashi Manabe <manabe@roy.dsl.tutics.tut.jp> * * Merge with the abstract console driver by Geert Uytterhoeven * <geert@linux-m68k.org>, Jan 1997. * * Original m68k console driver modifications by * * - Arno Griffioen <arno@usn.nl> * - David Carter <carter@cs.bris.ac.uk> * * Note that the abstract console driver allows all consoles to be of * potentially different sizes, so the following variables depend on the * current console (currcons): * * - video_num_columns * - video_num_lines * - video_size_row * - can_do_color * * The abstract console driver provides a generic interface for a text * console. It supports VGA text mode, frame buffer based graphical consoles * and special graphics processors that are only accessible through some * registers (e.g. a TMS340x0 GSP). * * The interface to the hardware is specified using a special structure * (struct consw) which contains function pointers to console operations * (see <linux/console.h> for more information). * * Support for changeable cursor shape * by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>, August 1997 * * Ported to i386 and con_scrolldelta fixed * by Emmanuel Marty <core@ggi-project.org>, April 1998 * * Resurrected character buffers in videoram plus lots of other trickery * by Martin Mares <mj@atrey.karlin.mff.cuni.cz>, July 1998 * * Removed old-style timers, introduced console_timer, made timer * deletion SMP-safe. 17Jun00, Andrew Morton <andrewm@uow.edu.au> * * Removed console_lock, enabled interrupts across all console operations * 13 March 2001, Andrew Morton */ #include <linux/module.h> #include <linux/sched.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/kd.h> #include <linux/slab.h> #include <linux/major.h> #include <linux/mm.h> #include <linux/console.h> #include <linux/init.h> #include <linux/devfs_fs_kernel.h> #include <linux/vt_kern.h> #include <linux/selection.h> #include <linux/kbd_kern.h> #include <linux/consolemap.h> #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/config.h> #include <linux/version.h> #include <linux/workqueue.h> #include <linux/bootmem.h> #include <linux/pm.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/bitops.h> #include "console_macros.h" const struct consw *conswitchp; /* A bitmap for codes <32. A bit of 1 indicates that the code * corresponding to that bit number invokes some special action * (such as cursor movement) and should not be displayed as a * glyph unless the disp_ctrl mode is explicitly enabled. */ #define CTRL_ACTION 0x0d00ff81 #define CTRL_ALWAYS 0x0800f501 /* Cannot be overridden by disp_ctrl */ /* * Here is the default bell parameters: 750HZ, 1/8th of a second */ #define DEFAULT_BELL_PITCH 750 #define DEFAULT_BELL_DURATION (HZ/8) extern void vcs_make_devfs (unsigned int index, int unregister); extern void console_map_init(void); #ifdef CONFIG_PROM_CONSOLE extern void prom_con_init(void); #endif #ifdef CONFIG_MDA_CONSOLE extern void mda_console_init(void); #endif #ifdef CONFIG_FRAMEBUFFER_CONSOLE extern int fb_console_init(void); #endif static struct tty_struct *console_table[MAX_NR_CONSOLES]; static struct termios *console_termios[MAX_NR_CONSOLES]; static struct termios *console_termios_locked[MAX_NR_CONSOLES]; struct vc vc_cons [MAX_NR_CONSOLES]; #ifndef VT_SINGLE_DRIVER static const struct consw *con_driver_map[MAX_NR_CONSOLES]; #endif static int con_open(struct tty_struct *, struct file *); static void vc_init(unsigned int console, unsigned int rows, unsigned int cols, int do_clear); static void blank_screen(unsigned long dummy); static void gotoxy(int currcons, int new_x, int new_y); static void save_cur(int currcons); static void reset_terminal(int currcons, int do_clear); static void con_flush_chars(struct tty_struct *tty); static void set_vesa_blanking(unsigned long arg); static void set_cursor(int currcons); static void hide_cursor(int currcons); static void unblank_screen_t(unsigned long dummy); static void console_callback(void *ignored); static void __init con_init_devfs (void); static int printable; /* Is console ready for printing? */ int do_poke_blanked_console; int console_blanked; static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */ static int blankinterval = 10*60*HZ; static int vesa_off_interval; static DECLARE_WORK(console_work, console_callback, NULL); /* * fg_console is the current virtual console, * last_console is the last used one, * want_console is the console we want to switch to, * kmsg_redirect is the console for kernel messages, */ int fg_console; int last_console; int want_console = -1; int kmsg_redirect; /* * For each existing display, we have a pointer to console currently visible * on that display, allowing consoles other than fg_console to be refreshed * appropriately. Unless the low-level driver supplies its own display_fg * variable, we use this one for the "master display". */ static struct vc_data *master_display_fg; /* * Unfortunately, we need to delay tty echo when we're currently writing to the * console since the code is (and always was) not re-entrant, so we schedule * all flip requests to process context with schedule-task() and run it from * console_callback(). */ /* * For the same reason, we defer scrollback to the console callback. */ static int scrollback_delta; /* * Hook so that the power management routines can (un)blank * the console on our behalf. */ int (*console_blank_hook)(int); static struct timer_list console_timer; /* * Low-Level Functions */ #define IS_FG (currcons == fg_console) #define IS_VISIBLE CON_IS_VISIBLE(vc_cons[currcons].d) #ifdef VT_BUF_VRAM_ONLY #define DO_UPDATE 0 #else #define DO_UPDATE IS_VISIBLE #endif static int pm_con_request(struct pm_dev *dev, pm_request_t rqst, void *data); static struct pm_dev *pm_con; static inline unsigned short *screenpos(int currcons, int offset, int viewed) { unsigned short *p; if (!viewed) p = (unsigned short *)(origin + offset); else if (!sw->con_screen_pos) p = (unsigned short *)(visible_origin + offset); else p = sw->con_screen_pos(vc_cons[currcons].d, offset); return p; } static inline void scrolldelta(int lines) { scrollback_delta += lines; schedule_console_callback(); } void schedule_console_callback(void) { schedule_work(&console_work); } static void scrup(int currcons, unsigned int t, unsigned int b, int nr) { unsigned short *d, *s; if (t+nr >= b) nr = b - t - 1; if (b > video_num_lines || t >= b || nr < 1) return; if (IS_VISIBLE && sw->con_scroll(vc_cons[currcons].d, t, b, SM_UP, nr)) return; d = (unsigned short *) (origin+video_size_row*t); s = (unsigned short *) (origin+video_size_row*(t+nr)); scr_memcpyw(d, s, (b-t-nr) * video_size_row); scr_memsetw(d + (b-t-nr) * video_num_columns, video_erase_char, video_size_row*nr); } static void scrdown(int currcons, unsigned int t, unsigned int b, int nr) { unsigned short *s; unsigned int step; if (t+nr >= b) nr = b - t - 1; if (b > video_num_lines || t >= b || nr < 1) return; if (IS_VISIBLE && sw->con_scroll(vc_cons[currcons].d, t, b, SM_DOWN, nr)) return; s = (unsigned short *) (origin+video_size_row*t); step = video_num_columns * nr; scr_memmovew(s + step, s, (b-t-nr)*video_size_row); scr_memsetw(s, video_erase_char, 2*step); } static void do_update_region(int currcons, unsigned long start, int count) { #ifndef VT_BUF_VRAM_ONLY unsigned int xx, yy, offset; u16 *p; p = (u16 *) start; if (!sw->con_getxy) { offset = (start - origin) / 2; xx = offset % video_num_columns; yy = offset / video_num_columns; } else { int nxx, nyy; start = sw->con_getxy(vc_cons[currcons].d, start, &nxx, &nyy); xx = nxx; yy = nyy; } for(;;) { u16 attrib = scr_readw(p) & 0xff00; int startx = xx; u16 *q = p; while (xx < video_num_columns && count) { if (attrib != (scr_readw(p) & 0xff00)) { if (p > q) sw->con_putcs(vc_cons[currcons].d, q, p-q, yy, startx); startx = xx; q = p; attrib = scr_readw(p) & 0xff00; } p++; xx++; count--; } if (p > q) sw->con_putcs(vc_cons[currcons].d, q, p-q, yy, startx); if (!count) break; xx = 0; yy++; if (sw->con_getxy) { p = (u16 *)start; start = sw->con_getxy(vc_cons[currcons].d, start, NULL, NULL); } } #endif } void update_region(int currcons, unsigned long start, int count) { if (DO_UPDATE) { hide_cursor(currcons); do_update_region(currcons, start, count); set_cursor(currcons); } } /* Structure of attributes is hardware-dependent */ static u8 build_attr(int currcons, u8 _color, u8 _intensity, u8 _blink, u8 _underline, u8 _reverse) { if (sw->con_build_attr) return sw->con_build_attr(vc_cons[currcons].d, _color, _intensity, _blink, _underline, _reverse); #ifndef VT_BUF_VRAM_ONLY /* * ++roman: I completely changed the attribute format for monochrome * mode (!can_do_color). The formerly used MDA (monochrome display * adapter) format didn't allow the combination of certain effects. * Now the attribute is just a bit vector: * Bit 0..1: intensity (0..2) * Bit 2 : underline * Bit 3 : reverse * Bit 7 : blink */ { u8 a = color; if (!can_do_color) return _intensity | (_underline ? 4 : 0) | (_reverse ? 8 : 0) | (_blink ? 0x80 : 0); if (_underline) a = (a & 0xf0) | ulcolor; else if (_intensity == 0) a = (a & 0xf0) | halfcolor; if (_reverse) a = ((a) & 0x88) | ((((a) >> 4) | ((a) << 4)) & 0x77); if (_blink) a ^= 0x80; if (_intensity == 2) a ^= 0x08; if (hi_font_mask == 0x100) a <<= 1; return a; } #else return 0; #endif } static void update_attr(int currcons) { attr = build_attr(currcons, color, intensity, blink, underline, reverse ^ decscnm); video_erase_char = (build_attr(currcons, color, 1, blink, 0, decscnm) << 8) | ' '; } /* Note: inverting the screen twice should revert to the original state */ void invert_screen(int currcons, int offset, int count, int viewed) { unsigned short *p; count /= 2; p = screenpos(currcons, offset, viewed); if (sw->con_invert_region) sw->con_invert_region(vc_cons[currcons].d, p, count); #ifndef VT_BUF_VRAM_ONLY else { u16 *q = p; int cnt = count; u16 a; if (!can_do_color) { while (cnt--) { a = scr_readw(q); a ^= 0x0800; scr_writew(a, q); q++; } } else if (hi_font_mask == 0x100) { while (cnt--) { a = scr_readw(q); a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | (((a) & 0x0e00) << 4); scr_writew(a, q); q++; } } else { while (cnt--) { a = scr_readw(q); a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | (((a) & 0x0700) << 4); scr_writew(a, q); q++; } } } #endif if (DO_UPDATE) do_update_region(currcons, (unsigned long) p, count); } /* used by selection: complement pointer position */ void complement_pos(int currcons, int offset) { static unsigned short *p; static unsigned short old; static unsigned short oldx, oldy; if (p) { scr_writew(old, p); if (DO_UPDATE) sw->con_putc(vc_cons[currcons].d, old, oldy, oldx); } if (offset == -1) p = NULL; else { unsigned short new; p = screenpos(currcons, offset, 1); old = scr_readw(p); new = old ^ complement_mask; scr_writew(new, p); if (DO_UPDATE) { oldx = (offset >> 1) % video_num_columns; oldy = (offset >> 1) / video_num_columns; sw->con_putc(vc_cons[currcons].d, new, oldy, oldx); } } } static void insert_char(int currcons, unsigned int nr) { unsigned short *p, *q = (unsigned short *) pos; p = q + video_num_columns - nr - x; while (--p >= q) scr_writew(scr_readw(p), p + nr); scr_memsetw(q, video_erase_char, nr*2); need_wrap = 0; if (DO_UPDATE) { unsigned short oldattr = attr; sw->con_bmove(vc_cons[currcons].d,y,x,y,x+nr,1, video_num_columns-x-nr); attr = video_erase_char >> 8; while (nr--) sw->con_putc(vc_cons[currcons].d, video_erase_char,y,x+nr); attr = oldattr; } } static void delete_char(int currcons, unsigned int nr) { unsigned int i = x; unsigned short *p = (unsigned short *) pos; while (++i <= video_num_columns - nr) { scr_writew(scr_readw(p+nr), p); p++; } scr_memsetw(p, video_erase_char, nr*2); need_wrap = 0; if (DO_UPDATE) { unsigned short oldattr = attr; sw->con_bmove(vc_cons[currcons].d, y, x+nr, y, x, 1, video_num_columns-x-nr); attr = video_erase_char >> 8; while (nr--) sw->con_putc(vc_cons[currcons].d, video_erase_char, y, video_num_columns-1-nr); attr = oldattr; } } static int softcursor_original; static void add_softcursor(int currcons) { int i = scr_readw((u16 *) pos); u32 type = cursor_type; if (! (type & 0x10)) return; if (softcursor_original != -1) return; softcursor_original = i; i |= ((type >> 8) & 0xff00 ); i ^= ((type) & 0xff00 ); if ((type & 0x20) && ((softcursor_original & 0x7000) == (i & 0x7000))) i ^= 0x7000; if ((type & 0x40) && ((i & 0x700) == ((i & 0x7000) >> 4))) i ^= 0x0700; scr_writew(i, (u16 *) pos); if (DO_UPDATE) sw->con_putc(vc_cons[currcons].d, i, y, x); } static void hide_cursor(int currcons) { if (currcons == sel_cons) clear_selection(); if (softcursor_original != -1) { scr_writew(softcursor_original,(u16 *) pos); if (DO_UPDATE) sw->con_putc(vc_cons[currcons].d, softcursor_original, y, x); softcursor_original = -1; } sw->con_cursor(vc_cons[currcons].d,CM_ERASE); } static void set_cursor(int currcons) { if (!IS_FG || console_blanked || vcmode == KD_GRAPHICS) return; if (deccm) { if (currcons == sel_cons) clear_selection(); add_softcursor(currcons); if ((cursor_type & 0x0f) != 1) sw->con_cursor(vc_cons[currcons].d,CM_DRAW); } else hide_cursor(currcons); } static void set_origin(int currcons) { if (!IS_VISIBLE || !sw->con_set_origin || !sw->con_set_origin(vc_cons[currcons].d)) origin = (unsigned long) screenbuf; visible_origin = origin; scr_end = origin + screenbuf_size; pos = origin + video_size_row*y + 2*x; } static inline void save_screen(int currcons) { if (sw->con_save_screen) sw->con_save_screen(vc_cons[currcons].d); } /* * Redrawing of screen */ void redraw_screen(int new_console, int is_switch) { int redraw = 1; int currcons, old_console; if (!vc_cons_allocated(new_console)) { /* strange ... */ /* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */ return; } if (is_switch) { currcons = fg_console; hide_cursor(currcons); if (fg_console != new_console) { struct vc_data **display = vc_cons[new_console].d->vc_display_fg; old_console = (*display) ? (*display)->vc_num : fg_console; *display = vc_cons[new_console].d; fg_console = new_console; currcons = old_console; if (!IS_VISIBLE) { save_screen(currcons); set_origin(currcons); } currcons = new_console; if (old_console == new_console) redraw = 0; } } else { currcons = new_console; hide_cursor(currcons); } if (redraw) { int update; set_origin(currcons); update = sw->con_switch(vc_cons[currcons].d); set_palette(currcons); if (update && vcmode != KD_GRAPHICS) do_update_region(currcons, origin, screenbuf_size/2); } set_cursor(currcons); if (is_switch) { set_leds(); compute_shiftstate(); } } /* * Allocation, freeing and resizing of VTs. */ int vc_cons_allocated(unsigned int i) { return (i < MAX_NR_CONSOLES && vc_cons[i].d); } static void visual_init(int currcons, int init) { /* ++Geert: sw->con_init determines console size */ sw = conswitchp; #ifndef VT_SINGLE_DRIVER if (con_driver_map[currcons]) sw = con_driver_map[currcons]; #endif cons_num = currcons; display_fg = &master_display_fg; vc_cons[currcons].d->vc_uni_pagedir_loc = &vc_cons[currcons].d->vc_uni_pagedir; vc_cons[currcons].d->vc_uni_pagedir = 0; hi_font_mask = 0; complement_mask = 0; can_do_color = 0; sw->con_init(vc_cons[currcons].d, init); if (!complement_mask) complement_mask = can_do_color ? 0x7700 : 0x0800; s_complement_mask = complement_mask; video_size_row = video_num_columns<<1; screenbuf_size = video_num_lines*video_size_row; } int vc_allocate(unsigned int currcons) /* return 0 on success */ { if (currcons >= MAX_NR_CONSOLES) return -ENXIO; if (!vc_cons[currcons].d) { long p, q; /* prevent users from taking too much memory */ if (currcons >= MAX_NR_USER_CONSOLES && !capable(CAP_SYS_RESOURCE)) return -EPERM; /* due to the granularity of kmalloc, we waste some memory here */ /* the alloc is done in two steps, to optimize the common situation of a 25x80 console (structsize=216, screenbuf_size=4000) */ /* although the numbers above are not valid since long ago, the point is still up-to-date and the comment still has its value even if only as a historical artifact. --mj, July 1998 */ p = (long) kmalloc(structsize, GFP_KERNEL); if (!p) return -ENOMEM; memset((void *)p, 0, structsize); vc_cons[currcons].d = (struct vc_data *)p; vt_cons[currcons] = (struct vt_struct *)(p+sizeof(struct vc_data)); visual_init(currcons, 1); if (!*vc_cons[currcons].d->vc_uni_pagedir_loc) con_set_default_unimap(currcons); q = (long)kmalloc(screenbuf_size, GFP_KERNEL); if (!q) { kfree((char *) p); vc_cons[currcons].d = NULL; vt_cons[currcons] = NULL; return -ENOMEM; } screenbuf = (unsigned short *) q; kmalloced = 1; vc_init(currcons, video_num_lines, video_num_columns, 1); if (!pm_con) { pm_con = pm_register(PM_SYS_DEV, PM_SYS_VGA, pm_con_request); } } return 0; } inline int resize_screen(int currcons, int width, int height) { /* Resizes the resolution of the display adapater */ int err = 0; if (vcmode != KD_GRAPHICS && sw->con_resize) err = sw->con_resize(vc_cons[currcons].d, width, height); return err; } /* * Change # of rows and columns (0 means unchanged/the size of fg_console) * [this is to be used together with some user program * like resize that changes the hardware videomode] */ int vc_resize(int currcons, unsigned int cols, unsigned int lines) { unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0; unsigned int old_cols, old_rows, old_row_size, old_screen_size; unsigned int new_cols, new_rows, new_row_size, new_screen_size; unsigned short *newscreen; if (!vc_cons_allocated(currcons)) return -ENXIO; new_cols = (cols ? cols : video_num_columns); new_rows = (lines ? lines : video_num_lines); new_row_size = new_cols << 1; new_screen_size = new_row_size * new_rows; if (new_cols == video_num_columns && new_rows == video_num_lines) return 0; newscreen = (unsigned short *) kmalloc(new_screen_size, GFP_USER); if (!newscreen) return -ENOMEM; old_rows = video_num_lines; old_cols = video_num_columns; old_row_size = video_size_row; old_screen_size = screenbuf_size; video_num_lines = new_rows; video_num_columns = new_cols; video_size_row = new_row_size; screenbuf_size = new_screen_size; err = resize_screen(currcons, new_cols, new_rows); if (err) return err; rlth = min(old_row_size, new_row_size); rrem = new_row_size - rlth; old_origin = origin; new_origin = (long) newscreen; new_scr_end = new_origin + new_screen_size; if (new_rows < old_rows) old_origin += (old_rows - new_rows) * old_row_size; update_attr(currcons); while (old_origin < scr_end) { scr_memcpyw((unsigned short *) new_origin, (unsigned short *) old_origin, rlth); if (rrem) scr_memsetw((void *)(new_origin + rlth), video_erase_char, rrem); old_origin += old_row_size; new_origin += new_row_size; } if (new_scr_end > new_origin) scr_memsetw((void *) new_origin, video_erase_char, new_scr_end - new_origin); if (kmalloced) kfree(screenbuf); screenbuf = newscreen; kmalloced = 1; screenbuf_size = new_screen_size; if (IS_VISIBLE) err = resize_screen(currcons, new_cols, new_rows); set_origin(currcons); /* do part of a reset_terminal() */ top = 0; bottom = video_num_lines; gotoxy(currcons, x, y); save_cur(currcons); if (vc_cons[currcons].d->vc_tty) { struct winsize ws, *cws = &vc_cons[currcons].d->vc_tty->winsize; memset(&ws, 0, sizeof(ws)); ws.ws_row = video_num_lines; ws.ws_col = video_num_columns; if ((ws.ws_row != cws->ws_row || ws.ws_col != cws->ws_col) && vc_cons[currcons].d->vc_tty->pgrp > 0) kill_pg(vc_cons[currcons].d->vc_tty->pgrp, SIGWINCH, 1); *cws = ws; } if (IS_VISIBLE) update_screen(currcons); return err; } void vc_disallocate(unsigned int currcons) { acquire_console_sem(); if (vc_cons_allocated(currcons)) { sw->con_deinit(vc_cons[currcons].d); if (kmalloced) kfree(screenbuf); if (currcons >= MIN_NR_CONSOLES) kfree(vc_cons[currcons].d); vc_cons[currcons].d = NULL; } release_console_sem(); } /* * VT102 emulator */ #define set_kbd(x) set_vc_kbd_mode(kbd_table+currcons,x) #define clr_kbd(x) clr_vc_kbd_mode(kbd_table+currcons,x) #define is_kbd(x) vc_kbd_mode(kbd_table+currcons,x) #define decarm VC_REPEAT #define decckm VC_CKMODE #define kbdapplic VC_APPLIC #define lnm VC_CRLF /* * this is what the terminal answers to a ESC-Z or csi0c query. */ #define VT100ID "\033[?1;2c" #define VT102ID "\033[?6c" unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15 }; /* the default colour table, for VGA+ colour systems */ int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa, 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff}; int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa, 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff}; int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa, 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff}; /* * gotoxy() must verify all boundaries, because the arguments * might also be negative. If the given position is out of * bounds, the cursor is placed at the nearest margin. */ static void gotoxy(int currcons, int new_x, int new_y) { int min_y, max_y; if (new_x < 0) x = 0; else if (new_x >= video_num_columns) x = video_num_columns - 1; else x = new_x; if (decom) { min_y = top; max_y = bottom; } else { min_y = 0; max_y = video_num_lines; } if (new_y < min_y) y = min_y; else if (new_y >= max_y) y = max_y - 1; else y = new_y; pos = origin + y*video_size_row + (x<<1); need_wrap = 0; } /* for absolute user moves, when decom is set */ static void gotoxay(int currcons, int new_x, int new_y) { gotoxy(currcons, new_x, decom ? (top+new_y) : new_y); } void scrollback(int lines) { int currcons = fg_console; if (!lines) lines = video_num_lines/2; scrolldelta(-lines); } void scrollfront(int lines) { int currcons = fg_console; if (!lines) lines = video_num_lines/2; scrolldelta(lines); } static void lf(int currcons) { /* don't scroll if above bottom of scrolling region, or * if below scrolling region */ if (y+1 == bottom) scrup(currcons,top,bottom,1); else if (y < video_num_lines-1) { y++; pos += video_size_row; } need_wrap = 0; } static void ri(int currcons) { /* don't scroll if below top of scrolling region, or * if above scrolling region */ if (y == top) scrdown(currcons,top,bottom,1); else if (y > 0) { y--; pos -= video_size_row; } need_wrap = 0; } static inline void cr(int currcons) { pos -= x<<1; need_wrap = x = 0; } static inline void bs(int currcons) { if (x) { pos -= 2; x--; need_wrap = 0; } } static inline void del(int currcons) { /* ignored */ } static void csi_J(int currcons, int vpar) { unsigned int count; unsigned short * start; switch (vpar) { case 0: /* erase from cursor to end of display */ count = (scr_end-pos)>>1; start = (unsigned short *) pos; if (DO_UPDATE) { /* do in two stages */ sw->con_clear(vc_cons[currcons].d, y, x, 1, video_num_columns-x); sw->con_clear(vc_cons[currcons].d, y+1, 0, video_num_lines-y-1, video_num_columns); } break; case 1: /* erase from start to cursor */ count = ((pos-origin)>>1)+1; start = (unsigned short *) origin; if (DO_UPDATE) { /* do in two stages */ sw->con_clear(vc_cons[currcons].d, 0, 0, y, video_num_columns); sw->con_clear(vc_cons[currcons].d, y, 0, 1, x + 1); } break; case 2: /* erase whole display */ count = video_num_columns * video_num_lines; start = (unsigned short *) origin; if (DO_UPDATE) sw->con_clear(vc_cons[currcons].d, 0, 0, video_num_lines, video_num_columns); break; default: return; } scr_memsetw(start, video_erase_char, 2*count); need_wrap = 0; } static void csi_K(int currcons, int vpar) { unsigned int count; unsigned short * start; switch (vpar) { case 0: /* erase from cursor to end of line */ count = video_num_columns-x; start = (unsigned short *) pos; if (DO_UPDATE) sw->con_clear(vc_cons[currcons].d, y, x, 1, video_num_columns-x); break; case 1: /* erase from start of line to cursor */ start = (unsigned short *) (pos - (x<<1)); count = x+1; if (DO_UPDATE) sw->con_clear(vc_cons[currcons].d, y, 0, 1, x + 1); break; case 2: /* erase whole line */ start = (unsigned short *) (pos - (x<<1)); count = video_num_columns; if (DO_UPDATE) sw->con_clear(vc_cons[currcons].d, y, 0, 1, video_num_columns); break; default: return; } scr_memsetw(start, video_erase_char, 2 * count); need_wrap = 0; } static void csi_X(int currcons, int vpar) /* erase the following vpar positions */ { /* not vt100? */ int count; if (!vpar) vpar++; count = (vpar > video_num_columns-x) ? (video_num_columns-x) : vpar; scr_memsetw((unsigned short *) pos, video_erase_char, 2 * count); if (DO_UPDATE) sw->con_clear(vc_cons[currcons].d, y, x, 1, count); need_wrap = 0; } static void default_attr(int currcons) { intensity = 1; underline = 0; reverse = 0; blink = 0; color = def_color; } /* console_sem is held */ static void csi_m(int currcons) { int i; for (i=0;i<=npar;i++) switch (par[i]) { case 0: /* all attributes off */ default_attr(currcons); break; case 1: intensity = 2; break; case 2: intensity = 0; break; case 4: underline = 1; break; case 5: blink = 1; break; case 7: reverse = 1; break; case 10: /* ANSI X3.64-1979 (SCO-ish?) * Select primary font, don't display * control chars if defined, don't set * bit 8 on output. */ translate = set_translate(charset == 0 ? G0_charset : G1_charset,currcons); disp_ctrl = 0; toggle_meta = 0; break; case 11: /* ANSI X3.64-1979 (SCO-ish?) * Select first alternate font, lets * chars < 32 be displayed as ROM chars. */ translate = set_translate(IBMPC_MAP,currcons); disp_ctrl = 1; toggle_meta = 0; break; case 12: /* ANSI X3.64-1979 (SCO-ish?) * Select second alternate font, toggle * high bit before displaying as ROM char. */ translate = set_translate(IBMPC_MAP,currcons); disp_ctrl = 1; toggle_meta = 1; break; case 21: case 22: intensity = 1; break; case 24: underline = 0; break; case 25: blink = 0; break; case 27: reverse = 0; break; case 38: /* ANSI X3.64-1979 (SCO-ish?) * Enables underscore, white foreground * with white underscore (Linux - use * default foreground). */ color = (def_color & 0x0f) | background; underline = 1; break; case 39: /* ANSI X3.64-1979 (SCO-ish?) * Disable underline option. * Reset colour to default? It did this * before... */ color = (def_color & 0x0f) | background; underline = 0; break; case 49: color = (def_color & 0xf0) | foreground; break; default: if (par[i] >= 30 && par[i] <= 37) color = color_table[par[i]-30] | background; else if (par[i] >= 40 && par[i] <= 47) color = (color_table[par[i]-40]<<4) | foreground; break; } update_attr(currcons); } static void respond_string(const char * p, struct tty_struct * tty) { while (*p) { tty_insert_flip_char(tty, *p, 0); p++; } con_schedule_flip(tty); } static void cursor_report(int currcons, struct tty_struct * tty) { char buf[40]; sprintf(buf, "\033[%d;%dR", y + (decom ? top+1 : 1), x+1); respond_string(buf, tty); } static inline void status_report(struct tty_struct * tty) { respond_string("\033[0n", tty); /* Terminal ok */ } static inline void respond_ID(struct tty_struct * tty) { respond_string(VT102ID, tty); } void mouse_report(struct tty_struct * tty, int butt, int mrx, int mry) { char buf[8]; sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx), (char)('!' + mry)); respond_string(buf, tty); } /* invoked via ioctl(TIOCLINUX) and through set_selection */ int mouse_reporting(void) { int currcons = fg_console; return report_mouse; } /* console_sem is held */ static void set_mode(int currcons, int on_off) { int i; for (i=0; i<=npar; i++) if (ques) switch(par[i]) { /* DEC private modes set/reset */ case 1: /* Cursor keys send ^[Ox/^[[x */ if (on_off) set_kbd(decckm); else clr_kbd(decckm); break; case 3: /* 80/132 mode switch unimplemented */ deccolm = on_off; #if 0 (void) vc_resize(deccolm ? 132 : 80, video_num_lines); /* this alone does not suffice; some user mode utility has to change the hardware regs */ #endif break; case 5: /* Inverted screen on/off */ if (decscnm != on_off) { decscnm = on_off; invert_screen(currcons, 0, screenbuf_size, 0); update_attr(currcons); } break; case 6: /* Origin relative/absolute */ decom = on_off; gotoxay(currcons,0,0); break; case 7: /* Autowrap on/off */ decawm = on_off; break; case 8: /* Autorepeat on/off */ if (on_off) set_kbd(decarm); else clr_kbd(decarm); break; case 9: report_mouse = on_off ? 1 : 0; break; case 25: /* Cursor on/off */ deccm = on_off; break; case 1000: report_mouse = on_off ? 2 : 0; break; } else switch(par[i]) { /* ANSI modes set/reset */ case 3: /* Monitor (display ctrls) */ disp_ctrl = on_off; break; case 4: /* Insert Mode on/off */ decim = on_off; break; case 20: /* Lf, Enter == CrLf/Lf */ if (on_off) set_kbd(lnm); else clr_kbd(lnm); break; } } /* console_sem is held */ static void setterm_command(int currcons) { switch(par[0]) { case 1: /* set color for underline mode */ if (can_do_color && par[1] < 16) { ulcolor = color_table[par[1]]; if (underline) update_attr(currcons); } break; case 2: /* set color for half intensity mode */ if (can_do_color && par[1] < 16) { halfcolor = color_table[par[1]]; if (intensity == 0) update_attr(currcons); } break; case 8: /* store colors as defaults */ def_color = attr; if (hi_font_mask == 0x100) def_color >>= 1; default_attr(currcons); update_attr(currcons); break; case 9: /* set blanking interval */ blankinterval = ((par[1] < 60) ? par[1] : 60) * 60 * HZ; poke_blanked_console(); break; case 10: /* set bell frequency in Hz */ if (npar >= 1) bell_pitch = par[1]; else bell_pitch = DEFAULT_BELL_PITCH; break; case 11: /* set bell duration in msec */ if (npar >= 1) bell_duration = (par[1] < 2000) ? par[1]*HZ/1000 : 0; else bell_duration = DEFAULT_BELL_DURATION; break; case 12: /* bring specified console to the front */ if (par[1] >= 1 && vc_cons_allocated(par[1]-1)) set_console(par[1] - 1); break; case 13: /* unblank the screen */ poke_blanked_console(); break; case 14: /* set vesa powerdown interval */ vesa_off_interval = ((par[1] < 60) ? par[1] : 60) * 60 * HZ; break; } } /* console_sem is held */ static void csi_at(int currcons, unsigned int nr) { if (nr > video_num_columns - x) nr = video_num_columns - x; else if (!nr) nr = 1; insert_char(currcons, nr); } /* console_sem is held */ static void csi_L(int currcons, unsigned int nr) { if (nr > video_num_lines - y) nr = video_num_lines - y; else if (!nr) nr = 1; scrdown(currcons,y,bottom,nr); need_wrap = 0; } /* console_sem is held */ static void csi_P(int currcons, unsigned int nr) { if (nr > video_num_columns - x) nr = video_num_columns - x; else if (!nr) nr = 1; delete_char(currcons, nr); } /* console_sem is held */ static void csi_M(int currcons, unsigned int nr) { if (nr > video_num_lines - y) nr = video_num_lines - y; else if (!nr) nr=1; scrup(currcons,y,bottom,nr); need_wrap = 0; } /* console_sem is held (except via vc_init->reset_terminal */ static void save_cur(int currcons) { saved_x = x; saved_y = y; s_intensity = intensity; s_underline = underline; s_blink = blink; s_reverse = reverse; s_charset = charset; s_color = color; saved_G0 = G0_charset; saved_G1 = G1_charset; } /* console_sem is held */ static void restore_cur(int currcons) { gotoxy(currcons,saved_x,saved_y); intensity = s_intensity; underline = s_underline; blink = s_blink; reverse = s_reverse; charset = s_charset; color = s_color; G0_charset = saved_G0; G1_charset = saved_G1; translate = set_translate(charset ? G1_charset : G0_charset,currcons); update_attr(currcons); need_wrap = 0; } enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey, EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd, ESpalette }; /* console_sem is held (except via vc_init()) */ static void reset_terminal(int currcons, int do_clear) { top = 0; bottom = video_num_lines; vc_state = ESnormal; ques = 0; translate = set_translate(LAT1_MAP,currcons); G0_charset = LAT1_MAP; G1_charset = GRAF_MAP; charset = 0; need_wrap = 0; report_mouse = 0; utf = 0; utf_count = 0; disp_ctrl = 0; toggle_meta = 0; decscnm = 0; decom = 0; decawm = 1; deccm = 1; decim = 0; set_kbd(decarm); clr_kbd(decckm); clr_kbd(kbdapplic); clr_kbd(lnm); kbd_table[currcons].lockstate = 0; kbd_table[currcons].slockstate = 0; kbd_table[currcons].ledmode = LED_SHOW_FLAGS; kbd_table[currcons].ledflagstate = kbd_table[currcons].default_ledflagstate; /* do not do set_leds here because this causes an endless tasklet loop when the keyboard hasn't been initialized yet */ cursor_type = CUR_DEFAULT; complement_mask = s_complement_mask; default_attr(currcons); update_attr(currcons); tab_stop[0] = 0x01010100; tab_stop[1] = tab_stop[2] = tab_stop[3] = tab_stop[4] = 0x01010101; bell_pitch = DEFAULT_BELL_PITCH; bell_duration = DEFAULT_BELL_DURATION; gotoxy(currcons,0,0); save_cur(currcons); if (do_clear) csi_J(currcons,2); } /* console_sem is held */ static void do_con_trol(struct tty_struct *tty, unsigned int currcons, int c) { /* * Control characters can be used in the _middle_ * of an escape sequence. */ switch (c) { case 0: return; case 7: if (bell_duration) kd_mksound(bell_pitch, bell_duration); return; case 8: bs(currcons); return; case 9: pos -= (x << 1); while (x < video_num_columns - 1) { x++; if (tab_stop[x >> 5] & (1 << (x & 31))) break; } pos += (x << 1); return; case 10: case 11: case 12: lf(currcons); if (!is_kbd(lnm)) return; case 13: cr(currcons); return; case 14: charset = 1; translate = set_translate(G1_charset,currcons); disp_ctrl = 1; return; case 15: charset = 0; translate = set_translate(G0_charset,currcons); disp_ctrl = 0; return; case 24: case 26: vc_state = ESnormal; return; case 27: vc_state = ESesc; return; case 127: del(currcons); return; case 128+27: vc_state = ESsquare; return; } switch(vc_state) { case ESesc: vc_state = ESnormal; switch (c) { case '[': vc_state = ESsquare; return; case ']': vc_state = ESnonstd; return; case '%': vc_state = ESpercent; return; case 'E': cr(currcons); lf(currcons); return; case 'M': ri(currcons); return; case 'D': lf(currcons); return; case 'H': tab_stop[x >> 5] |= (1 << (x & 31)); return; case 'Z': respond_ID(tty); return; case '7': save_cur(currcons); return; case '8': restore_cur(currcons); return; case '(': vc_state = ESsetG0; return; case ')': vc_state = ESsetG1; return; case '#': vc_state = EShash; return; case 'c': reset_terminal(currcons,1); return; case '>': /* Numeric keypad */ clr_kbd(kbdapplic); return; case '=': /* Appl. keypad */ set_kbd(kbdapplic); return; } return; case ESnonstd: if (c=='P') { /* palette escape sequence */ for (npar=0; npar<NPAR; npar++) par[npar] = 0 ; npar = 0 ; vc_state = ESpalette; return; } else if (c=='R') { /* reset palette */ reset_palette(currcons); vc_state = ESnormal; } else vc_state = ESnormal; return; case ESpalette: if ( (c>='0'&&c<='9') || (c>='A'&&c<='F') || (c>='a'&&c<='f') ) { par[npar++] = (c>'9' ? (c&0xDF)-'A'+10 : c-'0') ; if (npar==7) { int i = par[0]*3, j = 1; palette[i] = 16*par[j++]; palette[i++] += par[j++]; palette[i] = 16*par[j++]; palette[i++] += par[j++]; palette[i] = 16*par[j++]; palette[i] += par[j]; set_palette(currcons); vc_state = ESnormal; } } else vc_state = ESnormal; return; case ESsquare: for(npar = 0 ; npar < NPAR ; npar++) par[npar] = 0; npar = 0; vc_state = ESgetpars; if (c == '[') { /* Function key */ vc_state=ESfunckey; return; } ques = (c=='?'); if (ques) return; case ESgetpars: if (c==';' && npar<NPAR-1) { npar++; return; } else if (c>='0' && c<='9') { par[npar] *= 10; par[npar] += c-'0'; return; } else vc_state=ESgotpars; case ESgotpars: vc_state = ESnormal; switch(c) { case 'h': set_mode(currcons,1); return; case 'l': set_mode(currcons,0); return; case 'c': if (ques) { if (par[0]) cursor_type = par[0] | (par[1]<<8) | (par[2]<<16); else cursor_type = CUR_DEFAULT; return; } break; case 'm': if (ques) { clear_selection(); if (par[0]) complement_mask = par[0]<<8 | par[1]; else complement_mask = s_complement_mask; return; } break; case 'n': if (!ques) { if (par[0] == 5) status_report(tty); else if (par[0] == 6) cursor_report(currcons,tty); } return; } if (ques) { ques = 0; return; } switch(c) { case 'G': case '`': if (par[0]) par[0]--; gotoxy(currcons,par[0],y); return; case 'A': if (!par[0]) par[0]++; gotoxy(currcons,x,y-par[0]); return; case 'B': case 'e': if (!par[0]) par[0]++; gotoxy(currcons,x,y+par[0]); return; case 'C': case 'a': if (!par[0]) par[0]++; gotoxy(currcons,x+par[0],y); return; case 'D': if (!par[0]) par[0]++; gotoxy(currcons,x-par[0],y); return; case 'E': if (!par[0]) par[0]++; gotoxy(currcons,0,y+par[0]); return; case 'F': if (!par[0]) par[0]++; gotoxy(currcons,0,y-par[0]); return; case 'd': if (par[0]) par[0]--; gotoxay(currcons,x,par[0]); return; case 'H': case 'f': if (par[0]) par[0]--; if (par[1]) par[1]--; gotoxay(currcons,par[1],par[0]); return; case 'J': csi_J(currcons,par[0]); return; case 'K': csi_K(currcons,par[0]); return; case 'L': csi_L(currcons,par[0]); return; case 'M': csi_M(currcons,par[0]); return; case 'P': csi_P(currcons,par[0]); return; case 'c': if (!par[0]) respond_ID(tty); return; case 'g': if (!par[0]) tab_stop[x >> 5] &= ~(1 << (x & 31)); else if (par[0] == 3) { tab_stop[0] = tab_stop[1] = tab_stop[2] = tab_stop[3] = tab_stop[4] = 0; } return; case 'm': csi_m(currcons); return; case 'q': /* DECLL - but only 3 leds */ /* map 0,1,2,3 to 0,1,2,4 */ if (par[0] < 4) setledstate(kbd_table + currcons, (par[0] < 3) ? par[0] : 4); return; case 'r': if (!par[0]) par[0]++; if (!par[1]) par[1] = video_num_lines; /* Minimum allowed region is 2 lines */ if (par[0] < par[1] && par[1] <= video_num_lines) { top=par[0]-1; bottom=par[1]; gotoxay(currcons,0,0); } return; case 's': save_cur(currcons); return; case 'u': restore_cur(currcons); return; case 'X': csi_X(currcons, par[0]); return; case '@': csi_at(currcons,par[0]); return; case ']': /* setterm functions */ setterm_command(currcons); return; } return; case ESpercent: vc_state = ESnormal; switch (c) { case '@': /* defined in ISO 2022 */ utf = 0; return; case 'G': /* prelim official escape code */ case '8': /* retained for compatibility */ utf = 1; return; } return; case ESfunckey: vc_state = ESnormal; return; case EShash: vc_state = ESnormal; if (c == '8') { /* DEC screen alignment test. kludge :-) */ video_erase_char = (video_erase_char & 0xff00) | 'E'; csi_J(currcons, 2); video_erase_char = (video_erase_char & 0xff00) | ' '; do_update_region(currcons, origin, screenbuf_size/2); } return; case ESsetG0: if (c == '0') G0_charset = GRAF_MAP; else if (c == 'B') G0_charset = LAT1_MAP; else if (c == 'U') G0_charset = IBMPC_MAP; else if (c == 'K') G0_charset = USER_MAP; if (charset == 0) translate = set_translate(G0_charset,currcons); vc_state = ESnormal; return; case ESsetG1: if (c == '0') G1_charset = GRAF_MAP; else if (c == 'B') G1_charset = LAT1_MAP; else if (c == 'U') G1_charset = IBMPC_MAP; else if (c == 'K') G1_charset = USER_MAP; if (charset == 1) translate = set_translate(G1_charset,currcons); vc_state = ESnormal; return; default: vc_state = ESnormal; } } /* This is a temporary buffer used to prepare a tty console write * so that we can easily avoid touching user space while holding the * console spinlock. It is allocated in con_init and is shared by * this code and the vc_screen read/write tty calls. * * We have to allocate this statically in the kernel data section * since console_init (and thus con_init) are called before any * kernel memory allocation is available. */ char con_buf[PAGE_SIZE]; #define CON_BUF_SIZE PAGE_SIZE DECLARE_MUTEX(con_buf_sem); /* acquires console_sem */ static int do_con_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count) { #ifdef VT_BUF_VRAM_ONLY #define FLUSH do { } while(0); #else #define FLUSH if (draw_x >= 0) { \ sw->con_putcs(vc_cons[currcons].d, (u16 *)draw_from, (u16 *)draw_to-(u16 *)draw_from, y, draw_x); \ draw_x = -1; \ } #endif int c, tc, ok, n = 0, draw_x = -1; unsigned int currcons; unsigned long draw_from = 0, draw_to = 0; struct vt_struct *vt = (struct vt_struct *)tty->driver_data; u16 himask, charmask; const unsigned char *orig_buf = NULL; int orig_count; if (in_interrupt()) return count; currcons = vt->vc_num; if (!vc_cons_allocated(currcons)) { /* could this happen? */ static int error = 0; if (!error) { error = 1; printk("con_write: tty %d not allocated\n", currcons+1); } return 0; } orig_buf = buf; orig_count = count; if (from_user) { down(&con_buf_sem); again: if (count > CON_BUF_SIZE) count = CON_BUF_SIZE; console_conditional_schedule(); if (copy_from_user(con_buf, buf, count)) { n = 0; /* ?? are error codes legal here ?? */ goto out; } buf = con_buf; } /* At this point 'buf' is guarenteed to be a kernel buffer * and therefore no access to userspace (and therefore sleeping) * will be needed. The con_buf_sem serializes all tty based * console rendering and vcs write/read operations. We hold * the console spinlock during the entire write. */ acquire_console_sem(); himask = hi_font_mask; charmask = himask ? 0x1ff : 0xff; /* undraw cursor first */ if (IS_FG) hide_cursor(currcons); while (!tty->stopped && count) { c = *buf; buf++; n++; count--; if (utf) { /* Combine UTF-8 into Unicode */ /* Incomplete characters silently ignored */ if(c > 0x7f) { if (utf_count > 0 && (c & 0xc0) == 0x80) { utf_char = (utf_char << 6) | (c & 0x3f); utf_count--; if (utf_count == 0) tc = c = utf_char; else continue; } else { if ((c & 0xe0) == 0xc0) { utf_count = 1; utf_char = (c & 0x1f); } else if ((c & 0xf0) == 0xe0) { utf_count = 2; utf_char = (c & 0x0f); } else if ((c & 0xf8) == 0xf0) { utf_count = 3; utf_char = (c & 0x07); } else if ((c & 0xfc) == 0xf8) { utf_count = 4; utf_char = (c & 0x03); } else if ((c & 0xfe) == 0xfc) { utf_count = 5; utf_char = (c & 0x01); } else utf_count = 0; continue; } } else { tc = c; utf_count = 0; } } else { /* no utf */ tc = translate[toggle_meta ? (c|0x80) : c]; } /* If the original code was a control character we * only allow a glyph to be displayed if the code is * not normally used (such as for cursor movement) or * if the disp_ctrl mode has been explicitly enabled. * Certain characters (as given by the CTRL_ALWAYS * bitmap) are always displayed as control characters, * as the console would be pretty useless without * them; to display an arbitrary font position use the * direct-to-font zone in UTF-8 mode. */ ok = tc && (c >= 32 || (!utf && !(((disp_ctrl ? CTRL_ALWAYS : CTRL_ACTION) >> c) & 1))) && (c != 127 || disp_ctrl) && (c != 128+27); if (vc_state == ESnormal && ok) { /* Now try to find out how to display it */ tc = conv_uni_to_pc(vc_cons[currcons].d, tc); if ( tc == -4 ) { /* If we got -4 (not found) then see if we have defined a replacement character (U+FFFD) */ tc = conv_uni_to_pc(vc_cons[currcons].d, 0xfffd); /* One reason for the -4 can be that we just did a clear_unimap(); try at least to show something. */ if (tc == -4) tc = c; } else if ( tc == -3 ) { /* Bad hash table -- hope for the best */ tc = c; } if (tc & ~charmask) continue; /* Conversion failed */ if (need_wrap || decim) FLUSH if (need_wrap) { cr(currcons); lf(currcons); } if (decim) insert_char(currcons, 1); scr_writew(himask ? ((attr << 8) & ~himask) + ((tc & 0x100) ? himask : 0) + (tc & 0xff) : (attr << 8) + tc, (u16 *) pos); if (DO_UPDATE && draw_x < 0) { draw_x = x; draw_from = pos; } if (x == video_num_columns - 1) { need_wrap = decawm; draw_to = pos+2; } else { x++; draw_to = (pos+=2); } continue; } FLUSH do_con_trol(tty, currcons, c); } FLUSH console_conditional_schedule(); release_console_sem(); out: if (from_user) { /* If the user requested something larger than * the CON_BUF_SIZE, and the tty is not stopped, * keep going. */ if ((orig_count > CON_BUF_SIZE) && !tty->stopped) { orig_count -= CON_BUF_SIZE; orig_buf += CON_BUF_SIZE; count = orig_count; buf = orig_buf; goto again; } up(&con_buf_sem); } return n; #undef FLUSH } /* * This is the console switching callback. * * Doing console switching in a process context allows * us to do the switches asynchronously (needed when we want * to switch due to a keyboard interrupt). Synchronization * with other console code and prevention of re-entrancy is * ensured with console_sem. */ static void console_callback(void *ignored) { acquire_console_sem(); if (want_console >= 0) { if (want_console != fg_console && vc_cons_allocated(want_console)) { hide_cursor(fg_console); change_console(want_console); /* we only changed when the console had already been allocated - a new console is not created in an interrupt routine */ } want_console = -1; } if (do_poke_blanked_console) { /* do not unblank for a LED change */ do_poke_blanked_console = 0; poke_blanked_console(); } if (scrollback_delta) { int currcons = fg_console; clear_selection(); if (vcmode == KD_TEXT) sw->con_scrolldelta(vc_cons[currcons].d, scrollback_delta); scrollback_delta = 0; } release_console_sem(); } void set_console(int nr) { want_console = nr; schedule_console_callback(); } #ifdef CONFIG_VT_CONSOLE /* * Console on virtual terminal * * The console must be locked when we get here. */ void vt_console_print(struct console *co, const char * b, unsigned count) { int currcons = fg_console; unsigned char c; static unsigned long printing; const ushort *start; ushort cnt = 0; ushort myx; /* console busy or not yet initialized */ if (!printable || test_and_set_bit(0, &printing)) return; pm_access(pm_con); if (kmsg_redirect && vc_cons_allocated(kmsg_redirect - 1)) currcons = kmsg_redirect - 1; /* read `x' only after setting currecons properly (otherwise the `x' macro will read the x of the foreground console). */ myx = x; if (!vc_cons_allocated(currcons)) { /* impossible */ /* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */ goto quit; } if (vcmode != KD_TEXT) goto quit; /* undraw cursor first */ if (IS_FG) hide_cursor(currcons); start = (ushort *)pos; /* Contrived structure to try to emulate original need_wrap behaviour * Problems caused when we have need_wrap set on '\n' character */ while (count--) { c = *b++; if (c == 10 || c == 13 || c == 8 || need_wrap) { if (cnt > 0) { if (IS_VISIBLE) sw->con_putcs(vc_cons[currcons].d, start, cnt, y, x); x += cnt; if (need_wrap) x--; cnt = 0; } if (c == 8) { /* backspace */ bs(currcons); start = (ushort *)pos; myx = x; continue; } if (c != 13) lf(currcons); cr(currcons); start = (ushort *)pos; myx = x; if (c == 10 || c == 13) continue; } scr_writew((attr << 8) + c, (unsigned short *) pos); cnt++; if (myx == video_num_columns - 1) { need_wrap = 1; continue; } pos+=2; myx++; } if (cnt > 0) { if (IS_VISIBLE) sw->con_putcs(vc_cons[currcons].d, start, cnt, y, x); x += cnt; if (x == video_num_columns) { x--; need_wrap = 1; } } set_cursor(currcons); if (!oops_in_progress) poke_blanked_console(); quit: clear_bit(0, &printing); } static kdev_t vt_console_device(struct console *c) { return mk_kdev(TTY_MAJOR, c->index ? c->index : fg_console + 1); } struct console vt_console_driver = { .name = "tty", .write = vt_console_print, .device = vt_console_device, .unblank = unblank_screen, .flags = CON_PRINTBUFFER, .index = -1, }; #endif /* * Handling of Linux-specific VC ioctls */ /* * Generally a bit racy with respect to console_sem(). * * There are some functions which don't need it. * * There are some functions which can sleep for arbitrary periods (paste_selection) * but we don't need the lock there anyway. * * set_selection has locking, and definitely needs it */ int tioclinux(struct tty_struct *tty, unsigned long arg) { char type, data; int ret; if (tty->driver.type != TTY_DRIVER_TYPE_CONSOLE) return -EINVAL; if (current->tty != tty && !capable(CAP_SYS_ADMIN)) return -EPERM; if (get_user(type, (char *)arg)) return -EFAULT; ret = 0; switch (type) { case 2: acquire_console_sem(); ret = set_selection(arg, tty, 1); release_console_sem(); break; case 3: ret = paste_selection(tty); break; case 4: unblank_screen(); break; case 5: ret = sel_loadlut(arg); break; case 6: /* * Make it possible to react to Shift+Mousebutton. * Note that 'shift_state' is an undocumented * kernel-internal variable; programs not closely * related to the kernel should not use this. */ data = shift_state; ret = __put_user(data, (char *) arg); break; case 7: data = mouse_reporting(); ret = __put_user(data, (char *) arg); break; case 10: set_vesa_blanking(arg); break;; case 11: /* set kmsg redirect */ if (!capable(CAP_SYS_ADMIN)) { ret = -EPERM; } else { if (get_user(data, (char *)arg+1)) ret = -EFAULT; else kmsg_redirect = data; } break; case 12: /* get fg_console */ ret = fg_console; break; default: ret = -EINVAL; break; } return ret; } /* * /dev/ttyN handling */ static int con_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count) { int retval; pm_access(pm_con); retval = do_con_write(tty, from_user, buf, count); con_flush_chars(tty); return retval; } static void con_put_char(struct tty_struct *tty, unsigned char ch) { if (in_interrupt()) return; /* n_r3964 calls put_char() from interrupt context */ pm_access(pm_con); do_con_write(tty, 0, &ch, 1); } static int con_write_room(struct tty_struct *tty) { if (tty->stopped) return 0; return 4096; /* No limit, really; we're not buffering */ } static int con_chars_in_buffer(struct tty_struct *tty) { return 0; /* we're not buffering */ } /* * con_throttle and con_unthrottle are only used for * paste_selection(), which has to stuff in a large number of * characters... */ static void con_throttle(struct tty_struct *tty) { } static void con_unthrottle(struct tty_struct *tty) { struct vt_struct *vt = (struct vt_struct *) tty->driver_data; wake_up_interruptible(&vt->paste_wait); } /* * Turn the Scroll-Lock LED on when the tty is stopped */ static void con_stop(struct tty_struct *tty) { int console_num; if (!tty) return; console_num = minor(tty->device) - (tty->driver.minor_start); if (!vc_cons_allocated(console_num)) return; set_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK); set_leds(); } /* * Turn the Scroll-Lock LED off when the console is started */ static void con_start(struct tty_struct *tty) { int console_num; if (!tty) return; console_num = minor(tty->device) - (tty->driver.minor_start); if (!vc_cons_allocated(console_num)) return; clr_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK); set_leds(); } static void con_flush_chars(struct tty_struct *tty) { struct vt_struct *vt; if (in_interrupt()) /* from flush_to_ldisc */ return; pm_access(pm_con); /* if we race with con_close(), vt may be null */ acquire_console_sem(); vt = (struct vt_struct *)tty->driver_data; if (vt) set_cursor(vt->vc_num); release_console_sem(); } /* * Allocate the console screen memory. */ static int con_open(struct tty_struct *tty, struct file * filp) { unsigned int currcons; int i; currcons = minor(tty->device) - tty->driver.minor_start; i = vc_allocate(currcons); if (i) return i; vt_cons[currcons]->vc_num = currcons; tty->driver_data = vt_cons[currcons]; vc_cons[currcons].d->vc_tty = tty; if (!tty->winsize.ws_row && !tty->winsize.ws_col) { tty->winsize.ws_row = video_num_lines; tty->winsize.ws_col = video_num_columns; } if (tty->count == 1) vcs_make_devfs (currcons, 0); return 0; } static void con_close(struct tty_struct *tty, struct file * filp) { struct vt_struct *vt; if (!tty) return; if (tty->count != 1) return; vcs_make_devfs (minor(tty->device) - tty->driver.minor_start, 1); vt = (struct vt_struct*)tty->driver_data; if (vt) vc_cons[vt->vc_num].d->vc_tty = NULL; tty->driver_data = 0; } static void vc_init(unsigned int currcons, unsigned int rows, unsigned int cols, int do_clear) { int j, k ; video_num_columns = cols; video_num_lines = rows; video_size_row = cols<<1; screenbuf_size = video_num_lines * video_size_row; set_origin(currcons); pos = origin; reset_vc(currcons); for (j=k=0; j<16; j++) { vc_cons[currcons].d->vc_palette[k++] = default_red[j] ; vc_cons[currcons].d->vc_palette[k++] = default_grn[j] ; vc_cons[currcons].d->vc_palette[k++] = default_blu[j] ; } def_color = 0x07; /* white */ ulcolor = 0x0f; /* bold white */ halfcolor = 0x08; /* grey */ init_waitqueue_head(&vt_cons[currcons]->paste_wait); reset_terminal(currcons, do_clear); } /* * This routine initializes console interrupts, and does nothing * else. If you want the screen to clear, call tty_write with * the appropriate escape-sequence. */ struct tty_driver console_driver; static int console_refcount; void __init con_init(void) { const char *display_desc = NULL; unsigned int currcons = 0; if (conswitchp) display_desc = conswitchp->con_startup(); if (!display_desc) { fg_console = 0; return; } init_timer(&console_timer); console_timer.function = blank_screen; if (blankinterval) { mod_timer(&console_timer, jiffies + blankinterval); } /* * kmalloc is not running yet - we use the bootmem allocator. */ for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) { vc_cons[currcons].d = (struct vc_data *) alloc_bootmem(sizeof(struct vc_data)); vt_cons[currcons] = (struct vt_struct *) alloc_bootmem(sizeof(struct vt_struct)); visual_init(currcons, 1); screenbuf = (unsigned short *) alloc_bootmem(screenbuf_size); kmalloced = 0; vc_init(currcons, video_num_lines, video_num_columns, currcons || !sw->con_save_screen); } currcons = fg_console = 0; master_display_fg = vc_cons[currcons].d; set_origin(currcons); save_screen(currcons); gotoxy(currcons,x,y); csi_J(currcons, 0); update_screen(fg_console); printk("Console: %s %s %dx%d", can_do_color ? "colour" : "mono", display_desc, video_num_columns, video_num_lines); printable = 1; printk("\n"); #ifdef CONFIG_VT_CONSOLE register_console(&vt_console_driver); #endif } int __init vty_init(void) { memset(&console_driver, 0, sizeof(struct tty_driver)); console_driver.magic = TTY_DRIVER_MAGIC; console_driver.name = "vc/%d"; console_driver.name_base = 1; console_driver.major = TTY_MAJOR; console_driver.minor_start = 1; console_driver.num = MAX_NR_CONSOLES; console_driver.type = TTY_DRIVER_TYPE_CONSOLE; console_driver.init_termios = tty_std_termios; console_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS; /* Tell tty_register_driver() to skip consoles because they are * registered before kmalloc() is ready. We'll patch them in later. * See comments at console_init(); see also con_init_devfs(). */ console_driver.flags |= TTY_DRIVER_NO_DEVFS; console_driver.refcount = &console_refcount; console_driver.table = console_table; console_driver.termios = console_termios; console_driver.termios_locked = console_termios_locked; console_driver.open = con_open; console_driver.close = con_close; console_driver.write = con_write; console_driver.write_room = con_write_room; console_driver.put_char = con_put_char; console_driver.flush_chars = con_flush_chars; console_driver.chars_in_buffer = con_chars_in_buffer; console_driver.ioctl = vt_ioctl; console_driver.stop = con_stop; console_driver.start = con_start; console_driver.throttle = con_throttle; console_driver.unthrottle = con_unthrottle; if (tty_register_driver(&console_driver)) panic("Couldn't register console driver\n"); kbd_init(); console_map_init(); #ifdef CONFIG_PROM_CONSOLE prom_con_init(); #endif #ifdef CONFIG_MDA_CONSOLE mda_console_init(); #endif #ifdef CONFIG_FRAMEBUFFER_CONSOLE fb_console_init(); #endif con_init_devfs(); vcs_init(); return 0; } #ifndef VT_SINGLE_DRIVER static void clear_buffer_attributes(int currcons) { unsigned short *p = (unsigned short *) origin; int count = screenbuf_size/2; int mask = hi_font_mask | 0xff; for (; count > 0; count--, p++) { scr_writew((scr_readw(p)&mask) | (video_erase_char&~mask), p); } } /* * If we support more console drivers, this function is used * when a driver wants to take over some existing consoles * and become default driver for newly opened ones. */ void take_over_console(const struct consw *csw, int first, int last, int deflt) { int i, j = -1; const char *desc; desc = csw->con_startup(); if (!desc) return; if (deflt) conswitchp = csw; for (i = first; i <= last; i++) { int old_was_color; int currcons = i; con_driver_map[i] = csw; if (!vc_cons[i].d || !vc_cons[i].d->vc_sw) continue; j = i; if (IS_VISIBLE) save_screen(i); old_was_color = vc_cons[i].d->vc_can_do_color; vc_cons[i].d->vc_sw->con_deinit(vc_cons[i].d); visual_init(i, 0); update_attr(i); /* If the console changed between mono <-> color, then * the attributes in the screenbuf will be wrong. The * following resets all attributes to something sane. */ if (old_was_color != vc_cons[i].d->vc_can_do_color) clear_buffer_attributes(i); if (IS_VISIBLE) update_screen(i); } printk("Console: switching "); if (!deflt) printk("consoles %d-%d ", first+1, last+1); if (j >= 0) printk("to %s %s %dx%d\n", vc_cons[j].d->vc_can_do_color ? "colour" : "mono", desc, vc_cons[j].d->vc_cols, vc_cons[j].d->vc_rows); else printk("to %s\n", desc); } void give_up_console(const struct consw *csw) { int i; for(i = 0; i < MAX_NR_CONSOLES; i++) if (con_driver_map[i] == csw) con_driver_map[i] = NULL; } #endif /* * Screen blanking */ static void set_vesa_blanking(unsigned long arg) { char *argp = (char *)arg + 1; unsigned int mode; get_user(mode, argp); vesa_blank_mode = (mode < 4) ? mode : 0; } /* We can't register the console with devfs during con_init(), because it * is called before kmalloc() works. This function is called later to * do the registration. */ static void __init con_init_devfs (void) { int i; for (i = 0; i < console_driver.num; i++) tty_register_devfs (&console_driver, DEVFS_FL_AOPEN_NOTIFY, console_driver.minor_start + i); } /* * This is called by a timer handler */ static void vesa_powerdown(void) { struct vc_data *c = vc_cons[fg_console].d; /* * Power down if currently suspended (1 or 2), * suspend if currently blanked (0), * else do nothing (i.e. already powered down (3)). * Called only if powerdown features are allowed. */ switch (vesa_blank_mode) { case VESA_NO_BLANKING: c->vc_sw->con_blank(c, VESA_VSYNC_SUSPEND+1); break; case VESA_VSYNC_SUSPEND: case VESA_HSYNC_SUSPEND: c->vc_sw->con_blank(c, VESA_POWERDOWN+1); break; } } /* * This is a timer handler */ static void vesa_powerdown_screen(unsigned long dummy) { console_timer.function = unblank_screen_t; vesa_powerdown(); } static void timer_do_blank_screen(int entering_gfx, int from_timer_handler) { int currcons = fg_console; int i; if (console_blanked) return; /* entering graphics mode? */ if (entering_gfx) { hide_cursor(currcons); save_screen(currcons); sw->con_blank(vc_cons[currcons].d, -1); console_blanked = fg_console + 1; set_origin(currcons); return; } /* don't blank graphics */ if (vcmode != KD_TEXT) { console_blanked = fg_console + 1; return; } hide_cursor(currcons); if (!from_timer_handler) del_timer_sync(&console_timer); if (vesa_off_interval) { console_timer.function = vesa_powerdown_screen; mod_timer(&console_timer, jiffies + vesa_off_interval); } else { if (!from_timer_handler) del_timer_sync(&console_timer); console_timer.function = unblank_screen_t; } save_screen(currcons); /* In case we need to reset origin, blanking hook returns 1 */ i = sw->con_blank(vc_cons[currcons].d, 1); console_blanked = fg_console + 1; if (i) set_origin(currcons); if (console_blank_hook && console_blank_hook(1)) return; if (vesa_blank_mode) sw->con_blank(vc_cons[currcons].d, vesa_blank_mode + 1); } void do_blank_screen(int entering_gfx) { timer_do_blank_screen(entering_gfx, 0); } /* * This is a timer handler */ static void unblank_screen_t(unsigned long dummy) { unblank_screen(); } /* * Called by timer as well as from vt_console_driver */ void unblank_screen(void) { int currcons; if (!console_blanked) return; if (!vc_cons_allocated(fg_console)) { /* impossible */ printk("unblank_screen: tty %d not allocated ??\n", fg_console+1); return; } currcons = fg_console; if (vcmode != KD_TEXT) return; /* but leave console_blanked != 0 */ console_timer.function = blank_screen; if (blankinterval) { mod_timer(&console_timer, jiffies + blankinterval); } console_blanked = 0; if (console_blank_hook) console_blank_hook(0); set_palette(currcons); if (sw->con_blank(vc_cons[currcons].d, 0)) /* Low-level driver cannot restore -> do it ourselves */ update_screen(fg_console); set_cursor(fg_console); } /* * This is both a user-level callable and a timer handler */ static void blank_screen(unsigned long dummy) { timer_do_blank_screen(0, 1); } void poke_blanked_console(void) { del_timer(&console_timer); if (!vt_cons[fg_console] || vt_cons[fg_console]->vc_mode == KD_GRAPHICS) return; if (console_blanked) { console_timer.function = unblank_screen_t; mod_timer(&console_timer, jiffies); /* Now */ } else if (blankinterval) { mod_timer(&console_timer, jiffies + blankinterval); } } /* * Palettes */ void set_palette(int currcons) { if (vcmode != KD_GRAPHICS) sw->con_set_palette(vc_cons[currcons].d, color_table); } static int set_get_cmap(unsigned char *arg, int set) { int i, j, k; for (i = 0; i < 16; i++) if (set) { get_user(default_red[i], arg++); get_user(default_grn[i], arg++); get_user(default_blu[i], arg++); } else { put_user(default_red[i], arg++); put_user(default_grn[i], arg++); put_user(default_blu[i], arg++); } if (set) { for (i = 0; i < MAX_NR_CONSOLES; i++) if (vc_cons_allocated(i)) { for (j = k = 0; j < 16; j++) { vc_cons[i].d->vc_palette[k++] = default_red[j]; vc_cons[i].d->vc_palette[k++] = default_grn[j]; vc_cons[i].d->vc_palette[k++] = default_blu[j]; } set_palette(i); } } return 0; } /* * Load palette into the DAC registers. arg points to a colour * map, 3 bytes per colour, 16 colours, range from 0 to 255. */ int con_set_cmap(unsigned char *arg) { return set_get_cmap (arg,1); } int con_get_cmap(unsigned char *arg) { return set_get_cmap (arg,0); } void reset_palette(int currcons) { int j, k; for (j=k=0; j<16; j++) { palette[k++] = default_red[j]; palette[k++] = default_grn[j]; palette[k++] = default_blu[j]; } set_palette(currcons); } /* * Font switching * * Currently we only support fonts up to 32 pixels wide, at a maximum height * of 32 pixels. Userspace fontdata is stored with 32 bytes (shorts/ints, * depending on width) reserved for each character which is kinda wasty, but * this is done in order to maintain compatibility with the EGA/VGA fonts. It * is upto the actual low-level console-driver convert data into its favorite * format (maybe we should add a `fontoffset' field to the `display' * structure so we wont have to convert the fontdata all the time. * /Jes */ #define max_font_size 65536 int con_font_op(int currcons, struct console_font_op *op) { int rc = -EINVAL; int size = max_font_size, set; u8 *temp = NULL; struct console_font_op old_op; if (vt_cons[currcons]->vc_mode != KD_TEXT) goto quit; memcpy(&old_op, op, sizeof(old_op)); if (op->op == KD_FONT_OP_SET) { if (!op->data) return -EINVAL; if (op->charcount > 512) goto quit; if (!op->height) { /* Need to guess font height [compat] */ int h, i; u8 *charmap = op->data, tmp; /* If from KDFONTOP ioctl, don't allow things which can be done in userland, so that we can get rid of this soon */ if (!(op->flags & KD_FONT_FLAG_OLD)) goto quit; rc = -EFAULT; for (h = 32; h > 0; h--) for (i = 0; i < op->charcount; i++) { if (get_user(tmp, &charmap[32*i+h-1])) goto quit; if (tmp) goto nonzero; } rc = -EINVAL; goto quit; nonzero: rc = -EINVAL; op->height = h; } if (op->width > 32 || op->height > 32) goto quit; size = (op->width+7)/8 * 32 * op->charcount; if (size > max_font_size) return -ENOSPC; set = 1; } else if (op->op == KD_FONT_OP_GET) set = 0; else return sw->con_font_op(vc_cons[currcons].d, op); if (op->data) { temp = kmalloc(size, GFP_KERNEL); if (!temp) return -ENOMEM; if (set && copy_from_user(temp, op->data, size)) { rc = -EFAULT; goto quit; } op->data = temp; } acquire_console_sem(); rc = sw->con_font_op(vc_cons[currcons].d, op); release_console_sem(); op->data = old_op.data; if (!rc && !set) { int c = (op->width+7)/8 * 32 * op->charcount; if (op->data && op->charcount > old_op.charcount) rc = -ENOSPC; if (!(op->flags & KD_FONT_FLAG_OLD)) { if (op->width > old_op.width || op->height > old_op.height) rc = -ENOSPC; } else { if (op->width != 8) rc = -EIO; else if ((old_op.height && op->height > old_op.height) || op->height > 32) rc = -ENOSPC; } if (!rc && op->data && copy_to_user(op->data, temp, c)) rc = -EFAULT; } quit: if (temp) kfree(temp); return rc; } /* * Interface exported to selection and vcs. */ /* used by selection */ u16 screen_glyph(int currcons, int offset) { u16 w = scr_readw(screenpos(currcons, offset, 1)); u16 c = w & 0xff; if (w & hi_font_mask) c |= 0x100; return c; } /* used by vcs - note the word offset */ unsigned short *screen_pos(int currcons, int w_offset, int viewed) { return screenpos(currcons, 2 * w_offset, viewed); } void getconsxy(int currcons, char *p) { p[0] = x; p[1] = y; } void putconsxy(int currcons, char *p) { gotoxy(currcons, p[0], p[1]); set_cursor(currcons); } u16 vcs_scr_readw(int currcons, const u16 *org) { if ((unsigned long)org == pos && softcursor_original != -1) return softcursor_original; return scr_readw(org); } void vcs_scr_writew(int currcons, u16 val, u16 *org) { scr_writew(val, org); if ((unsigned long)org == pos) { softcursor_original = -1; add_softcursor(currcons); } } static int pm_con_request(struct pm_dev *dev, pm_request_t rqst, void *data) { switch (rqst) { case PM_RESUME: unblank_screen(); break; case PM_SUSPEND: do_blank_screen(0); break; } return 0; } /* * Visible symbols for modules */ EXPORT_SYMBOL(color_table); EXPORT_SYMBOL(default_red); EXPORT_SYMBOL(default_grn); EXPORT_SYMBOL(default_blu); EXPORT_SYMBOL(video_font_height); EXPORT_SYMBOL(video_scan_lines); EXPORT_SYMBOL(vc_cons_allocated); EXPORT_SYMBOL(update_region); EXPORT_SYMBOL(redraw_screen); EXPORT_SYMBOL(vc_resize); EXPORT_SYMBOL(fg_console); EXPORT_SYMBOL(console_blank_hook); EXPORT_SYMBOL(console_blanked); EXPORT_SYMBOL(vt_cons); EXPORT_SYMBOL(vc_cons); #ifndef VT_SINGLE_DRIVER EXPORT_SYMBOL(take_over_console); EXPORT_SYMBOL(give_up_console); #endif