diff --git a/Makefile b/Makefile index 1253729..870f3f2 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,9 @@ TEST_DEPS = src/baremetal-fib.s src/uart-print.s src/baremetal-poweroff.s USER_DEPS = src/boot.s src/uart-print.s src/baremetal-poweroff.s src/kernel.c \ src/syscalls.c src/pmp.c src/riscv.c src/fdt.c src/string.c \ src/proc_test.c src/spinlock.c src/proc.c src/context.s \ - src/pagealloc.c src/uart.c src/fs.c src/bakedinfs.c src/runflags.c \ - src/pipe.c \ + src/pagealloc.c src/fs.c src/bakedinfs.c src/runflags.c \ + src/pipe.c src/drivers/drivers.c src/drivers/uart/uart.c \ + src/plic.c \ user/src/userland.c user/src/usyscalls.S user/src/user-printf.s \ user/src/user-printf.c user/src/shell.c user/src/ustr.c TEST_SIFIVE_U_DEPS = $(TEST_DEPS) diff --git a/include/drivers/drivers.h b/include/drivers/drivers.h new file mode 100644 index 0000000..d5c5991 --- /dev/null +++ b/include/drivers/drivers.h @@ -0,0 +1,6 @@ +#ifndef _DRIVERS_H_ +#define _DRIVERS_H_ + +void drivers_init(); + +#endif // ifndef _DRIVERS_H_ diff --git a/include/drivers/uart/uart.h b/include/drivers/uart/uart.h new file mode 100644 index 0000000..df50709 --- /dev/null +++ b/include/drivers/uart/uart.h @@ -0,0 +1,67 @@ +#ifndef _UART_H_ +#define _UART_H_ + +#include "fs.h" + +#ifndef UART_BASE +#define UART_BASE 0x10010000 +#endif + +#define UART_TXDATA 0x00 +#define UART_RXDATA 0x04 +#define UART_TXCTRL 0x08 +#define UART_RXCTRL 0x0c +#define UART_IE 0x10 +#define UART_IP 0x14 +#define UART_BAUD_RATE_DIVISOR 0x18 + +#define UART_IE_TXWM 1 +#define UART_IE_RXWM 2 + +#define ASCII_DEL 0x7f + +#define UART_BUF_SZ 64 + +typedef struct uart_state_s { + // TODO: spinlock lock; + char rxbuf[UART_BUF_SZ]; + char txbuf[UART_BUF_SZ]; + int rx_rpos; + int rx_wpos; + int tx_rpos; + int tx_wpos; + int rx_num_newlines; // number of '\n' chars in the buffer + int rx_buff_full; // indicates that rxbuf got full +} uart_state_t; + +extern uart_state_t uart0; // defined in uart.c + +void uart_init(); + +// uart_enqueue_chars reads characters from rx FIFO and enqueues them into the +// uart0's rxbuf. If rxbuf is full, rx_buff_full is set to 1 and all +// incoming data is dropped on the floor (unless it's a backspace, which frees +// up some space instead of using up more buffer space). +// +// When a newline character gets enqueued, all processes waiting on uart0 are +// woken up to get them a chance to continue reading. +int uart_enqueue_chars(); + +// uart_writechar writes a single char to UART synchronously. +void uart_writechar(char ch); + +// uart_readline attempts to read a full line of characters from rxbuf. If +// rxbuf is empty or does not contain a newline character, the calling process +// yields execution and goes to sleep. +int32_t uart_readline(char* buf, uint32_t bufsize); + +int32_t uart_print(char const* data, uint32_t size); + +int32_t uart_read(file_t* f, uint32_t pos, void* buf, uint32_t bufsize); +int32_t uart_write(file_t* f, uint32_t pos, void* buf, uint32_t bufsize); + +// implemented in uart-print.s +extern int32_t uart_prints(char const* data); +extern int32_t uart_printc(char c); + +#endif // ifndef _UART_H_ diff --git a/include/machine/hifive1-revb.h b/include/machine/hifive1-revb.h index 08ca93d..50ed686 100644 --- a/include/machine/hifive1-revb.h +++ b/include/machine/hifive1-revb.h @@ -10,4 +10,8 @@ // system cost, can be driven by a factorytrimmed on-chip oscillator." #define ONE_SECOND 32768 +// https://github.com/sifive/freedom-e-sdk/blob/master/bsp/sifive-hifive1-revb/core.dts#L166 +// interrupts = <3>; +#define UART0_IRQ_NO 3 + #endif // ifndef _HIFIVE1_REVB_ diff --git a/include/machine/qemu_e.h b/include/machine/qemu_e.h index 3bc80a0..e465239 100644 --- a/include/machine/qemu_e.h +++ b/include/machine/qemu_e.h @@ -7,4 +7,8 @@ // It's used in riscv/sifive_e and in riscv/virt machines. #define ONE_SECOND (10*1000*1000) +// https://github.com/qemu/qemu/blob/master/include/hw/riscv/sifive_e.h#L82 +// SIFIVE_E_UART0_IRQ = 3, +#define UART0_IRQ_NO 3 + #endif // ifndef _QEMU_E_H_ diff --git a/include/machine/qemu_u.h b/include/machine/qemu_u.h index 9090839..f2b67c7 100644 --- a/include/machine/qemu_u.h +++ b/include/machine/qemu_u.h @@ -5,4 +5,8 @@ // https://github.com/qemu/qemu/blob/master/hw/riscv/sifive_u.c #define ONE_SECOND (1*1000*1000) +// https://github.com/qemu/qemu/blob/master/include/hw/riscv/sifive_u.h#L106 +// SIFIVE_U_UART0_IRQ = 4, +#define UART0_IRQ_NO 4 + #endif // ifndef _QEMU_U_H_ diff --git a/include/plic.h b/include/plic.h new file mode 100644 index 0000000..2bc9600 --- /dev/null +++ b/include/plic.h @@ -0,0 +1,28 @@ +#ifndef _PLIC_H_ +#define _PLIC_H_ + +#define PLIC_BASE 0x0c000000 +#define PLIC_PRIORITY (PLIC_BASE + 0x00000000) +#define PLIC_PENDING (PLIC_BASE + 0x00001000) +#define PLIC_ENABLE (PLIC_BASE + 0x00002000) +#define PLIC_THRESHOLD (PLIC_BASE + 0x00200000) +#define PLIC_CLAIM_RW (PLIC_BASE + 0x00200004) + +#define PLIC_MAX_PRIORITY 7 + +// plic_enable_intr enables a specified interrupt. +void plic_enable_intr(int intr_no); + +// plic_set_intr_priority sets a priority on a given interrupt. 7 is the +// highest priority, while a zero priority effectively disables an interrupt. +void plic_set_intr_priority(int intr_no, int priority); + +// plic_set_threshold sets a global threshold for PLIC interrupts. Only the +// interrupts with priority>threshold will be triggered. +void plic_set_threshold(int threshold); + +// plic_dispatch_interrupts does the actual job of handling a PLIC interrupt +// and calling the respective subsystems. +void plic_dispatch_interrupts(); + +#endif // ifndef _PLIC_H_ diff --git a/include/riscv.h b/include/riscv.h index 2ba3850..52b6ece 100644 --- a/include/riscv.h +++ b/include/riscv.h @@ -21,6 +21,11 @@ #define TRAP_DIRECT 0x00 #define TRAP_VECTORED 0x01 +#define MSTATUS_MPIE_BIT 7 + +#define MIE_MTIE_BIT 7 // mie.MTIE (Machine Timer Interrupt Enable) bit +#define MIE_MEIE_BIT 11 // mie.MEIE (Machine External Interrupt Enable) bit + unsigned int get_mhartid(); unsigned int get_mstatus(); void set_mstatus(unsigned int mstatus); diff --git a/include/uart.h b/include/uart.h deleted file mode 100644 index 081fe3f..0000000 --- a/include/uart.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _UART_H_ -#define _UART_H_ - -#include "fs.h" - -#ifndef UART_BASE -#define UART_BASE 0x10010000 -#endif - -#define UART_TXDATA 0 -#define UART_RXDATA 4 -#define UART_RXCTRL 0xc -#define UART_BAUD_RATE_DIVISOR 0x18 - -#define ASCII_DEL 0x7f - -void uart_init(); -char uart_readchar(); -void uart_writechar(char ch); -int32_t uart_readline(char* buf, uint32_t bufsize); -int32_t uart_print(char const* data, uint32_t size); - -int32_t uart_read(file_t* f, uint32_t pos, void* buf, uint32_t bufsize); -int32_t uart_write(file_t* f, uint32_t pos, void* buf, uint32_t bufsize); - -// implemented in uart-print.s -extern int32_t uart_prints(char const* data); -extern int32_t uart_printc(char c); - -#endif // ifndef _UART_H_ diff --git a/src/boot.s b/src/boot.s index ea65f3a..a8be7e2 100644 --- a/src/boot.s +++ b/src/boot.s @@ -282,7 +282,7 @@ interrupt_vector: .balign 4 j interrupt_noop # 10: reserved .balign 4 - j interrupt_noop # 11: machine external interrupt + j k_interrupt_plic # 11: machine external interrupt exception_vector: # 3.1.20 Machine Cause Register (mcause), Table 3.6: Machine cause register (mcause) values after trap. .balign 4 diff --git a/src/context.s b/src/context.s index 219e1f2..1023a98 100644 --- a/src/context.s +++ b/src/context.s @@ -18,6 +18,13 @@ k_interrupt_timer: # This will restore user registers from trap_frame and then mret: j ret_to_user +.globl k_interrupt_plic +k_interrupt_plic: + call kernel_plic_handler + + # This will restore user registers from trap_frame and then mret: + j ret_to_user + .globl ret_to_user ret_to_user: # load a pointer to trap_frame into t6: diff --git a/src/drivers/drivers.c b/src/drivers/drivers.c new file mode 100644 index 0000000..ccc778a --- /dev/null +++ b/src/drivers/drivers.c @@ -0,0 +1,5 @@ +#include "drivers/uart/uart.h" + +void drivers_init() { + uart_init(); +} diff --git a/src/drivers/uart/uart.c b/src/drivers/uart/uart.c new file mode 100644 index 0000000..c714b93 --- /dev/null +++ b/src/drivers/uart/uart.c @@ -0,0 +1,167 @@ +#include "sys.h" +#include "proc.h" +#include "drivers/uart/uart.h" +#include "plic.h" + +uart_state_t uart0; + +void uart_init() { + uart0.rx_rpos = 0; + uart0.rx_wpos = 0; + uart0.tx_rpos = 0; + uart0.tx_wpos = 0; + uart0.rx_num_newlines = 0; + uart0.rx_buff_full = 0; + + // enable reading: + *(uint32_t*)(UART_BASE + UART_RXCTRL) = 1; + + // enable read interrupts: + *(uint32_t*)(UART_BASE + UART_IE) = UART_IE_RXWM; + + // set baud divisor (the SiFive FE310-G002 manual lists a table of possible + // values in Section 18.9, determined this particular choice + // experimentally. Furthermore, it's the default on HiFive1-revB board): + *(uint32_t*)(UART_BASE + UART_BAUD_RATE_DIVISOR) = 138; + + plic_enable_intr(UART0_IRQ_NO); + plic_set_intr_priority(UART0_IRQ_NO, PLIC_MAX_PRIORITY); + plic_set_threshold(PLIC_MAX_PRIORITY - 1); +} + +// _decrement_wpos attempts to decrement a given wpos. It doesn't decrement +// behind rx_rpos and doesn't decrement onto the last newline. It also wraps +// around zero. +int _decrement_wpos(int wpos) { + int orig_wpos = wpos; + if (wpos == uart0.rx_rpos) { + return orig_wpos; + } + wpos--; + if (wpos < 0) { + wpos = UART_BUF_SZ - 1; + } + if (uart0.rxbuf[wpos] == '\n') { + return orig_wpos; + } + return wpos; +} + +int uart_enqueue_chars() { + volatile int32_t* rx = (int32_t*)(UART_BASE + UART_RXDATA); + int wpos = uart0.rx_wpos; + int num_enqueued = 0; + while (1) { + int32_t word = *rx; + if (word < 0) { + break; + } + char ch = word & 0xff; + if (ch == ASCII_DEL) { + int new_wpos = _decrement_wpos(wpos); + if (new_wpos != wpos) { + wpos = new_wpos; + uart_writechar('\b'); + uart_writechar(' '); + uart_writechar('\b'); + // we've decremented wpos, so unmark the buffer as full if it was such + if (uart0.rx_buff_full) { + uart0.rx_buff_full = 0; + } + } + continue; + } + if (uart0.rx_buff_full) { + proc_mark_for_wakeup(&uart0); + continue; // if rxbuf is full, just keep draining rx FIFO + } + if (ch == '\r') { + ch = '\n'; + uart0.rx_num_newlines++; + proc_mark_for_wakeup(&uart0); + } + uart_writechar(ch); // echo back to console + uart0.rxbuf[wpos] = ch; + wpos++; + num_enqueued++; + if (wpos >= UART_BUF_SZ) { + wpos = 0; + } + if (wpos == uart0.rx_rpos) { + uart0.rx_buff_full = 1; + } + } + uart0.rx_wpos = wpos; + return num_enqueued; +} + +void uart_writechar(char ch) { + volatile int32_t* tx = (int32_t*)(UART_BASE + UART_TXDATA); + while ((int32_t)(*tx) < 0); + *tx = ch; +} + +// _can_read_from checks whether the internal state of a given uart device +// allows to read from it. +int _can_read_from(uart_state_t *uart) { + if (uart->rx_num_newlines > 0) { + return 1; + } + if (uart->rx_buff_full == 1) { + return 1; + } + return 0; +} + +int32_t uart_readline(char* buf, uint32_t bufsize) { + while (!_can_read_from(&uart0)) { + proc_yield(&uart0); + } + int32_t nread = 0; + int32_t rpos = uart0.rx_rpos; + int32_t wpos = uart0.rx_wpos; + for (;;) { + if (nread >= bufsize) { + break; + } + if (rpos == wpos && nread > 0) { + break; + } + char ch = uart0.rxbuf[rpos]; + rpos++; + if (rpos == UART_BUF_SZ) { + rpos = 0; + } + if (ch == '\n') { + uart0.rx_num_newlines--; + break; + } + buf[nread] = ch; + nread++; + } + if (uart0.rx_rpos != rpos || nread > 0) { + uart0.rx_buff_full = 0; // at least one char was read, so it's no longer full + } + uart0.rx_rpos = rpos; + return nread; +} + +int32_t uart_print(char const* data, uint32_t size) { + if (size == -1) { + return uart_prints(data); + } + int i = 0; + while (i < size) { + uart_printc(data[i]); + i++; + } + return i; +} + +int32_t uart_read(file_t* f, uint32_t pos, void* buf, uint32_t bufsize) { + return uart_readline(buf, bufsize); +} + +int32_t uart_write(file_t* f, uint32_t pos, void* buf, uint32_t bufsize) { + return uart_print(buf, bufsize); +} diff --git a/src/fs.c b/src/fs.c index 39ceea8..c2f93b4 100644 --- a/src/fs.c +++ b/src/fs.c @@ -1,7 +1,7 @@ #include "fs.h" #include "pipe.h" #include "bakedinfs.h" -#include "uart.h" +#include "drivers/uart/uart.h" file_table_t ftable; file_t stdin; diff --git a/src/kernel.c b/src/kernel.c index 613f4ac..9e3b72f 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -4,9 +4,11 @@ #include "proc.h" #include "fdt.h" #include "pagealloc.h" -#include "uart.h" +#include "drivers/uart/uart.h" #include "pipe.h" #include "runflags.h" +#include "drivers/drivers.h" +#include "plic.h" spinlock init_lock = 0; @@ -18,7 +20,7 @@ void kinit(uintptr_t fdt_header_addr) { // TODO: support multi-core park_hart(); } - uart_init(); + drivers_init(); kprintf("kinit: cpu %d\n", cpu_id); fdt_init(fdt_header_addr); kprintf("bootargs: %s\n", fdt_get_bootargs()); @@ -74,6 +76,13 @@ void kernel_timer_tick() { enable_interrupts(); } +// kernel_plic_handler... +void kernel_plic_handler() { + disable_interrupts(); + plic_dispatch_interrupts(); + enable_interrupts(); +} + void set_mie(unsigned int value) { asm volatile ( "csrs mie, %0;" // set mie to the requested value @@ -92,11 +101,12 @@ void disable_interrupts() { void enable_interrupts() { // set mstatus.MPIE (Machine Pending Interrupt Enable) bit to 1: unsigned int mstatus = get_mstatus(); - mstatus |= (1 << 7); + mstatus |= (1 << MSTATUS_MPIE_BIT); set_mstatus(mstatus); // set the mie.MTIE (Machine Timer Interrupt Enable) bit to 1: - set_mie(1 << 7); + unsigned int mie = (1 << MIE_MTIE_BIT) | (1 << MIE_MEIE_BIT); + set_mie(mie); } void set_mtvec(void *ptr) { diff --git a/src/plic.c b/src/plic.c new file mode 100644 index 0000000..3296cb9 --- /dev/null +++ b/src/plic.c @@ -0,0 +1,26 @@ +#include "sys.h" +#include "plic.h" +#include "drivers/uart/uart.h" + +void plic_enable_intr(int intr_no) { + *(uint32_t*)(PLIC_ENABLE) = 1 << intr_no; +} + +void plic_set_intr_priority(int intr_no, int priority) { + uint32_t *priority_base = (uint32_t*)(PLIC_PRIORITY); + *(priority_base + intr_no) = priority; +} + +void plic_set_threshold(int threshold) { + *(uint32_t*)(PLIC_THRESHOLD) = threshold; +} + +void plic_dispatch_interrupts() { + uint32_t intr_no = *(uint32_t*)(PLIC_CLAIM_RW); // claim this interrupt + switch (intr_no) { + case UART0_IRQ_NO: + int nenq = uart_enqueue_chars(); + break; + } + *(uint32_t*)(PLIC_CLAIM_RW) = intr_no; // mark the completion +} diff --git a/src/proc.c b/src/proc.c index d588398..d7a3a4b 100644 --- a/src/proc.c +++ b/src/proc.c @@ -3,7 +3,7 @@ #include "programs.h" #include "kernel.h" #include "string.h" -#include "uart.h" +#include "drivers/uart/uart.h" proc_table_t proc_table; trap_frame_t trap_frame; diff --git a/src/uart.c b/src/uart.c deleted file mode 100644 index 0042061..0000000 --- a/src/uart.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "sys.h" -#include "uart.h" - -void uart_init() { - // enable reading: - *(uint32_t*)(UART_BASE + UART_RXCTRL) = 1; - // set baud divisor (the SiFive FE310-G002 manual lists a table of possible - // values in Section 18.9, determined this particular choice - // experimentally. Furthermore, it's the default on HiFive1-revB board): - *(uint32_t*)(UART_BASE + UART_BAUD_RATE_DIVISOR) = 138; -} - -char uart_readchar() { - volatile int32_t* rx = (int32_t*)(UART_BASE + UART_RXDATA); - int32_t word; - do { - word = *rx; - } while (word < 0); - return word & 0xff; -} - -void uart_writechar(char ch) { - volatile int32_t* tx = (int32_t*)(UART_BASE + UART_TXDATA); - while ((int32_t)(*tx) < 0); - *tx = ch; -} - -int32_t uart_readline(char* buf, uint32_t bufsize) { - int32_t nread = 0; - for (;;) { - if (nread >= bufsize) { - break; - } - char ch = uart_readchar(); - if (ch == ASCII_DEL) { - if (nread > 0) { - nread--; - uart_writechar('\b'); - uart_writechar(' '); - uart_writechar('\b'); - } - } else { - buf[nread] = ch; - nread++; - uart_writechar(ch); // echo back to console - } - if (ch == '\r') { - uart_writechar('\n'); // echo back to console - break; - } - } - return nread; -} - -int32_t uart_print(char const* data, uint32_t size) { - if (size == -1) { - return uart_prints(data); - } - int i = 0; - while (i < size) { - uart_printc(data[i]); - i++; - } - return i; -} - -int32_t uart_read(file_t* f, uint32_t pos, void* buf, uint32_t bufsize) { - return uart_readline(buf, bufsize); -} - -int32_t uart_write(file_t* f, uint32_t pos, void* buf, uint32_t bufsize) { - return uart_print(buf, bufsize); -} diff --git a/user/src/shell.c b/user/src/shell.c index 087ecf2..f717699 100644 --- a/user/src/shell.c +++ b/user/src/shell.c @@ -168,6 +168,8 @@ cmd_t* _userland parse(cmdbuf_t *pool, char const *input) { return head; } +char sh_exec_err_fmt[] _user_rodata = "ERROR: execv %s, %d\n"; + uint32_t _userland traverse(cmd_t *node, int depth) { if (!node) { return -1; @@ -204,7 +206,7 @@ uint32_t _userland traverse(cmd_t *node, int depth) { } uint32_t code = execv(node->args[0], node->args); // normally exec doesn't return, but if it did, it's an error: - prints("ERROR: execv\n"); + printf(sh_exec_err_fmt, node->args[0], code); exit(code); } @@ -218,8 +220,6 @@ uint32_t _userland traverse(cmd_t *node, int depth) { return pipefd[1]; } -char prog_name_fmt[] _user_rodata = "fmt"; - int _userland u_main_shell(int argc, char* argv[]) { cmd_t *cmd_slots = (cmd_t*)pgalloc(); int num_slots = PAGE_SIZE / sizeof(cmd_t);