Appearance
uart.c
TIP
low-level driver routines for 16550a UART.
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "defs.h"
TIP
the UART control registers are memory-mapped at address UART0. this macro returns the address of one of the registers.
#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg)))
TIP
the UART control registers. some have different meanings for read vs write. see http://byterunner.com/16550.html
#define RHR 0
#define THR 0
#define IER 1
#define IER_RX_ENABLE (1<<0)
#define IER_TX_ENABLE (1<<1)
#define FCR 2
#define FCR_FIFO_ENABLE (1<<0)
#define FCR_FIFO_CLEAR (3<<1)
#define ISR 2
#define LCR 3
#define LCR_EIGHT_BITS (3<<0)
#define LCR_BAUD_LATCH (1<<7)
#define LSR 5
#define LSR_RX_READY (1<<0)
#define LSR_TX_IDLE (1<<5)
#define ReadReg(reg) (*(Reg(reg)))
#define WriteReg(reg, v) (*(Reg(reg)) = (v))
TIP
the transmit output buffer.
struct spinlock uart_tx_lock;
#define UART_TX_BUF_SIZE 32
char uart_tx_buf[UART_TX_BUF_SIZE];
uint64 uart_tx_w;
uint64 uart_tx_r;
extern volatile int panicked;
void uartstart();
void
uartinit(void)
{
TIP
disable interrupts.
WriteReg(IER, 0x00);
TIP
special mode to set baud rate.
WriteReg(LCR, LCR_BAUD_LATCH);
TIP
LSB for baud rate of 38.4K.
WriteReg(0, 0x03);
TIP
MSB for baud rate of 38.4K.
WriteReg(1, 0x00);
TIP
leave set-baud mode, and set word length to 8 bits, no parity.
WriteReg(LCR, LCR_EIGHT_BITS);
TIP
reset and enable FIFOs.
WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
TIP
enable transmit and receive interrupts.
WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
initlock(&uart_tx_lock, "uart");
}
TIP
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);
if(panicked){
for(;;)
;
}
while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){
TIP
buffer is full. wait for uartstart() to open up space in the buffer.
sleep(&uart_tx_r, &uart_tx_lock);
}
uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;
uart_tx_w += 1;
uartstart();
release(&uart_tx_lock);
}
TIP
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();
if(panicked){
for(;;)
;
}
TIP
wait for Transmit Holding Empty to be set in LSR.
while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
;
WriteReg(THR, c);
pop_off();
}
TIP
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){
TIP
transmit buffer is empty.
ReadReg(ISR);
return;
}
if((ReadReg(LSR) & LSR_TX_IDLE) == 0){
TIP
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_BUF_SIZE];
uart_tx_r += 1;
TIP
maybe uartputc() is waiting for space in the buffer.
wakeup(&uart_tx_r);
WriteReg(THR, c);
}
}
TIP
read one input character from the UART. return -1 if none is waiting.
int
uartgetc(void)
{
if(ReadReg(LSR) & 0x01){
TIP
input data is ready.
return ReadReg(RHR);
} else {
return -1;
}
}
TIP
handle a uart interrupt, raised because input has arrived, or the uart is ready for more output, or both. called from devintr().
void
uartintr(void)
{
TIP
read and process incoming characters.
while(1){
int c = uartgetc();
if(c == -1)
break;
consoleintr(c);
}
TIP
send buffered characters.
acquire(&uart_tx_lock);
uartstart();
release(&uart_tx_lock);
}