1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 * Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes
5 * initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with
6 * EFI LoadedImageProtocol.
8 * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
19 static EFI_LOADED_IMAGE
* loaded_image_free(EFI_LOADED_IMAGE
*img
) {
22 mfree(img
->LoadOptions
);
26 static EFI_STATUS
loaded_image_register(
27 const CHAR8
*cmdline
, UINTN cmdline_len
,
28 const void *linux_buffer
, UINTN linux_length
,
29 EFI_HANDLE
*ret_image
) {
31 EFI_LOADED_IMAGE
*loaded_image
= NULL
;
34 assert(cmdline
|| cmdline_len
> 0);
35 assert(linux_buffer
&& linux_length
> 0);
38 /* create and install new LoadedImage Protocol */
39 loaded_image
= xnew(EFI_LOADED_IMAGE
, 1);
40 *loaded_image
= (EFI_LOADED_IMAGE
) {
41 .ImageBase
= (void *) linux_buffer
,
42 .ImageSize
= linux_length
45 /* if a cmdline is set convert it to UCS2 */
47 loaded_image
->LoadOptions
= xstra_to_str(cmdline
);
48 loaded_image
->LoadOptionsSize
= strsize16(loaded_image
->LoadOptions
);
51 /* install a new LoadedImage protocol. ret_handle is a new image handle */
52 err
= BS
->InstallMultipleProtocolInterfaces(
54 &LoadedImageProtocol
, loaded_image
,
57 loaded_image
= loaded_image_free(loaded_image
);
62 static EFI_STATUS
loaded_image_unregister(EFI_HANDLE loaded_image_handle
) {
63 EFI_LOADED_IMAGE_PROTOCOL
*loaded_image
;
66 if (!loaded_image_handle
)
69 /* get the LoadedImage protocol that we allocated earlier */
70 err
= BS
->OpenProtocol(
71 loaded_image_handle
, &LoadedImageProtocol
, (void **) &loaded_image
,
72 NULL
, NULL
, EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
76 /* close the handle */
77 (void) BS
->CloseProtocol(loaded_image_handle
, &LoadedImageProtocol
, NULL
, NULL
);
78 err
= BS
->UninstallMultipleProtocolInterfaces(
80 &LoadedImageProtocol
, loaded_image
,
84 loaded_image_handle
= NULL
;
85 loaded_image
= loaded_image_free(loaded_image
);
90 static inline void cleanup_loaded_image(EFI_HANDLE
*loaded_image_handle
) {
91 (void) loaded_image_unregister(*loaded_image_handle
);
92 *loaded_image_handle
= NULL
;
95 /* struct to call cleanup_pages */
97 EFI_PHYSICAL_ADDRESS addr
;
101 static inline void cleanup_pages(struct pages
*p
) {
104 (void) BS
->FreePages(p
->addr
, p
->num
);
107 EFI_STATUS
linux_exec(
109 const CHAR8
*cmdline
, UINTN cmdline_len
,
110 const void *linux_buffer
, UINTN linux_length
,
111 const void *initrd_buffer
, UINTN initrd_length
) {
113 _cleanup_(cleanup_initrd
) EFI_HANDLE initrd_handle
= NULL
;
114 _cleanup_(cleanup_loaded_image
) EFI_HANDLE loaded_image_handle
= NULL
;
115 UINT32 kernel_alignment
, kernel_size_of_image
, kernel_entry_address
;
116 EFI_IMAGE_ENTRY_POINT kernel_entry
;
117 _cleanup_(cleanup_pages
) struct pages kernel
= {};
122 assert(cmdline
|| cmdline_len
== 0);
123 assert(linux_buffer
&& linux_length
> 0);
124 assert(initrd_buffer
|| initrd_length
== 0);
126 /* get the necessary fields from the PE header */
127 err
= pe_alignment_info(linux_buffer
, &kernel_entry_address
, &kernel_size_of_image
, &kernel_alignment
);
131 assert(kernel_size_of_image
>= linux_length
);
133 /* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
134 is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
135 be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
136 space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
137 Code, (static) Data and Headers.
139 Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
140 if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
142 /* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
143 kernel
.num
= EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image
, kernel_alignment
) + kernel_alignment
);
144 err
= BS
->AllocatePages(AllocateAnyPages
, EfiLoaderData
, kernel
.num
, &kernel
.addr
);
146 return EFI_OUT_OF_RESOURCES
;
147 new_buffer
= PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel
.addr
, kernel_alignment
));
148 memcpy(new_buffer
, linux_buffer
, linux_length
);
149 /* zero out rest of memory (probably not needed, but BSS section should be 0) */
150 memset((UINT8
*)new_buffer
+ linux_length
, 0, kernel_size_of_image
- linux_length
);
152 /* get the entry point inside the relocated kernel */
153 kernel_entry
= (EFI_IMAGE_ENTRY_POINT
) ((const UINT8
*)new_buffer
+ kernel_entry_address
);
155 /* register a LoadedImage Protocol in order to pass on the commandline */
156 err
= loaded_image_register(cmdline
, cmdline_len
, new_buffer
, linux_length
, &loaded_image_handle
);
160 /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
161 err
= initrd_register(initrd_buffer
, initrd_length
, &initrd_handle
);
165 /* call the kernel */
166 return kernel_entry(loaded_image_handle
, ST
);