From 6b08e04d1a8e52d0880140b83470c53bb0c8b566 Mon Sep 17 00:00:00 2001 From: sunway_fw Date: Tue, 18 Feb 2025 14:58:36 +0800 Subject: [PATCH 2/5] sw64: Add Linux load logic We currently only support to run grub on SW64 as UEFI payload. Ideally, we also only want to support running Linux underneath as UEFI payload. Signed-off-by: sunway_fw --- grub-core/loader/sw64/efi/linux.c | 515 ++++++++++++++++++++++++++++++ include/grub/sw64/linux.h | 47 +++ 2 files changed, 562 insertions(+) create mode 100644 grub-core/loader/sw64/efi/linux.c create mode 100644 include/grub/sw64/linux.h diff --git a/grub-core/loader/sw64/efi/linux.c b/grub-core/loader/sw64/efi/linux.c new file mode 100644 index 0000000..88db8ea --- /dev/null +++ b/grub-core/loader/sw64/efi/linux.c @@ -0,0 +1,515 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2018 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#pragma GCC diagnostic ignored "-Wcast-align" + +#define GRUB_EFI_SW64_FIRMWARE_INFO { 0xc47a23c3, 0xcebb, 0x4cc9, \ + { 0xa5, 0xe2, 0xde, 0xd0, 0x8f, 0xe4, 0x20, 0xb5 } } + +#define GRUB_EFI_SW64_MEMORY_ATTRIBUTES { 0xdcfa911d, 0x26eb, 0x469f, \ + { 0xa2, 0x20, 0x38, 0xb7, 0xdc, 0x46, 0x12, 0x20 } } + +#define SW64_EFI_FIRMWARE_INFO_SIGNATURE \ + ('S' << 24 | 'H' << 16 | 'I' << 8 | 'F') + +static grub_dl_t my_mod; +static int loaded; +static char *linux_args; + +/* Initrd base and size. */ +static void *initrd_mem; +static grub_efi_uintn_t initrd_pages; +static grub_addr_t initrd_start; +static grub_efi_uintn_t initrd_size; +static grub_uint64_t linux_entry; + +struct sw64_firmware_info_head +{ + grub_uint32_t signature; + grub_uint32_t revision; + grub_uint32_t length; +}; + +struct sw64_firmware_info +{ + struct sw64_firmware_info_head firmware_info_head; + grub_uint32_t reserved; + grub_uint32_t need_boot_param; +}; + +void *raw_fdt; +static struct boot_param *sunway_boot_params = (struct boot_param *)BOOT_PARAM_START; + +static grub_efi_uint32_t +sw64_efi_get_boot_param (void) +{ + unsigned i; + struct sw64_firmware_info_head *firmware_info_head; + static grub_packed_guid_t info_guid = GRUB_EFI_SW64_FIRMWARE_INFO; + + for (i = 0; i < grub_efi_system_table->num_table_entries; i++) + { + grub_packed_guid_t *guid = + (grub_packed_guid_t *)&grub_efi_system_table->configuration_table[i].vendor_guid; + + if (! grub_memcmp (guid, &info_guid, sizeof (grub_packed_guid_t))) + { + firmware_info_head = grub_efi_system_table->configuration_table[i].vendor_table; + if (firmware_info_head->signature != SW64_EFI_FIRMWARE_INFO_SIGNATURE) { + grub_printf ("Get a legacy firmware info\n"); + return 1; + } + + if (firmware_info_head->revision == 1) { + return ((struct sw64_firmware_info *)firmware_info_head)->need_boot_param; + } + } + } + return 1; +} + +static void +sw64_efi_memattr_repair (void) +{ + unsigned i; + static grub_packed_guid_t info_guid = GRUB_EFI_SW64_MEMORY_ATTRIBUTES; + + for (i = 0; i < grub_efi_system_table->num_table_entries; i++) { + grub_packed_guid_t *guid = (grub_packed_guid_t *)&grub_efi_system_table->configuration_table[i].vendor_guid; + + if (! grub_memcmp (guid, &info_guid, sizeof (grub_packed_guid_t))) { + grub_efi_system_table->configuration_table[i].vendor_table = (void *)grub_virt_to_phys((grub_uint64_t)grub_efi_system_table->configuration_table[i].vendor_table); + } + } +} + +typedef +void +(*jump_to_kernel) ( + grub_uint64_t magic, + grub_uint64_t device_tree_base + ); + +static grub_err_t +finalize_params_linux (void) +{ + int retval; + int node; + grub_efi_uintn_t mmap_size; + grub_efi_uintn_t map_key; + grub_efi_uintn_t desc_size; + grub_efi_uint32_t desc_version; + grub_efi_memory_descriptor_t *mmap_buf; + grub_efi_uintn_t i; + grub_uint32_t bootargs_size; + const char *last_bootargs; + static char *temp_linux_args; + + mmap_buf = 0; + + raw_fdt = grub_fdt_load (GRUB_EFI_LINUX_FDT_EXTRA_SPACE); + + if (!raw_fdt || grub_fdt_check_header_nosize(raw_fdt)) { + goto failure; + } + + node = grub_fdt_find_subnode (raw_fdt, 0, "chosen"); + if (node < 0) + node = grub_fdt_add_subnode (raw_fdt, 0, "chosen"); + + if (node < 1) + goto failure; + + initrd_start = grub_virt_to_phys (initrd_start); + + if (initrd_start) { + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,initrd-start", initrd_start); + if (retval) + goto failure; + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,initrd-end", initrd_start + initrd_size); + if (retval) + goto failure; + } + + last_bootargs = grub_fdt_get_prop (raw_fdt, node, "bootargs", &bootargs_size); + if (last_bootargs && grub_strlen (last_bootargs) > 1) { + temp_linux_args = grub_malloc (grub_strlen (linux_args) + bootargs_size + 1); + if (!temp_linux_args) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory in finalize_params_linux")); + goto failure; + } + grub_memcpy (temp_linux_args, last_bootargs, bootargs_size - 1); + *(temp_linux_args + bootargs_size - 1) = ' '; + grub_memcpy (temp_linux_args + bootargs_size, linux_args, grub_strlen (linux_args) + 1); + grub_free (linux_args); + linux_args = temp_linux_args; + } + + retval = grub_fdt_set_prop (raw_fdt, node, "bootargs", linux_args, grub_strlen (linux_args) + 1); + if (retval) + goto failure; + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,uefi-system-table", + grub_virt_to_phys((grub_uint64_t)grub_efi_system_table)); + if (retval) + goto failure; + + mmap_size = grub_efi_find_mmap_size (); + if (! mmap_size) + goto failure; + + mmap_buf = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (mmap_size)); + if (! mmap_buf) + goto failure; + + grub_efi_finish_boot_services (&mmap_size, mmap_buf, &map_key, &desc_size, &desc_version); + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,uefi-mmap-start", + grub_virt_to_phys((grub_uint64_t)mmap_buf)); + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,uefi-mmap-size", mmap_size); + if (retval) + goto failure_without_dprintf; + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,uefi-mmap-desc-size", desc_size); + if (retval) + goto failure_without_dprintf; + + retval = grub_fdt_set_prop64 (raw_fdt, node, "linux,uefi-mmap-desc-ver", desc_version); + if (retval) + goto failure_without_dprintf; + + for (i = 0; i < mmap_size / desc_size; i++) { + grub_efi_memory_descriptor_t *curdesc = (grub_efi_memory_descriptor_t *) + ((char *) mmap_buf + desc_size * i); + + curdesc->physical_start = grub_virt_to_phys(curdesc->physical_start); + } + + sw64_efi_memattr_repair(); + + return GRUB_ERR_NONE; + +failure: + grub_dprintf ("linux", "some wrong in finalize_params_linux\n"); + +failure_without_dprintf: + grub_printf ("some wrong in finalize_params_linux\n"); + raw_fdt = 0; + return GRUB_ERR_BAD_OS; +} + +static grub_err_t +legacy_finalize_params_linux (void) +{ + grub_efi_uintn_t mmap_size; + grub_efi_uintn_t map_key; + grub_efi_uintn_t desc_size; + grub_efi_uint32_t desc_version; + grub_efi_memory_descriptor_t *mmap_buf; + grub_err_t err; + grub_efi_uintn_t i; + + /* Initrd. */ + sunway_boot_params->initrd_start = (grub_uint64_t)initrd_start; + sunway_boot_params->initrd_size = (grub_uint64_t)initrd_size; + + /* DTB. */ + raw_fdt = grub_fdt_load (GRUB_EFI_LINUX_FDT_EXTRA_SPACE); + + if (!raw_fdt) + { + sunway_boot_params->dtb_start = 0; + grub_dprintf ("linux", "not found registered FDT\n"); + } + + if (raw_fdt) + { + err = grub_fdt_check_header_nosize(raw_fdt); + if (err) + grub_dprintf ("linux", "illegal FDT file\n"); + + sunway_boot_params->dtb_start = (grub_uint64_t)raw_fdt; + grub_dprintf ("linux", "dtb: [addr=0x%lx, size=0x%x]\n", + (grub_uint64_t) raw_fdt, grub_fdt_get_totalsize(raw_fdt)); + } + + /* MDT. + Must be done after grub_machine_fini because map_key is used by + exit_boot_services. */ + mmap_size = grub_efi_find_mmap_size (); + if (! mmap_size) { + grub_dprintf ("linux", "unable to get mmap_size\n"); + return GRUB_ERR_BAD_OS; + } + mmap_buf = grub_efi_allocate_any_pages (GRUB_EFI_BYTES_TO_PAGES (mmap_size)); + if (! mmap_buf) { + grub_dprintf ("linux", "cannot allocate memory map\n"); + return GRUB_ERR_BAD_OS; + } + err = grub_efi_finish_boot_services (&mmap_size, mmap_buf, &map_key, + &desc_size, &desc_version); + for (i = 0; i < mmap_size / desc_size; i++) + { + grub_efi_memory_descriptor_t *curdesc = (grub_efi_memory_descriptor_t *) + ((char *) mmap_buf + desc_size * i); + + curdesc->physical_start = grub_virt_to_phys(curdesc->physical_start); + } + + sw64_efi_memattr_repair(); + + sunway_boot_params->command_line = (grub_uint64_t) linux_args; + sunway_boot_params->efi_systab = grub_virt_to_phys((grub_uint64_t)grub_efi_system_table); + sunway_boot_params->efi_memmap = grub_virt_to_phys((grub_uint64_t)mmap_buf); + sunway_boot_params->efi_memmap_size = mmap_size; + sunway_boot_params->efi_memdesc_size = desc_size; + sunway_boot_params->efi_memdesc_version = desc_version; + + return GRUB_ERR_NONE; +} +static void +start_kernel(void) +{ + local_irq_disable (); + grub_dprintf ("linux", "Jump to entry: %lx\n", linux_entry); + + jump_to_kernel jump = (jump_to_kernel) linux_entry; + if (sw64_efi_get_boot_param()) { + jump (0, 0); + } else { + jump (0xdeed2024, (grub_uint64_t)raw_fdt); + } +} + +static grub_err_t +grub_linux_boot (void) +{ + if (sw64_efi_get_boot_param()) { + legacy_finalize_params_linux(); + } else { + finalize_params_linux(); + } + + start_kernel (); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_load_elf64 (grub_elf_t elf, const char *filename) +{ + Elf64_Addr base_addr; + grub_size_t linux_size; + grub_uint64_t align; + + if (elf->ehdr.ehdr64.e_ident[EI_MAG0] != ELFMAG0 + || elf->ehdr.ehdr64.e_ident[EI_MAG1] != ELFMAG1 + || elf->ehdr.ehdr64.e_ident[EI_MAG2] != ELFMAG2 + || elf->ehdr.ehdr64.e_ident[EI_MAG3] != ELFMAG3 + || elf->ehdr.ehdr64.e_ident[EI_DATA] != ELFDATA2LSB) + return grub_error(GRUB_ERR_UNKNOWN_OS, + N_("invalid arch-independent ELF magic")); + + if (elf->ehdr.ehdr64.e_ident[EI_CLASS] != ELFCLASS64 + || elf->ehdr.ehdr64.e_version != EV_CURRENT + || elf->ehdr.ehdr64.e_machine != EM_SW_64) + return grub_error (GRUB_ERR_UNKNOWN_OS, + N_("invalid arch-dependent ELF magic")); + + if (elf->ehdr.ehdr64.e_type != ET_EXEC) + return grub_error (GRUB_ERR_UNKNOWN_OS, + N_("this ELF file is not of the right type")); + + /* FIXME: Should we support program headers at strange locations? */ + if (elf->ehdr.ehdr64.e_phoff + elf->ehdr.ehdr64.e_phnum * elf->ehdr.ehdr64.e_phentsize > GRUB_ELF_SEARCH) + return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset"); + + linux_size = grub_elf64_size (elf, &base_addr, &align); + if (linux_size == 0) + return grub_error (GRUB_ERR_BAD_OS, "linux size is 0"); + + linux_entry = (grub_uint64_t)elf->ehdr.ehdr64.e_entry; + grub_dprintf ("linux", "Segment phy_addr: %lx entry: %lx\n", (grub_uint64_t)base_addr,(grub_uint64_t)elf->ehdr.ehdr64.e_entry); + + /* Now load the segments into the area we claimed. */ + return grub_elf64_load (elf, filename, (void *) (grub_addr_t) (0), GRUB_ELF_LOAD_FLAGS_NONE, 0, 0); +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_ssize_t size; + grub_elf_t elf = 0; + + grub_dl_ref (my_mod); + + grub_loader_unset (); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + elf = grub_elf_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL); + if (! elf) + goto fail; + + grub_dprintf ("linux", "Loading linux: %s\n", argv[0]); + + if (grub_load_elf64 (elf, argv[0])) + goto fail; + + grub_memset (sunway_boot_params, 0, sizeof(*sunway_boot_params)); + size = grub_loader_cmdline_size(argc, argv); + + linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); + if (!linux_args) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); + goto fail; + } + + /* Create kernel command line. */ + grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1, + size, GRUB_VERIFY_KERNEL_CMDLINE); + + grub_dprintf ("linux", "linux_args: '%s'\n", linux_args); + grub_errno = GRUB_ERR_NONE; + + grub_loader_set (grub_linux_boot, grub_linux_unload, 0); + + fail: + if (elf) + grub_elf_close (elf); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + } + else + loaded = 1; + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + if (! loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); + goto fail; + } + + if (grub_initrd_init (argc, argv, &initrd_ctx)) + goto fail; + + initrd_size = grub_get_initrd_size (&initrd_ctx); + grub_dprintf ("linux", "Loading initrd %s\n", argv[0]); + + initrd_pages = (GRUB_EFI_BYTES_TO_PAGES (initrd_size)); + initrd_mem = grub_efi_allocate_any_pages (initrd_pages); + if (! initrd_mem) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate pages"); + goto fail; + } + + grub_dprintf ("linux", "initrd: [addr=0x%lx, size=0x%lx]\n", + (grub_uint64_t) initrd_mem, initrd_size); + + if (grub_initrd_load (&initrd_ctx, initrd_mem)) + goto fail; + + initrd_start = (grub_addr_t) initrd_mem; + + fail: + grub_initrd_close (&initrd_ctx); + if (initrd_mem && !initrd_start) + grub_efi_free_pages ((grub_addr_t) initrd_mem, initrd_pages); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT (linux) +{ + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + N_("FILE [ARGS...]"), N_("Load Linux.")); + + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + N_("FILE"), N_("Load initrd.")); + + my_mod = mod; +} + +GRUB_MOD_FINI (linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/include/grub/sw64/linux.h b/include/grub/sw64/linux.h new file mode 100644 index 0000000..c4d0907 --- /dev/null +++ b/include/grub/sw64/linux.h @@ -0,0 +1,47 @@ +#ifndef GRUB_SW_H +#define GRUB_SW_H 1 +#define PAGE_OFFSET 0xfff0000000000000UL +#define KTEXT_OFFSET 0xffffffff80000000UL +#define GRUB_PRINTK_START (PAGE_OFFSET | 0x800000) +#define GRUB_PRINTK_SIZE (0x20000) +#define BOOT_PARAM_START 0xfff000000090a100ULL + +#define GRUB_ELF_SEARCH 1024 +#define IPL_MIN 0 +#define IPL_MAX 7 +#define barrier() __asm__ __volatile__("": : :"memory") +#define local_irq_disable() do { swpipl(IPL_MAX); barrier(); } while(0) +#define local_irq_enable() do { barrier(); swpipl(IPL_MIN); } while(0) + +static inline +grub_uint64_t +grub_virt_to_phys(grub_uint64_t x) +{ + if (x >= KTEXT_OFFSET) + x -= KTEXT_OFFSET; + else if (x >= PAGE_OFFSET) + x -= PAGE_OFFSET; + + return x; +} + +struct boot_param +{ + grub_uint64_t initrd_start; /* logical address of initrd */ + grub_uint64_t initrd_size; /* size of initrd */ + grub_uint64_t dtb_start; /* logical address of dtb */ + grub_uint64_t efi_systab; /* logical address of EFI system table */ + grub_uint64_t efi_memmap; /* logical address of EFI memory map */ + grub_uint64_t efi_memmap_size; /* size of EFI memory map */ + grub_uint64_t efi_memdesc_size; /* size of an EFI memory map descriptor */ + grub_uint64_t efi_memdesc_version; /* memory descriptor version */ + grub_uint64_t command_line; /* logical address of cmdline */ +}; + +struct linux_sw64_kernel_header +{ +}; + +#define linux_arch_kernel_header linux_sw64_kernel_header + +#endif -- 2.33.0