1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright (c) 2012 Gary Mills
23 * Copyright 2020 Joyent, Inc.
24 *
25 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
26 * Use is subject to license terms.
27 */
28
29/*
30 * Boot console support.  Most of the file is shared between dboot, and the
31 * early kernel / fakebop.
32 */
33
34#include <sys/types.h>
35#include <sys/systm.h>
36#include <sys/archsystm.h>
37#include <sys/framebuffer.h>
38#include <sys/boot_console.h>
39#include <sys/panic.h>
40#include <sys/ctype.h>
41#include <sys/ascii.h>
42#include <sys/vgareg.h>
43#if defined(__xpv)
44#include <sys/hypervisor.h>
45#endif /* __xpv */
46
47#include "boot_console_impl.h"
48#include "boot_serial.h"
49
50#if defined(_BOOT)
51#include <dboot/dboot_asm.h>
52#include <dboot/dboot_xboot.h>
53#else /* _BOOT */
54#include <sys/bootconf.h>
55#if defined(__xpv)
56#include <sys/evtchn_impl.h>
57#endif /* __xpv */
58static char *defcons_buf;
59static char *defcons_cur;
60#endif /* _BOOT */
61
62#if defined(__xpv)
63extern void bcons_init_xen(char *);
64extern void bcons_putchar_xen(int);
65extern int bcons_getchar_xen(void);
66extern int bcons_ischar_xen(void);
67#endif /* __xpv */
68
69fb_info_t fb_info;
70static bcons_dev_t bcons_dev;				/* Device callbacks */
71static int console = CONS_SCREEN_TEXT;
72static int diag = CONS_INVALID;
73static int tty_num = 0;
74static int tty_addr[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8};
75static char *boot_line;
76static struct boot_env {
77	char	*be_env;	/* ends with double ascii nul */
78	size_t	be_size;	/* size of the environment, including nul */
79} boot_env;
80
81/*
82 * Simple console terminal emulator for early boot.
83 * We need this to support kmdb, all other console output is supposed
84 * to be simple text output.
85 */
86typedef enum btem_state_type {
87	A_STATE_START,
88	A_STATE_ESC,
89	A_STATE_CSI,
90	A_STATE_CSI_QMARK,
91	A_STATE_CSI_EQUAL
92} btem_state_type_t;
93
94#define	BTEM_MAXPARAMS	5
95typedef struct btem_state {
96	btem_state_type_t btem_state;
97	boolean_t btem_gotparam;
98	int btem_curparam;
99	int btem_paramval;
100	int btem_params[BTEM_MAXPARAMS];
101} btem_state_t;
102
103static btem_state_t boot_tem;
104
105static int serial_ischar(void);
106static int serial_getchar(void);
107static void serial_putchar(int);
108static void serial_adjust_prop(void);
109
110static void defcons_putchar(int);
111
112#if !defined(_BOOT)
113static boolean_t bootprop_set_tty_mode;
114#endif
115
116#if defined(__xpv)
117static int console_hypervisor_redirect = B_FALSE;
118static int console_hypervisor_device = CONS_INVALID;
119static int console_hypervisor_tty_num = 0;
120
121/* Obtain the hypervisor console type */
122int
123console_hypervisor_dev_type(int *tnum)
124{
125	if (tnum != NULL)
126		*tnum = console_hypervisor_tty_num;
127	return (console_hypervisor_device);
128}
129#endif /* __xpv */
130
131static int port;
132
133static void
134serial_init(void)
135{
136	port = tty_addr[tty_num];
137
138	outb(port + ISR, 0x20);
139	if (inb(port + ISR) & 0x20) {
140		/*
141		 * 82510 chip is present
142		 */
143		outb(port + DAT+7, 0x04);	/* clear status */
144		outb(port + ISR, 0x40);  /* set to bank 2 */
145		outb(port + MCR, 0x08);  /* IMD */
146		outb(port + DAT, 0x21);  /* FMD */
147		outb(port + ISR, 0x00);  /* set to bank 0 */
148	} else {
149		/*
150		 * set the UART in FIFO mode if it has FIFO buffers.
151		 * use 16550 fifo reset sequence specified in NS
152		 * application note. disable fifos until chip is
153		 * initialized.
154		 */
155		outb(port + FIFOR, 0x00);		/* clear */
156		outb(port + FIFOR, FIFO_ON);		/* enable */
157		outb(port + FIFOR, FIFO_ON|FIFORXFLSH);  /* reset */
158		outb(port + FIFOR,
159		    FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80);
160		if ((inb(port + ISR) & 0xc0) != 0xc0) {
161			/*
162			 * no fifo buffers so disable fifos.
163			 * this is true for 8250's
164			 */
165			outb(port + FIFOR, 0x00);
166		}
167	}
168
169	/* disable interrupts */
170	outb(port + ICR, 0);
171
172#if !defined(_BOOT)
173	if (IN_XPV_PANIC())
174		return;
175#endif
176
177	/* adjust setting based on tty properties */
178	serial_adjust_prop();
179}
180
181/* Advance str pointer past white space */
182#define	EAT_WHITE_SPACE(str)	{			\
183	while ((*str != '\0') && ISSPACE(*str))		\
184		str++;					\
185}
186
187/*
188 * boot_line is set when we call here.  Search it for the argument name,
189 * and if found, return a pointer to it.
190 */
191static char *
192find_boot_line_prop(const char *name)
193{
194	char *ptr;
195	char *ret = NULL;
196	char end_char;
197	size_t len;
198
199	if (boot_line == NULL)
200		return (NULL);
201
202	len = strlen(name);
203
204	/*
205	 * We have two nested loops here: the outer loop discards all options
206	 * except -B, and the inner loop parses the -B options looking for
207	 * the one we're interested in.
208	 */
209	for (ptr = boot_line; *ptr != '\0'; ptr++) {
210		EAT_WHITE_SPACE(ptr);
211
212		if (*ptr == '-') {
213			ptr++;
214			while ((*ptr != '\0') && (*ptr != 'B') &&
215			    !ISSPACE(*ptr))
216				ptr++;
217			if (*ptr == '\0')
218				goto out;
219			else if (*ptr != 'B')
220				continue;
221		} else {
222			while ((*ptr != '\0') && !ISSPACE(*ptr))
223				ptr++;
224			if (*ptr == '\0')
225				goto out;
226			continue;
227		}
228
229		do {
230			ptr++;
231			EAT_WHITE_SPACE(ptr);
232
233			if ((strncmp(ptr, name, len) == 0) &&
234			    (ptr[len] == '=')) {
235				ptr += len + 1;
236				if ((*ptr == '\'') || (*ptr == '"')) {
237					ret = ptr + 1;
238					end_char = *ptr;
239					ptr++;
240				} else {
241					ret = ptr;
242					end_char = ',';
243				}
244				goto consume_property;
245			}
246
247			/*
248			 * We have a property, and it's not the one we're
249			 * interested in.  Skip the property name.  A name
250			 * can end with '=', a comma, or white space.
251			 */
252			while ((*ptr != '\0') && (*ptr != '=') &&
253			    (*ptr != ',') && (!ISSPACE(*ptr)))
254				ptr++;
255
256			/*
257			 * We only want to go through the rest of the inner
258			 * loop if we have a comma.  If we have a property
259			 * name without a value, either continue or break.
260			 */
261			if (*ptr == '\0')
262				goto out;
263			else if (*ptr == ',')
264				continue;
265			else if (ISSPACE(*ptr))
266				break;
267			ptr++;
268
269			/*
270			 * Is the property quoted?
271			 */
272			if ((*ptr == '\'') || (*ptr == '"')) {
273				end_char = *ptr;
274				ptr++;
275			} else {
276				/*
277				 * Not quoted, so the string ends at a comma
278				 * or at white space.  Deal with white space
279				 * later.
280				 */
281				end_char = ',';
282			}
283
284			/*
285			 * Now, we can ignore any characters until we find
286			 * end_char.
287			 */
288consume_property:
289			for (; (*ptr != '\0') && (*ptr != end_char); ptr++) {
290				if ((end_char == ',') && ISSPACE(*ptr))
291					break;
292			}
293			if (*ptr && (*ptr != ',') && !ISSPACE(*ptr))
294				ptr++;
295		} while (*ptr == ',');
296	}
297out:
298	return (ret);
299}
300
301/*
302 * Find prop from boot env module. The data in module is list of C strings
303 * name=value, the list is terminated by double nul.
304 */
305static const char *
306find_boot_env_prop(const char *name)
307{
308	char *ptr;
309	size_t len;
310	uintptr_t size;
311
312	if (boot_env.be_env == NULL)
313		return (NULL);
314
315	ptr = boot_env.be_env;
316	len = strlen(name);
317
318	/*
319	 * Make sure we have at least len + 2 bytes in the environment.
320	 * We are looking for name=value\0 constructs, and the environment
321	 * itself is terminated by '\0'.
322	 */
323	if (boot_env.be_size < len + 2)
324		return (NULL);
325
326	do {
327		if ((strncmp(ptr, name, len) == 0) && (ptr[len] == '=')) {
328			ptr += len + 1;
329			return (ptr);
330		}
331		/* find the first '\0' */
332		while (*ptr != '\0') {
333			ptr++;
334			size = (uintptr_t)ptr - (uintptr_t)boot_env.be_env;
335			if (size > boot_env.be_size)
336				return (NULL);
337		}
338		ptr++;
339
340		/* If the remainder is shorter than name + 2, get out. */
341		size = (uintptr_t)ptr - (uintptr_t)boot_env.be_env;
342		if (boot_env.be_size - size < len + 2)
343			return (NULL);
344	} while (*ptr != '\0');
345	return (NULL);
346}
347
348/*
349 * Get prop value from either command line or boot environment.
350 * We always check kernel command line first, as this will keep the
351 * functionality and will allow user to override the values in environment.
352 */
353const char *
354find_boot_prop(const char *name)
355{
356	const char *value = find_boot_line_prop(name);
357
358	if (value == NULL)
359		value = find_boot_env_prop(name);
360	return (value);
361}
362
363#define	MATCHES(p, pat)	\
364	(strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0)
365
366#define	SKIP(p, c)				\
367	while (*(p) != 0 && *p != (c))		\
368		++(p);				\
369	if (*(p) == (c))			\
370		++(p);
371
372/*
373 * find a tty mode property either from cmdline or from boot properties
374 */
375static const char *
376get_mode_value(char *name)
377{
378	/*
379	 * when specified on boot line it looks like "name" "="....
380	 */
381	if (boot_line != NULL) {
382		return (find_boot_prop(name));
383	}
384
385#if defined(_BOOT)
386	return (NULL);
387#else
388	/*
389	 * if we're running in the full kernel we check the bootenv.rc settings
390	 */
391	{
392		static char propval[20];
393
394		propval[0] = 0;
395		if (do_bsys_getproplen(NULL, name) <= 0)
396			return (NULL);
397		(void) do_bsys_getprop(NULL, name, propval);
398		return (propval);
399	}
400#endif
401}
402
403/*
404 * adjust serial port based on properties
405 * These come either from the cmdline or from boot properties.
406 */
407static void
408serial_adjust_prop(void)
409{
410	char propname[20];
411	const char *propval;
412	const char *p;
413	ulong_t baud;
414	uchar_t lcr = 0;
415	uchar_t mcr = DTR | RTS;
416
417	(void) strcpy(propname, "ttyX-mode");
418	propname[3] = 'a' + tty_num;
419	propval = get_mode_value(propname);
420#if !defined(_BOOT)
421	if (propval != NULL)
422		bootprop_set_tty_mode = B_TRUE;
423#endif
424	if (propval == NULL)
425		propval = "9600,8,n,1,-";
426
427	/* property is of the form: "9600,8,n,1,-" */
428	p = propval;
429	if (MATCHES(p, "110,"))
430		baud = ASY110;
431	else if (MATCHES(p, "150,"))
432		baud = ASY150;
433	else if (MATCHES(p, "300,"))
434		baud = ASY300;
435	else if (MATCHES(p, "600,"))
436		baud = ASY600;
437	else if (MATCHES(p, "1200,"))
438		baud = ASY1200;
439	else if (MATCHES(p, "2400,"))
440		baud = ASY2400;
441	else if (MATCHES(p, "4800,"))
442		baud = ASY4800;
443	else if (MATCHES(p, "19200,"))
444		baud = ASY19200;
445	else if (MATCHES(p, "38400,"))
446		baud = ASY38400;
447	else if (MATCHES(p, "57600,"))
448		baud = ASY57600;
449	else if (MATCHES(p, "115200,"))
450		baud = ASY115200;
451	else {
452		baud = ASY9600;
453		SKIP(p, ',');
454	}
455	outb(port + LCR, DLAB);
456	outb(port + DAT + DLL, baud & 0xff);
457	outb(port + DAT + DLH, (baud >> 8) & 0xff);
458
459	switch (*p) {
460	case '5':
461		lcr |= BITS5;
462		++p;
463		break;
464	case '6':
465		lcr |= BITS6;
466		++p;
467		break;
468	case '7':
469		lcr |= BITS7;
470		++p;
471		break;
472	case '8':
473		++p;
474		/* FALLTHROUGH */
475	default:
476		lcr |= BITS8;
477		break;
478	}
479
480	SKIP(p, ',');
481
482	switch (*p) {
483	case 'n':
484		lcr |= PARITY_NONE;
485		++p;
486		break;
487	case 'o':
488		lcr |= PARITY_ODD;
489		++p;
490		break;
491	case 'e':
492		++p;
493		/* FALLTHROUGH */
494	default:
495		lcr |= PARITY_EVEN;
496		break;
497	}
498
499
500	SKIP(p, ',');
501
502	switch (*p) {
503	case '1':
504		/* STOP1 is 0 */
505		++p;
506		break;
507	default:
508		lcr |= STOP2;
509		break;
510	}
511	/* set parity bits */
512	outb(port + LCR, lcr);
513
514	(void) strcpy(propname, "ttyX-rts-dtr-off");
515	propname[3] = 'a' + tty_num;
516	propval = get_mode_value(propname);
517	if (propval == NULL)
518		propval = "false";
519	if (propval[0] != 'f' && propval[0] != 'F')
520		mcr = 0;
521	/* set modem control bits */
522	outb(port + MCR, mcr | OUT2);
523}
524
525/* Obtain the console type */
526int
527boot_console_type(int *tnum)
528{
529	if (tnum != NULL)
530		*tnum = tty_num;
531	return (console);
532}
533
534/*
535 * A structure to map console names to values.
536 */
537typedef struct {
538	char *name;
539	int value;
540} console_value_t;
541
542console_value_t console_devices[] = {
543	{ "ttya", CONS_TTY },	/* 0 */
544	{ "ttyb", CONS_TTY },	/* 1 */
545	{ "ttyc", CONS_TTY },	/* 2 */
546	{ "ttyd", CONS_TTY },	/* 3 */
547	{ "text", CONS_SCREEN_TEXT },
548	{ "graphics", CONS_SCREEN_GRAPHICS },
549#if defined(__xpv)
550	{ "hypervisor", CONS_HYPERVISOR },
551#endif
552#if !defined(_BOOT)
553	{ "usb-serial", CONS_USBSER },
554#endif
555	{ NULL, CONS_INVALID }
556};
557
558static void
559bcons_init_env(struct xboot_info *xbi)
560{
561	uint32_t i;
562	struct boot_modules *modules;
563
564	modules = (struct boot_modules *)(uintptr_t)xbi->bi_modules;
565	for (i = 0; i < xbi->bi_module_cnt; i++) {
566		if (modules[i].bm_type == BMT_ENV)
567			break;
568	}
569	if (i == xbi->bi_module_cnt)
570		return;
571
572	boot_env.be_env = (char *)(uintptr_t)modules[i].bm_addr;
573	boot_env.be_size = modules[i].bm_size;
574}
575
576int
577boot_fb(struct xboot_info *xbi, int console)
578{
579	if (xbi_fb_init(xbi, &bcons_dev) == B_FALSE)
580		return (console);
581
582	/* FB address is not set, fall back to serial terminal. */
583	if (fb_info.paddr == 0)
584		return (CONS_TTY);
585
586	fb_info.terminal.x = VGA_TEXT_COLS;
587	fb_info.terminal.y = VGA_TEXT_ROWS;
588	boot_fb_init(CONS_FRAMEBUFFER);
589
590	if (console == CONS_SCREEN_TEXT)
591		return (CONS_FRAMEBUFFER);
592	return (console);
593}
594
595/*
596 * TODO.
597 * quick and dirty local atoi. Perhaps should build with strtol, but
598 * dboot & early boot mix does overcomplicate things much.
599 * Stolen from libc anyhow.
600 */
601static int
602atoi(const char *p)
603{
604	int n, c, neg = 0;
605	unsigned char *up = (unsigned char *)p;
606
607	if (!isdigit(c = *up)) {
608		while (isspace(c))
609			c = *++up;
610		switch (c) {
611		case '-':
612			neg++;
613			/* FALLTHROUGH */
614		case '+':
615			c = *++up;
616		}
617		if (!isdigit(c))
618			return (0);
619	}
620	for (n = '0' - c; isdigit(c = *++up); ) {
621		n *= 10; /* two steps to avoid unnecessary overflow */
622		n += '0' - c; /* accum neg to avoid surprises at MAX */
623	}
624	return (neg ? n : -n);
625}
626
627static void
628bcons_init_fb(void)
629{
630	const char *propval;
631	int intval;
632
633	/* initialize with explicit default values */
634	fb_info.fg_color = CONS_COLOR;
635	fb_info.bg_color = 0;
636	fb_info.inverse = B_FALSE;
637	fb_info.inverse_screen = B_FALSE;
638
639	/* color values are 0 - 255 */
640	propval = find_boot_prop("tem.fg_color");
641	if (propval != NULL) {
642		intval = atoi(propval);
643		if (intval >= 0 && intval <= 255)
644			fb_info.fg_color = intval;
645	}
646
647	/* color values are 0 - 255 */
648	propval = find_boot_prop("tem.bg_color");
649	if (propval != NULL && ISDIGIT(*propval)) {
650		intval = atoi(propval);
651		if (intval >= 0 && intval <= 255)
652			fb_info.bg_color = intval;
653	}
654
655	/* get inverses. allow 0, 1, true, false */
656	propval = find_boot_prop("tem.inverse");
657	if (propval != NULL) {
658		if (*propval == '1' || MATCHES(propval, "true"))
659			fb_info.inverse = B_TRUE;
660	}
661
662	propval = find_boot_prop("tem.inverse-screen");
663	if (propval != NULL) {
664		if (*propval == '1' || MATCHES(propval, "true"))
665			fb_info.inverse_screen = B_TRUE;
666	}
667
668#if defined(_BOOT)
669	/*
670	 * Load cursor position from bootloader only in dboot,
671	 * dboot will pass cursor position to kernel via xboot info.
672	 */
673	propval = find_boot_prop("tem.cursor.row");
674	if (propval != NULL) {
675		intval = atoi(propval);
676		if (intval >= 0 && intval <= 0xFFFF)
677			fb_info.cursor.pos.y = intval;
678	}
679
680	propval = find_boot_prop("tem.cursor.col");
681	if (propval != NULL) {
682		intval = atoi(propval);
683		if (intval >= 0 && intval <= 0xFFFF)
684			fb_info.cursor.pos.x = intval;
685	}
686#endif
687}
688
689/*
690 * Go through the known console device names trying to match the string we were
691 * given.  The string on the command line must end with a comma or white space.
692 *
693 * For convenience, we provide the caller with an integer index for the CONS_TTY
694 * case.
695 */
696static int
697lookup_console_device(const char *cons_str, int *indexp)
698{
699	int n, cons;
700	size_t len, cons_len;
701	console_value_t *consolep;
702
703	cons = CONS_INVALID;
704	if (cons_str != NULL) {
705
706		cons_len = strlen(cons_str);
707		for (n = 0; console_devices[n].name != NULL; n++) {
708			consolep = &console_devices[n];
709			len = strlen(consolep->name);
710			if ((len <= cons_len) && ((cons_str[len] == '\0') ||
711			    (cons_str[len] == ',') || (cons_str[len] == '\'') ||
712			    (cons_str[len] == '"') || ISSPACE(cons_str[len])) &&
713			    (strncmp(cons_str, consolep->name, len) == 0)) {
714				cons = consolep->value;
715				if (cons == CONS_TTY)
716					*indexp = n;
717				break;
718			}
719		}
720	}
721	return (cons);
722}
723
724void
725bcons_init(struct xboot_info *xbi)
726{
727	const char *cons_str;
728#if !defined(_BOOT)
729	static char console_text[] = "text";
730	extern int post_fastreboot;
731#endif
732
733	if (xbi == NULL) {
734		/* This is very early dboot console, set up ttya. */
735		console = CONS_TTY;
736		serial_init();
737		return;
738	}
739
740	/* Set up data to fetch properties from commad line and boot env. */
741	boot_line = (char *)(uintptr_t)xbi->bi_cmdline;
742	bcons_init_env(xbi);
743	console = CONS_INVALID;
744
745	/* set up initial fb_info */
746	bcons_init_fb();
747
748#if defined(__xpv)
749	bcons_init_xen(boot_line);
750#endif /* __xpv */
751
752	/*
753	 * First check for diag-device.
754	 */
755	cons_str = find_boot_prop("diag-device");
756	if (cons_str != NULL)
757		diag = lookup_console_device(cons_str, &tty_num);
758
759	cons_str = find_boot_prop("console");
760	if (cons_str == NULL)
761		cons_str = find_boot_prop("output-device");
762
763#if !defined(_BOOT)
764	if (post_fastreboot && strcmp(cons_str, "graphics") == 0)
765		cons_str = console_text;
766#endif
767
768	if (cons_str != NULL)
769		console = lookup_console_device(cons_str, &tty_num);
770
771#if defined(__xpv)
772	/*
773	 * domU's always use the hypervisor regardless of what
774	 * the console variable may be set to.
775	 */
776	if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
777		console = CONS_HYPERVISOR;
778		console_hypervisor_redirect = B_TRUE;
779	}
780#endif /* __xpv */
781
782	if (console == CONS_INVALID)
783		console = CONS_SCREEN_TEXT;
784
785#if defined(__xpv)
786	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
787		switch (HYPERVISOR_console_io(CONSOLEIO_get_device, 0, NULL)) {
788			case XEN_CONSOLE_COM1:
789			case XEN_CONSOLE_COM2:
790				console_hypervisor_device = CONS_TTY;
791				console_hypervisor_tty_num = tty_num;
792				break;
793			case XEN_CONSOLE_VGA:
794				/*
795				 * Currently xen doesn't really support
796				 * keyboard/display console devices.
797				 * What this setting means is that
798				 * "vga=keep" has been enabled, which is
799				 * more of a xen debugging tool that a
800				 * true console mode.  Hence, we're going
801				 * to ignore this xen "console" setting.
802				 */
803				/*FALLTHROUGH*/
804			default:
805				console_hypervisor_device = CONS_INVALID;
806		}
807	}
808
809	/*
810	 * if the hypervisor is using the currently selected serial
811	 * port then default to using the hypervisor as the console
812	 * device.
813	 */
814	if (console == console_hypervisor_device) {
815		console = CONS_HYPERVISOR;
816		console_hypervisor_redirect = B_TRUE;
817	}
818#endif /* __xpv */
819
820	/* make sure the FB is set up if present */
821	console = boot_fb(xbi, console);
822	switch (console) {
823	case CONS_TTY:
824		serial_init();
825		break;
826
827	case CONS_HYPERVISOR:
828		break;
829
830#if !defined(_BOOT)
831	case CONS_USBSER:
832		/*
833		 * We can't do anything with the usb serial
834		 * until we have memory management.
835		 */
836		break;
837#endif
838	case CONS_SCREEN_GRAPHICS:
839		kb_init();
840		break;
841	case CONS_SCREEN_TEXT:
842		boot_vga_init(&bcons_dev);
843		/* Fall through */
844	default:
845		kb_init();
846		break;
847	}
848
849	/*
850	 * Initialize diag device unless already done.
851	 */
852	switch (diag) {
853	case CONS_TTY:
854		if (console != CONS_TTY)
855			serial_init();
856		break;
857	case CONS_SCREEN_GRAPHICS:
858	case CONS_SCREEN_TEXT:
859		if (console != CONS_SCREEN_GRAPHICS &&
860		    console != CONS_SCREEN_TEXT)
861			kb_init();
862		break;
863	default:
864		break;
865	}
866}
867
868static void
869serial_putchar(int c)
870{
871	int checks = 10000;
872
873	while (((inb(port + LSR) & XHRE) == 0) && checks--)
874		;
875	outb(port + DAT, (char)c);
876}
877
878static int
879serial_getchar(void)
880{
881	uchar_t lsr;
882
883	while (serial_ischar() == 0)
884		;
885
886	lsr = inb(port + LSR);
887	if (lsr & (SERIAL_BREAK | SERIAL_FRAME |
888	    SERIAL_PARITY | SERIAL_OVERRUN)) {
889		if (lsr & SERIAL_OVERRUN) {
890			return (inb(port + DAT));
891		} else {
892			/* Toss the garbage */
893			(void) inb(port + DAT);
894			return (0);
895		}
896	}
897	return (inb(port + DAT));
898}
899
900static int
901serial_ischar(void)
902{
903	return (inb(port + LSR) & RCA);
904}
905
906static void
907btem_control(btem_state_t *btem, int c)
908{
909	int y, rows, cols;
910
911	rows = fb_info.cursor.pos.y;
912	cols = fb_info.cursor.pos.x;
913
914	btem->btem_state = A_STATE_START;
915	switch (c) {
916	case A_BS:
917		bcons_dev.bd_setpos(rows, cols - 1);
918		break;
919
920	case A_HT:
921		cols += 8 - (cols % 8);
922		if (cols >= fb_info.terminal.x)
923			cols = fb_info.terminal.x - 1;
924		bcons_dev.bd_setpos(rows, cols);
925		break;
926
927	case A_CR:
928		bcons_dev.bd_setpos(rows, 0);
929		break;
930
931	case A_FF:
932		for (y = 0; y < fb_info.terminal.y; y++) {
933			bcons_dev.bd_setpos(y, 0);
934			bcons_dev.bd_eraseline();
935		}
936		bcons_dev.bd_setpos(0, 0);
937		break;
938
939	case A_ESC:
940		btem->btem_state = A_STATE_ESC;
941		break;
942
943	default:
944		bcons_dev.bd_putchar(c);
945		break;
946	}
947}
948
949/*
950 * if parameters [0..count - 1] are not set, set them to the value
951 * of newparam.
952 */
953static void
954btem_setparam(btem_state_t *btem, int count, int newparam)
955{
956	int i;
957
958	for (i = 0; i < count; i++) {
959		if (btem->btem_params[i] == -1)
960			btem->btem_params[i] = newparam;
961	}
962}
963
964static void
965btem_chkparam(btem_state_t *btem, int c)
966{
967	int rows, cols;
968
969	rows = fb_info.cursor.pos.y;
970	cols = fb_info.cursor.pos.x;
971	switch (c) {
972	case '@':			/* insert char */
973		btem_setparam(btem, 1, 1);
974		bcons_dev.bd_shift(btem->btem_params[0]);
975		break;
976
977	case 'A':			/* cursor up */
978		btem_setparam(btem, 1, 1);
979		bcons_dev.bd_setpos(rows - btem->btem_params[0], cols);
980		break;
981
982	case 'B':			/* cursor down */
983		btem_setparam(btem, 1, 1);
984		bcons_dev.bd_setpos(rows + btem->btem_params[0], cols);
985		break;
986
987	case 'C':			/* cursor right */
988		btem_setparam(btem, 1, 1);
989		bcons_dev.bd_setpos(rows, cols + btem->btem_params[0]);
990		break;
991
992	case 'D':			/* cursor left */
993		btem_setparam(btem, 1, 1);
994		bcons_dev.bd_setpos(rows, cols - btem->btem_params[0]);
995		break;
996
997	case 'K':
998		bcons_dev.bd_eraseline();
999		break;
1000	default:
1001		/* bcons_dev.bd_putchar(c); */
1002		break;
1003	}
1004	btem->btem_state = A_STATE_START;
1005}
1006
1007static void
1008btem_getparams(btem_state_t *btem, int c)
1009{
1010	if (isdigit(c)) {
1011		btem->btem_paramval = btem->btem_paramval * 10 + c - '0';
1012		btem->btem_gotparam = B_TRUE;
1013		return;
1014	}
1015
1016	if (btem->btem_curparam < BTEM_MAXPARAMS) {
1017		if (btem->btem_gotparam == B_TRUE) {
1018			btem->btem_params[btem->btem_curparam] =
1019			    btem->btem_paramval;
1020		}
1021		btem->btem_curparam++;
1022	}
1023
1024	if (c == ';') {
1025		/* Restart parameter search */
1026		btem->btem_gotparam = B_FALSE;
1027		btem->btem_paramval = 0;
1028	} else {
1029		btem_chkparam(btem, c);
1030	}
1031}
1032
1033/* Simple boot terminal parser. */
1034static void
1035btem_parse(btem_state_t *btem, int c)
1036{
1037	int i;
1038
1039	/* Normal state? */
1040	if (btem->btem_state == A_STATE_START) {
1041		if (c == A_CSI || c < ' ')
1042			btem_control(btem, c);
1043		else
1044			bcons_dev.bd_putchar(c);
1045		return;
1046	}
1047
1048	/* In <ESC> sequence */
1049	if (btem->btem_state != A_STATE_ESC) {
1050		btem_getparams(btem, c);
1051		return;
1052	}
1053
1054	/* Previous char was <ESC> */
1055	switch (c) {
1056	case '[':
1057		btem->btem_curparam = 0;
1058		btem->btem_paramval = 0;
1059		btem->btem_gotparam = B_FALSE;
1060		/* clear the parameters */
1061		for (i = 0; i < BTEM_MAXPARAMS; i++)
1062			btem->btem_params[i] = -1;
1063		btem->btem_state = A_STATE_CSI;
1064		return;
1065
1066	case 'Q':	/* <ESC>Q */
1067	case 'C':	/* <ESC>C */
1068		btem->btem_state = A_STATE_START;
1069		return;
1070
1071	default:
1072		btem->btem_state = A_STATE_START;
1073		break;
1074	}
1075
1076	if (c < ' ')
1077		btem_control(btem, c);
1078	else
1079		bcons_dev.bd_putchar(c);
1080}
1081
1082static void
1083_doputchar(int device, int c)
1084{
1085	switch (device) {
1086	case CONS_TTY:
1087		serial_putchar(c);
1088		return;
1089	case CONS_SCREEN_TEXT:
1090	case CONS_FRAMEBUFFER:
1091		bcons_dev.bd_cursor(B_FALSE);
1092		btem_parse(&boot_tem, c);
1093		bcons_dev.bd_cursor(B_TRUE);
1094		return;
1095	case CONS_SCREEN_GRAPHICS:
1096#if !defined(_BOOT)
1097	case CONS_USBSER:
1098		defcons_putchar(c);
1099#endif /* _BOOT */
1100	default:
1101		return;
1102	}
1103}
1104
1105void
1106bcons_putchar(int c)
1107{
1108#if defined(__xpv)
1109	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
1110	    console == CONS_HYPERVISOR) {
1111		bcons_putchar_xen(c);
1112		return;
1113	}
1114#endif /* __xpv */
1115
1116	if (c == '\n') {
1117		_doputchar(console, '\r');
1118		if (diag != console)
1119			_doputchar(diag, '\r');
1120	}
1121	_doputchar(console, c);
1122	if (diag != console)
1123		_doputchar(diag, c);
1124}
1125
1126/*
1127 * kernel character input functions
1128 */
1129int
1130bcons_getchar(void)
1131{
1132#if defined(__xpv)
1133	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
1134	    console == CONS_HYPERVISOR)
1135		return (bcons_getchar_xen());
1136#endif /* __xpv */
1137
1138	for (;;) {
1139		if (console == CONS_TTY || diag == CONS_TTY) {
1140			if (serial_ischar())
1141				return (serial_getchar());
1142		}
1143		if (console != CONS_INVALID || diag != CONS_INVALID) {
1144			if (kb_ischar())
1145				return (kb_getchar());
1146		}
1147	}
1148}
1149
1150/*
1151 * Nothing below is used by dboot.
1152 */
1153#if !defined(_BOOT)
1154
1155int
1156bcons_ischar(void)
1157{
1158	int c = 0;
1159
1160#if defined(__xpv)
1161	if (!DOMAIN_IS_INITDOMAIN(xen_info) ||
1162	    console == CONS_HYPERVISOR)
1163		return (bcons_ischar_xen());
1164#endif /* __xpv */
1165
1166	switch (console) {
1167	case CONS_TTY:
1168		c = serial_ischar();
1169		break;
1170
1171	case CONS_INVALID:
1172		break;
1173
1174	default:
1175		c = kb_ischar();
1176	}
1177	if (c != 0)
1178		return (c);
1179
1180	switch (diag) {
1181	case CONS_TTY:
1182		c = serial_ischar();
1183		break;
1184
1185	case CONS_INVALID:
1186		break;
1187
1188	default:
1189		c = kb_ischar();
1190	}
1191
1192	return (c);
1193}
1194
1195/*
1196 * 2nd part of console initialization: we've now processed bootenv.rc; update
1197 * console settings as appropriate. This only really processes serial console
1198 * modifications.
1199 */
1200void
1201bcons_post_bootenvrc(char *inputdev, char *outputdev, char *consoledev)
1202{
1203	int cons = CONS_INVALID;
1204	int ttyn;
1205	char *devnames[] = { consoledev, outputdev, inputdev, NULL };
1206	console_value_t *consolep;
1207	int i;
1208	extern int post_fastreboot;
1209
1210	ttyn = 0;
1211	if (post_fastreboot && console == CONS_SCREEN_GRAPHICS)
1212		console = CONS_SCREEN_TEXT;
1213
1214	/*
1215	 * USB serial and GRAPHICS console: we just collect data into a buffer.
1216	 */
1217	if (console == CONS_USBSER || console == CONS_SCREEN_GRAPHICS) {
1218		extern void *defcons_init(size_t);
1219		defcons_buf = defcons_cur = defcons_init(MMU_PAGESIZE);
1220		return;
1221	}
1222
1223	for (i = 0; devnames[i] != NULL; i++) {
1224		cons = lookup_console_device(devnames[i], &ttyn);
1225		if (cons != CONS_INVALID)
1226			break;
1227	}
1228
1229	if (cons == CONS_INVALID) {
1230		/*
1231		 * No console change, but let's see if bootenv.rc had a mode
1232		 * setting we should apply.
1233		 */
1234		if (console == CONS_TTY && !bootprop_set_tty_mode)
1235			serial_init();
1236		return;
1237	}
1238
1239#if defined(__xpv)
1240	/*
1241	 * if the hypervisor is using the currently selected console device then
1242	 * default to using the hypervisor as the console device.
1243	 */
1244	if (cons == console_hypervisor_device) {
1245		cons = CONS_HYPERVISOR;
1246		console_hypervisor_redirect = B_TRUE;
1247	}
1248#endif /* __xpv */
1249
1250	console = cons;
1251
1252	if (console == CONS_TTY) {
1253		tty_num = ttyn;
1254		serial_init();
1255	}
1256}
1257
1258#if defined(__xpv)
1259boolean_t
1260bcons_hypervisor_redirect(void)
1261{
1262	return (console_hypervisor_redirect);
1263}
1264
1265void
1266bcons_device_change(int new_console)
1267{
1268	if (new_console < CONS_MIN || new_console > CONS_MAX)
1269		return;
1270
1271	/*
1272	 * If we are asked to switch the console to the hypervisor, that
1273	 * really means to switch the console to whichever device the
1274	 * hypervisor is/was using.
1275	 */
1276	if (new_console == CONS_HYPERVISOR)
1277		new_console = console_hypervisor_device;
1278
1279	console = new_console;
1280
1281	if (new_console == CONS_TTY) {
1282		tty_num = console_hypervisor_tty_num;
1283		serial_init();
1284	}
1285}
1286#endif /* __xpv */
1287
1288static void
1289defcons_putchar(int c)
1290{
1291	if (defcons_buf != NULL &&
1292	    defcons_cur + 1 - defcons_buf < MMU_PAGESIZE) {
1293		*defcons_cur++ = c;
1294		*defcons_cur = 0;
1295	}
1296}
1297
1298#endif /* _BOOT */
1299