Index: stand/efi/loader/arch/arm/Makefile.inc =================================================================== --- stand/efi/loader/arch/arm/Makefile.inc +++ stand/efi/loader/arch/arm/Makefile.inc @@ -1,6 +1,7 @@ # $FreeBSD$ SRCS+= exec.c \ + efiserialio.c \ start.S HAVE_FDT=yes Index: stand/efi/loader/arch/arm64/Makefile.inc =================================================================== --- stand/efi/loader/arch/arm64/Makefile.inc +++ 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 Index: stand/efi/loader/conf.c =================================================================== --- stand/efi/loader/conf.c +++ stand/efi/loader/conf.c @@ -73,16 +73,16 @@ }; 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 Index: stand/efi/loader/efiserialio.c =================================================================== --- /dev/null +++ stand/efi/loader/efiserialio.c @@ -0,0 +1,564 @@ +/*- + * 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 +#include + +#include "loader_efi.h" + +static EFI_GUID serial = SERIAL_IO_PROTOCOL; + +/* + * On x86 we do support setting efi_com_port by traditional ioaddress. + */ +#if defined(__amd64__) || defined(__i386__) +#define COM1 0x3F8 +#define COM2 0x2F8 +#define COM3 0x3E8 +#define COM4 0x2E8 +#else +#define COM1 0 +#define COM2 1 +#define COM3 2 +#define COM4 3 +#endif + +typedef struct ioaddr_to_handle { + uint32_t ioaddr; /* used to match efi_com_port */ + int uid; /* internal port order */ + EFI_HANDLE handle; /* SIO handle */ +} ioaddr_to_uid_t; + +static ioaddr_to_uid_t ioaddr_array[] = { + { + .ioaddr = COM1, + .handle = NULL, + .uid = -1 + }, + { + .ioaddr = COM2, + .handle = NULL, + .uid = -1 + }, + { + .ioaddr = COM3, + .handle = NULL, + .uid = -1 + }, + { + .ioaddr = COM4, + .handle = NULL, + .uid = -1 + }, +}; + +#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 */ + SERIAL_IO_INTERFACE *sio; +}; + +static int efi_serial_get_uid(EFI_DEVICE_PATH *); +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; + +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 void +efi_serial_move_ioaddr(size_t idx) +{ + size_t i; + + for (i = nitems(ioaddr_array) - 1; i > idx; i--) { + ioaddr_array[i].handle = ioaddr_array[i - 1].handle; + ioaddr_array[i].uid = ioaddr_array[i - 1].uid; + } + ioaddr_array[i].handle = NULL; + ioaddr_array[i].uid = -1; +} + +static void +efi_serial_add_ioaddr(int uid, EFI_HANDLE handle) +{ + size_t i; + +restart: + for (i = 0; i < nitems(ioaddr_array); i++) { + if (ioaddr_array[i].handle == NULL) { + ioaddr_array[i].handle = handle; + ioaddr_array[i].uid = uid; + return; + } + if (ioaddr_array[i].uid < uid) + continue; + efi_serial_move_ioaddr(i); + goto restart; + } +} + +static EFI_STATUS +efi_serial_init(void) +{ + UINTN bufsz = 0; + EFI_STATUS status; + EFI_HANDLE *handles; + EFI_DEVICE_PATH *devpath; + unsigned i, nhandles; + int port, uid; + + /* + * get buffer size + */ + handles = NULL; + status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles); + if (status != EFI_BUFFER_TOO_SMALL) + return (status); + + if ((handles = malloc(bufsz)) == NULL) + return (EFI_OUT_OF_RESOURCES); + + /* + * get handle array + */ + status = BS->LocateHandle(ByProtocol, &serial, NULL, &bufsz, handles); + if (EFI_ERROR(status)) { + free(handles); + return (status); + } + nhandles = bufsz / sizeof(EFI_HANDLE); + + port = 0; + status = EFI_SUCCESS; + for (i = 0; i < nhandles; i++) { + devpath = efi_lookup_devpath(handles[i]); + if (devpath == NULL) { + status = EFI_OUT_OF_RESOURCES; + goto done; + } + uid = efi_serial_get_uid(devpath); + if (uid == -1) { + /* we have uart */ + ioaddr_array[i].handle = handles[i]; + } else { + efi_serial_add_ioaddr(uid, handles[i]); + } + /* efi_close_devpath(handle); */ + } +done: + free(handles); + return (status); +} + +/* + * Find serial device number from device path. + * Return -1 if not found. + */ +static int +efi_serial_get_uid(EFI_DEVICE_PATH *devpath) +{ + ACPI_HID_DEVICE_PATH *acpi; + CHAR16 *text; + + while (!IsDevicePathEnd(devpath)) { + if (DevicePathType(devpath) == ACPI_DEVICE_PATH && + DevicePathSubType(devpath) == ACPI_DP) { + + acpi = (ACPI_HID_DEVICE_PATH *)devpath; + if (acpi->HID == EISA_PNP_ID(PNP0501)) { + return (acpi->UID); + } + } + devpath = NextDevicePathNode(devpath); + } + return (-1); +} + +static EFI_HANDLE +efi_serial_get_handle(int port) +{ + size_t index; + + for (index = 0; index < nitems(ioaddr_array); index++) { + if (ioaddr_array[index].ioaddr == port) + return (ioaddr_array[index].handle); + } + + 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; + + /* are we already set up? */ + if (comc_port != NULL) + return; + + status = efi_serial_init(); + if (EFI_ERROR(status)) + return; + + comc_port = malloc(sizeof (struct serial)); + comc_port->baudrate = COMSPEED; + comc_port->ioaddr = COM1; /* 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; + + env = getenv("comconsole_port"); + if (comc_parse_intval(env, &val) == CMD_OK) + comc_port->ioaddr = val; + + if (env != NULL) + unsetenv("comconsole_port"); + snprintf(value, sizeof (value), "0x%x", comc_port->ioaddr); + env_setenv("comconsole_port", EV_VOLATILE, value, + comc_port_set, env_nounset); + + handle = efi_serial_get_handle(comc_port->ioaddr); + if (handle != NULL) { + status = BS->OpenProtocol(handle, &serial, + (void**)&comc_port->sio, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (EFI_ERROR(status)) + comc_port->sio = NULL; + } + + env = getenv("comconsole_speed"); + if (comc_parse_intval(env, &val) == CMD_OK) + comc_port->baudrate = val; + + if (env != NULL) + unsetenv("comconsole_speed"); + snprintf(value, sizeof (value), "%" PRIu64, comc_port->baudrate); + env_setenv("comconsole_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 (comc_port->sio == NULL || !comc_ischar()) + 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 (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_ioctl(struct console *cp __unused, int cmd __unused, void *data __unused) +{ + return (ENOTTY); +} + +COMMAND_SET(console, "console", "console info", comc_devinfo); +static int +comc_devinfo(int argc, char *argv[]) +{ + EFI_HANDLE handle; + EFI_DEVICE_PATH *dp; + CHAR16 *text; + + handle = efi_serial_get_handle(comc_port->ioaddr); + if (handle == NULL) { + printf("\tdevice is not present"); + return (CMD_ERROR); + } + + dp = efi_lookup_devpath(handle); + if (dp == NULL) + return (CMD_ERROR); + + text = efi_devpath_name(dp); + if (text == NULL) + return (CMD_ERROR); + + printf("\t%S", text); + efi_free_devpath_name(text); + /* efi_close_devpath(handle); */ + return (CMD_OK); +} + +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) +{ + EFI_HANDLE handle; + unsigned port; + + if (value == NULL) + return (CMD_ERROR); + + if (comc_parse_intval(value, &port) != CMD_OK) + return (CMD_ERROR); + + handle = efi_serial_get_handle(port); + if (handle != NULL) { + comc_port->ioaddr = port; + (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); +} + +#if 0 +static int +comc_cd_set(struct env_var *ev, int flags, const void *value) +{ + struct console *cp; + struct serial *sp; + + if (value == NULL) + return (CMD_ERROR); + + if ((cp = get_console(ev->ev_name)) == NULL) + return (CMD_ERROR); + + sp = cp->c_private; + if (strcmp(value, "true") == 0) + sp->ignore_cd = 1; + else if (strcmp(value, "false") == 0) + sp->ignore_cd = 0; + else + return (CMD_ERROR); + + (void) comc_setup(cp); + + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + + return (CMD_OK); +} + +static int +comc_rtsdtr_set(struct env_var *ev, int flags, const void *value) +{ + struct serial *sp = &comc_port; + + if (value == NULL) + return (CMD_ERROR); + + if (strcmp(value, "true") == 0) + sp->rtsdtr_off = 1; + else if (strcmp(value, "false") == 0) + sp->rtsdtr_off = 0; + else + return (CMD_ERROR); + + (void) comc_setup(sp); + + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + + return (CMD_OK); +} +#endif + +/* + * 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; + char intbuf[64]; + + /* 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); + sprintf(intbuf, "io:%d,br:%" PRIu64, comc_port->ioaddr, + comc_port->baudrate); + env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL); + + return (true); +} Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -723,6 +723,7 @@ } 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 &&