:: commit f49dbd4ae9fb76c0ecff0c8c40b91af3ab234629

Kacper Słomiński <kacper.slominski72@gmail.com> — 2024-06-11 19:58

parents: 2a0de56ea4

protos/linux: Fill DTB with information about UEFI

Also create a new DTB if one is not available. This replicates what Linux's
EFISTUB does internally before handing control over to the kernel proper.
diff --git a/common/protos/linux_riscv64.c b/common/protos/linux_riscv64.c
index 899c2147..1a4c5f19 100644
--- a/common/protos/linux_riscv64.c
+++ b/common/protos/linux_riscv64.c
@@ -42,6 +42,137 @@ struct linux_header {
 #define LINUX_HEADER_MAJOR_VER(ver)     (((ver) >> 16) & 0xffff)
 #define LINUX_HEADER_MINOR_VER(ver)     (((ver) >> 0)  & 0xffff)
 
+void *prepare_device_tree_blob(char *config, char *cmdline) {
+    void *dtb = get_device_tree_blob();
+    int ret;
+
+    if (!dtb) {
+        // Hopefully 4K should be enough (mainly depends on the length of cmdline).
+        dtb = ext_mem_alloc_type(0x1000, MEMMAP_KERNEL_AND_MODULES);
+        if (!dtb) {
+            panic(true, "linux: failed to allocate memory for a device tree blob");
+        }
+
+        ret = fdt_create_empty_tree(dtb, 0x1000);
+        if (ret < 0) {
+            panic(true, "linux: failed to create a device tree blob: '%s'", fdt_strerror(ret));
+        }
+
+        ret = fdt_setprop_u32(dtb, 0, "#address-cells", 2);
+        if (ret < 0) {
+            panic(true, "linux: failed to set #address-cells: '%s'", fdt_strerror(ret));
+        }
+
+        ret = fdt_setprop_u32(dtb, 0, "#size-cells", 1);
+        if (ret < 0) {
+            panic(true, "linux: failed to set #size-cells: '%s'", fdt_strerror(ret));
+        }
+    }
+
+    // Delete all /memory@... nodes. Linux will use the given UEFI memory map
+    // instead.
+    while (true) {
+        int offset = fdt_subnode_offset_namelen(dtb, 0, "memory@", 7);
+
+        if (offset == -FDT_ERR_NOTFOUND) {
+            break;
+        }
+
+        if (offset < 0) {
+            panic(true, "linux: failed to find node: '%s'", fdt_strerror(offset));
+        }
+
+        ret = fdt_del_node(dtb, offset);
+        if (ret < 0) {
+            panic(true, "linux: failed to delete memory node: '%s'", fdt_strerror(ret));
+        }
+    }
+
+    // Load an initrd if requested and add it to the device tree.
+    char *module_path = config_get_value(config, 0, "MODULE_PATH");
+    if (module_path) {
+        struct file_handle *module_file = uri_open(module_path);
+        if (!module_file) {
+            panic(true, "linux: failed to open module `%s`. Is the path correct?", module_path);
+        }
+
+        size_t module_size = module_file->size;
+        void *module_base = ext_mem_alloc_type_aligned(
+                        ALIGN_UP(module_size, 4096),
+                        MEMMAP_KERNEL_AND_MODULES, 4096);
+
+        fread(module_file, module_base, 0, module_size);
+        fclose(module_file);
+        printv("linux: loaded module `%s` at %x, size %u\n", module_path, module_base, module_size);
+
+        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-start", (uint64_t)module_base);
+        if (ret < 0) {
+            panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret));
+        }
+
+        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-end", (uint64_t)(module_base + module_size));
+        if (ret < 0) {
+            panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret));
+        }
+    }
+
+    // Set the kernel command line arguments.
+    ret = fdt_set_chosen_string(dtb, "bootargs", cmdline);
+    if (ret < 0) {
+        panic(true, "linux: failed to set bootargs: '%s'", fdt_strerror(ret));
+    }
+
+    // TODO(qookie): Once I figure out how, give Linux the framebuffer through
+    // the device tree here.
+
+    efi_exit_boot_services();
+
+    // Tell Linux about the UEFI memory map and system table.
+    ret = fdt_set_chosen_uint64(dtb, "linux,uefi-system-table", (uint64_t)gST);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI system table pointer: '%s'", fdt_strerror(ret));
+    }
+
+    ret = fdt_set_chosen_uint64(dtb, "linux,uefi-mmap-start", (uint64_t)efi_mmap);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI memory map pointer: '%s'", fdt_strerror(ret));
+    }
+
+    ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-size", efi_mmap_size);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI memory map size: '%s'", fdt_strerror(ret));
+    }
+
+    ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-size", efi_desc_size);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI memory map descriptor size: '%s'", fdt_strerror(ret));
+    }
+
+    ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-ver", efi_desc_ver);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI memory map descriptor version: '%s'", fdt_strerror(ret));
+    }
+
+    // TODO(qookie): Figure out whether secure boot is actually enabled.
+    ret = fdt_set_chosen_uint32(dtb, "linux,uefi-secure-boot", 0);
+    if (ret < 0) {
+        panic(true, "linux: failed to set UEFI secure boot state: '%s'", fdt_strerror(ret));
+    }
+
+    // TODO(qookie): We should fill out VirtualStart for runtime entries and do
+    // SetVirtualMap here. Not doing this works, but Linux can't use UEFI
+    // runtime services.
+    size_t efi_mmap_entry_count = efi_mmap_size / efi_desc_size;
+    for (size_t i = 0; i < efi_mmap_entry_count; i++) {
+        EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size;
+
+        if (entry->Attribute & EFI_MEMORY_RUNTIME)
+            entry->VirtualStart = 0xFFFFFFFFFFFFFFFF;
+    }
+
+    return dtb;
+}
+
 noreturn void linux_load(char *config, char *cmdline) {
     struct file_handle *kernel_file;
 
@@ -76,41 +207,9 @@ noreturn void linux_load(char *config, char *cmdline) {
     fclose(kernel_file);
     printv("linux: loaded kernel `%s` at %x, size %u\n", kernel_path, kernel_base, kernel_size);
 
-    void *dtb = get_device_tree_blob();
+    void *dtb = prepare_device_tree_blob(config, cmdline);
     if (!dtb) {
-        panic(true, "linux: no device tree blob found");
-    }
-
-    int ret = fdt_set_chosen_string(dtb, "bootargs", cmdline);
-    if (ret < 0) {
-       printv("linux: cannot set bootargs: `%s`\n", fdt_strerror(ret));
-    }
-
-    char *module_path = config_get_value(config, 0, "MODULE_PATH");
-    if (module_path) {
-        struct file_handle *module_file = uri_open(module_path);
-        if (!module_file) {
-            panic(true, "linux: failed to open module `%s`. Is the path correct?", module_path);
-        }
-
-        size_t module_size = module_file->size;
-        void *module_base = ext_mem_alloc_type_aligned(
-                        ALIGN_UP(module_size, 4096),
-                        MEMMAP_KERNEL_AND_MODULES, 4096);
-
-        fread(module_file, module_base, 0, module_size);
-        fclose(module_file);
-        printv("linux: loaded module `%s` at %x, size %u\n", module_path, module_base, module_size);
-
-        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-start", (uint64_t)module_base);
-        if (ret < 0) {
-            printv("linux: cannot set initrd parameter: %s\n", fdt_strerror(ret));
-        }
-
-        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-end", (uint64_t)(module_base + module_size));
-        if (ret < 0) {
-            printv("linux: cannot set initrd parameter: %s\n", fdt_strerror(ret));
-        }
+        panic(true, "linux: failed to prepare the device tree blob");
     }
 
     printv("linux: bsp hart %d, device tree blob at %x\n", bsp_hartid, dtb);
tab: 248 wrap: offon