#ifndef SERIAL_H #define SERIAL_H #include #include #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, SERIAL_DIVISOR = SERIAL_DIVISOR_LO, /** 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_IER { /** No interrupts */ SERIAL_IER_NONE = 0, /** Enable Receiver Buffer Full Interrupt */ SERIAL_IER_ERBFI = 1 << 0, /** Enable Transmitter Buffer Empty Interrupt */ SERIAL_IER_ETBEI = 1 << 1, /** Enable Line Status Interrupt */ SERIAL_IER_ELSI = 1 << 2, /** Enable Delta Status Signals Interrupt */ SERIAL_IER_EDSSI = 1 << 3, }; 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, SERIAL_FCR_FIFO_RESET = SERIAL_FCR_TX_RESET | SERIAL_FCR_RX_RESET, SERIAL_FCR_FIFO_DISABLE = 0 }; enum SERIAL_IIR { /** Mask for retrieving the interrupt that occured. See enum SERIAL_IID. */ SERIAL_IIR_ID = 0xF, SERIAL_IIR_FIFO1 = 1 << 6, SERIAL_IIR_FIFO2 = 1 << 7, }; enum SERIAL_IID { /** No interrupt. */ SERIAL_IID_NONE = 1, /** Highest priority: line error. OE, PE, etc. Serviced by reading LSR. */ SERIAL_IID_ERROR = 6, /** Second highest priority: data received or trigger reached. Serviced by reading data. */ SERIAL_IID_DATA = 4, /** Second highest priority: stale data in FIFO. Serviced by reading data. */ SERIAL_IID_STALE = 0xC, /** Third priority: transmitter buffer has room. Serviced by reading this reg/writing. */ SERIAL_IID_THRE = 2, /** Fourth priority: MSR change. Serviced by reading MSR. */ SERIAL_IID_MODEM = 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 { uint16_t divisor; uint8_t ier, lcr, mcr; }; static unsigned serial_num_ports() { uint16_t equipment = bda_get_equipment(); uint8_t numports = (equipment & 0xE00) >> 9; return numports; } static uint16_t serial_get_iobase(unsigned port) { uint16_t bdaport = bda_get_word((port - 1) * 2); if (bdaport > 10) { return bdaport; } // Even if BIOS says no, hardcode basic COM1/COM2 at least. switch (port) { case 1: return 0x3F8; case 2: return 0x2F8; default: return 0; } } static uint8_t serial_get_irq(unsigned port) { switch (port) { case 1: case 3: return 4; case 2: case 4: return 3; default: 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 = inpw(iobase + SERIAL_DIVISOR); // 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); outpw(iobase + SERIAL_DIVISOR, config->divisor); outp(iobase + SERIAL_LCR, config->lcr); outp(iobase + SERIAL_MCR, config->mcr); outp(iobase + SERIAL_IER, config->ier); } /// Configure line characteristics (speed, databytes, etc.) /// Disables interrupts static void serial_configure_line(unsigned iobase, uint16_t divisor, uint8_t lcr) { outp(iobase + SERIAL_LCR, SERIAL_LCR_DLAB); // Enable DLAB and clear all other flags outpw(iobase + SERIAL_DIVISOR, divisor); outp(iobase + SERIAL_LCR, lcr); // This also disables DLAB } /// Configures when exceptions are going to be raised /// it also clears all pending interrupts /// and enables/disables the OUT2 gator here, which I'm not sure if is conceptually clean static void serial_configure_interrupts(unsigned iobase, uint8_t ier) { uint8_t mcr = inp(iobase + SERIAL_MCR); outp(iobase + SERIAL_IER, ier); if (ier) { mcr |= SERIAL_MCR_OUT2; // Actually enables IRQ line for this UART } else { mcr &= ~SERIAL_MCR_OUT2; } outp(iobase + SERIAL_MCR, mcr); } static inline void serial_set_modem_control(unsigned iobase, uint8_t mcr) { outp(iobase + SERIAL_MCR, mcr); } static inline void serial_set_fifo_control(unsigned iobase, uint8_t fcr) { outp(iobase + SERIAL_FCR, fcr); } static inline uint8_t serial_read_pending_interrupt(unsigned iobase) { return inp(iobase + SERIAL_IIR) & SERIAL_IIR_ID; } static inline uint8_t serial_get_line_status(unsigned iobase) { return inp(iobase + SERIAL_LSR); } static inline uint8_t serial_get_modem_status(unsigned iobase) { return inp(iobase + SERIAL_MSR); } 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