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