diff --git a/kernel-src/include/kernel/interrupt.h b/kernel-src/include/kernel/interrupt.h index d4fae69f..d6de8ec9 100644 --- a/kernel-src/include/kernel/interrupt.h +++ b/kernel-src/include/kernel/interrupt.h @@ -17,6 +17,7 @@ #define IPL_TIMER 1 #define IPL_NET 400 #define IPL_DISK 500 +#define IPL_SERIAL 550 #define IPL_KEYBOARD 600 #define IPL_MOUSE 650 #define IPL_ACPI 700 diff --git a/kernel-src/io/serial/pc_com.c b/kernel-src/io/serial/pc_com.c new file mode 100644 index 00000000..0e487dc5 --- /dev/null +++ b/kernel-src/io/serial/pc_com.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __x86_64__ +#include +#endif + +#define PORT_RX 0 +#define PORT_TX 0 +#define PORT_INTERRUPT_ENABLE 1 +#define PORT_DIVISOR_LOW 0 +#define PORT_DIVISOR_HIGH 1 +#define PORT_INTERRUPT_ID 2 +#define PORT_FIFO_CONTROL 2 +#define PORT_LINE_CONTROL 3 +#define PORT_MODEM_CONTROL 4 +#define PORT_LINE_STATUS 5 +#define PORT_MODEM_STATUS 6 + +#define LINE_STATUS_DATA_READY 1 +#define LINE_STATUS_TX_BUFFER_EMPTY 32 + +#define MODEM_CONTROL_DATA_TERMINAL_READY 1 +#define MODEM_CONTROL_REQUEST_TO_SEND 2 +#define MODEM_CONTROL_AUX_1 4 +#define MODEM_CONTROL_AUX_2 8 + +#define INTERRUPT_STATUS_TX_EMPTY 2 + +#define INTERRUPT_ENABLE_NONE 0 +#define INTERRUPT_ENABLE_DATA_AVAILABLE 1 +#define INTERRUPT_ENABLE_TX_EMPTY 2 + +#define LINE_CONTROL_DIVISOR_LATCH 0x80 +#define LINE_CONTROL_ENABLE_BREAK 0x40 +#define LINE_CONTROL_PARITY_SPACE 0x38 +#define LINE_CONTROL_PARITY_MARK 0x28 +#define LINE_CONTROL_PARITY_EVEN 0x18 +#define LINE_CONTROL_PARITY_ODD 8 +#define LINE_CONTROL_STOP_BITS 4 +#define LINE_CONTROL_WORD_LENGTH_8_BITS 3 +#define LINE_CONTROL_WORD_LENGTH_7_BITS 2 +#define LINE_CONTROL_WORD_LENGTH_6_BITS 1 + +#define BASE_BAUD 115200 + +// TODO make a proper serial.c to abstract most serial ports out. +// for now, only pc com will be implemented +// TODO FIFO support + +typedef struct { + int id; + int io_base; + uacpi_resource_irq irq; + + ringbuffer_t tx_ringbuffer; + spinlock_t tx_spinlock; // locked in IPL_SERIAL + + ringbuffer_t rx_ringbuffer; + + thread_t *handler_thread; + semaphore_t rx_semaphore; + + tty_t *tty; + + int current_divisor; +} pc_com_t; + +static inline void write_port(pc_com_t *pc_com, int port, int value) { + outb(pc_com->io_base + port, value); +} + +static inline int read_port(pc_com_t *pc_com, int port) { + return inb(pc_com->io_base + port); +} + +static bool has_data(pc_com_t *pc_com) { + return read_port(pc_com, PORT_LINE_STATUS) & LINE_STATUS_DATA_READY; +} + +static char read_data(pc_com_t *pc_com) { + return read_port(pc_com, PORT_RX); +} + +static void process_data(pc_com_t *pc_com) { + while (has_data(pc_com)) { + char c = read_data(pc_com); + if (ringbuffer_write(&pc_com->rx_ringbuffer, &c, 1)) + semaphore_signal(&pc_com->rx_semaphore); + } +} + +static bool can_transmit(pc_com_t *pc_com) { + return read_port(pc_com, PORT_LINE_STATUS) & LINE_STATUS_TX_BUFFER_EMPTY; +} + +static void transmit_data(pc_com_t *pc_com) { + char c; + ringbuffer_read(&pc_com->tx_ringbuffer, &c, 1); + write_port(pc_com, PORT_TX, c); +} + +static void set_irqs(pc_com_t *pc_com, int irqs) { + write_port(pc_com, PORT_INTERRUPT_ENABLE, irqs); +} + +// expects I/O to be disabled +static void set_baud_divisor(pc_com_t *pc_com, int divisor) { + int old_port = read_port(pc_com, PORT_LINE_CONTROL); + write_port(pc_com, PORT_LINE_CONTROL, LINE_CONTROL_DIVISOR_LATCH); + + write_port(pc_com, PORT_DIVISOR_LOW, divisor & 0xff); + write_port(pc_com, PORT_DIVISOR_HIGH, (divisor >> 8) & 0xff); + + write_port(pc_com, PORT_LINE_CONTROL, old_port); + + pc_com->current_divisor = divisor; +} + +static void disable_io_and_flush_input(pc_com_t *pc_com) { + write_port(pc_com, PORT_MODEM_CONTROL, 0); + + process_data(pc_com); +} + +static void termios_callback(void *internal, termios_t *termios) { + pc_com_t *pc_com = internal; + + int baud = termios_baud_to_number(termios); + + int new_divisor = (baud == 0) ? 1 : BASE_BAUD / baud; + if (new_divisor == 0) + new_divisor = 1; + + if (new_divisor != pc_com->current_divisor) { + disable_io_and_flush_input(pc_com); + + set_baud_divisor(pc_com, new_divisor); + } + + int line_control = (termios->c_cflag & CSTOPB) ? LINE_CONTROL_STOP_BITS : 0; + + switch (termios->c_cflag & CSIZE) { + case CS6: + line_control = LINE_CONTROL_WORD_LENGTH_6_BITS; + break; + case CS7: + line_control = LINE_CONTROL_WORD_LENGTH_7_BITS; + break; + case CS8: + line_control = LINE_CONTROL_WORD_LENGTH_8_BITS; + break; + default: + } + + if (termios->c_cflag & PARENB) + line_control = (termios->c_cflag & PARODD) ? LINE_CONTROL_PARITY_ODD : LINE_CONTROL_PARITY_EVEN; + + write_port(pc_com, PORT_LINE_CONTROL, line_control); + + write_port(pc_com, PORT_MODEM_CONTROL, + MODEM_CONTROL_DATA_TERMINAL_READY | MODEM_CONTROL_REQUEST_TO_SEND | MODEM_CONTROL_AUX_1 | MODEM_CONTROL_AUX_2); +} + +static void handler_thread(void) { + pc_com_t *pc_com = current_thread()->kernelarg; + + for (;;) { + semaphore_wait(&pc_com->rx_semaphore, false); + + char c; + ringbuffer_read(&pc_com->rx_ringbuffer, &c, 1); + tty_process(pc_com->tty, c); + } +} + +static size_t write_tty(void *internal, char *str, size_t size) { + pc_com_t *pc_com = internal; + + long ipl = spinlock_acquire_raise_ipl(&pc_com->tx_spinlock, IPL_SERIAL); + + size_t written = ringbuffer_write(&pc_com->tx_ringbuffer, str, size); + if (can_transmit(pc_com)) + transmit_data(pc_com); + + spinlock_release_lower_ipl(&pc_com->tx_spinlock, ipl); + + return written; +} + +// TODO enqueue dpc and call tty_process directly from it once it is safe to be called from an interrupt context +static void irq(isr_t *self, context_t *) { + pc_com_t *pc_com = self->priv; + + int interrupt_status = read_port(pc_com, PORT_INTERRUPT_ID) & 0x0e; + + // check if there is data to be received + process_data(pc_com); + + // check if there is data to be transmitted and tx buffer is empty + spinlock_acquire(&pc_com->tx_spinlock); + + if (((interrupt_status == INTERRUPT_STATUS_TX_EMPTY) || can_transmit(pc_com)) && RINGBUFFER_DATACOUNT(&pc_com->tx_ringbuffer)) + transmit_data(pc_com); + + spinlock_release(&pc_com->tx_spinlock); +} + +static uacpi_iteration_decision set_com_resource(void *user, uacpi_resource *res) { + pc_com_t *pc_com = user; + switch (res->type) { + case UACPI_RESOURCE_TYPE_IRQ: + pc_com->irq = res->irq; + break; + case UACPI_RESOURCE_TYPE_IO: + pc_com->io_base = res->io.minimum; + break; + default: + printf("com%d: unknown resource type %d\n", pc_com->id, res->type); + } + + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static int pc_com_id = 0; + +static uacpi_iteration_decision init_com(void *, uacpi_namespace_node *node, unsigned int) { + uacpi_resources *res; + + pc_com_t *pc_com = alloc(sizeof(pc_com_t)); + __assert(pc_com); + __assert(ringbuffer_init(&pc_com->tx_ringbuffer, 1000) == 0); + __assert(ringbuffer_init(&pc_com->rx_ringbuffer, 1000) == 0); + + pc_com->id = pc_com_id++; + SEMAPHORE_INIT(&pc_com->rx_semaphore, 0); + SPINLOCK_INIT(pc_com->tx_spinlock); + + // get io port and gsi + uacpi_status ret = uacpi_get_current_resources(node, &res); + if (ret) { + printf("com%d: failed to get uacpi resources: %s\n", pc_com->id, uacpi_status_to_string(ret)); + return UACPI_ITERATION_DECISION_NEXT_PEER; + } + + ret = uacpi_for_each_resource(res, set_com_resource, pc_com); + if (ret) { + printf("com%d: failed to process uacpi resources: %s\n", pc_com->id, uacpi_status_to_string(ret)); + return UACPI_ITERATION_DECISION_NEXT_PEER; + } + + uacpi_free_resources(res); + + if (pc_com->irq.num_irqs != 1) { + printf("com%d: unsupported irq count\n"); + return UACPI_ITERATION_DECISION_NEXT_PEER; + } + + // set up port + set_irqs(pc_com, INTERRUPT_ENABLE_NONE); + set_baud_divisor(pc_com, 3); + write_port(pc_com, PORT_LINE_CONTROL, LINE_CONTROL_WORD_LENGTH_8_BITS); + set_irqs(pc_com, INTERRUPT_ENABLE_DATA_AVAILABLE | INTERRUPT_ENABLE_TX_EMPTY); + write_port(pc_com, PORT_MODEM_CONTROL, + MODEM_CONTROL_DATA_TERMINAL_READY | MODEM_CONTROL_REQUEST_TO_SEND | MODEM_CONTROL_AUX_1 | MODEM_CONTROL_AUX_2); + + // set up irq + isr_t *isr = interrupt_allocate(irq, ARCH_EOI, IPL_SERIAL); + __assert(isr); + isr->priv = pc_com; + + #ifdef __x86_64__ + arch_ioapic_setirq(pc_com->irq.irqs[0], isr->id & 0xff, current_cpu_id(), false); + #else + __assert(!"TODO"); + #endif + + pc_com->handler_thread = sched_newthread(handler_thread, PAGE_SIZE, 0, NULL, NULL); + __assert(pc_com->handler_thread); + pc_com->handler_thread->kernelarg = pc_com; + sched_queue(pc_com->handler_thread); + + char tty_name[10]; + snprintf(tty_name, 10, "com%d", pc_com->id); + + pc_com->tty = tty_create(tty_name, write_tty, NULL, termios_callback, pc_com); + if (pc_com->tty == NULL) { + printf("%s: failed to create tty\n", tty_name); + return UACPI_ITERATION_DECISION_NEXT_PEER; + } + + printf("com%d: port %04x isr %x\n", pc_com->id, pc_com->io_base, isr->id); + + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static void pc_com_find_devices(void) { + uacpi_find_devices("PNP0500", init_com, NULL); + uacpi_find_devices("PNP0501", init_com, NULL); +} + +// TODO: when device system is rewritten, have this be discovered by the acpi bus instead +INIT_ROUTINE_DEFINE(pc_com, INIT_ROUTINE_FLAGS_NONE, pc_com_find_devices, acpi, tty); diff --git a/tools/init/main.c b/tools/init/main.c index 50a81250..6202d84d 100644 --- a/tools/init/main.c +++ b/tools/init/main.c @@ -152,9 +152,9 @@ static void reap(int signum) { } } -static void snatchconsole(void) { - int rfd = open("/dev/console", O_RDONLY); - int wfd = open("/dev/console", O_WRONLY); +static void snatchconsole(const char *console) { + int rfd = open(console, O_RDONLY); + int wfd = open(console, O_WRONLY); close(0); close(1); close(2); @@ -213,7 +213,7 @@ static void dorootshell(void) { handlecommands(); } -static void dologinprompt(void) { +static void spawn_login_prompt(const char *console) { pid_t pid = fork(); if (pid == -1) { perror("init: fork failed"); @@ -222,13 +222,16 @@ static void dologinprompt(void) { if (pid == 0) { setsid(); - snatchconsole(); + snatchconsole(console); unblocksignals(); execl("/bin/login", NULL); perror("init: exec /bin/login failed"); exit(EXIT_FAILURE); } +} +static void dologinprompt(const char *console) { + spawn_login_prompt(console); handlecommands(); } @@ -340,12 +343,16 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // start a login shell in /dev/com0 if it exists + if (access("/dev/com0", F_OK) == 0) + spawn_login_prompt("/dev/com0"); + if (argc >1 && strcmp(argv[1], "withlogin") == 0) - dologinprompt(); + dologinprompt("/dev/console"); // open /dev/console as the controlling terminal if we're not doing login printf("init: reopening /dev/console as the controlling terminal\n"); - snatchconsole(); + snatchconsole("/dev/console"); if (argc > 1 && strcmp(argv[1], "withx") == 0) dostartwm();