:: commit 9274ee656e3bbd147a2ee44d4ff8a2f37359f2c9

xvanc <xvancm@gmail.com> — 2023-06-03 23:36

parents: e91196d452

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 (;;);
 }
tab: 248 wrap: offon