From 9274ee656e3bbd147a2ee44d4ff8a2f37359f2c9 Mon Sep 17 00:00:00 2001 From: xvanc Date: Sat, 3 Jun 2023 18:36:06 -0500 Subject: [PATCH] Initial riscv64 port (#274) * initial riscv64 port * enable Paging Mode feature for all architectures * riscv: add missing protocol docs * riscv: fix tests * docs: clarify `LIMINE_PAGING_MODE_DEFAULT` macro * build: fix whitespace in common/GNUmakefile * riscv: default to Sv48 paging when supported * vmm: make `VMM_MAX_LEVEL` 1-indexed * limine: do not call `reported_addr()` before finaling paging mode smp/riscv: do not overwrite the argument passed to APs * limine/riscv: update default paging mode in limine.h * test/riscv: pad OVMF.fd when downloading it --- .github/workflows/check.yml | 5 +- .gitignore | 1 + GNUmakefile.in | 37 ++++- PROTOCOL.md | 160 ++++++++++++++++++++++ README.md | 3 +- common/GNUmakefile | 94 +++++++++++++ common/efi_thunk.asm_uefi_riscv64 | 11 ++ common/lib/elf.c | 16 +++ common/lib/misc.c | 42 ++++++ common/lib/misc.h | 10 +- common/lib/panic.s2.c | 2 +- common/lib/spinup.asm_riscv64 | 45 ++++++ common/lib/term.c | 2 +- common/lib/trace.s2.c | 6 + common/linker_uefi_riscv64.ld.in | 94 +++++++++++++ common/menu.c | 6 +- common/menu_thunk.asm_riscv64 | 21 +++ common/mm/vmm.c | 189 ++++++++++++++++++++------ common/mm/vmm.h | 63 +++++++++ common/protos/limine.c | 149 ++++++++++++++++---- common/sys/cpu.h | 33 +++++ common/sys/sbi.asm_riscv64 | 15 ++ common/sys/sbi.h | 32 +++++ common/sys/smp.c | 127 ++++++++++++++++- common/sys/smp.h | 9 +- common/sys/smp_trampoline.asm_riscv64 | 63 +++++++++ configure.ac | 28 ++++ limine.h | 64 ++++++++- test.mk | 28 ++++ test/limine.c | 30 ++++ 30 files changed, 1302 insertions(+), 83 deletions(-) create mode 100644 common/efi_thunk.asm_uefi_riscv64 create mode 100644 common/lib/spinup.asm_riscv64 create mode 100644 common/linker_uefi_riscv64.ld.in create mode 100644 common/menu_thunk.asm_riscv64 create mode 100644 common/sys/sbi.asm_riscv64 create mode 100644 common/sys/sbi.h create mode 100644 common/sys/smp_trampoline.asm_riscv64 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a3c6bcf0..65b2dd8f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Install dependencies - run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S base-devel git autoconf automake nasm curl mtools llvm clang lld aarch64-linux-gnu-gcc + run: pacman --noconfirm -Syu && pacman --needed --noconfirm -S base-devel git autoconf automake nasm curl mtools llvm clang lld aarch64-linux-gnu-gcc riscv64-linux-gnu-gcc - name: Checkout code uses: actions/checkout@v3 @@ -26,3 +26,6 @@ jobs: - name: Build the bootloader (GNU, aarch64) run: ./bootstrap && ./configure TOOLCHAIN_FOR_TARGET=aarch64-linux-gnu --enable-werror --enable-uefi-aarch64 && make all && make maintainer-clean + + - name: Build the bootloader (GNU, riscv64) + run: ./bootstrap && ./configure TOOLCHAIN_FOR_TARGET=riscv64-linux-gnu --enable-werror --enable-uefi-riscv64 && make all && make maintainer-clean diff --git a/.gitignore b/.gitignore index 79fd6412..0c576f53 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,6 @@ /common-uefi-ia32 /common-uefi-x86-64 /common-uefi-aarch64 +/common-uefi-riscv64 /decompressor-build /stage1.stamp diff --git a/GNUmakefile.in b/GNUmakefile.in index 47581c53..49ba4f3e 100644 --- a/GNUmakefile.in +++ b/GNUmakefile.in @@ -40,6 +40,7 @@ override BUILD_BIOS := @BUILD_BIOS@ override BUILD_UEFI_X86_64 := @BUILD_UEFI_X86_64@ override BUILD_UEFI_IA32 := @BUILD_UEFI_IA32@ override BUILD_UEFI_AARCH64 := @BUILD_UEFI_AARCH64@ +override BUILD_UEFI_RISCV64 := @BUILD_UEFI_RISCV64@ override BUILD_CD_EFI := @BUILD_CD_EFI@ override BUILD_PXE := @BUILD_PXE@ override BUILD_CD := @BUILD_CD@ @@ -104,7 +105,7 @@ all: $(call MKESCAPE,$(BINDIR))/Makefile $(MAKE) all1 .PHONY: all1 -all1: $(BUILD_UEFI_X86_64) $(BUILD_UEFI_IA32) $(BUILD_UEFI_AARCH64) $(BUILD_BIOS) +all1: $(BUILD_UEFI_X86_64) $(BUILD_UEFI_IA32) $(BUILD_UEFI_AARCH64) $(BUILD_UEFI_RISCV64) $(BUILD_BIOS) $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine' $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' @@ -131,7 +132,7 @@ limine: $(MAKE) '$(call SHESCAPE,$(BINDIR))/limine' .PHONY: clean -clean: limine-bios-clean limine-uefi-ia32-clean limine-uefi-x86-64-clean limine-uefi-aarch64-clean +clean: limine-bios-clean limine-uefi-ia32-clean limine-uefi-x86-64-clean limine-uefi-aarch64-clean limine-uefi-riscv64-clean rm -rf '$(call SHESCAPE,$(BINDIR))' '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp' .PHONY: install @@ -161,6 +162,9 @@ endif ifeq ($(BUILD_UEFI_AARCH64),limine-uefi-aarch64) $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTAA64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' endif +ifeq ($(BUILD_UEFI_RISCV64),limine-uefi-riscv64) + $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' +endif ifeq ($(BUILD_UEFI_X86_64),limine-uefi-x86-64) $(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTX64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/' endif @@ -204,7 +208,7 @@ endif limine-bios: common-bios decompressor $(MAKE) '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp' -$(call MKESCAPE,$(BINDIR))/limine-cd-efi.bin: $(if $(BUILD_UEFI_IA32),$(call MKESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI) $(if $(BUILD_UEFI_X86_64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI) $(if $(BUILD_UEFI_AARCH64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI) +$(call MKESCAPE,$(BINDIR))/limine-cd-efi.bin: $(if $(BUILD_UEFI_IA32),$(call MKESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI) $(if $(BUILD_UEFI_X86_64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI) $(if $(BUILD_UEFI_AARCH64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI) $(if $(BUILD_UEFI_RISCV64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI) ifneq ($(BUILD_CD_EFI),no) $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' rm -f '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' @@ -214,6 +218,8 @@ ifneq ($(BUILD_CD_EFI),no) mmd -D s -i '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' ::/EFI/BOOT && \ ( ( [ -f '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' ] && \ mcopy -D o -i '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' ::/EFI/BOOT ) || true ) && \ + ( ( [ -f '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' ] && \ + mcopy -D o -i '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' ::/EFI/BOOT ) || true ) && \ ( ( [ -f '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI' ] && \ mcopy -D o -i '$(call SHESCAPE,$(BINDIR))/limine-cd-efi.bin' '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI' ::/EFI/BOOT ) || true ) && \ ( ( [ -f '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI' ] && \ @@ -252,6 +258,15 @@ limine-uefi-aarch64: $(MAKE) common-uefi-aarch64 $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTAA64.EFI' +$(call MKESCAPE,$(BINDIR))/BOOTRISCV64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI + $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))' + cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' '$(call SHESCAPE,$(BINDIR))/' + +.PHONY: limine-uefi-riscv64 +limine-uefi-riscv64: + $(MAKE) common-uefi-riscv64 + $(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI' + .PHONY: limine-bios-clean limine-bios-clean: common-bios-clean decompressor-clean @@ -264,6 +279,9 @@ limine-uefi-ia32-clean: common-uefi-ia32-clean .PHONY: limine-uefi-aarch64-clean limine-uefi-aarch64-clean: common-uefi-aarch64-clean +.PHONY: limine-uefi-riscv64-clean +limine-uefi-riscv64-clean: common-uefi-riscv64-clean + .PHONY: dist dist: rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)" @@ -275,7 +293,7 @@ dist: rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/freestanding-headers/.git" rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/.git" rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/.gitignore" - libgcc_needed="i686 x86_64-no-red-zone aarch64"; \ + libgcc_needed="i686 x86_64-no-red-zone aarch64 riscv64"; \ for f in $$libgcc_needed; do \ mv '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/libgcc-$$f.a" '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)/libgcc-binaries/libgcc-$$f.a.save"; \ done; \ @@ -328,6 +346,17 @@ common-uefi-aarch64: common-uefi-aarch64-clean: rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64' +.PHONY: common-uefi-riscv64 +common-uefi-riscv64: + $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' all \ + TOOLCHAIN_FILE='$(call SHESCAPE,$(BUILDDIR))/toolchain-files/uefi-riscv64-toolchain.mk' \ + TARGET=uefi-riscv64 \ + BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64' + +.PHONY: common-uefi-riscv64-clean +common-uefi-riscv64-clean: + rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64' + .PHONY: common-uefi-ia32 common-uefi-ia32: $(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' all \ diff --git a/PROTOCOL.md b/PROTOCOL.md index 7e304101..943f8901 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -197,6 +197,34 @@ Size Request (see below). All other general purpose registers (including `X29` and `X30`) are set to 0. Vector registers are in an undefined state. +### riscv64 + +At entry the machine is executing in Supervisor mode. + +`pc` will be the entry point as defined as part of the executable file format, +unless the an Entry Point feature is requested (see below), in which case, +the value of `pc` is going to be taken from there. + +`x1`(`ra`) is undefined, the kernel must not return from the entry point. + +`x2`(`sp`) is set to point to a stack, in bootloader-reclaimable memory, which is +at least 64KiB (65536 bytes) in size, or the size specified in the Stack +Size Request (see below). + +`x3`(`gp`) is undefined, kernel must load its own global pointer if needed. + +All other general purpose registers, with the exception of `x5`(`t0`), are set to 0. + +If booted by EFI/UEFI, boot services are exited. + +`stvec` is in an undefined state. `sstatus.SIE` and `sie` are set to 0. + +`sstatus.FS` and `sstatus.XS` are both set to `Off`. + +Paging is enable with the paging mode specified by the Paging Mode feature (see below). + +The (A)PLIC, if present, is in an undefined state. + ## Feature List Request IDs are composed of 4 64-bit unsigned integers, but the first 2 are @@ -632,8 +660,93 @@ struct limine_video_mode { }; ``` +### Paging Mode Feature + +The Paging Mode feature allows the kernel to control which paging mode is enabled +before control is passed to it. + +ID: +```c +#define LIMINE_PAGING_MODE_REQUEST { LIMINE_COMMON_MAGIC, 0x95c1a0edab0944cb, 0xa4e5cb3842f7488a } +``` + +Request: +```c +struct limine_paging_mode_request { + uint64_t id[4]; + uint64_t revision; + struct limine_paging_mode_response *response; + uint64_t mode; + uint64_t flags; +}; +``` + +Both the `mode` and `flags` fields are architecture-specific. + +The `LIMINE_PAGING_MODE_DEFAULT` macro is provided by all architectures to select +the default paging mode (see below). + +Response: +```c +struct limine_paging_mode_response { + uint64_t revision; + uint64_t mode; + uint64_t flags; +}; +``` + +The response indicates which paging mode was actually enabled by the bootloader. +Kernels must be prepared to handle the case where the requested paging mode is +not supported by the hardware. + +#### x86_64 + +Values for `mode`: +```c +#define LIMINE_PAGING_MODE_X86_64_4LVL 0 +#define LIMINE_PAGING_MODE_X86_64_5LVL 1 + +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_X86_64_4LVL +``` + +No `flags` are currently defined. + +The default mode (when this request is not provided) is `LIMINE_PAGING_MODE_X86_64_4LVL`. + +#### aarch64 + +Values for `mode`: +```c +#define LIMINE_PAGING_MODE_AARCH64_4LVL 0 +#define LIMINE_PAGING_MODE_AARCH64_5LVL 1 + +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_AARCH64_4LVL +``` + +No `flags` are currently defined. + +The default mode (when this request is not provided) is `LIMINE_PAGING_MODE_AARCH64_4LVL`. + +#### riscv64 + +Values for `mode`: +```c +#define LIMINE_PAGING_MODE_RISCV_SV39 0 +#define LIMINE_PAGING_MODE_RISCV_SV48 1 +#define LIMINE_PAGING_MODE_RISCV_SV57 2 + +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48 +``` + +No `flags` are currently defined. + +The default mode (when this request is not provided) is `LIMINE_PAGING_MODE_RISCV_SV48`. + ### 5-Level Paging Feature +Note: *This feature has been deprecated in favor of the [Paging Mode feature](#paging-mode-feature) +and will be removed entirely in a future release.* + ID: ```c #define LIMINE_5_LEVEL_PAGING_REQUEST { LIMINE_COMMON_MAGIC, 0x94469551da9b3192, 0xebe5e86db7382888 } @@ -775,6 +888,53 @@ processor. This field is unused for the structure describing the bootstrap processor. * `extra_argument` - A free for use field. +#### riscv64 + +Response: + +```c +struct limine_smp_response { + uint64_t revision; + uint32_t flags; + uint64_t bsp_hartid; + uint64_t cpu_count; + struct limine_smp_info **cpus; +}; +``` + +* `flags` - Always zero +* `bsp_hartid` - Hart ID of the bootstrap processor as reported by the UEFI RISC-V Boot Protocol or the SBI. +* `cpu_count` - How many CPUs are present. It includes the bootstrap processor. +* `cpus` - Pointer to an array of `cpu_count` pointers to +`struct limine_smp_info` structures. + +Notes: The presence of this request will prompt the bootloader to bootstrap +the secondary processors. This will not be done if this request is not present. + +```c +struct limine_smp_info; + +typedef void (*limine_goto_address)(struct limine_smp_info *); + +struct limine_smp_info { + uint32_t processor_id; + uint64_t hartid; + uint64_t reserved; + limine_goto_address goto_address; + uint64_t extra_argument; +}; +``` + +* `processor_id` - ACPI Processor UID as specified by the MADT (always 0 on non-ACPI systems). +* `hartid` - Hart ID of the processor as specified by the MADT or Device Tree. +* `goto_address` - An atomic write to this field causes the parked CPU to +jump to the written address, on a 64KiB (or Stack Size Request size) stack. A pointer to the +`struct limine_smp_info` structure of the CPU is passed in `x10`(`a0`). Other than +that, the CPU state will be the same as described for the bootstrap +processor. This field is unused for the structure describing the bootstrap +processor. +* `extra_argument` - A free for use field. + ### Memory Map Feature ID: diff --git a/README.md b/README.md index b82cc2e1..8ff6c76e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ as the reference implementation for the [Limine boot protocol](/PROTOCOL.md). * IA-32 (32-bit x86) * x86_64 * aarch64 (arm64) +* riscv64 ### Supported boot protocols * Linux @@ -40,7 +41,7 @@ opening issues or pull requests related to this. For 32-bit x86 systems, support is only ensured starting with those with Pentium Pro (i686) class CPUs. -All x86_64 and aarch64 (UEFI) systems are supported. +All x86_64, aarch64, and riscv64 (UEFI) systems are supported. ## Packaging status diff --git a/common/GNUmakefile b/common/GNUmakefile index 7d1aad30..23f0b51d 100644 --- a/common/GNUmakefile +++ b/common/GNUmakefile @@ -30,6 +30,8 @@ else ifeq ($(TARGET),uefi-ia32) override OBJCOPY2ELF_FLAGS := -B i386 -O elf32-i386 else ifeq ($(TARGET),uefi-aarch64) override OBJCOPY2ELF_FLAGS := -B aarch64 -O elf64-littleaarch64 +else ifeq ($(TARGET),uefi-riscv64) + override OBJCOPY2ELF_FLAGS := -B riscv64 -O elf64-littleriscv else $(error Invalid target) endif @@ -127,6 +129,26 @@ ifeq ($(TARGET),uefi-aarch64) -DUEFI endif +ifeq ($(TARGET),uefi-riscv64) + ifeq ($(CC_FOR_TARGET_IS_CLANG),yes) + override RISCV_CFLAGS += -march=rv64imac -mabi=lp64 + else + override RISCV_CFLAGS += -march=rv64imac_zicsr_zifencei -mabi=lp64 + endif + + override CFLAGS_FOR_TARGET += \ + -fPIE \ + -fshort-wchar \ + $(RISCV_CFLAGS) + + override CPPFLAGS_FOR_TARGET := \ + -I'$(call SHESCAPE,$(BUILDDIR))/limine-efi/inc' \ + -I'$(call SHESCAPE,$(BUILDDIR))/limine-efi/inc/riscv64' \ + $(CPPFLAGS_FOR_TARGET) \ + -DUEFI \ + -D__riscv64 +endif + override LDFLAGS_FOR_TARGET += \ -nostdlib \ -z max-page-size=0x1000 @@ -169,6 +191,15 @@ ifeq ($(TARGET),uefi-aarch64) -z text endif +ifeq ($(TARGET),uefi-riscv64) + override LDFLAGS_FOR_TARGET += \ + -m elf64lriscv \ + -static \ + -pie \ + --no-dynamic-linker \ + -z text +endif + override C_FILES := $(shell find . -type f -name '*.c') ifeq ($(TARGET),bios) override ASMX86_FILES := $(shell find . -type f -name '*.asm_x86') @@ -198,6 +229,12 @@ ifeq ($(TARGET),uefi-aarch64) override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(ASM64_FILES:.asm_aarch64=.o) $(ASM64U_FILES:.asm_uefi_aarch64=.o)) endif +ifeq ($(TARGET),uefi-riscv64) + override ASM64_FILES := $(shell find . -type f -name '*.asm_riscv64') + override ASM64U_FILES := $(shell find . -type f -name '*.asm_uefi_riscv64') + + override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(ASM64_FILES:.asm_riscv64=.o) $(ASM64U_FILES:.asm_uefi_riscv64=.o)) +endif override HEADER_DEPS := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.d)) @@ -211,6 +248,8 @@ else ifeq ($(TARGET),uefi-ia32) all: $(call MKESCAPE,$(BUILDDIR))/BOOTIA32.EFI else ifeq ($(TARGET),uefi-aarch64) all: $(call MKESCAPE,$(BUILDDIR))/BOOTAA64.EFI +else ifeq ($(TARGET),uefi-riscv64) +all: $(call MKESCAPE,$(BUILDDIR))/BOOTRISCV64.EFI endif ifeq ($(TARGET),bios) @@ -376,6 +415,45 @@ $(call MKESCAPE,$(BUILDDIR))/limine.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi '$(call OBJESCAPE,$^)' $(LDFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' endif +ifeq ($(TARGET),uefi-riscv64) + +$(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf + cd '$(call SHESCAPE,$(BUILDDIR))' && \ + '$(call SHESCAPE,$(SRCDIR))/gensyms.sh' '$(call SHESCAPE,$<)' full 64 '\.text' + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$(BUILDDIR))/full.map.S' -o '$(call SHESCAPE,$@)' + rm -f '$(call SHESCAPE,$(BUILDDIR))/full.map.S' '$(call SHESCAPE,$(BUILDDIR))/full.map.d' + +$(call MKESCAPE,$(BUILDDIR))/BOOTRISCV64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf + $(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-riscv64.o $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_riscv64.o: $(call MKESCAPE,$(BUILDDIR))/limine-efi + $(MAKE) -C '$(call SHESCAPE,$(BUILDDIR))/limine-efi/gnuefi' \ + CC="$(CC_FOR_TARGET)" \ + CFLAGS="$(BASE_CFLAGS) $(RISCV_CFLAGS)" \ + CPPFLAGS='-nostdinc -I$(call SHESCAPE,$(SRCDIR))/../freestanding-headers' \ + ARCH=riscv64 + +$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_riscv64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_riscv64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-riscv64.o $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_riscv64.o $(OBJ) ../libgcc-binaries/libgcc-riscv64.a + $(MAKE) '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld' \ + '$(call OBJESCAPE,$^)' $(LDFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/linker.ld: linker_uefi_riscv64.ld.in + $(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))' + $(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_riscv64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + +$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-riscv64.o $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_riscv64.o $(OBJ) ../libgcc-binaries/libgcc-riscv64.a $(call MKESCAPE,$(BUILDDIR))/full.map.o + $(MAKE) '$(call SHESCAPE,$(BUILDDIR))/linker.ld' + $(LD_FOR_TARGET) \ + -T'$(call SHESCAPE,$(BUILDDIR))/linker.ld' \ + '$(call OBJESCAPE,$^)' $(LDFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)' +endif + ifeq ($(TARGET),uefi-ia32) $(call MKESCAPE,$(BUILDDIR))/full.map.o: $(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf @@ -430,6 +508,12 @@ $(call MKESCAPE,$(BUILDDIR))/%.o: %.c $(call MKESCAPE,$(BUILDDIR))/limine-efi $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' endif +ifeq ($(TARGET),uefi-riscv64) +$(call MKESCAPE,$(BUILDDIR))/%.o: %.c $(call MKESCAPE,$(BUILDDIR))/limine-efi + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + ifeq ($(TARGET),uefi-ia32) $(call MKESCAPE,$(BUILDDIR))/%.o: %.c $(call MKESCAPE,$(BUILDDIR))/limine-efi $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" @@ -490,6 +574,16 @@ $(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_uefi_aarch64 $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' endif +ifeq ($(TARGET),uefi-riscv64) +$(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_riscv64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' + +$(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_uefi_riscv64 + $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" + $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)' +endif + ifeq ($(TARGET),uefi-ia32) $(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_ia32 $(MKDIR_P) "$$(dirname '$(call SHESCAPE,$@)')" diff --git a/common/efi_thunk.asm_uefi_riscv64 b/common/efi_thunk.asm_uefi_riscv64 new file mode 100644 index 00000000..507cc06c --- /dev/null +++ b/common/efi_thunk.asm_uefi_riscv64 @@ -0,0 +1,11 @@ + +.global efi_main +.extern uefi_entry +efi_main: +.option push +.option norelax + lla gp, __global_pointer$ +.option pop + mv fp, zero + mv ra, zero + j uefi_entry diff --git a/common/lib/elf.c b/common/lib/elf.c index 23132fc1..6abf326f 100644 --- a/common/lib/elf.c +++ b/common/lib/elf.c @@ -28,11 +28,14 @@ #define ARCH_X86_64 0x3e #define ARCH_X86_32 0x03 #define ARCH_AARCH64 0xb7 +#define ARCH_RISCV 0xf3 #define BITS_LE 0x01 +#define ELFCLASS64 0x02 #define ET_DYN 0x0003 #define SHT_RELA 0x00000004 #define R_X86_64_RELATIVE 0x00000008 #define R_AARCH64_RELATIVE 0x00000403 +#define R_RISCV_RELATIVE 0x00000003 /* Indices into identification array */ #define EI_CLASS 4 @@ -103,6 +106,8 @@ int elf_bits(uint8_t *elf) { case ARCH_X86_64: case ARCH_AARCH64: return 64; + case ARCH_RISCV: + return (hdr->ident[EI_CLASS] == ELFCLASS64) ? 64 : 32; case ARCH_X86_32: return 32; default: @@ -226,6 +231,8 @@ static bool elf64_apply_relocations(uint8_t *elf, struct elf64_hdr *hdr, void *b case R_X86_64_RELATIVE: #elif defined (__aarch64__) case R_AARCH64_RELATIVE: +#elif defined (__riscv64) + case R_RISCV_RELATIVE: #else #error Unknown architecture #endif @@ -281,6 +288,11 @@ bool elf64_load_section(uint8_t *elf, void *buffer, const char *name, size_t lim printv("elf: Not an aarch64 ELF file.\n"); return false; } +#elif defined (__riscv64) + if (hdr->machine != ARCH_RISCV && hdr->ident[EI_CLASS] == ELFCLASS64) { + printv("elf: Not a riscv64 ELF file.\n"); + return false; + } #else #error Unknown architecture #endif @@ -416,6 +428,10 @@ bool elf64_load(uint8_t *elf, uint64_t *entry_point, uint64_t *_slide, uint32_t if (hdr->machine != ARCH_AARCH64) { panic(true, "elf: Not an aarch64 ELF file.\n"); } +#elif defined (__riscv64) + if (hdr->machine != ARCH_RISCV && hdr->ident[EI_CLASS] == ELFCLASS64) { + panic(true, "elf: Not a riscv64 ELF file.\n"); + } #else #error Unknown architecture #endif diff --git a/common/lib/misc.c b/common/lib/misc.c index f9c3bd00..d1735979 100644 --- a/common/lib/misc.c +++ b/common/lib/misc.c @@ -109,6 +109,46 @@ uint32_t hex2bin(uint8_t *str, uint32_t size) { #if defined (UEFI) +#if defined (__riscv) + +RISCV_EFI_BOOT_PROTOCOL *get_riscv_boot_protocol(void) { + EFI_GUID boot_proto_guid = RISCV_EFI_BOOT_PROTOCOL_GUID; + RISCV_EFI_BOOT_PROTOCOL *proto; + + // LocateProtocol() is available from EFI version 1.1 + if (gBS->Hdr.Revision >= ((1 << 16) | 10)) { + if (gBS->LocateProtocol(&boot_proto_guid, NULL, (void **)&proto) == EFI_SUCCESS) { + return proto; + } + } + + UINTN bufsz = 0; + if (gBS->LocateHandle(ByProtocol, &boot_proto_guid, NULL, &bufsz, NULL) != EFI_BUFFER_TOO_SMALL) + return NULL; + + EFI_HANDLE *handles_buf = ext_mem_alloc(bufsz); + if (handles_buf == NULL) + return NULL; + + if (bufsz < sizeof(EFI_HANDLE)) + goto error; + + if (gBS->LocateHandle(ByProtocol, &boot_proto_guid, NULL, &bufsz, handles_buf) != EFI_SUCCESS) + goto error; + + if (gBS->HandleProtocol(handles_buf[0], &boot_proto_guid, (void **)&proto) != EFI_SUCCESS) + goto error; + + pmm_free(handles_buf, bufsz); + return proto; + +error: + pmm_free(handles_buf, bufsz); + return NULL; +} + +#endif + no_unwind bool efi_boot_services_exited = false; bool efi_exit_boot_services(void) { @@ -162,6 +202,8 @@ retry: asm volatile ("cli" ::: "memory"); #elif defined (__aarch64__) asm volatile ("msr daifset, #15" ::: "memory"); +#elif defined (__riscv64) + asm volatile ("csrci sstatus, 0x2" ::: "memory"); #else #error Unknown architecture #endif diff --git a/common/lib/misc.h b/common/lib/misc.h index a85e89fc..e6ff507d 100644 --- a/common/lib/misc.h +++ b/common/lib/misc.h @@ -10,6 +10,9 @@ #include #if defined (UEFI) # include +# if defined (__riscv64) +# include +# endif #endif #if defined (UEFI) @@ -57,7 +60,7 @@ uint64_t strtoui(const char *s, const char **end, int base); #if defined (__i386__) void memcpy32to64(uint64_t, uint64_t, uint64_t); -#elif defined (__x86_64__) || defined (__aarch64__) +#elif defined (__x86_64__) || defined (__aarch64__) || defined(__riscv64) # define memcpy32to64(X, Y, Z) memcpy((void *)(uintptr_t)(X), (void *)(uintptr_t)(Y), Z) #else #error Unknown architecture @@ -98,6 +101,11 @@ noreturn void enter_in_current_el(uint64_t entry, uint64_t sp, uint64_t sctlr, noreturn void enter_in_el1(uint64_t entry, uint64_t sp, uint64_t sctlr, uint64_t mair, uint64_t tcr, uint64_t ttbr0, uint64_t ttbr1, uint64_t target_x0); +#elif defined (__riscv64) +noreturn void riscv_spinup(uint64_t entry, uint64_t sp, uint64_t satp); +#if defined (UEFI) +RISCV_EFI_BOOT_PROTOCOL *get_riscv_boot_protocol(void); +#endif #else #error Unknown architecture #endif diff --git a/common/lib/panic.s2.c b/common/lib/panic.s2.c index 23634504..1c980020 100644 --- a/common/lib/panic.s2.c +++ b/common/lib/panic.s2.c @@ -74,7 +74,7 @@ noreturn void panic(bool allow_menu, const char *fmt, ...) { for (;;) { #if defined (__x86_64__) || defined (__i386__) asm ("hlt"); -#elif defined (__aarch64__) +#elif defined (__aarch64__) || defined (__riscv64) asm ("wfi"); #else #error Unknown architecture diff --git a/common/lib/spinup.asm_riscv64 b/common/lib/spinup.asm_riscv64 new file mode 100644 index 00000000..e01f808f --- /dev/null +++ b/common/lib/spinup.asm_riscv64 @@ -0,0 +1,45 @@ + +.section .text + +.global riscv_spinup +riscv_spinup: + + csrci sstatus, 0x2 + csrw sie, zero + csrw stvec, zero + + mv t0, a0 + mv sp, a1 + csrw satp, a2 + + mv a0, zero + mv a1, zero + mv a2, zero + mv a3, zero + mv a4, zero + mv a5, zero + mv a6, zero + mv a7, zero + mv s0, zero + mv s1, zero + mv s2, zero + mv s3, zero + mv s4, zero + mv s5, zero + mv s6, zero + mv s7, zero + mv s8, zero + mv s9, zero + mv s10, zero + mv s11, zero + mv t1, zero + mv t2, zero + mv t3, zero + mv t4, zero + mv t5, zero + mv t6, zero + mv tp, zero + mv gp, zero + mv ra, zero + + jr t0 diff --git a/common/lib/term.c b/common/lib/term.c index 57ca6049..3f2e1555 100644 --- a/common/lib/term.c +++ b/common/lib/term.c @@ -346,7 +346,7 @@ void _term_write(struct flanterm_context *term, uint64_t buf, uint64_t count) { } bool native = false; -#if defined (__x86_64__) || defined (__aarch64__) +#if defined (__x86_64__) || defined (__aarch64__) || defined (__riscv64) native = true; #elif !defined (__i386__) #error Unknown architecture diff --git a/common/lib/trace.s2.c b/common/lib/trace.s2.c index 6017d78b..8428f139 100644 --- a/common/lib/trace.s2.c +++ b/common/lib/trace.s2.c @@ -54,6 +54,8 @@ void print_stacktrace(size_t *base_ptr) { "movq %%rbp, %0" #elif defined (__aarch64__) "mov %0, x29" +#elif defined (__riscv64) + "mv %0, fp; addi %0, %0, -16" #endif : "=r"(base_ptr) :: "memory" @@ -73,7 +75,11 @@ void print_stacktrace(size_t *base_ptr) { print(" [%p]\n", ret_addr); if (!old_bp) break; +#if defined (__riscv) + base_ptr = (void *)(old_bp - 16); +#else base_ptr = (void*)old_bp; +#endif } print("End of trace. "); } diff --git a/common/linker_uefi_riscv64.ld.in b/common/linker_uefi_riscv64.ld.in new file mode 100644 index 00000000..b1178bec --- /dev/null +++ b/common/linker_uefi_riscv64.ld.in @@ -0,0 +1,94 @@ +OUTPUT_FORMAT(elf64-littleriscv) +OUTPUT_ARCH(riscv) +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; + data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; + dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; +} + +SECTIONS +{ + . = 0; + __image_base = .; + __image_size = __image_end - __image_base; + + .text : { + *(.pe_header) + . = ALIGN(0x1000); + + *(.text .text.*) + . = ALIGN(0x1000); + } :text + + __text_start = __image_base + 0x1000; + __text_size = SIZEOF(.text) - 0x1000; + __text_end = __text_start + __text_size; + + .data.sbat : { + *(.data.sbat) + . = ALIGN(0x1000); + } :data + + PROVIDE(__sbat_sizev = 1); + + __sbat_start = __text_end; + __sbat_size = SIZEOF(.data.sbat); + __sbat_end = __sbat_start + __sbat_size; + + .data.reloc : { + *(.data.reloc) + . = ALIGN(0x1000); + } :data + + __reloc_start = __sbat_end; + __reloc_size = SIZEOF(.data.reloc); + __reloc_end = __reloc_start + __reloc_size; + + .data : { + *(.rodata .rodata.*) + +#ifdef LINKER_NOMAP + full_map = .; +#else + *(.full_map) +#endif + + *(.no_unwind) + + data_begin = .; + *(.data .data.*) + *(.sdata .sdata.*) + __global_pointer$ = .; + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + data_end = .; + } :data + + .rela : { + *(.rela .rela.*) + } :data + + .got : { + *(.got .got.*) + } :data + + .dynamic : { + *(.dynamic) + . = ALIGN(0x1000); + } :data :dynamic + + __data_start = __reloc_end; + __data_size = SIZEOF(.data) + SIZEOF(.rela) + SIZEOF(.got) + SIZEOF(.dynamic); + __data_end = __data_start + __data_size; + + __image_end = __data_end; + + /DISCARD/ : { + *(.eh_frame) + *(.note .note.*) + } +} diff --git a/common/menu.c b/common/menu.c index 50888264..8792ec8a 100644 --- a/common/menu.c +++ b/common/menu.c @@ -958,21 +958,21 @@ noreturn void boot(char *config) { linux_load(config, cmdline); #else quiet = false; - print("TODO: Linux is not available on aarch64.\n\n"); + print("TODO: Linux is not available on aarch64 or riscv64.\n\n"); #endif } else if (!strcmp(proto, "multiboot1") || !strcmp(proto, "multiboot")) { #if defined (__x86_64__) || defined (__i386__) multiboot1_load(config, cmdline); #else quiet = false; - print("Multiboot 1 is not available on aarch64.\n\n"); + print("Multiboot 1 is not available on aarch64 or riscv64.\n\n"); #endif } else if (!strcmp(proto, "multiboot2")) { #if defined (__x86_64__) || defined (__i386__) multiboot2_load(config, cmdline); #else quiet = false; - print("Multiboot 2 is not available on aarch64.\n\n"); + print("Multiboot 2 is not available on aarch64 or riscv64.\n\n"); #endif } else if (!strcmp(proto, "chainload_next")) { chainload_next(config, cmdline); diff --git a/common/menu_thunk.asm_riscv64 b/common/menu_thunk.asm_riscv64 new file mode 100644 index 00000000..1427f895 --- /dev/null +++ b/common/menu_thunk.asm_riscv64 @@ -0,0 +1,21 @@ +.section .data + +.p2align 3 +stack_at_first_entry: + .8byte 0 + +.section .text + +.global menu +.extern _menu + +menu: + lla t0, stack_at_first_entry + ld t1, (t0) + beqz t1, 1f + mv sp, t1 + j 2f +1: sd sp, (t0) +2: mv fp, zero + mv ra, zero + j _menu diff --git a/common/mm/vmm.c b/common/mm/vmm.c index e2bdd7da..eca817b9 100644 --- a/common/mm/vmm.c +++ b/common/mm/vmm.c @@ -10,6 +10,7 @@ typedef uint64_t pt_entry_t; +static uint64_t page_sizes[5]; static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, uint64_t virt, enum page_size desired_sz, size_t level_idx, size_t entry); @@ -28,9 +29,12 @@ static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, #define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_LARGE)) == (PT_FLAG_VALID | PT_FLAG_LARGE)) #define PT_TO_VMM_FLAGS(x) ((x) & (PT_FLAG_WRITE | PT_FLAG_NX)) -pagemap_t new_pagemap(int lv) { +#define pte_new(addr, flags) ((pt_entry_t)(addr) | (flags)) +#define pte_addr(pte) ((pte) & PT_PADDR_MASK) + +pagemap_t new_pagemap(int paging_mode) { pagemap_t pagemap; - pagemap.levels = lv; + pagemap.levels = paging_mode == PAGING_MODE_X86_64_5LVL ? 5 : 4; pagemap.top_level = ext_mem_alloc(PT_SIZE); return pagemap; } @@ -146,6 +150,9 @@ void vmm_assert_4k_pages(void) { #define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_TABLE)) == PT_FLAG_VALID) #define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x)) +#define pte_new(addr, flags) ((pt_entry_t)(addr) | (flags)) +#define pte_addr(pte) ((pte) & PT_PADDR_MASK) + static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { uint64_t flags = 0; @@ -159,9 +166,9 @@ static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { return flags; } -pagemap_t new_pagemap(int lv) { +pagemap_t new_pagemap(int paging_mode) { pagemap_t pagemap; - pagemap.levels = lv; + pagemap.levels = paging_mode == PAGING_MODE_AARCH64_5LVL ? 5 : 4; pagemap.top_level[0] = ext_mem_alloc(PT_SIZE); pagemap.top_level[1] = ext_mem_alloc(PT_SIZE); return pagemap; @@ -221,57 +228,165 @@ level4: pml1[pml1_entry] = (pt_entry_t)(phys_addr | real_flags | PT_FLAG_4K_PAGE); } +#elif defined (__riscv64) + +#define PT_FLAG_VALID ((uint64_t)1 << 0) +#define PT_FLAG_READ ((uint64_t)1 << 1) +#define PT_FLAG_WRITE ((uint64_t)1 << 2) +#define PT_FLAG_EXEC ((uint64_t)1 << 3) +#define PT_FLAG_USER ((uint64_t)1 << 4) +#define PT_FLAG_ACCESSED ((uint64_t)1 << 6) +#define PT_FLAG_DIRTY ((uint64_t)1 << 7) +#define PT_PADDR_MASK ((uint64_t)0x003ffffffffffc00) + +#define PT_FLAG_RWX (PT_FLAG_READ | PT_FLAG_WRITE | PT_FLAG_EXEC) + +#define PT_TABLE_FLAGS PT_FLAG_VALID +#define PT_IS_TABLE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) == PT_FLAG_VALID) +#define PT_IS_LARGE(x) (((x) & (PT_FLAG_VALID | PT_FLAG_RWX)) > PT_FLAG_VALID) +#define PT_TO_VMM_FLAGS(x) (pt_to_vmm_flags_internal(x)) + +#define pte_new(addr, flags) (((pt_entry_t)(addr) >> 2) | (flags)) +#define pte_addr(pte) (((pte) & PT_PADDR_MASK) << 2) + +static uint64_t pt_to_vmm_flags_internal(pt_entry_t entry) { + uint64_t flags = 0; + + if (entry & PT_FLAG_WRITE) + flags |= VMM_FLAG_WRITE; + if (!(entry & PT_FLAG_EXEC)) + flags |= VMM_FLAG_NOEXEC; + + return flags; +} + +uint64_t paging_mode_higher_half(int paging_mode) { + switch (paging_mode) { + case PAGING_MODE_RISCV_SV39: + return 0xffffffc000000000; + case PAGING_MODE_RISCV_SV48: + return 0xffff800000000000; + case PAGING_MODE_RISCV_SV57: + return 0xff00000000000000; + default: + panic(false, "paging_mode_higher_half: invalid mode"); + } +} + +int vmm_max_paging_mode(void) +{ + static int max_level; + if (max_level > 0) + goto done; + + pt_entry_t *table = ext_mem_alloc(PT_SIZE); + + // Test each paging mode starting with Sv57. + // Since writes to `satp` with an invalid MODE have no effect, and pages can be mapped at + // any level, we can identity map the entire lower half (very likely guaranteeing everything + // this code needs will be mapped) and check if enabling the paging mode succeeds. + int lvl = 4; + for (; lvl >= 2; lvl--) { + pt_entry_t entry = PT_FLAG_ACCESSED | PT_FLAG_DIRTY | PT_FLAG_RWX | PT_FLAG_VALID; + for (int i = 0; i < 256; i++) { + table[i] = entry; + entry += page_sizes[lvl]; + } + + uint64_t satp = ((uint64_t)(6 + lvl) << 60) | ((uint64_t)table >> 12); + csr_write("satp", satp); + if (csr_read("satp") == satp) { + max_level = lvl; + break; + } + } + csr_write("satp", 0); + pmm_free(table, PT_SIZE); + + if (max_level == 0) + panic(false, "vmm: paging is not supported"); +done: + return 6 + max_level; +} + +pagemap_t new_pagemap(int paging_mode) { + pagemap_t pagemap; + pagemap.paging_mode = paging_mode; + pagemap.max_page_size = paging_mode - 6; + pagemap.top_level = ext_mem_alloc(PT_SIZE); + return pagemap; +} + +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size) { + // Truncate the requested page size to the maximum supported. + if (page_size > pagemap.max_page_size) + page_size = pagemap.max_page_size; + + // Convert VMM_FLAG_* into PT_FLAG_*. + // Set the ACCESSED and DIRTY flags to avoid faults. + pt_entry_t ptflags = PT_FLAG_VALID | PT_FLAG_READ | PT_FLAG_ACCESSED | PT_FLAG_DIRTY; + if (flags & VMM_FLAG_WRITE) + ptflags |= PT_FLAG_WRITE; + if (!(flags & VMM_FLAG_NOEXEC)) + ptflags |= PT_FLAG_EXEC; + + // Start at the highest level. + // The values of `enum page_size` map to the level index at which that size is mapped. + int level = pagemap.max_page_size; + pt_entry_t *table = pagemap.top_level; + for (;;) { + int index = (virt_addr >> (12 + 9 * level)) & 0x1ff; + + // Stop when we reach the level for the requested page size. + if (level == (int)page_size) { + table[index] = pte_new(phys_addr, ptflags); + break; + } + + table = get_next_level(pagemap, table, virt_addr, page_size, level, index); + level--; + } +} + #else #error Unknown architecture #endif +// Maps level indexes to the page size for that level. +_Static_assert(VMM_MAX_LEVEL <= 5, "6-level paging not supported"); +static uint64_t page_sizes[5] = { + 0x1000, + 0x200000, + 0x40000000, + 0x800000000000, + 0x100000000000000, +}; + static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, uint64_t virt, enum page_size desired_sz, size_t level_idx, size_t entry) { pt_entry_t *ret; if (PT_IS_TABLE(current_level[entry])) { - ret = (pt_entry_t *)(size_t)(current_level[entry] & PT_PADDR_MASK); + ret = (pt_entry_t *)(size_t)pte_addr(current_level[entry]); } else { if (PT_IS_LARGE(current_level[entry])) { // We are replacing an existing large page with a smaller page. // Split the previous mapping into mappings of the newly requested size // before performing the requested map operation. - uint64_t old_page_size, new_page_size; - switch (level_idx) { - case 2: - old_page_size = 0x40000000; - break; - case 1: - old_page_size = 0x200000; - break; + if ((level_idx >= VMM_MAX_LEVEL) || (level_idx == 0)) + panic(false, "Unexpected level in get_next_level"); + if (desired_sz >= VMM_MAX_LEVEL) + panic(false, "Unexpected page size in get_next_level"); - default: - panic(false, "Unexpected level in get_next_level"); - } - - switch (desired_sz) { - case Size1GiB: - new_page_size = 0x40000000; - break; - - case Size2MiB: - new_page_size = 0x200000; - break; - - case Size4KiB: - new_page_size = 0x1000; - break; - - default: - panic(false, "Unexpected page size in get_next_level"); - } + uint64_t old_page_size = page_sizes[level_idx]; + uint64_t new_page_size = page_sizes[desired_sz]; // Save all the information from the old entry at this level uint64_t old_flags = PT_TO_VMM_FLAGS(current_level[entry]); - uint64_t old_phys = current_level[entry] & PT_PADDR_MASK; + uint64_t old_phys = pte_addr(current_level[entry]); uint64_t old_virt = virt & ~(old_page_size - 1); if (old_phys & (old_page_size - 1)) @@ -279,7 +394,7 @@ static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, // Allocate a table for the next level ret = ext_mem_alloc(PT_SIZE); - current_level[entry] = (pt_entry_t)(size_t)ret | PT_TABLE_FLAGS; + current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS); // Recreate the old mapping with smaller pages for (uint64_t i = 0; i < old_page_size; i += new_page_size) { @@ -288,11 +403,9 @@ static pt_entry_t *get_next_level(pagemap_t pagemap, pt_entry_t *current_level, } else { // Allocate a table for the next level ret = ext_mem_alloc(PT_SIZE); - current_level[entry] = (pt_entry_t)(size_t)ret | PT_TABLE_FLAGS; + current_level[entry] = pte_new((size_t)ret, PT_TABLE_FLAGS); } } return ret; } - - diff --git a/common/mm/vmm.h b/common/mm/vmm.h index 3927c9bc..cddbd1eb 100644 --- a/common/mm/vmm.h +++ b/common/mm/vmm.h @@ -10,6 +10,19 @@ #define VMM_FLAG_NOEXEC ((uint64_t)1 << 63) #define VMM_FLAG_FB ((uint64_t)0) +#define VMM_MAX_LEVEL 3 + +#define PAGING_MODE_X86_64_4LVL 0 +#define PAGING_MODE_X86_64_5LVL 1 + +static inline uint64_t paging_mode_higher_half(int paging_mode) { + if (paging_mode == PAGING_MODE_X86_64_5LVL) { + return 0xff00000000000000; + } else { + return 0xffff800000000000; + } +} + typedef struct { int levels; void *top_level; @@ -32,6 +45,21 @@ void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_ #define VMM_FLAG_NOEXEC ((uint64_t)1 << 1) #define VMM_FLAG_FB ((uint64_t)1 << 2) +#define VMM_MAX_LEVEL 3 + +#define PAGING_MODE_AARCH64_4LVL 0 +#define PAGING_MODE_AARCH64_5LVL 1 + +#define paging_mode_va_bits(mode) ((mode) ? 57 : 48) + +static inline uint64_t paging_mode_higher_half(int paging_mode) { + if (paging_mode == PAGING_MODE_AARCH64_5LVL) { + return 0xff00000000000000; + } else { + return 0xffff800000000000; + } +} + typedef struct { int levels; void *top_level[2]; @@ -47,8 +75,43 @@ void vmm_assert_4k_pages(void); pagemap_t new_pagemap(int lv); void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); +#elif defined (__riscv64) + +// We use fake flags here because these don't properly map onto the +// RISC-V flags. +#define VMM_FLAG_WRITE ((uint64_t)1 << 0) +#define VMM_FLAG_NOEXEC ((uint64_t)1 << 1) +#define VMM_FLAG_FB ((uint64_t)1 << 2) + +#define VMM_MAX_LEVEL 5 + +#define PAGING_MODE_RISCV_SV39 8 +#define PAGING_MODE_RISCV_SV48 9 +#define PAGING_MODE_RISCV_SV57 10 + +enum page_size { + Size4KiB, + Size2MiB, + Size1GiB, + Size512GiB, + Size256TiB +}; + +typedef struct { + enum page_size max_page_size; + int paging_mode; + void *top_level; +} pagemap_t; + +uint64_t paging_mode_higher_half(int paging_mode); +int vmm_max_paging_mode(void); +pagemap_t new_pagemap(int paging_mode); +void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_t flags, enum page_size page_size); + #else #error Unknown architecture #endif +int vmm_max_paging_mode(void); + #endif diff --git a/common/protos/limine.c b/common/protos/limine.c index 8da7d295..3f2862be 100644 --- a/common/protos/limine.c +++ b/common/protos/limine.c @@ -35,10 +35,10 @@ #define MAX_REQUESTS 128 #define MAX_MEMMAP 256 -static pagemap_t build_pagemap(bool level5pg, bool nx, struct elf_range *ranges, size_t ranges_count, +static pagemap_t build_pagemap(int paging_mode, bool nx, struct elf_range *ranges, size_t ranges_count, uint64_t physical_base, uint64_t virtual_base, uint64_t direct_map_offset) { - pagemap_t pagemap = new_pagemap(level5pg ? 5 : 4); + pagemap_t pagemap = new_pagemap(paging_mode); if (ranges_count == 0) { // Map 0 to 2GiB at 0xffffffff80000000 @@ -183,7 +183,7 @@ extern symbol limine_spinup_32; | ((uint64_t)1 << 8) /* TTBR0 Inner WB RW-Allocate */ \ | ((uint64_t)(tsz) << 0)) /* Address bits in TTBR0 */ -#else +#elif !defined (__riscv64) #error Unknown architecture #endif @@ -191,6 +191,13 @@ static uint64_t physical_base, virtual_base, slide, direct_map_offset; static size_t requests_count; static void **requests; +static void set_paging_mode(int paging_mode, bool kaslr) { + direct_map_offset = paging_mode_higher_half(paging_mode); + if (kaslr) { + direct_map_offset += (rand64() & ~((uint64_t)0x40000000 - 1)) & 0xfffffffffff; + } +} + static uint64_t reported_addr(void *addr) { return (uint64_t)(uintptr_t)addr + direct_map_offset; } @@ -408,41 +415,106 @@ noreturn void limine_load(char *config, char *cmdline) { printv("limine: ELF entry point: %X\n", entry_point); printv("limine: Requests count: %u\n", requests_count); - // 5 level paging feature & HHDM slide - bool want_5lv; -FEAT_START - // Check if 5-level paging is available - bool level5pg = false; - // TODO(qookie): aarch64 also has optional 5 level paging when using 4K pages + // Paging Mode + int paging_mode, max_paging_mode; + #if defined (__x86_64__) || defined (__i386__) + paging_mode = max_paging_mode = PAGING_MODE_X86_64_4LVL; if (cpuid(0x00000007, 0, &eax, &ebx, &ecx, &edx) && (ecx & (1 << 16))) { printv("limine: CPU has 5-level paging support\n"); - level5pg = true; + max_paging_mode = PAGING_MODE_X86_64_5LVL; } + +#elif defined (__aarch64__) + paging_mode = max_paging_mode = PAGING_MODE_AARCH64_4LVL; + // TODO(qookie): aarch64 also has optional 5 level paging when using 4K pages + +#elif defined (__riscv64) + max_paging_mode = vmm_max_paging_mode(); + paging_mode = max_paging_mode >= PAGING_MODE_RISCV_SV48 ? PAGING_MODE_RISCV_SV48 : PAGING_MODE_RISCV_SV39; + +#else +#error Unknown architecture #endif - struct limine_5_level_paging_request *lv5pg_request = get_request(LIMINE_5_LEVEL_PAGING_REQUEST); - want_5lv = lv5pg_request != NULL && level5pg; +#if defined (__riscv64) +#define paging_mode_limine_to_vmm(x) (PAGING_MODE_RISCV_SV39 + (x)) +#define paging_mode_vmm_to_limine(x) ((x) - PAGING_MODE_RISCV_SV39) +#else +#define paging_mode_limine_to_vmm(x) (x) +#define paging_mode_vmm_to_limine(x) (x) +#endif - direct_map_offset = want_5lv ? 0xff00000000000000 : 0xffff800000000000; + bool have_paging_mode_request = false; + bool paging_mode_set = false; +FEAT_START + struct limine_paging_mode_request *pm_request = get_request(LIMINE_PAGING_MODE_REQUEST); + if (pm_request == NULL) + break; + have_paging_mode_request = true; - if (kaslr) { - direct_map_offset += (rand64() & ~((uint64_t)0x40000000 - 1)) & 0xfffffffffff; + if (pm_request->mode > LIMINE_PAGING_MODE_MAX) { + print("warning: ignoring invalid mode in paging mode request\n"); + break; } - if (want_5lv) { - void *lv5pg_response = ext_mem_alloc(sizeof(struct limine_5_level_paging_response)); - lv5pg_request->response = reported_addr(lv5pg_response); - } + paging_mode = paging_mode_limine_to_vmm(pm_request->mode); + if (paging_mode > max_paging_mode) + paging_mode = max_paging_mode; + + set_paging_mode(paging_mode, kaslr); + paging_mode_set = true; + + struct limine_paging_mode_response *pm_response = + ext_mem_alloc(sizeof(struct limine_paging_mode_response)); + + pm_response->mode = paging_mode_vmm_to_limine(paging_mode); + pm_request->response = reported_addr(pm_response); + FEAT_END + // 5 level paging feature & HHDM slide +FEAT_START + struct limine_5_level_paging_request *lv5pg_request = get_request(LIMINE_5_LEVEL_PAGING_REQUEST); + if (lv5pg_request == NULL) + break; + + if (have_paging_mode_request) { + print("paging: ignoring 5-level paging request in favor of paging mode request\n"); + break; + } +#if defined (__x86_64__) || defined (__i386__) + if (max_paging_mode < PAGING_MODE_X86_64_5LVL) + break; + paging_mode = PAGING_MODE_X86_64_5LVL; +#elif defined (__aarch64__) + if (max_paging_mode < PAGING_MODE_AARCH64_5LVL) + break; + paging_mode = PAGING_MODE_AARCH64_5LVL; +#elif defined (__riscv64) + print("warning: the 5-level paging request is not supported on RISC-V\n"); +#else +#error Unknown architecture +#endif + + set_paging_mode(paging_mode, kaslr); + paging_mode_set = true; + + void *lv5pg_response = ext_mem_alloc(sizeof(struct limine_5_level_paging_response)); + lv5pg_request->response = reported_addr(lv5pg_response); +FEAT_END + + if (!paging_mode_set) { + set_paging_mode(paging_mode, kaslr); + } + #if defined (__aarch64__) uint64_t aa64mmfr0; asm volatile ("mrs %0, id_aa64mmfr0_el1" : "=r" (aa64mmfr0)); uint64_t pa = aa64mmfr0 & 0xF; - uint64_t tsz = 64 - (want_5lv ? 57 : 48); + uint64_t tsz = 64 - paging_mode_va_bits(paging_mode); #endif struct limine_file *kf = ext_mem_alloc(sizeof(struct limine_file)); @@ -795,7 +867,7 @@ term_fail: #if defined (__i386__) actual_callback = (void *)limine_term_callback; limine_term_callback_ptr = terminal_request->callback; -#elif defined (__x86_64__) || defined (__aarch64__) +#elif defined (__x86_64__) || defined (__aarch64__) || defined (__riscv64) actual_callback = (void *)terminal_request->callback; #else #error Unknown architecture @@ -811,7 +883,7 @@ term_fail: limine_term_write_ptr = (uintptr_t)term_write_shim; terminal_response->write = (uintptr_t)(void *)limine_term_write_entry; -#elif defined (__x86_64__) || defined (__aarch64__) +#elif defined (__x86_64__) || defined (__aarch64__) || defined (__riscv64) terminal_response->write = (uintptr_t)term_write_shim; #else #error Unknown architecture @@ -1003,9 +1075,22 @@ FEAT_END #endif pagemap_t pagemap = {0}; - pagemap = build_pagemap(want_5lv, nx_available, ranges, ranges_count, + pagemap = build_pagemap(paging_mode, nx_available, ranges, ranges_count, physical_base, virtual_base, direct_map_offset); +#if defined (__riscv64) + // Fetch the BSP's Hart ID before exiting boot services. + size_t bsp_hartid; + bool have_bsp_hartid = false; + + RISCV_EFI_BOOT_PROTOCOL *riscv_boot_proto = get_riscv_boot_protocol(); + if (riscv_boot_proto != NULL) { + if (riscv_boot_proto->GetBootHartId(riscv_boot_proto, &bsp_hartid) == EFI_SUCCESS) { + have_bsp_hartid = true; + } + } +#endif + #if defined (UEFI) efi_exit_boot_services(); #endif @@ -1022,7 +1107,7 @@ FEAT_START #if defined (__x86_64__) || defined (__i386__) uint32_t bsp_lapic_id; smp_info = init_smp(&cpu_count, &bsp_lapic_id, - true, want_5lv, + true, paging_mode, pagemap, smp_request->flags & LIMINE_SMP_X2APIC, nx_available, direct_map_offset, true); #elif defined (__aarch64__) @@ -1030,6 +1115,13 @@ FEAT_START smp_info = init_smp(&cpu_count, &bsp_mpidr, pagemap, LIMINE_MAIR(fb_attr), LIMINE_TCR(tsz, pa), LIMINE_SCTLR); +#elif defined (__riscv64) + if (!have_bsp_hartid) { + printv("smp: failed to get bsp's hart id\n"); + break; + } + + smp_info = init_smp(&cpu_count, bsp_hartid, pagemap); #else #error Unknown architecture #endif @@ -1051,6 +1143,8 @@ FEAT_START smp_response->bsp_lapic_id = bsp_lapic_id; #elif defined (__aarch64__) smp_response->bsp_mpidr = bsp_mpidr; +#elif defined (__riscv64) + smp_response->bsp_hartid = bsp_hartid; #else #error Unknown architecture #endif @@ -1155,7 +1249,7 @@ FEAT_END uint64_t reported_stack = reported_addr(stack); common_spinup(limine_spinup_32, 8, - want_5lv, (uint32_t)(uintptr_t)pagemap.top_level, + paging_mode, (uint32_t)(uintptr_t)pagemap.top_level, (uint32_t)entry_point, (uint32_t)(entry_point >> 32), (uint32_t)reported_stack, (uint32_t)(reported_stack >> 32), (uint32_t)(uintptr_t)local_gdt, nx_available); @@ -1165,6 +1259,11 @@ FEAT_END enter_in_el1(entry_point, (uint64_t)stack, LIMINE_SCTLR, LIMINE_MAIR(fb_attr), LIMINE_TCR(tsz, pa), (uint64_t)pagemap.top_level[0], (uint64_t)pagemap.top_level[1], 0); +#elif defined (__riscv64) + uint64_t reported_stack = reported_addr(stack); + uint64_t satp = make_satp(pagemap.paging_mode, pagemap.top_level); + + riscv_spinup(entry_point, reported_stack, satp); #else #error Unknown architecture #endif diff --git a/common/sys/cpu.h b/common/sys/cpu.h index 335e5556..61ce5e5d 100644 --- a/common/sys/cpu.h +++ b/common/sys/cpu.h @@ -287,6 +287,39 @@ inline int current_el(void) { return v; } +#elif defined (__riscv64) + +inline uint64_t rdtsc(void) { + uint64_t v; + asm ("rdtime %0" : "=r"(v)); + return v; +} + +#define csr_read(csr) ({\ + size_t v;\ + asm volatile ("csrr %0, " csr : "=r"(v));\ + v;\ +}) + +#define csr_write(csr, v) ({\ + size_t old;\ + asm volatile ("csrrw %0, " csr ", %1" : "=r"(old) : "r"(v));\ + old;\ +}) + +#define make_satp(mode, ppn) (((size_t)(mode) << 60) | ((size_t)(ppn) >> 12)) + +#define locked_read(var) ({ \ + typeof(*var) locked_read__ret; \ + asm volatile ( \ + "ld %0, (%1); fence r, rw" \ + : "=r"(locked_read__ret) \ + : "r"(var) \ + : "memory" \ + ); \ + locked_read__ret; \ +}) + #else #error Unknown architecture #endif diff --git a/common/sys/sbi.asm_riscv64 b/common/sys/sbi.asm_riscv64 new file mode 100644 index 00000000..df1851a5 --- /dev/null +++ b/common/sys/sbi.asm_riscv64 @@ -0,0 +1,15 @@ + +.global sbicall +sbicall: + mv t0, a0 + mv t1, a1 + mv a0, a2 + mv a1, a3 + mv a2, a4 + mv a3, a5 + mv a4, a6 + mv a5, a7 + mv a7, t0 + mv a6, t1 + ecall + ret diff --git a/common/sys/sbi.h b/common/sys/sbi.h new file mode 100644 index 00000000..238f8550 --- /dev/null +++ b/common/sys/sbi.h @@ -0,0 +1,32 @@ +#ifndef __SYS__SBI_H__ +#define __SYS__SBI_H__ + +#include +#include + +struct sbiret { + long error; + long value; +}; + +#define SBI_SUCCESS ((long)0) +#define SBI_ERR_FAILED ((long)-1) +#define SBI_ERR_NOT_SUPPORTED ((long)-2) +#define SBI_ERR_INVALID_PARAM ((long)-3) +#define SBI_ERR_DENIED ((long)-4) +#define SBI_ERR_INVALID_ADDRESS ((long)-5) +#define SBI_ERR_ALREADY_AVAILABLE ((long)-6) +#define SBI_ERR_ALREADY_STARTED ((long)-7) +#define SBI_ERR_ALREADY_STOPPED ((long)-8) + +extern struct sbiret sbicall(int eid, int fid, ...); + +#define SBI_EID_HSM 0x48534d + +static inline struct sbiret sbi_hart_start(unsigned long hartid, + unsigned long start_addr, + unsigned long opaque) { + return sbicall(SBI_EID_HSM, 0, hartid, start_addr, opaque); +} + +#endif diff --git a/common/sys/smp.c b/common/sys/smp.c index 5d709a03..f01dd210 100644 --- a/common/sys/smp.c +++ b/common/sys/smp.c @@ -13,6 +13,9 @@ #include #define LIMINE_NO_POINTERS #include +#if defined (__riscv64) +#include +#endif struct madt { struct sdt header; @@ -64,6 +67,16 @@ struct madt_gicc { uint16_t spe_overflow_gsiv; } __attribute__((packed)); +// Reference: https://github.com/riscv-non-isa/riscv-acpi/issues/15 +struct madt_riscv_intc { + struct madt_header header; + uint8_t version; + uint8_t reserved; + uint32_t flags; + uint64_t hartid; + uint32_t acpi_processor_uid; +} __attribute__((packed)); + #if defined (__x86_64__) || defined (__i386__) struct trampoline_passed_info { @@ -77,7 +90,7 @@ struct trampoline_passed_info { static bool smp_start_ap(uint32_t lapic_id, struct gdtr *gdtr, struct limine_smp_info *info_struct, - bool longmode, bool lv5, uint32_t pagemap, + bool longmode, int paging_mode, uint32_t pagemap, bool x2apic, bool nx, uint64_t hhdm, bool wp) { // Prepare the trampoline static void *trampoline = NULL; @@ -97,7 +110,7 @@ static bool smp_start_ap(uint32_t lapic_id, struct gdtr *gdtr, passed_info->smp_tpl_booted_flag = 0; passed_info->smp_tpl_pagemap = pagemap; passed_info->smp_tpl_target_mode = ((uint32_t)x2apic << 2) - | ((uint32_t)lv5 << 1) + | ((uint32_t)paging_mode << 1) | ((uint32_t)nx << 3) | ((uint32_t)wp << 4) | ((uint32_t)longmode << 0); @@ -137,7 +150,7 @@ static bool smp_start_ap(uint32_t lapic_id, struct gdtr *gdtr, struct limine_smp_info *init_smp(size_t *cpu_count, uint32_t *_bsp_lapic_id, bool longmode, - bool lv5, + int paging_mode, pagemap_t pagemap, bool x2apic, bool nx, @@ -244,7 +257,7 @@ struct limine_smp_info *init_smp(size_t *cpu_count, // Try to start the AP if (!smp_start_ap(lapic->lapic_id, &gdtr, info_struct, - longmode, lv5, (uintptr_t)pagemap.top_level, + longmode, paging_mode, (uintptr_t)pagemap.top_level, x2apic, nx, hhdm, wp)) { print("smp: FAILED to bring-up AP\n"); continue; @@ -281,7 +294,7 @@ struct limine_smp_info *init_smp(size_t *cpu_count, // Try to start the AP if (!smp_start_ap(x2lapic->x2apic_id, &gdtr, info_struct, - longmode, lv5, (uintptr_t)pagemap.top_level, + longmode, paging_mode, (uintptr_t)pagemap.top_level, true, nx, hhdm, wp)) { print("smp: FAILED to bring-up AP\n"); continue; @@ -556,6 +569,110 @@ struct limine_smp_info *init_smp(size_t *cpu_count, return NULL; } +#elif defined (__riscv64) + +struct trampoline_passed_info { + uint64_t smp_tpl_booted_flag; + uint64_t smp_tpl_satp; + uint64_t smp_tpl_info_struct; +}; + +static bool smp_start_ap(size_t hartid, size_t satp, struct limine_smp_info *info_struct) { + static struct trampoline_passed_info passed_info; + + passed_info.smp_tpl_booted_flag = 0; + passed_info.smp_tpl_satp = satp; + passed_info.smp_tpl_info_struct = (uint64_t)info_struct; + + asm volatile ("" ::: "memory"); + + struct sbiret ret = sbi_hart_start(hartid, (size_t)smp_trampoline_start, (size_t)&passed_info); + if (ret.error != SBI_SUCCESS) + return false; + + for (int i = 0; i < 1000000; i++) { + if (locked_read(&passed_info.smp_tpl_booted_flag) == 1) + return true; + } + + return false; +} + +struct limine_smp_info *init_smp(size_t *cpu_count, + size_t bsp_hartid, + pagemap_t pagemap) { + // No RSDP means no ACPI. + // Parsing the Device Tree is the only other method for detecting APs. + if (acpi_get_rsdp() == NULL) { + printv("smp: ACPI is required to detect APs.\n"); + return NULL; + } + + struct madt *madt = acpi_get_table("APIC", 0); + if (madt == NULL) + return NULL; + + size_t max_cpus = 0; + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + switch (*madt_ptr) { + case 0x18: { + struct madt_riscv_intc *intc = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if ((intc->flags & 1) ^ ((intc->flags >> 1) & 1)) + max_cpus++; + + continue; + } + } + } + + struct limine_smp_info *ret = ext_mem_alloc(max_cpus * sizeof(struct limine_smp_info)); + *cpu_count = 0; + + // Try to start all APs + for (uint8_t *madt_ptr = (uint8_t *)madt->madt_entries_begin; + (uintptr_t)madt_ptr < (uintptr_t)madt + madt->header.length; + madt_ptr += *(madt_ptr + 1)) { + switch (*madt_ptr) { + case 0x18: { + struct madt_riscv_intc *intc = (void *)madt_ptr; + + // Check if we can actually try to start the AP + if (!((intc->flags & 1) ^ ((intc->flags >> 1) & 1))) + continue; + + struct limine_smp_info *info_struct = &ret[*cpu_count]; + + info_struct->processor_id = intc->acpi_processor_uid; + info_struct->hartid = intc->hartid; + + // Do not try to restart the BSP + if (intc->hartid == bsp_hartid) { + (*cpu_count)++; + continue; + } + + printv("smp: Found candidate AP for bring-up. Hart ID: %u\n", intc->hartid); + + // Try to start the AP. + size_t satp = make_satp(pagemap.paging_mode, pagemap.top_level); + if (!smp_start_ap(intc->hartid, satp, info_struct)) { + print("smp: FAILED to bring-up AP\n"); + continue; + } + + (*cpu_count)++; + continue; + } + } + } + + return ret; +} + #else #error Unknown architecture #endif diff --git a/common/sys/smp.h b/common/sys/smp.h index cd74518d..b05de5f1 100644 --- a/common/sys/smp.h +++ b/common/sys/smp.h @@ -13,7 +13,7 @@ struct limine_smp_info *init_smp(size_t *cpu_count, uint32_t *_bsp_lapic_id, bool longmode, - bool lv5, + int paging_mode, pagemap_t pagemap, bool x2apic, bool nx, @@ -28,6 +28,13 @@ struct limine_smp_info *init_smp(size_t *cpu_count, uint64_t mair, uint64_t tcr, uint64_t sctlr); + +#elif defined (__riscv64) + +struct limine_smp_info *init_smp(size_t *cpu_count, + uint64_t bsp_hartid, + pagemap_t pagemap); + #else #error Unknown architecture #endif diff --git a/common/sys/smp_trampoline.asm_riscv64 b/common/sys/smp_trampoline.asm_riscv64 new file mode 100644 index 00000000..5e98ab38 --- /dev/null +++ b/common/sys/smp_trampoline.asm_riscv64 @@ -0,0 +1,63 @@ + +.global smp_trampoline_start +smp_trampoline_start: + // The AP begins executing here with the following state: + // satp = 0 + // sstatus.SIE = 0 + // a0 = hartid + // a1 = struct trampoline_passed_info * + // + // All other registers are undefined. + + ld a0, 16(a1) + ld t0, 8(a1) + csrw satp, t0 + + // Tell the BSP we've started. + li t0, 1 + fence rw, w + sd t0, (a1) + + // Zero all the things. + // Preserve a0 + mv a1, zero + mv a2, zero + mv a3, zero + mv a4, zero + mv a5, zero + mv a6, zero + mv a7, zero + mv s0, zero + mv s1, zero + mv s2, zero + mv s3, zero + mv s4, zero + mv s5, zero + mv s6, zero + mv s7, zero + mv s8, zero + mv s9, zero + mv s10, zero + mv s11, zero + mv t1, zero + mv t2, zero + mv t3, zero + mv t4, zero + mv t5, zero + mv t6, zero + mv tp, zero + mv ra, zero + + csrw sie, zero + csrw stvec, zero + + // Wait for kernel to tell us where to go. +0: .insn i 0x0F, 0, x0, x0, 0x010 // pause + ld t0, 24(a0) + fence r, rw + beqz t0, 0b + + // Load sp from reserved field of info struct + ld sp, 16(a0) + + jr t0 diff --git a/configure.ac b/configure.ac index c12cc14b..2548d59c 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,34 @@ fi AC_SUBST([BUILD_UEFI_AARCH64]) +BUILD_UEFI_RISCV64="$BUILD_ALL" + +AC_ARG_ENABLE([uefi-riscv64], + [AS_HELP_STRING([--enable-uefi-riscv64], [enable building the riscv64 UEFI port])], + [BUILD_UEFI_RISCV64="$enableval"]) + +if test "x$BUILD_UEFI_RISCV64" = "xno"; then + BUILD_UEFI_RISCV64="" +else + mkdir -p "$BUILDDIR/toolchain-files" + CC="$CC" \ + ARCHITECTURE=riscv64 \ + FREESTANDING_TOOLCHAIN_SUFFIX="_FOR_TARGET" \ + FREESTANDING_TOOLCHAIN="$TOOLCHAIN_FOR_TARGET" \ + WANT_FREESTANDING_CC=yes \ + FREESTANDING_CC="$CC_FOR_TARGET" \ + WANT_FREESTANDING_LD=yes \ + FREESTANDING_LD="$LD_FOR_TARGET" \ + WANT_FREESTANDING_OBJCOPY=yes \ + FREESTANDING_OBJCOPY="$OBJCOPY_FOR_TARGET" \ + WANT_FREESTANDING_OBJDUMP=yes \ + FREESTANDING_OBJDUMP="$OBJDUMP_FOR_TARGET" \ + "$SRCDIR/freestanding-toolchain" >"$BUILDDIR/toolchain-files/uefi-riscv64-toolchain.mk" || exit 1 + BUILD_UEFI_RISCV64="limine-uefi-riscv64" +fi + +AC_SUBST([BUILD_UEFI_RISCV64]) + BUILD_CD_EFI="$BUILD_ALL" AC_ARG_ENABLE([uefi-cd], diff --git a/limine.h b/limine.h index f26d8c56..f302f2db 100644 --- a/limine.h +++ b/limine.h @@ -233,20 +233,62 @@ struct LIMINE_DEPRECATED limine_terminal_request { LIMINE_DEPRECATED_IGNORE_END +/* Paging mode */ + +#define LIMINE_PAGING_MODE_REQUEST { LIMINE_COMMON_MAGIC, 0x95c1a0edab0944cb, 0xa4e5cb3842f7488a } + +#if defined (__x86_64__) || defined (__i386__) +#define LIMINE_PAGING_MODE_X86_64_4LVL 0 +#define LIMINE_PAGING_MODE_X86_64_5LVL 1 +#define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_X86_64_5LVL +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_X86_64_4LVL +#elif defined (__aarch64__) +#define LIMINE_PAGING_MODE_AARCH64_4LVL 0 +#define LIMINE_PAGING_MODE_AARCH64_5LVL 1 +#define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_AARCH64_5LVL +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_AARCH64_4LVL +#elif defined (__riscv) && (__riscv_xlen == 64) +#define LIMINE_PAGING_MODE_RISCV_SV39 0 +#define LIMINE_PAGING_MODE_RISCV_SV48 1 +#define LIMINE_PAGING_MODE_RISCV_SV57 2 +#define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_RISCV_SV57 +#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48 +#else +#error Unknown architecture +#endif + +struct limine_paging_mode_response { + uint64_t revision; + uint64_t mode; + uint64_t flags; +}; + +struct limine_paging_mode_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_paging_mode_response *) response; + uint64_t mode; + uint64_t flags; +}; + /* 5-level paging */ #define LIMINE_5_LEVEL_PAGING_REQUEST { LIMINE_COMMON_MAGIC, 0x94469551da9b3192, 0xebe5e86db7382888 } -struct limine_5_level_paging_response { +LIMINE_DEPRECATED_IGNORE_START + +struct LIMINE_DEPRECATED limine_5_level_paging_response { uint64_t revision; }; -struct limine_5_level_paging_request { +struct LIMINE_DEPRECATED limine_5_level_paging_request { uint64_t id[4]; uint64_t revision; LIMINE_PTR(struct limine_5_level_paging_response *) response; }; +LIMINE_DEPRECATED_IGNORE_END + /* SMP */ #define LIMINE_SMP_REQUEST { LIMINE_COMMON_MAGIC, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0 } @@ -294,6 +336,24 @@ struct limine_smp_response { LIMINE_PTR(struct limine_smp_info **) cpus; }; +#elif defined (__riscv) && (__riscv_xlen == 64) + +struct limine_smp_info { + uint32_t processor_id; + uint64_t hartid; + uint64_t reserved; + LIMINE_PTR(limine_goto_address) goto_address; + uint64_t extra_argument; +}; + +struct limine_smp_response { + uint64_t revision; + uint32_t flags; + uint64_t bsp_hartid; + uint64_t cpu_count; + LIMINE_PTR(struct limine_smp_info **) cpus; +}; + #else #error Unknown architecture #endif diff --git a/test.mk b/test.mk index 5784383a..2d3c99f4 100644 --- a/test.mk +++ b/test.mk @@ -11,6 +11,10 @@ ovmf-aa64: mkdir -p ovmf-aa64 cd ovmf-aa64 && curl -o OVMF-AA64.zip https://efi.akeo.ie/OVMF/OVMF-AA64.zip && 7z x OVMF-AA64.zip +ovmf-rv64: + mkdir -p ovmf-rv64 + cd ovmf-rv64 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASERISCV64_VIRT.fd && dd if=/dev/zero of=OVMF.fd bs=1 count=0 seek=33554432 + ovmf-ia32: $(MKDIR_P) ovmf-ia32 cd ovmf-ia32 && curl -o OVMF-IA32.zip https://efi.akeo.ie/OVMF/OVMF-IA32.zip && 7z x OVMF-IA32.zip @@ -236,6 +240,30 @@ uefi-aa64-test: rm -rf test_image loopback_dev qemu-system-aarch64 -m 512M -M virt -cpu cortex-a72 -bios ovmf-aa64/OVMF.fd -net none -smp 4 -device ramfb -device qemu-xhci -device usb-kbd -hda test.hdd -serial stdio +.PHONY: uefi-rv64-test +uefi-rv64-test: + $(MAKE) ovmf-rv64 + $(MAKE) test-clean + $(MAKE) test.hdd + $(MAKE) limine-uefi-riscv64 + $(MAKE) -C test TOOLCHAIN_FILE='$(call SHESCAPE,$(BUILDDIR))/toolchain-files/uefi-riscv64-toolchain.mk' + rm -rf test_image/ + mkdir test_image + sudo losetup -Pf --show test.hdd > loopback_dev + sudo partprobe `cat loopback_dev` + sudo mkfs.fat -F 32 `cat loopback_dev`p1 + sudo mount `cat loopback_dev`p1 test_image + sudo mkdir test_image/boot + sudo cp -rv $(BINDIR)/* test_image/boot/ + sudo cp -rv test/* test_image/boot/ + sudo $(MKDIR_P) test_image/EFI/BOOT + sudo cp $(BINDIR)/BOOTRISCV64.EFI test_image/EFI/BOOT/ + sync + sudo umount test_image/ + sudo losetup -d `cat loopback_dev` + rm -rf test_image loopback_dev + qemu-system-riscv64 -m 512M -M virt -cpu rv64 -drive if=pflash,unit=1,format=raw,file=ovmf-rv64/OVMF.fd -net none -smp 4 -device ramfb -device qemu-xhci -device usb-kbd -device virtio-blk-device,drive=hd0 -drive id=hd0,format=raw,file=test.hdd -serial stdio + .PHONY: uefi-ia32-test uefi-ia32-test: $(MAKE) ovmf-ia32 diff --git a/test/limine.c b/test/limine.c index 8e8826f3..c66af7a3 100644 --- a/test/limine.c +++ b/test/limine.c @@ -151,6 +151,16 @@ struct limine_dtb_request _dtb_request = { __attribute__((section(".limine_reqs"))) void *dtb_req = &_dtb_request; +struct limine_paging_mode_request _pm_request = { + .id = LIMINE_PAGING_MODE_REQUEST, + .revision = 0, .response = NULL, + .mode = LIMINE_PAGING_MODE_DEFAULT, + .flags = 0, +}; + +__attribute__((section(".limine_reqs"))) +void *pm_req = &_pm_request; + static char *get_memmap_type(uint64_t type) { switch (type) { case LIMINE_MEMMAP_USABLE: @@ -216,6 +226,8 @@ void ap_entry(struct limine_smp_info *info) { #elif defined (__aarch64__) e9_printf("My GIC CPU Interface no.: %x", info->gic_iface_no); e9_printf("My MPIDR: %x", info->mpidr); +#elif defined (__riscv) + e9_printf("My Hart ID: %x", info->hartid); #endif __atomic_fetch_add(&ctr, 1, __ATOMIC_SEQ_CST); @@ -412,6 +424,8 @@ FEAT_START e9_printf("BSP LAPIC ID: %x", smp_response->bsp_lapic_id); #elif defined (__aarch64__) e9_printf("BSP MPIDR: %x", smp_response->bsp_mpidr); +#elif defined (__riscv) + e9_printf("BSP Hart ID: %x", smp_response->bsp_hartid); #endif e9_printf("CPU count: %d", smp_response->cpu_count); for (size_t i = 0; i < smp_response->cpu_count; i++) { @@ -422,6 +436,8 @@ FEAT_START #elif defined (__aarch64__) e9_printf("GIC CPU Interface no.: %x", cpu->gic_iface_no); e9_printf("MPIDR: %x", cpu->mpidr); +#elif defined (__riscv) + e9_printf("Hart ID: %x", cpu->hartid); #endif @@ -429,6 +445,8 @@ FEAT_START if (cpu->lapic_id != smp_response->bsp_lapic_id) { #elif defined (__aarch64__) if (cpu->mpidr != smp_response->bsp_mpidr) { +#elif defined (__riscv) + if (cpu->hartid != smp_response->bsp_hartid) { #endif uint32_t old_ctr = __atomic_load_n(&ctr, __ATOMIC_SEQ_CST); @@ -469,5 +487,17 @@ FEAT_START e9_printf("Device tree blob pointer: %x", dtb_response->dtb_ptr); FEAT_END +FEAT_START + e9_printf(""); + if (_pm_request.response == NULL) { + e9_printf("Paging mode not passed"); + break; + } + struct limine_paging_mode_response *pm_response = _pm_request.response; + e9_printf("Paging mode feature, revision %d", pm_response->revision); + e9_printf(" mode: %d", pm_response->mode); + e9_printf(" flags: %x", pm_response->flags); +FEAT_END + for (;;); }