![cinap_lenrek](/assets/img/avatar_default.png)
intrdisable() will always be able to unregister the interrupt now, so there is no reason to have it return an error value. all drivers except uart8250 already assumed it to never fail and theres no need to maintain that complexity.
708 lines
13 KiB
C
708 lines
13 KiB
C
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "../port/error.h"
|
|
|
|
/*
|
|
* 8250 UART and compatibles.
|
|
*/
|
|
enum {
|
|
Uart0 = 0x3F8, /* COM1 */
|
|
Uart0IRQ = 4,
|
|
Uart1 = 0x2F8, /* COM2 */
|
|
Uart1IRQ = 3,
|
|
|
|
UartFREQ = 1843200,
|
|
};
|
|
|
|
enum { /* I/O ports */
|
|
Rbr = 0, /* Receiver Buffer (RO) */
|
|
Thr = 0, /* Transmitter Holding (WO) */
|
|
Ier = 1, /* Interrupt Enable */
|
|
Iir = 2, /* Interrupt Identification (RO) */
|
|
Fcr = 2, /* FIFO Control (WO) */
|
|
Lcr = 3, /* Line Control */
|
|
Mcr = 4, /* Modem Control */
|
|
Lsr = 5, /* Line Status */
|
|
Msr = 6, /* Modem Status */
|
|
Scr = 7, /* Scratch Pad */
|
|
Dll = 0, /* Divisor Latch LSB */
|
|
Dlm = 1, /* Divisor Latch MSB */
|
|
};
|
|
|
|
enum { /* Ier */
|
|
Erda = 0x01, /* Enable Received Data Available */
|
|
Ethre = 0x02, /* Enable Thr Empty */
|
|
Erls = 0x04, /* Enable Receiver Line Status */
|
|
Ems = 0x08, /* Enable Modem Status */
|
|
};
|
|
|
|
enum { /* Iir */
|
|
Ims = 0x00, /* Ms interrupt */
|
|
Ip = 0x01, /* Interrupt Pending (not) */
|
|
Ithre = 0x02, /* Thr Empty */
|
|
Irda = 0x04, /* Received Data Available */
|
|
Irls = 0x06, /* Receiver Line Status */
|
|
Ictoi = 0x0C, /* Character Time-out Indication */
|
|
IirMASK = 0x3F,
|
|
Ifena = 0xC0, /* FIFOs enabled */
|
|
};
|
|
|
|
enum { /* Fcr */
|
|
FIFOena = 0x01, /* FIFO enable */
|
|
FIFOrclr = 0x02, /* clear Rx FIFO */
|
|
FIFOtclr = 0x04, /* clear Tx FIFO */
|
|
FIFO1 = 0x00, /* Rx FIFO trigger level 1 byte */
|
|
FIFO4 = 0x40, /* 4 bytes */
|
|
FIFO8 = 0x80, /* 8 bytes */
|
|
FIFO14 = 0xC0, /* 14 bytes */
|
|
};
|
|
|
|
enum { /* Lcr */
|
|
Wls5 = 0x00, /* Word Length Select 5 bits/byte */
|
|
Wls6 = 0x01, /* 6 bits/byte */
|
|
Wls7 = 0x02, /* 7 bits/byte */
|
|
Wls8 = 0x03, /* 8 bits/byte */
|
|
WlsMASK = 0x03,
|
|
Stb = 0x04, /* 2 stop bits */
|
|
Pen = 0x08, /* Parity Enable */
|
|
Eps = 0x10, /* Even Parity Select */
|
|
Stp = 0x20, /* Stick Parity */
|
|
Brk = 0x40, /* Break */
|
|
Dlab = 0x80, /* Divisor Latch Access Bit */
|
|
};
|
|
|
|
enum { /* Mcr */
|
|
Dtr = 0x01, /* Data Terminal Ready */
|
|
Rts = 0x02, /* Ready To Send */
|
|
Out1 = 0x04, /* no longer in use */
|
|
Ie = 0x08, /* IRQ Enable */
|
|
Dm = 0x10, /* Diagnostic Mode loopback */
|
|
};
|
|
|
|
enum { /* Lsr */
|
|
Dr = 0x01, /* Data Ready */
|
|
Oe = 0x02, /* Overrun Error */
|
|
Pe = 0x04, /* Parity Error */
|
|
Fe = 0x08, /* Framing Error */
|
|
Bi = 0x10, /* Break Interrupt */
|
|
Thre = 0x20, /* Thr Empty */
|
|
Temt = 0x40, /* Tramsmitter Empty */
|
|
FIFOerr = 0x80, /* error in receiver FIFO */
|
|
};
|
|
|
|
enum { /* Msr */
|
|
Dcts = 0x01, /* Delta Cts */
|
|
Ddsr = 0x02, /* Delta Dsr */
|
|
Teri = 0x04, /* Trailing Edge of Ri */
|
|
Ddcd = 0x08, /* Delta Dcd */
|
|
Cts = 0x10, /* Clear To Send */
|
|
Dsr = 0x20, /* Data Set Ready */
|
|
Ri = 0x40, /* Ring Indicator */
|
|
Dcd = 0x80, /* Data Set Ready */
|
|
};
|
|
|
|
typedef struct Ctlr {
|
|
int io;
|
|
int irq;
|
|
int tbdf;
|
|
int iena;
|
|
|
|
uchar sticky[8];
|
|
|
|
Lock;
|
|
int hasfifo;
|
|
int checkfifo;
|
|
int fena;
|
|
} Ctlr;
|
|
|
|
extern PhysUart i8250physuart;
|
|
|
|
static Ctlr i8250ctlr[2] = {
|
|
{ .io = Uart0,
|
|
.irq = Uart0IRQ,
|
|
.tbdf = BUSUNKNOWN, },
|
|
|
|
{ .io = Uart1,
|
|
.irq = Uart1IRQ,
|
|
.tbdf = BUSUNKNOWN, },
|
|
};
|
|
|
|
static Uart i8250uart[2] = {
|
|
{ .regs = &i8250ctlr[0],
|
|
.name = "COM1",
|
|
.freq = UartFREQ,
|
|
.phys = &i8250physuart,
|
|
.special= 0,
|
|
.next = &i8250uart[1], },
|
|
|
|
{ .regs = &i8250ctlr[1],
|
|
.name = "COM2",
|
|
.freq = UartFREQ,
|
|
.phys = &i8250physuart,
|
|
.special= 0,
|
|
.next = nil, },
|
|
};
|
|
|
|
#define csr8r(c, r) inb((c)->io+(r))
|
|
#define csr8w(c, r, v) outb((c)->io+(r), (c)->sticky[(r)]|(v))
|
|
|
|
static long
|
|
i8250status(Uart* uart, void* buf, long n, long offset)
|
|
{
|
|
char *p;
|
|
Ctlr *ctlr;
|
|
uchar ier, lcr, mcr, msr;
|
|
|
|
ctlr = uart->regs;
|
|
p = smalloc(READSTR);
|
|
mcr = ctlr->sticky[Mcr];
|
|
msr = csr8r(ctlr, Msr);
|
|
ier = ctlr->sticky[Ier];
|
|
lcr = ctlr->sticky[Lcr];
|
|
snprint(p, READSTR,
|
|
"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n"
|
|
"dev(%d) type(%d) framing(%d) overruns(%d) "
|
|
"berr(%d) serr(%d)%s%s%s%s\n",
|
|
|
|
uart->baud,
|
|
uart->hup_dcd,
|
|
(msr & Dsr) != 0,
|
|
uart->hup_dsr,
|
|
(lcr & WlsMASK) + 5,
|
|
(ier & Ems) != 0,
|
|
(lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n',
|
|
(mcr & Rts) != 0,
|
|
(lcr & Stb) ? 2: 1,
|
|
ctlr->fena,
|
|
|
|
uart->dev,
|
|
uart->type,
|
|
uart->ferr,
|
|
uart->oerr,
|
|
uart->berr,
|
|
uart->serr,
|
|
(msr & Cts) ? " cts": "",
|
|
(msr & Dsr) ? " dsr": "",
|
|
(msr & Dcd) ? " dcd": "",
|
|
(msr & Ri) ? " ring": ""
|
|
);
|
|
n = readstr(offset, buf, n, p);
|
|
free(p);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
i8250fifo(Uart* uart, int level)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
if(ctlr->hasfifo == 0)
|
|
return;
|
|
|
|
/*
|
|
* Changing the FIFOena bit in Fcr flushes data
|
|
* from both receive and transmit FIFOs; there's
|
|
* no easy way to guarantee not losing data on
|
|
* the receive side, but it's possible to wait until
|
|
* the transmitter is really empty.
|
|
*/
|
|
ilock(ctlr);
|
|
while(!(csr8r(ctlr, Lsr) & Temt))
|
|
;
|
|
|
|
/*
|
|
* Set the trigger level, default is the max.
|
|
* value.
|
|
* Some UARTs require FIFOena to be set before
|
|
* other bits can take effect, so set it twice.
|
|
*/
|
|
ctlr->fena = level;
|
|
switch(level){
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
level = FIFO1|FIFOena;
|
|
break;
|
|
case 4:
|
|
level = FIFO4|FIFOena;
|
|
break;
|
|
case 8:
|
|
level = FIFO8|FIFOena;
|
|
break;
|
|
default:
|
|
level = FIFO14|FIFOena;
|
|
break;
|
|
}
|
|
csr8w(ctlr, Fcr, level);
|
|
csr8w(ctlr, Fcr, level);
|
|
iunlock(ctlr);
|
|
}
|
|
|
|
static void
|
|
i8250dtr(Uart* uart, int on)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
/*
|
|
* Toggle DTR.
|
|
*/
|
|
ctlr = uart->regs;
|
|
if(on)
|
|
ctlr->sticky[Mcr] |= Dtr;
|
|
else
|
|
ctlr->sticky[Mcr] &= ~Dtr;
|
|
csr8w(ctlr, Mcr, 0);
|
|
}
|
|
|
|
static void
|
|
i8250rts(Uart* uart, int on)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
/*
|
|
* Toggle RTS.
|
|
*/
|
|
ctlr = uart->regs;
|
|
if(on)
|
|
ctlr->sticky[Mcr] |= Rts;
|
|
else
|
|
ctlr->sticky[Mcr] &= ~Rts;
|
|
csr8w(ctlr, Mcr, 0);
|
|
}
|
|
|
|
static void
|
|
i8250modemctl(Uart* uart, int on)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
ilock(&uart->tlock);
|
|
if(on){
|
|
ctlr->sticky[Ier] |= Ems;
|
|
csr8w(ctlr, Ier, ctlr->sticky[Ier]);
|
|
uart->modem = 1;
|
|
uart->cts = csr8r(ctlr, Msr) & Cts;
|
|
}
|
|
else{
|
|
ctlr->sticky[Ier] &= ~Ems;
|
|
csr8w(ctlr, Ier, ctlr->sticky[Ier]);
|
|
uart->modem = 0;
|
|
uart->cts = 1;
|
|
}
|
|
iunlock(&uart->tlock);
|
|
|
|
/* modem needs fifo */
|
|
(*uart->phys->fifo)(uart, on);
|
|
}
|
|
|
|
static int
|
|
i8250parity(Uart* uart, int parity)
|
|
{
|
|
int lcr;
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
lcr = ctlr->sticky[Lcr] & ~(Eps|Pen);
|
|
|
|
switch(parity){
|
|
case 'e':
|
|
lcr |= Eps|Pen;
|
|
break;
|
|
case 'o':
|
|
lcr |= Pen;
|
|
break;
|
|
case 'n':
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
ctlr->sticky[Lcr] = lcr;
|
|
csr8w(ctlr, Lcr, 0);
|
|
|
|
uart->parity = parity;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
i8250stop(Uart* uart, int stop)
|
|
{
|
|
int lcr;
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
lcr = ctlr->sticky[Lcr] & ~Stb;
|
|
|
|
switch(stop){
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
lcr |= Stb;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
ctlr->sticky[Lcr] = lcr;
|
|
csr8w(ctlr, Lcr, 0);
|
|
|
|
uart->stop = stop;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
i8250bits(Uart* uart, int bits)
|
|
{
|
|
int lcr;
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
lcr = ctlr->sticky[Lcr] & ~WlsMASK;
|
|
|
|
switch(bits){
|
|
case 5:
|
|
lcr |= Wls5;
|
|
break;
|
|
case 6:
|
|
lcr |= Wls6;
|
|
break;
|
|
case 7:
|
|
lcr |= Wls7;
|
|
break;
|
|
case 8:
|
|
lcr |= Wls8;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
ctlr->sticky[Lcr] = lcr;
|
|
csr8w(ctlr, Lcr, 0);
|
|
|
|
uart->bits = bits;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
i8250baud(Uart* uart, int baud)
|
|
{
|
|
ulong bgc;
|
|
Ctlr *ctlr;
|
|
|
|
/*
|
|
* Set the Baud rate by calculating and setting the Baud rate
|
|
* Generator Constant. This will work with fairly non-standard
|
|
* Baud rates.
|
|
*/
|
|
if(uart->freq == 0 || baud <= 0)
|
|
return -1;
|
|
bgc = (uart->freq+8*baud-1)/(16*baud);
|
|
|
|
ctlr = uart->regs;
|
|
csr8w(ctlr, Lcr, Dlab);
|
|
outb(ctlr->io+Dlm, bgc>>8);
|
|
outb(ctlr->io+Dll, bgc);
|
|
csr8w(ctlr, Lcr, 0);
|
|
|
|
uart->baud = baud;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
i8250break(Uart* uart, int ms)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
/*
|
|
* Send a break.
|
|
*/
|
|
if(ms <= 0)
|
|
ms = 200;
|
|
|
|
ctlr = uart->regs;
|
|
csr8w(ctlr, Lcr, Brk);
|
|
tsleep(&up->sleep, return0, 0, ms);
|
|
csr8w(ctlr, Lcr, 0);
|
|
}
|
|
|
|
static void
|
|
i8250kick(Uart* uart)
|
|
{
|
|
int i;
|
|
Ctlr *ctlr;
|
|
|
|
if(uart->cts == 0 || uart->blocked)
|
|
return;
|
|
|
|
/*
|
|
* 128 here is an arbitrary limit to make sure
|
|
* we don't stay in this loop too long. If the
|
|
* chip's output queue is longer than 128, too
|
|
* bad -- presotto
|
|
*/
|
|
ctlr = uart->regs;
|
|
for(i = 0; i < 128; i++){
|
|
if(!(csr8r(ctlr, Lsr) & Thre))
|
|
break;
|
|
if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
|
|
break;
|
|
outb(ctlr->io+Thr, *(uart->op++));
|
|
}
|
|
}
|
|
|
|
static void
|
|
i8250interrupt(Ureg*, void* arg)
|
|
{
|
|
Ctlr *ctlr;
|
|
Uart *uart;
|
|
int iir, lsr, old, r;
|
|
|
|
uart = arg;
|
|
|
|
ctlr = uart->regs;
|
|
for(iir = csr8r(ctlr, Iir); !(iir & Ip); iir = csr8r(ctlr, Iir)){
|
|
switch(iir & IirMASK){
|
|
case Ims: /* Ms interrupt */
|
|
r = csr8r(ctlr, Msr);
|
|
if(r & Dcts){
|
|
ilock(&uart->tlock);
|
|
old = uart->cts;
|
|
uart->cts = r & Cts;
|
|
if(old == 0 && uart->cts)
|
|
uart->ctsbackoff = 2;
|
|
iunlock(&uart->tlock);
|
|
}
|
|
if(r & Ddsr){
|
|
old = r & Dsr;
|
|
if(uart->hup_dsr && uart->dsr && !old)
|
|
uart->dohup = 1;
|
|
uart->dsr = old;
|
|
}
|
|
if(r & Ddcd){
|
|
old = r & Dcd;
|
|
if(uart->hup_dcd && uart->dcd && !old)
|
|
uart->dohup = 1;
|
|
uart->dcd = old;
|
|
}
|
|
break;
|
|
case Ithre: /* Thr Empty */
|
|
uartkick(uart);
|
|
break;
|
|
case Irda: /* Received Data Available */
|
|
case Irls: /* Receiver Line Status */
|
|
case Ictoi: /* Character Time-out Indication */
|
|
/*
|
|
* Consume any received data.
|
|
* If the received byte came in with a break,
|
|
* parity or framing error, throw it away;
|
|
* overrun is an indication that something has
|
|
* already been tossed.
|
|
*/
|
|
while((lsr = csr8r(ctlr, Lsr)) & Dr){
|
|
if(lsr & (FIFOerr|Oe))
|
|
uart->oerr++;
|
|
if(lsr & Pe)
|
|
uart->perr++;
|
|
if(lsr & Fe)
|
|
uart->ferr++;
|
|
r = csr8r(ctlr, Rbr);
|
|
if(!(lsr & (Bi|Fe|Pe)))
|
|
uartrecv(uart, r);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
iprint("weird uart interrupt 0x%2.2uX\n", iir);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
i8250disable(Uart* uart)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
/*
|
|
* Turn off DTR and RTS, disable interrupts and fifos.
|
|
*/
|
|
(*uart->phys->dtr)(uart, 0);
|
|
(*uart->phys->rts)(uart, 0);
|
|
(*uart->phys->fifo)(uart, 0);
|
|
|
|
ctlr = uart->regs;
|
|
ctlr->sticky[Ier] = 0;
|
|
csr8w(ctlr, Ier, ctlr->sticky[Ier]);
|
|
|
|
if(ctlr->iena != 0){
|
|
ctlr->iena = 0;
|
|
intrdisable(ctlr->irq, i8250interrupt, uart, ctlr->tbdf, uart->name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
i8250enable(Uart* uart, int ie)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
|
|
/*
|
|
* Check if there is a FIFO.
|
|
* Changing the FIFOena bit in Fcr flushes data
|
|
* from both receive and transmit FIFOs; there's
|
|
* no easy way to guarantee not losing data on
|
|
* the receive side, but it's possible to wait until
|
|
* the transmitter is really empty.
|
|
* Also, reading the Iir outwith i8250interrupt()
|
|
* can be dangerous, but this should only happen
|
|
* once before interrupts are enabled.
|
|
*/
|
|
ilock(ctlr);
|
|
if(!ctlr->checkfifo){
|
|
/*
|
|
* Wait until the transmitter is really empty.
|
|
*/
|
|
while(!(csr8r(ctlr, Lsr) & Temt))
|
|
;
|
|
csr8w(ctlr, Fcr, FIFOena);
|
|
if(csr8r(ctlr, Iir) & Ifena)
|
|
ctlr->hasfifo = 1;
|
|
csr8w(ctlr, Fcr, 0);
|
|
ctlr->checkfifo = 1;
|
|
}
|
|
iunlock(ctlr);
|
|
|
|
/*
|
|
* Enable interrupts and turn on DTR and RTS.
|
|
* Be careful if this is called to set up a polled serial line
|
|
* early on not to try to enable interrupts as interrupt-
|
|
* -enabling mechanisms might not be set up yet.
|
|
*/
|
|
if(ie){
|
|
if(ctlr->iena == 0){
|
|
intrenable(ctlr->irq, i8250interrupt, uart, ctlr->tbdf, uart->name);
|
|
ctlr->iena = 1;
|
|
}
|
|
ctlr->sticky[Ier] = Ethre|Erda;
|
|
ctlr->sticky[Mcr] |= Ie;
|
|
}
|
|
else{
|
|
ctlr->sticky[Ier] = 0;
|
|
ctlr->sticky[Mcr] = 0;
|
|
}
|
|
csr8w(ctlr, Ier, ctlr->sticky[Ier]);
|
|
csr8w(ctlr, Mcr, ctlr->sticky[Mcr]);
|
|
|
|
(*uart->phys->dtr)(uart, 1);
|
|
(*uart->phys->rts)(uart, 1);
|
|
|
|
/*
|
|
* During startup, the i8259 interrupt controller is reset.
|
|
* This may result in a lost interrupt from the i8250 uart.
|
|
* The i8250 thinks the interrupt is still outstanding and does not
|
|
* generate any further interrupts. The workaround is to call the
|
|
* interrupt handler to clear any pending interrupt events.
|
|
* Note: this must be done after setting Ier.
|
|
*/
|
|
if(ie)
|
|
i8250interrupt(nil, uart);
|
|
}
|
|
|
|
void*
|
|
i8250alloc(int io, int irq, int tbdf)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = malloc(sizeof(Ctlr));
|
|
if(ctlr == nil){
|
|
print("i8250alloc: no memory for Ctlr\n");
|
|
return nil;
|
|
}
|
|
ctlr->io = io;
|
|
ctlr->irq = irq;
|
|
ctlr->tbdf = tbdf;
|
|
return ctlr;
|
|
}
|
|
|
|
static Uart*
|
|
i8250pnp(void)
|
|
{
|
|
return i8250uart;
|
|
}
|
|
|
|
static int
|
|
i8250getc(Uart *uart)
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
while(!(csr8r(ctlr, Lsr)&Dr))
|
|
delay(1);
|
|
return csr8r(ctlr, Rbr);
|
|
}
|
|
|
|
static void
|
|
i8250putc(Uart *uart, int c)
|
|
{
|
|
int i;
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = uart->regs;
|
|
for(i = 0; !(csr8r(ctlr, Lsr)&Thre) && i < 128; i++)
|
|
delay(1);
|
|
outb(ctlr->io+Thr, c);
|
|
for(i = 0; !(csr8r(ctlr, Lsr)&Thre) && i < 128; i++)
|
|
delay(1);
|
|
}
|
|
|
|
PhysUart i8250physuart = {
|
|
.name = "i8250",
|
|
.pnp = i8250pnp,
|
|
.enable = i8250enable,
|
|
.disable = i8250disable,
|
|
.kick = i8250kick,
|
|
.dobreak = i8250break,
|
|
.baud = i8250baud,
|
|
.bits = i8250bits,
|
|
.stop = i8250stop,
|
|
.parity = i8250parity,
|
|
.modemctl = i8250modemctl,
|
|
.rts = i8250rts,
|
|
.dtr = i8250dtr,
|
|
.status = i8250status,
|
|
.fifo = i8250fifo,
|
|
.getc = i8250getc,
|
|
.putc = i8250putc,
|
|
};
|
|
|
|
void
|
|
i8250console(void)
|
|
{
|
|
Uart *uart;
|
|
int n;
|
|
char *cmd, *p;
|
|
|
|
if((p = getconf("console")) == nil)
|
|
return;
|
|
n = strtoul(p, &cmd, 0);
|
|
if(p == cmd || n < 0 || n >= nelem(i8250uart))
|
|
return;
|
|
uart = &i8250uart[n];
|
|
|
|
(*uart->phys->enable)(uart, 0);
|
|
uartctl(uart, "b9600 l8 pn s1");
|
|
if(*cmd != '\0')
|
|
uartctl(uart, cmd);
|
|
|
|
consuart = uart;
|
|
uart->console = 1;
|
|
}
|