diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c index 1cebae34ece..5b062d409a6 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -16,80 +16,41 @@ #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); -} +#define STUB_PAYLOAD_GUID \ + { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } } -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; - - 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( @@ -98,11 +59,7 @@ 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); @@ -126,46 +83,36 @@ EFI_STATUS linux_exec( initrd_length); #endif if (err != EFI_SUCCESS) - return err; - /* sanity check */ - assert(kernel_size_of_image >= linux_length); + return log_error_status_stall(err, u"Bad kernel image: %r", err); - /* 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); + _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; + err = load_image(parent, linux_buffer, linux_length, &kernel_image); if (err != EFI_SUCCESS) - return err; + return log_error_status_stall(err, u"Error loading kernel image: %r", err); - /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */ + EFI_LOADED_IMAGE_PROTOCOL *loaded_image; + err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image); + if (err != EFI_SUCCESS) + 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); + } + + _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); - /* call the kernel */ - return kernel_entry(loaded_image_handle, ST); + 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); + } + + return log_error_status_stall(err, u"Error starting kernel image: %r", err); } diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 8bd1e985c91..a842c5c6791 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -378,5 +378,5 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); graphics_mode(false); - return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err); + return err; }