blob: 67d1f75d525f1e32247e20946516bfffa4cecb22 [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
Ilias Apalodimas3510ba72020-02-21 09:55:45 +02007#include <efi_loader.h>
8#include <efi_load_initrd.h>
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +02009#include <efi_variable.h>
Ilias Apalodimasdaa924a2020-12-31 12:33:23 +020010#include <fs.h>
11#include <malloc.h>
12#include <mapmem.h>
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020013
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020014static efi_status_t EFIAPI
15efi_load_file2_initrd(struct efi_load_file_protocol *this,
16 struct efi_device_path *file_path, bool boot_policy,
17 efi_uintn_t *buffer_size, void *buffer);
18
19static const struct efi_load_file_protocol efi_lf2_protocol = {
20 .load_file = efi_load_file2_initrd,
21};
22
23/*
24 * Device path defined by Linux to identify the handle providing the
25 * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
26 */
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020027static const struct efi_initrd_dp dp_lf2_handle = {
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020028 .vendor = {
29 {
30 DEVICE_PATH_TYPE_MEDIA_DEVICE,
31 DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020032 sizeof(dp_lf2_handle.vendor),
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020033 },
34 EFI_INITRD_MEDIA_GUID,
35 },
36 .end = {
37 DEVICE_PATH_TYPE_END,
38 DEVICE_PATH_SUB_TYPE_END,
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020039 sizeof(dp_lf2_handle.end),
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020040 }
41};
42
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020043static efi_handle_t efi_initrd_handle;
44
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020045/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020046 * get_initrd_fp() - Get initrd device path from a FilePathList device path
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020047 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020048 * @initrd_fp: the final initrd filepath
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020049 *
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020050 * Return: status code. Caller must free initrd_fp
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020051 */
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020052static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020053{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020054 struct efi_device_path *dp = NULL;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020055
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020056 /*
57 * if bootmgr is setup with and initrd, the device path will be
58 * in the FilePathList[] of our load options in Boot####.
59 * The first device path of the multi instance device path will
60 * start with a VenMedia and the initrds will follow.
61 *
62 * If the device path is not found return EFI_INVALID_PARAMETER.
63 * We can then use this specific return value and not install the
64 * protocol, while allowing the boot to continue
65 */
Heinrich Schuchardt6c405cb2021-10-15 02:33:33 +020066 dp = efi_get_dp_from_boot(efi_lf2_initrd_guid);
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020067 if (!dp)
68 return EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020069
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020070 *initrd_fp = dp;
71 return EFI_SUCCESS;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020072}
73
74/**
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020075 * efi_load_file2_initrd() - load initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020076 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +020077 * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL
78 * in order to load an initial RAM disk requested by the Linux kernel stub.
79 *
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020080 * See the UEFI spec for details.
81 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +020082 * @this: EFI_LOAD_FILE2_PROTOCOL instance
83 * @file_path: media device path of the file, "" in this case
84 * @boot_policy: must be false
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020085 * @buffer_size: size of allocated buffer
86 * @buffer: buffer to load the file
87 *
88 * Return: status code
89 */
90static efi_status_t EFIAPI
91efi_load_file2_initrd(struct efi_load_file_protocol *this,
92 struct efi_device_path *file_path, bool boot_policy,
93 efi_uintn_t *buffer_size, void *buffer)
94{
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +020095 struct efi_device_path *initrd_fp = NULL;
96 efi_status_t ret = EFI_NOT_FOUND;
97 struct efi_file_handle *f = NULL;
98 efi_uintn_t bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +020099
100 EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
101 buffer_size, buffer);
102
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200103 if (!this || this != &efi_lf2_protocol ||
104 !buffer_size) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200105 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200106 goto out;
107 }
108
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200109 if (file_path->type != dp_lf2_handle.end.type ||
110 file_path->sub_type != dp_lf2_handle.end.sub_type) {
111 ret = EFI_INVALID_PARAMETER;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200112 goto out;
113 }
114
115 if (boot_policy) {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200116 ret = EFI_UNSUPPORTED;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200117 goto out;
118 }
119
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200120 ret = get_initrd_fp(&initrd_fp);
121 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200122 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200123
124 /* Open file */
125 f = efi_file_from_path(initrd_fp);
126 if (!f) {
127 log_err("Can't find initrd specified in Boot####\n");
128 ret = EFI_NOT_FOUND;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200129 goto out;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200130 }
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200131
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200132 /* Get file size */
133 ret = efi_file_size(f, &bs);
134 if (ret != EFI_SUCCESS)
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200135 goto out;
136
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200137 if (!buffer || *buffer_size < bs) {
138 ret = EFI_BUFFER_TOO_SMALL;
139 *buffer_size = bs;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200140 } else {
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200141 ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer));
142 *buffer_size = bs;
143 }
144
145out:
146 efi_free_pool(initrd_fp);
147 if (f)
148 EFI_CALL(f->close(f));
149 return EFI_EXIT(ret);
150}
151
152/**
153 * check_initrd() - Determine if the file defined as an initrd in Boot####
154 * load_options device path is present
155 *
156 * Return: status code
157 */
158static efi_status_t check_initrd(void)
159{
160 struct efi_device_path *initrd_fp = NULL;
161 struct efi_file_handle *f;
162 efi_status_t ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200163
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200164 ret = get_initrd_fp(&initrd_fp);
165 if (ret != EFI_SUCCESS)
166 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200167
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200168 /*
169 * If the file is not found, but the file path is set, return an error
170 * and trigger the bootmgr fallback
171 */
172 f = efi_file_from_path(initrd_fp);
173 if (!f) {
174 log_err("Can't find initrd specified in Boot####\n");
175 ret = EFI_NOT_FOUND;
176 goto out;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200177 }
178
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200179 EFI_CALL(f->close(f));
180
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200181out:
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200182 efi_free_pool(initrd_fp);
183 return ret;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200184}
185
186/**
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900187 * efi_initrd_deregister() - delete the handle for loading initial RAM disk
188 *
189 * This will delete the handle containing the Linux specific vendor device
190 * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd
191 *
192 * Return: status code
193 */
194efi_status_t efi_initrd_deregister(void)
195{
196 efi_status_t ret;
197
198 if (!efi_initrd_handle)
199 return EFI_SUCCESS;
200
201 ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle,
202 /* initramfs */
203 &efi_guid_device_path,
204 &dp_lf2_handle,
205 /* LOAD_FILE2 */
206 &efi_guid_load_file2_protocol,
207 &efi_lf2_protocol,
208 NULL);
209 efi_initrd_handle = NULL;
210
211 return ret;
212}
213
214/**
215 * efi_initrd_return_notify() - return to efibootmgr callback
216 *
217 * @event: the event for which this notification function is registered
218 * @context: event context
219 */
220static void EFIAPI efi_initrd_return_notify(struct efi_event *event,
221 void *context)
222{
223 efi_status_t ret;
224
225 EFI_ENTRY("%p, %p", event, context);
226 ret = efi_initrd_deregister();
227 EFI_EXIT(ret);
228}
229
230/**
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200231 * efi_initrd_register() - create handle for loading initial RAM disk
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200232 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200233 * This function creates a new handle and installs a Linux specific vendor
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200234 * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200235 * to identify the handle and then calls the LoadFile service of the
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200236 * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk.
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200237 *
Heinrich Schuchardtd1d89332020-10-03 12:44:31 +0200238 * Return: status code
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200239 */
240efi_status_t efi_initrd_register(void)
241{
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200242 efi_status_t ret;
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900243 struct efi_event *event;
Ilias Apalodimas3510ba72020-02-21 09:55:45 +0200244
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200245 /*
246 * Allow the user to continue if Boot#### file path is not set for
247 * an initrd
248 */
249 ret = check_initrd();
250 if (ret == EFI_INVALID_PARAMETER)
251 return EFI_SUCCESS;
252 if (ret != EFI_SUCCESS)
253 return ret;
254
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300255 ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle,
256 /* initramfs */
257 &efi_guid_device_path, &dp_lf2_handle,
258 /* LOAD_FILE2 */
259 &efi_guid_load_file2_protocol,
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300260 &efi_lf2_protocol,
Ilias Apalodimas8ac0ebe2022-10-06 16:08:46 +0300261 NULL);
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900262 if (ret != EFI_SUCCESS) {
263 log_err("installing EFI_LOAD_FILE2_PROTOCOL failed\n");
264 return ret;
265 }
Heinrich Schuchardta0ace872022-09-30 01:55:02 +0200266
Masahisa Kojima8c812d32023-12-04 13:30:14 +0900267 ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
268 efi_initrd_return_notify, NULL,
269 &efi_guid_event_group_return_to_efibootmgr,
270 &event);
271 if (ret != EFI_SUCCESS)
272 log_err("Creating event failed\n");
Ilias Apalodimas7e052792022-10-16 11:36:32 +0300273
274 return ret;
Ilias Apalodimasb307e3d2021-03-17 21:55:00 +0200275}