blob: 8bcce95e0b1049e1eb7f844535111355cb0e4b8c [file] [log] [blame]
/*
* 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 <sys/cdefs.h>
#include <stand.h>
#include <sys/errno.h>
#include <bootstrap.h>
#include <efi.h>
#include <efilib.h>
#include "loader_efi.h"
static EFI_GUID serial = SERIAL_IO_PROTOCOL;
#define COMC_TXWAIT 0x40000 /* transmit timeout */
#ifndef COMSPEED
#define COMSPEED 9600
#endif
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 void comc_probe(struct console *);
static int comc_init(struct console *, int);
static void comc_putchar(struct console *, int);
static int comc_getchar(struct console *);
static int comc_ischar(struct console *);
static void comc_setup(struct console *);
static char *comc_asprint_mode(struct serial *);
static int comc_parse_mode(struct serial *, const char *);
static int comc_mode_set(struct env_var *, int, const void *);
static int comc_cd_set(struct env_var *, int, const void *);
static int comc_rtsdtr_set(struct env_var *, int, const void *);
struct console ttya = {
.c_name = "ttya",
.c_desc = "serial port a",
.c_flags = 0,
.c_probe = comc_probe,
.c_init = comc_init,
.c_out = comc_putchar,
.c_in = comc_getchar,
.c_ready = comc_ischar,
.c_private = NULL
};
struct console ttyb = {
.c_name = "ttyb",
.c_desc = "serial port b",
.c_flags = 0,
.c_probe = comc_probe,
.c_init = comc_init,
.c_out = comc_putchar,
.c_in = comc_getchar,
.c_ready = comc_ischar,
.c_private = NULL
};
struct console ttyc = {
.c_name = "ttyc",
.c_desc = "serial port c",
.c_flags = 0,
.c_probe = comc_probe,
.c_init = comc_init,
.c_out = comc_putchar,
.c_in = comc_getchar,
.c_ready = comc_ischar,
.c_private = NULL
};
struct console ttyd = {
.c_name = "ttyd",
.c_desc = "serial port d",
.c_flags = 0,
.c_probe = comc_probe,
.c_init = comc_init,
.c_out = comc_putchar,
.c_in = comc_getchar,
.c_ready = comc_ischar,
.c_private = NULL
};
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);
}
static void
comc_probe(struct console *cp)
{
EFI_STATUS status;
struct serial *port;
char name[20];
char value[20];
char *env;
EFI_HANDLE *handles = NULL; /* array of handles */
int nhandles = 0; /* number of handles in array */
/* are we already set up? */
if (cp->c_private != NULL)
return;
/* make sure the handles are available */
status = efi_serial_init(&handles, &nhandles);
cp->c_private = malloc(sizeof (struct serial));
port = cp->c_private;
port->baudrate = COMSPEED;
if (strcmp(cp->c_name, "ttya") == 0)
port->ioaddr = 0;
else if (strcmp(cp->c_name, "ttyb") == 0)
port->ioaddr = 1;
else if (strcmp(cp->c_name, "ttyc") == 0)
port->ioaddr = 2;
else if (strcmp(cp->c_name, "ttyd") == 0)
port->ioaddr = 3;
if (port->ioaddr >= nhandles)
port->ioaddr = -1; /* invalid port */
port->databits = 8; /* 8,n,1 */
port->parity = NoParity; /* 8,n,1 */
port->stopbits = OneStopBit; /* 8,n,1 */
port->ignore_cd = 1; /* ignore cd */
port->rtsdtr_off = 0; /* rts-dtr is on */
port->sio = NULL;
if (port->ioaddr != -1) {
status = BS->OpenProtocol(handles[port->ioaddr],
&serial, (void**)&port->sio, IH, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(status))
port->ioaddr = -1; /* invalid port */
}
if (handles != NULL)
free(handles);
snprintf(name, sizeof (name), "%s-mode", cp->c_name);
env = getenv(name);
if (env != NULL)
(void) comc_parse_mode(port, env);
env = comc_asprint_mode(port);
if (env != NULL) {
unsetenv(name);
env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
free(env);
}
snprintf(name, sizeof (name), "%s-ignore-cd", cp->c_name);
env = getenv(name);
if (env != NULL) {
if (strcmp(env, "true") == 0)
port->ignore_cd = 1;
else if (strcmp(env, "false") == 0)
port->ignore_cd = 0;
}
snprintf(value, sizeof (value), "%s",
port->ignore_cd? "true" : "false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
snprintf(name, sizeof (name), "%s-rts-dtr-off", cp->c_name);
env = getenv(name);
if (env != NULL) {
if (strcmp(env, "true") == 0)
port->rtsdtr_off = 1;
else if (strcmp(env, "false") == 0)
port->rtsdtr_off = 0;
}
snprintf(value, sizeof (value), "%s",
port->rtsdtr_off? "true" : "false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
comc_setup(cp);
}
static int
comc_init(struct console *cp, int arg __attribute((unused)))
{
comc_setup(cp);
if ((cp->c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
(C_PRESENTIN | C_PRESENTOUT))
return (CMD_OK);
return (CMD_ERROR);
}
static void
comc_putchar(struct console *cp, int c)
{
int wait;
EFI_STATUS status;
UINTN bufsz = 1;
char cb = c;
struct serial *sp = cp->c_private;
if (sp->sio == NULL)
return;
for (wait = COMC_TXWAIT; wait > 0; wait--) {
status = sp->sio->Write(sp->sio, &bufsz, &cb);
if (status != EFI_TIMEOUT)
break;
}
}
static int
comc_getchar(struct console *cp)
{
EFI_STATUS status;
UINTN bufsz = 1;
char c;
struct serial *sp = cp->c_private;
if (sp->sio == NULL || !comc_ischar(cp))
return (-1);
status = sp->sio->Read(sp->sio, &bufsz, &c);
if (EFI_ERROR(status) || bufsz == 0)
return (-1);
return (c);
}
static int
comc_ischar(struct console *cp)
{
EFI_STATUS status;
uint32_t control;
struct serial *sp = cp->c_private;
if (sp->sio == NULL)
return (0);
status = sp->sio->GetControl(sp->sio, &control);
if (EFI_ERROR(status))
return (0);
return (!(control & EFI_SERIAL_INPUT_BUFFER_EMPTY));
}
static char *
comc_asprint_mode(struct serial *sp)
{
char par, *buf;
char *stop;
if (sp == NULL)
return (NULL);
switch (sp->parity) {
case NoParity:
par = 'n';
break;
case EvenParity:
par = 'e';
break;
case OddParity:
par = 'o';
break;
case MarkParity:
par = 'm';
break;
case SpaceParity:
par = 's';
break;
default:
par = 'n';
break;
}
switch (sp->stopbits) {
case OneStopBit:
stop = "1";
break;
case TwoStopBits:
stop = "2";
break;
case OneFiveStopBits:
stop = "1.5";
break;
default:
stop = "1";
break;
}
asprintf(&buf, "%ju,%d,%c,%s,-", sp->baudrate, sp->databits, par, stop);
return (buf);
}
static int
comc_parse_mode(struct serial *sp, const char *value)
{
unsigned long n;
uint64_t baudrate;
uint8_t databits = 8;
int parity = NoParity;
int stopbits = OneStopBit;
char *ep;
if (value == NULL || *value == '\0')
return (CMD_ERROR);
errno = 0;
n = strtoul(value, &ep, 10);
if (errno != 0 || *ep != ',')
return (CMD_ERROR);
baudrate = n;
ep++;
n = strtoul(ep, &ep, 10);
if (errno != 0 || *ep != ',')
return (CMD_ERROR);
switch (n) {
case 5: databits = 5;
break;
case 6: databits = 6;
break;
case 7: databits = 7;
break;
case 8: databits = 8;
break;
default:
return (CMD_ERROR);
}
ep++;
switch (*ep++) {
case 'n': parity = NoParity;
break;
case 'e': parity = EvenParity;
break;
case 'o': parity = OddParity;
break;
case 'm': parity = MarkParity;
break;
case 's': parity = SpaceParity;
break;
default:
return (CMD_ERROR);
}
if (*ep == ',')
ep++;
else
return (CMD_ERROR);
switch (*ep++) {
case '1': stopbits = OneStopBit;
if (ep[0] == '.' && ep[1] == '5') {
ep += 2;
stopbits = OneFiveStopBits;
}
break;
case '2': stopbits = TwoStopBits;
break;
default:
return (CMD_ERROR);
}
/* handshake is ignored, but we check syntax anyhow */
if (*ep == ',')
ep++;
else
return (CMD_ERROR);
switch (*ep++) {
case '-':
case 'h':
case 's':
break;
default:
return (CMD_ERROR);
}
if (*ep != '\0')
return (CMD_ERROR);
sp->baudrate = baudrate;
sp->databits = databits;
sp->parity = parity;
sp->stopbits = stopbits;
return (CMD_OK);
}
static struct console *
get_console(char *name)
{
struct console *cp = NULL;
switch (name[3]) {
case 'a': cp = &ttya;
break;
case 'b': cp = &ttyb;
break;
case 'c': cp = &ttyc;
break;
case 'd': cp = &ttyd;
break;
}
return (cp);
}
static int
comc_mode_set(struct env_var *ev, int flags, const void *value)
{
struct console *cp;
if (value == NULL)
return (CMD_ERROR);
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
if (comc_parse_mode(cp->c_private, value) == CMD_ERROR)
return (CMD_ERROR);
comc_setup(cp);
env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
return (CMD_OK);
}
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);
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 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->rtsdtr_off = 1;
else if (strcmp(value, "false") == 0)
sp->rtsdtr_off = 0;
else
return (CMD_ERROR);
comc_setup(cp);
env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
return (CMD_OK);
}
static void
comc_setup(struct console *cp)
{
EFI_STATUS status;
UINT32 control;
struct serial *sp = cp->c_private;
if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
return;
/* port is not usable */
if (sp->sio == NULL) {
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
return;
}
cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
status = sp->sio->Reset(sp->sio);
if (EFI_ERROR(status)) {
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
}
status = sp->sio->SetAttributes(sp->sio, sp->baudrate, 0, 0, sp->parity,
sp->databits, sp->stopbits);
if (EFI_ERROR(status)) {
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
}
if (sp->rtsdtr_off) {
status = sp->sio->GetControl(sp->sio, &control);
if (EFI_ERROR(status)) {
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
}
control &= ~(EFI_SERIAL_REQUEST_TO_SEND |
EFI_SERIAL_DATA_TERMINAL_READY);
status = sp->sio->SetControl(sp->sio, control);
if (EFI_ERROR(status)) {
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
}
}
}