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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/types.h>
29 #include <sys/systm.h>
30 #include <sys/archsystm.h>
31 #include <sys/boot_console.h>
32 #include <sys/ctype.h>
33 
34 #include "boot_serial.h"
35 #include "boot_vga.h"
36 
37 #if defined(_BOOT)
38 #include <dboot/dboot_xboot.h>
39 #else
40 #include <sys/bootconf.h>
41 static char *usbser_buf;
42 static char *usbser_cur;
43 #endif
44 
45 static int cons_color = CONS_COLOR;
46 int console = CONS_SCREEN_TEXT;
47 /* or CONS_TTYA, CONS_TTYB */
48 static int serial_ischar(void);
49 static int serial_getchar(void);
50 static void serial_putchar(int);
51 static void serial_adjust_prop(void);
52 
53 static char *boot_line = NULL;
54 
55 /* Set if the console or mode are expressed in the boot line */
56 static int console_set, console_mode_set;
57 
58 /* Clear the screen and initialize VIDEO, XPOS and YPOS. */
59 static void
60 clear_screen(void)
61 {
62 	/*
63 	 * XXX should set vga mode so we don't depend on the
64 	 * state left by the boot loader
65 	 */
66 	vga_clear(cons_color);
67 	vga_setpos(0, 0);
68 }
69 
70 /* Put the character C on the screen. */
71 static void
72 screen_putchar(int c)
73 {
74 	int row, col;
75 
76 	vga_getpos(&row, &col);
77 	switch (c) {
78 	case '\t':
79 		col += 8 - (col % 8);
80 		if (col == VGA_TEXT_COLS)
81 			col = 79;
82 		vga_setpos(row, col);
83 		break;
84 
85 	case '\r':
86 		vga_setpos(row, 0);
87 		break;
88 
89 	case '\b':
90 		if (col > 0)
91 			vga_setpos(row, col - 1);
92 		break;
93 
94 	case '\n':
95 		if (row < VGA_TEXT_ROWS - 1)
96 			vga_setpos(row + 1, col);
97 		else
98 			vga_scroll(cons_color);
99 		break;
100 
101 	default:
102 		vga_drawc(c, cons_color);
103 		if (col < VGA_TEXT_COLS -1)
104 			vga_setpos(row, col + 1);
105 		else if (row < VGA_TEXT_ROWS - 1)
106 			vga_setpos(row + 1, 0);
107 		else {
108 			vga_setpos(row, 0);
109 			vga_scroll(cons_color);
110 		}
111 		break;
112 	}
113 }
114 
115 /* serial port stuff */
116 static int port;
117 
118 static void
119 serial_init(void)
120 {
121 	switch (console) {
122 	case CONS_TTYA:
123 		port = 0x3f8;
124 		break;
125 	case CONS_TTYB:
126 		port = 0x2f8;
127 		break;
128 	}
129 
130 	outb(port + ISR, 0x20);
131 	if (inb(port + ISR) & 0x20) {
132 		/*
133 		 * 82510 chip is present
134 		 */
135 		outb(port + DAT+7, 0x04);	/* clear status */
136 		outb(port + ISR, 0x40);  /* set to bank 2 */
137 		outb(port + MCR, 0x08);  /* IMD */
138 		outb(port + DAT, 0x21);  /* FMD */
139 		outb(port + ISR, 0x00);  /* set to bank 0 */
140 	} else {
141 		/*
142 		 * set the UART in FIFO mode if it has FIFO buffers.
143 		 * use 16550 fifo reset sequence specified in NS
144 		 * application note. disable fifos until chip is
145 		 * initialized.
146 		 */
147 		outb(port + FIFOR, 0x00);		/* clear */
148 		outb(port + FIFOR, FIFO_ON);		/* enable */
149 		outb(port + FIFOR, FIFO_ON|FIFORXFLSH);  /* reset */
150 		outb(port + FIFOR,
151 		    FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80);
152 		if ((inb(port + ISR) & 0xc0) != 0xc0) {
153 			/*
154 			 * no fifo buffers so disable fifos.
155 			 * this is true for 8250's
156 			 */
157 			outb(port + FIFOR, 0x00);
158 		}
159 	}
160 
161 	/* disable interrupts */
162 	outb(port + ICR, 0);
163 
164 	/* adjust setting based on tty properties */
165 	serial_adjust_prop();
166 
167 #if defined(_BOOT)
168 	/*
169 	 * Do a full reset to match console behavior.
170 	 * 0x1B + c - reset everything
171 	 */
172 	serial_putchar(0x1B);
173 	serial_putchar('c');
174 #endif
175 }
176 
177 /* Advance str pointer past white space */
178 #define	EAT_WHITE_SPACE(str)	{			\
179 	while ((*str != '\0') && ISSPACE(*str))		\
180 		str++;					\
181 }
182 
183 /*
184  * boot_line is set when we call here.  Search it for the argument name,
185  * and if found, return a pointer to it.
186  */
187 static char *
188 find_boot_line_prop(const char *name)
189 {
190 	char *ptr;
191 	char end_char;
192 	size_t len;
193 
194 	if (boot_line == NULL)
195 		return (NULL);
196 
197 	len = strlen(name);
198 
199 	/*
200 	 * We have two nested loops here: the outer loop discards all options
201 	 * except -B, and the inner loop parses the -B options looking for
202 	 * the one we're interested in.
203 	 */
204 	for (ptr = boot_line; *ptr != '\0'; ptr++) {
205 		EAT_WHITE_SPACE(ptr);
206 
207 		if (*ptr == '-') {
208 			ptr++;
209 			while ((*ptr != '\0') && (*ptr != 'B') &&
210 			    !ISSPACE(*ptr))
211 				ptr++;
212 			if (*ptr == '\0')
213 				return (NULL);
214 			else if (*ptr != 'B')
215 				continue;
216 		} else {
217 			while ((*ptr != '\0') && !ISSPACE(*ptr))
218 				ptr++;
219 			if (*ptr == '\0')
220 				return (NULL);
221 			continue;
222 		}
223 
224 		do {
225 			ptr++;
226 			EAT_WHITE_SPACE(ptr);
227 
228 			if ((strncmp(ptr, name, len) == 0) &&
229 			    (ptr[len] == '=')) {
230 				ptr += len + 1;
231 				if ((*ptr == '\'') || (*ptr == '"'))
232 					return (ptr + 1);
233 				else
234 					return (ptr);
235 			}
236 
237 			/*
238 			 * We have a property, and it's not the one we're
239 			 * interested in.  Skip the property name.  A name
240 			 * can end with '=', a comma, or white space.
241 			 */
242 			while ((*ptr != '\0') && (*ptr != '=') &&
243 			    (*ptr != ',') && (!ISSPACE(*ptr)))
244 				ptr++;
245 
246 			/*
247 			 * We only want to go through the rest of the inner
248 			 * loop if we have a comma.  If we have a property
249 			 * name without a value, either continue or break.
250 			 */
251 			if (*ptr == '\0')
252 				return (NULL);
253 			else if (*ptr == ',')
254 				continue;
255 			else if (ISSPACE(*ptr))
256 				break;
257 			ptr++;
258 
259 			/*
260 			 * Is the property quoted?
261 			 */
262 			if ((*ptr == '\'') || (*ptr == '"')) {
263 				end_char = *ptr;
264 			} else {
265 				/*
266 				 * Not quoted, so the string ends at a comma
267 				 * or at white space.  Deal with white space
268 				 * later.
269 				 */
270 				end_char = ',';
271 			}
272 
273 			/*
274 			 * Now, we can ignore any characters until we find
275 			 * end_char.
276 			 */
277 			for (; (*ptr != '\0') && (*ptr != end_char); ptr++) {
278 				if ((end_char == ',') && ISSPACE(*ptr))
279 					break;
280 			}
281 			if (*ptr && (*ptr != ','))
282 				ptr++;
283 		} while (*ptr == ',');
284 	}
285 	return (NULL);
286 }
287 
288 
289 #define	MATCHES(p, pat)	\
290 	(strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0)
291 
292 #define	SKIP(p, c)				\
293 	while (*(p) != 0 && *p != (c))		\
294 		++(p);				\
295 	if (*(p) == (c))			\
296 		++(p);
297 
298 /*
299  * find a tty mode property either from cmdline or from boot properties
300  */
301 static char *
302 get_mode_value(char *name)
303 {
304 	/*
305 	 * when specified on boot line it looks like "name" "="....
306 	 */
307 	if (boot_line != NULL) {
308 		return (find_boot_line_prop(name));
309 	}
310 
311 #if defined(_BOOT)
312 	return (NULL);
313 #else
314 	/*
315 	 * if we're running in the full kernel we check the bootenv.rc settings
316 	 */
317 	{
318 		static char propval[20];
319 
320 		propval[0] = 0;
321 		if (do_bsys_getproplen(NULL, name) <= 0)
322 			return (NULL);
323 		(void) do_bsys_getprop(NULL, name, propval);
324 		return (propval);
325 	}
326 #endif
327 }
328 
329 /*
330  * adjust serial port based on properties
331  * These come either from the cmdline or from boot properties.
332  */
333 static void
334 serial_adjust_prop(void)
335 {
336 	char propname[20];
337 	char *propval;
338 	char *p;
339 	ulong_t baud;
340 	uchar_t lcr = 0;
341 	uchar_t mcr = DTR | RTS;
342 
343 	(void) strcpy(propname, "ttyX-mode");
344 	propname[3] = 'a' + console - CONS_TTYA;
345 	propval = get_mode_value(propname);
346 	if (propval == NULL)
347 		propval = "9600,8,n,1,-";
348 	else
349 		console_mode_set = 1;
350 
351 	/* property is of the form: "9600,8,n,1,-" */
352 	p = propval;
353 	if (MATCHES(p, "110,"))
354 		baud = ASY110;
355 	else if (MATCHES(p, "150,"))
356 		baud = ASY150;
357 	else if (MATCHES(p, "300,"))
358 		baud = ASY300;
359 	else if (MATCHES(p, "600,"))
360 		baud = ASY600;
361 	else if (MATCHES(p, "1200,"))
362 		baud = ASY1200;
363 	else if (MATCHES(p, "2400,"))
364 		baud = ASY2400;
365 	else if (MATCHES(p, "4800,"))
366 		baud = ASY4800;
367 	else if (MATCHES(p, "19200,"))
368 		baud = ASY19200;
369 	else if (MATCHES(p, "38400,"))
370 		baud = ASY38400;
371 	else if (MATCHES(p, "57600,"))
372 		baud = ASY57600;
373 	else if (MATCHES(p, "115200,"))
374 		baud = ASY115200;
375 	else {
376 		baud = ASY9600;
377 		SKIP(p, ',');
378 	}
379 	outb(port + LCR, DLAB);
380 	outb(port + DAT + DLL, baud & 0xff);
381 	outb(port + DAT + DLH, (baud >> 8) & 0xff);
382 
383 	switch (*p) {
384 	case '5':
385 		lcr |= BITS5;
386 		++p;
387 		break;
388 	case '6':
389 		lcr |= BITS6;
390 		++p;
391 		break;
392 	case '7':
393 		lcr |= BITS7;
394 		++p;
395 		break;
396 	case '8':
397 		++p;
398 	default:
399 		lcr |= BITS8;
400 		break;
401 	}
402 
403 	SKIP(p, ',');
404 
405 	switch (*p) {
406 	case 'n':
407 		lcr |= PARITY_NONE;
408 		++p;
409 		break;
410 	case 'o':
411 		lcr |= PARITY_ODD;
412 		++p;
413 		break;
414 	case 'e':
415 		++p;
416 	default:
417 		lcr |= PARITY_EVEN;
418 		break;
419 	}
420 
421 
422 	SKIP(p, ',');
423 
424 	switch (*p) {
425 	case '1':
426 		/* STOP1 is 0 */
427 		++p;
428 		break;
429 	default:
430 		lcr |= STOP2;
431 		break;
432 	}
433 	/* set parity bits */
434 	outb(port + LCR, lcr);
435 
436 	(void) strcpy(propname, "ttyX-rts-dtr-off");
437 	propname[3] = 'a' + console - CONS_TTYA;
438 	propval = get_mode_value(propname);
439 	if (propval == NULL)
440 		propval = "false";
441 	if (propval[0] != 'f' && propval[0] != 'F')
442 		mcr = 0;
443 	/* set modem control bits */
444 	outb(port + MCR, mcr | OUT2);
445 }
446 
447 /*
448  * A structure to map console names to values.
449  */
450 typedef struct {
451 	char *name;
452 	int value;
453 } console_value_t;
454 
455 console_value_t console_devices[] = {
456 	{ "ttya", CONS_TTYA },
457 	{ "ttyb", CONS_TTYB },
458 	{ "text", CONS_SCREEN_TEXT },
459 #if !defined(_BOOT)
460 	{ "usb-serial", CONS_USBSER },
461 #endif
462 	{ "", CONS_INVALID }
463 };
464 
465 void
466 bcons_init(char *bootstr)
467 {
468 	console_value_t *consolep;
469 	size_t len, cons_len;
470 	char *cons_str;
471 
472 	boot_line = bootstr;
473 	console = CONS_INVALID;
474 
475 	cons_str = find_boot_line_prop("console");
476 	if (cons_str == NULL)
477 		cons_str = find_boot_line_prop("output-device");
478 
479 	/*
480 	 * Go through the console_devices array trying to match the string
481 	 * we were given.  The string on the command line must end with
482 	 * a comma or white space.
483 	 */
484 	if (cons_str != NULL) {
485 		cons_len = strlen(cons_str);
486 		consolep = console_devices;
487 		for (; consolep->name[0] != '\0'; consolep++) {
488 			len = strlen(consolep->name);
489 			if ((len <= cons_len) && ((cons_str[len] == '\0') ||
490 			    (cons_str[len] == ',') || (cons_str[len] == '\'') ||
491 			    (cons_str[len] == '"') || ISSPACE(cons_str[len])) &&
492 			    (strncmp(cons_str, consolep->name, len) == 0)) {
493 				console = consolep->value;
494 				break;
495 			}
496 		}
497 	}
498 
499 	/*
500 	 * If no console device specified, default to text.
501 	 * Remember what was specified for second phase.
502 	 */
503 	if (console == CONS_INVALID)
504 		console = CONS_SCREEN_TEXT;
505 	else
506 		console_set = 1;
507 
508 	switch (console) {
509 	case CONS_TTYA:
510 	case CONS_TTYB:
511 		serial_init();
512 		break;
513 
514 #if !defined(_BOOT)
515 	case CONS_USBSER:
516 		/*
517 		 * We can't do anything with the usb serial
518 		 * until we have memory management.
519 		 */
520 		break;
521 #endif
522 	case CONS_SCREEN_TEXT:
523 	default:
524 #if defined(_BOOT)
525 		clear_screen();	/* clears the grub screen */
526 #endif
527 		kb_init();
528 		break;
529 	}
530 	boot_line = NULL;
531 }
532 
533 /*
534  * 2nd part of console initialization.
535  * In the kernel (ie. fakebop), this can be used only to switch to
536  * using a serial port instead of screen based on the contents
537  * of the bootenv.rc file.
538  */
539 /*ARGSUSED*/
540 void
541 bcons_init2(char *inputdev, char *outputdev, char *consoledev)
542 {
543 #if !defined(_BOOT)
544 	int cons = CONS_INVALID;
545 	char *devnames[] = { consoledev, outputdev, inputdev, NULL };
546 	console_value_t *consolep;
547 	int i;
548 
549 	if (console != CONS_USBSER) {
550 		if (console_set) {
551 			/*
552 			 * If the console was set on the command line,
553 			 * but the ttyX-mode was not, we only need to
554 			 * check bootenv.rc for that setting.
555 			 */
556 			if ((!console_mode_set) &&
557 			    (console == CONS_TTYA || console == CONS_TTYB))
558 				serial_init();
559 			return;
560 		}
561 
562 		for (i = 0; devnames[i] != NULL; i++) {
563 			consolep = console_devices;
564 			for (; consolep->name[0] != '\0'; consolep++) {
565 				if (strcmp(devnames[i], consolep->name) == 0) {
566 					cons = consolep->value;
567 				}
568 			}
569 			if (cons != CONS_INVALID)
570 				break;
571 		}
572 
573 		if (cons == CONS_INVALID)
574 			return;
575 		if (cons == console)
576 			return;
577 
578 		console = cons;
579 		if (cons == CONS_TTYA || cons == CONS_TTYB) {
580 			serial_init();
581 			return;
582 		}
583 	}
584 
585 	/*
586 	 * USB serial -- we just collect data into a buffer
587 	 */
588 	if (console == CONS_USBSER) {
589 		extern void *usbser_init(size_t);
590 		usbser_buf = usbser_cur = usbser_init(MMU_PAGESIZE);
591 	}
592 #endif	/* _BOOT */
593 }
594 
595 #if !defined(_BOOT)
596 static void
597 usbser_putchar(int c)
598 {
599 	if (usbser_cur - usbser_buf < MMU_PAGESIZE)
600 		*usbser_cur++ = c;
601 }
602 #endif	/* _BOOT */
603 
604 static void
605 serial_putchar(int c)
606 {
607 	int checks = 10000;
608 
609 	while (((inb(port + LSR) & XHRE) == 0) && checks--)
610 		;
611 	outb(port + DAT, (char)c);
612 }
613 
614 static int
615 serial_getchar(void)
616 {
617 	uchar_t lsr;
618 
619 	while (serial_ischar() == 0)
620 		;
621 
622 	lsr = inb(port + LSR);
623 	if (lsr & (SERIAL_BREAK | SERIAL_FRAME |
624 	    SERIAL_PARITY | SERIAL_OVERRUN)) {
625 		if (lsr & SERIAL_OVERRUN) {
626 			return (inb(port + DAT));
627 		} else {
628 			/* Toss the garbage */
629 			(void) inb(port + DAT);
630 			return (0);
631 		}
632 	}
633 	return (inb(port + DAT));
634 }
635 
636 static int
637 serial_ischar(void)
638 {
639 	return (inb(port + LSR) & RCA);
640 }
641 
642 static void
643 _doputchar(int c)
644 {
645 	switch (console) {
646 	case CONS_TTYA:
647 	case CONS_TTYB:
648 		serial_putchar(c);
649 		return;
650 	case CONS_SCREEN_TEXT:
651 		screen_putchar(c);
652 		return;
653 #if !defined(_BOOT)
654 	case CONS_USBSER:
655 		usbser_putchar(c);
656 		return;
657 #endif /* _BOOT */
658 	}
659 }
660 
661 void
662 bcons_putchar(int c)
663 {
664 	static int bhcharpos = 0;
665 
666 	if (c == '\t') {
667 		do {
668 			_doputchar(' ');
669 		} while (++bhcharpos % 8);
670 		return;
671 	} else  if (c == '\n' || c == '\r') {
672 		bhcharpos = 0;
673 		_doputchar('\r');
674 		_doputchar(c);
675 		return;
676 	} else if (c == '\b') {
677 		if (bhcharpos)
678 			bhcharpos--;
679 		_doputchar(c);
680 		return;
681 	}
682 
683 	bhcharpos++;
684 	_doputchar(c);
685 }
686 
687 /*
688  * kernel character input functions
689  */
690 int
691 bcons_getchar(void)
692 {
693 	switch (console) {
694 	case CONS_TTYA:
695 	case CONS_TTYB:
696 		return (serial_getchar());
697 	default:
698 		return (kb_getchar());
699 	}
700 }
701 
702 #if !defined(_BOOT)
703 
704 int
705 bcons_ischar(void)
706 {
707 	switch (console) {
708 	case CONS_TTYA:
709 	case CONS_TTYB:
710 		return (serial_ischar());
711 	default:
712 		return (kb_ischar());
713 	}
714 }
715 
716 #endif /* _BOOT */
717