On 1/26/24 15:23, Andrew Jones wrote: > 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 For my education what were the tmp3/4 args used for on arm? > +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; > +} Thanks Eric