xref: /illumos-gate/usr/src/boot/i386/libi386/comconsole.c (revision 422d8c47bfffd3d4b0a572b4e8093809f22968d4)
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	COM_NPORTS	4
50 #define	COM1_IOADDR	0x3f8
51 #define	COM2_IOADDR	0x2f8
52 #define	COM3_IOADDR	0x3e8
53 #define	COM4_IOADDR	0x2e8
54 
55 #define	STOP1		0x00
56 #define	STOP2		0x04
57 
58 #define	PARODD		0x00
59 #define	PAREN		0x08
60 #define	PAREVN		0x10
61 #define	PARMARK		0x20
62 
63 #define	BITS5		0x00	/* 5 bits per char */
64 #define	BITS6		0x01	/* 6 bits per char */
65 #define	BITS7		0x02	/* 7 bits per char */
66 #define	BITS8		0x03	/* 8 bits per char */
67 
68 struct serial {
69     int		speed;		/* baud rate */
70     uint8_t	lcr;		/* line control */
71     uint8_t	ignore_cd;	/* boolean */
72     uint8_t	rtsdtr_off;	/* boolean */
73     int		ioaddr;
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 int		comc_getspeed(int);
81 static int	comc_ischar(struct console *);
82 static int	comc_ioctl(struct console *, int, void *);
83 static bool	comc_setup(struct console *);
84 static char	*comc_asprint_mode(struct serial *);
85 static int	comc_parse_mode(struct serial *, const char *);
86 static int	comc_mode_set(struct env_var *, int, const void *);
87 static int	comc_cd_set(struct env_var *, int, const void *);
88 static int	comc_rtsdtr_set(struct env_var *, int, const void *);
89 static void	comc_devinfo(struct console *);
90 
91 static void
comc_devinfo(struct console * cp)92 comc_devinfo(struct console *cp)
93 {
94 	struct serial *port = cp->c_private;
95 
96 	printf("\tport %#x", port->ioaddr);
97 }
98 
99 static bool
comc_port_is_present(int ioaddr)100 comc_port_is_present(int ioaddr)
101 {
102 	/*
103 	 * Write byte to scratch register and read it out.
104 	 */
105 #define	COMC_TEST	0xbb
106 	outb(ioaddr + com_scr, COMC_TEST);
107 	return (inb(ioaddr + com_scr) == COMC_TEST);
108 }
109 
110 /*
111  * Set up following environment variables:
112  * ttyX-mode
113  * ttyX-rts-dtr-off
114  * ttyX-ignore-cd
115  */
116 static void
comc_setup_env(struct console * tty)117 comc_setup_env(struct console *tty)
118 {
119 	struct serial *port;
120 	char name[20];
121 	char value[20];
122 	char *env;
123 
124 	port = tty->c_private;
125 	snprintf(name, sizeof (name), "%s-mode", tty->c_name);
126 	env = comc_asprint_mode(port);
127 	if (env != NULL) {
128 		unsetenv(name);
129 		env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
130 		free(env);
131 	}
132 
133 	snprintf(name, sizeof (name), "%s-rts-dtr-off", tty->c_name);
134 	snprintf(value, sizeof (value), "%s",
135 	    port->rtsdtr_off? "true" : "false");
136 	unsetenv(name);
137 	env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
138 
139 	snprintf(name, sizeof (name), "%s-ignore-cd", tty->c_name);
140 	snprintf(value, sizeof (value), "%s",
141 	    port->ignore_cd? "true" : "false");
142 	unsetenv(name);
143 	env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
144 }
145 
146 /*
147  * Set up list of possible serial consoles.
148  * This function is run very early, so we do not expect to
149  * run out of memory, and on error, we can not print output.
150  */
151 void
comc_ini(void)152 comc_ini(void)
153 {
154 	uint_t n = 0, c;
155 	bool ports[COM_NPORTS];
156 	struct console **tmp;
157 	struct console *tty;
158 	struct serial *port;
159 
160 	/*
161 	 * Test the presence of 4 serial devices com1-com4
162 	 */
163 	ports[0] = comc_port_is_present(COM1_IOADDR);
164 	ports[1] = comc_port_is_present(COM2_IOADDR);
165 	ports[2] = comc_port_is_present(COM3_IOADDR);
166 	ports[3] = comc_port_is_present(COM4_IOADDR);
167 
168 	for (uint_t i = 0; i < COM_NPORTS; i++)
169 		if (ports[i])
170 			n++;
171 
172 	if (n == 0)	/* there are no serial ports */
173 		return;
174 
175 	c = cons_array_size();
176 	if (c == 0)
177 		n++;	/* For NULL pointer */
178 
179 	tmp = realloc(consoles, (c + n) * sizeof (*consoles));
180 	if (tmp == NULL)
181 		return;
182 	consoles = tmp;
183 	if (c > 0)
184 		c--;
185 
186 	for (uint_t i = 0; i < COM_NPORTS; i++) {
187 		if (!ports[i])
188 			continue;
189 		tty = malloc(sizeof (*tty));
190 		if (tty == NULL) {
191 			/* Out of memory?! can not continue */
192 			consoles[c] = tty;
193 			return;
194 		}
195 		if (asprintf(&tty->c_name, "tty%c", 'a' + i) < 0) {
196 			free(tty);
197 			consoles[c] = NULL;
198 			return;
199 		}
200 		if (asprintf(&tty->c_desc, "serial port %c", 'a' + i) < 0) {
201 			free(tty->c_name);
202 			free(tty);
203 			consoles[c] = NULL;
204 			return;
205 		}
206 		tty->c_flags = 0;
207 		tty->c_probe = comc_probe;
208 		tty->c_init = comc_init;
209 		tty->c_out = comc_putchar;
210 		tty->c_in = comc_getchar;
211 		tty->c_ready = comc_ischar;
212 		tty->c_ioctl = comc_ioctl;
213 		tty->c_devinfo = comc_devinfo;
214 		port = malloc(sizeof (*port));
215 		if (port == NULL) {
216 			free(tty->c_name);
217 			free(tty->c_desc);
218 			free(tty);
219 			consoles[c] = NULL;
220 			return;
221 		}
222 		port->speed = 0;	/* Leave this for comc_probe */
223 		switch (i) {
224 		case 0:
225 			port->ioaddr = COM1_IOADDR;
226 			break;
227 		case 1:
228 			port->ioaddr = COM2_IOADDR;
229 			break;
230 		case 2:
231 			port->ioaddr = COM3_IOADDR;
232 			break;
233 		case 3:
234 			port->ioaddr = COM4_IOADDR;
235 			break;
236 		}
237 		port->speed = comc_getspeed(port->ioaddr);
238 		port->lcr = BITS8;	/* 8,n,1 */
239 		port->ignore_cd = 1;	/* ignore cd */
240 		port->rtsdtr_off = 0;	/* rts-dtr is on */
241 
242 		tty->c_private = port;
243 		consoles[c++] = tty;
244 		comc_setup_env(tty);
245 
246 		/* Reset terminal to initial normal settings with ESC [ 0 m */
247 		comc_putchar(tty, 0x1b);
248 		comc_putchar(tty, '[');
249 		comc_putchar(tty, '0');
250 		comc_putchar(tty, 'm');
251 		/* drain input from random data */
252 		while (comc_getchar(tty) != -1)
253 			;
254 	}
255 	consoles[c] = NULL;
256 }
257 
258 static void
comc_probe(struct console * cp)259 comc_probe(struct console *cp)
260 {
261 	cp->c_flags = 0;
262 	if (comc_setup(cp))
263 		cp->c_flags = C_PRESENTIN | C_PRESENTOUT;
264 }
265 
266 static int
comc_init(struct console * cp,int arg __attribute ((unused)))267 comc_init(struct console *cp, int arg __attribute((unused)))
268 {
269 
270 	if (comc_setup(cp))
271 		return (CMD_OK);
272 
273 	cp->c_flags = 0;
274 	return (CMD_ERROR);
275 }
276 
277 static void
comc_putchar(struct console * cp,int c)278 comc_putchar(struct console *cp, int c)
279 {
280 	int wait;
281 	struct serial *sp = cp->c_private;
282 
283 	for (wait = COMC_TXWAIT; wait > 0; wait--)
284 		if (inb(sp->ioaddr + com_lsr) & LSR_TXRDY) {
285 			outb(sp->ioaddr + com_data, (uchar_t)c);
286 			break;
287 		}
288 }
289 
290 static int
comc_getchar(struct console * cp)291 comc_getchar(struct console *cp)
292 {
293 	struct serial *sp = cp->c_private;
294 	return (comc_ischar(cp) ? inb(sp->ioaddr + com_data) : -1);
295 }
296 
297 static int
comc_ischar(struct console * cp)298 comc_ischar(struct console *cp)
299 {
300 	struct serial *sp = cp->c_private;
301 	return (inb(sp->ioaddr + com_lsr) & LSR_RXRDY);
302 }
303 
304 static int
comc_ioctl(struct console * cp __unused,int cmd __unused,void * data __unused)305 comc_ioctl(struct console *cp __unused, int cmd __unused, void *data __unused)
306 {
307 	return (ENOTTY);
308 }
309 
310 static char *
comc_asprint_mode(struct serial * sp)311 comc_asprint_mode(struct serial *sp)
312 {
313 	char par, *buf;
314 
315 	if (sp == NULL)
316 		return (NULL);
317 
318 	if ((sp->lcr & (PAREN|PAREVN)) == (PAREN|PAREVN))
319 		par = 'e';
320 	else if ((sp->lcr & PAREN) == PAREN)
321 		par = 'o';
322 	else
323 		par = 'n';
324 
325 	asprintf(&buf, "%d,%d,%c,%d,-", sp->speed,
326 	    (sp->lcr & BITS8) == BITS8? 8:7,
327 	    par, (sp->lcr & STOP2) == STOP2? 2:1);
328 	return (buf);
329 }
330 
331 static int
comc_parse_mode(struct serial * sp,const char * value)332 comc_parse_mode(struct serial *sp, const char *value)
333 {
334 	unsigned long n;
335 	int speed;
336 	int lcr;
337 	char *ep;
338 
339 	if (value == NULL || *value == '\0')
340 		return (CMD_ERROR);
341 
342 	errno = 0;
343 	n = strtoul(value, &ep, 10);
344 	if (errno != 0 || *ep != ',')
345 		return (CMD_ERROR);
346 	speed = n;
347 
348 	ep++;
349 	errno = 0;
350 	n = strtoul(ep, &ep, 10);
351 	if (errno != 0 || *ep != ',')
352 		return (CMD_ERROR);
353 
354 	switch (n) {
355 	case 7: lcr = BITS7;
356 		break;
357 	case 8: lcr = BITS8;
358 		break;
359 	default:
360 		return (CMD_ERROR);
361 	}
362 
363 	ep++;
364 	switch (*ep++) {
365 	case 'n':
366 		break;
367 	case 'e': lcr |= PAREN|PAREVN;
368 		break;
369 	case 'o': lcr |= PAREN|PARODD;
370 		break;
371 	default:
372 		return (CMD_ERROR);
373 	}
374 
375 	if (*ep == ',')
376 		ep++;
377 	else
378 		return (CMD_ERROR);
379 
380 	switch (*ep++) {
381 	case '1':
382 		break;
383 	case '2': lcr |= STOP2;
384 		break;
385 	default:
386 		return (CMD_ERROR);
387 	}
388 
389 	/* handshake is ignored, but we check syntax anyhow */
390 	if (*ep == ',')
391 		ep++;
392 	else
393 		return (CMD_ERROR);
394 
395 	switch (*ep++) {
396 	case '-':
397 	case 'h':
398 	case 's':
399 		break;
400 	default:
401 		return (CMD_ERROR);
402 	}
403 
404 	if (*ep != '\0')
405 		return (CMD_ERROR);
406 
407 	sp->speed = speed;
408 	sp->lcr = lcr;
409 	return (CMD_OK);
410 }
411 
412 /*
413  * CMD_ERROR will cause set/setenv/setprop command to fail,
414  * when used in loader scripts (forth), this will cause processing
415  * of boot scripts to fail, rendering bootloading impossible.
416  * To prevent such unfortunate situation, we return CMD_OK when
417  * there is no such port, or there is invalid value in mode line.
418  */
419 static int
comc_mode_set(struct env_var * ev,int flags,const void * value)420 comc_mode_set(struct env_var *ev, int flags, const void *value)
421 {
422 	struct console *cp;
423 	char name[15];
424 
425 	if (value == NULL)
426 		return (CMD_ERROR);
427 
428 	if ((cp = cons_get_console(ev->ev_name)) == NULL)
429 		return (CMD_OK);
430 
431 	/* Do not override serial setup from SPCR */
432 	snprintf(name, sizeof (name), "%s-spcr-mode", cp->c_name);
433 	if (getenv(name) == NULL) {
434 		if (comc_parse_mode(cp->c_private, value) == CMD_ERROR) {
435 			printf("%s: invalid mode: %s\n", ev->ev_name,
436 			    (char *)value);
437 			return (CMD_OK);
438 		}
439 		(void) comc_setup(cp);
440 		env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
441 	}
442 
443 	return (CMD_OK);
444 }
445 
446 /*
447  * CMD_ERROR will cause set/setenv/setprop command to fail,
448  * when used in loader scripts (forth), this will cause processing
449  * of boot scripts to fail, rendering bootloading impossible.
450  * To prevent such unfortunate situation, we return CMD_OK when
451  * there is no such port or invalid value was used.
452  */
453 static int
comc_cd_set(struct env_var * ev,int flags,const void * value)454 comc_cd_set(struct env_var *ev, int flags, const void *value)
455 {
456 	struct console *cp;
457 	struct serial *sp;
458 
459 	if (value == NULL)
460 		return (CMD_OK);
461 
462 	if ((cp = cons_get_console(ev->ev_name)) == NULL)
463 		return (CMD_OK);
464 
465 	sp = cp->c_private;
466 	if (strcmp(value, "true") == 0) {
467 		sp->ignore_cd = 1;
468 	} else if (strcmp(value, "false") == 0) {
469 		sp->ignore_cd = 0;
470 	} else {
471 		printf("%s: invalid value: %s\n", ev->ev_name,
472 		    (char *)value);
473 		return (CMD_OK);
474 	}
475 
476 	(void) comc_setup(cp);
477 
478 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
479 
480 	return (CMD_OK);
481 }
482 
483 /*
484  * CMD_ERROR will cause set/setenv/setprop command to fail,
485  * when used in loader scripts (forth), this will cause processing
486  * of boot scripts to fail, rendering bootloading impossible.
487  * To prevent such unfortunate situation, we return CMD_OK when
488  * there is no such port, or invalid value was used.
489  */
490 static int
comc_rtsdtr_set(struct env_var * ev,int flags,const void * value)491 comc_rtsdtr_set(struct env_var *ev, int flags, const void *value)
492 {
493 	struct console *cp;
494 	struct serial *sp;
495 
496 	if (value == NULL)
497 		return (CMD_OK);
498 
499 	if ((cp = cons_get_console(ev->ev_name)) == NULL)
500 		return (CMD_OK);
501 
502 	sp = cp->c_private;
503 	if (strcmp(value, "true") == 0) {
504 		sp->rtsdtr_off = 1;
505 	} else if (strcmp(value, "false") == 0) {
506 		sp->rtsdtr_off = 0;
507 	} else {
508 		printf("%s: invalid value: %s\n", ev->ev_name,
509 		    (char *)value);
510 		return (CMD_OK);
511 	}
512 
513 	(void) comc_setup(cp);
514 
515 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
516 
517 	return (CMD_OK);
518 }
519 
520 /*
521  * In case of error, we also reset ACTIVE flags, so the console
522  * framefork will try alternate consoles.
523  */
524 static bool
comc_setup(struct console * cp)525 comc_setup(struct console *cp)
526 {
527 	struct serial *sp = cp->c_private;
528 	static int TRY_COUNT = 1000000;
529 	int tries;
530 
531 	outb(sp->ioaddr + com_cfcr, CFCR_DLAB | sp->lcr);
532 	outb(sp->ioaddr + com_dlbl, COMC_BPS(sp->speed) & 0xff);
533 	outb(sp->ioaddr + com_dlbh, COMC_BPS(sp->speed) >> 8);
534 	outb(sp->ioaddr + com_cfcr, sp->lcr);
535 	outb(sp->ioaddr + com_mcr,
536 	    sp->rtsdtr_off? ~(MCR_RTS | MCR_DTR) : MCR_RTS | MCR_DTR);
537 
538 	tries = 0;
539 	do {
540 		inb(sp->ioaddr + com_data);
541 	} while (inb(sp->ioaddr + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
542 
543 	if (tries == TRY_COUNT)
544 		return (false);
545 	/* Mark this port usable. */
546 	cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
547 	return (true);
548 }
549 
550 int
comc_getspeed(int ioaddr)551 comc_getspeed(int ioaddr)
552 {
553 	uint_t	divisor;
554 	uchar_t	dlbh;
555 	uchar_t	dlbl;
556 	uchar_t	cfcr;
557 
558 	cfcr = inb(ioaddr + com_cfcr);
559 	outb(ioaddr + com_cfcr, CFCR_DLAB | cfcr);
560 
561 	dlbl = inb(ioaddr + com_dlbl);
562 	dlbh = inb(ioaddr + com_dlbh);
563 
564 	outb(ioaddr + com_cfcr, cfcr);
565 
566 	divisor = dlbh << 8 | dlbl;
567 
568 	/* XXX there should be more sanity checking. */
569 	if (divisor == 0)
570 		return (COMSPEED);
571 	return (COMC_DIV2BPS(divisor));
572 }
573