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#include "loader_efi.h"
38
39static EFI_GUID serial = SERIAL_IO_PROTOCOL;
40
41#define	COMC_TXWAIT	0x40000		/* transmit timeout */
42
43#ifndef	COMSPEED
44#define	COMSPEED	9600
45#endif
46
47#define	PNP0501		0x501		/* 16550A-compatible COM port */
48
49struct serial {
50	uint64_t	baudrate;
51	uint8_t		databits;
52	EFI_PARITY_TYPE	parity;
53	EFI_STOP_BITS_TYPE stopbits;
54	uint8_t		ignore_cd;	/* boolean */
55	uint8_t		rtsdtr_off;	/* boolean */
56	int		ioaddr;		/* index in handles array */
57	EFI_HANDLE	currdev;	/* current serial device */
58	EFI_HANDLE	condev;		/* EFI Console device */
59	SERIAL_IO_INTERFACE *sio;
60};
61
62static void	comc_probe(struct console *);
63static int	comc_init(int);
64static void	comc_putchar(int);
65static int	comc_getchar(void);
66static int	comc_ischar(void);
67static bool	comc_setup(void);
68static int	comc_parse_intval(const char *, unsigned *);
69static int	comc_port_set(struct env_var *, int, const void *);
70static int	comc_speed_set(struct env_var *, int, const void *);
71
72static struct serial	*comc_port;
73extern struct console efi_console;
74
75struct console comconsole = {
76	.c_name = "comconsole",
77	.c_desc = "serial port",
78	.c_flags = 0,
79	.c_probe = comc_probe,
80	.c_init = comc_init,
81	.c_out = comc_putchar,
82	.c_in = comc_getchar,
83	.c_ready = comc_ischar,
84};
85
86static EFI_STATUS
87efi_serial_init(EFI_HANDLE **handlep, int *nhandles)
88{
89	UINTN bufsz = 0;
90	EFI_STATUS status;
91	EFI_HANDLE *handles;
92
93	/*
94	 * get buffer size
95	 */
96	*nhandles = 0;
97	handles = NULL;
98	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
99	if (status != EFI_BUFFER_TOO_SMALL)
100		return (status);
101
102	if ((handles = malloc(bufsz)) == NULL)
103		return (ENOMEM);
104
105	*nhandles = (int)(bufsz / sizeof (EFI_HANDLE));
106	/*
107	 * get handle array
108	 */
109	status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles);
110	if (EFI_ERROR(status)) {
111		free(handles);
112		*nhandles = 0;
113	} else
114		*handlep = handles;
115	return (status);
116}
117
118/*
119 * Find serial device number from device path.
120 * Return -1 if not found.
121 */
122static int
123efi_serial_get_index(EFI_DEVICE_PATH *devpath, int idx)
124{
125	ACPI_HID_DEVICE_PATH  *acpi;
126	CHAR16 *text;
127
128	while (!IsDevicePathEnd(devpath)) {
129		if (DevicePathType(devpath) == MESSAGING_DEVICE_PATH &&
130		    DevicePathSubType(devpath) == MSG_UART_DP)
131			return (idx);
132
133		if (DevicePathType(devpath) == ACPI_DEVICE_PATH &&
134		    (DevicePathSubType(devpath) == ACPI_DP ||
135		    DevicePathSubType(devpath) == ACPI_EXTENDED_DP)) {
136
137			acpi = (ACPI_HID_DEVICE_PATH *)devpath;
138			if (acpi->HID == EISA_PNP_ID(PNP0501)) {
139				return (acpi->UID);
140			}
141		}
142
143		devpath = NextDevicePathNode(devpath);
144	}
145	return (-1);
146}
147
148/*
149 * The order of handles from LocateHandle() is not known, we need to
150 * iterate handles, pick device path for handle, and check the device
151 * number.
152 */
153static EFI_HANDLE
154efi_serial_get_handle(int port, EFI_HANDLE condev)
155{
156	EFI_STATUS status;
157	EFI_HANDLE *handles, handle;
158	EFI_DEVICE_PATH *devpath;
159	int index, nhandles;
160
161	if (port == -1)
162		return (NULL);
163
164	handles = NULL;
165	nhandles = 0;
166	status = efi_serial_init(&handles, &nhandles);
167	if (EFI_ERROR(status))
168		return (NULL);
169
170	/*
171	 * We have console handle, set ioaddr for it.
172	 */
173	if (condev != NULL) {
174		for (index = 0; index < nhandles; index++) {
175			if (condev == handles[index]) {
176				devpath = efi_lookup_devpath(condev);
177				comc_port->ioaddr =
178				    efi_serial_get_index(devpath, index);
179				efi_close_devpath(condev);
180				free(handles);
181				return (condev);
182			}
183		}
184	}
185
186	handle = NULL;
187	for (index = 0; handle == NULL && index < nhandles; index++) {
188		devpath = efi_lookup_devpath(handles[index]);
189		if (port == efi_serial_get_index(devpath, index))
190			handle = (handles[index]);
191		efi_close_devpath(handles[index]);
192	}
193
194	/*
195	 * In case we did fail to identify the device by path, use port as
196	 * array index. Note, we did check port == -1 above.
197	 */
198	if (port < nhandles && handle == NULL)
199		handle = handles[port];
200
201	free(handles);
202	return (handle);
203}
204
205static EFI_HANDLE
206comc_get_con_serial_handle(const char *name)
207{
208	EFI_HANDLE handle;
209	EFI_DEVICE_PATH *node;
210	EFI_STATUS status;
211	char *buf, *ep;
212	size_t sz;
213
214	buf = NULL;
215	sz = 0;
216	status = efi_global_getenv(name, buf, &sz);
217	if (status == EFI_BUFFER_TOO_SMALL) {
218		buf = malloc(sz);
219		if (buf == NULL)
220			return (NULL);
221		status = efi_global_getenv(name, buf, &sz);
222	}
223	if (status != EFI_SUCCESS) {
224		free(buf);
225		return (NULL);
226	}
227
228	ep = buf + sz;
229	node = (EFI_DEVICE_PATH *)buf;
230	while ((char *)node < ep) {
231		status = BS->LocateDevicePath(&serial, &node, &handle);
232		if (status == EFI_SUCCESS) {
233			free(buf);
234			return (handle);
235		}
236
237		/* Sanity check the node before moving to the next node. */
238		if (DevicePathNodeLength(node) < sizeof(*node))
239			break;
240
241		/* Start of next device path in list. */
242		node = NextDevicePathNode(node);
243	}
244	free(buf);
245	return (NULL);
246}
247
248static void
249comc_probe(struct console *sc)
250{
251	EFI_STATUS status;
252	EFI_HANDLE handle;
253	char name[20];
254	char value[20];
255	unsigned val;
256	char *env, *buf, *ep;
257	size_t sz;
258
259	if (comc_port == NULL) {
260		comc_port = malloc(sizeof (struct serial));
261		if (comc_port == NULL)
262			return;
263	}
264	comc_port->baudrate = COMSPEED;
265	comc_port->ioaddr = 0;			/* default port */
266	comc_port->databits = 8;		/* 8,n,1 */
267	comc_port->parity = NoParity;		/* 8,n,1 */
268	comc_port->stopbits = OneStopBit;	/* 8,n,1 */
269	comc_port->ignore_cd = 1;		/* ignore cd */
270	comc_port->rtsdtr_off = 0;		/* rts-dtr is on */
271	comc_port->sio = NULL;
272
273	handle = NULL;
274	env = getenv("efi_com_port");
275	if (comc_parse_intval(env, &val) == CMD_OK) {
276		comc_port->ioaddr = val;
277	} else {
278		/*
279		 * efi_com_port is not set, we need to select default.
280		 * First, we consult ConOut variable to see if
281		 * we have serial port redirection. If not, we just
282		 * pick first device.
283		 */
284		handle = comc_get_con_serial_handle("ConOut");
285		comc_port->condev = handle;
286	}
287
288	handle = efi_serial_get_handle(comc_port->ioaddr, handle);
289	if (handle != NULL) {
290		comc_port->currdev = handle;
291		status = BS->OpenProtocol(handle, &serial,
292		    (void**)&comc_port->sio, IH, NULL,
293		    EFI_OPEN_PROTOCOL_GET_PROTOCOL);
294
295		if (EFI_ERROR(status))
296			comc_port->sio = NULL;
297	}
298
299	if (env != NULL)
300		unsetenv("efi_com_port");
301	snprintf(value, sizeof (value), "%u", comc_port->ioaddr);
302	env_setenv("efi_com_port", EV_VOLATILE, value,
303	    comc_port_set, env_nounset);
304
305	env = getenv("efi_com_speed");
306	if (comc_parse_intval(env, &val) == CMD_OK)
307		comc_port->baudrate = val;
308
309	if (env != NULL)
310		unsetenv("efi_com_speed");
311	snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate);
312	env_setenv("efi_com_speed", EV_VOLATILE, value,
313	    comc_speed_set, env_nounset);
314
315	comconsole.c_flags = 0;
316	if (comc_setup())
317		sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
318}
319
320static int
321comc_init(int arg __unused)
322{
323
324	if (comc_setup())
325		return (CMD_OK);
326
327	comconsole.c_flags = 0;
328	return (CMD_ERROR);
329}
330
331static void
332comc_putchar(int c)
333{
334	int wait;
335	EFI_STATUS status;
336	UINTN bufsz = 1;
337	char cb = c;
338
339	if (comc_port->sio == NULL)
340		return;
341
342	for (wait = COMC_TXWAIT; wait > 0; wait--) {
343		status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb);
344		if (status != EFI_TIMEOUT)
345			break;
346	}
347}
348
349static int
350comc_getchar(void)
351{
352	EFI_STATUS status;
353	UINTN bufsz = 1;
354	char c;
355
356
357	/*
358	 * if this device is also used as ConIn, some firmwares
359	 * fail to return all input via SIO protocol.
360	 */
361	if (comc_port->currdev == comc_port->condev) {
362		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
363			return (efi_console.c_in());
364		return (-1);
365	}
366
367	if (comc_port->sio == NULL)
368		return (-1);
369
370	status = comc_port->sio->Read(comc_port->sio, &bufsz, &c);
371	if (EFI_ERROR(status) || bufsz == 0)
372		return (-1);
373
374	return (c);
375}
376
377static int
378comc_ischar(void)
379{
380	EFI_STATUS status;
381	uint32_t control;
382
383	/*
384	 * if this device is also used as ConIn, some firmwares
385	 * fail to return all input via SIO protocol.
386	 */
387	if (comc_port->currdev == comc_port->condev) {
388		if ((efi_console.c_flags & C_ACTIVEIN) == 0)
389			return (efi_console.c_ready());
390		return (0);
391	}
392
393	if (comc_port->sio == NULL)
394		return (0);
395
396	status = comc_port->sio->GetControl(comc_port->sio, &control);
397	if (EFI_ERROR(status))
398		return (0);
399
400	return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
401}
402
403static int
404comc_parse_intval(const char *value, unsigned *valp)
405{
406	unsigned n;
407	char *ep;
408
409	if (value == NULL || *value == '\0')
410		return (CMD_ERROR);
411
412	errno = 0;
413	n = strtoul(value, &ep, 10);
414	if (errno != 0 || *ep != '\0')
415		return (CMD_ERROR);
416	*valp = n;
417
418	return (CMD_OK);
419}
420
421static int
422comc_port_set(struct env_var *ev, int flags, const void *value)
423{
424	unsigned port;
425	SERIAL_IO_INTERFACE *sio;
426	EFI_HANDLE handle;
427	EFI_STATUS status;
428
429	if (value == NULL)
430		return (CMD_ERROR);
431
432	if (comc_parse_intval(value, &port) != CMD_OK)
433		return (CMD_ERROR);
434
435	handle = efi_serial_get_handle(port, NULL);
436	if (handle == NULL) {
437		printf("no handle\n");
438		return (CMD_ERROR);
439	}
440
441	status = BS->OpenProtocol(handle, &serial,
442	    (void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
443
444	if (EFI_ERROR(status)) {
445		printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status));
446		return (CMD_ERROR);
447	}
448
449	comc_port->currdev = handle;
450	comc_port->ioaddr = port;
451	comc_port->sio = sio;
452
453	(void) comc_setup();
454
455	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
456	return (CMD_OK);
457}
458
459static int
460comc_speed_set(struct env_var *ev, int flags, const void *value)
461{
462	unsigned speed;
463
464	if (value == NULL)
465		return (CMD_ERROR);
466
467	if (comc_parse_intval(value, &speed) != CMD_OK)
468		return (CMD_ERROR);
469
470	comc_port->baudrate = speed;
471	(void) comc_setup();
472
473	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
474
475	return (CMD_OK);
476}
477
478/*
479 * In case of error, we also reset ACTIVE flags, so the console
480 * framefork will try alternate consoles.
481 */
482static bool
483comc_setup(void)
484{
485	EFI_STATUS status;
486	UINT32 control;
487
488	/* port is not usable */
489	if (comc_port->sio == NULL)
490		return (false);
491
492	status = comc_port->sio->Reset(comc_port->sio);
493	if (EFI_ERROR(status))
494		return (false);
495
496	status = comc_port->sio->SetAttributes(comc_port->sio,
497	    comc_port->baudrate, 0, 0, comc_port->parity,
498	    comc_port->databits, comc_port->stopbits);
499	if (EFI_ERROR(status))
500		return (false);
501
502	status = comc_port->sio->GetControl(comc_port->sio, &control);
503	if (EFI_ERROR(status))
504		return (false);
505	if (comc_port->rtsdtr_off) {
506		control &= ~(EFI_SERIAL_REQUEST_TO_SEND |
507		    EFI_SERIAL_DATA_TERMINAL_READY);
508	} else {
509		control |= EFI_SERIAL_REQUEST_TO_SEND;
510	}
511	(void) comc_port->sio->SetControl(comc_port->sio, control);
512	/* Mark this port usable. */
513	comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
514	return (true);
515}
516