OpenSolaris_b135/uts/i86pc/boot/boot_console.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/archsystm.h>
#include <sys/boot_console.h>
#include <sys/panic.h>
#include <sys/ctype.h>
#if defined(__xpv)
#include <sys/hypervisor.h>
#endif /* __xpv */

#include "boot_serial.h"
#include "boot_vga.h"

#if defined(_BOOT)
#include <dboot/dboot_asm.h>
#include <dboot/dboot_xboot.h>
#else /* _BOOT */
#include <sys/bootconf.h>
#if defined(__xpv)
#include <sys/evtchn_impl.h>
#endif /* __xpv */
static char *defcons_buf;
static char *defcons_cur;
#endif /* _BOOT */

#if defined(__xpv)
extern void bcons_init_xen(char *);
extern void bcons_putchar_xen(int);
extern int bcons_getchar_xen(void);
extern int bcons_ischar_xen(void);
#endif /* __xpv */

static int cons_color = CONS_COLOR;
int console = CONS_SCREEN_TEXT;
#if defined(__xpv)
static int console_hypervisor_redirect = B_FALSE;
int console_hypervisor_device = CONS_INVALID;
#endif /* __xpv */

static int serial_ischar(void);
static int serial_getchar(void);
static void serial_putchar(int);
static void serial_adjust_prop(void);

static char *boot_line = NULL;

#if !defined(_BOOT)
/* Set if the console or mode are expressed in the boot line */
static int console_set, console_mode_set;
#endif

/* Clear the screen and initialize VIDEO, XPOS and YPOS. */
void
clear_screen(void)
{
	/*
	 * XXX should set vga mode so we don't depend on the
	 * state left by the boot loader.  Note that we have to
	 * enable the cursor before clearing the screen since
	 * the cursor position is dependant upon the cursor
	 * skew, which is initialized by vga_cursor_display()
	 */
	vga_cursor_display();
	vga_clear(cons_color);
	vga_setpos(0, 0);
}

/* Put the character C on the screen. */
static void
screen_putchar(int c)
{
	int row, col;

	vga_getpos(&row, &col);
	switch (c) {
	case '\t':
		col += 8 - (col % 8);
		if (col == VGA_TEXT_COLS)
			col = 79;
		vga_setpos(row, col);
		break;

	case '\r':
		vga_setpos(row, 0);
		break;

	case '\b':
		if (col > 0)
			vga_setpos(row, col - 1);
		break;

	case '\n':
		if (row < VGA_TEXT_ROWS - 1)
			vga_setpos(row + 1, col);
		else
			vga_scroll(cons_color);
		break;

	default:
		vga_drawc(c, cons_color);
		if (col < VGA_TEXT_COLS -1)
			vga_setpos(row, col + 1);
		else if (row < VGA_TEXT_ROWS - 1)
			vga_setpos(row + 1, 0);
		else {
			vga_setpos(row, 0);
			vga_scroll(cons_color);
		}
		break;
	}
}

static int port;

static void
serial_init(void)
{
	switch (console) {
	case CONS_TTYA:
		port = 0x3f8;
		break;
	case CONS_TTYB:
		port = 0x2f8;
		break;
	}

	outb(port + ISR, 0x20);
	if (inb(port + ISR) & 0x20) {
		/*
		 * 82510 chip is present
		 */
		outb(port + DAT+7, 0x04);	/* clear status */
		outb(port + ISR, 0x40);  /* set to bank 2 */
		outb(port + MCR, 0x08);  /* IMD */
		outb(port + DAT, 0x21);  /* FMD */
		outb(port + ISR, 0x00);  /* set to bank 0 */
	} else {
		/*
		 * set the UART in FIFO mode if it has FIFO buffers.
		 * use 16550 fifo reset sequence specified in NS
		 * application note. disable fifos until chip is
		 * initialized.
		 */
		outb(port + FIFOR, 0x00);		/* clear */
		outb(port + FIFOR, FIFO_ON);		/* enable */
		outb(port + FIFOR, FIFO_ON|FIFORXFLSH);  /* reset */
		outb(port + FIFOR,
		    FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80);
		if ((inb(port + ISR) & 0xc0) != 0xc0) {
			/*
			 * no fifo buffers so disable fifos.
			 * this is true for 8250's
			 */
			outb(port + FIFOR, 0x00);
		}
	}

	/* disable interrupts */
	outb(port + ICR, 0);

#if !defined(_BOOT)
	if (IN_XPV_PANIC())
		return;
#endif

	/* adjust setting based on tty properties */
	serial_adjust_prop();

#if defined(_BOOT)
	/*
	 * Do a full reset to match console behavior.
	 * 0x1B + c - reset everything
	 */
	serial_putchar(0x1B);
	serial_putchar('c');
#endif
}

/* Advance str pointer past white space */
#define	EAT_WHITE_SPACE(str)	{			\
	while ((*str != '\0') && ISSPACE(*str))		\
		str++;					\
}

/*
 * boot_line is set when we call here.  Search it for the argument name,
 * and if found, return a pointer to it.
 */
static char *
find_boot_line_prop(const char *name)
{
	char *ptr;
	char *ret = NULL;
	char end_char;
	size_t len;

	if (boot_line == NULL)
		return (NULL);

	len = strlen(name);

	/*
	 * We have two nested loops here: the outer loop discards all options
	 * except -B, and the inner loop parses the -B options looking for
	 * the one we're interested in.
	 */
	for (ptr = boot_line; *ptr != '\0'; ptr++) {
		EAT_WHITE_SPACE(ptr);

		if (*ptr == '-') {
			ptr++;
			while ((*ptr != '\0') && (*ptr != 'B') &&
			    !ISSPACE(*ptr))
				ptr++;
			if (*ptr == '\0')
				goto out;
			else if (*ptr != 'B')
				continue;
		} else {
			while ((*ptr != '\0') && !ISSPACE(*ptr))
				ptr++;
			if (*ptr == '\0')
				goto out;
			continue;
		}

		do {
			ptr++;
			EAT_WHITE_SPACE(ptr);

			if ((strncmp(ptr, name, len) == 0) &&
			    (ptr[len] == '=')) {
				ptr += len + 1;
				if ((*ptr == '\'') || (*ptr == '"')) {
					ret = ptr + 1;
					end_char = *ptr;
					ptr++;
				} else {
					ret = ptr;
					end_char = ',';
				}
				goto consume_property;
			}

			/*
			 * We have a property, and it's not the one we're
			 * interested in.  Skip the property name.  A name
			 * can end with '=', a comma, or white space.
			 */
			while ((*ptr != '\0') && (*ptr != '=') &&
			    (*ptr != ',') && (!ISSPACE(*ptr)))
				ptr++;

			/*
			 * We only want to go through the rest of the inner
			 * loop if we have a comma.  If we have a property
			 * name without a value, either continue or break.
			 */
			if (*ptr == '\0')
				goto out;
			else if (*ptr == ',')
				continue;
			else if (ISSPACE(*ptr))
				break;
			ptr++;

			/*
			 * Is the property quoted?
			 */
			if ((*ptr == '\'') || (*ptr == '"')) {
				end_char = *ptr;
				ptr++;
			} else {
				/*
				 * Not quoted, so the string ends at a comma
				 * or at white space.  Deal with white space
				 * later.
				 */
				end_char = ',';
			}

			/*
			 * Now, we can ignore any characters until we find
			 * end_char.
			 */
consume_property:
			for (; (*ptr != '\0') && (*ptr != end_char); ptr++) {
				if ((end_char == ',') && ISSPACE(*ptr))
					break;
			}
			if (*ptr && (*ptr != ',') && !ISSPACE(*ptr))
				ptr++;
		} while (*ptr == ',');
	}
out:
	return (ret);
}


#define	MATCHES(p, pat)	\
	(strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0)

#define	SKIP(p, c)				\
	while (*(p) != 0 && *p != (c))		\
		++(p);				\
	if (*(p) == (c))			\
		++(p);

/*
 * find a tty mode property either from cmdline or from boot properties
 */
static char *
get_mode_value(char *name)
{
	/*
	 * when specified on boot line it looks like "name" "="....
	 */
	if (boot_line != NULL) {
		return (find_boot_line_prop(name));
	}

#if defined(_BOOT)
	return (NULL);
#else
	/*
	 * if we're running in the full kernel we check the bootenv.rc settings
	 */
	{
		static char propval[20];

		propval[0] = 0;
		if (do_bsys_getproplen(NULL, name) <= 0)
			return (NULL);
		(void) do_bsys_getprop(NULL, name, propval);
		return (propval);
	}
#endif
}

/*
 * adjust serial port based on properties
 * These come either from the cmdline or from boot properties.
 */
static void
serial_adjust_prop(void)
{
	char propname[20];
	char *propval;
	char *p;
	ulong_t baud;
	uchar_t lcr = 0;
	uchar_t mcr = DTR | RTS;

	(void) strcpy(propname, "ttyX-mode");
	propname[3] = 'a' + console - CONS_TTYA;
	propval = get_mode_value(propname);
	if (propval == NULL)
		propval = "9600,8,n,1,-";
#if !defined(_BOOT)
	else
		console_mode_set = 1;
#endif

	/* property is of the form: "9600,8,n,1,-" */
	p = propval;
	if (MATCHES(p, "110,"))
		baud = ASY110;
	else if (MATCHES(p, "150,"))
		baud = ASY150;
	else if (MATCHES(p, "300,"))
		baud = ASY300;
	else if (MATCHES(p, "600,"))
		baud = ASY600;
	else if (MATCHES(p, "1200,"))
		baud = ASY1200;
	else if (MATCHES(p, "2400,"))
		baud = ASY2400;
	else if (MATCHES(p, "4800,"))
		baud = ASY4800;
	else if (MATCHES(p, "19200,"))
		baud = ASY19200;
	else if (MATCHES(p, "38400,"))
		baud = ASY38400;
	else if (MATCHES(p, "57600,"))
		baud = ASY57600;
	else if (MATCHES(p, "115200,"))
		baud = ASY115200;
	else {
		baud = ASY9600;
		SKIP(p, ',');
	}
	outb(port + LCR, DLAB);
	outb(port + DAT + DLL, baud & 0xff);
	outb(port + DAT + DLH, (baud >> 8) & 0xff);

	switch (*p) {
	case '5':
		lcr |= BITS5;
		++p;
		break;
	case '6':
		lcr |= BITS6;
		++p;
		break;
	case '7':
		lcr |= BITS7;
		++p;
		break;
	case '8':
		++p;
	default:
		lcr |= BITS8;
		break;
	}

	SKIP(p, ',');

	switch (*p) {
	case 'n':
		lcr |= PARITY_NONE;
		++p;
		break;
	case 'o':
		lcr |= PARITY_ODD;
		++p;
		break;
	case 'e':
		++p;
	default:
		lcr |= PARITY_EVEN;
		break;
	}


	SKIP(p, ',');

	switch (*p) {
	case '1':
		/* STOP1 is 0 */
		++p;
		break;
	default:
		lcr |= STOP2;
		break;
	}
	/* set parity bits */
	outb(port + LCR, lcr);

	(void) strcpy(propname, "ttyX-rts-dtr-off");
	propname[3] = 'a' + console - CONS_TTYA;
	propval = get_mode_value(propname);
	if (propval == NULL)
		propval = "false";
	if (propval[0] != 'f' && propval[0] != 'F')
		mcr = 0;
	/* set modem control bits */
	outb(port + MCR, mcr | OUT2);
}

/*
 * A structure to map console names to values.
 */
typedef struct {
	char *name;
	int value;
} console_value_t;

console_value_t console_devices[] = {
	{ "ttya", CONS_TTYA },
	{ "ttyb", CONS_TTYB },
	{ "text", CONS_SCREEN_TEXT },
	{ "graphics", CONS_SCREEN_GRAPHICS },
#if defined(__xpv)
	{ "hypervisor", CONS_HYPERVISOR },
#endif
#if !defined(_BOOT)
	{ "usb-serial", CONS_USBSER },
#endif
	{ "", CONS_INVALID }
};

void
bcons_init(char *bootstr)
{
	console_value_t *consolep;
	size_t len, cons_len;
	char *cons_str;
#if !defined(_BOOT)
	static char console_text[] = "text";
	extern int post_fastreboot;
#endif

	boot_line = bootstr;
	console = CONS_INVALID;

#if defined(__xpv)
	bcons_init_xen(bootstr);
#endif /* __xpv */

	cons_str = find_boot_line_prop("console");
	if (cons_str == NULL)
		cons_str = find_boot_line_prop("output-device");

#if !defined(_BOOT)
	if (post_fastreboot && strcmp(cons_str, "graphics") == 0)
		cons_str = console_text;
#endif

	/*
	 * Go through the console_devices array trying to match the string
	 * we were given.  The string on the command line must end with
	 * a comma or white space.
	 */
	if (cons_str != NULL) {
		cons_len = strlen(cons_str);
		consolep = console_devices;
		for (; consolep->name[0] != '\0'; consolep++) {
			len = strlen(consolep->name);
			if ((len <= cons_len) && ((cons_str[len] == '\0') ||
			    (cons_str[len] == ',') || (cons_str[len] == '\'') ||
			    (cons_str[len] == '"') || ISSPACE(cons_str[len])) &&
			    (strncmp(cons_str, consolep->name, len) == 0)) {
				console = consolep->value;
				break;
			}
		}
	}

#if defined(__xpv)
	/*
	 * domU's always use the hypervisor regardless of what
	 * the console variable may be set to.
	 */
	if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
		console = CONS_HYPERVISOR;
		console_hypervisor_redirect = B_TRUE;
	}
#endif /* __xpv */

	/*
	 * If no console device specified, default to text.
	 * Remember what was specified for second phase.
	 */
	if (console == CONS_INVALID)
		console = CONS_SCREEN_TEXT;
#if !defined(_BOOT)
	else
		console_set = 1;
#endif

#if defined(__xpv)
	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		switch (HYPERVISOR_console_io(CONSOLEIO_get_device, 0, NULL)) {
			case XEN_CONSOLE_COM1:
				console_hypervisor_device = CONS_TTYA;
				break;
			case XEN_CONSOLE_COM2:
				console_hypervisor_device = CONS_TTYB;
				break;
			case XEN_CONSOLE_VGA:
				/*
				 * Currently xen doesn't really support
				 * keyboard/display console devices.
				 * What this setting means is that
				 * "vga=keep" has been enabled, which is
				 * more of a xen debugging tool that a
				 * true console mode.  Hence, we're going
				 * to ignore this xen "console" setting.
				 */
				/*FALLTHROUGH*/
			default:
				console_hypervisor_device = CONS_INVALID;
		}
	}

	/*
	 * if the hypervisor is using the currently selected serial
	 * port then default to using the hypervisor as the console
	 * device.
	 */
	if (console == console_hypervisor_device) {
		console = CONS_HYPERVISOR;
		console_hypervisor_redirect = B_TRUE;
	}
#endif /* __xpv */

	switch (console) {
	case CONS_TTYA:
	case CONS_TTYB:
		serial_init();
		break;

	case CONS_HYPERVISOR:
		break;

#if !defined(_BOOT)
	case CONS_USBSER:
		/*
		 * We can't do anything with the usb serial
		 * until we have memory management.
		 */
		break;
#endif
	case CONS_SCREEN_GRAPHICS:
		kb_init();
		break;
	case CONS_SCREEN_TEXT:
	default:
#if defined(_BOOT)
		clear_screen();	/* clears the grub or xen screen */
#endif /* _BOOT */
		kb_init();
		break;
	}
	boot_line = NULL;
}

#if !defined(_BOOT)
/*
 * 2nd part of console initialization.
 * In the kernel (ie. fakebop), this can be used only to switch to
 * using a serial port instead of screen based on the contents
 * of the bootenv.rc file.
 */
/*ARGSUSED*/
void
bcons_init2(char *inputdev, char *outputdev, char *consoledev)
{
	int cons = CONS_INVALID;
	char *devnames[] = { consoledev, outputdev, inputdev, NULL };
	console_value_t *consolep;
	int i;
	extern int post_fastreboot;

	if (post_fastreboot && console == CONS_SCREEN_GRAPHICS)
		console = CONS_SCREEN_TEXT;

	if (console != CONS_USBSER && console != CONS_SCREEN_GRAPHICS) {
		if (console_set) {
			/*
			 * If the console was set on the command line,
			 * but the ttyX-mode was not, we only need to
			 * check bootenv.rc for that setting.
			 */
			if ((!console_mode_set) &&
			    (console == CONS_TTYA || console == CONS_TTYB))
				serial_init();
			return;
		}

		for (i = 0; devnames[i] != NULL; i++) {
			consolep = console_devices;
			for (; consolep->name[0] != '\0'; consolep++) {
				if (strcmp(devnames[i], consolep->name) == 0) {
					cons = consolep->value;
				}
			}
			if (cons != CONS_INVALID)
				break;
		}

#if defined(__xpv)
		/*
		 * if the hypervisor is using the currently selected console
		 * device then default to using the hypervisor as the console
		 * device.
		 */
		if (cons == console_hypervisor_device) {
			cons = CONS_HYPERVISOR;
			console_hypervisor_redirect = B_TRUE;
		}
#endif /* __xpv */

		if ((cons == CONS_INVALID) || (cons == console)) {
			/*
			 * we're sticking with whatever the current setting is
			 */
			return;
		}

		console = cons;
		if (cons == CONS_TTYA || cons == CONS_TTYB) {
			serial_init();
			return;
		}
	} else {
		/*
		 * USB serial and GRAPHICS console
		 * we just collect data into a buffer
		 */
		extern void *defcons_init(size_t);
		defcons_buf = defcons_cur = defcons_init(MMU_PAGESIZE);
	}
}

#if defined(__xpv)
boolean_t
bcons_hypervisor_redirect(void)
{
	return (console_hypervisor_redirect);
}

void
bcons_device_change(int new_console)
{
	if (new_console < CONS_MIN || new_console > CONS_MAX)
		return;

	/*
	 * If we are asked to switch the console to the hypervisor, that
	 * really means to switch the console to whichever device the
	 * hypervisor is/was using.
	 */
	if (new_console == CONS_HYPERVISOR)
		new_console = console_hypervisor_device;

	console = new_console;

	if (new_console == CONS_TTYA || new_console == CONS_TTYB)
		serial_init();
}
#endif /* __xpv */

static void
defcons_putchar(int c)
{
	if (defcons_buf != NULL &&
	    defcons_cur + 1 - defcons_buf < MMU_PAGESIZE) {
		*defcons_cur++ = c;
		*defcons_cur = 0;
	}
}
#endif	/* _BOOT */

static void
serial_putchar(int c)
{
	int checks = 10000;

	while (((inb(port + LSR) & XHRE) == 0) && checks--)
		;
	outb(port + DAT, (char)c);
}

static int
serial_getchar(void)
{
	uchar_t lsr;

	while (serial_ischar() == 0)
		;

	lsr = inb(port + LSR);
	if (lsr & (SERIAL_BREAK | SERIAL_FRAME |
	    SERIAL_PARITY | SERIAL_OVERRUN)) {
		if (lsr & SERIAL_OVERRUN) {
			return (inb(port + DAT));
		} else {
			/* Toss the garbage */
			(void) inb(port + DAT);
			return (0);
		}
	}
	return (inb(port + DAT));
}

static int
serial_ischar(void)
{
	return (inb(port + LSR) & RCA);
}

static void
_doputchar(int c)
{
	switch (console) {
	case CONS_TTYA:
	case CONS_TTYB:
		serial_putchar(c);
		return;
	case CONS_SCREEN_TEXT:
		screen_putchar(c);
		return;
	case CONS_SCREEN_GRAPHICS:
#if !defined(_BOOT)
	case CONS_USBSER:
		defcons_putchar(c);
#endif /* _BOOT */
		return;
	}
}

void
bcons_putchar(int c)
{
	static int bhcharpos = 0;

#if defined(__xpv)
	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
	    console == CONS_HYPERVISOR) {
		bcons_putchar_xen(c);
		return;
	}
#endif /* __xpv */

	if (c == '\t') {
		do {
			_doputchar(' ');
		} while (++bhcharpos % 8);
		return;
	} else  if (c == '\n' || c == '\r') {
		bhcharpos = 0;
		_doputchar('\r');
		_doputchar(c);
		return;
	} else if (c == '\b') {
		if (bhcharpos)
			bhcharpos--;
		_doputchar(c);
		return;
	}

	bhcharpos++;
	_doputchar(c);
}

/*
 * kernel character input functions
 */
int
bcons_getchar(void)
{
#if defined(__xpv)
	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
	    console == CONS_HYPERVISOR)
		return (bcons_getchar_xen());
#endif /* __xpv */

	switch (console) {
	case CONS_TTYA:
	case CONS_TTYB:
		return (serial_getchar());
	default:
		return (kb_getchar());
	}
}

#if !defined(_BOOT)

int
bcons_ischar(void)
{

#if defined(__xpv)
	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
	    console == CONS_HYPERVISOR)
		return (bcons_ischar_xen());
#endif /* __xpv */

	switch (console) {
	case CONS_TTYA:
	case CONS_TTYB:
		return (serial_ischar());
	default:
		return (kb_ischar());
	}
}

#endif /* _BOOT */