1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes
* initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with
* EFI LoadedImageProtocol.
*
* This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
*/
#include <efi.h>
#include <efilib.h>
#include "initrd.h"
#include "linux.h"
#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;
assert(cmdline || cmdline_len > 0);
assert(linux_buffer && linux_length > 0);
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
};
/* 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;
}
/* struct to call cleanup_pages */
struct pages {
EFI_PHYSICAL_ADDRESS addr;
UINTN num;
};
static inline void cleanup_pages(struct pages *p) {
if (p->addr == 0)
return;
(void) BS->FreePages(p->addr, p->num);
}
EFI_STATUS linux_exec(
EFI_HANDLE image,
const char *cmdline, UINTN cmdline_len,
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;
_cleanup_(cleanup_pages) struct pages kernel = {};
void *new_buffer;
EFI_STATUS err;
assert(image);
assert(cmdline || cmdline_len == 0);
assert(linux_buffer && linux_length > 0);
assert(initrd_buffer || initrd_length == 0);
/* get the necessary fields from the PE header */
err = pe_alignment_info(linux_buffer, &kernel_entry_address, &kernel_size_of_image, &kernel_alignment);
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 */
kernel.num = EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment);
err = BS->AllocatePages(AllocateAnyPages, EfiLoaderCode, kernel.num, &kernel.addr);
if (err != EFI_SUCCESS)
return EFI_OUT_OF_RESOURCES;
new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
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);
if (err != EFI_SUCCESS)
return err;
/* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
if (err != EFI_SUCCESS)
return err;
/* call the kernel */
return kernel_entry(loaded_image_handle, ST);
}
|