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