nativeos/kernel/device/uart8250.c

285 lines
7.9 KiB
C

/**
* \file
* \brief UART 8250 driver
*
* UART 8250 is the basic serial port for the PC platform. While the COM port
* is deprecated and most computers don't have a COM port these days, it is
* still a extremely popular debug tool, because most virtual machines will
* provide a way to emulate COM ports, allowing to be used as a log or as a
* quick shell.
*
* The PC architecture supports up to four serial ports. At the moment,
* NativeOS will only support one serial port. Communication between the PC
* and the UART is handled via IO ports. There are eight ports per UART.
* For instance, for the first UART device these ports will be 0x3F8 to 0x3FF.
*
* Use cases for the UART port:
*
* \li The most important thing about the UART is the RX/TX (receive/transmit).
* OUT a byte to the 0x3F8 port, it will be send to the serial port.
* IN a byte from the 0x3F8 port, it will be received from the serial port.
* \li Then there is the line status register. It is in PORT+5 and you read
* the bits, you can have information about the status of the port, such as
* whether there is data waiting to be read.
* \li Settings are configured using the Line Control Register, which is in
* PORT+3. Update the value of this port to tweak the behaviour of the
* serial port. Should be used to tweak the preferences like baud rate or
* parity.
*
* Settings that can be configured realisticly:
*
* \li The baud rate. Turn the DLAB bit ON in the line control register, send
* the divisor for 115200 to use through PORT (LSB) and PORT+1 (MSB), turn
* off the DLAB bit.
* \li The data bits. Use bits 1 and 0 of the line control register to set it
* to 5 (00), 6 (01), 7 (10) or 8 (11). Nowadays 8 is the standard, and
* also the only accepted configuration at the moment.
* \li The stop bits, which are used for sync the bits of each byte. Set the
* bit 2 of the line control register to 0 for 1 bit, or 1 for 1.5 or 2
* bits (this use case depends on even bits per character). These days,
* standard is 1 bit.
* \li The parity, used for basic checksums to detect transmission errors.
* Use bit 3 to enable parity (0 = OFF, 1 = ON), and if ON, use the bits
* 4 and 5 to set the parity mode (00 = ODD, 01 = EVEN, 10 = MARK,
* 11 = SPACE). These days, standard is NO PARITY.
* \li Interrupts. With DLAB off, write a value to PORT+1 to configure the
* interrupt mode. Depending on the bits that are set to 1, you will
* receive more or less interruptions. Bit 0 = data available, bit 1 =
* transmitter is empty, bit 2 = error or break, bit 3 = status changed.
*
* Interrupts for the serial port will come from IRQ 12 for the first port,
* and IRQ 11 for the second port. Assuming that protected mode is enabled,
* which will be.
*/
#include <kernel/cpu/idt.h>
#include <machine/cpu.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/device.h>
#include <sys/ringbuf.h>
#include <sys/vfs.h>
#define UART_PORT 0x3F8
#define BAUD_RATE_115200 0
#define BAUD_RATE_38400 1
#define BAUD_RATE_19200 2
#define BAUD_RATE_9600 3
#define BAUD_RATE_7200 4
#define BAUD_RATE_4800 5
#define BAUD_RATE_3600 6
#define BAUD_RATE_2400 7
#define BAUD_RATE_2000 8
#define BAUD_RATE_1800 9
#define BAUD_RATE_1200 10
#define BAUD_RATE_600 11
#define BAUD_RATE_300 12
#define BAUD_RATE_150 13
#define BAUD_RATE_110 14
#define BAUD_RATE_50 15
/**
* \brief List of divisor values
* These are the accepted divisor values that can be set through the DLAB port
* when the baud rate is being configured. It is possible to pick one of these
* through code by using the BAUD_RATE constant definitions.
*/
static unsigned short uart8250_baud_divisors[] = {
0x001, // 115200
0x003, // 38400
0x006, // 19200
0x00C, // 9600
0x010, // 7200
0x018, // 4800
0x020, // 3600
0x030, // 2400
0x03A, // 2000
0x040, // 1800
0x060, // 1200
0x0C0, // 600
0x180, // 300
0x300, // 150
0x417, // 110
0x900, // 50
0x000, // NUL
};
static struct {
uint32_t flags; /**< Holds the flags of the current open mode. */
ringbuf_t *rx_buf; /**< Ringbuffer to store the bytes until read. */
} uart8250_context;
/**
* \brief Reconfigure the UART device
* Based on the current configuration of the context, set the device
*/
static void
uart8250_reconfigure(void)
{
unsigned short divisor = uart8250_baud_divisors[BAUD_RATE_19200];
port_out_byte(UART_PORT + 1, 0x00); // turn off interrupts
// Set the baud rate.
port_out_byte(UART_PORT + 3, 0x80); // DLAB = on
port_out_byte(UART_PORT + 0, divisor & 0xFF);
port_out_byte(UART_PORT + 1, divisor >> 8);
// Configure interrupts and status line.
// WHY DO I WASTE SO MANY HOURS DEBUGGING THIS PIECE OF ANCIENT SHIT
port_out_byte(UART_PORT + 3, 0x03); // 8N1 Mode
// port_outb(UART_PORT + 2, 0xC7); // FIFO and all this shit
port_out_byte(UART_PORT + 4, 0x07); // Modem, RTS, DTR, Interrupts ON
port_out_byte(UART_PORT + 1, 0x01); // Actually turn on ready interrupts
}
static void
acknowledge(void)
{
port_in_byte(UART_PORT + 2);
}
static int uart8250_init(void);
static void uart8250_tini(void);
static int uart8250_open(unsigned int flags);
static int uart8250_close(void);
static uint32_t uart8250_read(unsigned char *buf, uint32_t len);
static uint32_t uart8250_write(unsigned char *buf, uint32_t len);
static driver_t uart8250_driver = {
.drv_name = "uart8250",
.drv_flags = DV_FCHARDEV,
.drv_init = &uart8250_init,
.drv_tini = &uart8250_tini,
};
static device_t uart8250_device = {
.dev_family = &uart8250_driver,
.dev_open = &uart8250_open,
.dev_read_chr = &uart8250_read,
.dev_write_chr = &uart8250_write,
.dev_close = &uart8250_close,
};
static void
uart8250_interrupt(struct idt_data *idt)
{
uint8_t value;
/* Assert there is data. */
while (port_in_byte(UART_PORT + 5) & 1) {
value = port_in_byte(UART_PORT);
if (uart8250_context.flags & VO_FWRITE) {
ringbuf_write(uart8250_context.rx_buf, value);
}
}
acknowledge();
}
static int
uart8250_init(void)
{
device_install(&uart8250_device, "uart");
uart8250_reconfigure();
idt_set_handler(0x24, &uart8250_interrupt);
acknowledge();
return 0;
}
static void
uart8250_tini(void)
{
device_remove("uart");
}
static int
uart8250_open(unsigned int flags)
{
/* Device is already opened. */
if (uart8250_context.flags) {
return -1;
}
/* Reserve the device. */
uart8250_context.flags = flags;
uart8250_context.rx_buf = ringbuf_alloc(4096);
return 0;
}
static int
uart8250_close(void)
{
/* Is this possible? Do I need a spinlock? I don't want to know. */
if (!uart8250_context.flags) {
return -1;
} else {
ringbuf_free(uart8250_context.rx_buf);
uart8250_context.flags = 0;
return 0;
}
}
static uint32_t
uart8250_read(unsigned char *buf, uint32_t len)
{
size_t read_bytes = 0;
while (read_bytes <= len
&& ringbuf_test_ready(uart8250_context.rx_buf)) {
*buf++ = ringbuf_read(uart8250_context.rx_buf);
read_bytes++;
}
return read_bytes;
}
static uint32_t
uart8250_write_binary(unsigned char *buf, uint32_t len)
{
unsigned int write_bytes = 0;
while (len--) {
port_out_byte(UART_PORT, *buf++);
write_bytes++;
}
return write_bytes;
}
static uint32_t
uart8250_write_non_binary(unsigned char *buf, uint32_t len)
{
unsigned int write_bytes = 0;
while (len--) {
switch (*buf) {
case '\r':
port_out_byte(UART_PORT, '\r');
port_out_byte(UART_PORT, '\n');
write_bytes += 2;
buf++;
break;
case 127:
port_out_byte(UART_PORT, '\b');
write_bytes++;
buf++;
break;
default:
port_out_byte(UART_PORT, *buf++);
write_bytes++;
break;
}
}
return write_bytes;
}
static uint32_t
uart8250_write(unsigned char *buf, uint32_t len)
{
if (!buf || !*buf) {
return 0;
}
if (uart8250_context.flags & VO_FBINARY) {
return uart8250_write_binary(buf, len);
} else {
return uart8250_write_non_binary(buf, len);
}
}
DEVICE_DESCRIPTOR(uart8250, uart8250_driver);