/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * x86 specific code to for EFI handover boot protocol * Linux kernels version 5.8 and newer support providing the initrd by * LINUX_INITRD_MEDIA_GUID DevicePath. In order to support older kernels too, * this x86 specific linux_exec function passes the initrd by setting the * corresponding fields in the setup_header struct. * * see https://www.kernel.org/doc/html/latest/x86/boot.html */ #include #include #include "initrd.h" #include "linux.h" #include "macro-fundamental.h" #include "util.h" #define SETUP_MAGIC 0x53726448 /* "HdrS" */ struct setup_header { UINT8 setup_sects; UINT16 root_flags; UINT32 syssize; UINT16 ram_size; UINT16 vid_mode; UINT16 root_dev; UINT16 boot_flag; UINT16 jump; UINT32 header; UINT16 version; UINT32 realmode_swtch; UINT16 start_sys_seg; UINT16 kernel_version; UINT8 type_of_loader; UINT8 loadflags; UINT16 setup_move_size; UINT32 code32_start; UINT32 ramdisk_image; UINT32 ramdisk_size; UINT32 bootsect_kludge; UINT16 heap_end_ptr; UINT8 ext_loader_ver; UINT8 ext_loader_type; UINT32 cmd_line_ptr; UINT32 initrd_addr_max; UINT32 kernel_alignment; UINT8 relocatable_kernel; UINT8 min_alignment; UINT16 xloadflags; UINT32 cmdline_size; UINT32 hardware_subarch; UINT64 hardware_subarch_data; UINT32 payload_offset; UINT32 payload_length; UINT64 setup_data; UINT64 pref_address; UINT32 init_size; UINT32 handover_offset; } _packed_; /* adapted from linux' bootparam.h */ struct boot_params { UINT8 screen_info[64]; // was: struct screen_info UINT8 apm_bios_info[20]; // was: struct apm_bios_info UINT8 _pad2[4]; UINT64 tboot_addr; UINT8 ist_info[16]; // was: struct ist_info UINT8 _pad3[16]; UINT8 hd0_info[16]; UINT8 hd1_info[16]; UINT8 sys_desc_table[16]; // was: struct sys_desc_table UINT8 olpc_ofw_header[16]; // was: struct olpc_ofw_header UINT32 ext_ramdisk_image; UINT32 ext_ramdisk_size; UINT32 ext_cmd_line_ptr; UINT8 _pad4[116]; UINT8 edid_info[128]; // was: struct edid_info UINT8 efi_info[32]; // was: struct efi_info UINT32 alt_mem_k; UINT32 scratch; UINT8 e820_entries; UINT8 eddbuf_entries; UINT8 edd_mbr_sig_buf_entries; UINT8 kbd_status; UINT8 secure_boot; UINT8 _pad5[2]; UINT8 sentinel; UINT8 _pad6[1]; struct setup_header hdr; UINT8 _pad7[0x290-0x1f1-sizeof(struct setup_header)]; UINT32 edd_mbr_sig_buffer[16]; // was: edd_mbr_sig_buffer[EDD_MBR_SIG_MAX] UINT8 e820_table[20*128]; // was: struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE] UINT8 _pad8[48]; UINT8 eddbuf[6*82]; // was: struct edd_info eddbuf[EDDMAXNR] UINT8 _pad9[276]; } _packed_; #ifdef __i386__ #define __regparm0__ __attribute__((regparm(0))) #else #define __regparm0__ #endif typedef void(*handover_f)(void *image, EFI_SYSTEM_TABLE *table, struct boot_params *params) __regparm0__; static void linux_efi_handover(EFI_HANDLE image, struct boot_params *params) { handover_f handover; UINTN start = (UINTN)params->hdr.code32_start; assert(params); #ifdef __x86_64__ asm volatile ("cli"); start += 512; #endif handover = (handover_f)(start + params->hdr.handover_offset); handover(image, ST, params); } EFI_STATUS linux_exec( EFI_HANDLE image, const CHAR8 *cmdline, UINTN cmdline_len, const void *linux_buffer, UINTN linux_length, const void *initrd_buffer, UINTN initrd_length) { const struct boot_params *image_params; struct boot_params *boot_params; EFI_HANDLE initrd_handle = NULL; EFI_PHYSICAL_ADDRESS addr; UINT8 setup_sectors; EFI_STATUS err; assert(image); assert(cmdline || cmdline_len == 0); assert(linux_buffer); assert(initrd_buffer || initrd_length == 0); if (linux_length < sizeof(struct boot_params)) return EFI_LOAD_ERROR; image_params = (const struct boot_params *) linux_buffer; if (image_params->hdr.boot_flag != 0xAA55 || image_params->hdr.header != SETUP_MAGIC || image_params->hdr.version < 0x20b || !image_params->hdr.relocatable_kernel) return EFI_LOAD_ERROR; addr = UINT32_MAX; /* Below the 32bit boundary */ err = BS->AllocatePages( AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(0x4000), &addr); if (EFI_ERROR(err)) return err; boot_params = (struct boot_params *) PHYSICAL_ADDRESS_TO_POINTER(addr); ZeroMem(boot_params, 0x4000); boot_params->hdr = image_params->hdr; boot_params->hdr.type_of_loader = 0xff; setup_sectors = image_params->hdr.setup_sects > 0 ? image_params->hdr.setup_sects : 4; boot_params->hdr.code32_start = (UINT32) POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + (setup_sectors + 1) * 512; if (cmdline) { addr = 0xA0000; err = BS->AllocatePages( AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr); if (EFI_ERROR(err)) return err; CopyMem(PHYSICAL_ADDRESS_TO_POINTER(addr), cmdline, cmdline_len); ((CHAR8 *) PHYSICAL_ADDRESS_TO_POINTER(addr))[cmdline_len] = 0; boot_params->hdr.cmd_line_ptr = (UINT32) addr; } /* Providing the initrd via LINUX_INITRD_MEDIA_GUID is only supported by Linux 5.8+ (5.7+ on ARM64). Until supported kernels become more established, we continue to set ramdisk in the handover struct. This value is overridden by kernels that support LINUX_INITRD_MEDIA_GUID. If you need to know which protocol was used by the kernel, pass "efi=debug" to the kernel, this will print a line when InitrdMediaGuid was successfully used to load the initrd. */ boot_params->hdr.ramdisk_image = (UINT32) POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer); boot_params->hdr.ramdisk_size = (UINT32) initrd_length; /* register LINUX_INITRD_MEDIA_GUID */ err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); if (EFI_ERROR(err)) return err; linux_efi_handover(image, boot_params); (void) initrd_unregister(initrd_handle); initrd_handle = NULL; return EFI_LOAD_ERROR; }