summaryrefslogtreecommitdiff
path: root/kernel/uart.c
diff options
context:
space:
mode:
authorRobert Morris <[email protected]>2020-07-20 06:59:26 -0400
committerFrans Kaashoek <[email protected]>2020-08-10 11:19:10 -0400
commit27057bc9b467db64a3de600f27d6fa3239a04c88 (patch)
tree51f344b42031f0a5a41fcf08d00694bb13f10997 /kernel/uart.c
parent1e72d5ca087985938589ce509ace4914039860d3 (diff)
downloadxv6-labs-27057bc9b467db64a3de600f27d6fa3239a04c88.tar.gz
xv6-labs-27057bc9b467db64a3de600f27d6fa3239a04c88.tar.bz2
xv6-labs-27057bc9b467db64a3de600f27d6fa3239a04c88.zip
interrupt-driven uart output, hopefully a nice example for teaching.
Diffstat (limited to 'kernel/uart.c')
-rw-r--r--kernel/uart.c98
1 files changed, 88 insertions, 10 deletions
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");
+}
+
+// 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, int block)
+{
+ 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;
+ }
+ }
}
-// write one output character to the UART.
+// 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
-uartputc(int c)
+uartstart()
{
- // wait for Transmit Holding Empty to be set in LSR.
- while((ReadReg(LSR) & (1 << 5)) == 0)
- ;
- WriteReg(THR, c);
+ 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);
}