aboutsummaryrefslogtreecommitdiff
path: root/stand/efi/libefi/eficom.c
diff options
context:
space:
mode:
Diffstat (limited to 'stand/efi/libefi/eficom.c')
-rw-r--r--stand/efi/libefi/eficom.c601
1 files changed, 601 insertions, 0 deletions
diff --git a/stand/efi/libefi/eficom.c b/stand/efi/libefi/eficom.c
new file mode 100644
index 000000000000..d5f3f07e083f
--- /dev/null
+++ b/stand/efi/libefi/eficom.c
@@ -0,0 +1,601 @@
+/*-
+ * 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 <stand.h>
+#include <sys/errno.h>
+#include <bootstrap.h>
+#include <stdbool.h>
+
+#include <efi.h>
+#include <efilib.h>
+
+static EFI_GUID serial = SERIAL_IO_PROTOCOL;
+
+#define COMC_TXWAIT 0x40000 /* transmit timeout */
+
+#define PNP0501 0x501 /* 16550A-compatible COM port */
+
+struct serial {
+ uint64_t newbaudrate;
+ uint64_t baudrate;
+ uint32_t timeout;
+ uint32_t receivefifodepth;
+ uint32_t databits;
+ EFI_PARITY_TYPE parity;
+ EFI_STOP_BITS_TYPE stopbits;
+ 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 eficom = {
+ .c_name = "eficom",
+ .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,
+};
+
+#if defined(__aarch64__) && __FreeBSD_version < 1500000
+static void comc_probe_compat(struct console *);
+struct console comconsole = {
+ .c_name = "comconsole",
+ .c_desc = "serial port",
+ .c_flags = 0,
+ .c_probe = comc_probe_compat,
+ .c_init = comc_init,
+ .c_out = comc_putchar,
+ .c_in = comc_getchar,
+ .c_ready = comc_ischar,
+};
+#endif
+
+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)
+ return (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);
+ }
+
+ /* Sanity check the node before moving to the next node. */
+ if (DevicePathNodeLength(node) < sizeof(*node))
+ break;
+
+ /* Start of next device path in list. */
+ node = NextDevicePathNode(node);
+ }
+ free(buf);
+ return (NULL);
+}
+
+/*
+ * Called from cons_probe() to see if this device is available.
+ * Return immediately on x86, except for hyperv, since it interferes with
+ * common configurations otherwise (yes, this is just firewalling the bug).
+ */
+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;
+
+#ifdef __amd64__
+ /*
+ * This driver tickles issues on a number of different firmware loads.
+ * It is only required for HyperV, and is only known to work on HyperV,
+ * so only allow it on HyperV.
+ */
+ env = getenv("smbios.bios.version");
+ if (env == NULL || strncmp(env, "Hyper-V", 7) != 0) {
+ return;
+ }
+#endif
+
+ if (comc_port == NULL) {
+ comc_port = calloc(1, sizeof (struct serial));
+ if (comc_port == NULL)
+ return;
+ }
+
+ /* Use defaults from firmware */
+ comc_port->databits = 8;
+ comc_port->parity = DefaultParity;
+ comc_port->stopbits = DefaultStopBits;
+
+ 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;
+ } else {
+ comc_port->newbaudrate =
+ comc_port->baudrate = comc_port->sio->Mode->BaudRate;
+ comc_port->timeout = comc_port->sio->Mode->Timeout;
+ comc_port->receivefifodepth =
+ comc_port->sio->Mode->ReceiveFifoDepth;
+ comc_port->databits = comc_port->sio->Mode->DataBits;
+ comc_port->parity = comc_port->sio->Mode->Parity;
+ comc_port->stopbits = comc_port->sio->Mode->StopBits;
+ }
+ }
+
+ /*
+ * If there's no sio, then the device isn't there, so just return since
+ * the present flags aren't yet set.
+ */
+ if (comc_port->sio == NULL) {
+ free(comc_port);
+ comc_port = NULL;
+ return;
+ }
+
+ 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 (env == NULL)
+ /* fallback to comconsole setting */
+ env = getenv("comconsole_speed");
+
+ if (comc_parse_intval(env, &val) == CMD_OK)
+ comc_port->newbaudrate = 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);
+
+ if (comc_setup()) {
+ sc->c_flags = C_PRESENTIN | C_PRESENTOUT;
+ } else {
+ sc->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
+ free(comc_port);
+ comc_port = NULL;
+ }
+}
+
+#if defined(__aarch64__) && __FreeBSD_version < 1500000
+static void
+comc_probe_compat(struct console *sc)
+{
+ comc_probe(&eficom);
+ if (eficom.c_flags & (C_PRESENTIN | C_PRESENTOUT)) {
+ printf("comconsole: comconsole device name is deprecated, switch to eficom\n");
+ }
+ /*
+ * Note: We leave the present bits unset in sc to avoid ghosting.
+ */
+}
+#endif
+
+/*
+ * Called when the console is selected in cons_change. If we didn't detect the
+ * device, comc_port will be NULL, and comc_setup will fail. It may be called
+ * even when the device isn't present as a 'fallback' console or when listed
+ * specifically in console env, so we have to reset the c_flags in those case to
+ * say it's not present.
+ */
+static int
+comc_init(int arg __unused)
+{
+ if (comc_setup())
+ return (CMD_OK);
+
+ eficom.c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
+ 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 || comc_port == 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 || comc_port == NULL)
+ return (CMD_ERROR);
+
+ if (comc_parse_intval(value, &speed) != CMD_OK)
+ return (CMD_ERROR);
+
+ comc_port->newbaudrate = speed;
+ if (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;
+ char *ev;
+
+ /*
+ * If the device isn't active, or there's no port present.
+ */
+ if ((eficom.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0 || comc_port == NULL)
+ return (false);
+
+ if (comc_port->sio->Reset != NULL) {
+ status = comc_port->sio->Reset(comc_port->sio);
+ if (EFI_ERROR(status))
+ return (false);
+ }
+
+ /*
+ * Avoid setting the baud rate on Hyper-V. Also, only set the baud rate
+ * if the baud rate has changed from the default. And pass in '0' or
+ * DefaultFoo when we're not changing those values. Some EFI
+ * implementations get cranky when you set things to the values reported
+ * back even when they are unchanged.
+ */
+ if (comc_port->sio->SetAttributes != NULL &&
+ comc_port->newbaudrate != comc_port->baudrate) {
+ ev = getenv("smbios.bios.version");
+ if (ev != NULL && strncmp(ev, "Hyper-V", 7) != 0) {
+ status = comc_port->sio->SetAttributes(comc_port->sio,
+ comc_port->newbaudrate, 0, 0, DefaultParity, 0,
+ DefaultStopBits);
+ if (EFI_ERROR(status))
+ return (false);
+ comc_port->baudrate = comc_port->newbaudrate;
+ }
+ }
+
+#ifdef EFI_FORCE_RTS
+ if (comc_port->sio->GetControl != NULL && comc_port->sio->SetControl != NULL) {
+ UINT32 control;
+
+ status = comc_port->sio->GetControl(comc_port->sio, &control);
+ if (EFI_ERROR(status))
+ return (false);
+ control |= EFI_SERIAL_REQUEST_TO_SEND;
+ (void) comc_port->sio->SetControl(comc_port->sio, control);
+ }
+#endif
+ /* Mark this port usable. */
+ eficom.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
+ return (true);
+}