From 27057bc9b467db64a3de600f27d6fa3239a04c88 Mon Sep 17 00:00:00 2001 From: Robert Morris Date: Mon, 20 Jul 2020 06:59:26 -0400 Subject: [PATCH] interrupt-driven uart output, hopefully a nice example for teaching. --- kernel/console.c | 8 ++-- kernel/defs.h | 2 +- kernel/plic.c | 2 - kernel/trap.c | 8 +++- kernel/uart.c | 98 +++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/kernel/console.c b/kernel/console.c index 87a83ff..a97ef30 100644 --- a/kernel/console.c +++ b/kernel/console.c @@ -27,6 +27,8 @@ // // send one character to the uart. +// called by printf, and to echo input characters, +// but not from write(). // void consputc(int c) @@ -40,9 +42,9 @@ consputc(int c) if(c == BACKSPACE){ // if the user typed backspace, overwrite with a space. - uartputc('\b'); uartputc(' '); uartputc('\b'); + uartputc('\b', 0); uartputc(' ', 0); uartputc('\b', 0); } else { - uartputc(c); + uartputc(c, 0); } } @@ -70,7 +72,7 @@ consolewrite(int user_src, uint64 src, int n) char c; if(either_copyin(&c, user_src, src+i, 1) == -1) break; - consputc(c); + uartputc(c, 1); } release(&cons.lock); diff --git a/kernel/defs.h b/kernel/defs.h index f33f1f6..1b164a4 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -149,7 +149,7 @@ void usertrapret(void); // uart.c void uartinit(void); void uartintr(void); -void uartputc(int); +void uartputc(int, int); int uartgetc(void); // vm.c diff --git a/kernel/plic.c b/kernel/plic.c index eed8316..5acba39 100644 --- a/kernel/plic.c +++ b/kernel/plic.c @@ -33,7 +33,6 @@ int plic_claim(void) { int hart = cpuid(); - //int irq = *(uint32*)(PLIC + 0x201004); int irq = *(uint32*)PLIC_SCLAIM(hart); return irq; } @@ -43,6 +42,5 @@ void plic_complete(int irq) { int hart = cpuid(); - //*(uint32*)(PLIC + 0x201004) = irq; *(uint32*)PLIC_SCLAIM(hart) = irq; } diff --git a/kernel/trap.c b/kernel/trap.c index 77ff868..a63249e 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -91,8 +91,9 @@ usertrapret(void) { struct proc *p = myproc(); - // turn off interrupts, since we're switching - // now from kerneltrap() to usertrap(). + // we're about to switch the destination of traps from + // kerneltrap() to usertrap(), so turn off interrupts until + // we're back in user space, where usertrap() is correct. intr_off(); // send syscalls, interrupts, and exceptions to trampoline.S @@ -192,6 +193,9 @@ devintr() 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) plic_complete(irq); diff --git a/kernel/uart.c b/kernel/uart.c index 3a5cdc4..4d70b42 100644 --- a/kernel/uart.c +++ b/kernel/uart.c @@ -18,7 +18,7 @@ // the UART control registers. // some have different meanings for // read vs write. -// http://byterunner.com/16550.html +// see http://byterunner.com/16550.html #define RHR 0 // receive holding register (for input bytes) #define THR 0 // transmit holding register (for output bytes) #define IER 1 // interrupt enable register @@ -30,6 +30,15 @@ #define ReadReg(reg) (*(Reg(reg))) #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 uartinit(void) { @@ -52,18 +61,79 @@ uartinit(void) // reset and enable FIFOs. WriteReg(FCR, 0x07); - // enable receive interrupts. - WriteReg(IER, 0x01); + // enable transmit and receive interrupts. + 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 -uartputc(int c) +uartputc(int c, int block) { - // wait for Transmit Holding Empty to be set in LSR. - while((ReadReg(LSR) & (1 << 5)) == 0) - ; - WriteReg(THR, c); + acquire(&uart_tx_lock); + while(1){ + if(((uart_tx_w + 1) % UART_TX_BUF_SIZE) == uart_tx_r){ + // 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. @@ -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 uartintr(void) { + // read and process incoming characters. while(1){ int c = uartgetc(); if(c == -1) break; consoleintr(c); } + + // send buffered characters. + acquire(&uart_tx_lock); + uartstart(); + release(&uart_tx_lock); }