summaryrefslogtreecommitdiff
path: root/lib/efi_loader/efi_load_initrd.c
blob: 574a83d7e3081fd7ce2d10f61026a727fddf0705 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2020, Linaro Limited
 */

#include <common.h>
#include <env.h>
#include <malloc.h>
#include <mapmem.h>
#include <dm.h>
#include <fs.h>
#include <efi_loader.h>
#include <efi_load_initrd.h>

static const efi_guid_t efi_guid_load_file2_protocol =
		EFI_LOAD_FILE2_PROTOCOL_GUID;

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 = {
	.vendor = {
		{
		   DEVICE_PATH_TYPE_MEDIA_DEVICE,
		   DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
		   sizeof(dp.vendor),
		},
		EFI_INITRD_MEDIA_GUID,
	},
	.end = {
		DEVICE_PATH_TYPE_END,
		DEVICE_PATH_SUB_TYPE_END,
		sizeof(dp.end),
	}
};

/**
 * get_file_size() - retrieve the size of initramfs, set efi status on error
 *
 * @dev:			device to read from. i.e "mmc"
 * @part:			device partition. i.e "0:1"
 * @file:			name fo file
 * @status:			EFI exit code in case of failure
 *
 * Return:			size of file
 */
static loff_t get_file_size(const char *dev, const char *part, const char *file,
			    efi_status_t *status)
{
	loff_t sz = 0;
	int ret;

	ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
	if (ret) {
		*status = EFI_NO_MEDIA;
		goto out;
	}

	ret = fs_size(file, &sz);
	if (ret) {
		sz = 0;
		*status = EFI_NOT_FOUND;
		goto out;
	}

out:
	return sz;
}

/**
 * load_file2() - get information about random number generation
 *
 * This function implement the LoadFile2() service in order to load an initram
 * disk requested by the Linux kernel stub.
 * See the UEFI spec for details.
 *
 * @this:			loadfile2 protocol instance
 * @file_path:			relative path of the file. "" in this case
 * @boot_policy:		must be false for Loadfile2
 * @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)
{
	const char *filespec = CONFIG_EFI_INITRD_FILESPEC;
	efi_status_t status = EFI_NOT_FOUND;
	loff_t file_sz = 0, read_sz = 0;
	char *dev, *part, *file;
	char *s;
	int ret;

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

	s = strdup(filespec);
	if (!s)
		goto out;

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

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

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

	/* expect something like 'mmc 0:1 initrd.cpio.gz' */
	dev = strsep(&s, " ");
	if (!dev)
		goto out;
	part = strsep(&s, " ");
	if (!part)
		goto out;
	file = strsep(&s, " ");
	if (!file)
		goto out;

	file_sz = get_file_size(dev, part, file, &status);
	if (!file_sz)
		goto out;

	if (!buffer || *buffer_size < file_sz) {
		status = EFI_BUFFER_TOO_SMALL;
		*buffer_size = file_sz;
	} else {
		ret = fs_set_blk_dev(dev, part, FS_TYPE_ANY);
		if (ret) {
			status = EFI_NO_MEDIA;
			goto out;
		}

		ret = fs_read(file, map_to_sysmem(buffer), 0, *buffer_size,
			      &read_sz);
		if (ret || read_sz != file_sz)
			goto out;
		*buffer_size = read_sz;

		status = EFI_SUCCESS;
	}

out:
	free(s);
	return EFI_EXIT(status);
}

/**
 * efi_initrd_register() - Register a handle and loadfile2 protocol
 *
 * This function creates a new handle and installs a linux specific GUID
 * to handle initram disk loading during boot.
 * See the UEFI spec for details.
 *
 * Return:			status code
 */
efi_status_t efi_initrd_register(void)
{
	efi_handle_t efi_initrd_handle = NULL;
	efi_status_t ret;

	/*
	 * Set up the handle with the EFI_LOAD_FILE2_PROTOCOL which Linux may
	 * use to load the initial ramdisk.
	 */
	ret = EFI_CALL(efi_install_multiple_protocol_interfaces
		       (&efi_initrd_handle,
			/* initramfs */
			&efi_guid_device_path, &dp,
			/* LOAD_FILE2 */
			&efi_guid_load_file2_protocol,
			(void *)&efi_lf2_protocol,
			NULL));

	return ret;
}