:: commit d9a2fb95a9705385af7af2048fa33259dbb713d1

Keegan Saunders <77407552+ksaunders@users.noreply.github.com> — 2024-08-01 03:03

parents: 3be68d0502

Add LoongArch support

diff --git a/.gitignore b/.gitignore
index a9c925ed..d7369b8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,7 @@
 /common-uefi-x86-64
 /common-uefi-aarch64
 /common-uefi-riscv64
+/common-uefi-loongarch64
 /decompressor-build
 /stage1.stamp
 
diff --git a/CONFIG.md b/CONFIG.md
index 1ed6a49e..204c398b 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -126,6 +126,7 @@ Editor control options:
   * `MAX_PAGING_MODE`, `MIN_PAGING_MODE` - Limit the maximum and minimum paging modes to one of the following:
     - x86-64 and aarch64: `4level`, `5level`.
     - riscv64: `sv39`, `sv48`, `sv57`.
+    - loongarch64: `4level`.
   * `PAGING_MODE` - Equivalent to setting both `MAX_PAGING_MODE` and `MIN_PAGING_MODE` to the same value.
 
 * multiboot1 and multiboot2 protocols:
@@ -194,4 +195,4 @@ Macros must always be placed inside `${...}` where `...` is the arbitrary macro
 
 Limine automatically defines these macros:
 
-* `ARCH` - This built-in macro expands to the architecture of the machine. Possible values are: `x86-64`, `ia-32`, `aarch64`, `riscv64`. In the case of IA-32, BIOS or UEFI, the macro will always expand to `x86-64` if the 64-bit extensions are available, else `ia-32`.
+* `ARCH` - This built-in macro expands to the architecture of the machine. Possible values are: `x86-64`, `ia-32`, `aarch64`, `riscv64`, `loongarch64`. In the case of IA-32, BIOS or UEFI, the macro will always expand to `x86-64` if the 64-bit extensions are available, else `ia-32`.
diff --git a/GNUmakefile.in b/GNUmakefile.in
index 07971667..64988416 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -54,6 +54,7 @@ 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_UEFI_LOONGARCH64 := @BUILD_UEFI_LOONGARCH64@
 override BUILD_UEFI_CD := @BUILD_UEFI_CD@
 override BUILD_BIOS_PXE := @BUILD_BIOS_PXE@
 override BUILD_BIOS_CD := @BUILD_BIOS_CD@
@@ -124,7 +125,7 @@ all: $(call MKESCAPE,$(BINDIR))/Makefile
 	$(MAKE) all1
 
 .PHONY: all1
-all1: $(BUILD_UEFI_X86_64) $(BUILD_UEFI_IA32) $(BUILD_UEFI_AARCH64) $(BUILD_UEFI_RISCV64) $(BUILD_BIOS)
+all1: $(BUILD_UEFI_X86_64) $(BUILD_UEFI_IA32) $(BUILD_UEFI_AARCH64) $(BUILD_UEFI_RISCV64) $(BUILD_UEFI_LOONGARCH64) $(BUILD_BIOS)
 	$(MAKE) '$(call SHESCAPE,$(BINDIR))/limine'
 	$(MAKE) '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin'
 
@@ -151,7 +152,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 limine-uefi-riscv64-clean
+clean: limine-bios-clean limine-uefi-ia32-clean limine-uefi-x86-64-clean limine-uefi-aarch64-clean limine-uefi-riscv64-clean limine-uefi-loongarch64-clean
 	rm -rf '$(call SHESCAPE,$(BINDIR))' '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp'
 
 .PHONY: install
@@ -184,6 +185,9 @@ endif
 ifeq ($(BUILD_UEFI_RISCV64),limine-uefi-riscv64)
 	$(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI' '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine/'
 endif
+ifeq ($(BUILD_UEFI_LOONGARCH64),limine-uefi-loongarch64)
+	$(INSTALL_DATA) '$(call SHESCAPE,$(BINDIR))/BOOTLOONGARCH64.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
@@ -227,7 +231,7 @@ endif
 limine-bios: common-bios decompressor
 	$(MAKE) '$(call SHESCAPE,$(BUILDDIR))/stage1.stamp'
 
-$(call MKESCAPE,$(BINDIR))/limine-uefi-cd.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)
+$(call MKESCAPE,$(BINDIR))/limine-uefi-cd.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) $(if $(BUILD_UEFI_LOONGARCH64),$(call MKESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI)
 ifneq ($(BUILD_UEFI_CD),no)
 	$(MKDIR_P) '$(call SHESCAPE,$(BINDIR))'
 	rm -f '$(call SHESCAPE,$(BINDIR))/limine-uefi-cd.bin'
@@ -237,6 +241,7 @@ ifneq ($(BUILD_UEFI_CD),no)
 		mkdir -p "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT; \
 		cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-aarch64/BOOTAA64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \
 		cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64/BOOTRISCV64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \
+		cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \
 		cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-x86-64/BOOTX64.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \
 		cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32/BOOTIA32.EFI' "$$LIMINE_UEFI_CD_TMP"/EFI/BOOT/ 2>/dev/null; \
 		find "$$LIMINE_UEFI_CD_TMP" -exec touch -t $(SOURCE_DATE_EPOCH_TOUCH) '{}' + && \
@@ -284,6 +289,15 @@ limine-uefi-riscv64:
 	$(MAKE) common-uefi-riscv64
 	$(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTRISCV64.EFI'
 
+$(call MKESCAPE,$(BINDIR))/BOOTLOONGARCH64.EFI: $(call MKESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI
+	$(MKDIR_P) '$(call SHESCAPE,$(BINDIR))'
+	cp '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64/BOOTLOONGARCH64.EFI' '$(call SHESCAPE,$(BINDIR))/'
+
+.PHONY: limine-uefi-loongarch64
+limine-uefi-loongarch64:
+	$(MAKE) common-uefi-loongarch64
+	$(MAKE) '$(call SHESCAPE,$(BINDIR))/BOOTLOONGARCH64.EFI'
+
 .PHONY: limine-bios-clean
 limine-bios-clean: common-bios-clean decompressor-clean
 
@@ -299,6 +313,9 @@ limine-uefi-aarch64-clean: common-uefi-aarch64-clean
 .PHONY: limine-uefi-riscv64-clean
 limine-uefi-riscv64-clean: common-uefi-riscv64-clean
 
+.PHONY: limine-uefi-loongarch64-clean
+limine-uefi-loongarch64-clean: common-uefi-loongarch64-clean
+
 .PHONY: dist
 dist:
 	rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"limine-$(LIMINE_VERSION)"
@@ -377,6 +394,17 @@ common-uefi-riscv64:
 common-uefi-riscv64-clean:
 	rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-riscv64'
 
+.PHONY: common-uefi-loongarch64
+common-uefi-loongarch64:
+	$(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' all \
+		TOOLCHAIN_FILE='$(call SHESCAPE,$(BUILDDIR))/toolchain-files/uefi-loongarch64-toolchain.mk' \
+		TARGET=uefi-loongarch64 \
+		BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64'
+
+.PHONY: common-uefi-loongarch64-clean
+common-uefi-loongarch64-clean:
+	rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-loongarch64'
+
 .PHONY: common-uefi-ia32
 common-uefi-ia32:
 	$(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' all \
diff --git a/PROTOCOL.md b/PROTOCOL.md
index c7f5b238..f7bc43ca 100644
--- a/PROTOCOL.md
+++ b/PROTOCOL.md
@@ -247,6 +247,15 @@ with the default `PBMT=PMA`.
 If the `Svpbmt` extension is not available, no PMAs can be overridden (effectively,
 everything is mapped with `PBMT=PMA`).
 
+### loongarch64
+
+The kernel executable, loaded at or above `0xffffffff80000000`, sees all of its
+segments mapped using the Coherent Cached (CC) memory access type (MAT).
+
+All HHDM and identity map memory regions are mapped using the Coherent Cached (CC)
+MAT, except for the framebuffer regions, which are mapped in using the
+Weakly-ordered UnCached (WUC) MAT.
+
 ## Machine state at entry
 
 ### x86-64
@@ -368,6 +377,30 @@ Paging is enabled with the paging mode specified by the Paging Mode feature (see
 
 The (A)PLIC, if present, is in an undefined state.
 
+### loongarch64
+
+At entry the machine is executing in PLV0.
+
+`$pc` will be the entry point as defined as part of the executable file format,
+unless the Entry Point feature is requested (see below), in which case, the
+value of `$pc` is going to be taken from there.
+
+`$r1`(`$ra`) is set to 0, the kernel must not return from the entry point.
+
+`$r3`(`$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).
+
+All other general purpose registers, with the exception of `$r12`(`$t0`), are set to 0.
+
+If booted by EFI/UEFI, boot services are exited.
+
+`CSR.EENTRY`, `CSR.MERRENTRY` and `CSR.DWM0-3` are in an undefined state.
+
+`PG` in `CSR.CRMD` is 1, `DA` is 0, `IE` is 0 and `PLV` is 0 but is otherwise unspecified.
+
+`CSR.TLBRENTRY` is filled with a provided TLB refill handler.
+
 ## Feature List
 
 Request IDs are composed of 4 64-bit unsigned integers, but the first 2 are
@@ -664,6 +697,17 @@ Values for `mode`, `max_mode`, and `min_mode`:
 #define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_RISCV_SV57
 ```
 
+### loongarch64
+
+Values for `mode`, `max_mode`, and `min_mode`:
+```c
+#define LIMINE_PAGING_MODE_LOONGARCH64_4LVL 0
+
+#define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_LOONGARCH64_4LVL
+#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_LOONGARCH64_4LVL
+#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_LOONGARCH64_4LVL
+```
+
 ### SMP (multiprocessor) Feature
 
 ID:
diff --git a/README.md b/README.md
index acf15eb2..b6a626f1 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ Donations welcome, but absolutely not mandatory!
 * x86-64
 * aarch64 (arm64)
 * riscv64
+* loongarch64
 
 ### Supported boot protocols
 * Linux
@@ -53,7 +54,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, aarch64, and riscv64 (UEFI) systems are supported.
+All x86-64, aarch64, riscv64 and loongarch64 (UEFI) systems are supported.
 
 ## Packaging status
 
diff --git a/bootstrap b/bootstrap
index a309776e..bbe976f3 100755
--- a/bootstrap
+++ b/bootstrap
@@ -76,15 +76,15 @@ if ! test -f version; then
     done
 
     download_by_hash \
-        https://github.com/osdev0/freestanding-toolchain/raw/18a5e52483344e117d45738c9afb2b34792cbced/freestanding-toolchain \
+        https://github.com/osdev0/freestanding-toolchain/raw/a8b871c2a952f7efe89bd38658f8356bc00d4d29/freestanding-toolchain \
         build-aux/freestanding-toolchain \
-        b5b66c4e94d463116e549b10e78fb96cdb97530cc165f9b5babe31a97a78e90c
+        6e167855d2ea68ebaf1e3d7450f3297bec2ab3f425e56ed11ed7c72396f6b540
     chmod +x build-aux/freestanding-toolchain
 
     clone_repo_commit \
         https://github.com/limine-bootloader/limine-efi.git \
         limine-efi \
-        d8257094947b0edefe9fa4dcb15255235e3c5193
+        f31f942b6f1390777102af28ad0aabde7695a466
 
     clone_repo_commit \
         https://github.com/jibsen/tinf.git \
diff --git a/common/GNUmakefile b/common/GNUmakefile
index ac5ee3d8..3074e16d 100644
--- a/common/GNUmakefile
+++ b/common/GNUmakefile
@@ -157,6 +157,21 @@ ifeq ($(TARGET),uefi-riscv64)
         -D__riscv64
 endif
 
+ifeq ($(TARGET),uefi-loongarch64)
+    override CFLAGS_FOR_TARGET += \
+        -fPIE \
+        -fshort-wchar \
+        -march=loongarch64 \
+        -mabi=lp64s
+
+    override CPPFLAGS_FOR_TARGET := \
+        -I'$(call SHESCAPE,$(BUILDDIR))/limine-efi/inc' \
+        -I'$(call SHESCAPE,$(BUILDDIR))/limine-efi/inc/loongarch64' \
+        $(CPPFLAGS_FOR_TARGET) \
+        -DUEFI \
+        -D__loongarch64
+endif
+
 override LDFLAGS_FOR_TARGET += \
     -nostdlib \
     -z max-page-size=0x1000 \
@@ -202,6 +217,14 @@ ifeq ($(TARGET),uefi-riscv64)
         -z text
 endif
 
+ifeq ($(TARGET),uefi-loongarch64)
+    override LDFLAGS_FOR_TARGET += \
+        -m elf64loongarch \
+        --no-relax \
+        -pie \
+        -z text
+endif
+
 override C_FILES := $(shell find . -type f -name '*.c' | LC_ALL=C sort)
 ifeq ($(TARGET),bios)
     override ASMX86_FILES := $(shell find . -type f -name '*.asm_x86' | LC_ALL=C sort)
@@ -237,6 +260,12 @@ ifeq ($(TARGET),uefi-riscv64)
 
     override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(ASM64_FILES:.asm_riscv64=.o) $(ASM64U_FILES:.asm_uefi_riscv64=.o))
 endif
+ifeq ($(TARGET),uefi-loongarch64)
+    override ASM64_FILES := $(shell find . -type f -name '*.asm_loongarch64' | LC_ALL=C sort)
+    override ASM64U_FILES := $(shell find . -type f -name '*.asm_uefi_loongarch64' | LC_ALL=C sort)
+
+    override OBJ := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.o) $(ASM64_FILES:.asm_loongarch64=.o) $(ASM64U_FILES:.asm_uefi_loongarch64=.o))
+endif
 
 override HEADER_DEPS := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=.d))
 
@@ -252,6 +281,8 @@ else ifeq ($(TARGET),uefi-aarch64)
 all: $(call MKESCAPE,$(BUILDDIR))/BOOTAA64.EFI
 else ifeq ($(TARGET),uefi-riscv64)
 all: $(call MKESCAPE,$(BUILDDIR))/BOOTRISCV64.EFI
+else ifeq ($(TARGET),uefi-loongarch64)
+all: $(call MKESCAPE,$(BUILDDIR))/BOOTLOONGARCH64.EFI
 endif
 
 ifeq ($(TARGET),bios)
@@ -490,6 +521,52 @@ $(call MKESCAPE,$(BUILDDIR))/limine.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi
 		'$(call OBJESCAPE,$^)' $(LDFLAGS_FOR_TARGET) -o '$(call SHESCAPE,$@)'
 endif
 
+ifeq ($(TARGET),uefi-loongarch64)
+
+$(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))/BOOTLOONGARCH64.EFI: $(call MKESCAPE,$(BUILDDIR))/limine.elf
+	$(OBJCOPY_FOR_TARGET) -O binary '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)'
+	chmod -x '$(call SHESCAPE,$@)'
+	dd if=/dev/zero of='$(call SHESCAPE,$@)' bs=4096 count=0 seek=$$(( ($$(wc -c < '$(call SHESCAPE,$@)') + 4095) / 4096 ))
+
+$(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-loongarch64.S.o: limine-efi
+
+$(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_loongarch64.c.o: limine-efi
+
+.PHONY: limine-efi
+limine-efi: $(call MKESCAPE,$(BUILDDIR))/limine-efi
+	$(MAKE) -C '$(call SHESCAPE,$(BUILDDIR))/limine-efi/gnuefi' \
+		CC="$(CC_FOR_TARGET)" \
+		CFLAGS="$(BASE_CFLAGS)" \
+		CPPFLAGS='-nostdinc -isystem $(call SHESCAPE,$(SRCDIR))/../freestanding-headers' \
+		ARCH=loongarch64
+
+$(call MKESCAPE,$(BUILDDIR))/linker_nomap.ld: linker_uefi_loongarch64.ld.in
+	$(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))'
+	$(CC_FOR_TARGET) -x c -E -P -undef -DLINKER_NOMAP linker_uefi_loongarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker_nomap.ld'
+
+$(call MKESCAPE,$(BUILDDIR))/limine_nomap.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-loongarch64.S.o $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_loongarch64.c.o $(OBJ)
+	$(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_loongarch64.ld.in
+	$(MKDIR_P) '$(call SHESCAPE,$(BUILDDIR))'
+	$(CC_FOR_TARGET) -x c -E -P -undef linker_uefi_loongarch64.ld.in -o '$(call SHESCAPE,$(BUILDDIR))/linker.ld'
+
+$(call MKESCAPE,$(BUILDDIR))/limine.elf: $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/crt0-efi-loongarch64.S.o $(call MKESCAPE,$(BUILDDIR))/limine-efi/gnuefi/reloc_loongarch64.c.o $(OBJ) $(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
@@ -557,6 +634,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-loongarch64)
+$(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,$@)')"
@@ -627,6 +710,16 @@ $(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_uefi_riscv64
 	$(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) $(CPPFLAGS_FOR_TARGET) -x assembler-with-cpp -c '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)'
 endif
 
+ifeq ($(TARGET),uefi-loongarch64)
+$(call MKESCAPE,$(BUILDDIR))/%.o: %.asm_loongarch64
+	$(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_loongarch64
+	$(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_loongarch64 b/common/efi_thunk.asm_uefi_loongarch64
new file mode 100644
index 00000000..3184c674
--- /dev/null
+++ b/common/efi_thunk.asm_uefi_loongarch64
@@ -0,0 +1,10 @@
+.section .text
+
+.global efi_main
+.extern uefi_entry
+efi_main:
+    move $fp, $r0
+    move $ra, $r0
+    b uefi_entry
+
+.section .note.GNU-stack,"",%progbits
diff --git a/common/lib/config.c b/common/lib/config.c
index 968191c7..6c292d36 100644
--- a/common/lib/config.c
+++ b/common/lib/config.c
@@ -208,6 +208,8 @@ skip_loop:
     strcpy(arch_macro->value, "aarch64");
 #elif defined (__riscv64)
     strcpy(arch_macro->value, "riscv64");
+#elif defined (__loongarch64)
+    strcpy(arch_macro->value, "loongarch64");
 #else
 #error "Unspecified architecture"
 #endif
diff --git a/common/lib/elf.c b/common/lib/elf.c
index 0ef2405a..3720d9d7 100644
--- a/common/lib/elf.c
+++ b/common/lib/elf.c
@@ -42,6 +42,7 @@
 #define ARCH_X86_32  0x03
 #define ARCH_AARCH64 0xb7
 #define ARCH_RISCV   0xf3
+#define ARCH_LOONGARCH 0x102
 #define BITS_LE      0x01
 #define ELFCLASS64   0x02
 #define SHT_RELA     0x00000004
@@ -50,16 +51,20 @@
 #define R_X86_64_NONE      0x00000000
 #define R_AARCH64_NONE     0x00000000
 #define R_RISCV_NONE       0x00000000
+#define R_LARCH_NONE       0x00000000
 #define R_X86_64_RELATIVE  0x00000008
 #define R_AARCH64_RELATIVE 0x00000403
 #define R_RISCV_RELATIVE   0x00000003
+#define R_LARCH_RELATIVE   0x00000003
 #define R_X86_64_GLOB_DAT  0x00000006
 #define R_AARCH64_GLOB_DAT 0x00000401
 #define R_X86_64_JUMP_SLOT 0x00000007
 #define R_AARCH64_JUMP_SLOT 0x00000402
 #define R_RISCV_JUMP_SLOT  0x00000005
+#define R_LARCH_JUMP_SLOT  0x00000005
 #define R_X86_64_64        0x00000001
 #define R_RISCV_64         0x00000002
+#define R_LARCH_64         0x00000002
 #define R_AARCH64_ABS64    0x00000101
 
 #define R_INTERNAL_RELR    0xfffffff0
@@ -163,6 +168,10 @@ static bool elf64_validate(struct elf64_hdr *hdr) {
     if (hdr->machine != ARCH_RISCV && hdr->ident[EI_CLASS] == ELFCLASS64) {
         panic(true, "elf: Not a riscv64 ELF file.");
     }
+#elif defined (__loongarch64)
+    if (hdr->machine != ARCH_LOONGARCH && hdr->ident[EI_CLASS] == ELFCLASS64) {
+        panic(true, "elf: Not a loongarch64 ELF file.");
+    }
 #else
 #error Unknown architecture
 #endif
@@ -183,6 +192,7 @@ int elf_bits(uint8_t *elf) {
         case ARCH_AARCH64:
             return 64;
         case ARCH_RISCV:
+        case ARCH_LOONGARCH:
             return (hdr->ident[EI_CLASS] == ELFCLASS64) ? 64 : 32;
         case ARCH_X86_32:
             return 32;
@@ -465,6 +475,8 @@ end_of_pt_segment:
             case R_AARCH64_NONE:
 #elif defined (__riscv64)
             case R_RISCV_NONE:
+#elif defined (__loongarch64)
+            case R_LARCH_NONE:
 #endif
             {
                 break;
@@ -475,6 +487,8 @@ end_of_pt_segment:
             case R_AARCH64_RELATIVE:
 #elif defined (__riscv64)
             case R_RISCV_RELATIVE:
+#elif defined (__loongarch64)
+            case R_LARCH_RELATIVE:
 #endif
             {
                 *ptr = slide + relocation->r_addend;
@@ -493,6 +507,8 @@ end_of_pt_segment:
             case R_AARCH64_JUMP_SLOT:
 #elif defined (__riscv64)
             case R_RISCV_JUMP_SLOT:
+#elif defined (__loongarch64)
+            case R_LARCH_JUMP_SLOT:
 #endif
             {
                 struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
@@ -516,6 +532,8 @@ end_of_pt_segment:
             case R_AARCH64_ABS64:
 #elif defined (__riscv64)
             case R_RISCV_64:
+#elif defined (__loongarch64)
+            case R_LARCH_64:
 #endif
             {
                 struct elf64_sym *s = (void *)elf + symtab_offset + symtab_ent * relocation->r_symbol;
diff --git a/common/lib/misc.c b/common/lib/misc.c
index ff47a4a2..8bbbec74 100644
--- a/common/lib/misc.c
+++ b/common/lib/misc.c
@@ -253,6 +253,8 @@ retry:
     asm volatile ("msr daifset, #15" ::: "memory");
 #elif defined (__riscv64)
     asm volatile ("csrci sstatus, 0x2" ::: "memory");
+#elif defined (__loongarch64)
+    asm volatile ("csrxchg $r0, %0, 0x0" :: "r" (0x4) : "memory");
 #else
 #error Unknown architecture
 #endif
diff --git a/common/lib/misc.h b/common/lib/misc.h
index 612423c0..ba5e72d2 100644
--- a/common/lib/misc.h
+++ b/common/lib/misc.h
@@ -95,6 +95,9 @@ noreturn void riscv_spinup(uint64_t entry, uint64_t sp, uint64_t satp, uint64_t
 #if defined (UEFI)
 RISCV_EFI_BOOT_PROTOCOL *get_riscv_boot_protocol(void);
 #endif
+#elif defined (__loongarch64)
+noreturn void loongarch_spinup(uint64_t entry, uint64_t sp, uint64_t pgdl,
+                               uint64_t pgdh, uint64_t direct_map_offset);
 #else
 #error Unknown architecture
 #endif
diff --git a/common/lib/panic.s2.c b/common/lib/panic.s2.c
index 7c57315d..dcd76979 100644
--- a/common/lib/panic.s2.c
+++ b/common/lib/panic.s2.c
@@ -78,6 +78,8 @@ noreturn void panic(bool allow_menu, const char *fmt, ...) {
             asm ("hlt");
 #elif defined (__aarch64__) || defined (__riscv64)
             asm ("wfi");
+#elif defined (__loongarch64)
+            asm ("idle 0");
 #else
 #error Unknown architecture
 #endif
diff --git a/common/lib/spinup.asm_loongarch64 b/common/lib/spinup.asm_loongarch64
new file mode 100644
index 00000000..8f7752b8
--- /dev/null
+++ b/common/lib/spinup.asm_loongarch64
@@ -0,0 +1,110 @@
+.section .text
+
+#define PAGE_SHIFT      12
+#define PT_SHIFT        (PAGE_SHIFT - 3)
+#define PT_BASE(level)  (PAGE_SHIFT + PT_SHIFT * (level))
+
+#define MAKE_PWCL(Dir2_width, Dir2_base, Dirl_width, Dirl_base, PTwidth, PTbase) \
+        ((Dir2_width) << 25) | ((Dir2_base) << 20) | ((Dirl_width) << 15) | \
+        ((Dirl_base) << 10) | ((PTwidth) << 5) | ((PTbase) << 0)
+
+#define MAKE_PWCH(Dir4_width, Dir4_base, Dir3_width, Dir3_base) \
+        ((Dir4_width) << 18) | ((Dir4_base) << 12) | ((Dir3_width) << 6) | \
+        ((Dir3_base) << 0)
+
+#define CSR_CRMD        0x00
+#define CSR_EENTRY      0xc
+#define CSR_PGDL        0x19
+#define CSR_PGDH        0x1a
+#define CSR_PGD         0x1b
+#define CSR_PWCL        0x1c
+#define CSR_PWCH        0x1d
+#define CSR_STLBPS      0x1e
+#define CSR_TLBRENTRY   0x88
+#define CSR_TLBRSAVE    0x8b
+#define CSR_TLBREHI     0x8e
+#define CSR_MERRENTRY   0x93
+#define CSR_DMW0        0x180
+#define CSR_DMW1        0x181
+#define CSR_DMW2        0x182
+#define CSR_DMW3        0x183
+
+.global loongarch_spinup
+loongarch_spinup:
+        li.d    $t0, 0b010001           // MAT=01, PLV1..3=0, PLV0=1
+        csrwr   $t0, CSR_DMW0
+        csrwr   $zero, CSR_DMW1
+        csrwr   $zero, CSR_DMW2
+        csrwr   $zero, CSR_DMW3
+
+        li.d    $t0, 0b010110000        // DATF=01, DATM=01, PG=1, DA=0, IE=0, PLV=00
+        csrwr   $t0, CSR_CRMD
+
+        invtlb  0, $zero, $zero
+        li.d    $t0, PAGE_SHIFT
+        csrwr   $t0, CSR_STLBPS
+        csrwr   $t0, CSR_TLBREHI
+
+        csrwr   $a2, CSR_PGDL
+        csrwr   $a3, CSR_PGDH
+
+        li.d    $t0, MAKE_PWCL(PT_SHIFT, PT_BASE(2), PT_SHIFT, PT_BASE(1), PT_SHIFT, PT_BASE(0))
+        csrwr   $t0, CSR_PWCL
+        li.d    $t0, MAKE_PWCH(0, 0, PT_SHIFT, PT_BASE(3))
+        csrwr   $t0, CSR_PWCH
+
+        la      $t0, loongarch_handle_refill
+        csrwr   $t0, CSR_TLBRENTRY
+
+        csrwr   $zero, CSR_EENTRY
+        csrwr   $zero, CSR_MERRENTRY
+
+        move    $t0, $a0
+        move    $sp, $a1
+
+        move    $ra, $zero
+        move    $tp, $zero
+        move    $a0, $zero
+        move    $a1, $zero
+        move    $a2, $zero
+        move    $a3, $zero
+        move    $a4, $zero
+        move    $a5, $zero
+        move    $a6, $zero
+        move    $a7, $zero
+        move    $t1, $zero
+        move    $t2, $zero
+        move    $t3, $zero
+        move    $t4, $zero
+        move    $t5, $zero
+        move    $t6, $zero
+        move    $t7, $zero
+        move    $t8, $zero
+        move    $fp, $zero
+        move    $s0, $zero
+        move    $s1, $zero
+        move    $s2, $zero
+        move    $s3, $zero
+        move    $s4, $zero
+        move    $s5, $zero
+        move    $s6, $zero
+        move    $s7, $zero
+        move    $s8, $zero
+
+        jirl    $zero, $t0, 0
+
+.global loongarch_handle_refill
+.align 4
+loongarch_handle_refill:
+        csrwr   $t0, CSR_TLBRSAVE
+        csrrd   $t0, CSR_PGD
+        lddir   $t0, $t0, 3
+        lddir   $t0, $t0, 2
+        lddir   $t0, $t0, 1
+        ldpte   $t0, 0
+        ldpte   $t0, 1
+        tlbfill
+        csrrd   $t0, CSR_TLBRSAVE
+        ertn
+
+.section .note.GNU-stack,"",%progbits
diff --git a/common/lib/trace.s2.c b/common/lib/trace.s2.c
index 2ce6e18e..56885e9a 100644
--- a/common/lib/trace.s2.c
+++ b/common/lib/trace.s2.c
@@ -56,6 +56,8 @@ void print_stacktrace(size_t *base_ptr) {
             "mov %0, x29"
 #elif defined (__riscv64)
             "mv %0, fp;  addi %0, %0, -16"
+#elif defined (__loongarch64)
+            "move %0, $fp;  addi.d %0, %0, -16"
 #endif
             : "=r"(base_ptr)
             :: "memory"
@@ -75,7 +77,7 @@ void print_stacktrace(size_t *base_ptr) {
             print("  [%p]\n", ret_addr);
         if (!old_bp)
             break;
-#if defined (__riscv)
+#if defined (__riscv) || defined (__loongarch64)
         base_ptr = (void *)(old_bp - 16);
 #else
         base_ptr = (void*)old_bp;
diff --git a/common/linker_uefi_loongarch64.ld.in b/common/linker_uefi_loongarch64.ld.in
new file mode 100644
index 00000000..52987702
--- /dev/null
+++ b/common/linker_uefi_loongarch64.ld.in
@@ -0,0 +1,77 @@
+OUTPUT_FORMAT("elf64-loongarch", "elf64-loongarch", "elf64-loongarch")
+OUTPUT_ARCH(loongarch)
+ENTRY(_start)
+
+PHDRS
+{
+    text    PT_LOAD    FLAGS(0x05);
+    rodata  PT_LOAD    FLAGS(0x04);
+    data    PT_LOAD    FLAGS(0x06);
+    dynamic PT_DYNAMIC FLAGS(0x06);
+}
+
+SECTIONS
+{
+    . = 0;
+    __slide = .;
+    __image_base = ABSOLUTE(.);
+    __image_size = ABSOLUTE(__image_end - __image_base);
+
+    .text : {
+        KEEP(*(.pe_header))
+
+        . = ALIGN(0x1000);
+
+        __text_start = ABSOLUTE(.);
+        *(.text .text.*)
+    } :text
+
+    . = ALIGN(0x1000);
+    __text_end = ABSOLUTE(.);
+    __text_size = ABSOLUTE(__text_end - __text_start);
+
+    .rodata : {
+        __reloc_start = ABSOLUTE(.);
+        *(.dummy_reloc)
+
+        . = ALIGN(0x1000);
+        __reloc_end = ABSOLUTE(.);
+        __reloc_size = ABSOLUTE(__reloc_end - __reloc_start);
+
+        __data_start = ABSOLUTE(.);
+        *(.rodata .rodata.*)
+
+#ifdef LINKER_NOMAP
+   full_map = .;
+#else
+   *(.full_map)
+#endif
+    } :rodata
+
+    .data : {
+   data_begin = .;
+        *(.data .data.*)
+        *(.sdata .sdata.*)
+        *(.sbss .sbss.*)
+        *(.bss .bss.*)
+        *(COMMON)
+   data_end = .;
+
+   *(.no_unwind)
+    } :data
+
+    .dynamic : {
+        *(.dynamic)
+    } :data :dynamic
+
+    __data_end = ABSOLUTE(ALIGN(0x1000));
+    __data_size = ABSOLUTE(__data_end - __data_start);
+
+    __image_end = ABSOLUTE(ALIGN(0x1000));
+
+    /DISCARD/ : {
+        *(.eh_frame*)
+        *(.note .note.*)
+        *(.interp)
+    }
+}
diff --git a/common/menu.c b/common/menu.c
index 79af29a3..be2a0256 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -794,6 +794,8 @@ noreturn void _menu(bool first_run) {
             "riscv64"
 #elif defined (__aarch64__)
             "aarch64"
+#elif defined (__loongarch64)
+            "loongarch64"
 #endif
             ", UEFI)";
 #endif
@@ -1132,7 +1134,12 @@ noreturn void boot(char *config) {
     if (!strcmp(proto, "limine")) {
         limine_load(config, cmdline);
     } else if (!strcmp(proto, "linux")) {
+#if defined (__loongarch64)
+        quiet = false;
+        print("TODO: Linux is not available on LoongArch64.\n\n");
+#else
         linux_load(config, cmdline);
+#endif
     } else if (!strcmp(proto, "multiboot1") || !strcmp(proto, "multiboot")) {
 #if defined (__x86_64__) || defined (__i386__)
         multiboot1_load(config, cmdline);
diff --git a/common/menu_thunk.asm_loongarch64 b/common/menu_thunk.asm_loongarch64
new file mode 100644
index 00000000..831c5663
--- /dev/null
+++ b/common/menu_thunk.asm_loongarch64
@@ -0,0 +1,25 @@
+.section .data
+
+.align 3
+stack_at_first_entry:
+    .quad 0
+
+.section .text
+
+.global menu
+.extern _menu
+
+menu:
+    la $t0, stack_at_first_entry
+    ld.d $t1, $t0, 0
+    beqz $t1, 1f
+    move $sp, $t1
+    b 2f
+1:
+    st.d $sp, $t0, 0
+2:
+    move $fp, $r0
+    move $ra, $r0
+    b _menu
+
+.section .note.GNU-stack,"",%progbits
diff --git a/common/mm/vmm.c b/common/mm/vmm.c
index 2029a7e8..9ce4e8f6 100644
--- a/common/mm/vmm.c
+++ b/common/mm/vmm.c
@@ -389,6 +389,98 @@ void map_page(pagemap_t pagemap, uint64_t virt_addr, uint64_t phys_addr, uint64_
     }
 }
 
+#elif defined (__loongarch64)
+
+#define INVALID_PAGE    0
+
+#define PT_FLAG_VALID   ((uint64_t)1 << 0)
+#define PT_FLAG_DIRTY   ((uint64_t)1 << 1)
+#define PT_FLAG_MAT_CC  ((uint64_t)1 << 4)
+#define PT_FLAG_MAT_WUC ((uint64_t)1 << 5)
+#define PT_FLAG_GLOBAL  ((uint64_t)1 << 6)
+#define PT_FLAG_HUGE    ((uint64_t)1 << 6)
+#define PT_FLAG_WRITE   ((uint64_t)1 << 8)
+#define PT_FLAG_HGLOBAL ((uint64_t)1 << 12)
+#define PT_FLAG_NX      ((uint64_t)1 << 62)
+#define PT_PADDR_MASK   ((uint64_t)0x0000FFFFFFFFF000)
+#define PT_PADDR_HMASK  ((uint64_t)0x0000FFFFFF000000)
+
+#define PT_TABLE_FLAGS      0
+#define PT_IS_TABLE(x)      ((level_idx > 0) && (((x) & PT_FLAG_VALID) == 0) && ((x) != INVALID_PAGE))
+#define PT_IS_LARGE(x)      (((x) & (PT_FLAG_HGLOBAL | PT_FLAG_HUGE)) == (PT_FLAG_HGLOBAL | PT_FLAG_HUGE))
+#define PT_TO_VMM_FLAGS(x)  (pt_to_vmm_flags_internal(x))
+
+#define pte_new(addr, flags)    (pt_entry_t)((addr) | (flags))
+
+static inline uint64_t pte_addr(uint64_t pte) {
+    if (PT_IS_LARGE(pte)) {
+        return pte & PT_PADDR_HMASK;
+    }
+    return pte & PT_PADDR_MASK;
+}
+
+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_NX)
+        flags |= VMM_FLAG_NOEXEC;
+    if (entry & PT_FLAG_MAT_WUC)
+        flags |= VMM_FLAG_FB;
+
+    return flags;
+}
+
+pagemap_t new_pagemap(int paging_mode) {
+    (void)paging_mode;
+    pagemap_t pagemap;
+    pagemap.pgd[0] = ext_mem_alloc(PT_SIZE);
+    pagemap.pgd[1] = 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 pg_size) {
+    size_t pml4_entry = (virt_addr & ((uint64_t)0x1ff << 39)) >> 39;
+    size_t pml3_entry = (virt_addr & ((uint64_t)0x1ff << 30)) >> 30;
+    size_t pml2_entry = (virt_addr & ((uint64_t)0x1ff << 21)) >> 21;
+    size_t pml1_entry = (virt_addr & ((uint64_t)0x1ff << 12)) >> 12;
+
+    pt_entry_t *pml4, *pml3, *pml2, *pml1;
+
+    bool is_higher_half = virt_addr & ((uint64_t)1 << 63);
+
+    uint64_t real_flags = PT_FLAG_VALID | PT_FLAG_GLOBAL;
+    if (flags & VMM_FLAG_WRITE)
+        real_flags |= PT_FLAG_DIRTY | PT_FLAG_WRITE;
+    if (flags & VMM_FLAG_NOEXEC)
+        real_flags |= PT_FLAG_NX;
+    if (flags & VMM_FLAG_FB)
+        real_flags |= PT_FLAG_MAT_WUC;
+    else
+        real_flags |= PT_FLAG_MAT_CC;
+
+    pml4 = pagemap.pgd[is_higher_half];
+
+    pml3 = get_next_level(pagemap, pml4, virt_addr, pg_size, 3, pml4_entry);
+
+    if (pg_size == Size1GiB) {
+        pml3[pml3_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE);
+        return;
+    }
+
+    pml2 = get_next_level(pagemap, pml3, virt_addr, pg_size, 2, pml3_entry);
+
+    if (pg_size == Size2MiB) {
+        pml2[pml2_entry] = pte_new(phys_addr, real_flags | PT_FLAG_HGLOBAL | PT_FLAG_HUGE);
+        return;
+    }
+
+    pml1 = get_next_level(pagemap, pml2, virt_addr, pg_size, 1, pml2_entry);
+
+    pml1[pml1_entry] = pte_new(phys_addr, real_flags);
+}
+
 #else
 #error Unknown architecture
 #endif
diff --git a/common/mm/vmm.h b/common/mm/vmm.h
index f3ef4d38..144dc24c 100644
--- a/common/mm/vmm.h
+++ b/common/mm/vmm.h
@@ -121,6 +121,41 @@ 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);
 
+#elif defined (__loongarch64)
+
+#define paging_mode_va_bits(mode) 48
+
+static inline uint64_t paging_mode_higher_half(int paging_mode) {
+    (void)paging_mode;
+    return 0xffff000000000000;
+}
+
+// We use fake flags here because these don't properly map onto the
+// LoongArch 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 3
+
+#define PAGING_MODE_LOONGARCH64_4LVL 0
+
+#define PAGING_MODE_MIN PAGING_MODE_LOONGARCH64_4LVL
+#define PAGING_MODE_MAX PAGING_MODE_LOONGARCH64_4LVL
+
+enum page_size {
+    Size4KiB,
+    Size2MiB,
+    Size1GiB
+};
+
+typedef struct {
+    void *pgd[2];
+} pagemap_t;
+
+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
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 8dc92ba3..e5130d6e 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -288,7 +288,9 @@ extern symbol limine_spinup_32;
                             | ((uint64_t)1 << 8)             /* TTBR0 Inner WB RW-Allocate */ \
                             | ((uint64_t)(tsz) << 0))        /* Address bits in TTBR0 */
 
-#elif !defined (__riscv64)
+#elif defined (__riscv64)
+#elif defined (__loongarch64)
+#else
 #error Unknown architecture
 #endif
 
@@ -612,6 +614,12 @@ noreturn void limine_load(char *config, char *cmdline) {
     if (hhdm_span_top >= ((uint64_t)1 << paging_mode_va_bits(min_supported_paging_mode)) - 2) {
         goto hhdm_fail;
     }
+#elif defined (__loongarch64)
+    max_supported_paging_mode = PAGING_MODE_LOONGARCH64_4LVL;
+    min_supported_paging_mode = PAGING_MODE_LOONGARCH64_4LVL;
+    if (hhdm_span_top >= ((uint64_t)1 << paging_mode_va_bits(min_supported_paging_mode)) - 2) {
+        goto hhdm_fail;
+    }
 #else
 #error Unknown architecture
 #endif
@@ -652,6 +660,10 @@ hhdm_fail:
         } else if (strcasecmp(user_max_paging_mode_s, "sv57") == 0) {
             user_max_paging_mode = PAGING_MODE_RISCV_SV57;
         }
+#elif defined (__loongarch64)
+        if (strcasecmp(user_max_paging_mode_s, "4level") == 0) {
+            user_max_paging_mode = PAGING_MODE_LOONGARCH64_4LVL;
+        }
 #endif
         else {
             panic(true, "limine: Invalid MAX_PAGING_MODE: `%s`", user_max_paging_mode_s);
@@ -687,6 +699,10 @@ hhdm_fail:
         } else if (strcasecmp(user_min_paging_mode_s, "sv57") == 0) {
             user_min_paging_mode = PAGING_MODE_RISCV_SV57;
         }
+#elif defined (__loongarch64)
+        if (strcasecmp(user_min_paging_mode_s, "4level") == 0) {
+            user_min_paging_mode = PAGING_MODE_LOONGARCH64_4LVL;
+        }
 #endif
         else {
             panic(true, "limine: Invalid MIN_PAGING_MODE: `%s`", user_min_paging_mode_s);
@@ -716,6 +732,8 @@ hhdm_fail:
     paging_mode = max_supported_paging_mode >= PAGING_MODE_RISCV_SV48 ? PAGING_MODE_RISCV_SV48 : PAGING_MODE_RISCV_SV39;
 #elif defined (__aarch64__)
     paging_mode = PAGING_MODE_AARCH64_4LVL;
+#elif defined (__loongarch64)
+    paging_mode = PAGING_MODE_LOONGARCH64_4LVL;
 #endif
 
 #if defined (__riscv64)
@@ -1327,6 +1345,9 @@ FEAT_START
                         direct_map_offset);
 #elif defined (__riscv64)
     smp_info = init_smp(&cpu_count, pagemap, direct_map_offset);
+#elif defined (__loongarch64)
+    cpu_count = 0;
+    smp_info = NULL; // TODO: LoongArch SMP
 #else
 #error Unknown architecture
 #endif
@@ -1348,6 +1369,7 @@ FEAT_START
         if (smp_info[i].hartid == bsp_hartid) {
             continue;
         }
+#elif defined (__loongarch64)
 #else
 #error Unknown architecture
 #endif
@@ -1366,6 +1388,7 @@ FEAT_START
     smp_response->bsp_mpidr = bsp_mpidr;
 #elif defined (__riscv64)
     smp_response->bsp_hartid = bsp_hartid;
+#elif defined (__loongarch64)
 #else
 #error Unknown architecture
 #endif
@@ -1498,6 +1521,11 @@ FEAT_END
     uint64_t satp = make_satp(pagemap.paging_mode, pagemap.top_level);
 
     riscv_spinup(entry_point, reported_stack, satp, direct_map_offset);
+#elif defined (__loongarch64)
+    uint64_t reported_stack = reported_addr(stack);
+
+    loongarch_spinup(entry_point, reported_stack, (uint64_t)pagemap.pgd[0], (uint64_t)pagemap.pgd[1],
+                     direct_map_offset);
 #else
 #error Unknown architecture
 #endif
diff --git a/common/sys/cpu.h b/common/sys/cpu.h
index fae2ae4a..72a63666 100644
--- a/common/sys/cpu.h
+++ b/common/sys/cpu.h
@@ -344,6 +344,16 @@ static inline bool riscv_check_isa_extension(const char *ext, size_t *maj, size_
 
 void init_riscv(void);
 
+#elif defined (__loongarch64)
+
+#define LOONGARCH_CSR_TVAL 0x42
+
+static inline uint64_t rdtsc(void) {
+    uint64_t v;
+    asm volatile ("csrrd %0, %1" : "=r" (v) : "i" (LOONGARCH_CSR_TVAL));
+    return v;
+}
+
 #else
 #error Unknown architecture
 #endif
diff --git a/common/sys/smp.c b/common/sys/smp.c
index a8767174..70578fbe 100644
--- a/common/sys/smp.c
+++ b/common/sys/smp.c
@@ -799,6 +799,7 @@ struct limine_smp_info *init_smp(size_t *cpu_count, pagemap_t pagemap, uint64_t
     return ret;
 }
 
+#elif defined (__loongarch64)
 #else
 #error Unknown architecture
 #endif
diff --git a/common/sys/smp.h b/common/sys/smp.h
index 29960977..b0269f8a 100644
--- a/common/sys/smp.h
+++ b/common/sys/smp.h
@@ -35,6 +35,7 @@ struct limine_smp_info *init_smp(size_t   *cpu_count,
                                  pagemap_t pagemap,
                                  uint64_t  hhdm_offset);
 
+#elif defined (__loongarch64)
 #else
 #error Unknown architecture
 #endif
diff --git a/configure.ac b/configure.ac
index 300d2009..52c0db14 100644
--- a/configure.ac
+++ b/configure.ac
@@ -296,6 +296,34 @@ fi
 
 AC_SUBST([BUILD_UEFI_RISCV64])
 
+BUILD_UEFI_LOONGARCH64="$BUILD_ALL"
+
+AC_ARG_ENABLE([uefi-loongarch64],
+    [AS_HELP_STRING([--enable-uefi-loongarch64], [enable building the loongarch64 UEFI port])],
+    [BUILD_UEFI_LOONGARCH64="$enableval"])
+
+if test "x$BUILD_UEFI_LOONGARCH64" = "xno"; then
+    BUILD_UEFI_LOONGARCH64=""
+else
+    mkdir -p toolchain-files
+    CC="$CC" \
+        ARCHITECTURE=loongarch64 \
+        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/build-aux/freestanding-toolchain" 2>"toolchain-files/uefi-loongarch64-toolchain.mk" || exit 1
+    BUILD_UEFI_LOONGARCH64="limine-uefi-loongarch64"
+fi
+
+AC_SUBST([BUILD_UEFI_LOONGARCH64])
+
 BUILD_UEFI_CD="$BUILD_ALL"
 
 AC_ARG_ENABLE([uefi-cd],
diff --git a/host/Makefile b/host/Makefile
index 5544143c..26e60279 100644
--- a/host/Makefile
+++ b/host/Makefile
@@ -21,6 +21,7 @@ install: all
 	$(INSTALL) -m 644 BOOTIA32.EFI '$(DESTDIR)$(PREFIX)/share/limine/'
 	$(INSTALL) -m 644 BOOTAA64.EFI '$(DESTDIR)$(PREFIX)/share/limine/'
 	$(INSTALL) -m 644 BOOTRISCV64.EFI '$(DESTDIR)$(PREFIX)/share/limine/'
+	$(INSTALL) -m 644 BOOTLOONGARCH64.EFI '$(DESTDIR)$(PREFIX)/share/limine/'
 	$(INSTALL) -d '$(DESTDIR)$(PREFIX)/include'
 	$(INSTALL) -m 644 limine.h '$(DESTDIR)$(PREFIX)/include/'
 	$(INSTALL) -d '$(DESTDIR)$(PREFIX)/bin'
diff --git a/limine.h b/limine.h
index 63e8422f..099b6d1e 100644
--- a/limine.h
+++ b/limine.h
@@ -288,6 +288,11 @@ LIMINE_DEPRECATED_IGNORE_END
 #define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_RISCV_SV57
 #define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_RISCV_SV39
 #define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_RISCV_SV48
+#elif defined (__loongarch__) && (__loongarch_grlen == 64)
+#define LIMINE_PAGING_MODE_LOONGARCH64_4LVL 0
+#define LIMINE_PAGING_MODE_MAX LIMINE_PAGING_MODE_LOONGARCH64_4LVL
+#define LIMINE_PAGING_MODE_MIN LIMINE_PAGING_MODE_LOONGARCH64_4LVL
+#define LIMINE_PAGING_MODE_DEFAULT LIMINE_PAGING_MODE_LOONGARCH64_4LVL
 #else
 #error Unknown architecture
 #endif
@@ -389,6 +394,17 @@ struct limine_smp_response {
     LIMINE_PTR(struct limine_smp_info **) cpus;
 };
 
+#elif defined (__loongarch__) && (__loongarch_grlen == 64)
+
+struct limine_smp_info {
+    uint64_t reserved;
+};
+
+struct limine_smp_response {
+    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 993e6d4f..334a4fa8 100644
--- a/test.mk
+++ b/test.mk
@@ -19,6 +19,10 @@ ovmf-ia32:
 	$(MKDIR_P) ovmf-ia32
 	cd ovmf-ia32 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEIa32_OVMF.fd
 
+ovmf-loongarch64:
+	$(MKDIR_P) ovmf-loongarch64
+	cd ovmf-loongarch64 && curl -o OVMF.fd https://raw.githubusercontent.com/limine-bootloader/firmware/trunk/loongarch64/QEMU_EFI.fd
+
 .PHONY: test.hdd
 test.hdd:
 	rm -f test.hdd
@@ -264,6 +268,30 @@ uefi-rv64-test:
 	rm -rf test_image loopback_dev
 	qemu-system-riscv64 -m 512M -M virt -cpu rv64 -drive if=pflash,unit=0,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-loongarch64-test
+uefi-loongarch64-test:
+	$(MAKE) ovmf-loongarch64
+	$(MAKE) test-clean
+	$(MAKE) test.hdd
+	$(MAKE) limine-uefi-loongarch64
+	$(MAKE) -C test TOOLCHAIN_FILE='$(call SHESCAPE,$(BUILDDIR))/toolchain-files/uefi-loongarch64-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)/BOOTLOONGARCH64.EFI test_image/EFI/BOOT/
+	sync
+	sudo umount test_image/
+	sudo losetup -d `cat loopback_dev`
+	rm -rf test_image loopback_dev
+	qemu-system-loongarch64 -m 1G -net none -M virt -cpu la464 -device ramfb -device qemu-xhci -device usb-kbd -bios ovmf-loongarch64/OVMF.fd -hda test.hdd -serial stdio
+
 .PHONY: uefi-ia32-test
 uefi-ia32-test:
 	$(MAKE) ovmf-ia32
diff --git a/test/GNUmakefile b/test/GNUmakefile
index 555a676e..a0904780 100644
--- a/test/GNUmakefile
+++ b/test/GNUmakefile
@@ -24,6 +24,10 @@ ifneq ($(findstring riscv64,$(shell $(CC_FOR_TARGET) -dumpmachine)),)
 override LDFLAGS += \
     -m elf64lriscv
 endif
+ifneq ($(findstring loongarch64,$(shell $(CC_FOR_TARGET) -dumpmachine)),)
+override LDFLAGS += \
+    -m elf64loongarch
+endif
 
 override LDFLAGS += \
     -Tlinker.ld \
@@ -80,6 +84,14 @@ override LDFLAGS += \
     --no-relax
 endif
 
+ifneq ($(findstring loongarch64,$(shell $(CC_FOR_TARGET) -dumpmachine)),)
+override CFLAGS += \
+    -march=loongarch64 \
+    -mabi=lp64s
+override LDFLAGS += \
+    --no-relax
+endif
+
 override CFLAGS_MB := \
     -std=c11 \
     -nostdinc \
diff --git a/test/limine.c b/test/limine.c
index 38e6e71e..fb4facce 100644
--- a/test/limine.c
+++ b/test/limine.c
@@ -124,11 +124,13 @@ static volatile struct limine_kernel_address_request kernel_address_request = {
     .revision = 0, .response = NULL
 };
 
+#ifndef __loongarch__
 __attribute__((section(".limine_requests")))
 static volatile struct limine_smp_request _smp_request = {
     .id = LIMINE_SMP_REQUEST,
     .revision = 0, .response = NULL
 };
+#endif
 
 __attribute__((section(".limine_requests")))
 static volatile struct limine_dtb_request _dtb_request = {
@@ -231,6 +233,8 @@ void ap_entry(struct limine_smp_info *info) {
     e9_printf("My MPIDR: %x", info->mpidr);
 #elif defined (__riscv)
     e9_printf("My Hart ID: %x", info->hartid);
+#elif defined (__loongarch__)
+    (void)info;
 #endif
 
     __atomic_fetch_add(&ctr, 1, __ATOMIC_SEQ_CST);
@@ -455,6 +459,8 @@ FEAT_START
     e9_printf("Boot time: %d", boot_time_response->boot_time);
 FEAT_END
 
+// TODO: LoongArch SMP
+#ifndef __loongarch__
 FEAT_START
     e9_printf("");
     if (_smp_request.response == NULL) {
@@ -500,6 +506,7 @@ FEAT_START
         }
     }
 FEAT_END
+#endif
 
 FEAT_START
     e9_printf("");
tab: 248 wrap: offon