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