~funderscore blog cgit wiki get in touch
aboutsummaryrefslogtreecommitdiff
blob: 193433782c246fbea73926bb4f6b9bff00d80fdb (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2020, Linaro Limited
 */

#define LOG_CATEGORY LOGC_EFI
#include <common.h>
#include <efi_loader.h>
#include <efi_load_initrd.h>
#include <efi_variable.h>
#include <fs.h>
#include <malloc.h>
#include <mapmem.h>

static efi_status_t EFIAPI
efi_load_file2_initrd(struct efi_load_file_protocol *this,
		      struct efi_device_path *file_path, bool boot_policy,
		      efi_uintn_t *buffer_size, void *buffer);

static const struct efi_load_file_protocol efi_lf2_protocol = {
	.load_file = efi_load_file2_initrd,
};

/*
 * Device path defined by Linux to identify the handle providing the
 * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
 */
static const struct efi_initrd_dp dp_lf2_handle = {
	.vendor = {
		{
		   DEVICE_PATH_TYPE_MEDIA_DEVICE,
		   DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
		   sizeof(dp_lf2_handle.vendor),
		},
		EFI_INITRD_MEDIA_GUID,
	},
	.end = {
		DEVICE_PATH_TYPE_END,
		DEVICE_PATH_SUB_TYPE_END,
		sizeof(dp_lf2_handle.end),
	}
};

static efi_handle_t efi_initrd_handle;

/**
 * get_initrd_fp() - Get initrd device path from a FilePathList device path
 *
 * @initrd_fp:	the final initrd filepath
 *
 * Return:	status code. Caller must free initrd_fp
 */
static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp)
{
	struct efi_device_path *dp = NULL;

	/*
	 * if bootmgr is setup with and initrd, the device path will be
	 * in the FilePathList[] of our load options in Boot####.
	 * The first device path of the multi instance device path will
	 * start with a VenMedia and the initrds will follow.
	 *
	 * If the device path is not found return EFI_INVALID_PARAMETER.
	 * We can then use this specific return value and not install the
	 * protocol, while allowing the boot to continue
	 */
	dp = efi_get_dp_from_boot(efi_lf2_initrd_guid);
	if (!dp)
		return EFI_INVALID_PARAMETER;

	*initrd_fp = dp;
	return EFI_SUCCESS;
}

/**
 * efi_load_file2_initrd() - load initial RAM disk
 *
 * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL
 * in order to load an initial RAM disk requested by the Linux kernel stub.
 *
 * See the UEFI spec for details.
 *
 * @this:			EFI_LOAD_FILE2_PROTOCOL instance
 * @file_path:			media device path of the file, "" in this case
 * @boot_policy:		must be false
 * @buffer_size:		size of allocated buffer
 * @buffer:			buffer to load the file
 *
 * Return:			status code
 */
static efi_status_t EFIAPI
efi_load_file2_initrd(struct efi_load_file_protocol *this,
		      struct efi_device_path *file_path, bool boot_policy,
		      efi_uintn_t *buffer_size, void *buffer)
{
	struct efi_device_path *initrd_fp = NULL;
	efi_status_t ret = EFI_NOT_FOUND;
	struct efi_file_handle *f = NULL;
	efi_uintn_t bs;

	EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
		  buffer_size, buffer);

	if (!this || this != &efi_lf2_protocol ||
	    !buffer_size) {
		ret = EFI_INVALID_PARAMETER;
		goto out;
	}

	if (file_path->type != dp_lf2_handle.end.type ||
	    file_path->sub_type != dp_lf2_handle.end.sub_type) {
		ret = EFI_INVALID_PARAMETER;
		goto out;
	}

	if (boot_policy) {
		ret = EFI_UNSUPPORTED;
		goto out;
	}

	ret = get_initrd_fp(&initrd_fp);
	if (ret != EFI_SUCCESS)
		goto out;

	/* Open file */
	f = efi_file_from_path(initrd_fp);
	if (!f) {
		log_err("Can't find initrd specified in Boot####\n");
		ret = EFI_NOT_FOUND;
		goto out;
	}

	/* Get file size */
	ret = efi_file_size(f, &bs);
	if (ret != EFI_SUCCESS)
		goto out;

	if (!buffer || *buffer_size < bs) {
		ret = EFI_BUFFER_TOO_SMALL;
		*buffer_size = bs;
	} else {
		ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer));
		*buffer_size = bs;
	}

out:
	efi_free_pool(initrd_fp);
	if (f)
		EFI_CALL(f->close(f));
	return EFI_EXIT(ret);
}

/**
 * check_initrd() - Determine if the file defined as an initrd in Boot####
 *		    load_options device path is present
 *
 * Return:	status code
 */
static efi_status_t check_initrd(void)
{
	struct efi_device_path *initrd_fp = NULL;
	struct efi_file_handle *f;
	efi_status_t ret;

	ret = get_initrd_fp(&initrd_fp);
	if (ret != EFI_SUCCESS)
		goto out;

	/*
	 * If the file is not found, but the file path is set, return an error
	 * and trigger the bootmgr fallback
	 */
	f = efi_file_from_path(initrd_fp);
	if (!f) {
		log_err("Can't find initrd specified in Boot####\n");
		ret = EFI_NOT_FOUND;
		goto out;
	}

	EFI_CALL(f->close(f));

out:
	efi_free_pool(initrd_fp);
	return ret;
}

/**
 * efi_initrd_register() - create handle for loading initial RAM disk
 *
 * This function creates a new handle and installs a Linux specific vendor
 * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path
 * to identify the handle and then calls the LoadFile service of the
 * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk.
 *
 * Return:	status code
 */
efi_status_t efi_initrd_register(void)
{
	efi_status_t ret;

	/*
	 * Allow the user to continue if Boot#### file path is not set for
	 * an initrd
	 */
	ret = check_initrd();
	if (ret == EFI_INVALID_PARAMETER)
		return EFI_SUCCESS;
	if (ret != EFI_SUCCESS)
		return ret;

	ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle,
						       /* initramfs */
						       &efi_guid_device_path, &dp_lf2_handle,
						       /* LOAD_FILE2 */
						       &efi_guid_load_file2_protocol,
						       &efi_lf2_protocol,
						       NULL);

	return ret;
}

/**
 * efi_initrd_deregister() - delete the handle for loading initial RAM disk
 *
 * This will delete the handle containing the Linux specific vendor device
 * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd
 *
 * Return:	status code
 */
efi_status_t efi_initrd_deregister(void)
{
	efi_status_t ret;

	if (!efi_initrd_handle)
		return EFI_SUCCESS;

	ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle,
							 /* initramfs */
							 &efi_guid_device_path,
							 &dp_lf2_handle,
							 /* LOAD_FILE2 */
							 &efi_guid_load_file2_protocol,
							 &efi_lf2_protocol,
							 NULL);
	efi_initrd_handle = NULL;

	return ret;
}