1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Helpers for early access to EFI configuration table. |
4 | * |
5 | * Originally derived from arch/x86/boot/compressed/acpi.c |
6 | */ |
7 | |
8 | #include "misc.h" |
9 | |
10 | /** |
11 | * efi_get_type - Given a pointer to boot_params, determine the type of EFI environment. |
12 | * |
13 | * @bp: pointer to boot_params |
14 | * |
15 | * Return: EFI_TYPE_{32,64} for valid EFI environments, EFI_TYPE_NONE otherwise. |
16 | */ |
17 | enum efi_type efi_get_type(struct boot_params *bp) |
18 | { |
19 | struct efi_info *ei; |
20 | enum efi_type et; |
21 | const char *sig; |
22 | |
23 | ei = &bp->efi_info; |
24 | sig = (char *)&ei->efi_loader_signature; |
25 | |
26 | if (!strncmp(sig, EFI64_LOADER_SIGNATURE, 4)) { |
27 | et = EFI_TYPE_64; |
28 | } else if (!strncmp(sig, EFI32_LOADER_SIGNATURE, 4)) { |
29 | et = EFI_TYPE_32; |
30 | } else { |
31 | debug_putstr("No EFI environment detected.\n" ); |
32 | et = EFI_TYPE_NONE; |
33 | } |
34 | |
35 | #ifndef CONFIG_X86_64 |
36 | /* |
37 | * Existing callers like acpi.c treat this case as an indicator to |
38 | * fall-through to non-EFI, rather than an error, so maintain that |
39 | * functionality here as well. |
40 | */ |
41 | if (ei->efi_systab_hi || ei->efi_memmap_hi) { |
42 | debug_putstr("EFI system table is located above 4GB and cannot be accessed.\n" ); |
43 | et = EFI_TYPE_NONE; |
44 | } |
45 | #endif |
46 | |
47 | return et; |
48 | } |
49 | |
50 | /** |
51 | * efi_get_system_table - Given a pointer to boot_params, retrieve the physical address |
52 | * of the EFI system table. |
53 | * |
54 | * @bp: pointer to boot_params |
55 | * |
56 | * Return: EFI system table address on success. On error, return 0. |
57 | */ |
58 | unsigned long efi_get_system_table(struct boot_params *bp) |
59 | { |
60 | unsigned long sys_tbl_pa; |
61 | struct efi_info *ei; |
62 | enum efi_type et; |
63 | |
64 | /* Get systab from boot params. */ |
65 | ei = &bp->efi_info; |
66 | #ifdef CONFIG_X86_64 |
67 | sys_tbl_pa = ei->efi_systab | ((__u64)ei->efi_systab_hi << 32); |
68 | #else |
69 | sys_tbl_pa = ei->efi_systab; |
70 | #endif |
71 | if (!sys_tbl_pa) { |
72 | debug_putstr("EFI system table not found." ); |
73 | return 0; |
74 | } |
75 | |
76 | return sys_tbl_pa; |
77 | } |
78 | |
79 | /* |
80 | * EFI config table address changes to virtual address after boot, which may |
81 | * not be accessible for the kexec'd kernel. To address this, kexec provides |
82 | * the initial physical address via a struct setup_data entry, which is |
83 | * checked for here, along with some sanity checks. |
84 | */ |
85 | static struct efi_setup_data *get_kexec_setup_data(struct boot_params *bp, |
86 | enum efi_type et) |
87 | { |
88 | #ifdef CONFIG_X86_64 |
89 | struct efi_setup_data *esd = NULL; |
90 | struct setup_data *data; |
91 | u64 pa_data; |
92 | |
93 | pa_data = bp->hdr.setup_data; |
94 | while (pa_data) { |
95 | data = (struct setup_data *)pa_data; |
96 | if (data->type == SETUP_EFI) { |
97 | esd = (struct efi_setup_data *)(pa_data + sizeof(struct setup_data)); |
98 | break; |
99 | } |
100 | |
101 | pa_data = data->next; |
102 | } |
103 | |
104 | /* |
105 | * Original ACPI code falls back to attempting normal EFI boot in these |
106 | * cases, so maintain existing behavior by indicating non-kexec |
107 | * environment to the caller, but print them for debugging. |
108 | */ |
109 | if (esd && !esd->tables) { |
110 | debug_putstr("kexec EFI environment missing valid configuration table.\n" ); |
111 | return NULL; |
112 | } |
113 | |
114 | return esd; |
115 | #endif |
116 | return NULL; |
117 | } |
118 | |
119 | /** |
120 | * efi_get_conf_table - Given a pointer to boot_params, locate and return the physical |
121 | * address of EFI configuration table. |
122 | * |
123 | * @bp: pointer to boot_params |
124 | * @cfg_tbl_pa: location to store physical address of config table |
125 | * @cfg_tbl_len: location to store number of config table entries |
126 | * |
127 | * Return: 0 on success. On error, return params are left unchanged. |
128 | */ |
129 | int efi_get_conf_table(struct boot_params *bp, unsigned long *cfg_tbl_pa, |
130 | unsigned int *cfg_tbl_len) |
131 | { |
132 | unsigned long sys_tbl_pa; |
133 | enum efi_type et; |
134 | int ret; |
135 | |
136 | if (!cfg_tbl_pa || !cfg_tbl_len) |
137 | return -EINVAL; |
138 | |
139 | sys_tbl_pa = efi_get_system_table(bp); |
140 | if (!sys_tbl_pa) |
141 | return -EINVAL; |
142 | |
143 | /* Handle EFI bitness properly */ |
144 | et = efi_get_type(bp); |
145 | if (et == EFI_TYPE_64) { |
146 | efi_system_table_64_t *stbl = (efi_system_table_64_t *)sys_tbl_pa; |
147 | struct efi_setup_data *esd; |
148 | |
149 | /* kexec provides an alternative EFI conf table, check for it. */ |
150 | esd = get_kexec_setup_data(bp, et); |
151 | |
152 | *cfg_tbl_pa = esd ? esd->tables : stbl->tables; |
153 | *cfg_tbl_len = stbl->nr_tables; |
154 | } else if (et == EFI_TYPE_32) { |
155 | efi_system_table_32_t *stbl = (efi_system_table_32_t *)sys_tbl_pa; |
156 | |
157 | *cfg_tbl_pa = stbl->tables; |
158 | *cfg_tbl_len = stbl->nr_tables; |
159 | } else { |
160 | return -EINVAL; |
161 | } |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | /* Get vendor table address/guid from EFI config table at the given index */ |
167 | static int get_vendor_table(void *cfg_tbl, unsigned int idx, |
168 | unsigned long *vendor_tbl_pa, |
169 | efi_guid_t *vendor_tbl_guid, |
170 | enum efi_type et) |
171 | { |
172 | if (et == EFI_TYPE_64) { |
173 | efi_config_table_64_t *tbl_entry = (efi_config_table_64_t *)cfg_tbl + idx; |
174 | |
175 | if (!IS_ENABLED(CONFIG_X86_64) && tbl_entry->table >> 32) { |
176 | debug_putstr("Error: EFI config table entry located above 4GB.\n" ); |
177 | return -EINVAL; |
178 | } |
179 | |
180 | *vendor_tbl_pa = tbl_entry->table; |
181 | *vendor_tbl_guid = tbl_entry->guid; |
182 | |
183 | } else if (et == EFI_TYPE_32) { |
184 | efi_config_table_32_t *tbl_entry = (efi_config_table_32_t *)cfg_tbl + idx; |
185 | |
186 | *vendor_tbl_pa = tbl_entry->table; |
187 | *vendor_tbl_guid = tbl_entry->guid; |
188 | } else { |
189 | return -EINVAL; |
190 | } |
191 | |
192 | return 0; |
193 | } |
194 | |
195 | /** |
196 | * efi_find_vendor_table - Given EFI config table, search it for the physical |
197 | * address of the vendor table associated with GUID. |
198 | * |
199 | * @bp: pointer to boot_params |
200 | * @cfg_tbl_pa: pointer to EFI configuration table |
201 | * @cfg_tbl_len: number of entries in EFI configuration table |
202 | * @guid: GUID of vendor table |
203 | * |
204 | * Return: vendor table address on success. On error, return 0. |
205 | */ |
206 | unsigned long efi_find_vendor_table(struct boot_params *bp, |
207 | unsigned long cfg_tbl_pa, |
208 | unsigned int cfg_tbl_len, |
209 | efi_guid_t guid) |
210 | { |
211 | enum efi_type et; |
212 | unsigned int i; |
213 | |
214 | et = efi_get_type(bp); |
215 | if (et == EFI_TYPE_NONE) |
216 | return 0; |
217 | |
218 | for (i = 0; i < cfg_tbl_len; i++) { |
219 | unsigned long vendor_tbl_pa; |
220 | efi_guid_t vendor_tbl_guid; |
221 | int ret; |
222 | |
223 | ret = get_vendor_table(cfg_tbl: (void *)cfg_tbl_pa, idx: i, |
224 | vendor_tbl_pa: &vendor_tbl_pa, |
225 | vendor_tbl_guid: &vendor_tbl_guid, et); |
226 | if (ret) |
227 | return 0; |
228 | |
229 | if (!efi_guidcmp(left: guid, right: vendor_tbl_guid)) |
230 | return vendor_tbl_pa; |
231 | } |
232 | |
233 | return 0; |
234 | } |
235 | |