:: commit d0e93cb0451714f0b3580879754dde0b3abf3cb0

48cf <32851089+48cf@users.noreply.github.com> — 2023-11-27 15:50

parents: c60c33ee48

protos/linux: Support for native aarch64 boot

diff --git a/common/menu.c b/common/menu.c
index f9d03da0..e18b8eb1 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -1036,12 +1036,7 @@ noreturn void boot(char *config) {
     } else if (!strcmp(proto, "limine")) {
         limine_load(config, cmdline);
     } else if (!strcmp(proto, "linux")) {
-#if defined (__x86_64__) || defined (__i386__)
         linux_load(config, cmdline);
-#else
-        quiet = false;
-        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);
diff --git a/common/protos/linux.c b/common/protos/linux.c
index bd055d8b..80749d00 100644
--- a/common/protos/linux.c
+++ b/common/protos/linux.c
@@ -1,5 +1,3 @@
-#if defined (__x86_64__) || defined (__i386__)
-
 #include <stdint.h>
 #include <stddef.h>
 #include <stdnoreturn.h>
@@ -7,18 +5,21 @@
 #include <fs/file.h>
 #include <lib/libc.h>
 #include <lib/misc.h>
-#include <lib/real.h>
 #include <lib/term.h>
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/uri.h>
 #include <mm/pmm.h>
-#include <sys/idt.h>
 #include <lib/fb.h>
+#include <sys/cpu.h>
+
+#if defined (__x86_64__) || defined (__i386__)
 #include <lib/acpi.h>
+#include <lib/real.h>
 #include <drivers/edid.h>
 #include <drivers/vga_textmode.h>
 #include <drivers/gop.h>
+#include <sys/idt.h>
 
 noreturn void linux_spinup(void *entry, void *boot_params);
 
@@ -349,8 +350,38 @@ struct boot_params {
 } __attribute__((packed));
 
 // End of Linux code
+#elif defined(__aarch64__)
+#include <libfdt/libfdt.h>
+
+// Taken from https://www.kernel.org/doc/Documentation/arm64/booting.txt
+
+struct kernel_header {
+    uint32_t code0; /* Executable code */
+    uint32_t code1; /* Executable code */
+    uint64_t text_offset; /* Image load offset, little endian */
+    uint64_t image_size; /* Effective Image size, little endian */
+    uint64_t flags; /* kernel flags, little endian */
+    uint64_t res2; /* = 0, reserved */
+    uint64_t res3; /* = 0, reserved */
+    uint64_t res4; /* = 0, reserved */
+    uint32_t magic; /* = 0x644d5241, Magic number, little endian, "ARM\x64" */
+    uint32_t res5; /* reserved (used for PE COFF offset) */
+};
+
+struct linux_efi_initrd {
+    unsigned long base;
+    unsigned long size;
+};
+
+// Linux specifies that the maximum FDT size is 2M.
+#define MAX_FDT_SIZE 0x200000
+#endif
 
 noreturn void linux_load(char *config, char *cmdline) {
+#if defined(__riscv64__)
+    (void)cmdline;
+#endif
+
     struct file_handle *kernel_file;
 
     char *kernel_path = config_get_value(config, 0, "KERNEL_PATH");
@@ -362,6 +393,7 @@ noreturn void linux_load(char *config, char *cmdline) {
     if ((kernel_file = uri_open(kernel_path)) == NULL)
         panic(true, "linux: Failed to open kernel with path `%#`. Is the path correct?", kernel_path);
 
+#if defined (__x86_64__) || defined (__i386__)
     uint32_t signature;
     fread(kernel_file, &signature, 0x202, sizeof(uint32_t));
 
@@ -432,6 +464,130 @@ noreturn void linux_load(char *config, char *cmdline) {
         kernel_load_addr += 0x100000;
     }
     fread(kernel_file, (void *)kernel_load_addr, real_mode_code_size, kernel_file->size - real_mode_code_size);
+#elif defined(__aarch64__)
+    struct kernel_header header;
+    fread(kernel_file, &header, 0, sizeof(struct kernel_header));
+    if (header.magic != 0x644d5241) {
+        panic(true, "linux: Invalid kernel signature");
+    }
+
+    // Allocate physical memory for the kernel image
+    void *kernel = ext_mem_alloc(header.image_size);
+    fread(kernel_file, kernel, 0, header.image_size);
+    printv("linux: Kernel allocated at %X\n", (uintptr_t)kernel);
+
+    // Clean and invalidate caches for the memory range the kernel is loaded in
+    uintptr_t kernel_addr = (uintptr_t)kernel;
+    clean_dcache_poc(kernel_addr, kernel_addr + header.image_size);
+    inval_icache_pou(kernel_addr, kernel_addr + header.image_size);
+
+    // Get current exception level
+    uint64_t el;
+    asm volatile("mrs %0, currentel" : "=r"(el));
+
+    el >>= 2;
+    el &= 0x3;
+
+    uint64_t sctlr;
+    if (el == 1) {
+        printv("linux: Booting from EL1\n");
+        asm volatile("mrs %0, sctlr_el1" : "=r"(sctlr));
+    } else if (el == 2) {
+        printv("linux: Booting from EL2 with virtualization support\n");
+        asm volatile("mrs %0, sctlr_el2" : "=r"(sctlr));
+    } else {
+        panic(true, "linux: Linux cannot be booted from EL3 or EL0");
+    }
+
+    // Disable MMU and caches
+    sctlr &= ~((1 << 0) | (1 << 2) | (1 << 12));
+
+    if (el == 1) {
+        asm volatile("msr sctlr_el1, %0" :: "r"(sctlr));
+    } else if (el == 2) {
+        asm volatile("msr sctlr_el2, %0" :: "r"(sctlr));
+    }
+
+    // Look for the DTB in the configuration tables
+    void *fdt_addr = NULL;
+    const EFI_GUID dtb_guid = EFI_DTB_TABLE_GUID;
+
+    for (size_t i = 0; i < gST->NumberOfTableEntries; i++) {
+        EFI_CONFIGURATION_TABLE *cur_table = &gST->ConfigurationTable[i];
+        if (memcmp(&cur_table->VendorGuid, &dtb_guid, sizeof(EFI_GUID)) == 0) {
+            fdt_addr = cur_table->VendorTable;
+            break;
+        }
+    }
+
+    // Allocate memory for the FDT and copy it if it was found
+    // Otherwise, create an empty tree
+    void *fdt = ext_mem_alloc(MAX_FDT_SIZE);
+    if (fdt_addr) {
+        uint32_t fdt_size = fdt_totalsize(fdt_addr);
+        memcpy(fdt, fdt_addr, fdt_size);
+        print("linux: Copied %d bytes of FDT\n", fdt_size);
+    } else {
+        fdt_create_empty_tree(fdt, MAX_FDT_SIZE);
+    }
+
+    // Find the /chosen node, or create it if it doesn't exist
+    int node = fdt_subnode_offset(fdt, 0, "chosen");
+    if (node < 0) {
+        node = fdt_add_subnode(fdt, 0, "chosen");
+        if (node < 0) {
+            panic(true, "linux: Failed to add /chosen node to FDT");
+        }
+    }
+
+    int status;
+    if (!cmdline) {
+        goto no_cmdline;
+    }
+
+    // Set the bootargs property on the /chosen node
+    size_t cmdline_size = strlen(cmdline);
+    if (cmdline_size) {
+        status = fdt_setprop(fdt, node, "bootargs", cmdline, cmdline_size + 1);
+        if (status != 0) {
+            goto fdt_error;
+        }
+    }
+
+no_cmdline:;
+    // Setup the /chosen node properties for UEFI
+    // Right now they are just placeholder values, but they are updated later
+
+    uint32_t fdt_val32 = UINT32_MAX;
+    uint64_t fdt_val64 = UINT64_MAX;
+
+    status = fdt_setprop(fdt, node, "linux,uefi-system-table", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    status = fdt_setprop(fdt, node, "linux,uefi-mmap-start", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    status = fdt_setprop(fdt, node, "linux,uefi-mmap-size", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+#elif defined(__riscv64)
+    panic(true, "linux: RISC-V is not supported yet");
+#endif
 
     fclose(kernel_file);
 
@@ -439,10 +595,6 @@ noreturn void linux_load(char *config, char *cmdline) {
     // Modules
     ///////////////////////////////////////
 
-    uint32_t modules_mem_base = setup_header->initrd_addr_max;
-    if (modules_mem_base == 0)
-        modules_mem_base = 0x38000000;
-
     size_t size_of_all_modules = 0;
 
     for (size_t i = 0; ; i++) {
@@ -459,6 +611,15 @@ noreturn void linux_load(char *config, char *cmdline) {
         fclose(module);
     }
 
+    if (size_of_all_modules == 0) {
+        goto no_modules;
+    }
+
+#if defined(__x86_64__) || defined(__i386__)
+    uint32_t modules_mem_base = setup_header->initrd_addr_max;
+    if (modules_mem_base == 0)
+        modules_mem_base = 0x38000000;
+
     modules_mem_base -= size_of_all_modules;
     modules_mem_base = ALIGN_DOWN(modules_mem_base, 4096);
 
@@ -468,8 +629,15 @@ noreturn void linux_load(char *config, char *cmdline) {
             break;
         modules_mem_base -= 4096;
     }
+#else
+    uintptr_t modules_mem_base = (uintptr_t)ext_mem_alloc(size_of_all_modules);
+    if (modules_mem_base == 0) {
+        panic(true, "linux: Failed to allocate memory for modules");
+    }
+#endif
+
+    uintptr_t _modules_mem_base = modules_mem_base;
 
-    size_t _modules_mem_base = modules_mem_base;
     for (size_t i = 0; ; i++) {
         char *module_path = config_get_value(config, i, "MODULE_PATH");
         if (module_path == NULL)
@@ -486,10 +654,33 @@ noreturn void linux_load(char *config, char *cmdline) {
         _modules_mem_base += module->size;
     }
 
-    if (size_of_all_modules != 0) {
-        setup_header->ramdisk_image = (uint32_t)modules_mem_base;
-        setup_header->ramdisk_size  = (uint32_t)size_of_all_modules;
+#if defined(__x86_64__) || defined(__i386__)
+    setup_header->ramdisk_image = (uint32_t)modules_mem_base;
+    setup_header->ramdisk_size  = (uint32_t)size_of_all_modules;
+#elif defined(__aarch64__)
+    fdt_val64 = cpu_to_fdt64(_modules_mem_base);
+    status = fdt_setprop(fdt, node, "linux,initrd-start", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    fdt_val64 = cpu_to_fdt64(ALIGN_UP(_modules_mem_base, 4096));
+    status = fdt_setprop(fdt, node, "linux,initrd-end", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    struct linux_efi_initrd *initrd_tbl = ext_mem_alloc(sizeof(struct linux_efi_initrd));
+    initrd_tbl->base = _modules_mem_base;
+    initrd_tbl->size = size_of_all_modules;
+
+    EFI_GUID linux_initrd_guid = {0x5568e427, 0x68fc, 0x4f3d, {0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68}};
+    if (gBS->InstallConfigurationTable(&linux_initrd_guid, initrd_tbl) != EFI_SUCCESS) {
+        panic(true, "linux: Failed to install initrd table");
     }
+#endif
+
+no_modules:;
 
     ///////////////////////////////////////
     // Video
@@ -497,7 +688,9 @@ noreturn void linux_load(char *config, char *cmdline) {
 
     term_notready();
 
+#if defined(__x86_64__) || defined(__i386__)
     struct screen_info *screen_info = &boot_params->screen_info;
+#endif
 
 #if defined (BIOS)
     {
@@ -517,7 +710,7 @@ noreturn void linux_load(char *config, char *cmdline) {
 
     struct fb_info *fbs;
     size_t fbs_count;
-#if defined (UEFI)
+#if defined (UEFI) && (defined (__x86_64__) || defined (__i386__))
     gop_force_16 = true;
 #endif
     fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp);
@@ -537,6 +730,7 @@ set_textmode:;
         screen_info->orig_video_isVGA = VIDEO_TYPE_VGAC;
 #endif
     } else {
+#if defined(__x86_64__) || defined(__i386__)
         screen_info->capabilities   = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS;
         screen_info->flags          = VIDEO_FLAGS_NOCURSOR;
         screen_info->lfb_base       = (uint32_t)fbs[0].framebuffer_addr;
@@ -561,17 +755,21 @@ set_textmode:;
         screen_info->orig_video_isVGA = VIDEO_TYPE_VLFB;
 #elif defined (UEFI)
         screen_info->orig_video_isVGA = VIDEO_TYPE_EFI;
+#endif
 #endif
     }
 
 #if defined (UEFI)
 no_fb:;
 #endif
+
+#if defined(__x86_64__) || defined(__i386__)
     ///////////////////////////////////////
     // RSDP
     ///////////////////////////////////////
 
     boot_params->acpi_rsdp_addr = (uintptr_t)acpi_get_rsdp();
+#endif
 
     ///////////////////////////////////////
     // UEFI
@@ -579,10 +777,11 @@ no_fb:;
 #if defined (UEFI)
     efi_exit_boot_services();
 
-#if defined (__x86_64__)
-    memcpy(&boot_params->efi_info.efi_loader_signature, "EL64", 4);
-#elif defined (__i386__)
+#if defined (__x86_64__) || defined (__i386__)
+#if defined (__i386__)
     memcpy(&boot_params->efi_info.efi_loader_signature, "EL32", 4);
+#else
+    memcpy(&boot_params->efi_info.efi_loader_signature, "EL64", 4);
 #endif
 
     boot_params->efi_info.efi_systab    = (uint32_t)(uint64_t)(uintptr_t)gST;
@@ -592,8 +791,45 @@ no_fb:;
     boot_params->efi_info.efi_memmap_size     = efi_mmap_size;
     boot_params->efi_info.efi_memdesc_size    = efi_desc_size;
     boot_params->efi_info.efi_memdesc_version = efi_desc_ver;
+#elif defined(__aarch64__)
+    node = fdt_path_offset(fdt, "/chosen");
+    if (node < 0) {
+        panic(true, "linux: Failed to find /chosen node in FDT");
+    }
+
+    fdt_val64 = cpu_to_fdt64((uintptr_t)gST);
+    status = fdt_setprop_inplace(fdt, node, "linux,uefi-system-table", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    fdt_val64 = cpu_to_fdt64((uintptr_t)efi_mmap);
+    status = fdt_setprop_inplace(fdt, node, "linux,uefi-mmap-start", &fdt_val64, sizeof(uint64_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    fdt_val32 = cpu_to_fdt32(efi_mmap_size);
+    status = fdt_setprop_inplace(fdt, node, "linux,uefi-mmap-size", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    fdt_val32 = cpu_to_fdt32(efi_desc_size);
+    status = fdt_setprop_inplace(fdt, node, "linux,uefi-mmap-desc-size", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+
+    fdt_val32 = cpu_to_fdt32(efi_desc_ver);
+    status = fdt_setprop_inplace(fdt, node, "linux,uefi-mmap-desc-ver", &fdt_val32, sizeof(uint32_t));
+    if (status != 0) {
+        goto fdt_error;
+    }
+#endif
 #endif
 
+#if defined(__x86_64__) || defined(__i386__)
     ///////////////////////////////////////
     // e820
     ///////////////////////////////////////
@@ -613,15 +849,25 @@ no_fb:;
         j++;
         boot_params->e820_entries = j;
     }
+#endif
 
     ///////////////////////////////////////
     // Spin up
     ///////////////////////////////////////
 
+#if defined(__x86_64__) || defined(__i386__)
     irq_flush_type = IRQ_PIC_ONLY_FLUSH;
 
     common_spinup(linux_spinup, 2, (uint32_t)kernel_load_addr,
                                    (uint32_t)(uintptr_t)boot_params);
-}
+#elif defined(__aarch64__)
+    fdt_pack(fdt);
+    print("linux: FDT allocated at %X\n", (uintptr_t)fdt);
+
+    void(*kernel_entry)(void *, uint64_t, uint64_t, uint64_t) = kernel;
+    kernel_entry(fdt, 0, 0, 0);
 
+fdt_error:;
+    panic(true, "linux: Failed to update the FDT");
 #endif
+}
tab: 248 wrap: offon