Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

efi / ACPI: load SSTDs from EFI variables

This patch allows SSDTs to be loaded from EFI variables. It works by
specifying the EFI variable name containing the SSDT to be loaded. All
variables with the same name (regardless of the vendor GUID) will be
loaded.

Note that we can't use acpi_install_table and we must rely on the
dynamic ACPI table loading and bus re-scanning mechanisms. That is
because I2C/SPI controllers are initialized earlier then the EFI
subsystems and all I2C/SPI ACPI devices are enumerated when the
I2C/SPI controllers are initialized.

Signed-off-by: Octavian Purdila <octavian.purdila@intel.com>
Reviewed-by: Matt Fleming <matt@codeblueprint.co.uk>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Octavian Purdila and committed by
Rafael J. Wysocki
475fb4e8 7f24467f

+170
+67
Documentation/acpi/ssdt-overlays.txt
··· 89 89 # on top: 90 90 find kernel | cpio -H newc --create > /boot/instrumented_initrd 91 91 cat /boot/initrd >>/boot/instrumented_initrd 92 + 93 + == Loading ACPI SSDTs from EFI variables == 94 + 95 + This is the preferred method, when EFI is supported on the platform, because it 96 + allows a persistent, OS independent way of storing the user defined SSDTs. There 97 + is also work underway to implement EFI support for loading user defined SSDTs 98 + and using this method will make it easier to convert to the EFI loading 99 + mechanism when that will arrive. 100 + 101 + In order to load SSDTs from an EFI variable the efivar_ssdt kernel command line 102 + parameter can be used. The argument for the option is the variable name to 103 + use. If there are multiple variables with the same name but with different 104 + vendor GUIDs, all of them will be loaded. 105 + 106 + In order to store the AML code in an EFI variable the efivarfs filesystem can be 107 + used. It is enabled and mounted by default in /sys/firmware/efi/efivars in all 108 + recent distribution. 109 + 110 + Creating a new file in /sys/firmware/efi/efivars will automatically create a new 111 + EFI variable. Updating a file in /sys/firmware/efi/efivars will update the EFI 112 + variable. Please note that the file name needs to be specially formatted as 113 + "Name-GUID" and that the first 4 bytes in the file (little-endian format) 114 + represent the attributes of the EFI variable (see EFI_VARIABLE_MASK in 115 + include/linux/efi.h). Writing to the file must also be done with one write 116 + operation. 117 + 118 + For example, you can use the following bash script to create/update an EFI 119 + variable with the content from a given file: 120 + 121 + #!/bin/sh -e 122 + 123 + while ! [ -z "$1" ]; do 124 + case "$1" in 125 + "-f") filename="$2"; shift;; 126 + "-g") guid="$2"; shift;; 127 + *) name="$1";; 128 + esac 129 + shift 130 + done 131 + 132 + usage() 133 + { 134 + echo "Syntax: ${0##*/} -f filename [ -g guid ] name" 135 + exit 1 136 + } 137 + 138 + [ -n "$name" -a -f "$filename" ] || usage 139 + 140 + EFIVARFS="/sys/firmware/efi/efivars" 141 + 142 + [ -d "$EFIVARFS" ] || exit 2 143 + 144 + if stat -tf $EFIVARFS | grep -q -v de5e81e4; then 145 + mount -t efivarfs none $EFIVARFS 146 + fi 147 + 148 + # try to pick up an existing GUID 149 + [ -n "$guid" ] || guid=$(find "$EFIVARFS" -name "$name-*" | head -n1 | cut -f2- -d-) 150 + 151 + # use a randomly generated GUID 152 + [ -n "$guid" ] || guid="$(cat /proc/sys/kernel/random/uuid)" 153 + 154 + # efivarfs expects all of the data in one write 155 + tmp=$(mktemp) 156 + /bin/echo -ne "\007\000\000\000" | cat - $filename > $tmp 157 + dd if=$tmp of="$EFIVARFS/$name-$guid" bs=$(stat -c %s $tmp) 158 + rm $tmp
+7
Documentation/kernel-parameters.txt
··· 1185 1185 Address Range Mirroring feature even if your box 1186 1186 doesn't support it. 1187 1187 1188 + efivar_ssdt= [EFI; X86] Name of an EFI variable that contains an SSDT 1189 + that is to be dynamically loaded by Linux. If there are 1190 + multiple variables with the same name but with different 1191 + vendor GUIDs, all of them will be loaded. See 1192 + Documentation/acpi/ssdt-overlays.txt for details. 1193 + 1194 + 1188 1195 eisa_irq_edge= [PARISC,HW] 1189 1196 See header of drivers/parisc/eisa.c. 1190 1197
+96
drivers/firmware/efi/efi.c
··· 24 24 #include <linux/of_fdt.h> 25 25 #include <linux/io.h> 26 26 #include <linux/platform_device.h> 27 + #include <linux/slab.h> 28 + #include <linux/acpi.h> 29 + #include <linux/ucs2_string.h> 27 30 28 31 #include <asm/early_ioremap.h> 29 32 ··· 198 195 efivars_unregister(&generic_efivars); 199 196 } 200 197 198 + #if IS_ENABLED(CONFIG_ACPI) 199 + #define EFIVAR_SSDT_NAME_MAX 16 200 + static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX] __initdata; 201 + static int __init efivar_ssdt_setup(char *str) 202 + { 203 + if (strlen(str) < sizeof(efivar_ssdt)) 204 + memcpy(efivar_ssdt, str, strlen(str)); 205 + else 206 + pr_warn("efivar_ssdt: name too long: %s\n", str); 207 + return 0; 208 + } 209 + __setup("efivar_ssdt=", efivar_ssdt_setup); 210 + 211 + static __init int efivar_ssdt_iter(efi_char16_t *name, efi_guid_t vendor, 212 + unsigned long name_size, void *data) 213 + { 214 + struct efivar_entry *entry; 215 + struct list_head *list = data; 216 + char utf8_name[EFIVAR_SSDT_NAME_MAX]; 217 + int limit = min_t(unsigned long, EFIVAR_SSDT_NAME_MAX, name_size); 218 + 219 + ucs2_as_utf8(utf8_name, name, limit - 1); 220 + if (strncmp(utf8_name, efivar_ssdt, limit) != 0) 221 + return 0; 222 + 223 + entry = kmalloc(sizeof(*entry), GFP_KERNEL); 224 + if (!entry) 225 + return 0; 226 + 227 + memcpy(entry->var.VariableName, name, name_size); 228 + memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t)); 229 + 230 + efivar_entry_add(entry, list); 231 + 232 + return 0; 233 + } 234 + 235 + static __init int efivar_ssdt_load(void) 236 + { 237 + LIST_HEAD(entries); 238 + struct efivar_entry *entry, *aux; 239 + unsigned long size; 240 + void *data; 241 + int ret; 242 + 243 + ret = efivar_init(efivar_ssdt_iter, &entries, true, &entries); 244 + 245 + list_for_each_entry_safe(entry, aux, &entries, list) { 246 + pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt, 247 + &entry->var.VendorGuid); 248 + 249 + list_del(&entry->list); 250 + 251 + ret = efivar_entry_size(entry, &size); 252 + if (ret) { 253 + pr_err("failed to get var size\n"); 254 + goto free_entry; 255 + } 256 + 257 + data = kmalloc(size, GFP_KERNEL); 258 + if (!data) 259 + goto free_entry; 260 + 261 + ret = efivar_entry_get(entry, NULL, &size, data); 262 + if (ret) { 263 + pr_err("failed to get var data\n"); 264 + goto free_data; 265 + } 266 + 267 + ret = acpi_load_table(data); 268 + if (ret) { 269 + pr_err("failed to load table: %d\n", ret); 270 + goto free_data; 271 + } 272 + 273 + goto free_entry; 274 + 275 + free_data: 276 + kfree(data); 277 + 278 + free_entry: 279 + kfree(entry); 280 + } 281 + 282 + return ret; 283 + } 284 + #else 285 + static inline int efivar_ssdt_load(void) { return 0; } 286 + #endif 287 + 201 288 /* 202 289 * We register the efi subsystem with the firmware subsystem and the 203 290 * efivars subsystem with the efi subsystem, if the system was booted with ··· 310 217 error = generic_ops_register(); 311 218 if (error) 312 219 goto err_put; 220 + 221 + if (efi_enabled(EFI_RUNTIME_SERVICES)) 222 + efivar_ssdt_load(); 313 223 314 224 error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); 315 225 if (error) {