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