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