From 3dbd8c34176312b0bd3aba2309d284ac83181411 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Sun, 4 Jun 2023 15:13:43 +0300 Subject: visionfive2 boots + Make 8250 serial driver more generic + Still TODO: write a tutorial on how to boot on the visionfive2 --- arch/riscv64/config.h | 3 +- arch/riscv64/kernel/vmem.c | 16 ++++-- common/debug.c | 133 ++++++++++++++++++++++----------------------- common/initrd.c | 1 + common/pmem.c | 73 +++++++++++++++++++------ include/arch/vmem.h | 6 +- 6 files changed, 141 insertions(+), 91 deletions(-) diff --git a/arch/riscv64/config.h b/arch/riscv64/config.h index 3eb2e72..a2694d5 100644 --- a/arch/riscv64/config.h +++ b/arch/riscv64/config.h @@ -21,8 +21,7 @@ #include /* --- START ARCH USER CONFIG VALUES --- */ -/** Physical address to where the OS image will be loaded. */ -//#define RAM_BASE 0x80000000 +/* nada for now */ /* --- END ARCH USER CONFIG VALUES --- */ /* don't touch >:( */ diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c index f29c325..89d895f 100644 --- a/arch/riscv64/kernel/vmem.c +++ b/arch/riscv64/kernel/vmem.c @@ -369,7 +369,7 @@ void flush_tlb(uintptr_t addr) void flush_tlb_full() { - __asm__ volatile ("sfence.vma %0\n" :: "r" (0) : "memory"); + __asm__ volatile ("sfence.vma %0\n" : : "r" (0) : "memory"); } void flush_tlb_all() @@ -451,9 +451,17 @@ stat_t populate_kvmem(struct vmem *b) vm_t setup_kernel_io(struct vmem *b, vm_t paddr) { pm_t top_page = paddr / TOP_PAGE_SIZE; - b->leaf[IO_PAGE] = (struct vmem *)to_pte(top_page * TOP_PAGE_SIZE, - VM_V | VM_R | VM_W | VM_A | VM_D); - return -TOP_PAGE_SIZE + paddr - (top_page * TOP_PAGE_SIZE); + pm_t addr = top_page * TOP_PAGE_SIZE; + b->leaf[IO_PAGE] = (struct vmem *)to_pte(addr, + VM_V | VM_R | VM_W | VM_A | + VM_D); + /* flush might be necessary when we're in the actual vmem we're + * modifying, or during the startup stage where we don't have a tcb yet. + * I don't think checking the rpc context is necessary? */ + if (!cur_tcb() || cur_tcb()->proc.vmem == b) + flush_tlb((uintptr_t)pte_addr(b->leaf[IO_PAGE])); + + return -TOP_PAGE_SIZE + paddr - addr; } #endif diff --git a/common/debug.c b/common/debug.c index 346934a..f3cacdf 100644 --- a/common/debug.c +++ b/common/debug.c @@ -20,18 +20,21 @@ /** Debug context structure. */ struct dbg_info { - /** Address of serial device in memory. */ - pm_t dbg_ptr; - + /** Address of serial device in virtual memory. Always access registers + * through this address rather than \p addr. */ + pm_t base; + /** Shift count between registers of serial device. */ + size_t shift; /** Type of serial device. */ enum serial_dev dev; + /** Physical address of serial device. */ + pm_t addr; }; /** Static debugging information. */ static struct dbg_info dbg_info = (struct dbg_info){ 0 }; /* forward declarations. */ -static void __setup_dbg(pm_t pt, enum serial_dev dev); static struct dbg_info __dbg_from_fdt(const void *fdt); void init_dbg(const void *fdt) @@ -41,51 +44,41 @@ void init_dbg(const void *fdt) void setup_dmap_dbg() { - __setup_dbg(dbg_info.dbg_ptr, dbg_info.dev); + /* noop with the new 8250 driver, though might still be useful in the + * future if I add in some other kind of simple uart */ } void setup_io_dbg(struct vmem *b) { - vm_t io_ptr = map_io_dbg(b); - __setup_dbg(io_ptr, dbg_info.dev); + dbg_info.base = map_io_dbg(b); } vm_t map_io_dbg(struct vmem *b) { - return setup_kernel_io(b, dbg_info.dbg_ptr); + return setup_kernel_io(b, dbg_info.addr); } +/** 8250 data register index. */ +#define UART_8250_DATA 0 +/** 8250 irq register idex. */ +#define UART_8250_IRQ 1 +/** 8250 ird_id register index. */ +#define UART_8250_IRQ_ID 2 +/** 8250 lcr register index. */ +#define UART_8250_LCR 3 +/** 8250 mcr register index. */ +#define UART_8250_MCR 4 +/** 8250 lsr register index. */ +#define UART_8250_LSR 5 +/** 8250 msr register index. */ +#define UART_8250_MSR 6 +/** 8250 scr register index. */ +#define UART_8250_SCR 7 + /* if there arises a need for more supported serial drivers, I should probably * try to implement some kind of basic driver subsystem, but this is good enough * for now. */ -/** 8250 and compatible serial drivers. */ -struct __packed uart_8250 { - /** Receiver buffer/transmitter holding register. */ - uint8_t data; - - /** Interrupt enable register. */ - uint8_t irq; - - /** Interrupt identity/FIFO control register. */ - uint8_t irq_id; - - /** Line control register. */ - uint8_t lcr; - - /** Modem control register. */ - uint8_t mcr; - - /** Line status register. */ - uint8_t lsr; - - /** Modem status register. */ - uint8_t msr; - - /** Scratch register. */ - uint8_t scr; -}; - /** Line status data ready. */ #define LSR_DR (1 << 0) @@ -111,17 +104,11 @@ struct __packed uart_8250 { #define LSR_ERR (1 << 7) /** - * Address of generic 8250 port. If other serial drivers are added, this should - * maybe be made a void *. No support for quirks at the moment. - */ -static struct uart_8250 *port = 0; - -/** - * Serial transmitter empty. + * 8250 transmitter empty. * * @return \c 0 if not empty, non-zero otherwise. */ -static int __serial_tx_empty() +static int __8250_tx_empty() { /** * @todo visionfive2 loops on lsr indefinitely, why? @@ -129,23 +116,43 @@ static int __serial_tx_empty() * meaning the registers are spaced apart more in memory than my naive * struct. * */ - return port->lsr & LSR_THRE; + volatile uint8_t *lsr = (uint8_t *)dbg_info.base + + (UART_8250_LSR << dbg_info.shift); + return (*lsr) & LSR_THRE; } /** - * Put character out onto serial lines. + * Put character out onto 8250 serial lines. * * @param c Character to output. */ -static void __putchar(char c) +static void __8250_putchar(char c) { - if (!port) + if (!dbg_info.base) return; - while (__serial_tx_empty() == 0) + while (__8250_tx_empty() == 0) ; - port->data = c; + volatile uint8_t *data = (uint8_t *)dbg_info.base + + (UART_8250_DATA << dbg_info.shift); + *data = c; +} + +/** + * Put one character out on the serial lines. + * Automatically handles newlines. + * + * @param c Character to put. + */ +static void __putchar(char c) +{ + if (c == '\n') + __putchar('\r'); + + switch (dbg_info.dev) { + case UART_8250: __8250_putchar(c); return; + } } /** @@ -200,28 +207,18 @@ static struct dbg_info __dbg_from_fdt(const void *fdt) /* get serial device address */ struct cell_info ci = get_reginfo(fdt, stdout); void *reg_ptr = (void *)fdt_getprop(fdt, stdout_offset, "reg", NULL); - pm_t dbg_ptr = (pm_t)fdt_load_int_ptr(ci.addr_cells, reg_ptr); - return (struct dbg_info){ dbg_ptr, dev }; -} - -/** - * Set static port. - * - * @param pt Address of memory mapped serial device. - * @param dev Chosen device. - */ -void __setup_dbg(vm_t pt, enum serial_dev dev) -{ - switch (dev) { - case UART_8250: - port = (struct uart_8250 *)pt; - break; - } + /* get serial device offset if present */ + size_t shift = 0; + void *shift_ptr = (void *)fdt_getprop(fdt, stdout_offset, "reg-shift", + NULL); + if (shift_ptr) + shift = (size_t)fdt_load_int32_ptr(shift_ptr); - /* in the future possibly configure the serial connection, though the - * defaults (set by U-boot) seem to work alright */ + /* while in direct map, base == addr, and this changes only when we jump + * into virtually mapped io */ + return (struct dbg_info){ dbg_ptr, shift, dev, dbg_ptr }; } /** Printf formatting left align flag. */ diff --git a/common/initrd.c b/common/initrd.c index 4bb25e9..caff8b3 100644 --- a/common/initrd.c +++ b/common/initrd.c @@ -11,6 +11,7 @@ #include #include #include +#include #include /** GNU cpio, POSIX 'newc' format header. */ diff --git a/common/pmem.c b/common/pmem.c index c817c5a..0fa647f 100644 --- a/common/pmem.c +++ b/common/pmem.c @@ -518,6 +518,11 @@ pm_t probe_pmap(size_t ram_size) */ static void __mark_area_used(pm_t base, pm_t top) { + if (top < base) { + bug("top < base: %lx < %lx\n", top, base); + return; + } + size_t area_left = top - base; pm_t runner = base; while (area_left >= BASE_PAGE_SIZE) { @@ -537,20 +542,29 @@ static void __mark_area_used(pm_t base, pm_t top) */ static void __mark_reserved_mem(void *fdt) { - int rmem_offset = fdt_path_offset(fdt, "/reserved-memory/mmode_resv0"); - struct cell_info ci = get_reginfo(fdt, "/reserved-memory/mmode_resv0"); - uint8_t *rmem_reg = - (uint8_t *)fdt_getprop(fdt, rmem_offset, "reg", NULL); - - pm_t base = (pm_t)fdt_load_int_ptr(ci.addr_cells, rmem_reg); - - if (ci.addr_cells == 2) - rmem_reg += sizeof(fdt64_t); - else - rmem_reg += sizeof(fdt32_t); - - pm_t top = (pm_t)fdt_load_int_ptr(ci.size_cells, rmem_reg) + base; - __mark_area_used((pm_t)__va(base), (pm_t)__va(top)); + int rmem_offset = fdt_path_offset(fdt, "/reserved-memory"); + struct cell_info ci = get_reginfo(fdt, "/reserved-memory"); + + int node = 0; + fdt_for_each_subnode(node, fdt, rmem_offset) { + uint8_t *rmem_reg = + (uint8_t *)fdt_getprop(fdt, node, "reg", NULL); + + pm_t base = (pm_t)fdt_load_int_ptr(ci.addr_cells, rmem_reg); + + if (ci.addr_cells == 2) + rmem_reg += sizeof(fdt64_t); + else + rmem_reg += sizeof(fdt32_t); + + /** @todo make sure the top of a reserved memory area doesn't go + * against our assumptions in FW_MAX_SIZE? */ + pm_t top = + (pm_t)fdt_load_int_ptr(ci.size_cells, rmem_reg) + base; + __mark_area_used((pm_t)__va(base), (pm_t)__va(top)); + info("marked [%lx - %lx] reserved\n", (pm_t)__va( + base), (pm_t)__va(top)); + } } /** @@ -603,6 +617,13 @@ static pm_t __get_fdtbase(void *fdt) void init_pmem(void *fdt) { + /** @todo should I keep the info outputs? I suppose it's nice to see + * if any assumption is being broken in the serial log, but in that case + * I should really try adding more of them to other parts of the + * codebase as well, the pmem subsystem isn't really especially complex. + */ + info("initializing pmem\n"); + size_t max_order = 0; size_t base_bits = 0; size_t bits[NUM_ORDERS] = { 0 }; @@ -612,17 +633,29 @@ void init_pmem(void *fdt) pm_t ram_size = __get_ramtop(fdt) - get_ram_base(); pm_t ram_base = (pm_t)__va(get_ram_base()); + info("using ram range [%lx - %lx]\n", + ram_base, ram_base + ram_size); + /** @todo could probably improve error messages on failing to get fdt * values */ + pm_t initrd_base = get_initrdbase(fdt); pm_t initrd_top = get_initrdtop(fdt); + info("found initrd at [%lx - %lx]\n", initrd_base, initrd_top); + pm_t fdt_top = __get_fdttop(fdt); + pm_t fdt_base = __get_fdtbase(fdt); + info("found fdt at [%lx - %lx]\n", fdt_base, fdt_top); /* find probably most suitable contiguous region of ram for our physical * ram map */ pm_t pmap_base = align_up(MAX(initrd_top, fdt_top), sizeof(int)); + info("choosing to place pmem map at %lx\n", pmap_base); size_t probe_size = probe_pmap(ram_size); + info("pmem map probe size returned %lu\n", probe_size); + size_t actual_size = populate_pmap(ram_base, ram_size, pmap_base); + info("pmem map actual size %lu\n", actual_size); if (probe_size != actual_size) { bug("probe_size (%#lx) != actual_size (%#lx)\n", probe_size, @@ -632,17 +665,25 @@ void init_pmem(void *fdt) /* mark init stack, this should be unmapped once we get to executing * processes */ __mark_area_used(VM_STACK_BASE, VM_STACK_TOP); + info("marked stack [%lx - %lx] used\n", VM_STACK_BASE, VM_STACK_TOP); /* mark kernel */ /* this could be made more explicit, I suppose. */ __mark_area_used(VM_KERN, VM_KERN + PM_KERN_SIZE); + info("marked kernel [%lx - %lx] used\n", VM_KERN, + VM_KERN + PM_KERN_SIZE); /* mark fdt and initrd */ - __mark_area_used(get_initrdbase(fdt), initrd_top); - __mark_area_used(__get_fdtbase(fdt), fdt_top); + __mark_area_used(initrd_base, initrd_top); + info("marked initrd [%lx - %lx] used\n", initrd_base, initrd_top); + + __mark_area_used(fdt_base, fdt_top); + info("marked fdt [%lx - %lx] used\n", fdt_base, fdt_top); /* mark pmap */ __mark_area_used(pmap_base, pmap_base + actual_size); + info("marked pmap [%lx - %lx] used\n", pmap_base, + pmap_base + actual_size); /* mark reserved mem */ __mark_reserved_mem(fdt); diff --git a/include/arch/vmem.h b/include/arch/vmem.h index d2f031d..118e6ff 100644 --- a/include/arch/vmem.h +++ b/include/arch/vmem.h @@ -98,7 +98,11 @@ stat_t mod_vpage(struct vmem *branch, vm_t vaddr, pm_t paddr, vmflags_t flags); stat_t stat_vpage(struct vmem *branch, vm_t vaddr, pm_t *paddr, enum mm_order *order, vmflags_t *flags); -/** Flush tlb entry where associated with address \p addr. */ +/** + * Flush tlb entry where associated with address \p addr. + * + * @param addr Virtual address region to flush. + */ void flush_tlb(uintptr_t addr); /** Flush full tlb of current address space. */ -- cgit v1.3