summaryrefslogtreecommitdiff
path: root/kernel/uart.c
diff options
context:
space:
mode:
authorFrans Kaashoek <[email protected]>2020-08-10 13:05:17 -0400
committerGitHub <[email protected]>2020-08-10 13:05:17 -0400
commitc31d35d8031c88b7e1ea8657cc9806dfdd4c3ef9 (patch)
treea6c903e1c61c08f4cb87700c320752a737081dcb /kernel/uart.c
parent90eb90b5e203299427c3fde8c996a48835fc93cf (diff)
parentd8fe1773b26758c7c7b8f36724cd822555b33612 (diff)
downloadxv6-labs-c31d35d8031c88b7e1ea8657cc9806dfdd4c3ef9.tar.gz
xv6-labs-c31d35d8031c88b7e1ea8657cc9806dfdd4c3ef9.tar.bz2
xv6-labs-c31d35d8031c88b7e1ea8657cc9806dfdd4c3ef9.zip
Merge branch 'riscv' into riscv
Diffstat (limited to 'kernel/uart.c')
-rw-r--r--kernel/uart.c121
1 files changed, 105 insertions, 16 deletions
diff --git a/kernel/uart.c b/kernel/uart.c
index 3a5cdc4..daf9f04 100644
--- a/kernel/uart.c
+++ b/kernel/uart.c
@@ -18,18 +18,35 @@
// the UART control registers.
// some have different meanings for
// read vs write.
-// 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
-#define FCR 2 // FIFO control register
-#define ISR 2 // interrupt status register
-#define LCR 3 // line control register
-#define LSR 5 // line status register
+// 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
+#define IER_TX_ENABLE (1<<0)
+#define IER_RX_ENABLE (1<<1)
+#define FCR 2 // FIFO control register
+#define FCR_FIFO_ENABLE (1<<0)
+#define FCR_FIFO_CLEAR (3<<1) // clear the content of the two FIFOs
+#define ISR 2 // interrupt status register
+#define LCR 3 // line control register
+#define LCR_EIGHT_BITS (3<<0)
+#define LCR_BAUD_LATCH (1<<7) // special mode to set baud rate
+#define LSR 5 // line status register
+#define LSR_RX_READY (1<<0) // input is waiting to be read from RHR
+#define LSR_TX_IDLE (1<<5) // THR can accept another character to send
#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)
{
@@ -37,7 +54,7 @@ uartinit(void)
WriteReg(IER, 0x00);
// special mode to set baud rate.
- WriteReg(LCR, 0x80);
+ WriteReg(LCR, LCR_BAUD_LATCH);
// LSB for baud rate of 38.4K.
WriteReg(0, 0x03);
@@ -47,23 +64,87 @@ uartinit(void)
// leave set-baud mode,
// and set word length to 8 bits, no parity.
- WriteReg(LCR, 0x03);
+ WriteReg(LCR, LCR_EIGHT_BITS);
// reset and enable FIFOs.
- WriteReg(FCR, 0x07);
+ WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
+
+ // enable transmit and receive interrupts.
+ WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
- // enable receive interrupts.
- WriteReg(IER, 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.
+// blocks if the output buffer is full.
+// because it may block, it can't be called
+// from interrupts; it's only suitable for use
+// by write().
void
uartputc(int c)
{
+ acquire(&uart_tx_lock);
+ while(1){
+ if(((uart_tx_w + 1) % UART_TX_BUF_SIZE) == uart_tx_r){
+ // buffer is full.
+ // wait for uartstart() to open up space in the buffer.
+ sleep(&uart_tx_r, &uart_tx_lock);
+ } 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;
+ }
+ }
+}
+
+// alternate version of uartputc() that doesn't
+// use interrupts, for use by kernel printf() and
+// to echo characters. it spins waiting for the uart's
+// output register to be empty.
+void
+uartputc_sync(int c)
+{
+ push_off();
+
// wait for Transmit Holding Empty to be set in LSR.
- while((ReadReg(LSR) & (1 << 5)) == 0)
+ while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
;
WriteReg(THR, c);
+
+ pop_off();
+}
+
+// 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) & LSR_TX_IDLE) == 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 +160,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);
}