interrupt-driven uart output, hopefully a nice example for teaching.

This commit is contained in:
Robert Morris 2020-07-20 06:59:26 -04:00
parent 3b053f5d58
commit 823864099d
5 changed files with 100 additions and 18 deletions

View file

@ -27,6 +27,8 @@
// //
// send one character to the uart. // send one character to the uart.
// called by printf, and to echo input characters,
// but not from write().
// //
void void
consputc(int c) consputc(int c)
@ -40,9 +42,9 @@ consputc(int c)
if(c == BACKSPACE){ if(c == BACKSPACE){
// if the user typed backspace, overwrite with a space. // if the user typed backspace, overwrite with a space.
uartputc('\b'); uartputc(' '); uartputc('\b'); uartputc('\b', 0); uartputc(' ', 0); uartputc('\b', 0);
} else { } else {
uartputc(c); uartputc(c, 0);
} }
} }
@ -70,7 +72,7 @@ consolewrite(int user_src, uint64 src, int n)
char c; char c;
if(either_copyin(&c, user_src, src+i, 1) == -1) if(either_copyin(&c, user_src, src+i, 1) == -1)
break; break;
consputc(c); uartputc(c, 1);
} }
release(&cons.lock); release(&cons.lock);

View file

@ -149,7 +149,7 @@ void usertrapret(void);
// uart.c // uart.c
void uartinit(void); void uartinit(void);
void uartintr(void); void uartintr(void);
void uartputc(int); void uartputc(int, int);
int uartgetc(void); int uartgetc(void);
// vm.c // vm.c

View file

@ -33,7 +33,6 @@ int
plic_claim(void) plic_claim(void)
{ {
int hart = cpuid(); int hart = cpuid();
//int irq = *(uint32*)(PLIC + 0x201004);
int irq = *(uint32*)PLIC_SCLAIM(hart); int irq = *(uint32*)PLIC_SCLAIM(hart);
return irq; return irq;
} }
@ -43,6 +42,5 @@ void
plic_complete(int irq) plic_complete(int irq)
{ {
int hart = cpuid(); int hart = cpuid();
//*(uint32*)(PLIC + 0x201004) = irq;
*(uint32*)PLIC_SCLAIM(hart) = irq; *(uint32*)PLIC_SCLAIM(hart) = irq;
} }

View file

@ -91,8 +91,9 @@ usertrapret(void)
{ {
struct proc *p = myproc(); struct proc *p = myproc();
// turn off interrupts, since we're switching // we're about to switch the destination of traps from
// now from kerneltrap() to usertrap(). // kerneltrap() to usertrap(), so turn off interrupts until
// we're back in user space, where usertrap() is correct.
intr_off(); intr_off();
// send syscalls, interrupts, and exceptions to trampoline.S // send syscalls, interrupts, and exceptions to trampoline.S
@ -192,6 +193,9 @@ devintr()
printf("unexpected interrupt irq=%d\n", irq); printf("unexpected interrupt irq=%d\n", irq);
} }
// the PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if(irq) if(irq)
plic_complete(irq); plic_complete(irq);

View file

@ -18,7 +18,7 @@
// the UART control registers. // the UART control registers.
// some have different meanings for // some have different meanings for
// read vs write. // read vs write.
// http://byterunner.com/16550.html // see http://byterunner.com/16550.html
#define RHR 0 // receive holding register (for input bytes) #define RHR 0 // receive holding register (for input bytes)
#define THR 0 // transmit holding register (for output bytes) #define THR 0 // transmit holding register (for output bytes)
#define IER 1 // interrupt enable register #define IER 1 // interrupt enable register
@ -30,6 +30,15 @@
#define ReadReg(reg) (*(Reg(reg))) #define ReadReg(reg) (*(Reg(reg)))
#define WriteReg(reg, v) (*(Reg(reg)) = (v)) #define WriteReg(reg, v) (*(Reg(reg)) = (v))
// the transmit output buffer.
struct spinlock uart_tx_lock;
#define UART_TX_BUF_SIZE 32
char uart_tx_buf[UART_TX_BUF_SIZE];
int uart_tx_w; // write next to uart_tx_buf[uart_tx_w++]
int uart_tx_r; // read next from uart_tx_buf[uar_tx_r++]
void uartstart();
void void
uartinit(void) uartinit(void)
{ {
@ -52,18 +61,79 @@ uartinit(void)
// reset and enable FIFOs. // reset and enable FIFOs.
WriteReg(FCR, 0x07); WriteReg(FCR, 0x07);
// enable receive interrupts. // enable transmit and receive interrupts.
WriteReg(IER, 0x01); WriteReg(IER, 0x02 | 0x01);
initlock(&uart_tx_lock, "uart");
} }
// write one output character to the UART. // add a character to the output buffer and tell the
// UART to start sending if it isn't already.
//
// usually called from the top-half -- by a process
// calling write(). can also be called from a uart
// interrupt to echo a received character, or by printf
// or panic from anywhere in the kernel.
//
// the block argument controls what happens if the
// buffer is full. for write(), block is 1, and the
// process waits. for kernel printf's and echoed
// characters, block is 0, and the character is
// discarded; this is necessary since sleep() is
// not possible in interrupts.
void void
uartputc(int c) uartputc(int c, int block)
{ {
// wait for Transmit Holding Empty to be set in LSR. acquire(&uart_tx_lock);
while((ReadReg(LSR) & (1 << 5)) == 0) while(1){
; if(((uart_tx_w + 1) % UART_TX_BUF_SIZE) == uart_tx_r){
WriteReg(THR, c); // buffer is full.
if(block){
// wait for uartstart() to open up space in the buffer.
sleep(&uart_tx_r, &uart_tx_lock);
} else {
// caller does not want us to wait.
release(&uart_tx_lock);
return;
}
} else {
uart_tx_buf[uart_tx_w] = c;
uart_tx_w = (uart_tx_w + 1) % UART_TX_BUF_SIZE;
uartstart();
release(&uart_tx_lock);
return;
}
}
}
// if the UART is idle, and a character is waiting
// in the transmit buffer, send it.
// caller must hold uart_tx_lock.
// called from both the top- and bottom-half.
void
uartstart()
{
while(1){
if(uart_tx_w == uart_tx_r){
// transmit buffer is empty.
return;
}
if((ReadReg(LSR) & (1 << 5)) == 0){
// the UART transmit holding register is full,
// so we cannot give it another byte.
// it will interrupt when it's ready for a new byte.
return;
}
int c = uart_tx_buf[uart_tx_r];
uart_tx_r = (uart_tx_r + 1) % UART_TX_BUF_SIZE;
// maybe uartputc() is waiting for space in the buffer.
wakeup(&uart_tx_r);
WriteReg(THR, c);
}
} }
// read one input character from the UART. // read one input character from the UART.
@ -79,14 +149,22 @@ uartgetc(void)
} }
} }
// trap.c calls here when the uart interrupts. // handle a uart interrupt, raised because input has
// arrived, or the uart is ready for more output, or
// both. called from trap.c.
void void
uartintr(void) uartintr(void)
{ {
// read and process incoming characters.
while(1){ while(1){
int c = uartgetc(); int c = uartgetc();
if(c == -1) if(c == -1)
break; break;
consoleintr(c); consoleintr(c);
} }
// send buffered characters.
acquire(&uart_tx_lock);
uartstart();
release(&uart_tx_lock);
} }