comconsole.c revision 199767f8919635c4928607450d9e0abb932109ce
/*-
* 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 <bootstrap.h>
#include <machine/cpufunc.h>
#include <dev/ic/ns16550.h>
#include <dev/pci/pcireg.h>
#include "libi386.h"
#define COMC_TXWAIT 0x40000 /* transmit timeout */
#define COMC_BPS(x) (115200 / (x)) /* speed to DLAB divisor */
#define COMC_DIV2BPS(x) (115200 / (x)) /* DLAB divisor to speed */
#ifndef COMSPEED
#define COMSPEED 9600
#endif
#define COM1_IOADDR 0x3f8
#define COM2_IOADDR 0x2f8
#define COM3_IOADDR 0x3e8
#define COM4_IOADDR 0x2e8
#define STOP1 0x00
#define STOP2 0x04
#define PARODD 0x00
#define PAREN 0x08
#define PAREVN 0x10
#define PARMARK 0x20
#define BITS5 0x00 /* 5 bits per char */
#define BITS6 0x01 /* 6 bits per char */
#define BITS7 0x02 /* 7 bits per char */
#define BITS8 0x03 /* 8 bits per char */
struct serial {
int speed; /* baud rate */
uint8_t lcr; /* line control */
uint8_t ignore_cd; /* boolean */
uint8_t rtsdtr_off; /* boolean */
int ioaddr;
uint32_t locator;
};
static void comc_probe(struct console *cp);
static int comc_init(struct console *cp, int arg);
static void comc_putchar(struct console *cp, int c);
static int comc_getchar(struct console *cp);
static int comc_getspeed(struct serial *sp);
static int comc_ischar(struct console *cp);
static uint32_t comc_parse_pcidev(const char *string);
static int comc_pcidev_set(struct env_var *ev, int flags,
const void *value);
static int comc_pcidev_handle(struct console *cp, uint32_t locator);
static void comc_setup(struct console *cp);
static char *comc_print_mode(struct serial *sp, char *buf);
static int comc_parse_mode(struct serial *sp, const char *value);
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 = {
"ttya",
"serial port a",
0,
comc_probe,
comc_init,
comc_putchar,
comc_getchar,
comc_ischar,
NULL
};
struct console ttyb = {
"ttyb",
"serial port b",
0,
comc_probe,
comc_init,
comc_putchar,
comc_getchar,
comc_ischar,
NULL
};
struct console ttyc = {
"ttyc",
"serial port c",
0,
comc_probe,
comc_init,
comc_putchar,
comc_getchar,
comc_ischar,
NULL
};
struct console ttyd = {
"ttyd",
"serial port d",
0,
comc_probe,
comc_init,
comc_putchar,
comc_getchar,
comc_ischar,
NULL
};
static void
comc_probe(struct console *cp)
{
struct serial *port;
char name[20];
char value[20];
char *cons, *env;
if (cp->private == NULL) {
cp->private = malloc(sizeof(struct serial));
port = cp->private;
port->speed = COMSPEED;
if (strcmp(cp->c_name, "ttya") == 0)
port->ioaddr = COM1_IOADDR;
else if (strcmp(cp->c_name, "ttyb") == 0)
port->ioaddr = COM2_IOADDR;
else if (strcmp(cp->c_name, "ttyc") == 0)
port->ioaddr = COM3_IOADDR;
else if (strcmp(cp->c_name, "ttyd") == 0)
port->ioaddr = COM4_IOADDR;
port->lcr = BITS8; /* 8,n,1 */
port->ignore_cd = 1; /* ignore cd */
port->rtsdtr_off = 0; /* rts-dtr is on */
/*
* Assume that the speed was set by an earlier boot loader if
* comconsole is already the preferred console.
*/
cons = getenv("console");
if ((cons != NULL && strcmp(cons, cp->c_name) == 0) ||
getenv("boot_multicons") != NULL) {
port->speed = comc_getspeed(port);
}
snprintf(name, 20, "%s-mode", cp->c_name);
env = getenv(name);
if (env != NULL) {
(void) comc_parse_mode(port, env);
}
env = comc_print_mode(port, value);
unsetenv(name);
env_setenv(name, EV_VOLATILE, env, comc_mode_set, env_nounset);
snprintf(name, 20, "%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;
}
sprintf(value, "%s", port->ignore_cd? "true":"false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_cd_set, env_nounset);
snprintf(name, 20, "%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;
}
sprintf(value, "%s", port->rtsdtr_off? "true":"false");
unsetenv(name);
env_setenv(name, EV_VOLATILE, value, comc_rtsdtr_set, env_nounset);
snprintf(name, 20, "%s-pcidev", cp->c_name);
env = getenv(name);
if (env != NULL) {
port->locator = comc_parse_pcidev(env);
if (port->locator != 0)
comc_pcidev_handle(cp, port->locator);
}
unsetenv(name);
env_setenv(name, EV_VOLATILE, env, comc_pcidev_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;
struct serial *sp = cp->private;
for (wait = COMC_TXWAIT; wait > 0; wait--)
if (inb(sp->ioaddr + com_lsr) & LSR_TXRDY) {
outb(sp->ioaddr + com_data, (u_char)c);
break;
}
}
static int
comc_getchar(struct console *cp)
{
struct serial *sp = cp->private;
return (comc_ischar(cp) ? inb(sp->ioaddr + com_data) : -1);
}
static int
comc_ischar(struct console *cp)
{
struct serial *sp = cp->private;
return (inb(sp->ioaddr + com_lsr) & LSR_RXRDY);
}
static char *
comc_print_mode(struct serial *sp, char *buf)
{
char par;
if ((sp->lcr & (PAREN|PAREVN)) == (PAREN|PAREVN))
par = 'e';
else if ((sp->lcr & PAREN) == PAREN)
par = 'o';
else
par = 'n';
sprintf(buf, "%d,%d,%c,%d,-", sp->speed,
(sp->lcr & BITS8) == BITS8? 8:7,
par, (par & STOP2) == STOP2? 2:1);
return (buf);
}
static int
comc_parse_mode(struct serial *sp, const char *value)
{
int n;
int speed;
int lcr;
char *ep;
n = strtol(value, &ep, 0);
if (n > 0)
speed = n;
else
return (CMD_ERROR);
if (*ep == ',')
ep++;
else
return (CMD_ERROR);
n = strtol(ep, &ep, 0);
switch (n) {
case 7: lcr = BITS7;
break;
case 8: lcr = BITS8;
break;
default:
return (CMD_ERROR);
}
if (*ep == ',')
ep++;
else
return (CMD_ERROR);
switch (*ep++) {
case 'n':
break;
case 'e': lcr |= PAREN|PAREVN;
break;
case 'o': lcr |= PAREN|PARODD;
break;
default:
return (CMD_ERROR);
}
if (*ep == ',')
ep++;
else
return (CMD_ERROR);
switch (*ep++) {
case '1':
break;
case '2': lcr |= STOP2;
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->speed = speed;
sp->lcr = lcr;
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->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->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->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);
}
/*
* Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10.
* Output: bar[24:16] bus[15:8] dev[7:3] func[2:0]
*/
static uint32_t
comc_parse_pcidev(const char *string)
{
#ifdef NO_PCI
(void)string;
return (0);
#else
char *p, *p1;
uint8_t bus, dev, func, bar;
uint32_t locator;
int pres;
pres = strtol(string, &p, 0);
if (p == string || *p != ':' || pres < 0 )
return (0);
bus = pres;
p1 = ++p;
pres = strtol(p1, &p, 0);
if (p == string || *p != ':' || pres < 0 )
return (0);
dev = pres;
p1 = ++p;
pres = strtol(p1, &p, 0);
if (p == string || (*p != ':' && *p != '\0') || pres < 0 )
return (0);
func = pres;
if (*p == ':') {
p1 = ++p;
pres = strtol(p1, &p, 0);
if (p == string || *p != '\0' || pres <= 0 )
return (0);
bar = pres;
} else
bar = 0x10;
locator = (bar << 16) | biospci_locator(bus, dev, func);
return (locator);
#endif
}
static int
comc_pcidev_handle(struct console *cp, uint32_t locator)
{
#ifdef NO_PCI
(void)cp;
(void)locator;
return (CMD_ERROR);
#else
struct serial *sp = cp->private;
char intbuf[64];
uint32_t port;
if (biospci_read_config(locator & 0xffff,
(locator & 0xff0000) >> 16, 2, &port) == -1) {
printf("Cannot read bar at 0x%x\n", locator);
return (CMD_ERROR);
}
if (!PCI_BAR_IO(port)) {
printf("Memory bar at 0x%x\n", locator);
return (CMD_ERROR);
}
port &= PCIM_BAR_IO_BASE;
comc_setup(cp);
sp->locator = locator;
return (CMD_OK);
#endif
}
static int
comc_pcidev_set(struct env_var *ev, int flags, const void *value)
{
struct console *cp;
struct serial *sp;
uint32_t locator;
int error;
if ((cp = get_console(ev->ev_name)) == NULL)
return (CMD_ERROR);
sp = cp->private;
if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) {
printf("Invalid pcidev\n");
return (CMD_ERROR);
}
if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 &&
sp->locator != locator) {
error = comc_pcidev_handle(cp, locator);
if (error != CMD_OK)
return (error);
}
env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
return (CMD_OK);
}
static void
comc_setup(struct console *cp)
{
struct serial *sp = cp->private;
static int TRY_COUNT = 1000000;
int tries;
if ((cp->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
return;
outb(sp->ioaddr + com_cfcr, CFCR_DLAB | sp->lcr);
outb(sp->ioaddr + com_dlbl, COMC_BPS(sp->speed) & 0xff);
outb(sp->ioaddr + com_dlbh, COMC_BPS(sp->speed) >> 8);
outb(sp->ioaddr + com_cfcr, sp->lcr);
outb(sp->ioaddr + com_mcr,
sp->rtsdtr_off? ~(MCR_RTS | MCR_DTR):MCR_RTS | MCR_DTR);
tries = 0;
do
inb(sp->ioaddr + com_data);
while (inb(sp->ioaddr + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
if (tries < TRY_COUNT) {
cp->c_flags |= (C_PRESENTIN | C_PRESENTOUT);
} else
cp->c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
}
static int
comc_getspeed(struct serial *sp)
{
u_int divisor;
u_char dlbh;
u_char dlbl;
u_char cfcr;
cfcr = inb(sp->ioaddr + com_cfcr);
outb(sp->ioaddr + com_cfcr, CFCR_DLAB | cfcr);
dlbl = inb(sp->ioaddr + com_dlbl);
dlbh = inb(sp->ioaddr + com_dlbh);
outb(sp->ioaddr + com_cfcr, cfcr);
divisor = dlbh << 8 | dlbl;
/* XXX there should be more sanity checking. */
if (divisor == 0)
return (COMSPEED);
return (COMC_DIV2BPS(divisor));
}