grub2/sw64-Add-Linux-load-logic.patch
sunway_fw e100bf4cab add SW64 arch support
Signed-off-by: sunway_fw <sunway_fw@wxiat.com>
(cherry picked from commit c77b80a2ab9a7dc8f49fe33d54ea3a47d39c7d36)
2025-03-11 20:36:59 +08:00

594 lines
18 KiB
Diff

From 6b08e04d1a8e52d0880140b83470c53bb0c8b566 Mon Sep 17 00:00:00 2001
From: sunway_fw <sunway_fw@wxiat.com>
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 <sunway_fw@wxiat.com>
---
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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/loader.h>
+#include <grub/file.h>
+#include <grub/disk.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/types.h>
+#include <grub/command.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/cache.h>
+#include <grub/kernel.h>
+#include <grub/efi/api.h>
+#include <grub/efi/fdtload.h>
+#include <grub/efi/efi.h>
+#include <grub/elf.h>
+#include <grub/elfload.h>
+#include <grub/i18n.h>
+#include <grub/env.h>
+#include <grub/cpu/linux.h>
+#include <grub/cpu/pal.h>
+#include <grub/lib/cmdline.h>
+#include <grub/linux.h>
+#include <grub/fdt.h>
+#include <grub/efi/memory.h>
+
+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