Add the minimal amount of code possible in order to launch a first test, which just prints "Hello, world" using the expected UART address of the QEMU virt machine. Add files, stubs, and some support, such as barriers and MMIO read/write along the way in order to satisfy the compiler. Basically everything is either copied from the arm64 port of kvm-unit-tests, or at least inspired by it, and, in that case, the RISC-V Linux kernel code was copied. Run with qemu-system-riscv64 -nographic -M virt -kernel riscv/selftest.flat and then go to the monitor (ctrl-a c) and use 'q' to quit, since the unit test will just hang after printing hello world and the exit code. Signed-off-by: Andrew Jones <andrew.jones@xxxxxxxxx> Acked-by: Thomas Huth <thuth@xxxxxxxxxx> --- configure | 14 ++++++ lib/riscv/.gitignore | 1 + lib/riscv/asm-offsets.c | 6 +++ lib/riscv/asm/asm-offsets.h | 1 + lib/riscv/asm/barrier.h | 13 ++++++ lib/riscv/asm/csr.h | 7 +++ lib/riscv/asm/io.h | 78 +++++++++++++++++++++++++++++++ lib/riscv/asm/page.h | 7 +++ lib/riscv/asm/setup.h | 7 +++ lib/riscv/asm/spinlock.h | 7 +++ lib/riscv/asm/stack.h | 9 ++++ lib/riscv/io.c | 44 ++++++++++++++++++ lib/riscv/setup.c | 12 +++++ riscv/Makefile | 83 +++++++++++++++++++++++++++++++++ riscv/cstart.S | 92 +++++++++++++++++++++++++++++++++++++ riscv/flat.lds | 75 ++++++++++++++++++++++++++++++ riscv/selftest.c | 13 ++++++ 17 files changed, 469 insertions(+) create mode 100644 lib/riscv/.gitignore create mode 100644 lib/riscv/asm-offsets.c create mode 100644 lib/riscv/asm/asm-offsets.h create mode 100644 lib/riscv/asm/barrier.h create mode 100644 lib/riscv/asm/csr.h create mode 100644 lib/riscv/asm/io.h create mode 100644 lib/riscv/asm/page.h create mode 100644 lib/riscv/asm/setup.h create mode 100644 lib/riscv/asm/spinlock.h create mode 100644 lib/riscv/asm/stack.h create mode 100644 lib/riscv/io.c create mode 100644 lib/riscv/setup.c create mode 100644 riscv/Makefile create mode 100644 riscv/cstart.S create mode 100644 riscv/flat.lds create mode 100644 riscv/selftest.c diff --git a/configure b/configure index ada6512702a1..05e6702eab06 100755 --- a/configure +++ b/configure @@ -200,6 +200,11 @@ arch_name=$arch [ "$arch_name" = "arm64" ] && arch_name="aarch64" arch_libdir=$arch +if [ "$arch" = "riscv" ]; then + echo "riscv32 or riscv64 must be specified" + exit 1 +fi + if [ -z "$target" ]; then target="qemu" else @@ -307,6 +312,9 @@ elif [ "$arch" = "ppc64" ]; then echo "You must provide endianness (big or little)!" usage fi +elif [ "$arch" = "riscv32" ] || [ "$arch" = "riscv64" ]; then + testdir=riscv + arch_libdir=riscv else testdir=$arch fi @@ -438,6 +446,12 @@ cat <<EOF >> lib/config.h #define CONFIG_ERRATA_FORCE ${errata_force} #define CONFIG_PAGE_SIZE _AC(${page_size}, UL) +EOF +elif [ "$arch" = "riscv32" ] || [ "$arch" = "riscv64" ]; then +cat <<EOF >> lib/config.h + +#define CONFIG_UART_EARLY_BASE 0x10000000 + EOF fi echo "#endif" >> lib/config.h diff --git a/lib/riscv/.gitignore b/lib/riscv/.gitignore new file mode 100644 index 000000000000..82da12e6bd4e --- /dev/null +++ b/lib/riscv/.gitignore @@ -0,0 +1 @@ +/asm-offsets.[hs] diff --git a/lib/riscv/asm-offsets.c b/lib/riscv/asm-offsets.c new file mode 100644 index 000000000000..4a74df9e4a09 --- /dev/null +++ b/lib/riscv/asm-offsets.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only + +int main(void) +{ + return 0; +} diff --git a/lib/riscv/asm/asm-offsets.h b/lib/riscv/asm/asm-offsets.h new file mode 100644 index 000000000000..d370ee36a182 --- /dev/null +++ b/lib/riscv/asm/asm-offsets.h @@ -0,0 +1 @@ +#include <generated/asm-offsets.h> diff --git a/lib/riscv/asm/barrier.h b/lib/riscv/asm/barrier.h new file mode 100644 index 000000000000..c6a09066b2c7 --- /dev/null +++ b/lib/riscv/asm/barrier.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_BARRIER_H_ +#define _ASMRISCV_BARRIER_H_ + +#define RISCV_FENCE(p, s) \ + __asm__ __volatile__ ("fence " #p "," #s : : : "memory") + +/* These barriers need to enforce ordering on both devices or memory. */ +#define mb() RISCV_FENCE(iorw,iorw) +#define rmb() RISCV_FENCE(ir,ir) +#define wmb() RISCV_FENCE(ow,ow) + +#endif /* _ASMRISCV_BARRIER_H_ */ diff --git a/lib/riscv/asm/csr.h b/lib/riscv/asm/csr.h new file mode 100644 index 000000000000..5c4f2de34f64 --- /dev/null +++ b/lib/riscv/asm/csr.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_CSR_H_ +#define _ASMRISCV_CSR_H_ + +#define CSR_SSCRATCH 0x140 + +#endif /* _ASMRISCV_CSR_H_ */ diff --git a/lib/riscv/asm/io.h b/lib/riscv/asm/io.h new file mode 100644 index 000000000000..d2eb3acc9fda --- /dev/null +++ b/lib/riscv/asm/io.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * From Linux arch/riscv/include/asm/mmio.h + */ +#ifndef _ASMRISCV_IO_H_ +#define _ASMRISCV_IO_H_ +#include <libcflat.h> + +#define __iomem + +/* Generic IO read/write. These perform native-endian accesses. */ +#define __raw_writeb __raw_writeb +static inline void __raw_writeb(u8 val, volatile void __iomem *addr) +{ + asm volatile("sb %0, 0(%1)" : : "r" (val), "r" (addr)); +} + +#define __raw_writew __raw_writew +static inline void __raw_writew(u16 val, volatile void __iomem *addr) +{ + asm volatile("sh %0, 0(%1)" : : "r" (val), "r" (addr)); +} + +#define __raw_writel __raw_writel +static inline void __raw_writel(u32 val, volatile void __iomem *addr) +{ + asm volatile("sw %0, 0(%1)" : : "r" (val), "r" (addr)); +} + +#ifdef CONFIG_64BIT +#define __raw_writeq __raw_writeq +static inline void __raw_writeq(u64 val, volatile void __iomem *addr) +{ + asm volatile("sd %0, 0(%1)" : : "r" (val), "r" (addr)); +} +#endif + +#define __raw_readb __raw_readb +static inline u8 __raw_readb(const volatile void __iomem *addr) +{ + u8 val; + + asm volatile("lb %0, 0(%1)" : "=r" (val) : "r" (addr)); + return val; +} + +#define __raw_readw __raw_readw +static inline u16 __raw_readw(const volatile void __iomem *addr) +{ + u16 val; + + asm volatile("lh %0, 0(%1)" : "=r" (val) : "r" (addr)); + return val; +} + +#define __raw_readl __raw_readl +static inline u32 __raw_readl(const volatile void __iomem *addr) +{ + u32 val; + + asm volatile("lw %0, 0(%1)" : "=r" (val) : "r" (addr)); + return val; +} + +#ifdef CONFIG_64BIT +#define __raw_readq __raw_readq +static inline u64 __raw_readq(const volatile void __iomem *addr) +{ + u64 val; + + asm volatile("ld %0, 0(%1)" : "=r" (val) : "r" (addr)); + return val; +} +#endif + +#include <asm-generic/io.h> + +#endif /* _ASMRISCV_IO_H_ */ diff --git a/lib/riscv/asm/page.h b/lib/riscv/asm/page.h new file mode 100644 index 000000000000..7d7c9191605a --- /dev/null +++ b/lib/riscv/asm/page.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_PAGE_H_ +#define _ASMRISCV_PAGE_H_ + +#include <asm-generic/page.h> + +#endif /* _ASMRISCV_PAGE_H_ */ diff --git a/lib/riscv/asm/setup.h b/lib/riscv/asm/setup.h new file mode 100644 index 000000000000..385455f341cc --- /dev/null +++ b/lib/riscv/asm/setup.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_SETUP_H_ +#define _ASMRISCV_SETUP_H_ + +void setup(const void *fdt, phys_addr_t freemem_start); + +#endif /* _ASMRISCV_SETUP_H_ */ diff --git a/lib/riscv/asm/spinlock.h b/lib/riscv/asm/spinlock.h new file mode 100644 index 000000000000..6e2b3009abf3 --- /dev/null +++ b/lib/riscv/asm/spinlock.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_SPINLOCK_H_ +#define _ASMRISCV_SPINLOCK_H_ + +#include <asm-generic/spinlock.h> + +#endif /* _ASMRISCV_SPINLOCK_H_ */ diff --git a/lib/riscv/asm/stack.h b/lib/riscv/asm/stack.h new file mode 100644 index 000000000000..d081d0716d7b --- /dev/null +++ b/lib/riscv/asm/stack.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _ASMRISCV_STACK_H_ +#define _ASMRISCV_STACK_H_ + +#ifndef _STACK_H_ +#error Do not directly include <asm/stack.h>. Just use <stack.h>. +#endif + +#endif diff --git a/lib/riscv/io.c b/lib/riscv/io.c new file mode 100644 index 000000000000..3cfc235d19a6 --- /dev/null +++ b/lib/riscv/io.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Each architecture must implement puts() and exit() with the I/O + * devices exposed from QEMU, e.g. ns16550a. + * + * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@xxxxxxxxxxxxxxxx> + */ +#include <libcflat.h> +#include <config.h> +#include <asm/io.h> +#include <asm/spinlock.h> + +/* + * Use this guess for the uart base in order to make an attempt at + * having earlier printf support. We'll overwrite it with the real + * base address that we read from the device tree later. This is + * the address we expect the virtual machine manager to put in + * its generated device tree. + */ +#define UART_EARLY_BASE ((u8 *)(unsigned long)CONFIG_UART_EARLY_BASE) +static volatile u8 *uart0_base = UART_EARLY_BASE; +static struct spinlock uart_lock; + +void puts(const char *s) +{ + spin_lock(&uart_lock); + while (*s) + writeb(*s++, uart0_base); + spin_unlock(&uart_lock); +} + +/* + * Defining halt to take 'code' as an argument guarantees that it will + * be in a0 when we halt. That gives us a final chance to see the exit + * status while inspecting the halted unit test state. + */ +void halt(int code); + +void exit(int code) +{ + printf("\nEXIT: STATUS=%d\n", ((code) << 1) | 1); + halt(code); + __builtin_unreachable(); +} diff --git a/lib/riscv/setup.c b/lib/riscv/setup.c new file mode 100644 index 000000000000..8937525ccb7f --- /dev/null +++ b/lib/riscv/setup.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Initialize machine setup information and I/O. + * + * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@xxxxxxxxxxxxxxxx> + */ +#include <libcflat.h> +#include <asm/setup.h> + +void setup(const void *fdt, phys_addr_t freemem_start) +{ +} diff --git a/riscv/Makefile b/riscv/Makefile new file mode 100644 index 000000000000..f2e89f0e4c38 --- /dev/null +++ b/riscv/Makefile @@ -0,0 +1,83 @@ +# +# riscv makefile +# +# Authors: Andrew Jones <ajones@xxxxxxxxxxxxxxxx> +# + +ifeq ($(CONFIG_EFI),y) +exe = efi +else +exe = flat +endif + +tests = +tests += $(TEST_DIR)/selftest.$(exe) +#tests += $(TEST_DIR)/sieve.$(exe) + +all: $(tests) + +$(TEST_DIR)/sieve.elf: AUXFLAGS = 0x1 + +cstart.o = $(TEST_DIR)/cstart.o + +cflatobjs += lib/riscv/io.o +cflatobjs += lib/riscv/setup.o + +######################################## + +OBJDIRS += lib/riscv +FLATLIBS = $(libcflat) $(LIBFDT_archive) + +AUXFLAGS ?= 0x0 + +# stack.o relies on frame pointers. +KEEP_FRAME_POINTER := y + +# We want to keep intermediate files +.PRECIOUS: %.elf %.o + +define arch_elf_check = + $(if $(shell ! $(READELF) -rW $(1) >&/dev/null && echo "nok"), + $(error $(shell $(READELF) -rW $(1) 2>&1))) + $(if $(shell $(READELF) -rW $(1) | grep R_ | grep -v R_RISCV_RELATIVE), + $(error $(1) has unsupported reloc types)) +endef + +ifeq ($(ARCH),riscv64) +CFLAGS += -DCONFIG_64BIT +endif +CFLAGS += -DCONFIG_RELOC +CFLAGS += -mcmodel=medany +CFLAGS += -mstrict-align +CFLAGS += -std=gnu99 +CFLAGS += -ffreestanding +CFLAGS += -O2 +CFLAGS += -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt + +asm-offsets = lib/riscv/asm-offsets.h +include $(SRCDIR)/scripts/asm-offsets.mak + +ifeq ($(CONFIG_EFI),y) + # TODO +else +%.elf: LDFLAGS += -pie -n -z notext +%.elf: %.o $(FLATLIBS) $(SRCDIR)/riscv/flat.lds $(cstart.o) + $(CC) $(CFLAGS) -c -o $(@:.elf=.aux.o) $(SRCDIR)/lib/auxinfo.c \ + -DPROGNAME=\"$(notdir $(@:.elf=.flat))\" -DAUXFLAGS=$(AUXFLAGS) + $(LD) $(LDFLAGS) -o $@ -T $(SRCDIR)/riscv/flat.lds \ + $(filter %.o, $^) $(FLATLIBS) $(@:.elf=.aux.o) + $(RM) $(@:.elf=.aux.o) + @chmod a-x $@ + +%.flat: %.elf + $(call arch_elf_check, $^) + $(OBJCOPY) -O binary $^ $@ + @chmod a-x $@ +endif + +generated-files = $(asm-offsets) +$(tests:.$(exe)=.o) $(cstart.o) $(cflatobjs): $(generated-files) + +arch_clean: asm_offsets_clean + $(RM) $(TEST_DIR)/*.{o,flat,elf,so,efi,debug} \ + $(TEST_DIR)/.*.d $(TEST_DIR)/efi/.*.d lib/riscv/.*.d diff --git a/riscv/cstart.S b/riscv/cstart.S new file mode 100644 index 000000000000..a28d75e8021e --- /dev/null +++ b/riscv/cstart.S @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Boot entry point and assembler functions for riscv. + * + * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@xxxxxxxxxxxxxxxx> + */ +#include <asm/csr.h> + +.macro zero_range, tmp1, tmp2 +9998: beq \tmp1, \tmp2, 9997f + sd zero, 0(\tmp1) + addi \tmp1, \tmp1, 8 + j 9998b +9997: +.endm + + .section .init + +/* + * The hartid of the current core is in a0 + * The address of the devicetree is in a1 + * + * See Linux kernel doc Documentation/riscv/boot.rst + */ +.global start +start: + /* + * Stash the hartid in scratch and shift the dtb + * address into a0 + */ + csrw CSR_SSCRATCH, a0 + mv a0, a1 + + /* + * Update all R_RISCV_RELATIVE relocations using the table + * of Elf64_Rela entries between reloc_start/end. The build + * will not emit other relocation types. + * + * struct Elf64_Rela { + * uint64_t r_offset; + * uint64_t r_info; + * int64_t r_addend; + * } + */ + la a1, reloc_start + la a2, reloc_end + la a3, start // base +1: + bge a1, a2, 1f + ld a4, 0(a1) // r_offset + ld a5, 16(a1) // r_addend + add a4, a3, a4 // addr = base + r_offset + add a5, a3, a5 // val = base + r_addend + sd a5, 0(a4) // *addr = val + addi a1, a1, 24 + j 1b + +1: + /* zero BSS */ + la a1, bss + la a2, ebss + zero_range a1, a2 + + /* zero and set up stack */ + la sp, stacktop + li a1, -8192 + add a1, sp, a1 + zero_range a1, sp + + /* set up exception handling */ + //TODO + + /* complete setup */ + la a1, stacktop // a1 is the base of free memory + call setup // a0 is the addr of the dtb + + /* run the test */ + la a0, __argc + ld a0, 0(a0) + la a1, __argv + la a2, __environ + call main + call exit + j halt + + .text + +.balign 4 +.global halt +halt: +1: wfi + j 1b diff --git a/riscv/flat.lds b/riscv/flat.lds new file mode 100644 index 000000000000..d4853f82ba1c --- /dev/null +++ b/riscv/flat.lds @@ -0,0 +1,75 @@ +/* + * init::start will pass stacktop to setup() as the base of free memory. + * setup() will then move the FDT and initrd to that base before calling + * mem_init(). With those movements and this linker script, we'll end up + * having the following memory layout: + * + * +----------------------+ <-- top of physical memory + * | | + * ~ ~ + * | | + * +----------------------+ <-- top of initrd + * | | + * +----------------------+ <-- top of FDT + * | | + * +----------------------+ <-- top of cpu0's stack + * | | + * +----------------------+ <-- top of text/data/bss sections + * | | + * | | + * +----------------------+ <-- load address + * | | + * +----------------------+ <-- physical address 0x0 + */ + +PHDRS +{ + text PT_LOAD FLAGS(5); + data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + PROVIDE(_text = .); + .text : { *(.init) *(.text) *(.text.*) } :text + . = ALIGN(4K); + PROVIDE(_etext = .); + + PROVIDE(reloc_start = .); + .rela.dyn : { *(.rela.dyn) } + PROVIDE(reloc_end = .); + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .got : { *(.got) *(.got.plt) } + .eh_frame : { *(.eh_frame) } + + .rodata : { *(.rodata*) } :data + .data : { *(.data) } :data + . = ALIGN(16); + PROVIDE(bss = .); + .bss : { *(.bss) } + . = ALIGN(16); + PROVIDE(ebss = .); + . = ALIGN(4K); + PROVIDE(edata = .); + + /* + * stack depth is 8K and sp must be 16 byte aligned + * sp must always be strictly less than the true stacktop + */ + . += 12K; + . = ALIGN(4K); + PROVIDE(stackptr = . - 16); + PROVIDE(stacktop = .); + + /DISCARD/ : { + *(.note*) + *(.interp) + *(.comment) + *(.dynamic) + } +} + +ENTRY(start) diff --git a/riscv/selftest.c b/riscv/selftest.c new file mode 100644 index 000000000000..88afa732649e --- /dev/null +++ b/riscv/selftest.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test the framework itself. These tests confirm that setup works. + * + * Copyright (C) 2023, Ventana Micro Systems Inc., Andrew Jones <ajones@xxxxxxxxxxxxxxxx> + */ +#include <libcflat.h> + +int main(void) +{ + puts("Hello, world\n"); + return 0; +} -- 2.43.0