Skip to content

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);
}