diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c index 1b096a963555..7102d834c34d 100644 --- a/stand/efi/libefi/efi_console.c +++ b/stand/efi/libefi/efi_console.c @@ -377,9 +377,22 @@ efi_cons_respond(void *s __unused, const void *buf __unused, { } +/* + * Set up conin/conout/coninex to make sure we have input ready. + */ static void efi_cons_probe(struct console *cp) { + EFI_STATUS status; + + conout = ST->ConOut; + conin = ST->ConIn; + + status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid, + (void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (status != EFI_SUCCESS) + coninex = NULL; + cp->c_flags |= C_PRESENTIN | C_PRESENTOUT; } @@ -889,15 +902,7 @@ efi_cons_init(int arg) if (conin != NULL) return (0); - conout = ST->ConOut; - conin = ST->ConIn; - conout->EnableCursor(conout, TRUE); - status = BS->OpenProtocol(ST->ConsoleInHandle, &simple_input_ex_guid, - (void **)&coninex, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (status != EFI_SUCCESS) - coninex = NULL; - if (efi_cons_update_mode()) return (0); diff --git a/stand/efi/loader/arch/arm/Makefile.inc b/stand/efi/loader/arch/arm/Makefile.inc index 74e6616e991b..284e517bfbb8 100644 --- a/stand/efi/loader/arch/arm/Makefile.inc +++ b/stand/efi/loader/arch/arm/Makefile.inc @@ -1,6 +1,7 @@ # $FreeBSD$ SRCS+= exec.c \ + efiserialio.c \ start.S HAVE_FDT=yes diff --git a/stand/efi/loader/arch/arm64/Makefile.inc b/stand/efi/loader/arch/arm64/Makefile.inc index a71bcc2e1a1f..9978d9c4ea9c 100644 --- a/stand/efi/loader/arch/arm64/Makefile.inc +++ b/stand/efi/loader/arch/arm64/Makefile.inc @@ -3,6 +3,7 @@ HAVE_FDT=yes SRCS+= exec.c \ + efiserialio.c \ start.S .PATH: ${BOOTSRC}/arm64/libarm64 diff --git a/stand/efi/loader/conf.c b/stand/efi/loader/conf.c index a5d2ad7c613f..217372939685 100644 --- a/stand/efi/loader/conf.c +++ b/stand/efi/loader/conf.c @@ -73,16 +73,16 @@ struct netif_driver *netif_drivers[] = { }; extern struct console efi_console; -#if defined(__amd64__) || defined(__i386__) extern struct console comconsole; +#if defined(__amd64__) || defined(__i386__) extern struct console nullconsole; extern struct console spinconsole; #endif struct console *consoles[] = { &efi_console, -#if defined(__amd64__) || defined(__i386__) &comconsole, +#if defined(__amd64__) || defined(__i386__) &nullconsole, &spinconsole, #endif diff --git a/stand/efi/loader/efiserialio.c b/stand/efi/loader/efiserialio.c new file mode 100644 index 000000000000..c881e23680d2 --- /dev/null +++ b/stand/efi/loader/efiserialio.c @@ -0,0 +1,518 @@ +/*- + * Copyright (c) 1998 Michael Smith (msmith@freebsd.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include "loader_efi.h" + +static EFI_GUID serial = SERIAL_IO_PROTOCOL; + +#define COMC_TXWAIT 0x40000 /* transmit timeout */ + +#ifndef COMSPEED +#define COMSPEED 9600 +#endif + +#define PNP0501 0x501 /* 16550A-compatible COM port */ + +struct serial { + uint64_t baudrate; + uint8_t databits; + EFI_PARITY_TYPE parity; + EFI_STOP_BITS_TYPE stopbits; + uint8_t ignore_cd; /* boolean */ + uint8_t rtsdtr_off; /* boolean */ + int ioaddr; /* index in handles array */ + EFI_HANDLE currdev; /* current serial device */ + EFI_HANDLE condev; /* EFI Console device */ + SERIAL_IO_INTERFACE *sio; +}; + +static void comc_probe(struct console *); +static int comc_init(int); +static void comc_putchar(int); +static int comc_getchar(void); +static int comc_ischar(void); +static bool comc_setup(void); +static int comc_parse_intval(const char *, unsigned *); +static int comc_port_set(struct env_var *, int, const void *); +static int comc_speed_set(struct env_var *, int, const void *); + +static struct serial *comc_port; +extern struct console efi_console; + +struct console comconsole = { + .c_name = "comconsole", + .c_desc = "serial port", + .c_flags = 0, + .c_probe = comc_probe, + .c_init = comc_init, + .c_out = comc_putchar, + .c_in = comc_getchar, + .c_ready = comc_ischar, +}; + +static EFI_STATUS +efi_serial_init(EFI_HANDLE **handlep, int *nhandles) +{ + UINTN bufsz = 0; + EFI_STATUS status; + EFI_HANDLE *handles; + + /* + * get buffer size + */ + *nhandles = 0; + handles = NULL; + status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles); + if (status != EFI_BUFFER_TOO_SMALL) + return (status); + + if ((handles = malloc(bufsz)) == NULL) + return (ENOMEM); + + *nhandles = (int)(bufsz / sizeof (EFI_HANDLE)); + /* + * get handle array + */ + status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles); + if (EFI_ERROR(status)) { + free(handles); + *nhandles = 0; + } else + *handlep = handles; + return (status); +} + +/* + * Find serial device number from device path. + * Return -1 if not found. + */ +static int +efi_serial_get_index(EFI_DEVICE_PATH *devpath, int idx) +{ + ACPI_HID_DEVICE_PATH *acpi; + CHAR16 *text; + + while (!IsDevicePathEnd(devpath)) { + if (DevicePathType(devpath) == MESSAGING_DEVICE_PATH && + DevicePathSubType(devpath) == MSG_UART_DP) + return (idx); + + if (DevicePathType(devpath) == ACPI_DEVICE_PATH && + (DevicePathSubType(devpath) == ACPI_DP || + DevicePathSubType(devpath) == ACPI_EXTENDED_DP)) { + + acpi = (ACPI_HID_DEVICE_PATH *)devpath; + if (acpi->HID == EISA_PNP_ID(PNP0501)) { + return (acpi->UID); + } + } + + devpath = NextDevicePathNode(devpath); + } + return (-1); +} + +/* + * The order of handles from LocateHandle() is not known, we need to + * iterate handles, pick device path for handle, and check the device + * number. + */ +static EFI_HANDLE +efi_serial_get_handle(int port, EFI_HANDLE condev) +{ + EFI_STATUS status; + EFI_HANDLE *handles, handle; + EFI_DEVICE_PATH *devpath; + int index, nhandles; + + if (port == -1) + return (NULL); + + handles = NULL; + nhandles = 0; + status = efi_serial_init(&handles, &nhandles); + if (EFI_ERROR(status)) + return (NULL); + + /* + * We have console handle, set ioaddr for it. + */ + if (condev != NULL) { + for (index = 0; index < nhandles; index++) { + if (condev == handles[index]) { + devpath = efi_lookup_devpath(condev); + comc_port->ioaddr = + efi_serial_get_index(devpath, index); + efi_close_devpath(condev); + free(handles); + return (condev); + } + } + } + + handle = NULL; + for (index = 0; handle == NULL && index < nhandles; index++) { + devpath = efi_lookup_devpath(handles[index]); + if (port == efi_serial_get_index(devpath, index)) + handle = (handles[index]); + efi_close_devpath(handles[index]); + } + + /* + * In case we did fail to identify the device by path, use port as + * array index. Note, we did check port == -1 above. + */ + if (port < nhandles && handle == NULL) + handle = handles[port]; + + free(handles); + return (handle); +} + +static EFI_HANDLE +comc_get_con_serial_handle(const char *name) +{ + EFI_HANDLE handle; + EFI_DEVICE_PATH *node; + EFI_STATUS status; + char *buf, *ep; + size_t sz; + + buf = NULL; + sz = 0; + status = efi_global_getenv(name, buf, &sz); + if (status == EFI_BUFFER_TOO_SMALL) { + buf = malloc(sz); + if (buf != NULL) + status = efi_global_getenv(name, buf, &sz); + } + if (status != EFI_SUCCESS) { + free(buf); + return (NULL); + } + + ep = buf + sz; + node = (EFI_DEVICE_PATH *)buf; + while ((char *)node < ep) { + status = BS->LocateDevicePath(&serial, &node, &handle); + if (status == EFI_SUCCESS) { + free(buf); + return (handle); + } + if (IsDevicePathEndType(node) && + DevicePathSubType(node) == + END_INSTANCE_DEVICE_PATH_SUBTYPE) { + /* + * Start of next device path in list. + */ + node = NextDevicePathNode(node); + continue; + } + if (IsDevicePathEnd(node)) + break; + } + free(buf); + return (NULL); +} + +static void +comc_probe(struct console *sc) +{ + EFI_STATUS status; + EFI_HANDLE handle; + char name[20]; + char value[20]; + unsigned val; + char *env, *buf, *ep; + size_t sz; + + if (comc_port == NULL) { + comc_port = malloc(sizeof (struct serial)); + if (comc_port == NULL) + return; + } + comc_port->baudrate = COMSPEED; + comc_port->ioaddr = 0; /* default port */ + comc_port->databits = 8; /* 8,n,1 */ + comc_port->parity = NoParity; /* 8,n,1 */ + comc_port->stopbits = OneStopBit; /* 8,n,1 */ + comc_port->ignore_cd = 1; /* ignore cd */ + comc_port->rtsdtr_off = 0; /* rts-dtr is on */ + comc_port->sio = NULL; + + handle = NULL; + env = getenv("efi_com_port"); + if (comc_parse_intval(env, &val) == CMD_OK) { + comc_port->ioaddr = val; + } else { + /* + * efi_com_port is not set, we need to select default. + * First, we consult ConOut variable to see if + * we have serial port redirection. If not, we just + * pick first device. + */ + handle = comc_get_con_serial_handle("ConOut"); + comc_port->condev = handle; + } + + handle = efi_serial_get_handle(comc_port->ioaddr, handle); + if (handle != NULL) { + comc_port->currdev = handle; + status = BS->OpenProtocol(handle, &serial, + (void**)&comc_port->sio, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (EFI_ERROR(status)) + comc_port->sio = NULL; + } + + if (env != NULL) + unsetenv("efi_com_port"); + snprintf(value, sizeof (value), "%u", comc_port->ioaddr); + env_setenv("efi_com_port", EV_VOLATILE, value, + comc_port_set, env_nounset); + + env = getenv("efi_com_speed"); + if (comc_parse_intval(env, &val) == CMD_OK) + comc_port->baudrate = val; + + if (env != NULL) + unsetenv("efi_com_speed"); + snprintf(value, sizeof (value), "%ju", (uintmax_t)comc_port->baudrate); + env_setenv("efi_com_speed", EV_VOLATILE, value, + comc_speed_set, env_nounset); + + comconsole.c_flags = 0; + if (comc_setup()) + sc->c_flags = C_PRESENTIN | C_PRESENTOUT; +} + +static int +comc_init(int arg __unused) +{ + + if (comc_setup()) + return (CMD_OK); + + comconsole.c_flags = 0; + return (CMD_ERROR); +} + +static void +comc_putchar(int c) +{ + int wait; + EFI_STATUS status; + UINTN bufsz = 1; + char cb = c; + + if (comc_port->sio == NULL) + return; + + for (wait = COMC_TXWAIT; wait > 0; wait--) { + status = comc_port->sio->Write(comc_port->sio, &bufsz, &cb); + if (status != EFI_TIMEOUT) + break; + } +} + +static int +comc_getchar(void) +{ + EFI_STATUS status; + UINTN bufsz = 1; + char c; + + + /* + * if this device is also used as ConIn, some firmwares + * fail to return all input via SIO protocol. + */ + if (comc_port->currdev == comc_port->condev) { + if ((efi_console.c_flags & C_ACTIVEIN) == 0) + return (efi_console.c_in()); + return (-1); + } + + if (comc_port->sio == NULL) + return (-1); + + status = comc_port->sio->Read(comc_port->sio, &bufsz, &c); + if (EFI_ERROR(status) || bufsz == 0) + return (-1); + + return (c); +} + +static int +comc_ischar(void) +{ + EFI_STATUS status; + uint32_t control; + + /* + * if this device is also used as ConIn, some firmwares + * fail to return all input via SIO protocol. + */ + if (comc_port->currdev == comc_port->condev) { + if ((efi_console.c_flags & C_ACTIVEIN) == 0) + return (efi_console.c_ready()); + return (0); + } + + if (comc_port->sio == NULL) + return (0); + + status = comc_port->sio->GetControl(comc_port->sio, &control); + if (EFI_ERROR(status)) + return (0); + + return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY)); +} + +static int +comc_parse_intval(const char *value, unsigned *valp) +{ + unsigned n; + char *ep; + + if (value == NULL || *value == '\0') + return (CMD_ERROR); + + errno = 0; + n = strtoul(value, &ep, 10); + if (errno != 0 || *ep != '\0') + return (CMD_ERROR); + *valp = n; + + return (CMD_OK); +} + +static int +comc_port_set(struct env_var *ev, int flags, const void *value) +{ + unsigned port; + SERIAL_IO_INTERFACE *sio; + EFI_HANDLE handle; + EFI_STATUS status; + + if (value == NULL) + return (CMD_ERROR); + + if (comc_parse_intval(value, &port) != CMD_OK) + return (CMD_ERROR); + + handle = efi_serial_get_handle(port, NULL); + if (handle == NULL) { + printf("no handle\n"); + return (CMD_ERROR); + } + + status = BS->OpenProtocol(handle, &serial, + (void**)&sio, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (EFI_ERROR(status)) { + printf("OpenProtocol: %lu\n", EFI_ERROR_CODE(status)); + return (CMD_ERROR); + } + + comc_port->currdev = handle; + comc_port->ioaddr = port; + comc_port->sio = sio; + + (void) comc_setup(); + + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + return (CMD_OK); +} + +static int +comc_speed_set(struct env_var *ev, int flags, const void *value) +{ + unsigned speed; + + if (value == NULL) + return (CMD_ERROR); + + if (comc_parse_intval(value, &speed) != CMD_OK) + return (CMD_ERROR); + + comc_port->baudrate = speed; + (void) comc_setup(); + + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + + return (CMD_OK); +} + +/* + * In case of error, we also reset ACTIVE flags, so the console + * framefork will try alternate consoles. + */ +static bool +comc_setup(void) +{ + EFI_STATUS status; + UINT32 control; + + /* port is not usable */ + if (comc_port->sio == NULL) + return (false); + + status = comc_port->sio->Reset(comc_port->sio); + if (EFI_ERROR(status)) + return (false); + + status = comc_port->sio->SetAttributes(comc_port->sio, + comc_port->baudrate, 0, 0, comc_port->parity, + comc_port->databits, comc_port->stopbits); + if (EFI_ERROR(status)) + return (false); + + status = comc_port->sio->GetControl(comc_port->sio, &control); + if (EFI_ERROR(status)) + return (false); + if (comc_port->rtsdtr_off) { + control &= ~(EFI_SERIAL_REQUEST_TO_SEND | + EFI_SERIAL_DATA_TERMINAL_READY); + } else { + control |= EFI_SERIAL_REQUEST_TO_SEND; + } + (void) comc_port->sio->SetControl(comc_port->sio, control); + /* Mark this port usable. */ + comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT); + return (true); +} diff --git a/stand/efi/loader/main.c b/stand/efi/loader/main.c index 214b4bb7e5f2..14c320a09811 100644 --- a/stand/efi/loader/main.c +++ b/stand/efi/loader/main.c @@ -722,7 +722,8 @@ parse_uefi_con_out(void) while ((char *)node < ep) { pci_pending = false; if (DevicePathType(node) == ACPI_DEVICE_PATH && - DevicePathSubType(node) == ACPI_DP) { + (DevicePathSubType(node) == ACPI_DP || + DevicePathSubType(node) == ACPI_EXTENDED_DP)) { /* Check for Serial node */ acpi = (void *)node; if (EISA_ID_TO_NUM(acpi->HID) == 0x501) { @@ -731,7 +732,7 @@ parse_uefi_con_out(void) } } else if (DevicePathType(node) == MESSAGING_DEVICE_PATH && DevicePathSubType(node) == MSG_UART_DP) { - + com_seen = ++seen; uart = (void *)node; setenv_int("efi_com_speed", uart->BaudRate); } else if (DevicePathType(node) == ACPI_DEVICE_PATH && @@ -897,6 +898,11 @@ main(int argc, CHAR16 *argv[]) * changes to take effect, regardless of where they come from. */ setenv("console", "efi", 1); + uhowto = parse_uefi_con_out(); +#if defined(__aarch64__) || defined(__arm__) + if ((uhowto & RB_SERIAL) != 0) + setenv("console", "comconsole", 1); +#endif cons_probe(); /* Init the time source */ @@ -930,7 +936,6 @@ main(int argc, CHAR16 *argv[]) if (!has_kbd && (howto & RB_PROBE)) howto |= RB_SERIAL | RB_MULTIPLE; howto &= ~RB_PROBE; - uhowto = parse_uefi_con_out(); /* * Read additional environment variables from the boot device's