xref: /illumos-gate/usr/src/boot/i386/libi386/comconsole.c (revision 957246c9e6c47389c40079995d73eebcc659fb29)
1 /*
2  * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 /*
27  * This code is shared on BIOS and UEFI systems on x86 because
28  * we can access io ports on both platforms and the UEFI Serial IO protocol
29  * is not giving us reliable port order and we see issues with input.
30  */
31 #include <sys/cdefs.h>
32 
33 #include <stand.h>
34 #include <bootstrap.h>
35 #include <stdbool.h>
36 #include <machine/cpufunc.h>
37 #include <dev/ic/ns16550.h>
38 #include <dev/pci/pcireg.h>
39 #include "libi386.h"
40 
41 #define	COMC_TXWAIT	0x40000		/* transmit timeout */
42 #define	COMC_BPS(x)	(115200 / (x))	/* speed to DLAB divisor */
43 #define	COMC_DIV2BPS(x)	(115200 / (x))	/* DLAB divisor to speed */
44 
45 #ifndef	COMSPEED
46 #define	COMSPEED	9600
47 #endif
48 
49 #define	COM1_IOADDR	0x3f8
50 #define	COM2_IOADDR	0x2f8
51 #define	COM3_IOADDR	0x3e8
52 #define	COM4_IOADDR	0x2e8
53 
54 #define	STOP1		0x00
55 #define	STOP2		0x04
56 
57 #define	PARODD		0x00
58 #define	PAREN		0x08
59 #define	PAREVN		0x10
60 #define	PARMARK		0x20
61 
62 #define	BITS5		0x00	/* 5 bits per char */
63 #define	BITS6		0x01	/* 6 bits per char */
64 #define	BITS7		0x02	/* 7 bits per char */
65 #define	BITS8		0x03	/* 8 bits per char */
66 
67 struct serial {
68     int		speed;		/* baud rate */
69     uint8_t	lcr;		/* line control */
70     uint8_t	ignore_cd;	/* boolean */
71     uint8_t	rtsdtr_off;	/* boolean */
72     int		ioaddr;
73     uint32_t	locator;
74 };
75 
76 static void	comc_probe(struct console *);
77 static int	comc_init(struct console *, int);
78 static void	comc_putchar(struct console *, int);
79 static int	comc_getchar(struct console *);
80 static int	comc_getspeed(struct serial *);
81 static int	comc_ischar(struct console *);
82 static int	comc_ioctl(struct console *, int, void *);
83 static uint32_t comc_parse_pcidev(const char *);
84 static int	comc_pcidev_set(struct env_var *, int, const void *);
85 static int	comc_pcidev_handle(struct console *, uint32_t);
86 static bool	comc_setup(struct console *);
87 static char	*comc_asprint_mode(struct serial *);
88 static int	comc_parse_mode(struct serial *, const char *);
89 static int	comc_mode_set(struct env_var *, int, const void *);
90 static int	comc_cd_set(struct env_var *, int, const void *);
91 static int	comc_rtsdtr_set(struct env_var *, int, const void *);
92 static void	comc_devinfo(struct console *);
93 
94 struct console ttya = {
95 	.c_name = "ttya",
96 	.c_desc = "serial port a",
97 	.c_flags = 0,
98 	.c_probe = comc_probe,
99 	.c_init = comc_init,
100 	.c_out = comc_putchar,
101 	.c_in = comc_getchar,
102 	.c_ready = comc_ischar,
103 	.c_ioctl = comc_ioctl,
104 	.c_devinfo = comc_devinfo,
105 	.c_private = NULL
106 };
107 
108 struct console ttyb = {
109 	.c_name = "ttyb",
110 	.c_desc = "serial port b",
111 	.c_flags = 0,
112 	.c_probe = comc_probe,
113 	.c_init = comc_init,
114 	.c_out = comc_putchar,
115 	.c_in = comc_getchar,
116 	.c_ready = comc_ischar,
117 	.c_ioctl = comc_ioctl,
118 	.c_devinfo = comc_devinfo,
119 	.c_private = NULL
120 };
121 
122 struct console ttyc = {
123 	.c_name = "ttyc",
124 	.c_desc = "serial port c",
125 	.c_flags = 0,
126 	.c_probe = comc_probe,
127 	.c_init = comc_init,
128 	.c_out = comc_putchar,
129 	.c_in = comc_getchar,
130 	.c_ready = comc_ischar,
131 	.c_ioctl = comc_ioctl,
132 	.c_devinfo = comc_devinfo,
133 	.c_private = NULL
134 };
135 
136 struct console ttyd = {
137 	.c_name = "ttyd",
138 	.c_desc = "serial port d",
139 	.c_flags = 0,
140 	.c_probe = comc_probe,
141 	.c_init = comc_init,
142 	.c_out = comc_putchar,
143 	.c_in = comc_getchar,
144 	.c_ready = comc_ischar,
145 	.c_ioctl = comc_ioctl,
146 	.c_devinfo = comc_devinfo,
147 	.c_private = NULL
148 };
149 
150 static void
151 comc_devinfo(struct console *cp)
152 {
153 	struct serial *port = cp->c_private;
154 
155 	if (cp->c_flags != 0)
156 		printf("\tport %#x", port->ioaddr);
157 	else
158 		printf("\tdevice is not present");
159 }
160 
161 static void
162 comc_probe(struct console *cp)
163 {
164 	struct serial *port;
165 	char name[20];
166 	char value[20];
167 	char *cons, *env;
168 
169 	if (cp->c_private != NULL)
170 		return;
171 
172 	cp->c_private = malloc(sizeof (struct serial));
173 	port = cp->c_private;
174 	port->speed = COMSPEED;
175 
176 	if (strcmp(cp->c_name, "ttya") == 0)
177 		port->ioaddr = COM1_IOADDR;
178 	else if (strcmp(cp->c_name, "ttyb") == 0)
179 		port->ioaddr = COM2_IOADDR;
180 	else if (strcmp(cp->c_name, "ttyc") == 0)
181 		port->ioaddr = COM3_IOADDR;
182 	else if (strcmp(cp->c_name, "ttyd") == 0)
183 		port->ioaddr = COM4_IOADDR;
184 
185 	port->lcr = BITS8;	/* 8,n,1 */
186 	port->ignore_cd = 1;	/* ignore cd */
187 	port->rtsdtr_off = 0;	/* rts-dtr is on */
188 
189 	/*
190 	 * Assume that the speed was set by an earlier boot loader if
191 	 * comconsole is already the preferred console.
192 	 */
193 	cons = getenv("console");
194 	if ((cons != NULL && strcmp(cons, cp->c_name) == 0) ||
195 	    getenv("boot_multicons") != NULL) {
196 		port->speed = comc_getspeed(port);
197 	}
198 
199 	snprintf(name, sizeof (name), "%s-mode", cp->c_name);
200 	env = getenv(name);
201 
202 	if (env != NULL) {
203 		(void) comc_parse_mode(port, env);
204 	}
205 	env = comc_asprint_mode(port);
206 
207 	if (env != NULL) {
208 		unsetenv(name);
209 		env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
210 		free(env);
211 	}
212 
213 	snprintf(name, sizeof (name), "%s-ignore-cd", cp->c_name);
214 	env = getenv(name);
215 	if (env != NULL) {
216 		if (strcmp(env, "true") == 0)
217 			port->ignore_cd = 1;
218 		else if (strcmp(env, "false") == 0)
219 			port->ignore_cd = 0;
220 	}
221 
222 	snprintf(value, sizeof (value), "%s",
223 	    port->ignore_cd? "true" : "false");
224 	unsetenv(name);
225 	env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
226 
227 	snprintf(name, sizeof (name), "%s-rts-dtr-off", cp->c_name);
228 	env = getenv(name);
229 	if (env != NULL) {
230 		if (strcmp(env, "true") == 0)
231 			port->rtsdtr_off = 1;
232 		else if (strcmp(env, "false") == 0)
233 			port->rtsdtr_off = 0;
234 	}
235 
236 	snprintf(value, sizeof (value), "%s",
237 	    port->rtsdtr_off? "true" : "false");
238 	unsetenv(name);
239 	env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
240 
241 	snprintf(name, sizeof (name), "%s-pcidev", cp->c_name);
242 	env = getenv(name);
243 	if (env != NULL) {
244 		port->locator = comc_parse_pcidev(env);
245 		if (port->locator != 0)
246 			comc_pcidev_handle(cp, port->locator);
247 	}
248 
249 	unsetenv(name);
250 	env_setenv(name, EV_VOLATILE, env, comc_pcidev_set, env_nounset);
251 
252 	cp->c_flags = 0;
253 	if (comc_setup(cp))
254 		cp->c_flags = C_PRESENTIN | C_PRESENTOUT;
255 }
256 
257 static int
258 comc_init(struct console *cp, int arg __attribute((unused)))
259 {
260 
261 	if (comc_setup(cp))
262 		return (CMD_OK);
263 
264 	cp->c_flags = 0;
265 	return (CMD_ERROR);
266 }
267 
268 static void
269 comc_putchar(struct console *cp, int c)
270 {
271 	int wait;
272 	struct serial *sp = cp->c_private;
273 
274 	for (wait = COMC_TXWAIT; wait > 0; wait--)
275 		if (inb(sp->ioaddr + com_lsr) & LSR_TXRDY) {
276 			outb(sp->ioaddr + com_data, (uchar_t)c);
277 			break;
278 		}
279 }
280 
281 static int
282 comc_getchar(struct console *cp)
283 {
284 	struct serial *sp = cp->c_private;
285 	return (comc_ischar(cp) ? inb(sp->ioaddr + com_data) : -1);
286 }
287 
288 static int
289 comc_ischar(struct console *cp)
290 {
291 	struct serial *sp = cp->c_private;
292 	return (inb(sp->ioaddr + com_lsr) & LSR_RXRDY);
293 }
294 
295 static int
296 comc_ioctl(struct console *cp __unused, int cmd __unused, void *data __unused)
297 {
298 	return (ENOTTY);
299 }
300 
301 static char *
302 comc_asprint_mode(struct serial *sp)
303 {
304 	char par, *buf;
305 
306 	if (sp == NULL)
307 		return (NULL);
308 
309 	if ((sp->lcr & (PAREN|PAREVN)) == (PAREN|PAREVN))
310 		par = 'e';
311 	else if ((sp->lcr & PAREN) == PAREN)
312 		par = 'o';
313 	else
314 		par = 'n';
315 
316 	asprintf(&buf, "%d,%d,%c,%d,-", sp->speed,
317 	    (sp->lcr & BITS8) == BITS8? 8:7,
318 	    par, (sp->lcr & STOP2) == STOP2? 2:1);
319 	return (buf);
320 }
321 
322 static int
323 comc_parse_mode(struct serial *sp, const char *value)
324 {
325 	unsigned long n;
326 	int speed;
327 	int lcr;
328 	char *ep;
329 
330 	if (value == NULL || *value == '\0')
331 		return (CMD_ERROR);
332 
333 	errno = 0;
334 	n = strtoul(value, &ep, 10);
335 	if (errno != 0 || *ep != ',')
336 		return (CMD_ERROR);
337 	speed = n;
338 
339 	ep++;
340 	errno = 0;
341 	n = strtoul(ep, &ep, 10);
342 	if (errno != 0 || *ep != ',')
343 		return (CMD_ERROR);
344 
345 	switch (n) {
346 	case 7: lcr = BITS7;
347 		break;
348 	case 8: lcr = BITS8;
349 		break;
350 	default:
351 		return (CMD_ERROR);
352 	}
353 
354 	ep++;
355 	switch (*ep++) {
356 	case 'n':
357 		break;
358 	case 'e': lcr |= PAREN|PAREVN;
359 		break;
360 	case 'o': lcr |= PAREN|PARODD;
361 		break;
362 	default:
363 		return (CMD_ERROR);
364 	}
365 
366 	if (*ep == ',')
367 		ep++;
368 	else
369 		return (CMD_ERROR);
370 
371 	switch (*ep++) {
372 	case '1':
373 		break;
374 	case '2': lcr |= STOP2;
375 		break;
376 	default:
377 		return (CMD_ERROR);
378 	}
379 
380 	/* handshake is ignored, but we check syntax anyhow */
381 	if (*ep == ',')
382 		ep++;
383 	else
384 		return (CMD_ERROR);
385 
386 	switch (*ep++) {
387 	case '-':
388 	case 'h':
389 	case 's':
390 		break;
391 	default:
392 		return (CMD_ERROR);
393 	}
394 
395 	if (*ep != '\0')
396 		return (CMD_ERROR);
397 
398 	sp->speed = speed;
399 	sp->lcr = lcr;
400 	return (CMD_OK);
401 }
402 
403 static struct console *
404 get_console(char *name)
405 {
406 	struct console *cp = NULL;
407 
408 	switch (name[3]) {
409 	case 'a': cp = &ttya;
410 		break;
411 	case 'b': cp = &ttyb;
412 		break;
413 	case 'c': cp = &ttyc;
414 		break;
415 	case 'd': cp = &ttyd;
416 		break;
417 	}
418 	return (cp);
419 }
420 
421 static int
422 comc_mode_set(struct env_var *ev, int flags, const void *value)
423 {
424 	struct console *cp;
425 
426 	if (value == NULL)
427 		return (CMD_ERROR);
428 
429 	if ((cp = get_console(ev->ev_name)) == NULL)
430 		return (CMD_ERROR);
431 
432 	if (comc_parse_mode(cp->c_private, value) == CMD_ERROR)
433 		return (CMD_ERROR);
434 
435 	(void) comc_setup(cp);
436 
437 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
438 
439 	return (CMD_OK);
440 }
441 
442 static int
443 comc_cd_set(struct env_var *ev, int flags, const void *value)
444 {
445 	struct console *cp;
446 	struct serial *sp;
447 
448 	if (value == NULL)
449 		return (CMD_ERROR);
450 
451 	if ((cp = get_console(ev->ev_name)) == NULL)
452 		return (CMD_ERROR);
453 
454 	sp = cp->c_private;
455 	if (strcmp(value, "true") == 0)
456 		sp->ignore_cd = 1;
457 	else if (strcmp(value, "false") == 0)
458 		sp->ignore_cd = 0;
459 	else
460 		return (CMD_ERROR);
461 
462 	(void) comc_setup(cp);
463 
464 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
465 
466 	return (CMD_OK);
467 }
468 
469 static int
470 comc_rtsdtr_set(struct env_var *ev, int flags, const void *value)
471 {
472 	struct console *cp;
473 	struct serial *sp;
474 
475 	if (value == NULL)
476 		return (CMD_ERROR);
477 
478 	if ((cp = get_console(ev->ev_name)) == NULL)
479 		return (CMD_ERROR);
480 
481 	sp = cp->c_private;
482 	if (strcmp(value, "true") == 0)
483 		sp->rtsdtr_off = 1;
484 	else if (strcmp(value, "false") == 0)
485 		sp->rtsdtr_off = 0;
486 	else
487 		return (CMD_ERROR);
488 
489 	(void) comc_setup(cp);
490 
491 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
492 
493 	return (CMD_OK);
494 }
495 
496 /*
497  * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10.
498  * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0]
499  */
500 static uint32_t
501 comc_parse_pcidev(const char *string)
502 {
503 #ifdef EFI
504 	(void) string;
505 	return (0);
506 #else
507 	char *p, *p1;
508 	uint8_t bus, dev, func, bar;
509 	uint32_t locator;
510 	int pres;
511 
512 	errno = 0;
513 	pres = strtoul(string, &p, 10);
514 	if (errno != 0 || p == string || *p != ':' || pres < 0)
515 		return (0);
516 	bus = pres;
517 	p1 = ++p;
518 
519 	pres = strtoul(p1, &p, 10);
520 	if (errno != 0 || p == string || *p != ':' || pres < 0)
521 		return (0);
522 	dev = pres;
523 	p1 = ++p;
524 
525 	pres = strtoul(p1, &p, 10);
526 	if (errno != 0 || p == string || (*p != ':' && *p != '\0') || pres < 0)
527 		return (0);
528 	func = pres;
529 
530 	if (*p == ':') {
531 		p1 = ++p;
532 		pres = strtoul(p1, &p, 10);
533 		if (errno != 0 || p == string || *p != '\0' || pres <= 0)
534 			return (0);
535 		bar = pres;
536 	} else
537 		bar = 0x10;
538 
539 	locator = (bar << 16) | biospci_locator(bus, dev, func);
540 	return (locator);
541 #endif
542 }
543 
544 static int
545 comc_pcidev_handle(struct console *cp, uint32_t locator)
546 {
547 #ifdef EFI
548 	(void) cp;
549 	(void) locator;
550 	return (CMD_ERROR);
551 #else
552 	struct serial *sp = cp->c_private;
553 	uint32_t port;
554 
555 	if (biospci_read_config(locator & 0xffff,
556 	    (locator & 0xff0000) >> 16, 2, &port) == -1) {
557 		printf("Cannot read bar at 0x%x\n", locator);
558 		return (CMD_ERROR);
559 	}
560 	if (!PCI_BAR_IO(port)) {
561 		printf("Memory bar at 0x%x\n", locator);
562 		return (CMD_ERROR);
563 	}
564 	port &= PCIM_BAR_IO_BASE;
565 
566 	(void) comc_setup(cp);
567 
568 	sp->locator = locator;
569 
570 	return (CMD_OK);
571 #endif
572 }
573 
574 static int
575 comc_pcidev_set(struct env_var *ev, int flags, const void *value)
576 {
577 	struct console *cp;
578 	struct serial *sp;
579 	uint32_t locator;
580 	int error;
581 
582 	if ((cp = get_console(ev->ev_name)) == NULL)
583 		return (CMD_ERROR);
584 	sp = cp->c_private;
585 
586 	if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) {
587 		printf("Invalid pcidev\n");
588 		return (CMD_ERROR);
589 	}
590 	if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 &&
591 	    sp->locator != locator) {
592 		error = comc_pcidev_handle(cp, locator);
593 		if (error != CMD_OK)
594 			return (error);
595 	}
596 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
597 	return (CMD_OK);
598 }
599 
600 /*
601  * In case of error, we also reset ACTIVE flags, so the console
602  * framefork will try alternate consoles.
603  */
604 static bool
605 comc_setup(struct console *cp)
606 {
607 	struct serial *sp = cp->c_private;
608 	static int TRY_COUNT = 1000000;
609 	int tries;
610 
611 #define	COMC_TEST	0xbb
612 	/*
613 	 * Write byte to scratch register and read it out.
614 	 */
615 	outb(sp->ioaddr + com_scr, COMC_TEST);
616 	if (inb(sp->ioaddr + com_scr) != COMC_TEST)
617 		return (false);
618 
619 	outb(sp->ioaddr + com_cfcr, CFCR_DLAB | sp->lcr);
620 	outb(sp->ioaddr + com_dlbl, COMC_BPS(sp->speed) & 0xff);
621 	outb(sp->ioaddr + com_dlbh, COMC_BPS(sp->speed) >> 8);
622 	outb(sp->ioaddr + com_cfcr, sp->lcr);
623 	outb(sp->ioaddr + com_mcr,
624 	    sp->rtsdtr_off? ~(MCR_RTS | MCR_DTR) : MCR_RTS | MCR_DTR);
625 
626 	tries = 0;
627 	do {
628 		inb(sp->ioaddr + com_data);
629 	} while (inb(sp->ioaddr + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
630 
631 	if (tries == TRY_COUNT)
632 		return (false);
633 	/* Mark this port usable. */
634 	cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
635 	return (true);
636 }
637 
638 static int
639 comc_getspeed(struct serial *sp)
640 {
641 	uint_t	divisor;
642 	uchar_t	dlbh;
643 	uchar_t	dlbl;
644 	uchar_t	cfcr;
645 
646 	cfcr = inb(sp->ioaddr + com_cfcr);
647 	outb(sp->ioaddr + com_cfcr, CFCR_DLAB | cfcr);
648 
649 	dlbl = inb(sp->ioaddr + com_dlbl);
650 	dlbh = inb(sp->ioaddr + com_dlbh);
651 
652 	outb(sp->ioaddr + com_cfcr, cfcr);
653 
654 	divisor = dlbh << 8 | dlbl;
655 
656 	/* XXX there should be more sanity checking. */
657 	if (divisor == 0)
658 		return (COMSPEED);
659 	return (COMC_DIV2BPS(divisor));
660 }
661