blob: b5d58943a805a688eca7e9f8d9a251a5d74cdd08 [file] [log] [blame]
Ilias Apalodimas3510ba72020-02-21 09:55:45 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (c) 2020, Linaro Limited
4 */
5
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +02006#define LOG_CATEGORY LOGC_EFI
Heinrich Schuchardt955a3212025-01-16 20:26:59 +01007
Ilias Apalodimas3510ba72020-02-21 09:55:45 +02008#include <efi_loader.h>
9#include <efi_load_initrd.h>
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020010#include <efi_variable.h>
Ilias Apalodimasdaa924a2020-12-31 12:33:23 +020011#include <fs.h>
12#include <malloc.h>
13#include <mapmem.h>
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020014
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020015static efi_status_t EFIAPI
16efi_load_file2_initrd(struct efi_load_file_protocol *this,
17 struct efi_device_path *file_path, bool boot_policy,
18 efi_uintn_t *buffer_size, void *buffer);
19
20static const struct efi_load_file_protocol efi_lf2_protocol = {
21 .load_file = efi_load_file2_initrd,
22};
23
24/*
25 * Device path defined by Linux to identify the handle providing the
26 * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
27 */
Heinrich Schuchardt40da9e62024-06-10 10:00:01 +020028static const struct efi_lo_dp_prefix dp_lf2_handle = {
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020029 .vendor = {
30 {
31 DEVICE_PATH_TYPE_MEDIA_DEVICE,
32 DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020033 sizeof(dp_lf2_handle.vendor),
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020034 },
35 EFI_INITRD_MEDIA_GUID,
36 },
37 .end = {
38 DEVICE_PATH_TYPE_END,
39 DEVICE_PATH_SUB_TYPE_END,
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020040 sizeof(dp_lf2_handle.end),
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020041 }
42};
43
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020044static efi_handle_t efi_initrd_handle;
Adriano Cordova02d049d2025-03-19 11:44:59 -030045static struct efi_device_path *efi_initrd_dp;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020046
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020047/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020048 * get_initrd_fp() - Get initrd device path from a FilePathList device path
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020049 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020050 * @initrd_fp: the final initrd filepath
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020051 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020052 * Return: status code. Caller must free initrd_fp
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020053 */
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020054static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020055{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020056 struct efi_device_path *dp = NULL;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020057
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020058 /*
59 * if bootmgr is setup with and initrd, the device path will be
60 * in the FilePathList[] of our load options in Boot####.
61 * The first device path of the multi instance device path will
62 * start with a VenMedia and the initrds will follow.
63 *
64 * If the device path is not found return EFI_INVALID_PARAMETER.
65 * We can then use this specific return value and not install the
66 * protocol, while allowing the boot to continue
67 */
Heinrich Schuchardtefd90d72024-04-26 16:13:08 +020068 dp = efi_get_dp_from_boot(&efi_lf2_initrd_guid);
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020069 if (!dp)
70 return EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020071
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020072 *initrd_fp = dp;
73 return EFI_SUCCESS;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020074}
75
76/**
Adriano Cordova02d049d2025-03-19 11:44:59 -030077 * efi_initrd_from_mem() - load initial RAM disk from memory
78 *
79 * This function copies the initrd from the memory mapped device
80 * path pointed to by efi_initrd_dp
81 *
82 * @buffer_size: size of allocated buffer
83 * @buffer: buffer to load the file
84 *
85 * Return: status code
86 */
87static efi_status_t efi_initrd_from_mem(efi_uintn_t *buffer_size, void *buffer)
88{
89 efi_status_t ret = EFI_NOT_FOUND;
90 efi_uintn_t bs;
91 struct efi_device_path_memory *mdp;
92
93 mdp = (struct efi_device_path_memory *)efi_initrd_dp;
94 if (!mdp)
95 return ret;
96
97 bs = mdp->end_address - mdp->start_address;
98
99 if (!buffer || *buffer_size < bs) {
100 ret = EFI_BUFFER_TOO_SMALL;
101 *buffer_size = bs;
102 } else {
103 memcpy(buffer, (void *)(uintptr_t)mdp->start_address, bs);
104 *buffer_size = bs;
105 ret = EFI_SUCCESS;
106 }
107
108 return ret;
109}
110
111/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200112 * efi_load_file2_initrd() - load initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200113 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200114 * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL
115 * in order to load an initial RAM disk requested by the Linux kernel stub.
116 *
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200117 * See the UEFI spec for details.
118 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200119 * @this: EFI_LOAD_FILE2_PROTOCOL instance
120 * @file_path: media device path of the file, "" in this case
121 * @boot_policy: must be false
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200122 * @buffer_size: size of allocated buffer
123 * @buffer: buffer to load the file
124 *
125 * Return: status code
126 */
127static efi_status_t EFIAPI
128efi_load_file2_initrd(struct efi_load_file_protocol *this,
129 struct efi_device_path *file_path, bool boot_policy,
130 efi_uintn_t *buffer_size, void *buffer)
131{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200132 struct efi_device_path *initrd_fp = NULL;
133 efi_status_t ret = EFI_NOT_FOUND;
134 struct efi_file_handle *f = NULL;
135 efi_uintn_t bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200136
137 EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
138 buffer_size, buffer);
139
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200140 if (!this || this != &efi_lf2_protocol ||
141 !buffer_size) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200142 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200143 goto out;
144 }
145
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200146 if (file_path->type != dp_lf2_handle.end.type ||
147 file_path->sub_type != dp_lf2_handle.end.sub_type) {
148 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200149 goto out;
150 }
151
152 if (boot_policy) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200153 ret = EFI_UNSUPPORTED;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200154 goto out;
155 }
156
Adriano Cordova02d049d2025-03-19 11:44:59 -0300157 if (efi_initrd_dp)
158 return EFI_EXIT(efi_initrd_from_mem(buffer_size, buffer));
159
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200160 ret = get_initrd_fp(&initrd_fp);
161 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200162 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200163
164 /* Open file */
165 f = efi_file_from_path(initrd_fp);
166 if (!f) {
167 log_err("Can't find initrd specified in Boot####\n");
168 ret = EFI_NOT_FOUND;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200169 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200170 }
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200171
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200172 /* Get file size */
173 ret = efi_file_size(f, &bs);
174 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200175 goto out;
176
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200177 if (!buffer || *buffer_size < bs) {
178 ret = EFI_BUFFER_TOO_SMALL;
179 *buffer_size = bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200180 } else {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200181 ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer));
182 *buffer_size = bs;
183 }
184
185out:
186 efi_free_pool(initrd_fp);
187 if (f)
188 EFI_CALL(f->close(f));
189 return EFI_EXIT(ret);
190}
191
192/**
193 * check_initrd() - Determine if the file defined as an initrd in Boot####
194 * load_options device path is present
195 *
196 * Return: status code
197 */
198static efi_status_t check_initrd(void)
199{
200 struct efi_device_path *initrd_fp = NULL;
201 struct efi_file_handle *f;
202 efi_status_t ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200203
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200204 ret = get_initrd_fp(&initrd_fp);
205 if (ret != EFI_SUCCESS)
206 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200207
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200208 /*
209 * If the file is not found, but the file path is set, return an error
210 * and trigger the bootmgr fallback
211 */
212 f = efi_file_from_path(initrd_fp);
213 if (!f) {
214 log_err("Can't find initrd specified in Boot####\n");
215 ret = EFI_NOT_FOUND;
216 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200217 }
218
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200219 EFI_CALL(f->close(f));
220
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200221out:
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200222 efi_free_pool(initrd_fp);
223 return ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200224}
225
226/**
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900227 * efi_initrd_deregister() - delete the handle for loading initial RAM disk
228 *
229 * This will delete the handle containing the Linux specific vendor device
230 * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd
231 *
232 * Return: status code
233 */
234efi_status_t efi_initrd_deregister(void)
235{
236 efi_status_t ret;
237
238 if (!efi_initrd_handle)
239 return EFI_SUCCESS;
240
241 ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle,
242 /* initramfs */
243 &efi_guid_device_path,
244 &dp_lf2_handle,
245 /* LOAD_FILE2 */
246 &efi_guid_load_file2_protocol,
247 &efi_lf2_protocol,
248 NULL);
249 efi_initrd_handle = NULL;
250
Adriano Cordova02d049d2025-03-19 11:44:59 -0300251 efi_free_pool(efi_initrd_dp);
252 efi_initrd_dp = NULL;
253
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900254 return ret;
255}
256
257/**
258 * efi_initrd_return_notify() - return to efibootmgr callback
259 *
260 * @event: the event for which this notification function is registered
261 * @context: event context
262 */
263static void EFIAPI efi_initrd_return_notify(struct efi_event *event,
264 void *context)
265{
266 efi_status_t ret;
267
268 EFI_ENTRY("%p, %p", event, context);
269 ret = efi_initrd_deregister();
270 EFI_EXIT(ret);
271}
272
273/**
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200274 * efi_initrd_register() - create handle for loading initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200275 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200276 * This function creates a new handle and installs a Linux specific vendor
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200277 * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200278 * to identify the handle and then calls the LoadFile service of the
Adriano Cordova02d049d2025-03-19 11:44:59 -0300279 * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk. If dp_initrd is
280 * not provided, the initrd will be taken from the BootCurrent variable
281 *
282 * @dp_initrd: optional device path containing an initrd
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200283 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200284 * Return: status code
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200285 */
Adriano Cordova02d049d2025-03-19 11:44:59 -0300286efi_status_t efi_initrd_register(struct efi_device_path *dp_initrd)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200287{
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200288 efi_status_t ret;
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900289 struct efi_event *event;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200290
Adriano Cordova02d049d2025-03-19 11:44:59 -0300291 if (dp_initrd) {
292 efi_initrd_dp = dp_initrd;
293 } else {
294 /*
295 * Allow the user to continue if Boot#### file path is not set for
296 * an initrd
297 */
298 ret = check_initrd();
299 if (ret == EFI_INVALID_PARAMETER)
300 return EFI_SUCCESS;
301 if (ret != EFI_SUCCESS)
302 return ret;
303 }
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200304
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300305 ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle,
306 /* initramfs */
307 &efi_guid_device_path, &dp_lf2_handle,
308 /* LOAD_FILE2 */
309 &efi_guid_load_file2_protocol,
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300310 &efi_lf2_protocol,
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300311 NULL);
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900312 if (ret != EFI_SUCCESS) {
313 log_err("installing EFI_LOAD_FILE2_PROTOCOL failed\n");
314 return ret;
315 }
Heinrich Schuchardta0ace872022-09-30 01:55:02 +0200316
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900317 ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
318 efi_initrd_return_notify, NULL,
319 &efi_guid_event_group_return_to_efibootmgr,
320 &event);
321 if (ret != EFI_SUCCESS)
322 log_err("Creating event failed\n");
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300323
324 return ret;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200325}