diff options
Diffstat (limited to 'serial.h')
| -rw-r--r-- | serial.h | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/serial.h b/serial.h new file mode 100644 index 0000000..beeaf50 --- /dev/null +++ b/serial.h @@ -0,0 +1,210 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include <stdint.h> + +#include "bda.h" + +enum SERIAL_UART_REGS { + SERIAL_TX = 0, + SERIAL_RX = 0, + /** (only with DLAB enabled) LSB of divisor/baud rate */ + SERIAL_DIVISOR_LO = 0, + /** (only with DLAB enabled) MSB of divisor/baud rate */ + SERIAL_DIVISOR_HI = 1, + /** Interrupt Enable Register(read/write) */ + SERIAL_IER = 1, + /** Fifo Control Register (write-only) */ + SERIAL_FCR = 2, + /** Interrupt Identification Register (read-only) */ + SERIAL_IIR = 2, + /** Line Control Register (read/write), contains DLAB bit */ + SERIAL_LCR = 3, + /** Modem Control Register (read/write) */ + SERIAL_MCR = 4, + /** Line Status Register (read-only) */ + SERIAL_LSR = 5, + /** Modem Status Register (read-only) */ + SERIAL_MSR = 6 +}; + +#if 0 +// Comes straight from https://wiki.osdev.org/Serial_Ports#Initialization +outp(DLOG_TARGET_PORT + 1, 0x00); // Disable all interrupts +outp(DLOG_TARGET_PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) +outp(DLOG_TARGET_PORT + 0, 0x01); // Set divisor to 1 (lo byte) 115200 baud +outp(DLOG_TARGET_PORT + 1, 0x00); // (hi byte) +outp(DLOG_TARGET_PORT + 3, 0x03); // 8 bits, no parity, one stop bit +outp(DLOG_TARGET_PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold +outp(DLOG_TARGET_PORT + 4, 0x03); // RTS/DSR set, IRQs disabled +#endif + +enum SERIAL_DIVISOR { + SERIAL_DIVISOR_115200 = 1, + SEIRAL_DIVISOR_9600 = 12, + SERIAL_DIVISOR_1200 = 96 +}; + +enum SERIAL_FCR { + SERIAL_FCR_RX_TRIGER_1 = 0 << 6, + SERIAL_FCR_RX_TRIGER_4 = 1 << 6, + SERIAL_FCR_RX_TRIGER_8 = 2 << 6, + SERIAL_FCR_RX_TRIGER_14 = 3 << 6, + SERIAL_FCR_DMA_SEL = 1 << 3, + SERIAL_FCR_TX_RESET = 1 << 2, + SERIAL_FCR_RX_RESET = 1 << 1, + SERIAL_FCR_FIFO_ENABLE = 1 << 0, +}; + +enum SERIAL_LCR { + /** Word Length = 5 bits */ + SERIAL_LCR_WL_5 = 0, + SERIAL_LCR_WL_6 = 1, + SERIAL_LCR_WL_7 = 2, + SERIAL_LCR_WL_8 = 3, + /** STop Bit */ + SERIAL_LCR_STB = 1 << 2, + /** Parity ENable */ + SERIAL_LCR_PEN = 1 << 3, + /** Even Parity Select. */ + SERIAL_LCR_EPS = 1 << 4, + /** Stick Parity */ + SERIAL_LCR_SP = 1 << 5, + SERIAL_LCR_PAR_NONE = 0, + SERIAL_LCR_PAR_ODD = SERIAL_LCR_PEN, + SERIAL_LCR_PAR_EVEN = SERIAL_LCR_PEN | SERIAL_LCR_EPS, + SERIAL_LCR_PAR_MARK = SERIAL_LCR_PEN | SERIAL_LCR_SP, + SERIAL_LCR_PAR_SPACE = SERIAL_LCR_PEN | SERIAL_LCR_EPS | SERIAL_LCR_SP, + /** Set Break */ + SERIAL_LCR_SB = 1 << 6, + /** Divisor Latch Access Bit */ + SERIAL_LCR_DLAB = 1 << 7, +}; + +enum SERIAL_MCR { + SERIAL_MCR_DTR = 1 << 0, + SERIAL_MCR_RTS = 1 << 1, + SERIAL_MCR_OUT1 = 1 << 2, + SERIAL_MCR_OUT2 = 1 << 3, + SERIAL_MCR_LOOP = 1 << 4, +}; + +enum SERIAL_LSR { + /** Data Ready (i.e., data ready to be read) */ + SERIAL_LSR_DR = 1 << 0, + SERIAL_LSR_OE = 1 << 1, + SERIAL_LSR_PE = 1 << 2, + SERIAL_LSR_FE = 1 << 3, + SERIAL_LSR_BI = 1 << 4, + /** Transmitter Holding Register Empty (i.e. space to send data is available) */ + SERIAL_LSR_THRE = 1 << 5, + SERIAL_LSR_TEMT = 1 << 6, + SERIAL_LSR_RXERR = 1 << 7, +}; + +enum SERIAL_MSR { + SERIAL_MSR_DCD = 1 << 7, + SERIAL_MSR_RI = 1 << 6, + SERIAL_MSR_DSR = 1 << 5, + SERIAL_MSR_CTS = 1 << 4, + SERIAL_MSR_DDCD = 1 << 3, + SERIAL_MSR_TERI = 1 << 2, + SERIAL_MSR_DDSR = 1 << 1, + SERIAL_MSR_DCTS = 1 << 0 +}; + +struct serial_config +{ + uint8_t divisor_lo, divisor_hi; + uint8_t ier, lcr, mcr; +}; + +static unsigned serial_num_ports() +{ + uint8_t equipment = bda_get_byte(0x11); + uint8_t numports = (equipment & 0xE) >> 1; + return numports; +} + +static uint16_t serial_get_iobase(unsigned port) +{ + uint16_t bdaport = bda_get_word((port - 1) * 2); + if (bdaport > 10) { + return bdaport; + } else if (port == 1) { + // Even if BIOS says no, hardcode the basic COM1 port at least. + return 0x3F8; + } + return 0; +} + +static void serial_save_config(unsigned iobase, struct serial_config *config) +{ + config->ier = inp(iobase + SERIAL_IER); + config->lcr = inp(iobase + SERIAL_LCR); + config->mcr = inp(iobase + SERIAL_MCR); + // Set DLAB so that we can read divisor + outp(iobase + SERIAL_LCR, config->lcr | SERIAL_LCR_DLAB); + config->divisor_lo = inp(iobase + SERIAL_DIVISOR_LO); + config->divisor_hi = inp(iobase + SERIAL_DIVISOR_HI); + // Restore DLAB to whatever it was + outp(iobase + SERIAL_LCR, config->lcr); +} + +static void serial_restore_config(unsigned iobase, const struct serial_config *config) +{ + outp(iobase + SERIAL_LCR, config->lcr | SERIAL_LCR_DLAB); + outp(iobase + SERIAL_DIVISOR_LO, config->divisor_lo); + outp(iobase + SERIAL_DIVISOR_HI, config->divisor_hi); + outp(iobase + SERIAL_LCR, config->lcr); + outp(iobase + SERIAL_MCR, config->mcr); + outp(iobase + SERIAL_IER, config->ier); +} + +static void serial_configure(unsigned iobase, uint16_t divisor, uint8_t lcr) +{ + outp(iobase + SERIAL_IER, 0); // Disable all interrupts + outp(iobase + SERIAL_LCR, SERIAL_LCR_DLAB); // Enable DLAB and clear all other flags + outp(iobase + SERIAL_DIVISOR_LO, divisor); + outp(iobase + SERIAL_DIVISOR_HI, divisor >> 8); + outp(iobase + SERIAL_LCR, lcr); // This also disables DLAB + // Enable and reset both FIFOs + outp(iobase + SERIAL_FCR, SERIAL_FCR_RX_TRIGER_14|SERIAL_FCR_FIFO_ENABLE|SERIAL_FCR_RX_RESET|SERIAL_FCR_TX_RESET); +} + +static inline void serial_set_modem_control(unsigned iobase, uint8_t mcr) +{ + outp(iobase + SERIAL_MCR, mcr); +} + +static inline void serial_set_modem_status(unsigned iobase, uint8_t msr) +{ + outp(iobase + SERIAL_MSR, msr); +} + +static inline uint8_t serial_get_line_status(unsigned iobase) +{ + return inp(iobase + SERIAL_LSR); +} + +static inline bool serial_data_ready(unsigned iobase) +{ + return serial_get_line_status(iobase) & SERIAL_LSR_DR; +} + +static inline uint8_t serial_read_data(unsigned iobase) +{ + return inp(iobase + SERIAL_RX); +} + +static inline bool serial_tx_ready(unsigned iobase) +{ + return serial_get_line_status(iobase) & SERIAL_LSR_THRE; +} + +static inline void serial_send_data(unsigned iobase, uint8_t data) +{ + outp(iobase + SERIAL_TX, data); +} + +#endif // SERIAL_H |
