summaryrefslogtreecommitdiff
path: root/arch/x86/boot/compressed/efi.c
blob: 6edd034b0b30cb2df90937e06d30e125cf88268f (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
// SPDX-License-Identifier: GPL-2.0
/*
 * Helpers for early access to EFI configuration table.
 *
 * Originally derived from arch/x86/boot/compressed/acpi.c
 */

#include "misc.h"

/**
 * efi_get_type - Given a pointer to boot_params, determine the type of EFI environment.
 *
 * @bp:         pointer to boot_params
 *
 * Return: EFI_TYPE_{32,64} for valid EFI environments, EFI_TYPE_NONE otherwise.
 */
enum efi_type efi_get_type(struct boot_params *bp)
{
	struct efi_info *ei;
	enum efi_type et;
	const char *sig;

	ei = &bp->efi_info;
	sig = (char *)&ei->efi_loader_signature;

	if (!strncmp(sig, EFI64_LOADER_SIGNATURE, 4)) {
		et = EFI_TYPE_64;
	} else if (!strncmp(sig, EFI32_LOADER_SIGNATURE, 4)) {
		et = EFI_TYPE_32;
	} else {
		debug_putstr("No EFI environment detected.\n");
		et = EFI_TYPE_NONE;
	}

#ifndef CONFIG_X86_64
	/*
	 * Existing callers like acpi.c treat this case as an indicator to
	 * fall-through to non-EFI, rather than an error, so maintain that
	 * functionality here as well.
	 */
	if (ei->efi_systab_hi || ei->efi_memmap_hi) {
		debug_putstr("EFI system table is located above 4GB and cannot be accessed.\n");
		et = EFI_TYPE_NONE;
	}
#endif

	return et;
}

/**
 * efi_get_system_table - Given a pointer to boot_params, retrieve the physical address
 *                        of the EFI system table.
 *
 * @bp:         pointer to boot_params
 *
 * Return: EFI system table address on success. On error, return 0.
 */
unsigned long efi_get_system_table(struct boot_params *bp)
{
	unsigned long sys_tbl_pa;
	struct efi_info *ei;
	enum efi_type et;

	/* Get systab from boot params. */
	ei = &bp->efi_info;
#ifdef CONFIG_X86_64
	sys_tbl_pa = ei->efi_systab | ((__u64)ei->efi_systab_hi << 32);
#else
	sys_tbl_pa = ei->efi_systab;
#endif
	if (!sys_tbl_pa) {
		debug_putstr("EFI system table not found.");
		return 0;
	}

	return sys_tbl_pa;
}

/*
 * EFI config table address changes to virtual address after boot, which may
 * not be accessible for the kexec'd kernel. To address this, kexec provides
 * the initial physical address via a struct setup_data entry, which is
 * checked for here, along with some sanity checks.
 */
static struct efi_setup_data *get_kexec_setup_data(struct boot_params *bp,
						   enum efi_type et)
{
#ifdef CONFIG_X86_64
	struct efi_setup_data *esd = NULL;
	struct setup_data *data;
	u64 pa_data;

	pa_data = bp->hdr.setup_data;
	while (pa_data) {
		data = (struct setup_data *)pa_data;
		if (data->type == SETUP_EFI) {
			esd = (struct efi_setup_data *)(pa_data + sizeof(struct setup_data));
			break;
		}

		pa_data = data->next;
	}

	/*
	 * Original ACPI code falls back to attempting normal EFI boot in these
	 * cases, so maintain existing behavior by indicating non-kexec
	 * environment to the caller, but print them for debugging.
	 */
	if (esd && !esd->tables) {
		debug_putstr("kexec EFI environment missing valid configuration table.\n");
		return NULL;
	}

	return esd;
#endif
	return NULL;
}

/**
 * efi_get_conf_table - Given a pointer to boot_params, locate and return the physical
 *                      address of EFI configuration table.
 *
 * @bp:                 pointer to boot_params
 * @cfg_tbl_pa:         location to store physical address of config table
 * @cfg_tbl_len:        location to store number of config table entries
 *
 * Return: 0 on success. On error, return params are left unchanged.
 */
int efi_get_conf_table(struct boot_params *bp, unsigned long *cfg_tbl_pa,
		       unsigned int *cfg_tbl_len)
{
	unsigned long sys_tbl_pa;
	enum efi_type et;
	int ret;

	if (!cfg_tbl_pa || !cfg_tbl_len)
		return -EINVAL;

	sys_tbl_pa = efi_get_system_table(bp);
	if (!sys_tbl_pa)
		return -EINVAL;

	/* Handle EFI bitness properly */
	et = efi_get_type(bp);
	if (et == EFI_TYPE_64) {
		efi_system_table_64_t *stbl = (efi_system_table_64_t *)sys_tbl_pa;
		struct efi_setup_data *esd;

		/* kexec provides an alternative EFI conf table, check for it. */
		esd = get_kexec_setup_data(bp, et);

		*cfg_tbl_pa = esd ? esd->tables : stbl->tables;
		*cfg_tbl_len = stbl->nr_tables;
	} else if (et == EFI_TYPE_32) {
		efi_system_table_32_t *stbl = (efi_system_table_32_t *)sys_tbl_pa;

		*cfg_tbl_pa = stbl->tables;
		*cfg_tbl_len = stbl->nr_tables;
	} else {
		return -EINVAL;
	}

	return 0;
}

/* Get vendor table address/guid from EFI config table at the given index */
static int get_vendor_table(void *cfg_tbl, unsigned int idx,
			    unsigned long *vendor_tbl_pa,
			    efi_guid_t *vendor_tbl_guid,
			    enum efi_type et)
{
	if (et == EFI_TYPE_64) {
		efi_config_table_64_t *tbl_entry = (efi_config_table_64_t *)cfg_tbl + idx;

		if (!IS_ENABLED(CONFIG_X86_64) && tbl_entry->table >> 32) {
			debug_putstr("Error: EFI config table entry located above 4GB.\n");
			return -EINVAL;
		}

		*vendor_tbl_pa = tbl_entry->table;
		*vendor_tbl_guid = tbl_entry->guid;

	} else if (et == EFI_TYPE_32) {
		efi_config_table_32_t *tbl_entry = (efi_config_table_32_t *)cfg_tbl + idx;

		*vendor_tbl_pa = tbl_entry->table;
		*vendor_tbl_guid = tbl_entry->guid;
	} else {
		return -EINVAL;
	}

	return 0;
}

/**
 * efi_find_vendor_table - Given EFI config table, search it for the physical
 *                         address of the vendor table associated with GUID.
 *
 * @bp:                pointer to boot_params
 * @cfg_tbl_pa:        pointer to EFI configuration table
 * @cfg_tbl_len:       number of entries in EFI configuration table
 * @guid:              GUID of vendor table
 *
 * Return: vendor table address on success. On error, return 0.
 */
unsigned long efi_find_vendor_table(struct boot_params *bp,
				    unsigned long cfg_tbl_pa,
				    unsigned int cfg_tbl_len,
				    efi_guid_t guid)
{
	enum efi_type et;
	unsigned int i;

	et = efi_get_type(bp);
	if (et == EFI_TYPE_NONE)
		return 0;

	for (i = 0; i < cfg_tbl_len; i++) {
		unsigned long vendor_tbl_pa;
		efi_guid_t vendor_tbl_guid;
		int ret;

		ret = get_vendor_table((void *)cfg_tbl_pa, i,
				       &vendor_tbl_pa,
				       &vendor_tbl_guid, et);
		if (ret)
			return 0;

		if (!efi_guidcmp(guid, vendor_tbl_guid))
			return vendor_tbl_pa;
	}

	return 0;
}