Message ID | 20240126142324.66674-28-andrew.jones@linux.dev (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Introduce RISC-V | expand |
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@linux.dev> > Acked-by: Thomas Huth <thuth@redhat.com> > --- > 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@ventanamicro.com> > + */ > +#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@ventanamicro.com> > + */ > +#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@ventanamicro.com> > +# > + > +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@ventanamicro.com> > + */ > +#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@ventanamicro.com> > + */ > +#include <libcflat.h> > + > +int main(void) > +{ > + puts("Hello, world\n"); > + return 0; > +} Thanks Eric
On Thu, Feb 01, 2024 at 09:29:36AM +0100, Eric Auger wrote: ... > > --- /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@ventanamicro.com> > > + */ > > +#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 > > + arm doesn't have a zero register like arm64 and riscv32/64 have, so at least one extra tmp register is needed to hold the zero stored to the memory. We use two tmp registers because arm has a 'strd' instruction allowing us to write two at once, as long as the first register is an even-numbered register and the second is the immediately following odd-numbered register. (We should probably write a comment about the purpose and even/odd constraints of tmp3/4 above the zero_range macro in arm/cstart.S) Thanks, drew
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@ventanamicro.com> + */ +#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@ventanamicro.com> + */ +#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@ventanamicro.com> +# + +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@ventanamicro.com> + */ +#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@ventanamicro.com> + */ +#include <libcflat.h> + +int main(void) +{ + puts("Hello, world\n"); + return 0; +}