:: commit 240ecc29e933108debbc0fd6abfe34e78757b055

Yao Zi <ziyao@disroot.org> — 2024-12-20 13:46

parents: 3f57bb5a2c

protos/linux_risc: add loongarch64 support

LoongArch kernels have more address space requirements than aarch64/
riscv64. All params (commandline, initrd, etc.) should be passed
as EFI configuration tables.

This has been tested on qemu with ACPI on. DTB support isn't tested and
isn't really matter, either. Most LoongArch devices come with an
ACPI-compatible firmware.

Signed-off-by: Yao Zi <ziyao@disroot.org>
diff --git a/common/menu.c b/common/menu.c
index cce5a91d..18405626 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -1190,12 +1190,7 @@ 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/protos/linux_risc.c b/common/protos/linux_risc.c
index 694b955a..f7cd738d 100644
--- a/common/protos/linux_risc.c
+++ b/common/protos/linux_risc.c
@@ -1,4 +1,4 @@
-#if defined(__riscv) || defined(__aarch64__)
+#if defined(__riscv) || defined(__aarch64__) || defined(__loongarch__)
 
 #include <stdint.h>
 #include <stddef.h>
@@ -22,6 +22,7 @@
 // kernel headers released under GPL-2.0 WITH Linux-syscall-note
 // allowing their inclusion in non GPL compliant code.
 
+#if defined(__riscv) || defined(__aarch64__)
 struct linux_header {
     uint32_t code0;
     uint32_t code1;
@@ -35,6 +36,22 @@ struct linux_header {
     uint32_t magic2;
     uint32_t res4;
 } __attribute__((packed));
+#elif defined(__loongarch__)
+struct linux_header {
+    uint32_t mz;
+    uint32_t res0;
+    uint64_t kernel_entry;
+    uint64_t image_size;
+    uint64_t load_offset;
+    uint64_t res1;
+    uint64_t res2;
+    uint64_t res3;
+    uint32_t magic2;       // LINUX_PE_MAGIC
+    uint32_t pe_offset;
+} __attribute__((packed));
+#else
+#error "Unknown architecture"
+#endif
 
 struct linux_efi_memreserve {
     int size;
@@ -74,6 +91,8 @@ struct boot_param {
 #define LINUX_HEADER_MINOR_VER(ver)     (((ver) >> 0)  & 0xffff)
 #elif defined(__aarch64__)
 #define LINUX_HEADER_MAGIC2             0x644d5241
+#elif defined(__loongarch__)
+#define LINUX_HEADER_MAGIC2             0x818223cd
 #endif
 
 const char *verify_kernel(struct boot_param *p) {
@@ -334,7 +353,13 @@ void prepare_mmap(struct boot_param *p) {
         EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size;
 
         if (entry->Attribute & EFI_MEMORY_RUNTIME) {
-            entry->VirtualStart = entry->PhysicalStart;
+	    // LoongArch kernel requires the virtual address stays in the
+	    // privileged, direct-mapped window
+	    #if defined(__loongarch__)
+       	        entry->VirtualStart = entry->PhysicalStart | (0x8ULL << 60);
+            #else
+	        entry->VirtualStart = entry->PhysicalStart;
+	    #endif
         }
     }
 
@@ -360,6 +385,34 @@ noreturn void jump_to_kernel(struct boot_param *p) {
     void (*kernel_entry)(uint64_t dtb, uint64_t res0, uint64_t res1, uint64_t res2) = p->kernel_base;
     asm ("msr daifset, 0xF");
     kernel_entry((uint64_t)p->dtb, 0, 0, 0);
+#elif defined(__loongarch__)
+// LoongArch kernel used to store virtual address in header.kernel_entry
+// clearing the high 16bits ensures compatibility
+#define TO_PHYS(addr) ((addr) & ((1ULL << 48) - 1))
+#define CSR_DMW_PLV0  1ULL
+#define CSR_DMW0_VSEG 0x8000ULL
+#define CSR_DMW0_BASE (CSR_DMW0_VSEG << 48)
+#define CSR_DMW0_INIT (CSR_DMW0_BASE | CSR_DMW_PLV0)
+#define CSR_DMW1_MAT  (1 << 4)
+#define CSR_DMW1_VSEG 0x9000ULL
+#define CSR_DMW1_BASE (CSR_DMW1_VSEG << 48)
+#define CSR_DMW1_INIT (CSR_DMW1_BASE | CSR_DMW1_MAT | CSR_DMW_PLV0)
+#define CSR_DMW2_VSEG 0xa000ULL
+#define CSR_DMW2_MAT  (2 << 4)
+#define CSR_DMW2_BASE (CSR_DMW2_VSEG << 48)
+#define CSR_DMW2_INIT (CSR_DMW2_BASE | CSR_DMW2_MAT | CSR_DMW_PLV0)
+#define CSR_DMW3_INIT 0
+
+    struct linux_header *header = p->kernel_base;
+    void (*kernel_entry)(uint64_t efi_boot, uint64_t cmdline, uint64_t st);
+    kernel_entry = p->kernel_base + (TO_PHYS(header->kernel_entry) - header->load_offset);
+
+    asm volatile ("csrxchg $r0, %0, 0x0" :: "r" (0x4) : "memory");
+    asm volatile ("csrwr   %0,  0x180"   :: "r" (CSR_DMW0_INIT) : "memory");
+    asm volatile ("csrwr   %0,  0x181"   :: "r" (CSR_DMW1_INIT) : "memory");
+    asm volatile ("csrwr   %0,  0x182"   :: "r" (CSR_DMW2_INIT) : "memory");
+    asm volatile ("csrwr   %0,  0x183"   :: "r" (CSR_DMW3_INIT) : "memory");
+    kernel_entry(1, (uint64_t)p->cmdline, (uint64_t)gST);
 #endif
     __builtin_unreachable();
 }
tab: 248 wrap: offon