xref: /illumos-gate/usr/src/uts/i86pc/boot/boot_console.c (revision 4e93fb0f6383eaac21897dcdae56b87118131e4d)
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 
33 #include "boot_serial.h"
34 #include "boot_vga.h"
35 
36 #if defined(_BOOT)
37 #include "../dboot/dboot_xboot.h"
38 #include <util/string.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 /* Clear the screen and initialize VIDEO, XPOS and YPOS. */
56 static void
57 clear_screen(void)
58 {
59 	/*
60 	 * XXX should set vga mode so we don't depend on the
61 	 * state left by the boot loader
62 	 */
63 	vga_clear(cons_color);
64 	vga_setpos(0, 0);
65 }
66 
67 /* Put the character C on the screen. */
68 static void
69 screen_putchar(int c)
70 {
71 	int row, col;
72 
73 	vga_getpos(&row, &col);
74 	switch (c) {
75 	case '\t':
76 		col += 8 - (col % 8);
77 		if (col == VGA_TEXT_COLS)
78 			col = 79;
79 		vga_setpos(row, col);
80 		break;
81 
82 	case '\r':
83 		vga_setpos(row, 0);
84 		break;
85 
86 	case '\b':
87 		if (col > 0)
88 			vga_setpos(row, col - 1);
89 		break;
90 
91 	case '\n':
92 		if (row < VGA_TEXT_ROWS - 1)
93 			vga_setpos(row + 1, col);
94 		else
95 			vga_scroll(cons_color);
96 		break;
97 
98 	default:
99 		vga_drawc(c, cons_color);
100 		if (col < VGA_TEXT_COLS -1)
101 			vga_setpos(row, col + 1);
102 		else if (row < VGA_TEXT_ROWS - 1)
103 			vga_setpos(row + 1, 0);
104 		else {
105 			vga_setpos(row, 0);
106 			vga_scroll(cons_color);
107 		}
108 		break;
109 	}
110 }
111 
112 /* serial port stuff */
113 static int port;
114 
115 static void
116 serial_init(void)
117 {
118 	switch (console) {
119 	case CONS_TTYA:
120 		port = 0x3f8;
121 		break;
122 	case CONS_TTYB:
123 		port = 0x2f8;
124 		break;
125 	}
126 
127 	outb(port + ISR, 0x20);
128 	if (inb(port + ISR) & 0x20) {
129 		/*
130 		 * 82510 chip is present
131 		 */
132 		outb(port + DAT+7, 0x04);	/* clear status */
133 		outb(port + ISR, 0x40);  /* set to bank 2 */
134 		outb(port + MCR, 0x08);  /* IMD */
135 		outb(port + DAT, 0x21);  /* FMD */
136 		outb(port + ISR, 0x00);  /* set to bank 0 */
137 	} else {
138 		/*
139 		 * set the UART in FIFO mode if it has FIFO buffers.
140 		 * use 16550 fifo reset sequence specified in NS
141 		 * application note. disable fifos until chip is
142 		 * initialized.
143 		 */
144 		outb(port + FIFOR, 0x00);		/* clear */
145 		outb(port + FIFOR, FIFO_ON);		/* enable */
146 		outb(port + FIFOR, FIFO_ON|FIFORXFLSH);  /* reset */
147 		outb(port + FIFOR,
148 		    FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80);
149 		if ((inb(port + ISR) & 0xc0) != 0xc0) {
150 			/*
151 			 * no fifo buffers so disable fifos.
152 			 * this is true for 8250's
153 			 */
154 			outb(port + FIFOR, 0x00);
155 		}
156 	}
157 
158 	/* disable interrupts */
159 	outb(port + ICR, 0);
160 
161 	/* adjust setting based on tty properties */
162 	serial_adjust_prop();
163 
164 #if defined(_BOOT)
165 	/*
166 	 * Do a full reset to match console behavior.
167 	 * 0x1B + c - reset everything
168 	 */
169 	serial_putchar(0x1B);
170 	serial_putchar('c');
171 #endif
172 }
173 
174 
175 #define	MATCHES(p, pat)	\
176 	(strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0)
177 
178 #define	SKIP(p, c)				\
179 	while (*(p) != 0 && *p != (c))		\
180 		++(p);				\
181 	if (*(p) == (c))			\
182 		++(p);
183 
184 /*
185  * find a tty mode property either from cmdline or from boot properties
186  */
187 static char *
188 get_mode_value(char *name)
189 {
190 	char *p;
191 
192 	/*
193 	 * when specified on boot line it looks like "name" "="....
194 	 */
195 	if (boot_line != NULL) {
196 		p = strstr(boot_line, name);
197 		if (p == NULL)
198 			return (NULL);
199 		SKIP(p, '=');
200 		return (p);
201 	}
202 
203 #if defined(_BOOT)
204 	return (NULL);
205 #else
206 	/*
207 	 * if we're running in the full kernel we check the bootenv.rc settings
208 	 */
209 	{
210 		static char propval[20];
211 
212 		propval[0] = 0;
213 		if (bootops == NULL || BOP_GETPROPLEN(bootops, name) == 0)
214 			return (NULL);
215 		(void) BOP_GETPROP(bootops, name, propval);
216 		return (propval);
217 	}
218 #endif
219 }
220 
221 /*
222  * adjust serial port based on properties
223  * These come either from the cmdline or from boot properties.
224  */
225 static void
226 serial_adjust_prop(void)
227 {
228 	char propname[20];
229 	char *propval;
230 	char *p;
231 	ulong_t baud;
232 	uchar_t lcr = 0;
233 	uchar_t mcr = DTR | RTS;
234 
235 	(void) strcpy(propname, "ttyX-mode");
236 	propname[3] = 'a' + console - CONS_TTYA;
237 	propval = get_mode_value(propname);
238 	if (propval == NULL)
239 		propval = "9600,8,n,1,-";
240 
241 	/* property is of the form: "9600,8,n,1,-" */
242 	p = propval;
243 	if (MATCHES(p, "110,"))
244 		baud = ASY110;
245 	else if (MATCHES(p, "150,"))
246 		baud = ASY150;
247 	else if (MATCHES(p, "300,"))
248 		baud = ASY300;
249 	else if (MATCHES(p, "600,"))
250 		baud = ASY600;
251 	else if (MATCHES(p, "1200,"))
252 		baud = ASY1200;
253 	else if (MATCHES(p, "2400,"))
254 		baud = ASY2400;
255 	else if (MATCHES(p, "4800,"))
256 		baud = ASY4800;
257 	else if (MATCHES(p, "19200,"))
258 		baud = ASY19200;
259 	else if (MATCHES(p, "38400,"))
260 		baud = ASY38400;
261 	else if (MATCHES(p, "57600,"))
262 		baud = ASY57600;
263 	else if (MATCHES(p, "115200,"))
264 		baud = ASY115200;
265 	else {
266 		baud = ASY9600;
267 		SKIP(p, ',');
268 	}
269 	outb(port + LCR, DLAB);
270 	outb(port + DAT + DLL, baud & 0xff);
271 	outb(port + DAT + DLH, (baud >> 8) & 0xff);
272 
273 	switch (*p) {
274 	case '5':
275 		lcr |= BITS5;
276 		++p;
277 		break;
278 	case '6':
279 		lcr |= BITS6;
280 		++p;
281 		break;
282 	case '7':
283 		lcr |= BITS7;
284 		++p;
285 		break;
286 	case '8':
287 		++p;
288 	default:
289 		lcr |= BITS8;
290 		break;
291 	}
292 
293 	SKIP(p, ',');
294 
295 	switch (*p) {
296 	case 'n':
297 		lcr |= PARITY_NONE;
298 		++p;
299 		break;
300 	case 'o':
301 		lcr |= PARITY_ODD;
302 		++p;
303 		break;
304 	case 'e':
305 		++p;
306 	default:
307 		lcr |= PARITY_EVEN;
308 		break;
309 	}
310 
311 
312 	SKIP(p, ',');
313 
314 	switch (*p) {
315 	case '1':
316 		/* STOP1 is 0 */
317 		++p;
318 		break;
319 	default:
320 		lcr |= STOP2;
321 		break;
322 	}
323 	/* set parity bits */
324 	outb(port + LCR, lcr);
325 
326 	(void) strcpy(propname, "ttyX-rts-dtr-off");
327 	propname[3] = 'a' + console - CONS_TTYA;
328 	propval = get_mode_value(propname);
329 	if (propval == NULL)
330 		propval = "false";
331 	if (propval[0] != 'f' && propval[0] != 'F')
332 		mcr = 0;
333 	/* set modem control bits */
334 	outb(port + MCR, mcr | OUT2);
335 }
336 
337 void
338 bcons_init(char *bootstr)
339 {
340 	boot_line = bootstr;
341 	console = CONS_INVALID;
342 
343 	if (strstr(bootstr, "console=ttya") != 0)
344 		console = CONS_TTYA;
345 	else if (strstr(bootstr, "console=ttyb") != 0)
346 		console = CONS_TTYB;
347 	else if (strstr(bootstr, "console=text") != 0)
348 		console = CONS_SCREEN_TEXT;
349 
350 	/*
351 	 * If no console device specified, default to text.
352 	 * Remember what was specified for second phase.
353 	 */
354 	if (console == CONS_INVALID)
355 		console = CONS_SCREEN_TEXT;
356 
357 	switch (console) {
358 	case CONS_TTYA:
359 	case CONS_TTYB:
360 		serial_init();
361 		break;
362 
363 	case CONS_SCREEN_TEXT:
364 	default:
365 #if defined(_BOOT)
366 		clear_screen();	/* clears the grub screen */
367 #endif
368 		kb_init();
369 		break;
370 	}
371 	boot_line = NULL;
372 }
373 
374 /*
375  * 2nd part of console initialization.
376  * In the kernel (ie. fakebop), this can be used only to switch to
377  * using a serial port instead of screen based on the contents
378  * of the bootenv.rc file.
379  */
380 /*ARGSUSED*/
381 void
382 bcons_init2(char *inputdev, char *outputdev, char *consoledev)
383 {
384 #if !defined(_BOOT)
385 	int cons = CONS_INVALID;
386 
387 	if (consoledev) {
388 		if (strstr(consoledev, "ttya") != 0)
389 			cons = CONS_TTYA;
390 		else if (strstr(consoledev, "ttyb") != 0)
391 			cons = CONS_TTYB;
392 		else if (strstr(consoledev, "usb-serial") != 0)
393 			cons = CONS_USBSER;
394 	}
395 
396 	if (cons == CONS_INVALID && inputdev) {
397 		if (strstr(inputdev, "ttya") != 0)
398 			cons = CONS_TTYA;
399 		else if (strstr(inputdev, "ttyb") != 0)
400 			cons = CONS_TTYB;
401 		else if (strstr(inputdev, "usb-serial") != 0)
402 			cons = CONS_USBSER;
403 	}
404 
405 	if (cons == CONS_INVALID && outputdev) {
406 		if (strstr(outputdev, "ttya") != 0)
407 			cons = CONS_TTYA;
408 		else if (strstr(outputdev, "ttyb") != 0)
409 			cons = CONS_TTYB;
410 		else if (strstr(outputdev, "usb-serial") != 0)
411 			cons = CONS_USBSER;
412 	}
413 
414 	if (cons == CONS_INVALID)
415 		return;
416 	if (cons == console)
417 		return;
418 
419 	console = cons;
420 	if (cons == CONS_TTYA || cons == CONS_TTYB) {
421 		serial_init();
422 		return;
423 	}
424 
425 	/*
426 	 * USB serial -- we just collect data into a buffer
427 	 */
428 	if (cons == CONS_USBSER) {
429 		extern void *usbser_init(size_t);
430 		usbser_buf = usbser_cur = usbser_init(MMU_PAGESIZE);
431 	}
432 #endif	/* _BOOT */
433 }
434 
435 #if !defined(_BOOT)
436 static void
437 usbser_putchar(int c)
438 {
439 	if (usbser_cur - usbser_buf < MMU_PAGESIZE)
440 		*usbser_cur++ = c;
441 }
442 #endif	/* _BOOT */
443 
444 static void
445 serial_putchar(int c)
446 {
447 	int checks = 10000;
448 
449 	while (((inb(port + LSR) & XHRE) == 0) && checks--)
450 		;
451 	outb(port + DAT, (char)c);
452 }
453 
454 static int
455 serial_getchar(void)
456 {
457 	uchar_t lsr;
458 
459 	while (serial_ischar() == 0)
460 		;
461 
462 	lsr = inb(port + LSR);
463 	if (lsr & (SERIAL_BREAK | SERIAL_FRAME |
464 	    SERIAL_PARITY | SERIAL_OVERRUN)) {
465 		if (lsr & SERIAL_OVERRUN) {
466 			return (inb(port + DAT));
467 		} else {
468 			/* Toss the garbage */
469 			(void) inb(port + DAT);
470 			return (0);
471 		}
472 	}
473 	return (inb(port + DAT));
474 }
475 
476 static int
477 serial_ischar(void)
478 {
479 	return (inb(port + LSR) & RCA);
480 }
481 
482 static void
483 _doputchar(int c)
484 {
485 	switch (console) {
486 	case CONS_TTYA:
487 	case CONS_TTYB:
488 		serial_putchar(c);
489 		return;
490 	case CONS_SCREEN_TEXT:
491 		screen_putchar(c);
492 		return;
493 #if !defined(_BOOT)
494 	case CONS_USBSER:
495 		usbser_putchar(c);
496 		return;
497 #endif /* _BOOT */
498 	}
499 }
500 
501 void
502 bcons_putchar(int c)
503 {
504 	static int bhcharpos = 0;
505 
506 	if (c == '\t') {
507 		do {
508 			_doputchar(' ');
509 		} while (++bhcharpos % 8);
510 		return;
511 	} else  if (c == '\n' || c == '\r') {
512 		bhcharpos = 0;
513 		_doputchar('\r');
514 		_doputchar(c);
515 		return;
516 	} else if (c == '\b') {
517 		if (bhcharpos)
518 			bhcharpos--;
519 		_doputchar(c);
520 		return;
521 	}
522 
523 	bhcharpos++;
524 	_doputchar(c);
525 }
526 
527 /*
528  * kernel character input functions
529  */
530 int
531 bcons_getchar(void)
532 {
533 	switch (console) {
534 	case CONS_TTYA:
535 	case CONS_TTYB:
536 		return (serial_getchar());
537 	default:
538 		return (kb_getchar());
539 	}
540 }
541 
542 #if !defined(_BOOT)
543 
544 int
545 bcons_ischar(void)
546 {
547 	switch (console) {
548 	case CONS_TTYA:
549 	case CONS_TTYB:
550 		return (serial_ischar());
551 	default:
552 		return (kb_ischar());
553 	}
554 }
555 
556 #endif /* _BOOT */
557