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
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 <lib/libc.h>
#if defined (UEFI)
# include <efi.h>
+# if defined (__riscv64)
+# include <protocol/riscv/efiboot.h>
+# 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;
-
- default:
- panic(false, "Unexpected level in get_next_level");
- }
- switch (desired_sz) {
- case Size1GiB:
- new_page_size = 0x40000000;
- 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");
- 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 <stddef.h>
+#include <stdint.h>
+
+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 <mm/pmm.h>
#define LIMINE_NO_POINTERS
#include <limine.h>
+#if defined (__riscv64)
+#include <sys/sbi.h>
+#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 (;;);
}
