// // 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" // 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)) // the UART control registers. // some have different meanings for // read vs write. // 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 FCR 2 // FIFO control register #define ISR 2 // interrupt status register #define LCR 3 // line control register #define LSR 5 // line status register #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) { // disable interrupts. WriteReg(IER, 0x00); // special mode to set baud rate. WriteReg(LCR, 0x80); // LSB for baud rate of 38.4K. WriteReg(0, 0x03); // MSB for baud rate of 38.4K. WriteReg(1, 0x00); // leave set-baud mode, // and set word length to 8 bits, no parity. WriteReg(LCR, 0x03); // reset and enable FIFOs. WriteReg(FCR, 0x07); // 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. // 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) ; 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) & (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. // return -1 if none is waiting. int uartgetc(void) { if(ReadReg(LSR) & 0x01){ // input data is ready. return ReadReg(RHR); } else { return -1; } } // 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); }