#include "pe.h"
#include "util.h"
-static EFI_LOADED_IMAGE_PROTOCOL *loaded_image_free(EFI_LOADED_IMAGE_PROTOCOL *img) {
- if (!img)
- return NULL;
- mfree(img->LoadOptions);
- return mfree(img);
-}
-
-static EFI_STATUS loaded_image_register(
- const char *cmdline, UINTN cmdline_len,
- const void *linux_buffer, UINTN linux_length,
- EFI_HANDLE *ret_image) {
-
- EFI_LOADED_IMAGE_PROTOCOL *loaded_image = NULL;
- EFI_STATUS err;
+#define STUB_PAYLOAD_GUID \
+ { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } }
- assert(cmdline || cmdline_len > 0);
- assert(linux_buffer && linux_length > 0);
+EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) {
+ assert(parent);
+ assert(source);
assert(ret_image);
- /* create and install new LoadedImage Protocol */
- loaded_image = xnew(EFI_LOADED_IMAGE_PROTOCOL, 1);
- *loaded_image = (EFI_LOADED_IMAGE_PROTOCOL) {
- .ImageBase = (void *) linux_buffer,
- .ImageSize = linux_length
+ /* We could pass a NULL device path, but it's nicer to provide something. */
+ struct {
+ VENDOR_DEVICE_PATH payload;
+ EFI_DEVICE_PATH end;
+ } _packed_ payload_device_path = {
+ .payload = {
+ .Header = {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_VENDOR_DP,
+ .Length = { sizeof(payload_device_path.payload), 0 },
+ },
+ .Guid = STUB_PAYLOAD_GUID,
+ },
+ .end = {
+ .Type = END_DEVICE_PATH_TYPE,
+ .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
+ .Length = { sizeof(payload_device_path.end), 0 },
+ },
};
- /* if a cmdline is set convert it to UCS2 */
- if (cmdline) {
- loaded_image->LoadOptions = xstra_to_str(cmdline);
- loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
- }
-
- /* install a new LoadedImage protocol. ret_handle is a new image handle */
- err = BS->InstallMultipleProtocolInterfaces(
- ret_image,
- &LoadedImageProtocol, loaded_image,
- NULL);
- if (err != EFI_SUCCESS)
- loaded_image = loaded_image_free(loaded_image);
-
- return err;
-}
-
-static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) {
- EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
- EFI_STATUS err;
-
- if (!loaded_image_handle)
- return EFI_SUCCESS;
-
- /* get the LoadedImage protocol that we allocated earlier */
- err = BS->OpenProtocol(
- loaded_image_handle, &LoadedImageProtocol, (void **) &loaded_image,
- NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
- if (err != EFI_SUCCESS)
- return err;
-
- /* close the handle */
- (void) BS->CloseProtocol(loaded_image_handle, &LoadedImageProtocol, NULL, NULL);
- err = BS->UninstallMultipleProtocolInterfaces(
- loaded_image_handle,
- &LoadedImageProtocol, loaded_image,
- NULL);
- if (err != EFI_SUCCESS)
- return err;
- loaded_image_handle = NULL;
- loaded_image = loaded_image_free(loaded_image);
-
- return EFI_SUCCESS;
-}
-
-static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) {
- (void) loaded_image_unregister(*loaded_image_handle);
- *loaded_image_handle = NULL;
+ return BS->LoadImage(
+ /*BootPolicy=*/false,
+ parent,
+ &payload_device_path.payload.Header,
+ (void *) source,
+ len,
+ ret_image);
}
EFI_STATUS linux_exec(
const void *linux_buffer, UINTN linux_length,
const void *initrd_buffer, UINTN initrd_length) {
- _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
- _cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL;
- uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address;
- EFI_IMAGE_ENTRY_POINT kernel_entry;
- void *new_buffer;
+ uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address = 0;
EFI_STATUS err;
assert(parent);
initrd_length);
#endif
if (err != EFI_SUCCESS)
- return err;
- /* sanity check */
- assert(kernel_size_of_image >= linux_length);
-
- /* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
- is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
- be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
- space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
- Code, (static) Data and Headers.
-
- Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
- if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
- */
- /* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
- _cleanup_pages_ Pages kernel = xmalloc_pages(
- AllocateAnyPages,
- EfiLoaderCode,
- EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment),
- 0);
- new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
- if (!new_buffer) /* Silence gcc 11.2.0, assert(new_buffer) doesn't work. */
- return EFI_OUT_OF_RESOURCES;
-
- memcpy(new_buffer, linux_buffer, linux_length);
- /* zero out rest of memory (probably not needed, but BSS section should be 0) */
- memset((uint8_t *)new_buffer + linux_length, 0, kernel_size_of_image - linux_length);
-
- /* get the entry point inside the relocated kernel */
- kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *)new_buffer + kernel_entry_address);
-
- /* register a LoadedImage Protocol in order to pass on the commandline */
- err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle);
+ return log_error_status_stall(err, u"Bad kernel image: %r", err);
+
+ _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL;
+ err = load_image(parent, linux_buffer, linux_length, &kernel_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, u"Error loading kernel image: %r", err);
+
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
+ err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image);
if (err != EFI_SUCCESS)
- return err;
+ return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err);
+
+ if (cmdline) {
+ loaded_image->LoadOptions = xstra_to_str(cmdline);
+ loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
+ }
- /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
+ _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
if (err != EFI_SUCCESS)
- return err;
+ return log_error_status_stall(err, u"Error registering initrd: %r", err);
+
+ err = BS->StartImage(kernel_image, NULL, NULL);
+
+ /* Try calling the kernel compat entry point if one exists. */
+ if (err == EFI_UNSUPPORTED && kernel_entry_address > 0) {
+ EFI_IMAGE_ENTRY_POINT compat_entry =
+ (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + kernel_entry_address);
+ err = compat_entry(kernel_image, ST);
+ }
- /* call the kernel */
- return kernel_entry(loaded_image_handle, ST);
+ return log_error_status_stall(err, u"Error starting kernel image: %r", err);
}