From f24e866c023ecfccb10d3e0d30cbc71f1f2bfbd9 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Thu, 19 Dec 2024 16:39:41 +0000 Subject: [PATCH 1/3] protos/linux_risc: clean up Split architecture-dependent code and dtb-specific operations into different functions to keep condition compilation in the leaf and prepare for support of ACPi-only systems. Signed-off-by: Yao Zi --- common/protos/linux_risc.c | 288 +++++++++++++++++++++---------------- 1 file changed, 162 insertions(+), 126 deletions(-) diff --git a/common/protos/linux_risc.c b/common/protos/linux_risc.c index 25fb8ceb..6d69863c 100644 --- a/common/protos/linux_risc.c +++ b/common/protos/linux_risc.c @@ -37,13 +37,22 @@ struct linux_header { } __attribute__((packed)); struct linux_efi_memreserve { - int size; + int size; int count; uint64_t next; }; // End of Linux code +struct boot_param { + void *kernel_base; + size_t kernel_size; + void *module_base; + size_t module_size; + char *cmdline; + void *dtb; +}; + #if defined(__riscv) #define LINUX_HEADER_MAGIC2 0x05435352 #define LINUX_HEADER_MAJOR_VER(ver) (((ver) >> 16) & 0xffff) @@ -52,37 +61,28 @@ struct linux_efi_memreserve { #define LINUX_HEADER_MAGIC2 0x644d5241 #endif -void add_framebuffer(struct fb_info *fb) { - struct screen_info *screen_info = ext_mem_alloc(sizeof(struct screen_info)); +const char *verify_kernel(struct boot_param *p) { + struct linux_header *header = p->kernel_base; - screen_info->capabilities = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS; - screen_info->flags = VIDEO_FLAGS_NOCURSOR; - screen_info->lfb_base = (uint32_t)fb->framebuffer_addr; - screen_info->ext_lfb_base = (uint32_t)(fb->framebuffer_addr >> 32); - screen_info->lfb_size = fb->framebuffer_pitch * fb->framebuffer_height; - screen_info->lfb_width = fb->framebuffer_width; - screen_info->lfb_height = fb->framebuffer_height; - screen_info->lfb_depth = fb->framebuffer_bpp; - screen_info->lfb_linelength = fb->framebuffer_pitch; - screen_info->red_size = fb->red_mask_size; - screen_info->red_pos = fb->red_mask_shift; - screen_info->green_size = fb->green_mask_size; - screen_info->green_pos = fb->green_mask_shift; - screen_info->blue_size = fb->blue_mask_size; - screen_info->blue_pos = fb->blue_mask_shift; - - screen_info->orig_video_isVGA = VIDEO_TYPE_EFI; - - EFI_GUID screen_info_table_guid = {0xe03fc20a, 0x85dc, 0x406e, {0xb9, 0x0e, 0x4a, 0xb5, 0x02, 0x37, 0x1d, 0x95}}; - EFI_STATUS ret = gBS->InstallConfigurationTable(&screen_info_table_guid, screen_info); + if (header->magic2 != LINUX_HEADER_MAGIC2) { + return "kernel header magic does not match"; + } - if (ret != EFI_SUCCESS) { - panic(true, "linux: failed to install screen info configuration table: '%x'", ret); + // riscv-specific version requirements +#if defined(__riscv) + printv("linux: boot protocol version %d.%d\n", + LINUX_HEADER_MAJOR_VER(header->version), + LINUX_HEADER_MINOR_VER(header->version)); + if (LINUX_HEADER_MAJOR_VER(header->version) == 0 + && LINUX_HEADER_MINOR_VER(header->version) < 2) { + return "linux: protocols < 0.2 are not supported"; } +#endif + + return NULL; } -void *prepare_device_tree_blob(char *config, char *cmdline) { - void *dtb = NULL; +void load_files(struct boot_param *p, char *config) { char *dtb_path = config_get_value(config, 0, "DTB_PATH"); if (dtb_path) { @@ -90,13 +90,32 @@ void *prepare_device_tree_blob(char *config, char *cmdline) { if ((dtb_file = uri_open(dtb_path)) == NULL) panic(true, "linux: Failed to open device tree blob with path `%#`. Is the path correct?", dtb_path); - dtb = freadall(dtb_file, MEMMAP_BOOTLOADER_RECLAIMABLE); + p->dtb = freadall(dtb_file, MEMMAP_BOOTLOADER_RECLAIMABLE); fclose(dtb_file); - } else { - // Hopefully 4K should be enough (mainly depends on the length of cmdline). - dtb = get_device_tree_blob(0x1000); } + char *module_path = config_get_value(config, 0, "MODULE_PATH"); + if (module_path) { + print("linux: Loading module `%#`...\n", 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); + } + + p->module_size = module_file->size; + p->module_base = ext_mem_alloc_type_aligned( + ALIGN_UP(p->module_size, 4096), + MEMMAP_KERNEL_AND_MODULES, 4096); + + fread(module_file, p->module_base, 0, p->module_size); + fclose(module_file); + printv("linux: loaded module `%s` at %x, size %u\n", module_path, p->module_base, p->module_size); + } +} + +void prepare_device_tree_blob(struct boot_param *p) { + void *dtb = p->dtb; int ret; // Delete all /memory@... nodes. Linux will use the given UEFI memory map @@ -118,59 +137,93 @@ void *prepare_device_tree_blob(char *config, char *cmdline) { } } - // 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) { - print("linux: Loading module `%#`...\n", 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 (p->module_base) { + ret = fdt_set_chosen_uint64(dtb, "linux,initrd-start", (uint64_t)p->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)); + ret = fdt_set_chosen_uint64(dtb, "linux,initrd-end", (uint64_t)(p->module_base + p->module_size)); if (ret < 0) { panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret)); } } - size_t req_width = 0, req_height = 0, req_bpp = 0; + // Set the kernel command line arguments. + ret = fdt_set_chosen_string(dtb, "bootargs", p->cmdline); + if (ret < 0) { + panic(true, "linux: failed to set bootargs: '%s'", fdt_strerror(ret)); + } - char *resolution = config_get_value(config, 0, "RESOLUTION"); - if (resolution != NULL) { - parse_resolution(&req_width, &req_height, &req_bpp, resolution); + // 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)); } - struct fb_info *fbs; - size_t fbs_count; + // This property is not required by mainline Linux, but is required by + // Debian (and derivative) kernels, because Debian has a patch that adds + // this flag, and the existing logic that deals with it will just outright + // fail if any of the properties is missing. We don't care about Debian's + // hardening or whatever, so just always report that secure boot is off. + 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)); + } +} - term_notready(); +void add_framebuffer(struct fb_info *fb) { + struct screen_info *screen_info = ext_mem_alloc(sizeof(struct screen_info)); - fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + screen_info->capabilities = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS; + screen_info->flags = VIDEO_FLAGS_NOCURSOR; + screen_info->lfb_base = (uint32_t)fb->framebuffer_addr; + screen_info->ext_lfb_base = (uint32_t)(fb->framebuffer_addr >> 32); + screen_info->lfb_size = fb->framebuffer_pitch * fb->framebuffer_height; + screen_info->lfb_width = fb->framebuffer_width; + screen_info->lfb_height = fb->framebuffer_height; + screen_info->lfb_depth = fb->framebuffer_bpp; + screen_info->lfb_linelength = fb->framebuffer_pitch; + screen_info->red_size = fb->red_mask_size; + screen_info->red_pos = fb->red_mask_shift; + screen_info->green_size = fb->green_mask_size; + screen_info->green_pos = fb->green_mask_shift; + screen_info->blue_size = fb->blue_mask_size; + screen_info->blue_pos = fb->blue_mask_shift; + + screen_info->orig_video_isVGA = VIDEO_TYPE_EFI; - // TODO(qookie): Let the user pick a framebuffer if there's > 1 - if (fbs_count > 0) { - add_framebuffer(&fbs[0]); + EFI_GUID screen_info_table_guid = {0xe03fc20a, 0x85dc, 0x406e, {0xb9, 0x0e, 0x4a, 0xb5, 0x02, 0x37, 0x1d, 0x95}}; + EFI_STATUS ret = gBS->InstallConfigurationTable(&screen_info_table_guid, screen_info); + + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install screen info configuration table: '%x'", 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)); +void prepare_efi_tables(struct boot_param *p, char *config) { + (void)p; + int ret = 0; + + { + size_t req_width = 0, req_height = 0, req_bpp = 0; + + char *resolution = config_get_value(config, 0, "RESOLUTION"); + if (resolution != NULL) { + parse_resolution(&req_width, &req_height, &req_bpp, resolution); + } + + struct fb_info *fbs; + size_t fbs_count; + + term_notready(); + + fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp); + + // TODO(qookie): Let the user pick a framebuffer if there's > 1 + if (fbs_count > 0) { + add_framebuffer(&fbs[0]); + } } { @@ -187,16 +240,12 @@ void *prepare_device_tree_blob(char *config, char *cmdline) { panic(true, "linux: failed to install memory reservation configuration table: '%x'", ret); } } - 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); +void prepare_mmap(struct boot_param *p) { + void *dtb = p->dtb; + int 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)); } @@ -216,16 +265,6 @@ void *prepare_device_tree_blob(char *config, char *cmdline) { panic(true, "linux: failed to set UEFI memory map descriptor version: '%s'", fdt_strerror(ret)); } - // This property is not required by mainline Linux, but is required by - // Debian (and derivative) kernels, because Debian has a patch that adds - // this flag, and the existing logic that deals with it will just outright - // fail if any of the properties is missing. We don't care about Debian's - // hardening or whatever, so just always report that secure boot is off. - 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)); - } - 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; @@ -238,11 +277,33 @@ void *prepare_device_tree_blob(char *config, char *cmdline) { if (status != EFI_SUCCESS) { panic(false, "linux: failed to set UEFI virtual address map: '%x'", status); } +} + +noreturn void jump_to_kernel(struct boot_param *p) { +#if defined(__riscv) + printv("linux: bsp hart %d, device tree blob at %x\n", bsp_hartid, p->dtb); + + void (*kernel_entry)(uint64_t hartid, uint64_t dtb) = p->kernel_base; + asm ("csrci sstatus, 0x2\n\t" + "csrw sie, zero\n\t"); + kernel_entry(bsp_hartid, (uint64_t)p->dtb); +#elif defined(__aarch64__) + printv("linux: device tree blob at %x\n", p->dtb); - return dtb; + 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); +#endif + __builtin_unreachable(); } noreturn void linux_load(char *config, char *cmdline) { + struct boot_param p; + memset(&p, 0, sizeof(p)); + p.cmdline = cmdline; + // Hopefully 4K should be enough (mainly depends on the length of cmdline) + p.dtb = get_device_tree_blob(0x1000); + struct file_handle *kernel_file; char *kernel_path = config_get_value(config, 0, "KERNEL_PATH"); @@ -256,52 +317,27 @@ noreturn void linux_load(char *config, char *cmdline) { panic(true, "linux: failed to open kernel `%s`. Is the path correct?", kernel_path); } - struct linux_header header; - fread(kernel_file, &header, 0, sizeof(header)); - - if (header.magic2 != LINUX_HEADER_MAGIC2) { - panic(true, "linux: kernel header magic does not match"); - } - - // Version fields are RV-specific -#if defined(__riscv) - printv("linux: boot protocol version %d.%d\n", - LINUX_HEADER_MAJOR_VER(header.version), - LINUX_HEADER_MINOR_VER(header.version)); - if (LINUX_HEADER_MAJOR_VER(header.version) == 0 - && LINUX_HEADER_MINOR_VER(header.version) < 2) { - panic(true, "linux: protocols < 0.2 are not supported"); - } -#endif - - size_t kernel_size = kernel_file->size; - void *kernel_base = ext_mem_alloc_type_aligned( - ALIGN_UP(kernel_size, 4096), + p.kernel_size = kernel_file->size; + p.kernel_base = ext_mem_alloc_type_aligned( + ALIGN_UP(p.kernel_size, 4096), MEMMAP_KERNEL_AND_MODULES, 2 * 1024 * 1024); - fread(kernel_file, kernel_base, 0, kernel_size); + fread(kernel_file, p.kernel_base, 0, p.kernel_size); fclose(kernel_file); - printv("linux: loaded kernel `%s` at %x, size %u\n", kernel_path, kernel_base, kernel_size); + printv("linux: loaded kernel `%s` at %x, size %u\n", kernel_path, p.kernel_base, p.kernel_size); - void *dtb = prepare_device_tree_blob(config, cmdline); - if (!dtb) { - panic(true, "linux: failed to prepare the device tree blob"); - } + const char *reason = verify_kernel(&p); + if (reason) + panic(true, "linux: invalid kernel image: %s", reason); -#if defined(__riscv) - printv("linux: bsp hart %d, device tree blob at %x\n", bsp_hartid, dtb); + load_files(&p, config); - void (*kernel_entry)(uint64_t hartid, uint64_t dtb) = kernel_base; - asm ("csrci sstatus, 0x2\n\t" - "csrw sie, zero\n\t"); - kernel_entry(bsp_hartid, (uint64_t)dtb); -#elif defined(__aarch64__) - printv("linux: device tree blob at %x\n", dtb); + prepare_device_tree_blob(&p); - void (*kernel_entry)(uint64_t dtb, uint64_t res0, uint64_t res1, uint64_t res2) = kernel_base; - asm ("msr daifset, 0xF"); - kernel_entry((uint64_t)dtb, 0, 0, 0); -#endif - __builtin_unreachable(); + prepare_efi_tables(&p, config); + + prepare_mmap(&p); + + jump_to_kernel(&p); } #endif From 3f57bb5a2c99f23f016e9da143fa9da224c21f19 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Fri, 20 Dec 2024 12:57:47 +0000 Subject: [PATCH 2/3] protos/linux_risc: pass memory map and initrd by EFI configuration table Newer Linux kernel supports passing boot params without a devicetree. In this case, the bootloader should register a Linux-specific EFI configuration table, which contains memory mapping information or initrd address. This is the only option on some new platforms like LoongArch, and is necessary to allow booting Linux kernel without a DTB on aarch64/riscv64. Signed-off-by: Yao Zi --- common/lib/misc.c | 9 ++-- common/lib/misc.h | 2 +- common/protos/linux_risc.c | 103 ++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/common/lib/misc.c b/common/lib/misc.c index 614842bf..366d1f84 100644 --- a/common/lib/misc.c +++ b/common/lib/misc.c @@ -16,7 +16,7 @@ EFI_BOOT_SERVICES *gBS; EFI_RUNTIME_SERVICES *gRT; EFI_HANDLE efi_image_handle; EFI_MEMORY_DESCRIPTOR *efi_mmap = NULL; -UINTN efi_mmap_size = 0, efi_desc_size = 0; +UINTN efi_mmap_size = 0, efi_desc_size = 0, efi_mmap_key = 0; UINT32 efi_desc_ver = 0; #endif @@ -205,9 +205,8 @@ bool efi_exit_boot_services(void) { EFI_MEMORY_DESCRIPTOR tmp_mmap[1]; efi_mmap_size = sizeof(tmp_mmap); - UINTN mmap_key = 0; - gBS->GetMemoryMap(&efi_mmap_size, tmp_mmap, &mmap_key, &efi_desc_size, &efi_desc_ver); + gBS->GetMemoryMap(&efi_mmap_size, tmp_mmap, &efi_mmap_key, &efi_desc_size, &efi_desc_ver); efi_mmap_size += 4096; @@ -232,13 +231,13 @@ bool efi_exit_boot_services(void) { size_t retries = 0; retry: - status = gBS->GetMemoryMap(&efi_mmap_size, efi_mmap, &mmap_key, &efi_desc_size, &efi_desc_ver); + status = gBS->GetMemoryMap(&efi_mmap_size, efi_mmap, &efi_mmap_key, &efi_desc_size, &efi_desc_ver); if (retries == 0 && status) { goto fail; } // Be gone, UEFI! - status = gBS->ExitBootServices(efi_image_handle, mmap_key); + status = gBS->ExitBootServices(efi_image_handle, efi_mmap_key); if (status) { if (retries == 128) { goto fail; diff --git a/common/lib/misc.h b/common/lib/misc.h index 3995e59b..38810e32 100644 --- a/common/lib/misc.h +++ b/common/lib/misc.h @@ -21,7 +21,7 @@ extern EFI_BOOT_SERVICES *gBS; extern EFI_RUNTIME_SERVICES *gRT; extern EFI_HANDLE efi_image_handle; extern EFI_MEMORY_DESCRIPTOR *efi_mmap; -extern UINTN efi_mmap_size, efi_desc_size; +extern UINTN efi_mmap_size, efi_desc_size, efi_mmap_key; extern UINT32 efi_desc_ver; extern bool efi_boot_services_exited; diff --git a/common/protos/linux_risc.c b/common/protos/linux_risc.c index 6d69863c..694b955a 100644 --- a/common/protos/linux_risc.c +++ b/common/protos/linux_risc.c @@ -42,6 +42,20 @@ struct linux_efi_memreserve { uint64_t next; }; +struct linux_efi_boot_memmap { + UINTN map_size; + UINTN desc_size; + uint32_t desc_ver; + UINTN map_key; + UINTN buff_size; + EFI_MEMORY_DESCRIPTOR descs[]; +}; + +struct linux_efi_initrd { + UINTN base; + UINTN size; +}; + // End of Linux code struct boot_param { @@ -51,6 +65,7 @@ struct boot_param { size_t module_size; char *cmdline; void *dtb; + struct linux_efi_boot_memmap *memmap; }; #if defined(__riscv) @@ -203,7 +218,7 @@ void add_framebuffer(struct fb_info *fb) { void prepare_efi_tables(struct boot_param *p, char *config) { (void)p; - int ret = 0; + EFI_STATUS ret = 0; { size_t req_width = 0, req_height = 0, req_bpp = 0; @@ -226,6 +241,7 @@ void prepare_efi_tables(struct boot_param *p, char *config) { } } + { struct linux_efi_memreserve *rsv = ext_mem_alloc(sizeof(struct linux_efi_memreserve)); @@ -240,39 +256,90 @@ void prepare_efi_tables(struct boot_param *p, char *config) { panic(true, "linux: failed to install memory reservation configuration table: '%x'", ret); } } + + if (p->module_base) { + struct linux_efi_initrd *initrd_table; + + ret = gBS->AllocatePool(EfiLoaderData, sizeof(*initrd_table), (void **)&initrd_table); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate Linux initrd table"); + } + + initrd_table->base = (UINTN)p->module_base; + initrd_table->size = p->module_size; + + EFI_GUID initrd_table_guid = { 0x5568e427, 0x68fc, 0x4f3d, { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68}}; + ret = gBS->InstallConfigurationTable(&initrd_table_guid, initrd_table); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install initrd\n"); + } + } + + { + EFI_MEMORY_DESCRIPTOR tmp_mmap[1]; + size_t mmap_size = sizeof(tmp_mmap); + UINTN mmap_key = 0; + + gBS->GetMemoryMap(&efi_mmap_size, tmp_mmap, &mmap_key, &efi_desc_size, &efi_desc_ver); + mmap_size += 4096 + sizeof(struct linux_efi_boot_memmap); + + ret = gBS->AllocatePool(EfiLoaderData, efi_mmap_size, (void **)&p->memmap); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to allocate UEFI memory map"); + } + + p->memmap->buff_size = mmap_size; + + EFI_GUID memmap_table_guid = { 0x800f683f, 0xd08b, 0x423a, { 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4}}; + ret = gBS->InstallConfigurationTable(&memmap_table_guid, p->memmap); + if (ret != EFI_SUCCESS) { + panic(true, "linux: failed to install UEFI memory map"); + } + } + efi_exit_boot_services(); } void prepare_mmap(struct boot_param *p) { - void *dtb = p->dtb; - int 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)); - } + { + void *dtb = p->dtb; + int 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-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-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)); + 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)); + } } + p->memmap->map_size = efi_mmap_size; + p->memmap->desc_size = efi_desc_size; + p->memmap->desc_ver = efi_desc_ver; + p->memmap->map_key = efi_mmap_key; + 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) + if (entry->Attribute & EFI_MEMORY_RUNTIME) { entry->VirtualStart = entry->PhysicalStart; + } } + memcpy(&p->memmap->descs, efi_mmap, efi_mmap_size); + EFI_STATUS status = gRT->SetVirtualAddressMap(efi_mmap_size, efi_desc_size, efi_desc_ver, efi_mmap); if (status != EFI_SUCCESS) { panic(false, "linux: failed to set UEFI virtual address map: '%x'", status); From 240ecc29e933108debbc0fd6abfe34e78757b055 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Fri, 20 Dec 2024 13:46:48 +0000 Subject: [PATCH 3/3] 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 --- common/menu.c | 5 ---- common/protos/linux_risc.c | 57 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) 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 #include @@ -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(); }