]>
Commit | Line | Data |
---|---|---|
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 | |
dc467928 MR |
19 | static EFI_LOADED_IMAGE * loaded_image_free(EFI_LOADED_IMAGE *img) { |
20 | if (!img) | |
21 | return NULL; | |
22 | mfree(img->LoadOptions); | |
23 | return mfree(img); | |
24 | } | |
25 | ||
26 | static EFI_STATUS loaded_image_register( | |
27 | const CHAR8 *cmdline, UINTN cmdline_len, | |
70cd15e9 | 28 | const void *linux_buffer, UINTN linux_length, |
dc467928 MR |
29 | EFI_HANDLE *ret_image) { |
30 | ||
31 | EFI_LOADED_IMAGE *loaded_image = NULL; | |
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 */ |
0a15a824 | 39 | loaded_image = xnew(EFI_LOADED_IMAGE, 1); |
dc467928 | 40 | *loaded_image = (EFI_LOADED_IMAGE) { |
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); | |
56 | if (EFI_ERROR(err)) | |
57 | loaded_image = loaded_image_free(loaded_image); | |
58 | ||
59 | return err; | |
60 | } | |
61 | ||
62 | static 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 MR |
72 | NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); |
73 | if (EFI_ERROR(err)) | |
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); | |
82 | if (EFI_ERROR(err)) | |
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 |
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; | |
93 | } | |
94 | ||
95 | /* struct to call cleanup_pages */ | |
96 | struct pages { | |
97 | EFI_PHYSICAL_ADDRESS addr; | |
98 | UINTN num; | |
99 | }; | |
100 | ||
101 | static inline void cleanup_pages(struct pages *p) { | |
102 | if (p->addr == 0) | |
103 | return; | |
12f32748 | 104 | (void) BS->FreePages(p->addr, p->num); |
0fa2cac4 | 105 | } |
0fa2cac4 | 106 | |
a6089431 MR |
107 | EFI_STATUS linux_exec( |
108 | EFI_HANDLE image, | |
109 | const CHAR8 *cmdline, UINTN cmdline_len, | |
70cd15e9 JJ |
110 | const void *linux_buffer, UINTN linux_length, |
111 | const void *initrd_buffer, UINTN initrd_length) { | |
0d43ce52 | 112 | |
dc467928 MR |
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 = {}; | |
70cd15e9 | 118 | void *new_buffer; |
0fa2cac4 KS |
119 | EFI_STATUS err; |
120 | ||
508df915 | 121 | assert(image); |
a6089431 | 122 | assert(cmdline || cmdline_len == 0); |
dc467928 | 123 | assert(linux_buffer && linux_length > 0); |
a6089431 | 124 | assert(initrd_buffer || initrd_length == 0); |
508df915 | 125 | |
dc467928 MR |
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); | |
0fa2cac4 KS |
128 | if (EFI_ERROR(err)) |
129 | return err; | |
dc467928 MR |
130 | /* sanity check */ |
131 | assert(kernel_size_of_image >= linux_length); | |
132 | ||
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 | |
ba669952 | 135 | be in a memory segment that's SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has |
dc467928 MR |
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. | |
138 | ||
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. | |
141 | */ | |
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); | |
12f32748 | 144 | err = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, kernel.num, &kernel.addr); |
dc467928 MR |
145 | if (EFI_ERROR(err)) |
146 | return EFI_OUT_OF_RESOURCES; | |
147 | new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment)); | |
bbc1f2ea | 148 | memcpy(new_buffer, linux_buffer, linux_length); |
dc467928 | 149 | /* zero out rest of memory (probably not needed, but BSS section should be 0) */ |
bbc1f2ea | 150 | memset((UINT8 *)new_buffer + linux_length, 0, kernel_size_of_image - linux_length); |
0fa2cac4 | 151 | |
dc467928 MR |
152 | /* get the entry point inside the relocated kernel */ |
153 | kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const UINT8 *)new_buffer + kernel_entry_address); | |
0fa2cac4 | 154 | |
dc467928 MR |
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); | |
157 | if (EFI_ERROR(err)) | |
158 | return err; | |
a6089431 | 159 | |
dc467928 | 160 | /* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */ |
a6089431 MR |
161 | err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); |
162 | if (EFI_ERROR(err)) | |
163 | return err; | |
dc467928 MR |
164 | |
165 | /* call the kernel */ | |
12f32748 | 166 | return kernel_entry(loaded_image_handle, ST); |
0fa2cac4 | 167 | } |