view usr/src/boot/sys/boot/common/tem.c @ 19240:5cc8d6ae0443

11973 tem: we only can translate colors 0-7 Reviewed by: Robert Mustacchi <rm@fingolfin.org> Reviewed by: Garrett D'Amore <garrett@damore.org> Approved by: Dan McDonald <danmcd@joyent.com>
author Toomas Soome <tsoome@me.com>
date Thu, 14 Nov 2019 14:35:34 +0200
parents 93a13d54674c
children ccfd88678127
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2016 Joyent, Inc.
 */

/*
 * ANSI terminal emulator module; parse ANSI X3.64 escape sequences and
 * the like.
 *
 * How Virtual Terminal Emulator Works:
 *
 * Every virtual terminal is associated with a tem_vt_state structure
 * and maintains a virtual screen buffer in tvs_screen_buf, which contains
 * all the characters which should be shown on the physical screen when
 * the terminal is activated.
 *
 * Data written to a virtual terminal is composed of characters which
 * should be displayed on the screen when this virtual terminal is
 * activated, fg/bg colors of these characters, and other control
 * information (escape sequence, etc).
 *
 * When data is passed to a virtual terminal it first is parsed for
 * control information by tem_parse().  Subsequently the character
 * and color data are written to tvs_screen_buf.
 * They are saved in buffer in order to refresh the screen when this
 * terminal is activated.  If the terminal is currently active, the data
 * (characters and colors) are also written to the physical screen by
 * invoking a callback function, tem_text_callbacks() or tem_pix_callbacks().
 *
 * When rendering data to the framebuffer, if the framebuffer is in
 * VIS_PIXEL mode, the character data will first be converted to pixel
 * data using tem_pix_bit2pix(), and then the pixels get displayed
 * on the physical screen.  We only store the character and color data in
 * tem_vt_state since the bit2pix conversion only happens when actually
 * rendering to the physical framebuffer.
 */


#include <stand.h>
#include <sys/ascii.h>
#include <sys/errno.h>
#include <sys/tem_impl.h>
#ifdef _HAVE_TEM_FIRMWARE
#include <sys/promif.h>
#endif /* _HAVE_TEM_FIRMWARE */
#include <sys/consplat.h>
#include <sys/kd.h>
#include <stdbool.h>
#include <lz4.h>

/* Terminal emulator internal helper functions */
static void	tems_setup_terminal(struct vis_devinit *, size_t, size_t);
static void	tems_modechange_callback(struct vis_modechg_arg *,
		    struct vis_devinit *);

static void	tems_reset_colormap(void);

static void	tem_free_buf(struct tem_vt_state *);
static void	tem_internal_init(struct tem_vt_state *, boolean_t, boolean_t);
static void	tems_get_initial_color(tem_color_t *pcolor);

static void	tem_control(struct tem_vt_state *, uint8_t);
static void	tem_setparam(struct tem_vt_state *, int, int);
static void	tem_selgraph(struct tem_vt_state *);
static void	tem_chkparam(struct tem_vt_state *, uint8_t);
static void	tem_getparams(struct tem_vt_state *, uint8_t);
static void	tem_outch(struct tem_vt_state *, tem_char_t);
static void	tem_parse(struct tem_vt_state *, tem_char_t);

static void	tem_new_line(struct tem_vt_state *);
static void	tem_cr(struct tem_vt_state *);
static void	tem_lf(struct tem_vt_state *);
static void	tem_send_data(struct tem_vt_state *);
static void	tem_cls(struct tem_vt_state *);
static void	tem_tab(struct tem_vt_state *);
static void	tem_back_tab(struct tem_vt_state *);
static void	tem_clear_tabs(struct tem_vt_state *, int);
static void	tem_set_tab(struct tem_vt_state *);
static void	tem_mv_cursor(struct tem_vt_state *, int, int);
static void	tem_shift(struct tem_vt_state *, int, int);
static void	tem_scroll(struct tem_vt_state *, int, int, int, int);
static void	tem_clear_chars(struct tem_vt_state *tem,
			int count, screen_pos_t row, screen_pos_t col);
static void	tem_copy_area(struct tem_vt_state *tem,
			screen_pos_t s_col, screen_pos_t s_row,
			screen_pos_t e_col, screen_pos_t e_row,
			screen_pos_t t_col, screen_pos_t t_row);
static void	tem_bell(struct tem_vt_state *tem);
static void	tem_pix_clear_prom_output(struct tem_vt_state *tem);

static void	tem_virtual_cls(struct tem_vt_state *, size_t, screen_pos_t,
		    screen_pos_t);
static void	tem_virtual_display(struct tem_vt_state *, term_char_t *,
		    size_t, screen_pos_t, screen_pos_t);
static void	tem_align_cursor(struct tem_vt_state *tem);

static void	tem_check_first_time(struct tem_vt_state *tem);
static void	tem_reset_display(struct tem_vt_state *, boolean_t, boolean_t);
static void	tem_terminal_emulate(struct tem_vt_state *, uint8_t *, int);
static void	tem_text_cursor(struct tem_vt_state *, short);
static void	tem_text_cls(struct tem_vt_state *,
		    int count, screen_pos_t row, screen_pos_t col);
static void	tem_pix_display(struct tem_vt_state *, term_char_t *,
		    int, screen_pos_t, screen_pos_t);
static void	tem_pix_copy(struct tem_vt_state *,
		    screen_pos_t, screen_pos_t,
		    screen_pos_t, screen_pos_t,
		    screen_pos_t, screen_pos_t);
static void	tem_pix_cursor(struct tem_vt_state *, short);
static void	tem_get_attr(struct tem_vt_state *, text_color_t *,
		    text_color_t *, text_attr_t *, uint8_t);
static void	tem_get_color(text_color_t *, text_color_t *, term_char_t);
static void	tem_pix_align(struct tem_vt_state *);
static void	tem_text_display(struct tem_vt_state *, term_char_t *, int,
		    screen_pos_t, screen_pos_t);
static void	tem_text_copy(struct tem_vt_state *,
		    screen_pos_t, screen_pos_t, screen_pos_t, screen_pos_t,
		    screen_pos_t, screen_pos_t);
static void	tem_pix_bit2pix(struct tem_vt_state *, term_char_t);
static void	tem_pix_cls_range(struct tem_vt_state *, screen_pos_t, int,
		    int, screen_pos_t, int, int, boolean_t);
static void	tem_pix_cls(struct tem_vt_state *, int,
		    screen_pos_t, screen_pos_t);

static void	bit_to_pix4(struct tem_vt_state *tem, tem_char_t c,
		    text_color_t fg_color, text_color_t bg_color);
static void	bit_to_pix8(struct tem_vt_state *tem, tem_char_t c,
		    text_color_t fg_color, text_color_t bg_color);
static void	bit_to_pix16(struct tem_vt_state *tem, tem_char_t c,
		    text_color_t fg_color, text_color_t bg_color);
static void	bit_to_pix24(struct tem_vt_state *tem, tem_char_t c,
		    text_color_t fg_color, text_color_t bg_color);
static void	bit_to_pix32(struct tem_vt_state *tem, tem_char_t c,
		    text_color_t fg_color, text_color_t bg_color);

/*
 * Globals
 */
tem_state_t	tems;	/* common term info */

tem_callbacks_t tem_text_callbacks = {
	.tsc_display = &tem_text_display,
	.tsc_copy = &tem_text_copy,
	.tsc_cursor = &tem_text_cursor,
	.tsc_bit2pix = NULL,
	.tsc_cls = &tem_text_cls
};
tem_callbacks_t tem_pix_callbacks = {
	.tsc_display = &tem_pix_display,
	.tsc_copy = &tem_pix_copy,
	.tsc_cursor = &tem_pix_cursor,
	.tsc_bit2pix = &tem_pix_bit2pix,
	.tsc_cls = &tem_pix_cls
};

#define	tem_callback_display	(*tems.ts_callbacks->tsc_display)
#define	tem_callback_copy	(*tems.ts_callbacks->tsc_copy)
#define	tem_callback_cursor	(*tems.ts_callbacks->tsc_cursor)
#define	tem_callback_cls	(*tems.ts_callbacks->tsc_cls)
#define	tem_callback_bit2pix	(*tems.ts_callbacks->tsc_bit2pix)

static void
tem_add(struct tem_vt_state *tem)
{
	list_insert_head(&tems.ts_list, tem);
}

/*
 * This is the main entry point to the module.  It handles output requests
 * during normal system operation, when (e.g.) mutexes are available.
 */
void
tem_write(tem_vt_state_t tem_arg, uint8_t *buf, ssize_t len)
{
	struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg;

	if (tems.ts_initialized == 0 || tem->tvs_initialized == 0) {
		return;
	}

	tem_check_first_time(tem);
	tem_terminal_emulate(tem, buf, len);
}

static void
tem_internal_init(struct tem_vt_state *ptem,
    boolean_t init_color, boolean_t clear_screen)
{
	size_t size, width, height;

	if (tems.ts_display_mode == VIS_PIXEL) {
		ptem->tvs_pix_data_size = tems.ts_pix_data_size;
		ptem->tvs_pix_data = malloc(ptem->tvs_pix_data_size);
	}

	width = tems.ts_c_dimension.width;
	height = tems.ts_c_dimension.height;

	size = width * sizeof (tem_char_t);
	ptem->tvs_outbuf = malloc(size);
	if (ptem->tvs_outbuf == NULL)
		panic("out of memory in tem_internal_init()\n");

	tem_reset_display(ptem, clear_screen, init_color);

	ptem->tvs_utf8_left = 0;
	ptem->tvs_utf8_partial = 0;

	ptem->tvs_initialized  = true;

	/*
	 * Out of memory is not fatal there, without the screen history,
	 * we can not optimize the screen copy.
	 */
	size = width * height * sizeof (term_char_t);
	ptem->tvs_screen_buf = malloc(size);
	tem_virtual_cls(ptem, width * height, 0, 0);
}

int
tem_initialized(tem_vt_state_t tem_arg)
{
	struct tem_vt_state *ptem = (struct tem_vt_state *)tem_arg;

	return (ptem->tvs_initialized);
}

tem_vt_state_t
tem_init(void)
{
	struct tem_vt_state *ptem;

	ptem = calloc(1, sizeof (struct tem_vt_state));
	if (ptem == NULL)
		return ((tem_vt_state_t)ptem);

	ptem->tvs_isactive = false;
	ptem->tvs_fbmode = KD_TEXT;

	/*
	 * A tem is regarded as initialized only after tem_internal_init(),
	 * will be set at the end of tem_internal_init().
	 */
	ptem->tvs_initialized = 0;

	if (!tems.ts_initialized) {
		/*
		 * Only happens during early console configuration.
		 */
		tem_add(ptem);
		return ((tem_vt_state_t)ptem);
	}

	tem_internal_init(ptem, B_TRUE, B_FALSE);
	tem_add(ptem);

	return ((tem_vt_state_t)ptem);
}

/*
 * re-init the tem after video mode has changed and tems_info has
 * been re-inited.
 */
static void
tem_reinit(struct tem_vt_state *tem, boolean_t reset_display)
{
	tem_free_buf(tem); /* only free virtual buffers */

	/* reserve color */
	tem_internal_init(tem, B_FALSE, reset_display);
}

static void
tem_free_buf(struct tem_vt_state *tem)
{
	free(tem->tvs_outbuf);
	tem->tvs_outbuf = NULL;

	free(tem->tvs_pix_data);
	tem->tvs_pix_data = NULL;

	free(tem->tvs_screen_buf);
	tem->tvs_screen_buf = NULL;
}

static int
tems_failed(boolean_t finish_ioctl)
{
	if (finish_ioctl && tems.ts_hdl != NULL)
		(void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_DEVFINI, NULL);

	tems.ts_hdl = NULL;
	return (ENXIO);
}

/*
 * Only called once during boot
 */
int
tem_info_init(struct console *cp)
{
	int			ret;
	struct vis_devinit	temargs;
	size_t height = 0;
	size_t width = 0;
	struct tem_vt_state *p;

	if (tems.ts_initialized) {
		return (0);
	}

	list_create(&tems.ts_list, sizeof (struct tem_vt_state),
	    __offsetof(struct tem_vt_state, tvs_list_node));
	tems.ts_active = NULL;

	tems.ts_hdl = cp;
	bzero(&temargs, sizeof (temargs));
	temargs.modechg_cb  = (vis_modechg_cb_t)tems_modechange_callback;
	temargs.modechg_arg = NULL;

	/*
	 * Initialize the console and get the device parameters
	 */
	if (cp->c_ioctl(cp, VIS_DEVINIT, &temargs) != 0) {
		printf("terminal emulator: Compatible fb not found\n");
		ret = tems_failed(B_FALSE);
		return (ret);
	}

	/* Make sure the fb driver and terminal emulator versions match */
	if (temargs.version != VIS_CONS_REV) {
		printf(
		    "terminal emulator: VIS_CONS_REV %d (see sys/visual_io.h) "
		    "of console fb driver not supported\n", temargs.version);
		ret = tems_failed(B_TRUE);
		return (ret);
	}

	/* other sanity checks */
	if (!((temargs.depth == 4) || (temargs.depth == 8) ||
	    (temargs.depth == 15) || (temargs.depth == 16) ||
	    (temargs.depth == 24) || (temargs.depth == 32))) {
		printf("terminal emulator: unsupported depth\n");
		ret = tems_failed(B_TRUE);
		return (ret);
	}

	if ((temargs.mode != VIS_TEXT) && (temargs.mode != VIS_PIXEL)) {
		printf("terminal emulator: unsupported mode\n");
		ret = tems_failed(B_TRUE);
		return (ret);
	}

	plat_tem_get_prom_size(&height, &width);

	/*
	 * Initialize the common terminal emulator info
	 */
	tems_setup_terminal(&temargs, height, width);

	tems_reset_colormap();
	tems_get_initial_color(&tems.ts_init_color);

	tems.ts_initialized = 1; /* initialization flag */

	for (p = list_head(&tems.ts_list); p != NULL;
	    p = list_next(&tems.ts_list, p)) {
		tem_internal_init(p, B_TRUE, B_FALSE);
		if (temargs.mode == VIS_PIXEL)
			tem_pix_align(p);
	}

	return (0);
}

#define	TEMS_DEPTH_DIFF		0x01
#define	TEMS_DIMENSION_DIFF	0x02

static uint8_t
tems_check_videomode(struct vis_devinit *tp)
{
	uint8_t result = 0;

	if (tems.ts_pdepth != tp->depth)
		result |= TEMS_DEPTH_DIFF;

	if (tp->mode == VIS_TEXT) {
		if (tems.ts_c_dimension.width != tp->width ||
		    tems.ts_c_dimension.height != tp->height)
			result |= TEMS_DIMENSION_DIFF;
	} else {
		if (tems.ts_p_dimension.width != tp->width ||
		    tems.ts_p_dimension.height != tp->height)
			result |= TEMS_DIMENSION_DIFF;
	}
	if (tems.update_font == true)
		result |= TEMS_DIMENSION_DIFF;

	return (result);
}

static int
env_screen_nounset(struct env_var *ev __unused)
{
	if (tems.ts_p_dimension.width == 0 &&
	    tems.ts_p_dimension.height == 0)
		return (0);
	return (EPERM);
}

static void
tems_setup_font(screen_size_t height, screen_size_t width)
{
	bitmap_data_t *font_data;
	int i;

	/*
	 * set_font() will select a appropriate sized font for
	 * the number of rows and columns selected.  If we don't
	 * have a font that will fit, then it will use the
	 * default builtin font and adjust the rows and columns
	 * to fit on the screen.
	 */
	font_data = set_font(&tems.ts_c_dimension.height,
	    &tems.ts_c_dimension.width, height, width);

	/*
	 * The built in font is compressed, to use it, we
	 * uncompress it into the allocated buffer.
	 * To use loaded font, we assign the loaded buffer.
	 * In case of next load, the previously loaded data
	 * is freed by the process of loading the new font.
	 */
	if (tems.ts_font.vf_bytes == NULL) {
		for (i = 0; i < VFNT_MAPS; i++) {
			tems.ts_font.vf_map[i] =
			    font_data->font->vf_map[i];
		}

		if (font_data->compressed_size != 0) {
			/*
			 * We only expect this allocation to
			 * happen at startup, and therefore not to fail.
			 */
			tems.ts_font.vf_bytes =
			    malloc(font_data->uncompressed_size);
			if (tems.ts_font.vf_bytes == NULL)
				panic("out of memory");
			(void) lz4_decompress(
			    font_data->compressed_data,
			    tems.ts_font.vf_bytes,
			    font_data->compressed_size,
			    font_data->uncompressed_size, 0);
		} else {
			tems.ts_font.vf_bytes =
			    font_data->font->vf_bytes;
		}
		tems.ts_font.vf_width = font_data->font->vf_width;
		tems.ts_font.vf_height = font_data->font->vf_height;
		for (i = 0; i < VFNT_MAPS; i++) {
			tems.ts_font.vf_map_count[i] =
			    font_data->font->vf_map_count[i];
		}
	}
}

static void
tems_setup_terminal(struct vis_devinit *tp, size_t height, size_t width)
{
	char env[8];

	tems.ts_pdepth = tp->depth;
	tems.ts_linebytes = tp->linebytes;
	tems.ts_display_mode = tp->mode;
	tems.ts_color_map = tp->color_map;

	switch (tp->mode) {
	case VIS_TEXT:
		/* Set fake pixel dimensions to assist set_font() */
		tems.ts_p_dimension.width = 0;
		tems.ts_p_dimension.height = 0;
		tems.ts_c_dimension.width = tp->width;
		tems.ts_c_dimension.height = tp->height;
		tems.ts_callbacks = &tem_text_callbacks;

		tems_setup_font(16 * tp->height + BORDER_PIXELS,
		    8 * tp->width + BORDER_PIXELS);

		/* ensure the following are not set for text mode */
		unsetenv("screen-height");
		unsetenv("screen-width");
		break;

	case VIS_PIXEL:
		/*
		 * First check to see if the user has specified a screen size.
		 * If so, use those values.  Else use 34x80 as the default.
		 */
		if (width == 0) {
			width = TEM_DEFAULT_COLS;
			height = TEM_DEFAULT_ROWS;
		}
		tems.ts_c_dimension.height = (screen_size_t)height;
		tems.ts_c_dimension.width = (screen_size_t)width;
		tems.ts_p_dimension.height = tp->height;
		tems.ts_p_dimension.width = tp->width;
		tems.ts_callbacks = &tem_pix_callbacks;

		tems_setup_font(tp->height, tp->width);

		snprintf(env, sizeof (env), "%d", tems.ts_p_dimension.height);
		env_setenv("screen-height", EV_VOLATILE | EV_NOHOOK, env,
		    env_noset, env_screen_nounset);
		snprintf(env, sizeof (env), "%d", tems.ts_p_dimension.width);
		env_setenv("screen-width", EV_VOLATILE | EV_NOHOOK, env,
		    env_noset, env_screen_nounset);

		tems.ts_p_offset.y = (tems.ts_p_dimension.height -
		    (tems.ts_c_dimension.height * tems.ts_font.vf_height)) / 2;
		tems.ts_p_offset.x = (tems.ts_p_dimension.width -
		    (tems.ts_c_dimension.width * tems.ts_font.vf_width)) / 2;
		tems.ts_pix_data_size =
		    tems.ts_font.vf_width * tems.ts_font.vf_height;
		tems.ts_pix_data_size *= 4;
		tems.ts_pdepth = tp->depth;

		break;
	}

	tems.update_font = false;

	snprintf(env, sizeof (env), "%d", tems.ts_c_dimension.height);
	env_setenv("screen-#rows", EV_VOLATILE | EV_NOHOOK, env,
	    env_noset, env_nounset);
	snprintf(env, sizeof (env), "%d", tems.ts_c_dimension.width);
	env_setenv("screen-#cols", EV_VOLATILE | EV_NOHOOK, env,
	    env_noset, env_nounset);

	snprintf(env, sizeof (env), "%dx%d", tems.ts_font.vf_width,
	    tems.ts_font.vf_height);
	env_setenv("screen-font", EV_VOLATILE | EV_NOHOOK, env, NULL,
	    NULL);
}

/*
 * This is a callback function that we register with the frame
 * buffer driver layered underneath.  It gets invoked from
 * the underlying frame buffer driver to reconfigure the terminal
 * emulator to a new screen size and depth in conjunction with
 * framebuffer videomode changes.
 * Here we keep the foreground/background color and attributes,
 * which may be different with the initial settings, so that
 * the color won't change while the framebuffer videomode changes.
 * And we also reset the kernel terminal emulator and clear the
 * whole screen.
 */
/* ARGSUSED */
void
tems_modechange_callback(struct vis_modechg_arg *arg __unused,
    struct vis_devinit *devinit)
{
	uint8_t diff;
	struct tem_vt_state *p;
	tem_modechg_cb_t cb;
	tem_modechg_cb_arg_t cb_arg;
	size_t height = 0;
	size_t width = 0;
	int state;

	diff = tems_check_videomode(devinit);
	if (diff == 0) {
		/*
		 * This is color related change, reset color and redraw the
		 * screen. Only need to reinit the active tem.
		 */
		struct tem_vt_state *active = tems.ts_active;
		tems_get_initial_color(&tems.ts_init_color);
		active->tvs_fg_color = tems.ts_init_color.fg_color;
		active->tvs_bg_color = tems.ts_init_color.bg_color;
		active->tvs_flags = tems.ts_init_color.a_flags;
		tem_reinit(active, B_TRUE);
		return;
	}

	diff = diff & TEMS_DIMENSION_DIFF;

	if (diff == 0) {
		/*
		 * Only need to reinit the active tem.
		 */
		struct tem_vt_state *active = tems.ts_active;
		tems.ts_pdepth = devinit->depth;
		/* color depth did change, reset colors */
		tems_reset_colormap();
		tems_get_initial_color(&tems.ts_init_color);
		tem_reinit(active, B_TRUE);

		return;
	}

	plat_tem_get_prom_size(&height, &width);

	state = tems.ts_initialized;
	tems.ts_initialized = 0;	/* stop all output */
	tems_setup_terminal(devinit, height, width);

	tems_reset_colormap();
	tems_get_initial_color(&tems.ts_init_color);
	tems.ts_initialized = state;	/* restore state */

	for (p = list_head(&tems.ts_list); p != NULL;
	    p = list_next(&tems.ts_list, p)) {
		tem_reinit(p, p->tvs_isactive);
	}


	if (tems.ts_modechg_cb == NULL) {
		return;
	}

	cb = tems.ts_modechg_cb;
	cb_arg = tems.ts_modechg_arg;

	cb(cb_arg);
}

/*
 * This function is used to clear entire screen via the underlying framebuffer
 * driver.
 */
int
tems_cls(struct vis_consclear *pda)
{
	if (tems.ts_hdl == NULL)
		return (1);
	return (tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCLEAR, pda));
}

/*
 * This function is used to display a rectangular blit of data
 * of a given size and location via the underlying framebuffer driver.
 * The blit can be as small as a pixel or as large as the screen.
 */
void
tems_display(struct vis_consdisplay *pda)
{
	if (tems.ts_hdl != NULL)
		(void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSDISPLAY, pda);
}

/*
 * This function is used to invoke a block copy operation in the
 * underlying framebuffer driver.  Rectangle copies are how scrolling
 * is implemented, as well as horizontal text shifting escape seqs.
 * such as from vi when deleting characters and words.
 */
void
tems_copy(struct vis_conscopy *pma)
{
	if (tems.ts_hdl != NULL)
		(void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCOPY, pma);
}

/*
 * This function is used to show or hide a rectangluar monochrom
 * pixel inverting, text block cursor via the underlying framebuffer.
 */
void
tems_cursor(struct vis_conscursor *pca)
{
	if (tems.ts_hdl != NULL)
		(void) tems.ts_hdl->c_ioctl(tems.ts_hdl, VIS_CONSCURSOR, pca);
}

static void
tem_kdsetmode(int mode)
{
	if (tems.ts_hdl != NULL) {
		(void) tems.ts_hdl->c_ioctl(tems.ts_hdl, KDSETMODE,
		    (void *)(intptr_t)mode);
	}
}

static void
tems_reset_colormap(void)
{
	struct vis_cmap cm;

	switch (tems.ts_pdepth) {
	case 8:
		cm.index = 0;
		cm.count = 16;
		/* 8-bits (1/3 of TrueColor 24) */
		cm.red   = (uint8_t *)cmap4_to_24.red;
		/* 8-bits (1/3 of TrueColor 24) */
		cm.blue  = (uint8_t *)cmap4_to_24.blue;
		/* 8-bits (1/3 of TrueColor 24) */
		cm.green = (uint8_t *)cmap4_to_24.green;
		if (tems.ts_hdl != NULL)
			(void) tems.ts_hdl->c_ioctl(tems.ts_hdl,
			    VIS_PUTCMAP, &cm);
		break;
	}
}

void
tem_get_size(uint16_t *r, uint16_t *c, uint16_t *x, uint16_t *y)
{
	*r = (uint16_t)tems.ts_c_dimension.height;
	*c = (uint16_t)tems.ts_c_dimension.width;
	*x = (uint16_t)tems.ts_p_dimension.width;
	*y = (uint16_t)tems.ts_p_dimension.height;
}

/*
 * Loader extension. Store important data in environment. Intended to be used
 * just before booting the OS to make the data available in kernel
 * environment module.
 */
void
tem_save_state(void)
{
	struct tem_vt_state *active = tems.ts_active;
	char buf[80];

	/*
	 * We already have in environment:
	 * tem.inverse, tem.inverse_screen
	 * tem.fg_color, tem.bg_color.
	 * So we only need to add the position of the cursor.
	 */

	if (active != NULL) {
		snprintf(buf, sizeof (buf), "%d", active->tvs_c_cursor.col);
		setenv("tem.cursor.col", buf, 1);
		snprintf(buf, sizeof (buf), "%d", active->tvs_c_cursor.row);
		setenv("tem.cursor.row", buf, 1);
	}
}

void
tem_register_modechg_cb(tem_modechg_cb_t func, tem_modechg_cb_arg_t arg)
{
	tems.ts_modechg_cb = func;
	tems.ts_modechg_arg = arg;
}

/*
 * This function is to scroll up the OBP output, which has
 * different screen height and width with our kernel console.
 */
static void
tem_prom_scroll_up(struct tem_vt_state *tem, int nrows)
{
	struct vis_conscopy	ma;
	int	ncols, width;

	/* copy */
	ma.s_row = nrows * tems.ts_font.vf_height;
	ma.e_row = tems.ts_p_dimension.height - 1;
	ma.t_row = 0;

	ma.s_col = 0;
	ma.e_col = tems.ts_p_dimension.width - 1;
	ma.t_col = 0;

	tems_copy(&ma);

	/* clear */
	width = tems.ts_font.vf_width;
	ncols = (tems.ts_p_dimension.width + (width - 1)) / width;

	tem_pix_cls_range(tem, 0, nrows, tems.ts_p_offset.y,
	    0, ncols, 0, B_TRUE);
}

/*
 * This function is to compute the starting row of the console, according to
 * PROM cursor's position. Here we have to take different fonts into account.
 */
static int
tem_adjust_row(struct tem_vt_state *tem, int prom_row)
{
	int	tem_row;
	int	tem_y;
	int	prom_charheight = 0;
	int	prom_window_top = 0;
	int	scroll_up_lines;

	plat_tem_get_prom_font_size(&prom_charheight, &prom_window_top);
	if (prom_charheight == 0)
		prom_charheight = tems.ts_font.vf_height;

	tem_y = (prom_row + 1) * prom_charheight + prom_window_top -
	    tems.ts_p_offset.y;
	tem_row = (tem_y + tems.ts_font.vf_height - 1) /
	    tems.ts_font.vf_height - 1;

	if (tem_row < 0) {
		tem_row = 0;
	} else if (tem_row >= (tems.ts_c_dimension.height - 1)) {
		/*
		 * Scroll up the prom outputs if the PROM cursor's position is
		 * below our tem's lower boundary.
		 */
		scroll_up_lines = tem_row -
		    (tems.ts_c_dimension.height - 1);
		tem_prom_scroll_up(tem, scroll_up_lines);
		tem_row = tems.ts_c_dimension.height - 1;
	}

	return (tem_row);
}

static void
tem_pix_align(struct tem_vt_state *tem)
{
	uint32_t row = 0;
	uint32_t col = 0;

	if (plat_stdout_is_framebuffer()) {
		plat_tem_hide_prom_cursor();

		/*
		 * We are getting the current cursor position in pixel
		 * mode so that we don't over-write the console output
		 * during boot.
		 */
		plat_tem_get_prom_pos(&row, &col);

		/*
		 * Adjust the row if necessary when the font of our
		 * kernel console tem is different with that of prom
		 * tem.
		 */
		row = tem_adjust_row(tem, row);

		/* first line of our kernel console output */
		tem->tvs_first_line = row + 1;

		/* re-set and align cursor position */
		tem->tvs_s_cursor.row = tem->tvs_c_cursor.row =
		    (screen_pos_t)row;
		tem->tvs_s_cursor.col = tem->tvs_c_cursor.col = 0;
	} else {
		tem_reset_display(tem, B_TRUE, B_TRUE);
	}
}

static void
tems_get_inverses(boolean_t *p_inverse, boolean_t *p_inverse_screen)
{
	int i_inverse = 0;
	int i_inverse_screen = 0;

	plat_tem_get_inverses(&i_inverse, &i_inverse_screen);

	*p_inverse = (i_inverse == 0) ? B_FALSE : B_TRUE;
	*p_inverse_screen = (i_inverse_screen == 0) ? B_FALSE : B_TRUE;
}

/*
 * Get the foreground/background color and attributes from environment.
 */
static void
tems_get_initial_color(tem_color_t *pcolor)
{
	boolean_t inverse, inverse_screen;
	unsigned short  flags = 0;

	pcolor->fg_color = DEFAULT_ANSI_FOREGROUND;
	pcolor->bg_color = DEFAULT_ANSI_BACKGROUND;
	plat_tem_get_colors(&pcolor->fg_color, &pcolor->bg_color);

	tems_get_inverses(&inverse, &inverse_screen);
	if (inverse)
		flags |= TEM_ATTR_REVERSE;
	if (inverse_screen)
		flags |= TEM_ATTR_SCREEN_REVERSE;

	/*
	 * In case of black on white we want bright white for BG.
	 * In case if white on black, to improve readability,
	 * we want bold white.
	 */
	if (flags != 0) {
		/*
		 * If either reverse flag is set, the screen is in
		 * white-on-black mode.  We set the bold flag to
		 * improve readability.
		 */
		flags |= TEM_ATTR_BOLD;
	} else {
		/*
		 * Otherwise, the screen is in black-on-white mode.
		 * The SPARC PROM console, which starts in this mode,
		 * uses the bright white background colour so we
		 * match it here.
		 */
		if (pcolor->bg_color == ANSI_COLOR_WHITE)
			flags |= TEM_ATTR_BRIGHT_BG;
	}

	pcolor->a_flags = flags;
}

void
tem_activate(tem_vt_state_t tem_arg, boolean_t unblank)
{
	struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg;

	tems.ts_active = tem;
	tem->tvs_isactive = true;

	tem_kdsetmode(tem->tvs_fbmode);

	if (unblank)
		tem_cls(tem);
}

static void
tem_check_first_time(struct tem_vt_state *tem)
{
	static int first_time = 1;

	/*
	 * Realign the console cursor. We did this in tem_init().
	 * However, drivers in the console stream may emit additional
	 * messages before we are ready. This causes text overwrite
	 * on the screen. This is a workaround.
	 */
	if (!first_time)
		return;

	first_time = 0;
	if (tems.ts_display_mode == VIS_TEXT)
		tem_text_cursor(tem, VIS_GET_CURSOR);
	else
		tem_pix_cursor(tem, VIS_GET_CURSOR);
	tem_align_cursor(tem);
}

/* Process partial UTF-8 sequence. */
static void
tem_input_partial(struct tem_vt_state *tem)
{
	unsigned i;
	tem_char_t c;

	if (tem->tvs_utf8_left == 0)
		return;

	for (i = 0; i < sizeof (tem->tvs_utf8_partial); i++) {
		c = (tem->tvs_utf8_partial >> (24 - (i << 3))) & 0xff;
		if (c != 0) {
			tem_parse(tem, c);
		}
	}
	tem->tvs_utf8_left = 0;
	tem->tvs_utf8_partial = 0;
}

/*
 * Handle UTF-8 sequences.
 */
static void
tem_input_byte(struct tem_vt_state *tem, uint8_t c)
{
	/*
	 * Check for UTF-8 code points. In case of error fall back to
	 * 8-bit code. As we only have 8859-1 fonts for console, this will set
	 * the limits on what chars we actually can display, therefore we
	 * have to return to this code once we have solved the font issue.
	 */
	if ((c & 0x80) == 0x00) {
		/* One-byte sequence. */
		tem_input_partial(tem);
		tem_parse(tem, c);
		return;
	}
	if ((c & 0xe0) == 0xc0) {
		/* Two-byte sequence. */
		tem_input_partial(tem);
		tem->tvs_utf8_left = 1;
		tem->tvs_utf8_partial = c;
		return;
	}
	if ((c & 0xf0) == 0xe0) {
		/* Three-byte sequence. */
		tem_input_partial(tem);
		tem->tvs_utf8_left = 2;
		tem->tvs_utf8_partial = c;
		return;
	}
	if ((c & 0xf8) == 0xf0) {
		/* Four-byte sequence. */
		tem_input_partial(tem);
		tem->tvs_utf8_left = 3;
		tem->tvs_utf8_partial = c;
		return;
	}
	if ((c & 0xc0) == 0x80) {
		/* Invalid state? */
		if (tem->tvs_utf8_left == 0) {
			tem_parse(tem, c);
			return;
		}
		tem->tvs_utf8_left--;
		tem->tvs_utf8_partial = (tem->tvs_utf8_partial << 8) | c;
		if (tem->tvs_utf8_left == 0) {
			tem_char_t v, u;
			uint8_t b;

			/*
			 * Transform the sequence of 2 to 4 bytes to
			 * unicode number.
			 */
			v = 0;
			u = tem->tvs_utf8_partial;
			b = (u >> 24) & 0xff;
			if (b != 0) {		/* Four-byte sequence */
				v = b & 0x07;
				b = (u >> 16) & 0xff;
				v = (v << 6) | (b & 0x3f);
				b = (u >> 8) & 0xff;
				v = (v << 6) | (b & 0x3f);
				b = u & 0xff;
				v = (v << 6) | (b & 0x3f);
			} else if ((b = (u >> 16) & 0xff) != 0) {
				v = b & 0x0f;	/* Three-byte sequence */
				b = (u >> 8) & 0xff;
				v = (v << 6) | (b & 0x3f);
				b = u & 0xff;
				v = (v << 6) | (b & 0x3f);
			} else if ((b = (u >> 8) & 0xff) != 0) {
				v = b & 0x1f;	/* Two-byte sequence */
				b = u & 0xff;
				v = (v << 6) | (b & 0x3f);
			}

			tem_parse(tem, v);
			tem->tvs_utf8_partial = 0;
		}
		return;
	}
	/* Anything left is illegal in UTF-8 sequence. */
	tem_input_partial(tem);
	tem_parse(tem, c);
}

/*
 * This is the main entry point into the terminal emulator.
 *
 * For each data message coming downstream, ANSI assumes that it is composed
 * of ASCII characters, which are treated as a byte-stream input to the
 * parsing state machine. All data is parsed immediately -- there is
 * no enqueing.
 */
static void
tem_terminal_emulate(struct tem_vt_state *tem, uint8_t *buf, int len)
{
	if (tem->tvs_isactive)
		tem_callback_cursor(tem, VIS_HIDE_CURSOR);

	for (; len > 0; len--, buf++)
		tem_input_byte(tem, *buf);

	/*
	 * Send the data we just got to the framebuffer.
	 */
	tem_send_data(tem);

	if (tem->tvs_isactive)
		tem_callback_cursor(tem, VIS_DISPLAY_CURSOR);
}

/*
 * send the appropriate control message or set state based on the
 * value of the control character ch
 */

static void
tem_control(struct tem_vt_state *tem, uint8_t ch)
{
	tem->tvs_state = A_STATE_START;
	switch (ch) {
	case A_BEL:
		tem_bell(tem);
		break;

	case A_BS:
		tem_mv_cursor(tem,
		    tem->tvs_c_cursor.row,
		    tem->tvs_c_cursor.col - 1);
		break;

	case A_HT:
		tem_tab(tem);
		break;

	case A_NL:
		/*
		 * tem_send_data(tem, credp, called_from);
		 * tem_new_line(tem, credp, called_from);
		 * break;
		 */

	case A_VT:
		tem_send_data(tem);
		tem_lf(tem);
		break;

	case A_FF:
		tem_send_data(tem);
		tem_cls(tem);
		break;

	case A_CR:
		tem_send_data(tem);
		tem_cr(tem);
		break;

	case A_ESC:
		tem->tvs_state = A_STATE_ESC;
		break;

	case A_CSI:
		tem->tvs_curparam = 0;
		tem->tvs_paramval = 0;
		tem->tvs_gotparam = B_FALSE;
		/* clear the parameters */
		for (int i = 0; i < TEM_MAXPARAMS; i++)
			tem->tvs_params[i] = -1;
		tem->tvs_state = A_STATE_CSI;
		break;

	case A_GS:
		tem_back_tab(tem);
		break;

	default:
		break;
	}
}


/*
 * if parameters [0..count - 1] are not set, set them to the value
 * of newparam.
 */

static void
tem_setparam(struct tem_vt_state *tem, int count, int newparam)
{
	int i;

	for (i = 0; i < count; i++) {
		if (tem->tvs_params[i] == -1)
			tem->tvs_params[i] = newparam;
	}
}

static void
tem_select_color(struct tem_vt_state *tem, text_color_t color, bool fg)
{
	if (tems.ts_pdepth >= 24 ||
	    (color < 8 && tems.ts_pdepth < 24)) {
		if (fg == true) {
			tem->tvs_fg_color = color;
			tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG;
		} else {
			tem->tvs_bg_color = color;
			tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG;
		}
		return;
	}

	if (color > 15)
		return;

	/* Bright color and depth < 24 */
	color -= 8;
	if (fg == true) {
		tem->tvs_fg_color = color;
		tem->tvs_flags |= TEM_ATTR_BRIGHT_FG;
	} else {
		tem->tvs_bg_color = color;
		tem->tvs_flags |= TEM_ATTR_BRIGHT_BG;
	}
}

/*
 * select graphics mode based on the param vals stored in a_params
 */
static void
tem_selgraph(struct tem_vt_state *tem)
{
	int curparam;
	int count = 0;
	int param;

	tem->tvs_state = A_STATE_START;

	curparam = tem->tvs_curparam;
	do {
		param = tem->tvs_params[count];

		switch (param) {
		case -1:
		case 0:
			/* reset to initial normal settings */
			tem->tvs_fg_color = tems.ts_init_color.fg_color;
			tem->tvs_bg_color = tems.ts_init_color.bg_color;
			tem->tvs_flags = tems.ts_init_color.a_flags;
			break;

		case 1: /* Bold Intense */
			tem->tvs_flags |= TEM_ATTR_BOLD;
			break;

		case 2: /* Faint Intense */
			tem->tvs_flags &= ~TEM_ATTR_BOLD;
			break;

		case 4: /* Underline */
			tem->tvs_flags |= TEM_ATTR_UNDERLINE;
			break;

		case 5: /* Blink */
			tem->tvs_flags |= TEM_ATTR_BLINK;
			break;

		case 7: /* Reverse video */
			if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) {
				tem->tvs_flags &= ~TEM_ATTR_REVERSE;
			} else {
				tem->tvs_flags |= TEM_ATTR_REVERSE;
			}
			break;

		case 22: /* Remove Bold */
			tem->tvs_flags &= ~TEM_ATTR_BOLD;
			break;

		case 24: /* Remove Underline */
			tem->tvs_flags &= ~TEM_ATTR_UNDERLINE;
			break;

		case 25: /* Remove Blink */
			tem->tvs_flags &= ~TEM_ATTR_BLINK;
			break;

		case 27: /* Remove Reverse */
			if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) {
				tem->tvs_flags |= TEM_ATTR_REVERSE;
			} else {
				tem->tvs_flags &= ~TEM_ATTR_REVERSE;
			}
			break;

		case 30: /* black	(grey)		foreground */
		case 31: /* red		(light red)	foreground */
		case 32: /* green	(light green)	foreground */
		case 33: /* brown	(yellow)	foreground */
		case 34: /* blue	(light blue)	foreground */
		case 35: /* magenta	(light magenta)	foreground */
		case 36: /* cyan	(light cyan)	foreground */
		case 37: /* white	(bright white)	foreground */
			tem->tvs_fg_color = param - 30;
			tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG;
			break;

		case 38:
			/* We should have at least 3 parameters */
			if (curparam < 3)
				break;

			/*
			 * 256 and truecolor needs depth at least 24, but
			 * we still need to process the sequence.
			 */
			count++;
			curparam--;
			param = tem->tvs_params[count];
			switch (param) {
			case 5:	/* 256 colors */
				count++;
				curparam--;
				tem_select_color(tem, tem->tvs_params[count],
				    true);
				break;
			default:
				break;
			}
			break;

		case 39:
			/*
			 * Reset the foreground colour and brightness.
			 */
			tem->tvs_fg_color = tems.ts_init_color.fg_color;
			if (tems.ts_init_color.a_flags & TEM_ATTR_BRIGHT_FG)
				tem->tvs_flags |= TEM_ATTR_BRIGHT_FG;
			else
				tem->tvs_flags &= ~TEM_ATTR_BRIGHT_FG;
			break;

		case 40: /* black	(grey)		background */
		case 41: /* red		(light red)	background */
		case 42: /* green	(light green)	background */
		case 43: /* brown	(yellow)	background */
		case 44: /* blue	(light blue)	background */
		case 45: /* magenta	(light magenta)	background */
		case 46: /* cyan	(light cyan)	background */
		case 47: /* white	(bright white)	background */
			tem->tvs_bg_color = param - 40;
			tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG;
			break;

		case 48:
			/* We should have at least 3 parameters */
			if (curparam < 3)
				break;

			/*
			 * 256 and truecolor needs depth at least 24, but
			 * we still need to process the sequence.
			 */
			count++;
			curparam--;
			param = tem->tvs_params[count];
			switch (param) {
			case 5:	/* 256 colors */
				count++;
				curparam--;
				tem_select_color(tem, tem->tvs_params[count],
				    false);
				break;
			default:
				break;
			}
			break;

		case 49:
			/*
			 * Reset the background colour and brightness.
			 */
			tem->tvs_bg_color = tems.ts_init_color.bg_color;
			if (tems.ts_init_color.a_flags & TEM_ATTR_BRIGHT_BG)
				tem->tvs_flags |= TEM_ATTR_BRIGHT_BG;
			else
				tem->tvs_flags &= ~TEM_ATTR_BRIGHT_BG;
			break;

		case 90: /* black	(grey)		foreground */
		case 91: /* red		(light red)	foreground */
		case 92: /* green	(light green)	foreground */
		case 93: /* brown	(yellow)	foreground */
		case 94: /* blue	(light blue)	foreground */
		case 95: /* magenta	(light magenta)	foreground */
		case 96: /* cyan	(light cyan)	foreground */
		case 97: /* white	(bright white)	foreground */
			tem->tvs_fg_color = param - 90;
			tem->tvs_flags |= TEM_ATTR_BRIGHT_FG;
			break;

		case 100: /* black	(grey)		background */
		case 101: /* red	(light red)	background */
		case 102: /* green	(light green)	background */
		case 103: /* brown	(yellow)	background */
		case 104: /* blue	(light blue)	background */
		case 105: /* magenta	(light magenta)	background */
		case 106: /* cyan	(light cyan)	background */
		case 107: /* white	(bright white)	background */
			tem->tvs_bg_color = param - 100;
			tem->tvs_flags |= TEM_ATTR_BRIGHT_BG;
			break;

		default:
			break;
		}
		count++;
		curparam--;

	} while (curparam > 0);
}

/*
 * perform the appropriate action for the escape sequence
 *
 * General rule:  This code does not validate the arguments passed.
 *                It assumes that the next lower level will do so.
 */
static void
tem_chkparam(struct tem_vt_state *tem, uint8_t ch)
{
	int	i;
	int	row;
	int	col;

	row = tem->tvs_c_cursor.row;
	col = tem->tvs_c_cursor.col;

	switch (ch) {

	case 'm': /* select terminal graphics mode */
		tem_send_data(tem);
		tem_selgraph(tem);
		break;

	case '@':		/* insert char */
		tem_setparam(tem, 1, 1);
		tem_shift(tem, tem->tvs_params[0], TEM_SHIFT_RIGHT);
		break;

	case 'A':		/* cursor up */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row - tem->tvs_params[0], col);
		break;

	case 'd':		/* VPA - vertical position absolute */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, tem->tvs_params[0] - 1, col);
		break;

	case 'e':		/* VPR - vertical position relative */
	case 'B':		/* cursor down */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row + tem->tvs_params[0], col);
		break;

	case 'a':		/* HPR - horizontal position relative */
	case 'C':		/* cursor right */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row, col + tem->tvs_params[0]);
		break;

	case '`':		/* HPA - horizontal position absolute */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row, tem->tvs_params[0] - 1);
		break;

	case 'D':		/* cursor left */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row, col - tem->tvs_params[0]);
		break;

	case 'E':		/* CNL cursor next line */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row + tem->tvs_params[0], 0);
		break;

	case 'F':		/* CPL cursor previous line */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row - tem->tvs_params[0], 0);
		break;

	case 'G':		/* cursor horizontal position */
		tem_setparam(tem, 1, 1);
		tem_mv_cursor(tem, row, tem->tvs_params[0] - 1);
		break;

	case 'g':		/* clear tabs */
		tem_setparam(tem, 1, 0);
		tem_clear_tabs(tem, tem->tvs_params[0]);
		break;

	case 'f':		/* HVP Horizontal and Vertical Position */
	case 'H':		/* CUP position cursor */
		tem_setparam(tem, 2, 1);
		tem_mv_cursor(tem,
		    tem->tvs_params[0] - 1, tem->tvs_params[1] - 1);
		break;

	case 'I':		/* CHT - Cursor Horizontal Tab */
		/* Not implemented */
		break;

	case 'J':		/* ED - Erase in Display */
		tem_send_data(tem);
		tem_setparam(tem, 1, 0);
		switch (tem->tvs_params[0]) {
		case 0:
			/* erase cursor to end of screen */
			/* FIRST erase cursor to end of line */
			tem_clear_chars(tem,
			    tems.ts_c_dimension.width -
			    tem->tvs_c_cursor.col,
			    tem->tvs_c_cursor.row,
			    tem->tvs_c_cursor.col);

			/* THEN erase lines below the cursor */
			for (row = tem->tvs_c_cursor.row + 1;
			    row < tems.ts_c_dimension.height;
			    row++) {
				tem_clear_chars(tem,
				    tems.ts_c_dimension.width, row, 0);
			}
			break;

		case 1:
			/* erase beginning of screen to cursor */
			/* FIRST erase lines above the cursor */
			for (row = 0;
			    row < tem->tvs_c_cursor.row;
			    row++) {
				tem_clear_chars(tem,
				    tems.ts_c_dimension.width, row, 0);
			}
			/* THEN erase beginning of line to cursor */
			tem_clear_chars(tem,
			    tem->tvs_c_cursor.col + 1,
			    tem->tvs_c_cursor.row, 0);
			break;

		case 2:
			/* erase whole screen */
			for (row = 0;
			    row < tems.ts_c_dimension.height;
			    row++) {
				tem_clear_chars(tem,
				    tems.ts_c_dimension.width, row, 0);
			}
			break;
		}
		break;

	case 'K':		/* EL - Erase in Line */
		tem_send_data(tem);
		tem_setparam(tem, 1, 0);
		switch (tem->tvs_params[0]) {
		case 0:
			/* erase cursor to end of line */
			tem_clear_chars(tem,
			    (tems.ts_c_dimension.width -
			    tem->tvs_c_cursor.col),
			    tem->tvs_c_cursor.row,
			    tem->tvs_c_cursor.col);
			break;

		case 1:
			/* erase beginning of line to cursor */
			tem_clear_chars(tem,
			    tem->tvs_c_cursor.col + 1,
			    tem->tvs_c_cursor.row, 0);
			break;

		case 2:
			/* erase whole line */
			tem_clear_chars(tem,
			    tems.ts_c_dimension.width,
			    tem->tvs_c_cursor.row, 0);
			break;
		}
		break;

	case 'L':		/* insert line */
		tem_send_data(tem);
		tem_setparam(tem, 1, 1);
		tem_scroll(tem,
		    tem->tvs_c_cursor.row,
		    tems.ts_c_dimension.height - 1,
		    tem->tvs_params[0], TEM_SCROLL_DOWN);
		break;

	case 'M':		/* delete line */
		tem_send_data(tem);
		tem_setparam(tem, 1, 1);
		tem_scroll(tem,
		    tem->tvs_c_cursor.row,
		    tems.ts_c_dimension.height - 1,
		    tem->tvs_params[0], TEM_SCROLL_UP);
		break;

	case 'P':		/* DCH - delete char */
		tem_setparam(tem, 1, 1);
		tem_shift(tem, tem->tvs_params[0], TEM_SHIFT_LEFT);
		break;

	case 'S':		/* scroll up */
		tem_send_data(tem);
		tem_setparam(tem, 1, 1);
		tem_scroll(tem, 0,
		    tems.ts_c_dimension.height - 1,
		    tem->tvs_params[0], TEM_SCROLL_UP);
		break;

	case 'T':		/* scroll down */
		tem_send_data(tem);
		tem_setparam(tem, 1, 1);
		tem_scroll(tem, 0,
		    tems.ts_c_dimension.height - 1,
		    tem->tvs_params[0], TEM_SCROLL_DOWN);
		break;

	case 'X':		/* erase char */
		tem_setparam(tem, 1, 1);
		tem_clear_chars(tem,
		    tem->tvs_params[0],
		    tem->tvs_c_cursor.row,
		    tem->tvs_c_cursor.col);
		break;

	case 'Z':		/* cursor backward tabulation */
		tem_setparam(tem, 1, 1);

		/*
		 * Rule exception - We do sanity checking here.
		 *
		 * Restrict the count to a sane value to keep from
		 * looping for a long time.  There can't be more than one
		 * tab stop per column, so use that as a limit.
		 */
		if (tem->tvs_params[0] > tems.ts_c_dimension.width)
			tem->tvs_params[0] = tems.ts_c_dimension.width;

		for (i = 0; i < tem->tvs_params[0]; i++)
			tem_back_tab(tem);
		break;
	}
	tem->tvs_state = A_STATE_START;
}


/*
 * Gather the parameters of an ANSI escape sequence
 */
static void
tem_getparams(struct tem_vt_state *tem, uint8_t ch)
{
	if (isdigit(ch)) {
		tem->tvs_paramval = ((tem->tvs_paramval * 10) + (ch - '0'));
		tem->tvs_gotparam = B_TRUE;  /* Remember got parameter */
		return; /* Return immediately */
	} else if (tem->tvs_state == A_STATE_CSI_EQUAL ||
	    tem->tvs_state == A_STATE_CSI_QMARK) {
		tem->tvs_state = A_STATE_START;
	} else {
		if (tem->tvs_curparam < TEM_MAXPARAMS) {
			if (tem->tvs_gotparam) {
				/* get the parameter value */
				tem->tvs_params[tem->tvs_curparam] =
				    tem->tvs_paramval;
			}
			tem->tvs_curparam++;
		}

		if (ch == ';') {
			/* Restart parameter search */
			tem->tvs_gotparam = B_FALSE;
			tem->tvs_paramval = 0; /* No parame value yet */
		} else {
			/* Handle escape sequence */
			tem_chkparam(tem, ch);
		}
	}
}

/*
 * Add character to internal buffer.
 * When its full, send it to the next layer.
 */
static void
tem_outch(struct tem_vt_state *tem, tem_char_t ch)
{
	text_color_t fg;
	text_color_t bg;
	text_attr_t attr;

	/* buffer up the character until later */
	tem_get_attr(tem, &fg, &bg, &attr, TEM_ATTR_REVERSE);
	tem->tvs_outbuf[tem->tvs_outindex].tc_char = ch | TEM_ATTR(attr);
	tem->tvs_outbuf[tem->tvs_outindex].tc_fg_color = fg;
	tem->tvs_outbuf[tem->tvs_outindex].tc_bg_color = bg;
	tem->tvs_outindex++;
	tem->tvs_c_cursor.col++;
	if (tem->tvs_c_cursor.col >= tems.ts_c_dimension.width) {
		tem_send_data(tem);
		tem_new_line(tem);
	}
}

static void
tem_new_line(struct tem_vt_state *tem)
{
	tem_cr(tem);
	tem_lf(tem);
}

static void
tem_cr(struct tem_vt_state *tem)
{
	tem->tvs_c_cursor.col = 0;
	tem_align_cursor(tem);
}

static void
tem_lf(struct tem_vt_state *tem)
{
	int row;

	/*
	 * Sanity checking notes:
	 * . a_nscroll was validated when it was set.
	 * . Regardless of that, tem_scroll and tem_mv_cursor
	 *   will prevent anything bad from happening.
	 */
	row = tem->tvs_c_cursor.row + 1;

	if (row >= tems.ts_c_dimension.height) {
		if (tem->tvs_nscroll != 0) {
			tem_scroll(tem, 0,
			    tems.ts_c_dimension.height - 1,
			    tem->tvs_nscroll, TEM_SCROLL_UP);
			row = tems.ts_c_dimension.height -
			    tem->tvs_nscroll;
		} else {	/* no scroll */
			/*
			 * implement Esc[#r when # is zero.  This means no
			 * scroll but just return cursor to top of screen,
			 * do not clear screen.
			 */
			row = 0;
		}
	}

	tem_mv_cursor(tem, row, tem->tvs_c_cursor.col);

	if (tem->tvs_nscroll == 0) {
		/* erase rest of cursor line */
		tem_clear_chars(tem,
		    tems.ts_c_dimension.width -
		    tem->tvs_c_cursor.col,
		    tem->tvs_c_cursor.row,
		    tem->tvs_c_cursor.col);

	}

	tem_align_cursor(tem);
}

static void
tem_send_data(struct tem_vt_state *tem)
{
	if (tem->tvs_outindex == 0) {
		tem_align_cursor(tem);
		return;
	}

	tem_virtual_display(tem, tem->tvs_outbuf, tem->tvs_outindex,
	    tem->tvs_s_cursor.row, tem->tvs_s_cursor.col);

	if (tem->tvs_isactive) {
		/*
		 * Call the primitive to render this data.
		 */
		tem_callback_display(tem,
		    tem->tvs_outbuf, tem->tvs_outindex,
		    tem->tvs_s_cursor.row, tem->tvs_s_cursor.col);
	}

	tem->tvs_outindex = 0;

	tem_align_cursor(tem);
}


/*
 * We have just done something to the current output point.  Reset the start
 * point for the buffered data in a_outbuf.  There shouldn't be any data
 * buffered yet.
 */
static void
tem_align_cursor(struct tem_vt_state *tem)
{
	tem->tvs_s_cursor.row = tem->tvs_c_cursor.row;
	tem->tvs_s_cursor.col = tem->tvs_c_cursor.col;
}

/*
 * State machine parser based on the current state and character input
 * major terminations are to control character or normal character
 */

static void
tem_parse(struct tem_vt_state *tem, tem_char_t ch)
{
	int	i;

	if (tem->tvs_state == A_STATE_START) {	/* Normal state? */
		if (ch == A_CSI || ch == A_ESC || ch < ' ') {
			/* Control */
			tem_control(tem, ch);
		} else {
			/* Display */
			tem_outch(tem, ch);
		}
		return;
	}

	/* In <ESC> sequence */
	if (tem->tvs_state != A_STATE_ESC) {	/* Need to get parameters? */
		if (tem->tvs_state != A_STATE_CSI) {
			tem_getparams(tem, ch);
			return;
		}

		switch (ch) {
		case '?':
			tem->tvs_state = A_STATE_CSI_QMARK;
			return;
		case '=':
			tem->tvs_state = A_STATE_CSI_EQUAL;
			return;
		case 's':
			/*
			 * As defined below, this sequence
			 * saves the cursor.  However, Sun
			 * defines ESC[s as reset.  We resolved
			 * the conflict by selecting reset as it
			 * is exported in the termcap file for
			 * sun-mon, while the "save cursor"
			 * definition does not exist anywhere in
			 * /etc/termcap.
			 * However, having no coherent
			 * definition of reset, we have not
			 * implemented it.
			 */

			/*
			 * Original code
			 * tem->tvs_r_cursor.row = tem->tvs_c_cursor.row;
			 * tem->tvs_r_cursor.col = tem->tvs_c_cursor.col;
			 * tem->tvs_state = A_STATE_START;
			 */

			tem->tvs_state = A_STATE_START;
			return;
		case 'u':
			tem_mv_cursor(tem, tem->tvs_r_cursor.row,
			    tem->tvs_r_cursor.col);
			tem->tvs_state = A_STATE_START;
			return;
		case 'p':	/* sunbow */
			tem_send_data(tem);
			/*
			 * Don't set anything if we are
			 * already as we want to be.
			 */
			if (tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE) {
				tem->tvs_flags &= ~TEM_ATTR_SCREEN_REVERSE;
				/*
				 * If we have switched the characters to be the
				 * inverse from the screen, then switch them as
				 * well to keep them the inverse of the screen.
				 */
				if (tem->tvs_flags & TEM_ATTR_REVERSE)
					tem->tvs_flags &= ~TEM_ATTR_REVERSE;
				else
					tem->tvs_flags |= TEM_ATTR_REVERSE;
			}
			tem_cls(tem);
			tem->tvs_state = A_STATE_START;
			return;
		case 'q':	/* sunwob */
			tem_send_data(tem);
			/*
			 * Don't set anything if we are
			 * already where as we want to be.
			 */
			if (!(tem->tvs_flags & TEM_ATTR_SCREEN_REVERSE)) {
				tem->tvs_flags |= TEM_ATTR_SCREEN_REVERSE;
				/*
				 * If we have switched the characters to be the
				 * inverse from the screen, then switch them as
				 * well to keep them the inverse of the screen.
				 */
				if (!(tem->tvs_flags & TEM_ATTR_REVERSE))
					tem->tvs_flags |= TEM_ATTR_REVERSE;
				else
					tem->tvs_flags &= ~TEM_ATTR_REVERSE;
			}

			tem_cls(tem);
			tem->tvs_state = A_STATE_START;
			return;
		case 'r':	/* sunscrl */
			/*
			 * Rule exception:  check for validity here.
			 */
			tem->tvs_nscroll = tem->tvs_paramval;
			if (tem->tvs_nscroll > tems.ts_c_dimension.height)
				tem->tvs_nscroll = tems.ts_c_dimension.height;
			if (tem->tvs_nscroll < 0)
				tem->tvs_nscroll = 1;
			tem->tvs_state = A_STATE_START;
			return;
		default:
			tem_getparams(tem, ch);
			return;
		}
	}

	/* Previous char was <ESC> */
	if (ch == '[') {
		tem->tvs_curparam = 0;
		tem->tvs_paramval = 0;
		tem->tvs_gotparam = B_FALSE;
		/* clear the parameters */
		for (i = 0; i < TEM_MAXPARAMS; i++)
			tem->tvs_params[i] = -1;
		tem->tvs_state = A_STATE_CSI;
	} else if (ch == 'Q') {	/* <ESC>Q ? */
		tem->tvs_state = A_STATE_START;
	} else if (ch == 'C') {	/* <ESC>C ? */
		tem->tvs_state = A_STATE_START;
	} else {
		tem->tvs_state = A_STATE_START;
		if (ch == 'c') {
			/* ESC c resets display */
			tem_reset_display(tem, B_TRUE, B_TRUE);
		} else if (ch == 'H') {
			/* ESC H sets a tab */
			tem_set_tab(tem);
		} else if (ch == '7') {
			/* ESC 7 Save Cursor position */
			tem->tvs_r_cursor.row = tem->tvs_c_cursor.row;
			tem->tvs_r_cursor.col = tem->tvs_c_cursor.col;
		} else if (ch == '8') {
			/* ESC 8 Restore Cursor position */
			tem_mv_cursor(tem, tem->tvs_r_cursor.row,
			    tem->tvs_r_cursor.col);
		/* check for control chars */
		} else if (ch < ' ') {
			tem_control(tem, ch);
		} else {
			tem_outch(tem, ch);
		}
	}
}

/* ARGSUSED */
static void
tem_bell(struct tem_vt_state *tem __unused)
{
		/* (void) beep(BEEP_CONSOLE); */
}


static void
tem_scroll(struct tem_vt_state *tem, int start, int end, int count,
    int direction)
{
	int	row;
	int	lines_affected;

	lines_affected = end - start + 1;
	if (count > lines_affected)
		count = lines_affected;
	if (count <= 0)
		return;

	switch (direction) {
	case TEM_SCROLL_UP:
		if (count < lines_affected) {
			tem_copy_area(tem, 0, start + count,
			    tems.ts_c_dimension.width - 1, end, 0, start);
		}
		for (row = (end - count) + 1; row <= end; row++) {
			tem_clear_chars(tem, tems.ts_c_dimension.width, row, 0);
		}
		break;

	case TEM_SCROLL_DOWN:
		if (count < lines_affected) {
			tem_copy_area(tem, 0, start,
			    tems.ts_c_dimension.width - 1,
			    end - count, 0, start + count);
		}
		for (row = start; row < start + count; row++) {
			tem_clear_chars(tem, tems.ts_c_dimension.width, row, 0);
		}
		break;
	}
}

static int
tem_copy_width(term_char_t *src, term_char_t *dst, int cols)
{
	int width = cols - 1;

	while (width >= 0) {
		/* We do not have image bits to compare, stop there. */
		if (TEM_CHAR_ATTR(src[width].tc_char) == TEM_ATTR_IMAGE ||
		    TEM_CHAR_ATTR(dst[width].tc_char) == TEM_ATTR_IMAGE)
			break;

		/*
		 * Find difference on line, compare char with its attributes
		 * and colors.
		 */
		if (src[width].tc_char != dst[width].tc_char ||
		    src[width].tc_fg_color != dst[width].tc_fg_color ||
		    src[width].tc_bg_color != dst[width].tc_bg_color) {
			break;
		}
		width--;
	}
	return (width + 1);
}

static void
tem_copy_area(struct tem_vt_state *tem,
    screen_pos_t s_col, screen_pos_t s_row,
    screen_pos_t e_col, screen_pos_t e_row,
    screen_pos_t t_col, screen_pos_t t_row)
{
	size_t soffset, toffset;
	term_char_t *src, *dst;
	int rows;
	int cols;

	if (s_col < 0 || s_row < 0 ||
	    e_col < 0 || e_row < 0 ||
	    t_col < 0 || t_row < 0 ||
	    s_col >= tems.ts_c_dimension.width ||
	    e_col >= tems.ts_c_dimension.width ||
	    t_col >= tems.ts_c_dimension.width ||
	    s_row >= tems.ts_c_dimension.height ||
	    e_row >= tems.ts_c_dimension.height ||
	    t_row >= tems.ts_c_dimension.height)
		return;

	if (s_row > e_row || s_col > e_col)
		return;

	rows = e_row - s_row + 1;
	cols = e_col - s_col + 1;
	if (t_row + rows > tems.ts_c_dimension.height ||
	    t_col + cols > tems.ts_c_dimension.width)
		return;

	if (tem->tvs_screen_buf == NULL) {
		if (tem->tvs_isactive) {
			tem_callback_copy(tem, s_col, s_row,
			    e_col, e_row, t_col, t_row);
		}
		return;
	}

	soffset = s_col + s_row * tems.ts_c_dimension.width;
	toffset = t_col + t_row * tems.ts_c_dimension.width;
	src = tem->tvs_screen_buf + soffset;
	dst = tem->tvs_screen_buf + toffset;

	/*
	 * Copy line by line. We determine the length by comparing the
	 * screen content from cached text in tvs_screen_buf.
	 */
	if (toffset <= soffset) {
		for (int i = 0; i < rows; i++) {
			int increment = i * tems.ts_c_dimension.width;
			int width;

			width = tem_copy_width(src + increment,
			    dst + increment, cols);
			memmove(dst + increment, src + increment,
			    width * sizeof (term_char_t));
			if (tem->tvs_isactive) {
				tem_callback_copy(tem, s_col, s_row + i,
				    e_col - cols + width, s_row + i,
				    t_col, t_row + i);
			}
		}
	} else {
		for (int i = rows - 1; i >= 0; i--) {
			int increment = i * tems.ts_c_dimension.width;
			int width;

			width = tem_copy_width(src + increment,
			    dst + increment, cols);
			memmove(dst + increment, src + increment,
			    width * sizeof (term_char_t));
			if (tem->tvs_isactive) {
				tem_callback_copy(tem, s_col, s_row + i,
				    e_col - cols + width, s_row + i,
				    t_col, t_row + i);
			}
		}
	}
}

static void
tem_clear_chars(struct tem_vt_state *tem, int count, screen_pos_t row,
    screen_pos_t col)
{
	if (row < 0 || row >= tems.ts_c_dimension.height ||
	    col < 0 || col >= tems.ts_c_dimension.width ||
	    count < 0)
		return;

	/*
	 * Note that very large values of "count" could cause col+count
	 * to overflow, so we check "count" independently.
	 */
	if (count > tems.ts_c_dimension.width ||
	    col + count > tems.ts_c_dimension.width)
		count = tems.ts_c_dimension.width - col;

	tem_virtual_cls(tem, count, row, col);

	if (!tem->tvs_isactive)
		return;

	tem_callback_cls(tem, count, row, col);
}

static void
tem_text_display(struct tem_vt_state *tem __unused, term_char_t *string,
    int count, screen_pos_t row, screen_pos_t col)
{
	struct vis_consdisplay da;
	int i;
	tem_char_t c;

	if (count == 0)
		return;

	da.data = (unsigned char *)&c;
	da.width = 1;
	da.row = row;
	da.col = col;

	for (i = 0; i < count; i++) {
		tem_get_color(&da.fg_color, &da.bg_color, string[i]);
		c = TEM_CHAR(string[i].tc_char);
		tems_display(&da);
		da.col++;
	}
}

/*
 * This function is used to mark a rectangular image area so the scrolling
 * will know we need to copy the data from there.
 */
void
tem_image_display(struct tem_vt_state *tem, screen_pos_t s_row,
    screen_pos_t s_col, screen_pos_t e_row, screen_pos_t e_col)
{
	screen_pos_t i, j;
	term_char_t c;

	c.tc_char = TEM_ATTR(TEM_ATTR_IMAGE);

	for (i = s_row; i <= e_row; i++) {
		for (j = s_col; j <= e_col; j++) {
			tem_virtual_display(tem, &c, 1, i, j);
		}
	}
}

/*ARGSUSED*/
static void
tem_text_copy(struct tem_vt_state *tem __unused,
    screen_pos_t s_col, screen_pos_t s_row,
    screen_pos_t e_col, screen_pos_t e_row,
    screen_pos_t t_col, screen_pos_t t_row)
{
	struct vis_conscopy da;

	da.s_row = s_row;
	da.s_col = s_col;
	da.e_row = e_row;
	da.e_col = e_col;
	da.t_row = t_row;
	da.t_col = t_col;
	tems_copy(&da);
}

static void
tem_text_cls(struct tem_vt_state *tem,
    int count, screen_pos_t row, screen_pos_t col)
{
	text_attr_t attr;
	term_char_t c;
	int i;

	tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr,
	    TEM_ATTR_SCREEN_REVERSE);
	c.tc_char = TEM_ATTR(attr & ~TEM_ATTR_UNDERLINE) | ' ';

	if (count > tems.ts_c_dimension.width ||
	    col + count > tems.ts_c_dimension.width)
		count = tems.ts_c_dimension.width - col;

	for (i = 0; i < count; i++)
		tem_text_display(tem, &c, 1, row, col++);

}

static void
tem_pix_display(struct tem_vt_state *tem,
    term_char_t *string, int count,
    screen_pos_t row, screen_pos_t col)
{
	struct vis_consdisplay da;
	int	i;

	da.data = (uint8_t *)tem->tvs_pix_data;
	da.width = tems.ts_font.vf_width;
	da.height = tems.ts_font.vf_height;
	da.row = (row * da.height) + tems.ts_p_offset.y;
	da.col = (col * da.width) + tems.ts_p_offset.x;

	for (i = 0; i < count; i++) {
		tem_callback_bit2pix(tem, string[i]);
		tems_display(&da);
		da.col += da.width;
	}
}

static void
tem_pix_copy(struct tem_vt_state *tem,
    screen_pos_t s_col, screen_pos_t s_row,
    screen_pos_t e_col, screen_pos_t e_row,
    screen_pos_t t_col, screen_pos_t t_row)
{
	struct vis_conscopy ma;
	static boolean_t need_clear = B_TRUE;

	if (need_clear && tem->tvs_first_line > 0) {
		/*
		 * Clear OBP output above our kernel console term
		 * when our kernel console term begins to scroll up,
		 * we hope it is user friendly.
		 * (Also see comments on tem_pix_clear_prom_output)
		 *
		 * This is only one time call.
		 */
		tem_pix_clear_prom_output(tem);
	}
	need_clear = B_FALSE;

	ma.s_row = s_row * tems.ts_font.vf_height + tems.ts_p_offset.y;
	ma.e_row = (e_row + 1) * tems.ts_font.vf_height +
	    tems.ts_p_offset.y - 1;
	ma.t_row = t_row * tems.ts_font.vf_height + tems.ts_p_offset.y;

	/*
	 * Check if we're in process of clearing OBP's columns area,
	 * which only happens when term scrolls up a whole line.
	 */
	if (tem->tvs_first_line > 0 && t_row < s_row && t_col == 0 &&
	    e_col == tems.ts_c_dimension.width - 1) {
		/*
		 * We need to clear OBP's columns area outside our kernel
		 * console term. So that we set ma.e_col to entire row here.
		 */
		ma.s_col = s_col * tems.ts_font.vf_width;
		ma.e_col = tems.ts_p_dimension.width - 1;

		ma.t_col = t_col * tems.ts_font.vf_width;
	} else {
		ma.s_col = s_col * tems.ts_font.vf_width + tems.ts_p_offset.x;
		ma.e_col = (e_col + 1) * tems.ts_font.vf_width +
		    tems.ts_p_offset.x - 1;
		ma.t_col = t_col * tems.ts_font.vf_width + tems.ts_p_offset.x;
	}

	tems_copy(&ma);

	if (tem->tvs_first_line > 0 && t_row < s_row) {
		/* We have scrolled up (s_row - t_row) rows. */
		tem->tvs_first_line -= (s_row - t_row);
		if (tem->tvs_first_line <= 0) {
			/* All OBP rows have been cleared. */
			tem->tvs_first_line = 0;
		}
	}
}

static void
tem_pix_bit2pix(struct tem_vt_state *tem, term_char_t c)
{
	text_color_t fg, bg;
	void (*fp)(struct tem_vt_state *, tem_char_t,
	    unsigned char, unsigned char);

	fg = DEFAULT_ANSI_FOREGROUND;
	bg = DEFAULT_ANSI_BACKGROUND;

	tem_get_color(&fg, &bg, c);
	switch (tems.ts_pdepth) {
	case 4:
		fp = bit_to_pix4;
		break;
	case 8:
		fp = bit_to_pix8;
		break;
	case 15:
	case 16:
		fp = bit_to_pix16;
		break;
	case 24:
		fp = bit_to_pix24;
		break;
	case 32:
		fp = bit_to_pix32;
		break;
	default:
		return;
	}

	fp(tem, c.tc_char, fg, bg);
}


/*
 * This function only clears count of columns in one row
 */
static void
tem_pix_cls(struct tem_vt_state *tem, int count,
    screen_pos_t row, screen_pos_t col)
{
	tem_pix_cls_range(tem, row, 1, tems.ts_p_offset.y,
	    col, count, tems.ts_p_offset.x, B_FALSE);
}

/*
 * This function clears OBP output above our kernel console term area
 * because OBP's term may have a bigger terminal window than that of
 * our kernel console term. So we need to clear OBP output garbage outside
 * of our kernel console term at a proper time, which is when the first
 * row output of our kernel console term scrolls at the first screen line.
 *
 *	_________________________________
 *	|   _____________________	|  ---> OBP's bigger term window
 *	|   |			|	|
 *	|___|			|	|
 *	| | |			|	|
 *	| | |			|	|
 *	|_|_|___________________|_______|
 *	  | |			|	   ---> first line
 *	  | |___________________|---> our kernel console term window
 *	  |
 *	  |---> columns area to be cleared
 *
 * This function only takes care of the output above our kernel console term,
 * and tem_prom_scroll_up takes care of columns area outside of our kernel
 * console term.
 */
static void
tem_pix_clear_prom_output(struct tem_vt_state *tem)
{
	int	nrows, ncols, width, height, offset;

	width = tems.ts_font.vf_width;
	height = tems.ts_font.vf_height;
	offset = tems.ts_p_offset.y % height;

	nrows = tems.ts_p_offset.y / height;
	ncols = (tems.ts_p_dimension.width + (width - 1)) / width;

	if (nrows > 0)
		tem_pix_cls_range(tem, 0, nrows, offset, 0, ncols, 0,
		    B_FALSE);
}

/*
 * Clear the whole screen and reset the cursor to start point.
 */
static void
tem_cls(struct tem_vt_state *tem)
{
	struct vis_consclear cl;
	text_color_t fg_color;
	text_color_t bg_color;
	text_attr_t attr;
	term_char_t c;
	int row;

	for (row = 0; row < tems.ts_c_dimension.height; row++) {
		tem_virtual_cls(tem, tems.ts_c_dimension.width, row, 0);
	}

	if (!tem->tvs_isactive)
		return;

	tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr,
	    TEM_ATTR_SCREEN_REVERSE);
	c.tc_char = TEM_ATTR(attr);

	fg_color = DEFAULT_ANSI_FOREGROUND;
	bg_color = DEFAULT_ANSI_BACKGROUND;
	tem_get_color(&fg_color, &bg_color, c);
	cl.bg_color = bg_color;
	(void) tems_cls(&cl);

	tem->tvs_c_cursor.row = 0;
	tem->tvs_c_cursor.col = 0;
	tem_align_cursor(tem);
}

static void
tem_back_tab(struct tem_vt_state *tem)
{
	int	i;
	screen_pos_t	tabstop;

	tabstop = 0;

	for (i = tem->tvs_ntabs - 1; i >= 0; i--) {
		if (tem->tvs_tabs[i] < tem->tvs_c_cursor.col) {
			tabstop = tem->tvs_tabs[i];
			break;
		}
	}

	tem_mv_cursor(tem, tem->tvs_c_cursor.row, tabstop);
}

static void
tem_tab(struct tem_vt_state *tem)
{
	int	i;
	screen_pos_t	tabstop;

	tabstop = tems.ts_c_dimension.width - 1;

	for (i = 0; i < tem->tvs_ntabs; i++) {
		if (tem->tvs_tabs[i] > tem->tvs_c_cursor.col) {
			tabstop = tem->tvs_tabs[i];
			break;
		}
	}

	tem_mv_cursor(tem, tem->tvs_c_cursor.row, tabstop);
}

static void
tem_set_tab(struct tem_vt_state *tem)
{
	int	i;
	int	j;

	if (tem->tvs_ntabs == TEM_MAXTAB)
		return;
	if (tem->tvs_ntabs == 0 ||
	    tem->tvs_tabs[tem->tvs_ntabs] < tem->tvs_c_cursor.col) {
			tem->tvs_tabs[tem->tvs_ntabs++] = tem->tvs_c_cursor.col;
			return;
	}
	for (i = 0; i < tem->tvs_ntabs; i++) {
		if (tem->tvs_tabs[i] == tem->tvs_c_cursor.col)
			return;
		if (tem->tvs_tabs[i] > tem->tvs_c_cursor.col) {
			for (j = tem->tvs_ntabs - 1; j >= i; j--)
				tem->tvs_tabs[j+ 1] = tem->tvs_tabs[j];
			tem->tvs_tabs[i] = tem->tvs_c_cursor.col;
			tem->tvs_ntabs++;
			return;
		}
	}
}

static void
tem_clear_tabs(struct tem_vt_state *tem, int action)
{
	int	i;
	int	j;

	switch (action) {
	case 3: /* clear all tabs */
		tem->tvs_ntabs = 0;
		break;
	case 0: /* clr tab at cursor */

		for (i = 0; i < tem->tvs_ntabs; i++) {
			if (tem->tvs_tabs[i] == tem->tvs_c_cursor.col) {
				tem->tvs_ntabs--;
				for (j = i; j < tem->tvs_ntabs; j++)
					tem->tvs_tabs[j] = tem->tvs_tabs[j + 1];
				return;
			}
		}
		break;
	}
}

static void
tem_mv_cursor(struct tem_vt_state *tem, int row, int col)
{
	/*
	 * Sanity check and bounds enforcement.  Out of bounds requests are
	 * clipped to the screen boundaries.  This seems to be what SPARC
	 * does.
	 */
	if (row < 0)
		row = 0;
	if (row >= tems.ts_c_dimension.height)
		row = tems.ts_c_dimension.height - 1;
	if (col < 0)
		col = 0;
	if (col >= tems.ts_c_dimension.width)
		col = tems.ts_c_dimension.width - 1;

	tem_send_data(tem);
	tem->tvs_c_cursor.row = (screen_pos_t)row;
	tem->tvs_c_cursor.col = (screen_pos_t)col;
	tem_align_cursor(tem);
}

/* ARGSUSED */
static void
tem_reset_emulator(struct tem_vt_state *tem, boolean_t init_color)
{
	int j;

	tem->tvs_c_cursor.row = 0;
	tem->tvs_c_cursor.col = 0;
	tem->tvs_r_cursor.row = 0;
	tem->tvs_r_cursor.col = 0;
	tem->tvs_s_cursor.row = 0;
	tem->tvs_s_cursor.col = 0;
	tem->tvs_outindex = 0;
	tem->tvs_state = A_STATE_START;
	tem->tvs_gotparam = B_FALSE;
	tem->tvs_curparam = 0;
	tem->tvs_paramval = 0;
	tem->tvs_nscroll = 1;

	if (init_color) {
		/* use initial settings */
		tem->tvs_fg_color = tems.ts_init_color.fg_color;
		tem->tvs_bg_color = tems.ts_init_color.bg_color;
		tem->tvs_flags = tems.ts_init_color.a_flags;
	}

	/*
	 * set up the initial tab stops
	 */
	tem->tvs_ntabs = 0;
	for (j = 8; j < tems.ts_c_dimension.width; j += 8)
		tem->tvs_tabs[tem->tvs_ntabs++] = (screen_pos_t)j;

	for (j = 0; j < TEM_MAXPARAMS; j++)
		tem->tvs_params[j] = 0;
}

static void
tem_reset_display(struct tem_vt_state *tem,
    boolean_t clear_txt, boolean_t init_color)
{
	tem_reset_emulator(tem, init_color);

	if (clear_txt) {
		if (tem->tvs_isactive)
			tem_callback_cursor(tem, VIS_HIDE_CURSOR);

		tem_cls(tem);

		if (tem->tvs_isactive)
			tem_callback_cursor(tem, VIS_DISPLAY_CURSOR);
	}
}

static void
tem_shift(struct tem_vt_state *tem, int count, int direction)
{
	int rest_of_line;

	rest_of_line = tems.ts_c_dimension.width - tem->tvs_c_cursor.col;
	if (count > rest_of_line)
		count = rest_of_line;

	if (count <= 0)
		return;

	switch (direction) {
	case TEM_SHIFT_LEFT:
		if (count < rest_of_line) {
			tem_copy_area(tem,
			    tem->tvs_c_cursor.col + count,
			    tem->tvs_c_cursor.row,
			    tems.ts_c_dimension.width - 1,
			    tem->tvs_c_cursor.row,
			    tem->tvs_c_cursor.col,
			    tem->tvs_c_cursor.row);
		}

		tem_clear_chars(tem, count, tem->tvs_c_cursor.row,
		    (tems.ts_c_dimension.width - count));
		break;
	case TEM_SHIFT_RIGHT:
		if (count < rest_of_line) {
			tem_copy_area(tem,
			    tem->tvs_c_cursor.col,
			    tem->tvs_c_cursor.row,
			    tems.ts_c_dimension.width - count - 1,
			    tem->tvs_c_cursor.row,
			    tem->tvs_c_cursor.col + count,
			    tem->tvs_c_cursor.row);
		}

		tem_clear_chars(tem, count, tem->tvs_c_cursor.row,
		    tem->tvs_c_cursor.col);
		break;
	}
}

static void
tem_text_cursor(struct tem_vt_state *tem, short action)
{
	struct vis_conscursor	ca;

	ca.row = tem->tvs_c_cursor.row;
	ca.col = tem->tvs_c_cursor.col;
	ca.action = action;

	tems_cursor(&ca);

	if (action == VIS_GET_CURSOR) {
		tem->tvs_c_cursor.row = ca.row;
		tem->tvs_c_cursor.col = ca.col;
	}
}

static void
tem_pix_cursor(struct tem_vt_state *tem, short action)
{
	struct vis_conscursor	ca;
	uint32_t color;
	text_color_t fg, bg;
	term_char_t c;
	text_attr_t attr;

	ca.row = tem->tvs_c_cursor.row * tems.ts_font.vf_height +
	    tems.ts_p_offset.y;
	ca.col = tem->tvs_c_cursor.col * tems.ts_font.vf_width +
	    tems.ts_p_offset.x;
	ca.width = tems.ts_font.vf_width;
	ca.height = tems.ts_font.vf_height;

	tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr,
	    TEM_ATTR_REVERSE);
	c.tc_char = TEM_ATTR(attr);

	fg = DEFAULT_ANSI_FOREGROUND;
	bg = DEFAULT_ANSI_BACKGROUND;
	tem_get_color(&fg, &bg, c);

	switch (tems.ts_pdepth) {
	case 4:
		ca.fg_color.mono = fg;
		ca.bg_color.mono = bg;
		break;
	case 8:
		ca.fg_color.mono = tems.ts_color_map(fg);
		ca.bg_color.mono = tems.ts_color_map(bg);
		break;
	case 15:
	case 16:
		color = tems.ts_color_map(fg);
		ca.fg_color.sixteen[0] = (color >> 8) & 0xFF;
		ca.fg_color.sixteen[1] = color & 0xFF;
		color = tems.ts_color_map(bg);
		ca.bg_color.sixteen[0] = (color >> 8) & 0xFF;
		ca.bg_color.sixteen[1] = color & 0xFF;
		break;
	case 24:
	case 32:
		color = tems.ts_color_map(fg);
		ca.fg_color.twentyfour[0] = (color >> 16) & 0xFF;
		ca.fg_color.twentyfour[1] = (color >> 8) & 0xFF;
		ca.fg_color.twentyfour[2] = color & 0xFF;
		color = tems.ts_color_map(bg);
		ca.bg_color.twentyfour[0] = (color >> 16) & 0xFF;
		ca.bg_color.twentyfour[1] = (color >> 8) & 0xFF;
		ca.bg_color.twentyfour[2] = color & 0xFF;
		break;
	}

	ca.action = action;

	tems_cursor(&ca);

	if (action == VIS_GET_CURSOR) {
		tem->tvs_c_cursor.row = 0;
		tem->tvs_c_cursor.col = 0;

		if (ca.row != 0) {
			tem->tvs_c_cursor.row = (ca.row - tems.ts_p_offset.y) /
			    tems.ts_font.vf_height;
		}
		if (ca.col != 0) {
			tem->tvs_c_cursor.col = (ca.col - tems.ts_p_offset.x) /
			    tems.ts_font.vf_width;
		}
	}
}

static void
bit_to_pix4(struct tem_vt_state *tem,
    tem_char_t c,
    text_color_t fg_color,
    text_color_t bg_color)
{
	uint8_t *dest = (uint8_t *)tem->tvs_pix_data;
	font_bit_to_pix4(&tems.ts_font, dest, c, fg_color, bg_color);
}

static void
bit_to_pix8(struct tem_vt_state *tem,
    tem_char_t c,
    text_color_t fg_color,
    text_color_t bg_color)
{
	uint8_t *dest = (uint8_t *)tem->tvs_pix_data;

	fg_color = (text_color_t)tems.ts_color_map(fg_color);
	bg_color = (text_color_t)tems.ts_color_map(bg_color);
	font_bit_to_pix8(&tems.ts_font, dest, c, fg_color, bg_color);
}

static void
bit_to_pix16(struct tem_vt_state *tem,
    tem_char_t c,
    text_color_t fg_color4,
    text_color_t bg_color4)
{
	uint16_t fg_color16, bg_color16;
	uint16_t *dest;

	fg_color16 = (uint16_t)tems.ts_color_map(fg_color4);
	bg_color16 = (uint16_t)tems.ts_color_map(bg_color4);

	dest = (uint16_t *)tem->tvs_pix_data;
	font_bit_to_pix16(&tems.ts_font, dest, c, fg_color16, bg_color16);
}

static void
bit_to_pix24(struct tem_vt_state *tem,
    tem_char_t c,
    text_color_t fg_color4,
    text_color_t bg_color4)
{
	uint32_t fg_color32, bg_color32;
	uint8_t *dest;

	fg_color32 = tems.ts_color_map(fg_color4);
	bg_color32 = tems.ts_color_map(bg_color4);

	dest = (uint8_t *)tem->tvs_pix_data;
	font_bit_to_pix24(&tems.ts_font, dest, c, fg_color32, bg_color32);
}

static void
bit_to_pix32(struct tem_vt_state *tem,
    tem_char_t c,
    text_color_t fg_color4,
    text_color_t bg_color4)
{
	uint32_t fg_color32, bg_color32, *dest;

	fg_color32 = (0xFF << 24) | tems.ts_color_map(fg_color4);
	bg_color32 = (0xFF << 24) | tems.ts_color_map(bg_color4);

	dest = (uint32_t *)tem->tvs_pix_data;
	font_bit_to_pix32(&tems.ts_font, dest, c, fg_color32, bg_color32);
}

/*
 * flag: TEM_ATTR_SCREEN_REVERSE or TEM_ATTR_REVERSE
 */
static void
tem_get_attr(struct tem_vt_state *tem, text_color_t *fg,
    text_color_t *bg, text_attr_t *attr, uint8_t flag)
{
	if (tem->tvs_flags & flag) {
		*fg = tem->tvs_bg_color;
		*bg = tem->tvs_fg_color;
	} else {
		*fg = tem->tvs_fg_color;
		*bg = tem->tvs_bg_color;
	}

	if (attr == NULL)
		return;

	*attr = tem->tvs_flags;
}

static void
tem_get_color(text_color_t *fg, text_color_t *bg, term_char_t c)
{
	*fg = c.tc_fg_color;
	*bg = c.tc_bg_color;

	if (c.tc_fg_color < XLATE_NCOLORS) {
		if (TEM_CHAR_ATTR(c.tc_char) &
		    (TEM_ATTR_BRIGHT_FG | TEM_ATTR_BOLD))
			*fg = brt_xlate[c.tc_fg_color];
		else
			*fg = dim_xlate[c.tc_fg_color];
	}

	if (c.tc_bg_color < XLATE_NCOLORS) {
		if (TEM_CHAR_ATTR(c.tc_char) & TEM_ATTR_BRIGHT_BG)
			*bg = brt_xlate[c.tc_bg_color];
		else
			*bg = dim_xlate[c.tc_bg_color];
	}
}

void
tem_get_colors(tem_vt_state_t tem_arg, text_color_t *fg, text_color_t *bg)
{
	struct tem_vt_state *tem = (struct tem_vt_state *)tem_arg;
	text_attr_t attr;
	term_char_t c;

	tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr,
	    TEM_ATTR_REVERSE);
	c.tc_char = TEM_ATTR(attr);
	tem_get_color(fg, bg, c);
}

/*
 * Clear a rectangle of screen for pixel mode.
 *
 * arguments:
 *    row:	start row#
 *    nrows:	the number of rows to clear
 *    offset_y:	the offset of height in pixels to begin clear
 *    col:	start col#
 *    ncols:	the number of cols to clear
 *    offset_x:	the offset of width in pixels to begin clear
 *    scroll_up: whether this function is called during sroll up,
 *		 which is called only once.
 */
static void
tem_pix_cls_range(struct tem_vt_state *tem,
    screen_pos_t row, int nrows, int offset_y,
    screen_pos_t col, int ncols, int offset_x,
    boolean_t sroll_up)
{
	struct vis_consdisplay da;
	int	i, j;
	int	row_add = 0;
	term_char_t c;
	text_attr_t attr;

	if (sroll_up)
		row_add = tems.ts_c_dimension.height - 1;

	da.width = tems.ts_font.vf_width;
	da.height = tems.ts_font.vf_height;

	tem_get_attr(tem, &c.tc_fg_color, &c.tc_bg_color, &attr,
	    TEM_ATTR_SCREEN_REVERSE);
	/* Make sure we will not draw underlines */
	c.tc_char = TEM_ATTR(attr & ~TEM_ATTR_UNDERLINE) | ' ';

	tem_callback_bit2pix(tem, c);
	da.data = (uint8_t *)tem->tvs_pix_data;

	for (i = 0; i < nrows; i++, row++) {
		da.row = (row + row_add) * da.height + offset_y;
		da.col = col * da.width + offset_x;
		for (j = 0; j < ncols; j++) {
			tems_display(&da);
			da.col += da.width;
		}
	}
}

/*
 * virtual screen operations
 */
static void
tem_virtual_display(struct tem_vt_state *tem, term_char_t *string,
    size_t count, screen_pos_t row, screen_pos_t col)
{
	size_t i, width;
	term_char_t *addr;

	if (tem->tvs_screen_buf == NULL)
		return;

	if (row < 0 || row >= tems.ts_c_dimension.height ||
	    col < 0 || col >= tems.ts_c_dimension.width ||
	    col + count > (size_t)tems.ts_c_dimension.width)
		return;

	width = tems.ts_c_dimension.width;
	addr = tem->tvs_screen_buf + (row * width + col);
	for (i = 0; i < count; i++) {
		*addr++ = string[i];
	}
}

static void
tem_virtual_cls(struct tem_vt_state *tem, size_t count,
    screen_pos_t row, screen_pos_t col)
{
	term_char_t c;

	c.tc_char = ' ';
	tem_get_colors((tem_vt_state_t)tem, &c.tc_fg_color, &c.tc_bg_color);

	while (count > 0) {
		tem_virtual_display(tem, &c, 1, row, col);
		col++;
		count--;
	}
}