blob: fb8cc7bcbe3c21fcfcc30ef19bd14d54625bda97 [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;
45
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020046/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020047 * get_initrd_fp() - Get initrd device path from a FilePathList device path
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020048 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020049 * @initrd_fp: the final initrd filepath
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020050 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020051 * Return: status code. Caller must free initrd_fp
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020052 */
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020053static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020054{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020055 struct efi_device_path *dp = NULL;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020056
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020057 /*
58 * if bootmgr is setup with and initrd, the device path will be
59 * in the FilePathList[] of our load options in Boot####.
60 * The first device path of the multi instance device path will
61 * start with a VenMedia and the initrds will follow.
62 *
63 * If the device path is not found return EFI_INVALID_PARAMETER.
64 * We can then use this specific return value and not install the
65 * protocol, while allowing the boot to continue
66 */
Heinrich Schuchardtefd90d72024-04-26 16:13:08 +020067 dp = efi_get_dp_from_boot(&efi_lf2_initrd_guid);
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020068 if (!dp)
69 return EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020070
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020071 *initrd_fp = dp;
72 return EFI_SUCCESS;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020073}
74
75/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020076 * efi_load_file2_initrd() - load initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020077 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +020078 * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL
79 * in order to load an initial RAM disk requested by the Linux kernel stub.
80 *
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020081 * See the UEFI spec for details.
82 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +020083 * @this: EFI_LOAD_FILE2_PROTOCOL instance
84 * @file_path: media device path of the file, "" in this case
85 * @boot_policy: must be false
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020086 * @buffer_size: size of allocated buffer
87 * @buffer: buffer to load the file
88 *
89 * Return: status code
90 */
91static efi_status_t EFIAPI
92efi_load_file2_initrd(struct efi_load_file_protocol *this,
93 struct efi_device_path *file_path, bool boot_policy,
94 efi_uintn_t *buffer_size, void *buffer)
95{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020096 struct efi_device_path *initrd_fp = NULL;
97 efi_status_t ret = EFI_NOT_FOUND;
98 struct efi_file_handle *f = NULL;
99 efi_uintn_t bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200100
101 EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
102 buffer_size, buffer);
103
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200104 if (!this || this != &efi_lf2_protocol ||
105 !buffer_size) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200106 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200107 goto out;
108 }
109
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200110 if (file_path->type != dp_lf2_handle.end.type ||
111 file_path->sub_type != dp_lf2_handle.end.sub_type) {
112 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200113 goto out;
114 }
115
116 if (boot_policy) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200117 ret = EFI_UNSUPPORTED;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200118 goto out;
119 }
120
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200121 ret = get_initrd_fp(&initrd_fp);
122 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200123 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200124
125 /* Open file */
126 f = efi_file_from_path(initrd_fp);
127 if (!f) {
128 log_err("Can't find initrd specified in Boot####\n");
129 ret = EFI_NOT_FOUND;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200130 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200131 }
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200132
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200133 /* Get file size */
134 ret = efi_file_size(f, &bs);
135 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200136 goto out;
137
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200138 if (!buffer || *buffer_size < bs) {
139 ret = EFI_BUFFER_TOO_SMALL;
140 *buffer_size = bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200141 } else {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200142 ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer));
143 *buffer_size = bs;
144 }
145
146out:
147 efi_free_pool(initrd_fp);
148 if (f)
149 EFI_CALL(f->close(f));
150 return EFI_EXIT(ret);
151}
152
153/**
154 * check_initrd() - Determine if the file defined as an initrd in Boot####
155 * load_options device path is present
156 *
157 * Return: status code
158 */
159static efi_status_t check_initrd(void)
160{
161 struct efi_device_path *initrd_fp = NULL;
162 struct efi_file_handle *f;
163 efi_status_t ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200164
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200165 ret = get_initrd_fp(&initrd_fp);
166 if (ret != EFI_SUCCESS)
167 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200168
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200169 /*
170 * If the file is not found, but the file path is set, return an error
171 * and trigger the bootmgr fallback
172 */
173 f = efi_file_from_path(initrd_fp);
174 if (!f) {
175 log_err("Can't find initrd specified in Boot####\n");
176 ret = EFI_NOT_FOUND;
177 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200178 }
179
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200180 EFI_CALL(f->close(f));
181
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200182out:
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200183 efi_free_pool(initrd_fp);
184 return ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200185}
186
187/**
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900188 * efi_initrd_deregister() - delete the handle for loading initial RAM disk
189 *
190 * This will delete the handle containing the Linux specific vendor device
191 * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd
192 *
193 * Return: status code
194 */
195efi_status_t efi_initrd_deregister(void)
196{
197 efi_status_t ret;
198
199 if (!efi_initrd_handle)
200 return EFI_SUCCESS;
201
202 ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle,
203 /* initramfs */
204 &efi_guid_device_path,
205 &dp_lf2_handle,
206 /* LOAD_FILE2 */
207 &efi_guid_load_file2_protocol,
208 &efi_lf2_protocol,
209 NULL);
210 efi_initrd_handle = NULL;
211
212 return ret;
213}
214
215/**
216 * efi_initrd_return_notify() - return to efibootmgr callback
217 *
218 * @event: the event for which this notification function is registered
219 * @context: event context
220 */
221static void EFIAPI efi_initrd_return_notify(struct efi_event *event,
222 void *context)
223{
224 efi_status_t ret;
225
226 EFI_ENTRY("%p, %p", event, context);
227 ret = efi_initrd_deregister();
228 EFI_EXIT(ret);
229}
230
231/**
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200232 * efi_initrd_register() - create handle for loading initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200233 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200234 * This function creates a new handle and installs a Linux specific vendor
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200235 * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200236 * to identify the handle and then calls the LoadFile service of the
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200237 * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk.
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200238 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200239 * Return: status code
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200240 */
241efi_status_t efi_initrd_register(void)
242{
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200243 efi_status_t ret;
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900244 struct efi_event *event;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200245
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200246 /*
247 * Allow the user to continue if Boot#### file path is not set for
248 * an initrd
249 */
250 ret = check_initrd();
251 if (ret == EFI_INVALID_PARAMETER)
252 return EFI_SUCCESS;
253 if (ret != EFI_SUCCESS)
254 return ret;
255
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300256 ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle,
257 /* initramfs */
258 &efi_guid_device_path, &dp_lf2_handle,
259 /* LOAD_FILE2 */
260 &efi_guid_load_file2_protocol,
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300261 &efi_lf2_protocol,
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300262 NULL);
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900263 if (ret != EFI_SUCCESS) {
264 log_err("installing EFI_LOAD_FILE2_PROTOCOL failed\n");
265 return ret;
266 }
Heinrich Schuchardta0ace872022-09-30 01:55:02 +0200267
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900268 ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
269 efi_initrd_return_notify, NULL,
270 &efi_guid_event_group_return_to_efibootmgr,
271 &event);
272 if (ret != EFI_SUCCESS)
273 log_err("Creating event failed\n");
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300274
275 return ret;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200276}