diff mbox

[v4,6/8] efi: load SSTDs from EFI variables

Message ID 1466164336-9508-7-git-send-email-octavian.purdila@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Octavian Purdila June 17, 2016, 11:52 a.m. UTC
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>
---
 Documentation/acpi/ssdt-overlays.txt |  67 ++++++++++++++++++++++
 Documentation/kernel-parameters.txt  |   7 +++
 drivers/firmware/efi/efi.c           | 106 +++++++++++++++++++++++++++++++++++
 3 files changed, 180 insertions(+)

Comments

Matt Fleming June 23, 2016, 1:22 p.m. UTC | #1
On Fri, 17 Jun, at 02:52:14PM, Octavian Purdila wrote:
> 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>
> ---
>  Documentation/acpi/ssdt-overlays.txt |  67 ++++++++++++++++++++++
>  Documentation/kernel-parameters.txt  |   7 +++
>  drivers/firmware/efi/efi.c           | 106 +++++++++++++++++++++++++++++++++++
>  3 files changed, 180 insertions(+)

[...]

> @@ -195,6 +197,107 @@ static void generic_ops_unregister(void)
>  	efivars_unregister(&generic_efivars);
>  }
>  
> +#if IS_ENABLED(CONFIG_ACPI)
> +#define EFIVAR_SSDT_NAME_MAX	16
> +static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX];
> +static int __init efivar_ssdt_setup(char *str)
> +{
> +	if (strlen(str) < sizeof(efivar_ssdt))
> +		memcpy(efivar_ssdt, str, strlen(str));
> +	else
> +		pr_warn("efivar_ssdt: name too long: %s\n", str);
> +	return 0;
> +}
> +__setup("efivar_ssdt=", efivar_ssdt_setup);
> +
> +static LIST_HEAD(efivar_ssdts);
> +
> +static inline void pr_efivar_name(efi_char16_t *name16)
> +{
> +	char name[EFIVAR_SSDT_NAME_MAX];
> +	int i;
> +
> +	for (i = 0; i < EFIVAR_SSDT_NAME_MAX - 1; i++)
> +		name[i] = name16[i] & 0xFF;
> +	name[i] = 0;
> +	pr_cont("%s", name);
> +}

Please use the various ucs2_* functions we already have in lib/.

> +static __init int efivar_acpi_iter(efi_char16_t *name, efi_guid_t vendor,
> +				   unsigned long name_size, void *data)
> +{
> +	int i;
> +	int str_len = name_size / sizeof(efi_char16_t);
> +	struct efivar_entry *entry;
> +
> +	if (str_len != strlen(efivar_ssdt) + 1)
> +		return 0;
> +
> +	for (i = 0; i < str_len; i++)
> +		if ((name[i] & 0xFF) != efivar_ssdt[i])
> +			return 0;
> +
> +	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> +	if (!entry)
> +		return -ENOMEM;
> +
> +	memcpy(entry->var.VariableName, name, name_size);
> +	memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t));
> +
> +	efivar_entry_add(entry, &efivar_ssdts);
> +
> +	return 0;
> +}
> +
> +static __init int efivar_ssdt_load(void)
> +{
> +	struct efivar_entry *i;
> +	int err;
> +
> +	err = efivar_init(efivar_acpi_iter, NULL, true, &efivar_ssdts);
> +	if (err) {
> +		pr_err("%s: efivar_init failed: %d\n", __func__, err);
> +		return err;
> +	}
> +
> +	list_for_each_entry(i, &efivar_ssdts, list) {
> +		void *data;
> +		unsigned long size;
> +
> +		pr_info("loading SSDT from EFI variable ");
> +		pr_efivar_name(i->var.VariableName); pr_cont("\n");
> +
> +		err = efivar_entry_size(i, &size);
> +		if (err) {
> +			pr_err("failed to get size\n");
> +			continue;
> +		}
> +
> +		data = kmalloc(size, GFP_KERNEL);
> +		if (!data)
> +			continue;
> +
> +		err = efivar_entry_get(i, NULL, &size, data);
> +		if (err) {
> +			pr_err("failed to get data\n");
> +			kfree(data);
> +			continue;
> +		}
> +
> +		err = acpi_load_table(data);
> +		if (err) {
> +			pr_err("failed to load table: %d\n", err);
> +			kfree(data);
> +			continue;
> +		}
> +	}
> +

Since 'efivar_ssdts' isn't exported to userspace and is never
traversed again after its built in efivar_acpi_iter(), can't you just
fold the code from the above list_for_each_entry() loop into
efivar_acpi_iter()?

You should be able to call efivar_entry_get() without 'entry' actually
being on any list since the list is only used for duplicate variable
detection in this case.
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Octavian Purdila June 30, 2016, 11:56 a.m. UTC | #2
On Thu, Jun 23, 2016 at 4:22 PM, Matt Fleming <matt@codeblueprint.co.uk> wrote:

> On Fri, 17 Jun, at 02:52:14PM, Octavian Purdila wrote:
>> 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>
>> ---
>>  Documentation/acpi/ssdt-overlays.txt |  67 ++++++++++++++++++++++
>>  Documentation/kernel-parameters.txt  |   7 +++
>>  drivers/firmware/efi/efi.c           | 106 +++++++++++++++++++++++++++++++++++
>>  3 files changed, 180 insertions(+)
>
> [...]
>
>> @@ -195,6 +197,107 @@ static void generic_ops_unregister(void)
>>       efivars_unregister(&generic_efivars);
>>  }
>>
>> +#if IS_ENABLED(CONFIG_ACPI)
>> +#define EFIVAR_SSDT_NAME_MAX 16
>> +static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX];
>> +static int __init efivar_ssdt_setup(char *str)
>> +{
>> +     if (strlen(str) < sizeof(efivar_ssdt))
>> +             memcpy(efivar_ssdt, str, strlen(str));
>> +     else
>> +             pr_warn("efivar_ssdt: name too long: %s\n", str);
>> +     return 0;
>> +}
>> +__setup("efivar_ssdt=", efivar_ssdt_setup);
>> +
>> +static LIST_HEAD(efivar_ssdts);
>> +
>> +static inline void pr_efivar_name(efi_char16_t *name16)
>> +{
>> +     char name[EFIVAR_SSDT_NAME_MAX];
>> +     int i;
>> +
>> +     for (i = 0; i < EFIVAR_SSDT_NAME_MAX - 1; i++)
>> +             name[i] = name16[i] & 0xFF;
>> +     name[i] = 0;
>> +     pr_cont("%s", name);
>> +}
>
> Please use the various ucs2_* functions we already have in lib/.
>

Okay, after reading the archives regarding UTF8, ucs2, etc. it looks
like the best thing to do in kernel is to avoid interpreting them. So
I'll just print the kernel command line parameter and use ucs2_as_utf8
and memcmp to check if the variable name matches the kernel command
line.

>> +static __init int efivar_acpi_iter(efi_char16_t *name, efi_guid_t vendor,
>> +                                unsigned long name_size, void *data)
>> +{
>> +     int i;
>> +     int str_len = name_size / sizeof(efi_char16_t);
>> +     struct efivar_entry *entry;
>> +
>> +     if (str_len != strlen(efivar_ssdt) + 1)
>> +             return 0;
>> +
>> +     for (i = 0; i < str_len; i++)
>> +             if ((name[i] & 0xFF) != efivar_ssdt[i])
>> +                     return 0;
>> +
>> +     entry = kzalloc(sizeof(*entry), GFP_KERNEL);
>> +     if (!entry)
>> +             return -ENOMEM;
>> +
>> +     memcpy(entry->var.VariableName, name, name_size);
>> +     memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t));
>> +
>> +     efivar_entry_add(entry, &efivar_ssdts);
>> +
>> +     return 0;
>> +}
>> +
>> +static __init int efivar_ssdt_load(void)
>> +{
>> +     struct efivar_entry *i;
>> +     int err;
>> +
>> +     err = efivar_init(efivar_acpi_iter, NULL, true, &efivar_ssdts);
>> +     if (err) {
>> +             pr_err("%s: efivar_init failed: %d\n", __func__, err);
>> +             return err;
>> +     }
>> +
>> +     list_for_each_entry(i, &efivar_ssdts, list) {
>> +             void *data;
>> +             unsigned long size;
>> +
>> +             pr_info("loading SSDT from EFI variable ");
>> +             pr_efivar_name(i->var.VariableName); pr_cont("\n");
>> +
>> +             err = efivar_entry_size(i, &size);
>> +             if (err) {
>> +                     pr_err("failed to get size\n");
>> +                     continue;
>> +             }
>> +
>> +             data = kmalloc(size, GFP_KERNEL);
>> +             if (!data)
>> +                     continue;
>> +
>> +             err = efivar_entry_get(i, NULL, &size, data);
>> +             if (err) {
>> +                     pr_err("failed to get data\n");
>> +                     kfree(data);
>> +                     continue;
>> +             }
>> +
>> +             err = acpi_load_table(data);
>> +             if (err) {
>> +                     pr_err("failed to load table: %d\n", err);
>> +                     kfree(data);
>> +                     continue;
>> +             }
>> +     }
>> +
>
> Since 'efivar_ssdts' isn't exported to userspace and is never
> traversed again after its built in efivar_acpi_iter(), can't you just
> fold the code from the above list_for_each_entry() loop into
> efivar_acpi_iter()?
>
> You should be able to call efivar_entry_get() without 'entry' actually
> being on any list since the list is only used for duplicate variable
> detection in this case.

Since commit 1cfd63166 (efi: Merge boolean flag arguments) it is a bit
hairy to do this. We need to call efivar_init() with duplicates set to
false to allow passing a  NULL list but in that case the __efivars
lock is not release when calling the iterator function which means
that calls like efivar_entry_get from within the iterator function
will deadlock.

What seems to work is to call efivar_init() with duplicates set and
pass a temporary empty list. How does that sound?
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/acpi/ssdt-overlays.txt b/Documentation/acpi/ssdt-overlays.txt
index 8050259..c4b57ba 100644
--- a/Documentation/acpi/ssdt-overlays.txt
+++ b/Documentation/acpi/ssdt-overlays.txt
@@ -89,3 +89,70 @@  cp ssdt.aml kernel/firmware/acpi
 # on top:
 find kernel | cpio -H newc --create > /boot/instrumented_initrd
 cat /boot/initrd >>/boot/instrumented_initrd
+
+== Loading ACPI SSDTs from EFI variables ==
+
+This is the preferred method, when EFI is supported on the platform, because it
+allows a persistent, OS independent way of storing the user defined SSDTs. There
+is also work underway to implement EFI support for loading user defined SSDTs
+and using this method will make it easier to convert to the EFI loading
+mechanism when that will arrive.
+
+In order to load SSDTs from an EFI variable the efivar_ssdt kernel command line
+parameter can be used. The argument for the option is the variable name to
+use. If there are multiple variables with the same name but with different
+vendor GUIDs, all of them will be loaded.
+
+In order to store the AML code in an EFI variable the efivarfs filesystem can be
+used. It is enabled and mounted by default in /sys/firmware/efi/efivars in all
+recent distribution.
+
+Creating a new file in /sys/firmware/efi/efivars will automatically create a new
+EFI variable. Updating a file in /sys/firmware/efi/efivars will update the EFI
+variable. Please note that the file name needs to be specially formatted as
+"Name-GUID" and that the first 4 bytes in the file (little-endian format)
+represent the attributes of the EFI variable (see EFI_VARIABLE_MASK in
+include/linux/efi.h). Writing to the file must also be done with one write
+operation.
+
+For example, you can use the following bash script to create/update an EFI
+variable with the content from a given file:
+
+#!/bin/sh -e
+
+while ! [ -z "$1" ]; do
+        case "$1" in
+        "-f") filename="$2"; shift;;
+        "-g") guid="$2"; shift;;
+        *) name="$1";;
+        esac
+        shift
+done
+
+usage()
+{
+        echo "Syntax: ${0##*/} -f filename [ -g guid ] name"
+        exit 1
+}
+
+[ -n "$name" -a -f "$filename" ] || usage
+
+EFIVARFS="/sys/firmware/efi/efivars"
+
+[ -d "$EFIVARFS" ] || exit 2
+
+if stat -tf $EFIVARFS | grep -q -v de5e81e4; then
+        mount -t efivarfs none $EFIVARFS
+fi
+
+# try to pick up an existing GUID
+[ -n "$guid" ] || guid=$(find "$EFIVARFS" -name "$name-*" | head -n1 | cut -f2- -d-)
+
+# use a randomly generated GUID
+[ -n "$guid" ] || guid="$(cat /proc/sys/kernel/random/uuid)"
+
+# efivarfs expects all of the data in one write
+tmp=$(mktemp)
+/bin/echo -ne "\007\000\000\000" | cat - $filename > $tmp
+dd if=$tmp of="$EFIVARFS/$name-$guid" bs=$(stat -c %s $tmp)
+rm $tmp
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index 82b42c9..bbfb56f 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.
 			Address Range Mirroring feature even if your box
 			doesn't support it.
 
+	efivar_ssdt=	[EFI; X86] Name of an EFI variable that contains an SSDT
+			that is to be dynamically loaded by Linux. If there are
+			multiple variables with the same name but with different
+			vendor GUIDs, all of them will be loaded. See
+			Documentation/acpi/ssdt-overlays.txt for details.
+
+
 	eisa_irq_edge=	[PARISC,HW]
 			See header of drivers/parisc/eisa.c.
 
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 05509f3..b68cb8d 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -24,6 +24,8 @@ 
 #include <linux/of_fdt.h>
 #include <linux/io.h>
 #include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
 
 #include <asm/early_ioremap.h>
 
@@ -195,6 +197,107 @@  static void generic_ops_unregister(void)
 	efivars_unregister(&generic_efivars);
 }
 
+#if IS_ENABLED(CONFIG_ACPI)
+#define EFIVAR_SSDT_NAME_MAX	16
+static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX];
+static int __init efivar_ssdt_setup(char *str)
+{
+	if (strlen(str) < sizeof(efivar_ssdt))
+		memcpy(efivar_ssdt, str, strlen(str));
+	else
+		pr_warn("efivar_ssdt: name too long: %s\n", str);
+	return 0;
+}
+__setup("efivar_ssdt=", efivar_ssdt_setup);
+
+static LIST_HEAD(efivar_ssdts);
+
+static inline void pr_efivar_name(efi_char16_t *name16)
+{
+	char name[EFIVAR_SSDT_NAME_MAX];
+	int i;
+
+	for (i = 0; i < EFIVAR_SSDT_NAME_MAX - 1; i++)
+		name[i] = name16[i] & 0xFF;
+	name[i] = 0;
+	pr_cont("%s", name);
+}
+
+static __init int efivar_acpi_iter(efi_char16_t *name, efi_guid_t vendor,
+				   unsigned long name_size, void *data)
+{
+	int i;
+	int str_len = name_size / sizeof(efi_char16_t);
+	struct efivar_entry *entry;
+
+	if (str_len != strlen(efivar_ssdt) + 1)
+		return 0;
+
+	for (i = 0; i < str_len; i++)
+		if ((name[i] & 0xFF) != efivar_ssdt[i])
+			return 0;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	memcpy(entry->var.VariableName, name, name_size);
+	memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t));
+
+	efivar_entry_add(entry, &efivar_ssdts);
+
+	return 0;
+}
+
+static __init int efivar_ssdt_load(void)
+{
+	struct efivar_entry *i;
+	int err;
+
+	err = efivar_init(efivar_acpi_iter, NULL, true, &efivar_ssdts);
+	if (err) {
+		pr_err("%s: efivar_init failed: %d\n", __func__, err);
+		return err;
+	}
+
+	list_for_each_entry(i, &efivar_ssdts, list) {
+		void *data;
+		unsigned long size;
+
+		pr_info("loading SSDT from EFI variable ");
+		pr_efivar_name(i->var.VariableName); pr_cont("\n");
+
+		err = efivar_entry_size(i, &size);
+		if (err) {
+			pr_err("failed to get size\n");
+			continue;
+		}
+
+		data = kmalloc(size, GFP_KERNEL);
+		if (!data)
+			continue;
+
+		err = efivar_entry_get(i, NULL, &size, data);
+		if (err) {
+			pr_err("failed to get data\n");
+			kfree(data);
+			continue;
+		}
+
+		err = acpi_load_table(data);
+		if (err) {
+			pr_err("failed to load table: %d\n", err);
+			kfree(data);
+			continue;
+		}
+	}
+
+	return 0;
+}
+#else
+static inline int efivar_ssdt_load(void) { return 0; }
+#endif
+
 /*
  * We register the efi subsystem with the firmware subsystem and the
  * efivars subsystem with the efi subsystem, if the system was booted with
@@ -218,6 +321,9 @@  static int __init efisubsys_init(void)
 	if (error)
 		goto err_put;
 
+	if (efi_enabled(EFI_RUNTIME_SERVICES))
+		efivar_ssdt_load();
+
 	error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group);
 	if (error) {
 		pr_err("efi: Sysfs attribute export failed with error %d.\n",