/*
 *  linux/drivers/char/console.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>
 *
 * Split out con_write_ctrl_* functions from do_con_write & changed
 * vc_state to function pointer
 * by Russell King <rmk@arm.linux.org.uk>, July 1998
 */

#define CONSOLE_WIP

#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/console_struct.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/tqueue.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 <asm/linux_logo.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);

#ifndef MIN
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#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 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;

/*
 * 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 insert
 * all filp requests to con_task_queue instead of tq_timer and run it from
 * the console_tasklet.  The console_tasklet is protected by the IRQ
 * protected console_lock.
 */
DECLARE_TASK_QUEUE(con_task_queue);

/*
 * For the same reason, we defer scrollback to the console tasklet.
 */
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;
	tasklet_schedule(&console_tasklet);
}

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;

		if (!can_do_color) {
			while (cnt--) *q++ ^= 0x0800;
		} else if (hi_font_mask == 0x100) {
			while (cnt--) {
				u16 a = *q;
				a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | (((a) & 0x0e00) << 4);
				*q++ = a;
			}
		} else {
			while (cnt--) {
				u16 a = *q;
				a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | (((a) & 0x0700) << 4);
				*q++ = a;
			}
		}
	}
#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;
	    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;
}

/*
 * 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(unsigned int lines, unsigned int cols,
	      unsigned int first, unsigned int last)
{
	unsigned int cc, ll, ss, sr, todo = 0;
	unsigned int currcons = fg_console, i;
	unsigned short *newscreens[MAX_NR_CONSOLES];

	cc = (cols ? cols : video_num_columns);
	ll = (lines ? lines : video_num_lines);
	sr = cc << 1;
	ss = sr * ll;

 	for (currcons = first; currcons <= last; currcons++) {
		if (!vc_cons_allocated(currcons) ||
		    (cc == video_num_columns && ll == video_num_lines))
			newscreens[currcons] = NULL;
		else {
			unsigned short *p = (unsigned short *) kmalloc(ss, GFP_USER);
			if (!p) {
				for (i = first; i< currcons; i++)
					if (newscreens[i])
						kfree(newscreens[i]);
				return -ENOMEM;
			}
			newscreens[currcons] = p;
			todo++;
		}
	}
	if (!todo)
		return 0;

	for (currcons = first; currcons <= last; currcons++) {
		unsigned int occ, oll, oss, osr;
		unsigned long ol, nl, nlend, rlth, rrem;
		if (!newscreens[currcons] || !vc_cons_allocated(currcons))
			continue;

		oll = video_num_lines;
		occ = video_num_columns;
		osr = video_size_row;
		oss = screenbuf_size;

		video_num_lines = ll;
		video_num_columns = cc;
		video_size_row = sr;
		screenbuf_size = ss;

		rlth = MIN(osr, sr);
		rrem = sr - rlth;
		ol = origin;
		nl = (long) newscreens[currcons];
		nlend = nl + ss;
		if (ll < oll)
			ol += (oll - ll) * osr;

		update_attr(currcons);

		while (ol < scr_end) {
			scr_memcpyw((unsigned short *) nl, (unsigned short *) ol, rlth);
			if (rrem)
				scr_memsetw((void *)(nl + rlth), video_erase_char, rrem);
			ol += osr;
			nl += sr;
		}
		if (nlend > nl)
			scr_memsetw((void *) nl, video_erase_char, nlend - nl);
		if (kmalloced)
			kfree(screenbuf);
		screenbuf = newscreens[currcons];
		kmalloced = 1;
		screenbuf_size = ss;
		set_origin(currcons);

		/* do part of a reset_terminal() */
		top = 0;
		bottom = video_num_lines;
		gotoxy(currcons, x, y);
		save_cur(currcons);

		if (console_table[currcons]) {
			struct winsize ws, *cws = &console_table[currcons]->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) &&
			    console_table[currcons]->pgrp > 0)
				kill_pg(console_table[currcons]->pgrp, SIGWINCH, 1);
			*cws = ws;
		}

		if (IS_VISIBLE)
			update_screen(currcons);
	}

	return 0;
}


void vc_disallocate(unsigned int currcons)
{
	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;
	}
}

/*
 *	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,0xaa,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;
}

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;
}

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(video_num_lines, deccolm ? 132 : 80);
				/* 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;
		}
}

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;
	}
}

static void insert_line(int currcons, unsigned int nr)
{
	scrdown(currcons,y,bottom,nr);
	need_wrap = 0;
}


static void delete_line(int currcons, unsigned int nr)
{
	scrup(currcons,y,bottom,nr);
	need_wrap = 0;
}

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);
}

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;
	insert_line(currcons, nr);
}

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);
}

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;
	delete_line(currcons, nr);
}

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;
}

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;
}

static int con_write_ctrl_ESnormal(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESesc(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESnonstd(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESpalette(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESsquare(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESgetpars(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESgotpars(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESpercent(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESfunckey(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_EShash(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESsetG0(int, struct tty_struct *, unsigned int);
static int con_write_ctrl_ESsetG1(int, struct tty_struct *, unsigned int);

enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
	EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
	ESpalette };

static void reset_terminal(int currcons, int do_clear)
{
	top		= 0;
	bottom		= video_num_lines;
	vc_state	= con_write_ctrl_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;
	set_leds();

	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);
}

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 = con_write_ctrl_ESnormal;
		return;
	case 27:
		vc_state = con_write_ctrl_ESesc;
		return;
	case 127:
		del(currcons);
		return;
	case 128+27:
		vc_state = con_write_ctrl_ESsquare;
		return;
	}
	vc_state(currcons, tty, c);
}

static int con_write_utf(int currcons, int c)
{
	unsigned int chr;

	/* Combine UTF-8 into Unicode */
	/* Incomplete characters silently ignored */
	if (c < 0x80) {
		utf_count = 0;
		return c;
	}

	if (utf_count > 0 && (c & 0xc0) == 0x80) {
		chr = (utf_char << 6) | (c & 0x3f);
		utf_count--;
		if (utf_count == 0)
			return chr;
	} else {
		unsigned int count;
		if ((c & 0xe0) == 0xc0) {
			count = 1;
			chr = (c & 0x1f);
		} else if ((c & 0xf0) == 0xe0) {
			count = 2;
			chr = (c & 0x0f);
		} else if ((c & 0xf8) == 0xf0) {
			count = 3;
			chr = (c & 0x07);
		} else if ((c & 0xfc) == 0xf8) {
			count = 4;
			chr = (c & 0x03);
		} else if ((c & 0xfe) == 0xfc) {
			count = 5;
			chr = (c & 0x01);
		} else {
			count = 0;
			chr = 0;
		}
		utf_count = count;
	}

	utf_char = chr;
	return -1;
}

static int con_write_ctrl_ESnormal(int currcons, struct tty_struct *tty, unsigned int c)
{
	return 0;
}

static int con_write_ctrl_ESesc(int currcons, struct tty_struct *tty, unsigned int c)
{
	vc_state = con_write_ctrl_ESnormal;
	switch (c) {
	case '[':
		vc_state = con_write_ctrl_ESsquare;
		break;
	case ']':
		vc_state = con_write_ctrl_ESnonstd;
		break;
	case '%':
		vc_state = con_write_ctrl_ESpercent;
		break;
	case 'E':
		cr(currcons);
		lf(currcons);
		break;
	case 'M':
		ri(currcons);
		break;
	case 'D':
		lf(currcons);
		break;
	case 'H':
		tab_stop[x >> 5] |= (1 << (x & 31));
		break;
	case 'Z':
		respond_ID(tty);
		break;
	case '7':
		save_cur(currcons);
		break;
	case '8':
		restore_cur(currcons);
		return 1;
	case '(':
		vc_state = con_write_ctrl_ESsetG0;
		break;
	case ')':
		vc_state = con_write_ctrl_ESsetG1;
		break;
	case '#':
		vc_state = con_write_ctrl_EShash;
		break;
	case 'c':
		reset_terminal(currcons,1);
		return 1;
	case '>':  /* Numeric keypad */
		clr_kbd(kbdapplic);
		break;
	case '=':  /* Appl. keypad */
		set_kbd(kbdapplic);
	 	break;
	}
	return 0;
}

static int con_write_ctrl_ESnonstd(int currcons, struct tty_struct *tty, unsigned int c)
{
	switch (c) {
	case 'P': /* palette escape sequence */
		for (npar=0; npar<NPAR; npar++)
			par[npar] = 0 ;
		npar = 0 ;
		vc_state = con_write_ctrl_ESpalette;
		break;
	case 'R': /* reset palette */
		reset_palette (currcons);
	default:
		vc_state = con_write_ctrl_ESnormal;
	}
	return 0;
}

static int con_write_ctrl_ESpalette(int currcons, struct tty_struct *tty, unsigned int c)
{
	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 = con_write_ctrl_ESnormal;
		}
	} else
		vc_state = con_write_ctrl_ESnormal;
	return 0;
}

static int con_write_ctrl_ESsquare(int currcons, struct tty_struct *tty, unsigned int c)
{
	for(npar = 0 ; npar < NPAR ; npar++)
		par[npar] = 0;
	npar = 0;
	vc_state = con_write_ctrl_ESgetpars;
	if (c == '[') { /* Function key */
		vc_state = con_write_ctrl_ESfunckey;
		return 0;
	}
	ques = (c=='?');
	if (ques)
		return 0;
	return con_write_ctrl_ESgetpars(currcons, tty, c);
}

static int con_write_ctrl_ESgetpars(int currcons, struct tty_struct *tty, unsigned int c)
{
	if (c==';' && npar<NPAR-1) {
		npar++;
		return 0;
	} else if (c>='0' && c<='9') {
		par[npar] *= 10;
		par[npar] += c-'0';
		return 0;
	} else vc_state = con_write_ctrl_ESgotpars;
	return con_write_ctrl_ESgotpars(currcons, tty, c);
}

static int con_write_ctrl_ESgotpars(int currcons, struct tty_struct *tty, unsigned int c)
{
	vc_state = con_write_ctrl_ESnormal;
	switch(c) {
	case 'h':
		set_mode(currcons,1);
		return 0;
	case 'l':
		set_mode(currcons,0);
		return 0;
	case 'c':
		if (ques) {
			if (par[0])
				cursor_type = par[0] | (par[1]<<8) | (par[2]<<16);
			else
				cursor_type = CUR_DEFAULT;
			return 0;
		}
		break;
	case 'm':
		if (ques) {
			clear_selection();
			if (par[0])
				complement_mask = par[0]<<8 | par[1];
			else
				complement_mask = s_complement_mask;
			return 0;
		}
		break;
	case 'n':
		if (!ques) {
			if (par[0] == 5)
				status_report(tty);
			else if (par[0] == 6)
				cursor_report(currcons,tty);
		}
		return 0;
	}
	if (ques) {
		ques = 0;
		return 0;
	}
	switch(c) {
	case 'G': case '`':
		if (par[0]) par[0]--;
		gotoxy(currcons,par[0],y);
		break;
	case 'A':
		if (!par[0]) par[0]++;
		gotoxy(currcons,x,y-par[0]);
		break;
	case 'B': case 'e':
		if (!par[0]) par[0]++;
		gotoxy(currcons,x,y+par[0]);
		break;
	case 'C': case 'a':
		if (!par[0]) par[0]++;
		gotoxy(currcons,x+par[0],y);
		break;
	case 'D':
		if (!par[0]) par[0]++;
		gotoxy(currcons,x-par[0],y);
		break;
	case 'E':
		if (!par[0]) par[0]++;
		gotoxy(currcons,0,y+par[0]);
		break;
	case 'F':
		if (!par[0]) par[0]++;
		gotoxy(currcons,0,y-par[0]);
		break;
	case 'd':
		if (par[0]) par[0]--;
		gotoxay(currcons,x,par[0]);
		break;
	case 'H': case 'f':
		if (par[0]) par[0]--;
		if (par[1]) par[1]--;
		gotoxay(currcons,par[1],par[0]);
		break;
	case 'J':
		csi_J(currcons,par[0]);
		break;
	case 'K':
		csi_K(currcons,par[0]);
		break;
	case 'L':
		csi_L(currcons,par[0]);
		break;
	case 'M':
		csi_M(currcons,par[0]);
		break;
	case 'P':
		csi_P(currcons,par[0]);
		break;
	case 'c':
		if (!par[0])
			respond_ID(tty);
		break;
	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;
		}
		break;
	case 'm':
		csi_m(currcons);
		return 1;
	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);
		break;
	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);
		}
		break;
	case 's':
		save_cur(currcons);
		break;
	case 'u':
		restore_cur(currcons);
		return 1;
	case 'X':
		csi_X(currcons, par[0]);
		break;
	case '@':
		csi_at(currcons,par[0]);
		break;
	case ']': /* setterm functions */
		setterm_command(currcons);
		break;
	}
	return 0;
}

static int con_write_ctrl_ESpercent(int currcons, struct tty_struct *tty, unsigned int c)
{
	vc_state = con_write_ctrl_ESnormal;
	switch (c) {
	case '@':  /* defined in ISO 2022 */
		utf = 0;
		break;
	case 'G':  /* prelim official escape code */
	case '8':  /* retained for compatibility */
		utf = 1;
		break;
	}
	return 0;
}

static int con_write_ctrl_ESfunckey(int currcons, struct tty_struct *tty, unsigned int c)
{
	vc_state = con_write_ctrl_ESnormal;
	return 0;
}

static int con_write_ctrl_EShash(int currcons, struct tty_struct *tty, unsigned int c)
{
	vc_state = con_write_ctrl_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 0;
}

static int con_write_ctrl_ESsetG0(int currcons, struct tty_struct *tty, unsigned int c)
{
	switch (c) {
	case '0':
		G0_charset = GRAF_MAP;
		break;
	case 'B':
		G0_charset = LAT1_MAP;
		break;
	case 'U':
		G0_charset = IBMPC_MAP;
		break;
	case 'K':
		G0_charset = USER_MAP;
		break;
	}
	if (charset == 0) {
		translate = set_translate(G0_charset,currcons);
		return 1;
	}
	vc_state = con_write_ctrl_ESnormal;
	return 0;
}

static int con_write_ctrl_ESsetG1(int currcons, struct tty_struct *tty, unsigned int c)
{
	switch (c) {
	case '0':
		G1_charset = GRAF_MAP;
		break;
	case 'B':
		G1_charset = LAT1_MAP;
		break;
	case 'U':
		G1_charset = IBMPC_MAP;
		break;
	case 'K':
		G1_charset = USER_MAP;
		break;
	}
	if (charset == 1) {
		translate = set_translate(G1_charset,currcons);
		return 1;
	}
	vc_state = con_write_ctrl_ESnormal;
	return 0;
}

/* 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);

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;
	int orig_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;
		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.
	 */

	spin_lock_irq(&console_lock);

	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) {
			tc = con_write_utf(currcons, c);
			if (tc < 0)
				continue;
			c = tc;
		} 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 == con_write_ctrl_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
	spin_unlock_irq(&console_lock);

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 tasklet.
 *
 * Doing console switching in a tasklet 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_lock.
 */
static void console_softint(unsigned long ignored)
{
	/* Runs the task queue outside of the console lock.  These
	 * callbacks can come back into the console code and thus
	 * will perform their own locking.
	 */
	run_task_queue(&con_task_queue);

	spin_lock_irq(&console_lock);

	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;
	}

	spin_unlock_irq(&console_lock);
}

#ifdef CONFIG_VT_CONSOLE

/*
 *	Console on virtual terminal
 *
 * The console_lock must be held 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 currcons 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);

quit:
	clear_bit(0, &printing);
}

static kdev_t vt_console_device(struct console *c)
{
	return MKDEV(TTY_MAJOR, c->index ? c->index : fg_console + 1);
}

struct console vt_console_driver = {
	name:		"tty",
	write:		vt_console_print,
	device:		vt_console_device,
	wait_key:	keyboard_wait_for_keypress,
	unblank:	unblank_screen,
	flags:		CON_PRINTBUFFER,
	index:		-1,
};
#endif

/*
 *	Handling of Linux-specific VC ioctls
 */

int tioclinux(struct tty_struct *tty, unsigned long arg)
{
	char type, data;

	if (tty->driver.type != TTY_DRIVER_TYPE_CONSOLE)
		return -EINVAL;
	if (current->tty != tty && !suser())
		return -EPERM;
	if (get_user(type, (char *)arg))
		return -EFAULT;
	switch (type)
	{
		case 2:
			return set_selection(arg, tty, 1);
		case 3:
			return paste_selection(tty);
		case 4:
			unblank_screen();
			return 0;
		case 5:
			return sel_loadlut(arg);
		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;
			return __put_user(data, (char *) arg);
		case 7:
			data = mouse_reporting();
			return __put_user(data, (char *) arg);
		case 10:
			set_vesa_blanking(arg);
			return 0;
		case 11:	/* set kmsg redirect */
			if (!suser())
				return -EPERM;
			if (get_user(data, (char *)arg+1))
					return -EFAULT;
			kmsg_redirect = data;
			return 0;
		case 12:	/* get fg_console */
			return fg_console;
	}
	return -EINVAL;
}

/*
 *	/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)
{
	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)
{
	unsigned long flags;
	struct vt_struct *vt = (struct vt_struct *)tty->driver_data;

	pm_access(pm_con);
	spin_lock_irqsave(&console_lock, flags);
	set_cursor(vt->vc_num);
	spin_unlock_irqrestore(&console_lock, flags);
}

/*
 * 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];

	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)
{
	if (!tty)
		return;
	if (tty->count != 1) return;
	vcs_make_devfs (MINOR (tty->device) - tty->driver.minor_start, 1);
	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;

DECLARE_TASKLET_DISABLED(console_tasklet, console_softint, 0);

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;
	}

	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");

	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

	tasklet_enable(&console_tasklet);
	tasklet_schedule(&console_tasklet);
}

#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.
 */
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);
}

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;
    }
}

static void vesa_powerdown_screen(unsigned long dummy)
{
	console_timer.function = unblank_screen_t;	/* I don't have a clue why this is necessary */

	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);
}

static void unblank_screen_t(unsigned long dummy)
{
	unblank_screen();
}

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;
	}
	console_timer.function = blank_screen;
	if (blankinterval) {
		mod_timer(&console_timer, jiffies + blankinterval);
	}

	currcons = fg_console;
	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);
}

static void blank_screen(unsigned long dummy)
{
	timer_do_blank_screen(0, 1);
}

void poke_blanked_console(void)
{
	del_timer(&console_timer);	/* Can't use _sync here: called from tasklet */
	if (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;
	}

	spin_lock_irq(&console_lock);
	rc = sw->con_font_op(vc_cons[currcons].d, op);
	spin_unlock_irq(&console_lock);

	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();
		/* Just temporary fix. Remove cursor after wakeup */
#if defined(CONFIG_SA1100_BITSY)
		hide_cursor(fg_console);
#endif
		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_resize);
EXPORT_SYMBOL(fg_console);
EXPORT_SYMBOL(console_blank_hook);
#ifdef CONFIG_VT
EXPORT_SYMBOL(vt_cons);
#endif
#ifndef VT_SINGLE_DRIVER
EXPORT_SYMBOL(take_over_console);
EXPORT_SYMBOL(give_up_console);
#endif
