xref: /titanic_54/usr/src/boot/sys/boot/efi/loader/comconsole.c (revision 3f440373b46b16166e8dc61c0928e7d6d9ec5e12)
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 #include <sys/cdefs.h>
27 
28 #include <stand.h>
29 #include <sys/errno.h>
30 #include <bootstrap.h>
31 
32 #include <efi.h>
33 #include <efilib.h>
34 
35 #include "loader_efi.h"
36 
37 static EFI_GUID serial = SERIAL_IO_PROTOCOL;
38 
39 #define	COMC_TXWAIT	0x40000		/* transmit timeout */
40 
41 #ifndef	COMSPEED
42 #define	COMSPEED	9600
43 #endif
44 
45 struct serial {
46 	uint64_t	baudrate;
47 	uint8_t		databits;
48 	EFI_PARITY_TYPE	parity;
49 	EFI_STOP_BITS_TYPE stopbits;
50 	uint8_t		ignore_cd;	/* boolean */
51 	uint8_t		rtsdtr_off;	/* boolean */
52 	int		ioaddr;		/* index in handles array */
53 	SERIAL_IO_INTERFACE *sio;
54 };
55 
56 static void	comc_probe(struct console *);
57 static int	comc_init(struct console *, int);
58 static void	comc_putchar(struct console *, int);
59 static int	comc_getchar(struct console *);
60 static int	comc_ischar(struct console *);
61 static void	comc_setup(struct console *);
62 static char	*comc_asprint_mode(struct serial *);
63 static int	comc_parse_mode(struct serial *, const char *);
64 static int	comc_mode_set(struct env_var *, int, const void *);
65 static int	comc_cd_set(struct env_var *, int, const void *);
66 static int	comc_rtsdtr_set(struct env_var *, int, const void *);
67 
68 struct console ttya = {
69 	.c_name = "ttya",
70 	.c_desc = "serial port a",
71 	.c_flags = 0,
72 	.c_probe = comc_probe,
73 	.c_init = comc_init,
74 	.c_out = comc_putchar,
75 	.c_in = comc_getchar,
76 	.c_ready = comc_ischar,
77 	.c_private = NULL
78 };
79 
80 struct console ttyb = {
81 	.c_name = "ttyb",
82 	.c_desc = "serial port b",
83 	.c_flags = 0,
84 	.c_probe = comc_probe,
85 	.c_init = comc_init,
86 	.c_out = comc_putchar,
87 	.c_in = comc_getchar,
88 	.c_ready = comc_ischar,
89 	.c_private = NULL
90 };
91 
92 struct console ttyc = {
93 	.c_name = "ttyc",
94 	.c_desc = "serial port c",
95 	.c_flags = 0,
96 	.c_probe = comc_probe,
97 	.c_init = comc_init,
98 	.c_out = comc_putchar,
99 	.c_in = comc_getchar,
100 	.c_ready = comc_ischar,
101 	.c_private = NULL
102 };
103 
104 struct console ttyd = {
105 	.c_name = "ttyd",
106 	.c_desc = "serial port d",
107 	.c_flags = 0,
108 	.c_probe = comc_probe,
109 	.c_init = comc_init,
110 	.c_out = comc_putchar,
111 	.c_in = comc_getchar,
112 	.c_ready = comc_ischar,
113 	.c_private = NULL
114 };
115 
116 EFI_STATUS
117 efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
118 {
119 	UINTN bufsz = 0;
120 	EFI_STATUS status;
121 	EFI_HANDLE *handles;
122 
123 	/*
124 	 * get buffer size
125 	 */
126 	*nhandles = 0;
127 	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
128 	if (status != EFI_BUFFER_TOO_SMALL)
129 		return (status);
130 
131 	if ((handles = malloc(bufsz)) == NULL)
132 		return (ENOMEM);
133 
134 	*nhandles = (int)(bufsz/sizeof (EFI_HANDLE));
135 	/*
136 	 * get handle array
137 	 */
138 	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
139 	if (EFI_ERROR(status)) {
140 		free(handles);
141 		*nhandles = 0;
142 	} else
143 		*handlep = handles;
144 	return (status);
145 }
146 
147 static void
148 comc_probe(struct console *cp)
149 {
150 	EFI_STATUS status;
151 	struct serial *port;
152 	char name[20];
153 	char value[20];
154 	char *cons, *env;
155 	EFI_HANDLE *handles = NULL;	/* array of handles */
156 	int nhandles = 0;		/* number of handles in array */
157 
158 	/* are we already set up? */
159 	if (cp->c_private != NULL)
160 		return;
161 
162 	/* make sure the handles are available */
163 	status = efi_serial_init(&handles, &nhandles);
164 
165 	cp->c_private = malloc(sizeof (struct serial));
166 	port = cp->c_private;
167 	port->baudrate = COMSPEED;
168 
169 	if (strcmp(cp->c_name, "ttya") == 0)
170 		port->ioaddr = 0;
171 	else if (strcmp(cp->c_name, "ttyb") == 0)
172 		port->ioaddr = 1;
173 	else if (strcmp(cp->c_name, "ttyc") == 0)
174 		port->ioaddr = 2;
175 	else if (strcmp(cp->c_name, "ttyd") == 0)
176 		port->ioaddr = 3;
177 
178 	if (port->ioaddr >= nhandles)
179 		port->ioaddr = -1;	/* invalid port */
180 
181 	port->databits = 8;		/* 8,n,1 */
182 	port->parity = NoParity;	/* 8,n,1 */
183 	port->stopbits = OneStopBit;	/* 8,n,1 */
184 	port->ignore_cd = 1;		/* ignore cd */
185 	port->rtsdtr_off = 0;		/* rts-dtr is on */
186 	port->sio = NULL;
187 
188 	if (port->ioaddr != -1) {
189 		status = BS->OpenProtocol(handles[port->ioaddr],
190 		    &serial, (void**)&port->sio, IH, NULL,
191 		    EFI_OPEN_PROTOCOL_GET_PROTOCOL);
192 
193 		if (EFI_ERROR(status))
194 			port->ioaddr = -1;	/* invalid port */
195 	}
196 	if (handles != NULL)
197 		free(handles);
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 	comc_setup(cp);
241 }
242 
243 static int
244 comc_init(struct console *cp, int arg __attribute((unused)))
245 {
246 
247 	comc_setup(cp);
248 
249 	if ((cp->c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
250 	    (C_PRESENTIN | C_PRESENTOUT))
251 		return (CMD_OK);
252 	return (CMD_ERROR);
253 }
254 
255 static void
256 comc_putchar(struct console *cp, int c)
257 {
258 	int wait;
259 	EFI_STATUS status;
260 	UINTN bufsz = 1;
261 	char cb = c;
262 	struct serial *sp = cp->c_private;
263 
264 	if (sp->sio == NULL)
265 		return;
266 
267 	for (wait = COMC_TXWAIT; wait > 0; wait--) {
268 		status = sp->sio->Write(sp->sio, &bufsz, &cb);
269 		if (status != EFI_TIMEOUT)
270 			break;
271 	}
272 }
273 
274 static int
275 comc_getchar(struct console *cp)
276 {
277 	EFI_STATUS status;
278 	UINTN bufsz = 1;
279 	char c;
280 	struct serial *sp = cp->c_private;
281 
282 	if (sp->sio == NULL || !comc_ischar(cp))
283 		return (-1);
284 
285 	status = sp->sio->Read(sp->sio, &bufsz, &c);
286 	if (EFI_ERROR(status) || bufsz == 0)
287 		return (-1);
288 
289 	return (c);
290 }
291 
292 static int
293 comc_ischar(struct console *cp)
294 {
295 	EFI_STATUS status;
296 	uint32_t control;
297 	struct serial *sp = cp->c_private;
298 
299 	if (sp->sio == NULL)
300 		return (0);
301 
302 	status = sp->sio->GetControl(sp->sio, &control);
303 	if (EFI_ERROR(status))
304 		return (0);
305 
306 	return (!(status & EFI_SERIAL_INPUT_BUFFER_EMPTY));
307 }
308 
309 static char *
310 comc_asprint_mode(struct serial *sp)
311 {
312 	char par = 'n', *buf;
313 	int stop = 1;
314 
315 	if (sp == NULL)
316 		return (NULL);
317 
318 	switch (sp->parity) {
319 	case NoParity: par = 'n';
320 		break;
321 	case EvenParity: par = 'e';
322 		break;
323 	case OddParity: par = 'o';
324 		break;
325 	}
326 	switch (sp->stopbits) {
327 	case OneStopBit: stop = 1;
328 		break;
329 	case TwoStopBits: stop = 2;
330 		break;
331 	}
332 
333 	asprintf(&buf, "%ju,%d,%c,%d,-", sp->baudrate, sp->databits, par, stop);
334 	return (buf);
335 }
336 
337 static int
338 comc_parse_mode(struct serial *sp, const char *value)
339 {
340 	unsigned long n;
341 	uint64_t baudrate;
342 	uint8_t databits = 8;
343 	int parity = NoParity;
344 	int stopbits = OneStopBit;
345 	char *ep;
346 
347 	if (value == NULL || *value == '\0')
348 		return (CMD_ERROR);
349 
350 	errno = 0;
351 	n = strtoul(value, &ep, 10);
352 	if (errno != 0 || *ep != ',')
353 		return (CMD_ERROR);
354 	baudrate = n;
355 
356 	ep++;
357 	n = strtoul(ep, &ep, 10);
358 	if (errno != 0 || *ep != ',')
359 		return (CMD_ERROR);
360 
361 	switch (n) {
362 	case 7: databits = 7;
363 		break;
364 	case 8: databits = 8;
365 		break;
366 	default:
367 		return (CMD_ERROR);
368 	}
369 
370 	ep++;
371 	switch (*ep++) {
372 	case 'n': parity = NoParity;
373 		break;
374 	case 'e': parity = EvenParity;
375 		break;
376 	case 'o': parity = OddParity;
377 		break;
378 	default:
379 		return (CMD_ERROR);
380 	}
381 
382 	if (*ep == ',')
383 		ep++;
384 	else
385 		return (CMD_ERROR);
386 
387 	switch (*ep++) {
388 	case '1': stopbits = OneStopBit;
389 		break;
390 	case '2': stopbits = TwoStopBits;
391 		break;
392 	default:
393 		return (CMD_ERROR);
394 	}
395 
396 	/* handshake is ignored, but we check syntax anyhow */
397 	if (*ep == ',')
398 		ep++;
399 	else
400 		return (CMD_ERROR);
401 
402 	switch (*ep++) {
403 	case '-':
404 	case 'h':
405 	case 's':
406 		break;
407 	default:
408 		return (CMD_ERROR);
409 	}
410 
411 	if (*ep != '\0')
412 		return (CMD_ERROR);
413 
414 	sp->baudrate = baudrate;
415 	sp->databits = databits;
416 	sp->parity = parity;
417 	sp->stopbits = stopbits;
418 	return (CMD_OK);
419 }
420 
421 static struct console *
422 get_console(char *name)
423 {
424 	struct console *cp = NULL;
425 
426 	switch (name[3]) {
427 	case 'a': cp = &ttya;
428 		break;
429 	case 'b': cp = &ttyb;
430 		break;
431 	case 'c': cp = &ttyc;
432 		break;
433 	case 'd': cp = &ttyd;
434 		break;
435 	}
436 	return (cp);
437 }
438 
439 static int
440 comc_mode_set(struct env_var *ev, int flags, const void *value)
441 {
442 	struct console *cp;
443 
444 	if (value == NULL)
445 		return (CMD_ERROR);
446 
447 	if ((cp = get_console(ev->ev_name)) == NULL)
448 		return (CMD_ERROR);
449 
450 	if (comc_parse_mode(cp->c_private, value) == CMD_ERROR)
451 		return (CMD_ERROR);
452 
453 	comc_setup(cp);
454 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
455 
456 	return (CMD_OK);
457 }
458 
459 static int
460 comc_cd_set(struct env_var *ev, int flags, const void *value)
461 {
462 	struct console *cp;
463 	struct serial *sp;
464 
465 	if (value == NULL)
466 		return (CMD_ERROR);
467 
468 	if ((cp = get_console(ev->ev_name)) == NULL)
469 		return (CMD_ERROR);
470 
471 	sp = cp->c_private;
472 	if (strcmp(value, "true") == 0)
473 		sp->ignore_cd = 1;
474 	else if (strcmp(value, "false") == 0)
475 		sp->ignore_cd = 0;
476 	else
477 		return (CMD_ERROR);
478 
479 	comc_setup(cp);
480 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
481 
482 	return (CMD_OK);
483 }
484 
485 static int
486 comc_rtsdtr_set(struct env_var *ev, int flags, const void *value)
487 {
488 	struct console *cp;
489 	struct serial *sp;
490 
491 	if (value == NULL)
492 		return (CMD_ERROR);
493 
494 	if ((cp = get_console(ev->ev_name)) == NULL)
495 		return (CMD_ERROR);
496 
497 	sp = cp->c_private;
498 	if (strcmp(value, "true") == 0)
499 		sp->rtsdtr_off = 1;
500 	else if (strcmp(value, "false") == 0)
501 		sp->rtsdtr_off = 0;
502 	else
503 		return (CMD_ERROR);
504 
505 	comc_setup(cp);
506 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
507 
508 	return (CMD_OK);
509 }
510 
511 static void
512 comc_setup(struct console *cp)
513 {
514 	EFI_STATUS status;
515 	UINT32 control;
516 	struct serial *sp = cp->c_private;
517 
518 	if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
519 		return;
520 
521 	/* port is not usable */
522 	if (sp->sio == NULL) {
523 		cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
524 		return;
525 	}
526 
527 	cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
528 	status = sp->sio->Reset(sp->sio);
529 	if (EFI_ERROR(status)) {
530 		cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
531 	}
532 
533 	status = sp->sio->SetAttributes(sp->sio, sp->baudrate, 0, 0, sp->parity,
534 	    sp->databits, sp->stopbits);
535 	if (EFI_ERROR(status)) {
536 		cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
537 	}
538 
539 	if (sp->rtsdtr_off) {
540 		status = sp->sio->GetControl(sp->sio, &control);
541 		if (EFI_ERROR(status)) {
542 			cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
543 		}
544 		control &= ~(EFI_SERIAL_REQUEST_TO_SEND |
545 		    EFI_SERIAL_DATA_TERMINAL_READY);
546 		status = sp->sio->SetControl(sp->sio, control);
547 		if (EFI_ERROR(status)) {
548 			cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
549 		}
550 	}
551 }
552