]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/linux_x86.c
tree-wide: "<n>bit" → "<n>-bit"
[thirdparty/systemd.git] / src / boot / efi / linux_x86.c
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://docs.kernel.org/x86/boot.html
11 */
12
13 #include "initrd.h"
14 #include "linux.h"
15 #include "macro-fundamental.h"
16 #include "util.h"
17
18 #define KERNEL_SECTOR_SIZE 512u
19 #define BOOT_FLAG_MAGIC 0xAA55u
20 #define SETUP_MAGIC 0x53726448u /* "HdrS" */
21 #define SETUP_VERSION_2_11 0x20bu
22 #define SETUP_VERSION_2_12 0x20cu
23 #define SETUP_VERSION_2_15 0x20fu
24 #define CMDLINE_PTR_MAX 0xA0000u
25
26 enum {
27 XLF_KERNEL_64 = 1 << 0,
28 XLF_CAN_BE_LOADED_ABOVE_4G = 1 << 1,
29 XLF_EFI_HANDOVER_32 = 1 << 2,
30 XLF_EFI_HANDOVER_64 = 1 << 3,
31 #ifdef __x86_64__
32 XLF_EFI_HANDOVER = XLF_EFI_HANDOVER_64,
33 #else
34 XLF_EFI_HANDOVER = XLF_EFI_HANDOVER_32,
35 #endif
36 };
37
38 typedef struct {
39 uint8_t setup_sects;
40 uint16_t root_flags;
41 uint32_t syssize;
42 uint16_t ram_size;
43 uint16_t vid_mode;
44 uint16_t root_dev;
45 uint16_t boot_flag;
46 uint8_t jump; /* We split the 2-byte jump field from the spec in two for convenience. */
47 uint8_t setup_size;
48 uint32_t header;
49 uint16_t version;
50 uint32_t realmode_swtch;
51 uint16_t start_sys_seg;
52 uint16_t kernel_version;
53 uint8_t type_of_loader;
54 uint8_t loadflags;
55 uint16_t setup_move_size;
56 uint32_t code32_start;
57 uint32_t ramdisk_image;
58 uint32_t ramdisk_size;
59 uint32_t bootsect_kludge;
60 uint16_t heap_end_ptr;
61 uint8_t ext_loader_ver;
62 uint8_t ext_loader_type;
63 uint32_t cmd_line_ptr;
64 uint32_t initrd_addr_max;
65 uint32_t kernel_alignment;
66 uint8_t relocatable_kernel;
67 uint8_t min_alignment;
68 uint16_t xloadflags;
69 uint32_t cmdline_size;
70 uint32_t hardware_subarch;
71 uint64_t hardware_subarch_data;
72 uint32_t payload_offset;
73 uint32_t payload_length;
74 uint64_t setup_data;
75 uint64_t pref_address;
76 uint32_t init_size;
77 uint32_t handover_offset;
78 } _packed_ SetupHeader;
79
80 /* We really only care about a few fields, but we still have to provide a full page otherwise. */
81 typedef struct {
82 uint8_t pad[192];
83 uint32_t ext_ramdisk_image;
84 uint32_t ext_ramdisk_size;
85 uint32_t ext_cmd_line_ptr;
86 uint8_t pad2[293];
87 SetupHeader hdr;
88 uint8_t pad3[3480];
89 } _packed_ BootParams;
90 assert_cc(offsetof(BootParams, ext_ramdisk_image) == 0x0C0);
91 assert_cc(sizeof(BootParams) == 4096);
92
93 #ifdef __i386__
94 # define __regparm0__ __attribute__((regparm(0)))
95 #else
96 # define __regparm0__
97 #endif
98
99 typedef void (*handover_f)(void *parent, EFI_SYSTEM_TABLE *table, BootParams *params) __regparm0__
100 __attribute__((sysv_abi));
101
102 static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams *params) {
103 assert(params);
104
105 kernel += (params->hdr.setup_sects + 1) * KERNEL_SECTOR_SIZE; /* 32-bit entry address. */
106
107 /* Old kernels needs this set, while newer ones seem to ignore this. */
108 params->hdr.code32_start = kernel;
109
110 #ifdef __x86_64__
111 kernel += KERNEL_SECTOR_SIZE; /* 64-bit entry address. */
112 #endif
113
114 kernel += params->hdr.handover_offset; /* 32/64-bit EFI handover address. */
115
116 /* Note in EFI mixed mode this now points to the correct 32-bit handover entry point, allowing a 64-bit
117 * kernel to be booted from a 32-bit sd-stub. */
118
119 handover_f handover = (handover_f) kernel;
120 handover(parent, ST, params);
121 }
122
123 EFI_STATUS linux_exec_efi_handover(
124 EFI_HANDLE parent,
125 const char16_t *cmdline,
126 const void *linux_buffer,
127 size_t linux_length,
128 const void *initrd_buffer,
129 size_t initrd_length) {
130
131 assert(parent);
132 assert(linux_buffer);
133 assert(initrd_buffer || initrd_length == 0);
134
135 if (linux_length < sizeof(BootParams))
136 return EFI_LOAD_ERROR;
137
138 const BootParams *image_params = (const BootParams *) linux_buffer;
139 if (image_params->hdr.header != SETUP_MAGIC || image_params->hdr.boot_flag != BOOT_FLAG_MAGIC)
140 return log_error_status(EFI_UNSUPPORTED, "Unsupported kernel image.");
141 if (image_params->hdr.version < SETUP_VERSION_2_11)
142 return log_error_status(EFI_UNSUPPORTED, "Kernel too old.");
143 if (!image_params->hdr.relocatable_kernel)
144 return log_error_status(EFI_UNSUPPORTED, "Kernel is not relocatable.");
145
146 /* The xloadflags were added in version 2.12+ of the boot protocol but the handover support predates
147 * that, so we cannot safety-check this for 2.11. */
148 if (image_params->hdr.version >= SETUP_VERSION_2_12 &&
149 !FLAGS_SET(image_params->hdr.xloadflags, XLF_EFI_HANDOVER))
150 return log_error_status(EFI_UNSUPPORTED, "Kernel does not support EFI handover protocol.");
151
152 bool can_4g = image_params->hdr.version >= SETUP_VERSION_2_12 &&
153 FLAGS_SET(image_params->hdr.xloadflags, XLF_CAN_BE_LOADED_ABOVE_4G);
154
155 /* There is no way to pass the high bits of code32_start. Newer kernels seems to handle this
156 * just fine, but older kernels will fail even if they otherwise have above 4G boot support. */
157 _cleanup_pages_ Pages linux_relocated = {};
158 if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) {
159 linux_relocated = xmalloc_pages(
160 AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX);
161 linux_buffer = memcpy(
162 PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length);
163 }
164
165 _cleanup_pages_ Pages initrd_relocated = {};
166 if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX) {
167 initrd_relocated = xmalloc_pages(
168 AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd_length), UINT32_MAX);
169 initrd_buffer = memcpy(
170 PHYSICAL_ADDRESS_TO_POINTER(initrd_relocated.addr),
171 initrd_buffer,
172 initrd_length);
173 }
174
175 _cleanup_pages_ Pages boot_params_page = xmalloc_pages(
176 can_4g ? AllocateAnyPages : AllocateMaxAddress,
177 EfiLoaderData,
178 EFI_SIZE_TO_PAGES(sizeof(BootParams)),
179 UINT32_MAX /* Below the 4G boundary */);
180 BootParams *boot_params = PHYSICAL_ADDRESS_TO_POINTER(boot_params_page.addr);
181 *boot_params = (BootParams){};
182
183 /* Setup size is determined by offset 0x0202 + byte value at offset 0x0201, which is the same as
184 * offset of the header field and the target from the jump field (which we split for this reason). */
185 memcpy(&boot_params->hdr,
186 &image_params->hdr,
187 offsetof(SetupHeader, header) + image_params->hdr.setup_size);
188
189 boot_params->hdr.type_of_loader = 0xff;
190
191 /* Spec says: For backwards compatibility, if the setup_sects field contains 0, the real value is 4. */
192 if (boot_params->hdr.setup_sects == 0)
193 boot_params->hdr.setup_sects = 4;
194
195 _cleanup_pages_ Pages cmdline_pages = {};
196 if (cmdline) {
197 size_t len = MIN(strlen16(cmdline), image_params->hdr.cmdline_size);
198
199 cmdline_pages = xmalloc_pages(
200 can_4g ? AllocateAnyPages : AllocateMaxAddress,
201 EfiLoaderData,
202 EFI_SIZE_TO_PAGES(len + 1),
203 CMDLINE_PTR_MAX);
204
205 /* Convert cmdline to ASCII. */
206 char *cmdline8 = PHYSICAL_ADDRESS_TO_POINTER(cmdline_pages.addr);
207 for (size_t i = 0; i < len; i++)
208 cmdline8[i] = cmdline[i] <= 0x7E ? cmdline[i] : ' ';
209 cmdline8[len] = '\0';
210
211 boot_params->hdr.cmd_line_ptr = (uint32_t) cmdline_pages.addr;
212 boot_params->ext_cmd_line_ptr = cmdline_pages.addr >> 32;
213 assert(can_4g || cmdline_pages.addr <= CMDLINE_PTR_MAX);
214 }
215
216 boot_params->hdr.ramdisk_image = (uintptr_t) initrd_buffer;
217 boot_params->ext_ramdisk_image = POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) >> 32;
218 boot_params->hdr.ramdisk_size = initrd_length;
219 boot_params->ext_ramdisk_size = ((uint64_t) initrd_length) >> 32;
220
221 log_wait();
222 linux_efi_handover(parent, (uintptr_t) linux_buffer, boot_params);
223 return EFI_LOAD_ERROR;
224 }