xref: /freebsd/stand/efi/libefi/eficom.c (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
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 #include <stand.h>
28 #include <sys/errno.h>
29 #include <bootstrap.h>
30 #include <stdbool.h>
31 
32 #include <efi.h>
33 #include <efilib.h>
34 
35 static EFI_GUID serial = SERIAL_IO_PROTOCOL;
36 
37 #define	COMC_TXWAIT	0x40000		/* transmit timeout */
38 
39 #define	PNP0501		0x501		/* 16550A-compatible COM port */
40 
41 struct serial {
42 	uint64_t	newbaudrate;
43 	uint64_t	baudrate;
44 	uint32_t	timeout;
45 	uint32_t	receivefifodepth;
46 	uint32_t	databits;
47 	EFI_PARITY_TYPE	parity;
48 	EFI_STOP_BITS_TYPE stopbits;
49 	int		ioaddr;		/* index in handles array */
50 	EFI_HANDLE	currdev;	/* current serial device */
51 	EFI_HANDLE	condev;		/* EFI Console device */
52 	SERIAL_IO_INTERFACE *sio;
53 };
54 
55 static void	comc_probe(struct console *);
56 static int	comc_init(int);
57 static void	comc_putchar(int);
58 static int	comc_getchar(void);
59 static int	comc_ischar(void);
60 static bool	comc_setup(void);
61 static int	comc_parse_intval(const char *, unsigned *);
62 static int	comc_port_set(struct env_var *, int, const void *);
63 static int	comc_speed_set(struct env_var *, int, const void *);
64 
65 static struct serial	*comc_port;
66 extern struct console efi_console;
67 
68 struct console eficom = {
69 	.c_name = "eficom",
70 	.c_desc = "serial port",
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 };
78 
79 #if defined(__aarch64__) && __FreeBSD_version < 1500000
80 static void	comc_probe_compat(struct console *);
81 struct console comconsole = {
82 	.c_name = "comconsole",
83 	.c_desc = "serial port",
84 	.c_flags = 0,
85 	.c_probe = comc_probe_compat,
86 	.c_init = comc_init,
87 	.c_out = comc_putchar,
88 	.c_in = comc_getchar,
89 	.c_ready = comc_ischar,
90 };
91 #endif
92 
93 static EFI_STATUS
94 efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
95 {
96 	UINTN bufsz = 0;
97 	EFI_STATUS status;
98 	EFI_HANDLE *handles;
99 
100 	/*
101 	 * get buffer size
102 	 */
103 	*nhandles = 0;
104 	handles = NULL;
105 	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
106 	if (status != EFI_BUFFER_TOO_SMALL)
107 		return (status);
108 
109 	if ((handles = malloc(bufsz)) == NULL)
110 		return (ENOMEM);
111 
112 	*nhandles = (int)(bufsz / sizeof (EFI_HANDLE));
113 	/*
114 	 * get handle array
115 	 */
116 	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
117 	if (EFI_ERROR(status)) {
118 		free(handles);
119 		*nhandles = 0;
120 	} else
121 		*handlep = handles;
122 	return (status);
123 }
124 
125 /*
126  * Find serial device number from device path.
127  * Return -1 if not found.
128  */
129 static int
130 efi_serial_get_index(EFI_DEVICE_PATH *devpath, int idx)
131 {
132 	ACPI_HID_DEVICE_PATH  *acpi;
133 	CHAR16 *text;
134 
135 	while (!IsDevicePathEnd(devpath)) {
136 		if (DevicePathType(devpath) == MESSAGING_DEVICE_PATH &&
137 		    DevicePathSubType(devpath) == MSG_UART_DP)
138 			return (idx);
139 
140 		if (DevicePathType(devpath) == ACPI_DEVICE_PATH &&
141 		    (DevicePathSubType(devpath) == ACPI_DP ||
142 		    DevicePathSubType(devpath) == ACPI_EXTENDED_DP)) {
143 
144 			acpi = (ACPI_HID_DEVICE_PATH *)devpath;
145 			if (acpi->HID == EISA_PNP_ID(PNP0501)) {
146 				return (acpi->UID);
147 			}
148 		}
149 
150 		devpath = NextDevicePathNode(devpath);
151 	}
152 	return (-1);
153 }
154 
155 /*
156  * The order of handles from LocateHandle() is not known, we need to
157  * iterate handles, pick device path for handle, and check the device
158  * number.
159  */
160 static EFI_HANDLE
161 efi_serial_get_handle(int port, EFI_HANDLE condev)
162 {
163 	EFI_STATUS status;
164 	EFI_HANDLE *handles, handle;
165 	EFI_DEVICE_PATH *devpath;
166 	int index, nhandles;
167 
168 	if (port == -1)
169 		return (NULL);
170 
171 	handles = NULL;
172 	nhandles = 0;
173 	status = efi_serial_init(&handles, &nhandles);
174 	if (EFI_ERROR(status))
175 		return (NULL);
176 
177 	/*
178 	 * We have console handle, set ioaddr for it.
179 	 */
180 	if (condev != NULL) {
181 		for (index = 0; index < nhandles; index++) {
182 			if (condev == handles[index]) {
183 				devpath = efi_lookup_devpath(condev);
184 				comc_port->ioaddr =
185 				    efi_serial_get_index(devpath, index);
186 				efi_close_devpath(condev);
187 				free(handles);
188 				return (condev);
189 			}
190 		}
191 	}
192 
193 	handle = NULL;
194 	for (index = 0; handle == NULL && index < nhandles; index++) {
195 		devpath = efi_lookup_devpath(handles[index]);
196 		if (port == efi_serial_get_index(devpath, index))
197 			handle = (handles[index]);
198 		efi_close_devpath(handles[index]);
199 	}
200 
201 	/*
202 	 * In case we did fail to identify the device by path, use port as
203 	 * array index. Note, we did check port == -1 above.
204 	 */
205 	if (port < nhandles && handle == NULL)
206 		handle = handles[port];
207 
208 	free(handles);
209 	return (handle);
210 }
211 
212 static EFI_HANDLE
213 comc_get_con_serial_handle(const char *name)
214 {
215 	EFI_HANDLE handle;
216 	EFI_DEVICE_PATH *node;
217 	EFI_STATUS status;
218 	char *buf, *ep;
219 	size_t sz;
220 
221 	buf = NULL;
222 	sz = 0;
223 	status = efi_global_getenv(name, buf, &sz);
224 	if (status == EFI_BUFFER_TOO_SMALL) {
225 		buf = malloc(sz);
226 		if (buf == NULL)
227 			return (NULL);
228 		status = efi_global_getenv(name, buf, &sz);
229 	}
230 	if (status != EFI_SUCCESS) {
231 		free(buf);
232 		return (NULL);
233 	}
234 
235 	ep = buf + sz;
236 	node = (EFI_DEVICE_PATH *)buf;
237 	while ((char *)node < ep) {
238 		status = BS->LocateDevicePath(&serial, &node, &handle);
239 		if (status == EFI_SUCCESS) {
240 			free(buf);
241 			return (handle);
242 		}
243 
244 		/* Sanity check the node before moving to the next node. */
245 		if (DevicePathNodeLength(node) < sizeof(*node))
246 			break;
247 
248 		/* Start of next device path in list. */
249 		node = NextDevicePathNode(node);
250 	}
251 	free(buf);
252 	return (NULL);
253 }
254 
255 /*
256  * Called from cons_probe() to see if this device is available.
257  * Return immediately on x86, except for hyperv, since it interferes with
258  * common configurations otherwise (yes, this is just firewalling the bug).
259  */
260 static void
261 comc_probe(struct console *sc)
262 {
263 	EFI_STATUS status;
264 	EFI_HANDLE handle;
265 	char name[20];
266 	char value[20];
267 	unsigned val;
268 	char *env, *buf, *ep;
269 	size_t sz;
270 
271 #ifdef __amd64__
272 	/*
273 	 * This driver tickles issues on a number of different firmware loads.
274 	 * It is only required for HyperV, and is only known to work on HyperV,
275 	 * so only allow it on HyperV.
276 	 */
277 	env = getenv("smbios.bios.version");
278 	if (env == NULL || strncmp(env, "Hyper-V", 7) != 0) {
279 		return;
280 	}
281 #endif
282 
283 	if (comc_port == NULL) {
284 		comc_port = calloc(1, sizeof (struct serial));
285 		if (comc_port == NULL)
286 			return;
287 	}
288 
289 	/* Use defaults from firmware */
290 	comc_port->databits = 8;
291 	comc_port->parity = DefaultParity;
292 	comc_port->stopbits = DefaultStopBits;
293 
294 	handle = NULL;
295 	env = getenv("efi_com_port");
296 	if (comc_parse_intval(env, &val) == CMD_OK) {
297 		comc_port->ioaddr = val;
298 	} else {
299 		/*
300 		 * efi_com_port is not set, we need to select default.
301 		 * First, we consult ConOut variable to see if
302 		 * we have serial port redirection. If not, we just
303 		 * pick first device.
304 		 */
305 		handle = comc_get_con_serial_handle("ConOut");
306 		comc_port->condev = handle;
307 	}
308 
309 	handle = efi_serial_get_handle(comc_port->ioaddr, handle);
310 	if (handle != NULL) {
311 		comc_port->currdev = handle;
312 		status = BS->OpenProtocol(handle, &serial,
313 		    (void**)&comc_port->sio, IH, NULL,
314 		    EFI_OPEN_PROTOCOL_GET_PROTOCOL);
315 
316 		if (EFI_ERROR(status)) {
317 			comc_port->sio = NULL;
318 		} else {
319 			comc_port->newbaudrate =
320 			    comc_port->baudrate = comc_port->sio->Mode->BaudRate;
321 			comc_port->timeout = comc_port->sio->Mode->Timeout;
322 			comc_port->receivefifodepth =
323 			    comc_port->sio->Mode->ReceiveFifoDepth;
324 			comc_port->databits = comc_port->sio->Mode->DataBits;
325 			comc_port->parity = comc_port->sio->Mode->Parity;
326 			comc_port->stopbits = comc_port->sio->Mode->StopBits;
327 		}
328 	}
329 
330 	/*
331 	 * If there's no sio, then the device isn't there, so just return since
332 	 * the present flags aren't yet set.
333 	 */
334 	if (comc_port->sio == NULL) {
335 		free(comc_port);
336 		comc_port = NULL;
337 		return;
338 	}
339 
340 	if (env != NULL)
341 		unsetenv("efi_com_port");
342 	snprintf(value, sizeof (value), "%u", comc_port->ioaddr);
343 	env_setenv("efi_com_port", EV_VOLATILE, value,
344 	    comc_port_set, env_nounset);
345 
346 	env = getenv("efi_com_speed");
347 	if (env == NULL)
348 		/* fallback to comconsole setting */
349 		env = getenv("comconsole_speed");
350 
351 	if (comc_parse_intval(env, &val) == CMD_OK)
352 		comc_port->newbaudrate = val;
353 
354 	if (env != NULL)
355 		unsetenv("efi_com_speed");
356 	snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate);
357 	env_setenv("efi_com_speed", EV_VOLATILE, value,
358 	    comc_speed_set, env_nounset);
359 
360 	if (comc_setup()) {
361 		sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
362 	} else {
363 		sc->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
364 		free(comc_port);
365 		comc_port = NULL;
366 	}
367 }
368 
369 #if defined(__aarch64__) && __FreeBSD_version < 1500000
370 static void
371 comc_probe_compat(struct console *sc)
372 {
373 	comc_probe(&eficom);
374 	if (eficom.c_flags & (C_PRESENTIN | C_PRESENTOUT)) {
375 		printf("comconsole: comconsole device name is deprecated, switch to eficom\n");
376 	}
377 	/*
378 	 * Note: We leave the present bits unset in sc to avoid ghosting.
379 	 */
380 }
381 #endif
382 
383 /*
384  * Called when the console is selected in cons_change. If we didn't detect the
385  * device, comc_port will be NULL, and comc_setup will fail. It may be called
386  * even when the device isn't present as a 'fallback' console or when listed
387  * specifically in console env, so we have to reset the c_flags in those case to
388  * say it's not present.
389  */
390 static int
391 comc_init(int arg __unused)
392 {
393 	if (comc_setup())
394 		return (CMD_OK);
395 
396 	eficom.c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
397 	return (CMD_ERROR);
398 }
399 
400 static void
401 comc_putchar(int c)
402 {
403 	int wait;
404 	EFI_STATUS status;
405 	UINTN bufsz = 1;
406 	char cb = c;
407 
408 	if (comc_port->sio == NULL)
409 		return;
410 
411 	for (wait = COMC_TXWAIT; wait > 0; wait--) {
412 		status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb);
413 		if (status != EFI_TIMEOUT)
414 			break;
415 	}
416 }
417 
418 static int
419 comc_getchar(void)
420 {
421 	EFI_STATUS status;
422 	UINTN bufsz = 1;
423 	char c;
424 
425 
426 	/*
427 	 * if this device is also used as ConIn, some firmwares
428 	 * fail to return all input via SIO protocol.
429 	 */
430 	if (comc_port->currdev == comc_port->condev) {
431 		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
432 			return (efi_console.c_in());
433 		return (-1);
434 	}
435 
436 	if (comc_port->sio == NULL)
437 		return (-1);
438 
439 	status = comc_port->sio->Read(comc_port->sio, &bufsz, &c);
440 	if (EFI_ERROR(status) || bufsz == 0)
441 		return (-1);
442 
443 	return (c);
444 }
445 
446 static int
447 comc_ischar(void)
448 {
449 	EFI_STATUS status;
450 	uint32_t control;
451 
452 	/*
453 	 * if this device is also used as ConIn, some firmwares
454 	 * fail to return all input via SIO protocol.
455 	 */
456 	if (comc_port->currdev == comc_port->condev) {
457 		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
458 			return (efi_console.c_ready());
459 		return (0);
460 	}
461 
462 	if (comc_port->sio == NULL)
463 		return (0);
464 
465 	status = comc_port->sio->GetControl(comc_port->sio, &control);
466 	if (EFI_ERROR(status))
467 		return (0);
468 
469 	return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
470 }
471 
472 static int
473 comc_parse_intval(const char *value, unsigned *valp)
474 {
475 	unsigned n;
476 	char *ep;
477 
478 	if (value == NULL || *value == '\0')
479 		return (CMD_ERROR);
480 
481 	errno = 0;
482 	n = strtoul(value, &ep, 10);
483 	if (errno != 0 || *ep != '\0')
484 		return (CMD_ERROR);
485 	*valp = n;
486 
487 	return (CMD_OK);
488 }
489 
490 static int
491 comc_port_set(struct env_var *ev, int flags, const void *value)
492 {
493 	unsigned port;
494 	SERIAL_IO_INTERFACE *sio;
495 	EFI_HANDLE handle;
496 	EFI_STATUS status;
497 
498 	if (value == NULL || comc_port == NULL)
499 		return (CMD_ERROR);
500 
501 	if (comc_parse_intval(value, &port) != CMD_OK)
502 		return (CMD_ERROR);
503 
504 	handle = efi_serial_get_handle(port, NULL);
505 	if (handle == NULL) {
506 		printf("no handle\n");
507 		return (CMD_ERROR);
508 	}
509 
510 	status = BS->OpenProtocol(handle, &serial,
511 	    (void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
512 
513 	if (EFI_ERROR(status)) {
514 		printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status));
515 		return (CMD_ERROR);
516 	}
517 
518 	comc_port->currdev = handle;
519 	comc_port->ioaddr = port;
520 	comc_port->sio = sio;
521 
522 	(void) comc_setup();
523 
524 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
525 	return (CMD_OK);
526 }
527 
528 static int
529 comc_speed_set(struct env_var *ev, int flags, const void *value)
530 {
531 	unsigned speed;
532 
533 	if (value == NULL || comc_port == NULL)
534 		return (CMD_ERROR);
535 
536 	if (comc_parse_intval(value, &speed) != CMD_OK)
537 		return (CMD_ERROR);
538 
539 	comc_port->newbaudrate = speed;
540 	if (comc_setup())
541 		env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
542 
543 	return (CMD_OK);
544 }
545 
546 /*
547  * In case of error, we also reset ACTIVE flags, so the console
548  * framefork will try alternate consoles.
549  */
550 static bool
551 comc_setup(void)
552 {
553 	EFI_STATUS status;
554 	char *ev;
555 
556 	/*
557 	 * If the device isn't active, or there's no port present.
558 	 */
559 	if ((eficom.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0 || comc_port == NULL)
560 		return (false);
561 
562 	if (comc_port->sio->Reset != NULL) {
563 		status = comc_port->sio->Reset(comc_port->sio);
564 		if (EFI_ERROR(status))
565 			return (false);
566 	}
567 
568 	/*
569 	 * Avoid setting the baud rate on Hyper-V. Also, only set the baud rate
570 	 * if the baud rate has changed from the default. And pass in '0' or
571 	 * DefaultFoo when we're not changing those values. Some EFI
572 	 * implementations get cranky when you set things to the values reported
573 	 * back even when they are unchanged.
574 	 */
575 	if (comc_port->sio->SetAttributes != NULL &&
576 	    comc_port->newbaudrate != comc_port->baudrate) {
577 		ev = getenv("smbios.bios.version");
578 		if (ev != NULL && strncmp(ev, "Hyper-V", 7) != 0) {
579 			status = comc_port->sio->SetAttributes(comc_port->sio,
580 			    comc_port->newbaudrate, 0, 0, DefaultParity, 0,
581 			    DefaultStopBits);
582 			if (EFI_ERROR(status))
583 				return (false);
584 			comc_port->baudrate = comc_port->newbaudrate;
585 		}
586 	}
587 
588 #ifdef EFI_FORCE_RTS
589 	if (comc_port->sio->GetControl != NULL && comc_port->sio->SetControl != NULL) {
590 		UINT32 control;
591 
592 		status = comc_port->sio->GetControl(comc_port->sio, &control);
593 		if (EFI_ERROR(status))
594 			return (false);
595 		control |= EFI_SERIAL_REQUEST_TO_SEND;
596 		(void) comc_port->sio->SetControl(comc_port->sio, control);
597 	}
598 #endif
599 	/* Mark this port usable. */
600 	eficom.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
601 	return (true);
602 }
603