]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/efi/linux.c
stub: Be explicit about EFI handover calling convention
[thirdparty/systemd.git] / src / boot / efi / linux.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
0fa2cac4 2
dc467928
MR
3/*
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.
7 *
8 * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
9 */
10
0fa2cac4
KS
11#include <efi.h>
12#include <efilib.h>
13
a6089431 14#include "initrd.h"
dc467928
MR
15#include "linux.h"
16#include "pe.h"
cf0fbc49 17#include "util.h"
0fa2cac4 18
b30a43df 19static EFI_LOADED_IMAGE_PROTOCOL *loaded_image_free(EFI_LOADED_IMAGE_PROTOCOL *img) {
dc467928
MR
20 if (!img)
21 return NULL;
22 mfree(img->LoadOptions);
23 return mfree(img);
24}
25
26static EFI_STATUS loaded_image_register(
07d0fde4 27 const char *cmdline, UINTN cmdline_len,
70cd15e9 28 const void *linux_buffer, UINTN linux_length,
dc467928
MR
29 EFI_HANDLE *ret_image) {
30
b30a43df 31 EFI_LOADED_IMAGE_PROTOCOL *loaded_image = NULL;
dc467928
MR
32 EFI_STATUS err;
33
34 assert(cmdline || cmdline_len > 0);
35 assert(linux_buffer && linux_length > 0);
36 assert(ret_image);
4287d083 37
dc467928 38 /* create and install new LoadedImage Protocol */
b30a43df
JJ
39 loaded_image = xnew(EFI_LOADED_IMAGE_PROTOCOL, 1);
40 *loaded_image = (EFI_LOADED_IMAGE_PROTOCOL) {
70cd15e9 41 .ImageBase = (void *) linux_buffer,
dc467928
MR
42 .ImageSize = linux_length
43 };
0fa2cac4 44
4f943415 45 /* if a cmdline is set convert it to UCS2 */
dc467928 46 if (cmdline) {
4f943415 47 loaded_image->LoadOptions = xstra_to_str(cmdline);
60c2af56 48 loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
dc467928 49 }
508df915 50
dc467928 51 /* install a new LoadedImage protocol. ret_handle is a new image handle */
12f32748 52 err = BS->InstallMultipleProtocolInterfaces(
dc467928
MR
53 ret_image,
54 &LoadedImageProtocol, loaded_image,
55 NULL);
2a5e4fe4 56 if (err != EFI_SUCCESS)
dc467928
MR
57 loaded_image = loaded_image_free(loaded_image);
58
59 return err;
60}
61
62static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) {
63 EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
64 EFI_STATUS err;
65
66 if (!loaded_image_handle)
67 return EFI_SUCCESS;
68
69 /* get the LoadedImage protocol that we allocated earlier */
12f32748 70 err = BS->OpenProtocol(
70cd15e9 71 loaded_image_handle, &LoadedImageProtocol, (void **) &loaded_image,
dc467928 72 NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2a5e4fe4 73 if (err != EFI_SUCCESS)
dc467928
MR
74 return err;
75
76 /* close the handle */
12f32748
JJ
77 (void) BS->CloseProtocol(loaded_image_handle, &LoadedImageProtocol, NULL, NULL);
78 err = BS->UninstallMultipleProtocolInterfaces(
dc467928
MR
79 loaded_image_handle,
80 &LoadedImageProtocol, loaded_image,
81 NULL);
2a5e4fe4 82 if (err != EFI_SUCCESS)
dc467928
MR
83 return err;
84 loaded_image_handle = NULL;
85 loaded_image = loaded_image_free(loaded_image);
86
87 return EFI_SUCCESS;
88}
89
dc467928
MR
90static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) {
91 (void) loaded_image_unregister(*loaded_image_handle);
92 *loaded_image_handle = NULL;
93}
94
a6089431
MR
95EFI_STATUS linux_exec(
96 EFI_HANDLE image,
07d0fde4 97 const char *cmdline, UINTN cmdline_len,
70cd15e9
JJ
98 const void *linux_buffer, UINTN linux_length,
99 const void *initrd_buffer, UINTN initrd_length) {
0d43ce52 100
dc467928
MR
101 _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
102 _cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL;
db4122d1 103 uint32_t kernel_alignment, kernel_size_of_image, kernel_entry_address;
dc467928 104 EFI_IMAGE_ENTRY_POINT kernel_entry;
70cd15e9 105 void *new_buffer;
0fa2cac4
KS
106 EFI_STATUS err;
107
508df915 108 assert(image);
a6089431 109 assert(cmdline || cmdline_len == 0);
dc467928 110 assert(linux_buffer && linux_length > 0);
a6089431 111 assert(initrd_buffer || initrd_length == 0);
508df915 112
dc467928
MR
113 /* get the necessary fields from the PE header */
114 err = pe_alignment_info(linux_buffer, &kernel_entry_address, &kernel_size_of_image, &kernel_alignment);
2a5e4fe4 115 if (err != EFI_SUCCESS)
0fa2cac4 116 return err;
dc467928
MR
117 /* sanity check */
118 assert(kernel_size_of_image >= linux_length);
119
120 /* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
121 is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
ba669952 122 be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
dc467928
MR
123 space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
124 Code, (static) Data and Headers.
125
126 Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
127 if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
128 */
129 /* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
09173c91
JJ
130 _cleanup_pages_ Pages kernel = xmalloc_pages(
131 AllocateAnyPages,
132 EfiLoaderCode,
133 EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment),
134 0);
dc467928 135 new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
bbc1f2ea 136 memcpy(new_buffer, linux_buffer, linux_length);
dc467928 137 /* zero out rest of memory (probably not needed, but BSS section should be 0) */
db4122d1 138 memset((uint8_t *)new_buffer + linux_length, 0, kernel_size_of_image - linux_length);
0fa2cac4 139
dc467928 140 /* get the entry point inside the relocated kernel */
db4122d1 141 kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const uint8_t *)new_buffer + kernel_entry_address);
0fa2cac4 142
dc467928
MR
143 /* register a LoadedImage Protocol in order to pass on the commandline */
144 err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle);
2a5e4fe4 145 if (err != EFI_SUCCESS)
dc467928 146 return err;
a6089431 147
dc467928 148 /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
a6089431 149 err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
2a5e4fe4 150 if (err != EFI_SUCCESS)
a6089431 151 return err;
dc467928
MR
152
153 /* call the kernel */
12f32748 154 return kernel_entry(loaded_image_handle, ST);
0fa2cac4 155}