]>
Commit | Line | Data |
---|---|---|
dc467928 MR |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | /* | |
4 | * x86 specific code to for EFI handover boot protocol | |
5 | * Linux kernels version 5.8 and newer support providing the initrd by | |
6 | * LINUX_INITRD_MEDIA_GUID DevicePath. In order to support older kernels too, | |
7 | * this x86 specific linux_exec function passes the initrd by setting the | |
8 | * corresponding fields in the setup_header struct. | |
9 | * | |
10 | * see https://www.kernel.org/doc/html/latest/x86/boot.html | |
11 | */ | |
12 | ||
13 | #include <efi.h> | |
14 | #include <efilib.h> | |
15 | ||
16 | #include "initrd.h" | |
17 | #include "linux.h" | |
18 | #include "macro-fundamental.h" | |
19 | #include "util.h" | |
20 | ||
21 | #define SETUP_MAGIC 0x53726448 /* "HdrS" */ | |
22 | ||
23 | struct setup_header { | |
db4122d1 JJ |
24 | uint8_t setup_sects; |
25 | uint16_t root_flags; | |
26 | uint32_t syssize; | |
27 | uint16_t ram_size; | |
28 | uint16_t vid_mode; | |
29 | uint16_t root_dev; | |
30 | uint16_t boot_flag; | |
31 | uint16_t jump; | |
32 | uint32_t header; | |
33 | uint16_t version; | |
34 | uint32_t realmode_swtch; | |
35 | uint16_t start_sys_seg; | |
36 | uint16_t kernel_version; | |
37 | uint8_t type_of_loader; | |
38 | uint8_t loadflags; | |
39 | uint16_t setup_move_size; | |
40 | uint32_t code32_start; | |
41 | uint32_t ramdisk_image; | |
42 | uint32_t ramdisk_size; | |
43 | uint32_t bootsect_kludge; | |
44 | uint16_t heap_end_ptr; | |
45 | uint8_t ext_loader_ver; | |
46 | uint8_t ext_loader_type; | |
47 | uint32_t cmd_line_ptr; | |
48 | uint32_t initrd_addr_max; | |
49 | uint32_t kernel_alignment; | |
50 | uint8_t relocatable_kernel; | |
51 | uint8_t min_alignment; | |
52 | uint16_t xloadflags; | |
53 | uint32_t cmdline_size; | |
54 | uint32_t hardware_subarch; | |
55 | uint64_t hardware_subarch_data; | |
56 | uint32_t payload_offset; | |
57 | uint32_t payload_length; | |
58 | uint64_t setup_data; | |
59 | uint64_t pref_address; | |
60 | uint32_t init_size; | |
61 | uint32_t handover_offset; | |
dc467928 MR |
62 | } _packed_; |
63 | ||
64 | /* adapted from linux' bootparam.h */ | |
65 | struct boot_params { | |
db4122d1 JJ |
66 | uint8_t screen_info[64]; // was: struct screen_info |
67 | uint8_t apm_bios_info[20]; // was: struct apm_bios_info | |
68 | uint8_t _pad2[4]; | |
69 | uint64_t tboot_addr; | |
70 | uint8_t ist_info[16]; // was: struct ist_info | |
71 | uint8_t _pad3[16]; | |
72 | uint8_t hd0_info[16]; | |
73 | uint8_t hd1_info[16]; | |
74 | uint8_t sys_desc_table[16]; // was: struct sys_desc_table | |
75 | uint8_t olpc_ofw_header[16]; // was: struct olpc_ofw_header | |
76 | uint32_t ext_ramdisk_image; | |
77 | uint32_t ext_ramdisk_size; | |
78 | uint32_t ext_cmd_line_ptr; | |
79 | uint8_t _pad4[116]; | |
80 | uint8_t edid_info[128]; // was: struct edid_info | |
81 | uint8_t efi_info[32]; // was: struct efi_info | |
82 | uint32_t alt_mem_k; | |
83 | uint32_t scratch; | |
84 | uint8_t e820_entries; | |
85 | uint8_t eddbuf_entries; | |
86 | uint8_t edd_mbr_sig_buf_entries; | |
87 | uint8_t kbd_status; | |
88 | uint8_t secure_boot; | |
89 | uint8_t _pad5[2]; | |
90 | uint8_t sentinel; | |
91 | uint8_t _pad6[1]; | |
dc467928 | 92 | struct setup_header hdr; |
db4122d1 JJ |
93 | uint8_t _pad7[0x290-0x1f1-sizeof(struct setup_header)]; |
94 | uint32_t edd_mbr_sig_buffer[16]; // was: edd_mbr_sig_buffer[EDD_MBR_SIG_MAX] | |
95 | uint8_t e820_table[20*128]; // was: struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE] | |
96 | uint8_t _pad8[48]; | |
97 | uint8_t eddbuf[6*82]; // was: struct edd_info eddbuf[EDDMAXNR] | |
98 | uint8_t _pad9[276]; | |
dc467928 MR |
99 | } _packed_; |
100 | ||
101 | #ifdef __i386__ | |
102 | #define __regparm0__ __attribute__((regparm(0))) | |
103 | #else | |
104 | #define __regparm0__ | |
105 | #endif | |
106 | ||
70cd15e9 | 107 | typedef void(*handover_f)(void *image, EFI_SYSTEM_TABLE *table, struct boot_params *params) __regparm0__; |
dc467928 | 108 | |
70cd15e9 | 109 | static void linux_efi_handover(EFI_HANDLE image, struct boot_params *params) { |
dc467928 MR |
110 | handover_f handover; |
111 | UINTN start = (UINTN)params->hdr.code32_start; | |
112 | ||
113 | assert(params); | |
114 | ||
115 | #ifdef __x86_64__ | |
116 | asm volatile ("cli"); | |
117 | start += 512; | |
118 | #endif | |
119 | handover = (handover_f)(start + params->hdr.handover_offset); | |
120 | handover(image, ST, params); | |
121 | } | |
122 | ||
123 | EFI_STATUS linux_exec( | |
124 | EFI_HANDLE image, | |
07d0fde4 | 125 | const char *cmdline, UINTN cmdline_len, |
70cd15e9 JJ |
126 | const void *linux_buffer, UINTN linux_length, |
127 | const void *initrd_buffer, UINTN initrd_length) { | |
dc467928 MR |
128 | |
129 | const struct boot_params *image_params; | |
130 | struct boot_params *boot_params; | |
131 | EFI_HANDLE initrd_handle = NULL; | |
132 | EFI_PHYSICAL_ADDRESS addr; | |
db4122d1 | 133 | uint8_t setup_sectors; |
dc467928 MR |
134 | EFI_STATUS err; |
135 | ||
136 | assert(image); | |
137 | assert(cmdline || cmdline_len == 0); | |
138 | assert(linux_buffer); | |
139 | assert(initrd_buffer || initrd_length == 0); | |
140 | ||
2fffe2ed ZJS |
141 | if (linux_length < sizeof(struct boot_params)) |
142 | return EFI_LOAD_ERROR; | |
143 | ||
dc467928 MR |
144 | image_params = (const struct boot_params *) linux_buffer; |
145 | ||
146 | if (image_params->hdr.boot_flag != 0xAA55 || | |
147 | image_params->hdr.header != SETUP_MAGIC || | |
148 | image_params->hdr.version < 0x20b || | |
149 | !image_params->hdr.relocatable_kernel) | |
150 | return EFI_LOAD_ERROR; | |
151 | ||
152 | addr = UINT32_MAX; /* Below the 32bit boundary */ | |
12f32748 | 153 | err = BS->AllocatePages( |
dc467928 MR |
154 | AllocateMaxAddress, |
155 | EfiLoaderData, | |
156 | EFI_SIZE_TO_PAGES(0x4000), | |
157 | &addr); | |
2a5e4fe4 | 158 | if (err != EFI_SUCCESS) |
dc467928 MR |
159 | return err; |
160 | ||
161 | boot_params = (struct boot_params *) PHYSICAL_ADDRESS_TO_POINTER(addr); | |
bbc1f2ea | 162 | memset(boot_params, 0, 0x4000); |
dc467928 MR |
163 | boot_params->hdr = image_params->hdr; |
164 | boot_params->hdr.type_of_loader = 0xff; | |
165 | setup_sectors = image_params->hdr.setup_sects > 0 ? image_params->hdr.setup_sects : 4; | |
db4122d1 | 166 | boot_params->hdr.code32_start = (uint32_t) POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + (setup_sectors + 1) * 512; |
dc467928 MR |
167 | |
168 | if (cmdline) { | |
169 | addr = 0xA0000; | |
170 | ||
12f32748 | 171 | err = BS->AllocatePages( |
dc467928 MR |
172 | AllocateMaxAddress, |
173 | EfiLoaderData, | |
174 | EFI_SIZE_TO_PAGES(cmdline_len + 1), | |
175 | &addr); | |
2a5e4fe4 | 176 | if (err != EFI_SUCCESS) |
dc467928 MR |
177 | return err; |
178 | ||
bbc1f2ea | 179 | memcpy(PHYSICAL_ADDRESS_TO_POINTER(addr), cmdline, cmdline_len); |
07d0fde4 | 180 | ((char *) PHYSICAL_ADDRESS_TO_POINTER(addr))[cmdline_len] = 0; |
db4122d1 | 181 | boot_params->hdr.cmd_line_ptr = (uint32_t) addr; |
dc467928 MR |
182 | } |
183 | ||
184 | /* Providing the initrd via LINUX_INITRD_MEDIA_GUID is only supported by Linux 5.8+ (5.7+ on ARM64). | |
185 | Until supported kernels become more established, we continue to set ramdisk in the handover struct. | |
186 | This value is overridden by kernels that support LINUX_INITRD_MEDIA_GUID. | |
187 | If you need to know which protocol was used by the kernel, pass "efi=debug" to the kernel, | |
188 | this will print a line when InitrdMediaGuid was successfully used to load the initrd. | |
189 | */ | |
db4122d1 JJ |
190 | boot_params->hdr.ramdisk_image = (uint32_t) POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer); |
191 | boot_params->hdr.ramdisk_size = (uint32_t) initrd_length; | |
dc467928 MR |
192 | |
193 | /* register LINUX_INITRD_MEDIA_GUID */ | |
194 | err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); | |
2a5e4fe4 | 195 | if (err != EFI_SUCCESS) |
dc467928 MR |
196 | return err; |
197 | linux_efi_handover(image, boot_params); | |
198 | (void) initrd_unregister(initrd_handle); | |
199 | initrd_handle = NULL; | |
200 | return EFI_LOAD_ERROR; | |
201 | } |