xref: /freebsd/stand/efi/libefi/eficom.c (revision d91f8db5f1822c43cd256f19aae1d059e4b25a26)
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 static void
258 comc_probe(struct console *sc)
259 {
260 	EFI_STATUS status;
261 	EFI_HANDLE handle;
262 	char name[20];
263 	char value[20];
264 	unsigned val;
265 	char *env, *buf, *ep;
266 	size_t sz;
267 
268 	if (comc_port == NULL) {
269 		comc_port = calloc(1, sizeof (struct serial));
270 		if (comc_port == NULL)
271 			return;
272 	}
273 
274 	/* Use defaults from firmware */
275 	comc_port->databits = 8;
276 	comc_port->parity = DefaultParity;
277 	comc_port->stopbits = DefaultStopBits;
278 
279 	handle = NULL;
280 	env = getenv("efi_com_port");
281 	if (comc_parse_intval(env, &val) == CMD_OK) {
282 		comc_port->ioaddr = val;
283 	} else {
284 		/*
285 		 * efi_com_port is not set, we need to select default.
286 		 * First, we consult ConOut variable to see if
287 		 * we have serial port redirection. If not, we just
288 		 * pick first device.
289 		 */
290 		handle = comc_get_con_serial_handle("ConOut");
291 		comc_port->condev = handle;
292 	}
293 
294 	handle = efi_serial_get_handle(comc_port->ioaddr, handle);
295 	if (handle != NULL) {
296 		comc_port->currdev = handle;
297 		status = BS->OpenProtocol(handle, &serial,
298 		    (void**)&comc_port->sio, IH, NULL,
299 		    EFI_OPEN_PROTOCOL_GET_PROTOCOL);
300 
301 		if (EFI_ERROR(status)) {
302 			comc_port->sio = NULL;
303 		} else {
304 			comc_port->newbaudrate =
305 			    comc_port->baudrate = comc_port->sio->Mode->BaudRate;
306 			comc_port->timeout = comc_port->sio->Mode->Timeout;
307 			comc_port->receivefifodepth =
308 			    comc_port->sio->Mode->ReceiveFifoDepth;
309 			comc_port->databits = comc_port->sio->Mode->DataBits;
310 			comc_port->parity = comc_port->sio->Mode->Parity;
311 			comc_port->stopbits = comc_port->sio->Mode->StopBits;
312 		}
313 	}
314 
315 	if (env != NULL)
316 		unsetenv("efi_com_port");
317 	snprintf(value, sizeof (value), "%u", comc_port->ioaddr);
318 	env_setenv("efi_com_port", EV_VOLATILE, value,
319 	    comc_port_set, env_nounset);
320 
321 	env = getenv("efi_com_speed");
322 	if (env == NULL)
323 		/* fallback to comconsole setting */
324 		env = getenv("comconsole_speed");
325 
326 	if (comc_parse_intval(env, &val) == CMD_OK)
327 		comc_port->newbaudrate = val;
328 
329 	if (env != NULL)
330 		unsetenv("efi_com_speed");
331 	snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate);
332 	env_setenv("efi_com_speed", EV_VOLATILE, value,
333 	    comc_speed_set, env_nounset);
334 
335 	eficom.c_flags = 0;
336 	if (comc_setup()) {
337 		sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
338 	}
339 }
340 
341 #if defined(__aarch64__) && __FreeBSD_version < 1500000
342 static void
343 comc_probe_compat(struct console *sc)
344 {
345 	comc_probe(sc);
346 	if (sc->c_flags & (C_PRESENTIN | C_PRESENTOUT)) {
347 		printf("comconsole: comconsole device name is deprecated, switch to eficom\n");
348 	}
349 }
350 #endif
351 
352 static int
353 comc_init(int arg __unused)
354 {
355 
356 	if (comc_setup())
357 		return (CMD_OK);
358 
359 	eficom.c_flags = 0;
360 	return (CMD_ERROR);
361 }
362 
363 static void
364 comc_putchar(int c)
365 {
366 	int wait;
367 	EFI_STATUS status;
368 	UINTN bufsz = 1;
369 	char cb = c;
370 
371 	if (comc_port->sio == NULL)
372 		return;
373 
374 	for (wait = COMC_TXWAIT; wait > 0; wait--) {
375 		status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb);
376 		if (status != EFI_TIMEOUT)
377 			break;
378 	}
379 }
380 
381 static int
382 comc_getchar(void)
383 {
384 	EFI_STATUS status;
385 	UINTN bufsz = 1;
386 	char c;
387 
388 
389 	/*
390 	 * if this device is also used as ConIn, some firmwares
391 	 * fail to return all input via SIO protocol.
392 	 */
393 	if (comc_port->currdev == comc_port->condev) {
394 		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
395 			return (efi_console.c_in());
396 		return (-1);
397 	}
398 
399 	if (comc_port->sio == NULL)
400 		return (-1);
401 
402 	status = comc_port->sio->Read(comc_port->sio, &bufsz, &c);
403 	if (EFI_ERROR(status) || bufsz == 0)
404 		return (-1);
405 
406 	return (c);
407 }
408 
409 static int
410 comc_ischar(void)
411 {
412 	EFI_STATUS status;
413 	uint32_t control;
414 
415 	/*
416 	 * if this device is also used as ConIn, some firmwares
417 	 * fail to return all input via SIO protocol.
418 	 */
419 	if (comc_port->currdev == comc_port->condev) {
420 		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
421 			return (efi_console.c_ready());
422 		return (0);
423 	}
424 
425 	if (comc_port->sio == NULL)
426 		return (0);
427 
428 	status = comc_port->sio->GetControl(comc_port->sio, &control);
429 	if (EFI_ERROR(status))
430 		return (0);
431 
432 	return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
433 }
434 
435 static int
436 comc_parse_intval(const char *value, unsigned *valp)
437 {
438 	unsigned n;
439 	char *ep;
440 
441 	if (value == NULL || *value == '\0')
442 		return (CMD_ERROR);
443 
444 	errno = 0;
445 	n = strtoul(value, &ep, 10);
446 	if (errno != 0 || *ep != '\0')
447 		return (CMD_ERROR);
448 	*valp = n;
449 
450 	return (CMD_OK);
451 }
452 
453 static int
454 comc_port_set(struct env_var *ev, int flags, const void *value)
455 {
456 	unsigned port;
457 	SERIAL_IO_INTERFACE *sio;
458 	EFI_HANDLE handle;
459 	EFI_STATUS status;
460 
461 	if (value == NULL)
462 		return (CMD_ERROR);
463 
464 	if (comc_parse_intval(value, &port) != CMD_OK)
465 		return (CMD_ERROR);
466 
467 	handle = efi_serial_get_handle(port, NULL);
468 	if (handle == NULL) {
469 		printf("no handle\n");
470 		return (CMD_ERROR);
471 	}
472 
473 	status = BS->OpenProtocol(handle, &serial,
474 	    (void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
475 
476 	if (EFI_ERROR(status)) {
477 		printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status));
478 		return (CMD_ERROR);
479 	}
480 
481 	comc_port->currdev = handle;
482 	comc_port->ioaddr = port;
483 	comc_port->sio = sio;
484 
485 	(void) comc_setup();
486 
487 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
488 	return (CMD_OK);
489 }
490 
491 static int
492 comc_speed_set(struct env_var *ev, int flags, const void *value)
493 {
494 	unsigned speed;
495 
496 	if (value == NULL)
497 		return (CMD_ERROR);
498 
499 	if (comc_parse_intval(value, &speed) != CMD_OK)
500 		return (CMD_ERROR);
501 
502 	comc_port->newbaudrate = speed;
503 	if (comc_setup())
504 		env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
505 
506 	return (CMD_OK);
507 }
508 
509 /*
510  * In case of error, we also reset ACTIVE flags, so the console
511  * framefork will try alternate consoles.
512  */
513 static bool
514 comc_setup(void)
515 {
516 	EFI_STATUS status;
517 	char *ev;
518 
519 	/* port is not usable */
520 	if (comc_port->sio == NULL)
521 		return (false);
522 
523 	if (comc_port->sio->Reset != NULL) {
524 		status = comc_port->sio->Reset(comc_port->sio);
525 		if (EFI_ERROR(status))
526 			return (false);
527 	}
528 
529 	/*
530 	 * Avoid setting the baud rate on Hyper-V. Also, only set the baud rate
531 	 * if the baud rate has changed from the default. And pass in '0' or
532 	 * DefaultFoo when we're not changing those values. Some EFI
533 	 * implementations get cranky when you set things to the values reported
534 	 * back even when they are unchanged.
535 	 */
536 	if (comc_port->sio->SetAttributes != NULL &&
537 	    comc_port->newbaudrate != comc_port->baudrate) {
538 		ev = getenv("smbios.bios.version");
539 		if (ev != NULL && strncmp(ev, "Hyper-V", 7) != 0) {
540 			status = comc_port->sio->SetAttributes(comc_port->sio,
541 			    comc_port->newbaudrate, 0, 0, DefaultParity, 0,
542 			    DefaultStopBits);
543 			if (EFI_ERROR(status))
544 				return (false);
545 			comc_port->baudrate = comc_port->newbaudrate;
546 		}
547 	}
548 
549 #ifdef EFI_FORCE_RTS
550 	if (comc_port->sio->GetControl != NULL && comc_port->sio->SetControl != NULL) {
551 		UINT32 control;
552 
553 		status = comc_port->sio->GetControl(comc_port->sio, &control);
554 		if (EFI_ERROR(status))
555 			return (false);
556 		control |= EFI_SERIAL_REQUEST_TO_SEND;
557 		(void) comc_port->sio->SetControl(comc_port->sio, control);
558 	}
559 #endif
560 	/* Mark this port usable. */
561 	eficom.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
562 	return (true);
563 }
564