diff options
-rw-r--r-- | Documentation/acpi/ssdt-overlays.txt | 67 | ||||
-rw-r--r-- | Documentation/kernel-parameters.txt | 7 | ||||
-rw-r--r-- | drivers/firmware/efi/efi.c | 96 |
3 files changed, 170 insertions, 0 deletions
diff --git a/Documentation/acpi/ssdt-overlays.txt b/Documentation/acpi/ssdt-overlays.txt index 805025940248..c4b57badd0b7 100644 --- a/Documentation/acpi/ssdt-overlays.txt +++ b/Documentation/acpi/ssdt-overlays.txt | |||
@@ -89,3 +89,70 @@ cp ssdt.aml kernel/firmware/acpi | |||
89 | # on top: | 89 | # on top: |
90 | find kernel | cpio -H newc --create > /boot/instrumented_initrd | 90 | find kernel | cpio -H newc --create > /boot/instrumented_initrd |
91 | cat /boot/initrd >>/boot/instrumented_initrd | 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 | ||
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 82b42c958d1c..bbfb56fe7b92 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt | |||
@@ -1185,6 +1185,13 @@ bytes respectively. Such letter suffixes can also be entirely omitted. | |||
1185 | Address Range Mirroring feature even if your box | 1185 | Address Range Mirroring feature even if your box |
1186 | doesn't support it. | 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 | eisa_irq_edge= [PARISC,HW] | 1195 | eisa_irq_edge= [PARISC,HW] |
1189 | See header of drivers/parisc/eisa.c. | 1196 | See header of drivers/parisc/eisa.c. |
1190 | 1197 | ||
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index 05509f3aaee8..8730fd475bf3 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c | |||
@@ -24,6 +24,9 @@ | |||
24 | #include <linux/of_fdt.h> | 24 | #include <linux/of_fdt.h> |
25 | #include <linux/io.h> | 25 | #include <linux/io.h> |
26 | #include <linux/platform_device.h> | 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 | #include <asm/early_ioremap.h> | 31 | #include <asm/early_ioremap.h> |
29 | 32 | ||
@@ -195,6 +198,96 @@ static void generic_ops_unregister(void) | |||
195 | efivars_unregister(&generic_efivars); | 198 | efivars_unregister(&generic_efivars); |
196 | } | 199 | } |
197 | 200 | ||
201 | #if IS_ENABLED(CONFIG_ACPI) | ||
202 | #define EFIVAR_SSDT_NAME_MAX 16 | ||
203 | static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX] __initdata; | ||
204 | static int __init efivar_ssdt_setup(char *str) | ||
205 | { | ||
206 | if (strlen(str) < sizeof(efivar_ssdt)) | ||
207 | memcpy(efivar_ssdt, str, strlen(str)); | ||
208 | else | ||
209 | pr_warn("efivar_ssdt: name too long: %s\n", str); | ||
210 | return 0; | ||
211 | } | ||
212 | __setup("efivar_ssdt=", efivar_ssdt_setup); | ||
213 | |||
214 | static __init int efivar_ssdt_iter(efi_char16_t *name, efi_guid_t vendor, | ||
215 | unsigned long name_size, void *data) | ||
216 | { | ||
217 | struct efivar_entry *entry; | ||
218 | struct list_head *list = data; | ||
219 | char utf8_name[EFIVAR_SSDT_NAME_MAX]; | ||
220 | int limit = min_t(unsigned long, EFIVAR_SSDT_NAME_MAX, name_size); | ||
221 | |||
222 | ucs2_as_utf8(utf8_name, name, limit - 1); | ||
223 | if (strncmp(utf8_name, efivar_ssdt, limit) != 0) | ||
224 | return 0; | ||
225 | |||
226 | entry = kmalloc(sizeof(*entry), GFP_KERNEL); | ||
227 | if (!entry) | ||
228 | return 0; | ||
229 | |||
230 | memcpy(entry->var.VariableName, name, name_size); | ||
231 | memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t)); | ||
232 | |||
233 | efivar_entry_add(entry, list); | ||
234 | |||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static __init int efivar_ssdt_load(void) | ||
239 | { | ||
240 | LIST_HEAD(entries); | ||
241 | struct efivar_entry *entry, *aux; | ||
242 | unsigned long size; | ||
243 | void *data; | ||
244 | int ret; | ||
245 | |||
246 | ret = efivar_init(efivar_ssdt_iter, &entries, true, &entries); | ||
247 | |||
248 | list_for_each_entry_safe(entry, aux, &entries, list) { | ||
249 | pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt, | ||
250 | &entry->var.VendorGuid); | ||
251 | |||
252 | list_del(&entry->list); | ||
253 | |||
254 | ret = efivar_entry_size(entry, &size); | ||
255 | if (ret) { | ||
256 | pr_err("failed to get var size\n"); | ||
257 | goto free_entry; | ||
258 | } | ||
259 | |||
260 | data = kmalloc(size, GFP_KERNEL); | ||
261 | if (!data) | ||
262 | goto free_entry; | ||
263 | |||
264 | ret = efivar_entry_get(entry, NULL, &size, data); | ||
265 | if (ret) { | ||
266 | pr_err("failed to get var data\n"); | ||
267 | goto free_data; | ||
268 | } | ||
269 | |||
270 | ret = acpi_load_table(data); | ||
271 | if (ret) { | ||
272 | pr_err("failed to load table: %d\n", ret); | ||
273 | goto free_data; | ||
274 | } | ||
275 | |||
276 | goto free_entry; | ||
277 | |||
278 | free_data: | ||
279 | kfree(data); | ||
280 | |||
281 | free_entry: | ||
282 | kfree(entry); | ||
283 | } | ||
284 | |||
285 | return ret; | ||
286 | } | ||
287 | #else | ||
288 | static inline int efivar_ssdt_load(void) { return 0; } | ||
289 | #endif | ||
290 | |||
198 | /* | 291 | /* |
199 | * We register the efi subsystem with the firmware subsystem and the | 292 | * We register the efi subsystem with the firmware subsystem and the |
200 | * efivars subsystem with the efi subsystem, if the system was booted with | 293 | * efivars subsystem with the efi subsystem, if the system was booted with |
@@ -218,6 +311,9 @@ static int __init efisubsys_init(void) | |||
218 | if (error) | 311 | if (error) |
219 | goto err_put; | 312 | goto err_put; |
220 | 313 | ||
314 | if (efi_enabled(EFI_RUNTIME_SERVICES)) | ||
315 | efivar_ssdt_load(); | ||
316 | |||
221 | error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); | 317 | error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); |
222 | if (error) { | 318 | if (error) { |
223 | pr_err("efi: Sysfs attribute export failed with error %d.\n", | 319 | pr_err("efi: Sysfs attribute export failed with error %d.\n", |