diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2023-05-29 21:31:09 +0300 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2023-05-29 21:31:09 +0300 |
| commit | 3ace1ddd76b9c04f3ed23883de6279b4485612e8 (patch) | |
| tree | 2f4af69d70967c2455cb83d8c18a5416c8d1b54d | |
| parent | b7f53b9a9001708dea5303faf52f1b508eefb712 (diff) | |
| download | kmi-3ace1ddd76b9c04f3ed23883de6279b4485612e8.tar.gz kmi-3ace1ddd76b9c04f3ed23883de6279b4485612e8.zip | |
start trying to boot on visionfive 2
+ Not bootable quite yet. Among other things, I couldn't
get the current starfive u-boot fork to boot, so
try adding support for booting with precompiled u-boot
via the `go` command. Initial testing with qemu shows that
this should be possible, and if it works, might be useful
in the (far) future with other slightly janky SBCs.
Also, NS16550 is 8250-based, and I'm really only using the base
8250, so rename and add visionfive 2 uart to list of compatibles.
Visionfive 2 is still completely untested.
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | README.md | 18 | ||||
| -rwxr-xr-x | arch/riscv64/conf/mkimage.sh | 4 | ||||
| -rw-r--r-- | arch/riscv64/init/init.c | 17 | ||||
| -rw-r--r-- | arch/riscv64/init/start.S | 20 | ||||
| -rw-r--r-- | arch/riscv64/kernel/vmem.c | 3 | ||||
| -rw-r--r-- | common/debug.c | 25 | ||||
| -rw-r--r-- | common/pmem.c | 2 | ||||
| -rw-r--r-- | common/string.c | 136 | ||||
| -rw-r--r-- | include/kmi/debug.h | 4 | ||||
| -rw-r--r-- | include/kmi/string.h | 15 |
11 files changed, 233 insertions, 16 deletions
@@ -5,6 +5,9 @@ DEBUGFLAGS != [ $(RELEASE) ] \ && echo "-flto -O2 -DNDEBUG" \ || echo "-O0 -DDEBUG" +UBOOTFLAGS != [ $(GENERIC_UBOOT) ] \ + && echo "-DGENERIC_UBOOT=1" + CFLAGS = -ffreestanding -nostdlib -static -fno-pie -std=c17 \ -Wall -Wextra -Wvla -D$(ARCH) -g @@ -46,7 +49,7 @@ CLEANUP_CMD := include arch/$(ARCH)/source.mk -COMPILE_FLAGS := $(CFLAGS) $(ARCH_CFLAGS) +COMPILE_FLAGS := $(CFLAGS) $(ARCH_CFLAGS) $(UBOOTFLAGS) LINK_FLAGS := $(LDFLAGS) $(ARCH_LDFLAGS) INCLUDE_FLAGS := -I include -include config.h -include arch/$(ARCH)/config.h @@ -27,7 +27,7 @@ Open `docs/output/html/index.html` in your favorite web browser. + `RELEASE=<0/1>`: Enable optimizations when set to `1`. Default is `0`. Note that with `RELEASE=1` the built in serial driver is disabled, as eventually -I'd like to provide it as a separate userspace driver. At the moment only NS16550A and +I'd like to provide it as a separate userspace driver. At the moment only 8250 and compatible serial devices are supported. + `LLVM=<0/1>`: Use LLVM toolchain when set to `1`. Default is `0`. @@ -36,6 +36,22 @@ Note that due to some bugs in the toolchain, LTO is disabled with `RELEASE=1`. + `UBSAN=<0/1>`: Enable undefined behavior sanitizer, outputs a number of warnings at runtime when undefined behavior is detected. Only available with `RELEASE=0`. ++ `GENERIC_UBOOT=<0/1>`: Compile for use with generic u-boot. This allows +booting the kernel through u-boot's `go` command. This would in theory allow +using essentially any precompiled u-boot as a bootloader, but requires some +extra effort by the user to manage loading different parts to where they should +go. I should probably come up with a full example, but approximately: + +1. Load `kmi.bin` to address `A` +2. Load `initrd` to address `B` +3. Load FDT to address `C` (either provided by u-boot or a separate file) +4. Add `initrd` to FDT's `chosen` node +5. Boot with `go ${A} ${C}`. + +Note that u-boot likes to skip the `0x` prefix for +hexadecimal values, which can confuse my string converter. +Check that the strings you pass to `go` have their correct prefixes. + + `run`: Load a test program into `qemu` and run it. Requires some outside support at the moment, please see [kmi-example](https://github.com/Kimplul/kmi-example). This command might eventually be moved out of this repo. diff --git a/arch/riscv64/conf/mkimage.sh b/arch/riscv64/conf/mkimage.sh index badac4c..1726e57 100755 --- a/arch/riscv64/conf/mkimage.sh +++ b/arch/riscv64/conf/mkimage.sh @@ -14,6 +14,10 @@ SSIZE=$(fdisk -l rootfs.img | awk '$1=="Units:" {print $8}') OFFSET=$(fdisk -l rootfs.img | awk '$1=="rootfs.img1" {print $2}') mount -o loop,offset=$((${SSIZE}*${OFFSET})) rootfs.img fs +# copy over files outside the .itb to mess around with manual booting +cp kmi.bin fs +cp arch/riscv64/conf/initrd fs + # not entirely pleased with this solution, although eventually I should probably # move `run` out of the kernel repo and into some `kmios` repo with a runtime # and proper initrd etc. so this is good enough for now diff --git a/arch/riscv64/init/init.c b/arch/riscv64/init/init.c index 92f8053..278b9db 100644 --- a/arch/riscv64/init/init.c +++ b/arch/riscv64/init/init.c @@ -54,7 +54,8 @@ static void init_bootmem(uintptr_t load_addr, uintptr_t ram_base) extern char *__kernel; extern char *__kernel_size; - uintptr_t top = load_addr + (uintptr_t)&__kernel + (uintptr_t)&__kernel_size; + uintptr_t top = load_addr + (uintptr_t)&__kernel + + (uintptr_t)&__kernel_size; /* this could be risky, as we might overwrite some bits of initrd or fdt * if they're allocated too close to the kernel payload. @@ -119,3 +120,17 @@ void init(void *fdt, pm_t load_addr) jump_to_kernel(fdt, ram_base, (void *)VM_KERN); } + +#if GENERIC_UBOOT +void init_go(int argc, char **argv, pm_t load_addr) +{ + if (argc != 2) + return; + + void *fdt = (void *)strtouintptr(argv[1]); + + /* fdt is passed as second argument, load address first but since we + * already have it due to our ingenious _start, no need to parse it */ + init(fdt, load_addr); +} +#endif diff --git a/arch/riscv64/init/start.S b/arch/riscv64/init/start.S index 7439d93..b6394f7 100644 --- a/arch/riscv64/init/start.S +++ b/arch/riscv64/init/start.S @@ -1,17 +1,33 @@ /* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ +/* generic u-boot passes argc and argv as if we were a regular + * program, so our load address must be placed third. + * If we have a 'custom' u-boot with kmi support, the fdt + * will be passed to us directly, so the load address can go second. + */ +#if GENERIC_UBOOT +# define LOAD_REG a2 +# define INIT init_go +#else +# define LOAD_REG a1 +# define INIT init +#endif + .section .init .global _start /* Entry point to the kernel loader. */ _start: /* get load address */ -auipc a1, 0 +auipc LOAD_REG, 0 /* keep using bootloader stack for now */ //li sp, PM_STACK_TOP /* make sure there's no garbage in tp, important for id assignment */ li tp, 0 -call init +call INIT +// INIT shouldn't return, but if we do we might as well just jump back to u-boot +// or wherever +ret .section .text .global jump_to_kernel diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c index d0cac2c..235884b 100644 --- a/arch/riscv64/kernel/vmem.c +++ b/arch/riscv64/kernel/vmem.c @@ -427,7 +427,8 @@ stat_t populate_kvmem(struct vmem *b) size_t flags = VM_V | VM_R | VM_W | VM_X | VM_G; for (size_t i = KSTART_PAGE; i < IO_PAGE; ++i) b->leaf[i] = (struct vmem *)to_pte( - get_ram_base() + TOP_PAGE_SIZE * (i - KSTART_PAGE), flags); + get_ram_base() + TOP_PAGE_SIZE * (i - KSTART_PAGE), + flags); /* map in IO region */ map_io_dbg(b); diff --git a/common/debug.c b/common/debug.c index 1bc87f7..2cd4570 100644 --- a/common/debug.c +++ b/common/debug.c @@ -59,8 +59,8 @@ vm_t map_io_dbg(struct vmem *b) * try to implement some kind of basic driver subsystem, but this is good enough * for now. */ -/** NS16550A and compatible serial drivers. */ -struct __packed ns16550a { +/** 8250 and compatible serial drivers. */ +struct __packed uart_8250 { /** Receiver buffer/transmitter holding register. */ uint8_t data; @@ -111,10 +111,10 @@ struct __packed ns16550a { #define LSR_ERR (1 << 7) /** - * Address of ns16550a port. If other serial drivers are added, this should - * maybe be made a void *. + * 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 ns16550a *port = 0; +static struct uart_8250 *port = 0; /** * Serial transmitter empty. @@ -150,8 +150,17 @@ static void __putchar(char c) */ static enum serial_dev __serial_dev_enum(const char *dev_name) { + /* qemu, for example */ if (strncmp("ns16550", dev_name, 7) == 0) - return NS16550A; + return UART_8250; + + /* mentioned in dtc documentation as an example */ + if (strncmp("ns8250", dev_name, 7) == 0) + return UART_8250; + + /* starfive visionfive 2, for example (hopefully works) */ + if (strncmp("snps,dw-apb-uart", dev_name, 16) == 0) + return UART_8250; return -1; } @@ -194,8 +203,8 @@ static struct dbg_info __dbg_from_fdt(const void *fdt) void __setup_dbg(vm_t pt, enum serial_dev dev) { switch (dev) { - case NS16550A: - port = (struct ns16550a *)pt; + case UART_8250: + port = (struct uart_8250 *)pt; break; } diff --git a/common/pmem.c b/common/pmem.c index 22a1493..c817c5a 100644 --- a/common/pmem.c +++ b/common/pmem.c @@ -612,6 +612,8 @@ 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()); + /** @todo could probably improve error messages on failing to get fdt + * values */ pm_t initrd_top = get_initrdtop(fdt); pm_t fdt_top = __get_fdttop(fdt); diff --git a/common/string.c b/common/string.c index 6685c08..a76c875 100644 --- a/common/string.c +++ b/common/string.c @@ -324,3 +324,139 @@ __weak int memcmp(const void *ptr1, const void *ptr2, size_t num) return (int)(p1[-1] - p2[-1]); } + +/** + * Convert ASCII hex character to integer. + * Allows both upper- and lowercase letters. + * + * @param c Character to convert. + * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc. + * \c -1 if conversion failed. + */ +static int __hexval(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a'; + + if (c >= 'A' && c <= 'F') + return c - 'A'; + + return -1; +} + +/** + * Convert string assumed to represent hex + * value to corresponding pointer. + * + * @param s String to convert to value. + * @return Corresponding pointer value. + */ +static uintptr_t __hexuintptr(const char *s) +{ + uintptr_t res = 0; + int val = 0; + while ((val = __hexval(*(s++))) != -1) { + res *= 16; + res += val; + } + + return res; +} + +/** + * Convert ASCII decimal character to integer. + * + * @param c Character to convert. + * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc. + * \c -1 if conversion failed. + */ +static int __decval(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + return -1; +} + +/** + * Convert string assumed to represent decimal + * value to corresponding pointer. + * + * @param s String to convert to value. + * @return Corresponding pointer value. + */ +static uintptr_t __decuintptr(const char *s) +{ + uintptr_t res = 0; + int val = 0; + while ((val = __decval(*(s++))) != -1) { + res *= 10; + res += val; + } + + return res; +} + +/** + * Convert ASCII octal character to integer. + * + * @param c Character to convert. + * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc. + * \c -1 if conversion failed. + */ +static int __octval(char c) +{ + if (c >= '0' && c <= '7') + return c - '0'; + + return -1; +} + +/** + * Convert string assumed to represent octal + * value to corresponding pointer. + * + * @param s String to convert to value. + * @return Corresponding pointer value. + */ +static uintptr_t __octuintptr(const char *s) +{ + uintptr_t res = 0; + int val = 0; + while ((val = __octval(*(s++))) != -1) { + res *= 8; + res += val; + } + + return res; +} + +uintptr_t strtouintptr(const char *s) +{ + if (!s) + return 0; + + if (s[0] == 0) + return 0; + + if (s[0] == '0') { + if (s[1] == 0) + return 0; + + if (s[1] == 'x' || s[1] == 'X') + return __hexuintptr(s + 2); + + return __octuintptr(s + 1); + } + + if (s[0] == '-') + return -__decuintptr(s + 1); + + if (s[0] == '+') + return __decuintptr(s + 1); + + return __decuintptr(s); +} diff --git a/include/kmi/debug.h b/include/kmi/debug.h index deca4ca..4079048 100644 --- a/include/kmi/debug.h +++ b/include/kmi/debug.h @@ -320,8 +320,8 @@ #if defined(DEBUG) /** Serial devices supported. */ enum serial_dev { - /** NS16550A and compatible. Currently the only supported serial device. */ - NS16550A, + /** 8250 and compatible. Currently the only supported serial device. */ + UART_8250, }; /** diff --git a/include/kmi/string.h b/include/kmi/string.h index 2b26a2d..f72299b 100644 --- a/include/kmi/string.h +++ b/include/kmi/string.h @@ -215,6 +215,21 @@ void *memmove(void *dst, const void *src, size_t num); */ int memcmp(const void *ptr1, const void *ptr2, size_t num); +/** + * Try to convert string \p s into a corresponding + * \c uintptr_t. Handles decimal, octal and hex, + * assuming hex starts with \c 0x or \c 0X and octal with + * \c 0, otherwise assumes decimal. Allows \c + and \c - in + * decimal. + * + * Note that u-boot likes to skip leading \c 0x when using hex, + * so if you're passing u-boot variables make sure to check prefixes. + * + * @param s String to convert to pointer. + * @return Corresponding pointer value. + */ +uintptr_t strtouintptr(const char *s); + /* Honorable mentions: * * char *strerror(int err); |
