Merge branch 'acpi-tables'
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 25 Jul 2016 11:41:01 +0000 (13:41 +0200)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 25 Jul 2016 11:41:01 +0000 (13:41 +0200)
* acpi-tables:
  ACPI: Rename configfs.c to acpi_configfs.c to prevent link error
  ACPI: add support for loading SSDTs via configfs
  ACPI: add support for configfs
  efi / ACPI: load SSTDs from EFI variables
  spi / ACPI: add support for ACPI reconfigure notifications
  i2c / ACPI: add support for ACPI reconfigure notifications
  ACPI: add support for ACPI reconfiguration notifiers
  ACPI / scan: fix enumeration (visited) flags for bus rescans
  ACPI / documentation: add SSDT overlays documentation
  ACPI: ARM64: support for ACPI_TABLE_UPGRADE
  ACPI / tables: introduce ARCH_HAS_ACPI_TABLE_UPGRADE
  ACPI / tables: move arch-specific symbol to asm/acpi.h
  ACPI / tables: table upgrade: refactor function definitions
  ACPI / tables: table upgrade: use cacheable map for tables

Conflicts:
arch/arm64/include/asm/acpi.h

22 files changed:
Documentation/ABI/testing/configfs-acpi [new file with mode: 0644]
Documentation/acpi/ssdt-overlays.txt [new file with mode: 0644]
Documentation/kernel-parameters.txt
MAINTAINERS
arch/arm64/Kconfig
arch/arm64/include/asm/acpi.h
arch/arm64/kernel/setup.c
arch/x86/Kconfig
arch/x86/include/asm/acpi.h
arch/x86/kernel/setup.c
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/acpi_configfs.c [new file with mode: 0644]
drivers/acpi/bus.c
drivers/acpi/internal.h
drivers/acpi/scan.c
drivers/acpi/sysfs.c
drivers/acpi/tables.c
drivers/firmware/efi/efi.c
drivers/i2c/i2c-core.c
drivers/spi/spi.c
include/linux/acpi.h

diff --git a/Documentation/ABI/testing/configfs-acpi b/Documentation/ABI/testing/configfs-acpi
new file mode 100644 (file)
index 0000000..4ab4e99
--- /dev/null
@@ -0,0 +1,36 @@
+What:          /config/acpi
+Date:          July 2016
+KernelVersion: 4.8
+Contact:       linux-acpi@vger.kernel.org
+Description:
+               This represents the ACPI subsystem entry point directory. It
+               contains sub-groups corresponding to ACPI configurable options.
+
+What:          /config/acpi/table
+Date:          July 2016
+KernelVersion: 4.8
+Description:
+
+               This group contains the configuration for user defined ACPI
+               tables. The attributes of a user define table are:
+
+               aml             - a binary attribute that the user can use to
+                               fill in the ACPI aml definitions. Once the aml
+                               data is written to this file and the file is
+                               closed the table will be loaded and ACPI devices
+                               will be enumerated. To check if the operation is
+                               successful the user must check the error code
+                               for close(). If the operation is successful,
+                               subsequent writes to this attribute will fail.
+
+               The rest of the attributes are read-only and are valid only
+               after the table has been loaded by filling the aml entry:
+
+               signature       - ASCII table signature
+               length          - length of table in bytes, including the header
+               revision        - ACPI Specification minor version number
+               oem_id          - ASCII OEM identification
+               oem_table_id    - ASCII OEM table identification
+               oem_revision    - OEM revision number
+               asl_compiler_id - ASCII ASL compiler vendor ID
+               asl_compiler_revision - ASL compiler version
diff --git a/Documentation/acpi/ssdt-overlays.txt b/Documentation/acpi/ssdt-overlays.txt
new file mode 100644 (file)
index 0000000..5ae13f1
--- /dev/null
@@ -0,0 +1,172 @@
+
+In order to support ACPI open-ended hardware configurations (e.g. development
+boards) we need a way to augment the ACPI configuration provided by the firmware
+image. A common example is connecting sensors on I2C / SPI buses on development
+boards.
+
+Although this can be accomplished by creating a kernel platform driver or
+recompiling the firmware image with updated ACPI tables, neither is practical:
+the former proliferates board specific kernel code while the latter requires
+access to firmware tools which are often not publicly available.
+
+Because ACPI supports external references in AML code a more practical
+way to augment firmware ACPI configuration is by dynamically loading
+user defined SSDT tables that contain the board specific information.
+
+For example, to enumerate a Bosch BMA222E accelerometer on the I2C bus of the
+Minnowboard MAX development board exposed via the LSE connector [1], the
+following ASL code can be used:
+
+DefinitionBlock ("minnowmax.aml", "SSDT", 1, "Vendor", "Accel", 0x00000003)
+{
+    External (\_SB.I2C6, DeviceObj)
+
+    Scope (\_SB.I2C6)
+    {
+        Device (STAC)
+        {
+            Name (_ADR, Zero)
+            Name (_HID, "BMA222E")
+
+            Method (_CRS, 0, Serialized)
+            {
+                Name (RBUF, ResourceTemplate ()
+                {
+                    I2cSerialBus (0x0018, ControllerInitiated, 0x00061A80,
+                                  AddressingMode7Bit, "\\_SB.I2C6", 0x00,
+                                  ResourceConsumer, ,)
+                    GpioInt (Edge, ActiveHigh, Exclusive, PullDown, 0x0000,
+                             "\\_SB.GPO2", 0x00, ResourceConsumer, , )
+                    { // Pin list
+                        0
+                    }
+                })
+                Return (RBUF)
+            }
+        }
+    }
+}
+
+which can then be compiled to AML binary format:
+
+$ iasl minnowmax.asl
+
+Intel ACPI Component Architecture
+ASL Optimizing Compiler version 20140214-64 [Mar 29 2014]
+Copyright (c) 2000 - 2014 Intel Corporation
+
+ASL Input:     minnomax.asl - 30 lines, 614 bytes, 7 keywords
+AML Output:    minnowmax.aml - 165 bytes, 6 named objects, 1 executable opcodes
+
+[1] http://wiki.minnowboard.org/MinnowBoard_MAX#Low_Speed_Expansion_Connector_.28Top.29
+
+The resulting AML code can then be loaded by the kernel using one of the methods
+below.
+
+== Loading ACPI SSDTs from initrd ==
+
+This option allows loading of user defined SSDTs from initrd and it is useful
+when the system does not support EFI or when there is not enough EFI storage.
+
+It works in a similar way with initrd based ACPI tables override/upgrade: SSDT
+aml code must be placed in the first, uncompressed, initrd under the
+"kernel/firmware/acpi" path. Multiple files can be used and this will translate
+in loading multiple tables. Only SSDT and OEM tables are allowed. See
+initrd_table_override.txt for more details.
+
+Here is an example:
+
+# Add the raw ACPI tables to an uncompressed cpio archive.
+# They must be put into a /kernel/firmware/acpi directory inside the
+# cpio archive.
+# The uncompressed cpio archive must be the first.
+# Other, typically compressed cpio archives, must be
+# concatenated on top of the uncompressed one.
+mkdir -p kernel/firmware/acpi
+cp ssdt.aml kernel/firmware/acpi
+
+# Create the uncompressed cpio archive and concatenate the original initrd
+# 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
+
+== Loading ACPI SSDTs from configfs ==
+
+This option allows loading of user defined SSDTs from userspace via the configfs
+interface. The CONFIG_ACPI_CONFIGFS option must be select and configfs must be
+mounted. In the following examples, we assume that configfs has been mounted in
+/config.
+
+New tables can be loading by creating new directories in /config/acpi/table/ and
+writing the SSDT aml code in the aml attribute:
+
+cd /config/acpi/table
+mkdir my_ssdt
+cat ~/ssdt.aml > my_ssdt/aml
index 82b42c9..bbfb56f 100644 (file)
@@ -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.
 
index 8c20323..455b758 100644 (file)
@@ -288,6 +288,7 @@ F:  include/linux/acpi.h
 F:     include/acpi/
 F:     Documentation/acpi/
 F:     Documentation/ABI/testing/sysfs-bus-acpi
+F:     Documentation/ABI/testing/configfs-acpi
 F:     drivers/pci/*acpi*
 F:     drivers/pci/*/*acpi*
 F:     drivers/pci/*/*/*acpi*
index 5a0a691..20d5a60 100644 (file)
@@ -4,6 +4,7 @@ config ARM64
        select ACPI_GENERIC_GSI if ACPI
        select ACPI_REDUCED_HARDWARE_ONLY if ACPI
        select ARCH_HAS_DEVMEM_IS_ALLOWED
+       select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI
        select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
        select ARCH_HAS_ELF_RANDOMIZE
        select ARCH_HAS_GCOV_PROFILE_ALL
index 4b13ecd..5420cb0 100644 (file)
@@ -121,4 +121,6 @@ static inline int arm64_acpi_numa_init(void) { return -ENOSYS; }
 static inline int acpi_numa_get_nid(unsigned int cpu, u64 hwid) { return NUMA_NO_NODE; }
 #endif /* CONFIG_ACPI_NUMA */
 
+#define ACPI_TABLE_UPGRADE_MAX_PHYS MEMBLOCK_ALLOC_ACCESSIBLE
+
 #endif /*_ASM_ACPI_H*/
index 3279def..92f0e1e 100644 (file)
@@ -260,11 +260,13 @@ void __init setup_arch(char **cmdline_p)
        efi_init();
        arm64_memblock_init();
 
+       paging_init();
+
+       acpi_table_upgrade();
+
        /* Parse the ACPI tables for possible boot-time configuration */
        acpi_boot_table_init();
 
-       paging_init();
-
        if (acpi_disabled)
                unflatten_device_tree();
 
index d9a94da..f1a6244 100644 (file)
@@ -22,6 +22,7 @@ config X86
        select ANON_INODES
        select ARCH_CLOCKSOURCE_DATA
        select ARCH_DISCARD_MEMBLOCK
+       select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI
        select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
        select ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS
        select ARCH_HAS_DEVMEM_IS_ALLOWED
index 65f1e95..5391b0a 100644 (file)
@@ -169,4 +169,6 @@ static inline pgprot_t arch_apei_get_mem_attribute(phys_addr_t addr)
 }
 #endif
 
+#define ACPI_TABLE_UPGRADE_MAX_PHYS (max_low_pfn_mapped << PAGE_SHIFT)
+
 #endif /* _ASM_X86_ACPI_H */
index c4e7b39..aac91be 100644 (file)
@@ -399,10 +399,6 @@ static void __init reserve_initrd(void)
        memblock_free(ramdisk_image, ramdisk_end - ramdisk_image);
 }
 
-static void __init early_initrd_acpi_init(void)
-{
-       early_acpi_table_init((void *)initrd_start, initrd_end - initrd_start);
-}
 #else
 static void __init early_reserve_initrd(void)
 {
@@ -410,9 +406,6 @@ static void __init early_reserve_initrd(void)
 static void __init reserve_initrd(void)
 {
 }
-static void __init early_initrd_acpi_init(void)
-{
-}
 #endif /* CONFIG_BLK_DEV_INITRD */
 
 static void __init parse_setup_data(void)
@@ -1146,7 +1139,7 @@ void __init setup_arch(char **cmdline_p)
 
        reserve_initrd();
 
-       early_initrd_acpi_init();
+       acpi_table_upgrade();
 
        vsmp_init();
 
index dd76b36..5f6c76b 100644 (file)
@@ -311,9 +311,12 @@ config ACPI_CUSTOM_DSDT
        bool
        default ACPI_CUSTOM_DSDT_FILE != ""
 
+config ARCH_HAS_ACPI_TABLE_UPGRADE
+       def_bool n
+
 config ACPI_TABLE_UPGRADE
        bool "Allow upgrading ACPI tables via initrd"
-       depends on BLK_DEV_INITRD && X86
+       depends on BLK_DEV_INITRD && ARCH_HAS_ACPI_TABLE_UPGRADE
        default y
        help
          This option provides functionality to upgrade arbitrary ACPI tables
@@ -521,4 +524,12 @@ config XPOWER_PMIC_OPREGION
 
 endif
 
+config ACPI_CONFIGFS
+       tristate "ACPI configfs support"
+       select CONFIGFS_FS
+       help
+         Select this option to enable support for ACPI configuration from
+         userspace. The configurable ACPI groups will be visible under
+         /config/acpi, assuming configfs is mounted under /config.
+
 endif  # ACPI
index 251ce85..f72a83d 100644 (file)
@@ -99,5 +99,6 @@ obj-$(CONFIG_ACPI_EXTLOG)     += acpi_extlog.o
 obj-$(CONFIG_PMIC_OPREGION)    += pmic/intel_pmic.o
 obj-$(CONFIG_CRC_PMIC_OPREGION) += pmic/intel_pmic_crc.o
 obj-$(CONFIG_XPOWER_PMIC_OPREGION) += pmic/intel_pmic_xpower.o
+obj-$(CONFIG_ACPI_CONFIGFS)    += acpi_configfs.o
 
 video-objs                     += acpi_video.o video_detect.o
diff --git a/drivers/acpi/acpi_configfs.c b/drivers/acpi/acpi_configfs.c
new file mode 100644 (file)
index 0000000..146a77f
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * ACPI configfs support
+ *
+ * Copyright (c) 2016 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "ACPI configfs: " fmt
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/configfs.h>
+#include <linux/acpi.h>
+
+static struct config_group *acpi_table_group;
+
+struct acpi_table {
+       struct config_item cfg;
+       struct acpi_table_header *header;
+};
+
+static ssize_t acpi_table_aml_write(struct config_item *cfg,
+                                   const void *data, size_t size)
+{
+       const struct acpi_table_header *header = data;
+       struct acpi_table *table;
+       int ret;
+
+       table = container_of(cfg, struct acpi_table, cfg);
+
+       if (table->header) {
+               pr_err("table already loaded\n");
+               return -EBUSY;
+       }
+
+       if (header->length != size) {
+               pr_err("invalid table length\n");
+               return -EINVAL;
+       }
+
+       if (memcmp(header->signature, ACPI_SIG_SSDT, 4)) {
+               pr_err("invalid table signature\n");
+               return -EINVAL;
+       }
+
+       table = container_of(cfg, struct acpi_table, cfg);
+
+       table->header = kmemdup(header, header->length, GFP_KERNEL);
+       if (!table->header)
+               return -ENOMEM;
+
+       ret = acpi_load_table(table->header);
+       if (ret) {
+               kfree(table->header);
+               table->header = NULL;
+       }
+
+       return ret;
+}
+
+static inline struct acpi_table_header *get_header(struct config_item *cfg)
+{
+       struct acpi_table *table = container_of(cfg, struct acpi_table, cfg);
+
+       if (!table->header)
+               pr_err("table not loaded\n");
+
+       return table->header;
+}
+
+static ssize_t acpi_table_aml_read(struct config_item *cfg,
+                                  void *data, size_t size)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       if (data)
+               memcpy(data, h, h->length);
+
+       return h->length;
+}
+
+#define MAX_ACPI_TABLE_SIZE (128 * 1024)
+
+CONFIGFS_BIN_ATTR(acpi_table_, aml, NULL, MAX_ACPI_TABLE_SIZE);
+
+struct configfs_bin_attribute *acpi_table_bin_attrs[] = {
+       &acpi_table_attr_aml,
+       NULL,
+};
+
+ssize_t acpi_table_signature_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%.*s\n", ACPI_NAME_SIZE, h->signature);
+}
+
+ssize_t acpi_table_length_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%d\n", h->length);
+}
+
+ssize_t acpi_table_revision_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%d\n", h->revision);
+}
+
+ssize_t acpi_table_oem_id_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%.*s\n", ACPI_OEM_ID_SIZE, h->oem_id);
+}
+
+ssize_t acpi_table_oem_table_id_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%.*s\n", ACPI_OEM_TABLE_ID_SIZE, h->oem_table_id);
+}
+
+ssize_t acpi_table_oem_revision_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%d\n", h->oem_revision);
+}
+
+ssize_t acpi_table_asl_compiler_id_show(struct config_item *cfg, char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%.*s\n", ACPI_NAME_SIZE, h->asl_compiler_id);
+}
+
+ssize_t acpi_table_asl_compiler_revision_show(struct config_item *cfg,
+                                             char *str)
+{
+       struct acpi_table_header *h = get_header(cfg);
+
+       if (!h)
+               return -EINVAL;
+
+       return sprintf(str, "%d\n", h->asl_compiler_revision);
+}
+
+CONFIGFS_ATTR_RO(acpi_table_, signature);
+CONFIGFS_ATTR_RO(acpi_table_, length);
+CONFIGFS_ATTR_RO(acpi_table_, revision);
+CONFIGFS_ATTR_RO(acpi_table_, oem_id);
+CONFIGFS_ATTR_RO(acpi_table_, oem_table_id);
+CONFIGFS_ATTR_RO(acpi_table_, oem_revision);
+CONFIGFS_ATTR_RO(acpi_table_, asl_compiler_id);
+CONFIGFS_ATTR_RO(acpi_table_, asl_compiler_revision);
+
+struct configfs_attribute *acpi_table_attrs[] = {
+       &acpi_table_attr_signature,
+       &acpi_table_attr_length,
+       &acpi_table_attr_revision,
+       &acpi_table_attr_oem_id,
+       &acpi_table_attr_oem_table_id,
+       &acpi_table_attr_oem_revision,
+       &acpi_table_attr_asl_compiler_id,
+       &acpi_table_attr_asl_compiler_revision,
+       NULL,
+};
+
+static struct config_item_type acpi_table_type = {
+       .ct_owner = THIS_MODULE,
+       .ct_bin_attrs = acpi_table_bin_attrs,
+       .ct_attrs = acpi_table_attrs,
+};
+
+static struct config_item *acpi_table_make_item(struct config_group *group,
+                                               const char *name)
+{
+       struct acpi_table *table;
+
+       table = kzalloc(sizeof(*table), GFP_KERNEL);
+       if (!table)
+               return ERR_PTR(-ENOMEM);
+
+       config_item_init_type_name(&table->cfg, name, &acpi_table_type);
+       return &table->cfg;
+}
+
+struct configfs_group_operations acpi_table_group_ops = {
+       .make_item = acpi_table_make_item,
+};
+
+static struct config_item_type acpi_tables_type = {
+       .ct_owner = THIS_MODULE,
+       .ct_group_ops = &acpi_table_group_ops,
+};
+
+static struct config_item_type acpi_root_group_type = {
+       .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem acpi_configfs = {
+       .su_group = {
+               .cg_item = {
+                       .ci_namebuf = "acpi",
+                       .ci_type = &acpi_root_group_type,
+               },
+       },
+       .su_mutex = __MUTEX_INITIALIZER(acpi_configfs.su_mutex),
+};
+
+static int __init acpi_configfs_init(void)
+{
+       int ret;
+       struct config_group *root = &acpi_configfs.su_group;
+
+       config_group_init(root);
+
+       ret = configfs_register_subsystem(&acpi_configfs);
+       if (ret)
+               return ret;
+
+       acpi_table_group = configfs_register_default_group(root, "table",
+                                                          &acpi_tables_type);
+       return PTR_ERR_OR_ZERO(acpi_table_group);
+}
+module_init(acpi_configfs_init);
+
+static void __exit acpi_configfs_exit(void)
+{
+       configfs_unregister_default_group(acpi_table_group);
+       configfs_unregister_subsystem(&acpi_configfs);
+}
+module_exit(acpi_configfs_exit);
+
+MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>");
+MODULE_DESCRIPTION("ACPI configfs support");
+MODULE_LICENSE("GPL v2");
index 262ca31..97e270e 100644 (file)
@@ -990,6 +990,13 @@ void __init acpi_subsystem_init(void)
        }
 }
 
+static acpi_status acpi_bus_table_handler(u32 event, void *table, void *context)
+{
+       acpi_scan_table_handler(event, table, context);
+
+       return acpi_sysfs_table_handler(event, table, context);
+}
+
 static int __init acpi_bus_init(void)
 {
        int result;
@@ -1043,6 +1050,8 @@ static int __init acpi_bus_init(void)
         * _PDC control method may load dynamic SSDT tables,
         * and we need to install the table handler before that.
         */
+       status = acpi_install_table_handler(acpi_bus_table_handler, NULL);
+
        acpi_sysfs_init();
 
        acpi_early_processor_set_pdc();
index 27cc7fe..940218f 100644 (file)
@@ -87,6 +87,9 @@ bool acpi_queue_hotplug_work(struct work_struct *work);
 void acpi_device_hotplug(struct acpi_device *adev, u32 src);
 bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent);
 
+acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context);
+void acpi_scan_table_handler(u32 event, void *table, void *context);
+
 /* --------------------------------------------------------------------------
                      Device Node Initialization / Removal
    -------------------------------------------------------------------------- */
index 5f28cf7..405056b 100644 (file)
@@ -494,6 +494,8 @@ static void acpi_device_del(struct acpi_device *device)
        device_del(&device->dev);
 }
 
+static BLOCKING_NOTIFIER_HEAD(acpi_reconfig_chain);
+
 static LIST_HEAD(acpi_device_del_list);
 static DEFINE_MUTEX(acpi_device_del_lock);
 
@@ -514,6 +516,9 @@ static void acpi_device_del_work_fn(struct work_struct *work_not_used)
 
                mutex_unlock(&acpi_device_del_lock);
 
+               blocking_notifier_call_chain(&acpi_reconfig_chain,
+                                            ACPI_RECONFIG_DEVICE_REMOVE, adev);
+
                acpi_device_del(adev);
                /*
                 * Drop references to all power resources that might have been
@@ -1406,7 +1411,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,
        acpi_bus_get_flags(device);
        device->flags.match_driver = false;
        device->flags.initialized = true;
-       device->flags.visited = false;
+       acpi_device_clear_enumerated(device);
        device_initialize(&device->dev);
        dev_set_uevent_suppress(&device->dev, true);
        acpi_init_coherency(device);
@@ -1676,15 +1681,20 @@ static void acpi_default_enumeration(struct acpi_device *device)
        bool is_spi_i2c_slave = false;
 
        /*
-        * Do not enemerate SPI/I2C slaves as they will be enuerated by their
+        * Do not enumerate SPI/I2C slaves as they will be enumerated by their
         * respective parents.
         */
        INIT_LIST_HEAD(&resource_list);
        acpi_dev_get_resources(device, &resource_list, acpi_check_spi_i2c_slave,
                               &is_spi_i2c_slave);
        acpi_dev_free_resource_list(&resource_list);
-       if (!is_spi_i2c_slave)
+       if (!is_spi_i2c_slave) {
                acpi_create_platform_device(device);
+               acpi_device_set_enumerated(device);
+       } else {
+               blocking_notifier_call_chain(&acpi_reconfig_chain,
+                                            ACPI_RECONFIG_DEVICE_ADD, device);
+       }
 }
 
 static const struct acpi_device_id generic_device_ids[] = {
@@ -1751,7 +1761,7 @@ static void acpi_bus_attach(struct acpi_device *device)
        acpi_bus_get_status(device);
        /* Skip devices that are not present. */
        if (!acpi_device_is_present(device)) {
-               device->flags.visited = false;
+               acpi_device_clear_enumerated(device);
                device->flags.power_manageable = 0;
                return;
        }
@@ -1766,7 +1776,7 @@ static void acpi_bus_attach(struct acpi_device *device)
 
                device->flags.initialized = true;
        }
-       device->flags.visited = false;
+
        ret = acpi_scan_attach_handler(device);
        if (ret < 0)
                return;
@@ -1780,7 +1790,6 @@ static void acpi_bus_attach(struct acpi_device *device)
                if (!ret && device->pnp.type.platform_id)
                        acpi_default_enumeration(device);
        }
-       device->flags.visited = true;
 
  ok:
        list_for_each_entry(child, &device->children, node)
@@ -1872,7 +1881,7 @@ void acpi_bus_trim(struct acpi_device *adev)
         */
        acpi_device_set_power(adev, ACPI_STATE_D3_COLD);
        adev->flags.initialized = false;
-       adev->flags.visited = false;
+       acpi_device_clear_enumerated(adev);
 }
 EXPORT_SYMBOL_GPL(acpi_bus_trim);
 
@@ -1916,6 +1925,8 @@ static int acpi_bus_scan_fixed(void)
        return result < 0 ? result : 0;
 }
 
+static bool acpi_scan_initialized;
+
 int __init acpi_scan_init(void)
 {
        int result;
@@ -1960,6 +1971,8 @@ int __init acpi_scan_init(void)
 
        acpi_update_all_gpes();
 
+       acpi_scan_initialized = true;
+
  out:
        mutex_unlock(&acpi_scan_lock);
        return result;
@@ -2003,3 +2016,57 @@ int __init __acpi_probe_device_table(struct acpi_probe_entry *ap_head, int nr)
 
        return count;
 }
+
+struct acpi_table_events_work {
+       struct work_struct work;
+       void *table;
+       u32 event;
+};
+
+static void acpi_table_events_fn(struct work_struct *work)
+{
+       struct acpi_table_events_work *tew;
+
+       tew = container_of(work, struct acpi_table_events_work, work);
+
+       if (tew->event == ACPI_TABLE_EVENT_LOAD) {
+               acpi_scan_lock_acquire();
+               acpi_bus_scan(ACPI_ROOT_OBJECT);
+               acpi_scan_lock_release();
+       }
+
+       kfree(tew);
+}
+
+void acpi_scan_table_handler(u32 event, void *table, void *context)
+{
+       struct acpi_table_events_work *tew;
+
+       if (!acpi_scan_initialized)
+               return;
+
+       if (event != ACPI_TABLE_EVENT_LOAD)
+               return;
+
+       tew = kmalloc(sizeof(*tew), GFP_KERNEL);
+       if (!tew)
+               return;
+
+       INIT_WORK(&tew->work, acpi_table_events_fn);
+       tew->table = table;
+       tew->event = event;
+
+       schedule_work(&tew->work);
+}
+
+int acpi_reconfig_notifier_register(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_register(&acpi_reconfig_chain, nb);
+}
+EXPORT_SYMBOL(acpi_reconfig_notifier_register);
+
+int acpi_reconfig_notifier_unregister(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_unregister(&acpi_reconfig_chain, nb);
+}
+EXPORT_SYMBOL(acpi_reconfig_notifier_unregister);
index 4b3a9e2..358165e 100644 (file)
@@ -378,8 +378,7 @@ static void acpi_table_attr_init(struct acpi_table_attr *table_attr,
        return;
 }
 
-static acpi_status
-acpi_sysfs_table_handler(u32 event, void *table, void *context)
+acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context)
 {
        struct acpi_table_attr *table_attr;
 
@@ -452,9 +451,8 @@ static int acpi_tables_sysfs_init(void)
 
        kobject_uevent(tables_kobj, KOBJ_ADD);
        kobject_uevent(dynamic_tables_kobj, KOBJ_ADD);
-       status = acpi_install_table_handler(acpi_sysfs_table_handler, NULL);
 
-       return ACPI_FAILURE(status) ? -EINVAL : 0;
+       return 0;
 err_dynamic_tables:
        kobject_put(tables_kobj);
 err:
index a372f9e..9f0ad6e 100644 (file)
@@ -34,6 +34,8 @@
 #include <linux/bootmem.h>
 #include <linux/earlycpio.h>
 #include <linux/memblock.h>
+#include <linux/initrd.h>
+#include <linux/acpi.h>
 #include "internal.h"
 
 #ifdef CONFIG_ACPI_CUSTOM_DSDT
@@ -481,8 +483,10 @@ static DECLARE_BITMAP(acpi_initrd_installed, NR_ACPI_INITRD_TABLES);
 
 #define MAP_CHUNK_SIZE   (NR_FIX_BTMAPS << PAGE_SHIFT)
 
-static void __init acpi_table_initrd_init(void *data, size_t size)
+void __init acpi_table_upgrade(void)
 {
+       void *data = (void *)initrd_start;
+       size_t size = initrd_end - initrd_start;
        int sig, no, table_nr = 0, total_offset = 0;
        long offset = 0;
        struct acpi_table_header *table;
@@ -540,7 +544,7 @@ static void __init acpi_table_initrd_init(void *data, size_t size)
                return;
 
        acpi_tables_addr =
-               memblock_find_in_range(0, max_low_pfn_mapped << PAGE_SHIFT,
+               memblock_find_in_range(0, ACPI_TABLE_UPGRADE_MAX_PHYS,
                                       all_tables_size, PAGE_SIZE);
        if (!acpi_tables_addr) {
                WARN_ON(1);
@@ -578,10 +582,10 @@ static void __init acpi_table_initrd_init(void *data, size_t size)
                        clen = size;
                        if (clen > MAP_CHUNK_SIZE - slop)
                                clen = MAP_CHUNK_SIZE - slop;
-                       dest_p = early_ioremap(dest_addr & PAGE_MASK,
-                                                clen + slop);
+                       dest_p = early_memremap(dest_addr & PAGE_MASK,
+                                               clen + slop);
                        memcpy(dest_p + slop, src_p, clen);
-                       early_iounmap(dest_p, clen + slop);
+                       early_memunmap(dest_p, clen + slop);
                        src_p += clen;
                        dest_addr += clen;
                        size -= clen;
@@ -696,10 +700,6 @@ next_table:
        }
 }
 #else
-static void __init acpi_table_initrd_init(void *data, size_t size)
-{
-}
-
 static acpi_status
 acpi_table_initrd_override(struct acpi_table_header *existing_table,
                           acpi_physical_address *address,
@@ -742,11 +742,6 @@ acpi_os_table_override(struct acpi_table_header *existing_table,
        return AE_OK;
 }
 
-void __init early_acpi_table_init(void *data, size_t size)
-{
-       acpi_table_initrd_init(data, size);
-}
-
 /*
  * acpi_table_init()
  *
index 05509f3..8730fd4 100644 (file)
@@ -24,6 +24,9 @@
 #include <linux/of_fdt.h>
 #include <linux/io.h>
 #include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/ucs2_string.h>
 
 #include <asm/early_ioremap.h>
 
@@ -195,6 +198,96 @@ 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] __initdata;
+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 __init int efivar_ssdt_iter(efi_char16_t *name, efi_guid_t vendor,
+                                  unsigned long name_size, void *data)
+{
+       struct efivar_entry *entry;
+       struct list_head *list = data;
+       char utf8_name[EFIVAR_SSDT_NAME_MAX];
+       int limit = min_t(unsigned long, EFIVAR_SSDT_NAME_MAX, name_size);
+
+       ucs2_as_utf8(utf8_name, name, limit - 1);
+       if (strncmp(utf8_name, efivar_ssdt, limit) != 0)
+               return 0;
+
+       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return 0;
+
+       memcpy(entry->var.VariableName, name, name_size);
+       memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t));
+
+       efivar_entry_add(entry, list);
+
+       return 0;
+}
+
+static __init int efivar_ssdt_load(void)
+{
+       LIST_HEAD(entries);
+       struct efivar_entry *entry, *aux;
+       unsigned long size;
+       void *data;
+       int ret;
+
+       ret = efivar_init(efivar_ssdt_iter, &entries, true, &entries);
+
+       list_for_each_entry_safe(entry, aux, &entries, list) {
+               pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt,
+                       &entry->var.VendorGuid);
+
+               list_del(&entry->list);
+
+               ret = efivar_entry_size(entry, &size);
+               if (ret) {
+                       pr_err("failed to get var size\n");
+                       goto free_entry;
+               }
+
+               data = kmalloc(size, GFP_KERNEL);
+               if (!data)
+                       goto free_entry;
+
+               ret = efivar_entry_get(entry, NULL, &size, data);
+               if (ret) {
+                       pr_err("failed to get var data\n");
+                       goto free_data;
+               }
+
+               ret = acpi_load_table(data);
+               if (ret) {
+                       pr_err("failed to load table: %d\n", ret);
+                       goto free_data;
+               }
+
+               goto free_entry;
+
+free_data:
+               kfree(data);
+
+free_entry:
+               kfree(entry);
+       }
+
+       return ret;
+}
+#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 +311,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",
index af11b65..74e5aea 100644 (file)
@@ -107,12 +107,11 @@ struct acpi_i2c_lookup {
        acpi_handle device_handle;
 };
 
-static int acpi_i2c_find_address(struct acpi_resource *ares, void *data)
+static int acpi_i2c_fill_info(struct acpi_resource *ares, void *data)
 {
        struct acpi_i2c_lookup *lookup = data;
        struct i2c_board_info *info = lookup->info;
        struct acpi_resource_i2c_serialbus *sb;
-       acpi_handle adapter_handle;
        acpi_status status;
 
        if (info->addr || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS)
@@ -122,80 +121,102 @@ static int acpi_i2c_find_address(struct acpi_resource *ares, void *data)
        if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C)
                return 1;
 
-       /*
-        * Extract the ResourceSource and make sure that the handle matches
-        * with the I2C adapter handle.
-        */
        status = acpi_get_handle(lookup->device_handle,
                                 sb->resource_source.string_ptr,
-                                &adapter_handle);
-       if (ACPI_SUCCESS(status) && adapter_handle == lookup->adapter_handle) {
-               info->addr = sb->slave_address;
-               if (sb->access_mode == ACPI_I2C_10BIT_MODE)
-                       info->flags |= I2C_CLIENT_TEN;
-       }
+                                &lookup->adapter_handle);
+       if (!ACPI_SUCCESS(status))
+               return 1;
+
+       info->addr = sb->slave_address;
+       if (sb->access_mode == ACPI_I2C_10BIT_MODE)
+               info->flags |= I2C_CLIENT_TEN;
 
        return 1;
 }
 
-static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level,
-                                      void *data, void **return_value)
+static int acpi_i2c_get_info(struct acpi_device *adev,
+                            struct i2c_board_info *info,
+                            acpi_handle *adapter_handle)
 {
-       struct i2c_adapter *adapter = data;
        struct list_head resource_list;
-       struct acpi_i2c_lookup lookup;
        struct resource_entry *entry;
-       struct i2c_board_info info;
-       struct acpi_device *adev;
+       struct acpi_i2c_lookup lookup;
        int ret;
 
-       if (acpi_bus_get_device(handle, &adev))
-               return AE_OK;
-       if (acpi_bus_get_status(adev) || !adev->status.present)
-               return AE_OK;
+       if (acpi_bus_get_status(adev) || !adev->status.present ||
+           acpi_device_enumerated(adev))
+               return -EINVAL;
 
-       memset(&info, 0, sizeof(info));
-       info.fwnode = acpi_fwnode_handle(adev);
+       memset(info, 0, sizeof(*info));
+       info->fwnode = acpi_fwnode_handle(adev);
 
        memset(&lookup, 0, sizeof(lookup));
-       lookup.adapter_handle = ACPI_HANDLE(&adapter->dev);
-       lookup.device_handle = handle;
-       lookup.info = &info;
+       lookup.device_handle = acpi_device_handle(adev);
+       lookup.info = info;
 
-       /*
-        * Look up for I2cSerialBus resource with ResourceSource that
-        * matches with this adapter.
-        */
+       /* Look up for I2cSerialBus resource */
        INIT_LIST_HEAD(&resource_list);
        ret = acpi_dev_get_resources(adev, &resource_list,
-                                    acpi_i2c_find_address, &lookup);
+                                    acpi_i2c_fill_info, &lookup);
        acpi_dev_free_resource_list(&resource_list);
 
-       if (ret < 0 || !info.addr)
-               return AE_OK;
+       if (ret < 0 || !info->addr)
+               return -EINVAL;
+
+       *adapter_handle = lookup.adapter_handle;
 
        /* Then fill IRQ number if any */
        ret = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
        if (ret < 0)
-               return AE_OK;
+               return -EINVAL;
 
        resource_list_for_each_entry(entry, &resource_list) {
                if (resource_type(entry->res) == IORESOURCE_IRQ) {
-                       info.irq = entry->res->start;
+                       info->irq = entry->res->start;
                        break;
                }
        }
 
        acpi_dev_free_resource_list(&resource_list);
 
+       strlcpy(info->type, dev_name(&adev->dev), sizeof(info->type));
+
+       return 0;
+}
+
+static void acpi_i2c_register_device(struct i2c_adapter *adapter,
+                                    struct acpi_device *adev,
+                                    struct i2c_board_info *info)
+{
        adev->power.flags.ignore_parent = true;
-       strlcpy(info.type, dev_name(&adev->dev), sizeof(info.type));
-       if (!i2c_new_device(adapter, &info)) {
+       acpi_device_set_enumerated(adev);
+
+       if (!i2c_new_device(adapter, info)) {
                adev->power.flags.ignore_parent = false;
                dev_err(&adapter->dev,
                        "failed to add I2C device %s from ACPI\n",
                        dev_name(&adev->dev));
        }
+}
+
+static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level,
+                                      void *data, void **return_value)
+{
+       struct i2c_adapter *adapter = data;
+       struct acpi_device *adev;
+       acpi_handle adapter_handle;
+       struct i2c_board_info info;
+
+       if (acpi_bus_get_device(handle, &adev))
+               return AE_OK;
+
+       if (acpi_i2c_get_info(adev, &info, &adapter_handle))
+               return AE_OK;
+
+       if (adapter_handle != ACPI_HANDLE(&adapter->dev))
+               return AE_OK;
+
+       acpi_i2c_register_device(adapter, adev, &info);
 
        return AE_OK;
 }
@@ -225,8 +246,80 @@ static void acpi_i2c_register_devices(struct i2c_adapter *adap)
                dev_warn(&adap->dev, "failed to enumerate I2C slaves\n");
 }
 
+static int acpi_i2c_match_adapter(struct device *dev, void *data)
+{
+       struct i2c_adapter *adapter = i2c_verify_adapter(dev);
+
+       if (!adapter)
+               return 0;
+
+       return ACPI_HANDLE(dev) == (acpi_handle)data;
+}
+
+static int acpi_i2c_match_device(struct device *dev, void *data)
+{
+       return ACPI_COMPANION(dev) == data;
+}
+
+static struct i2c_adapter *acpi_i2c_find_adapter_by_handle(acpi_handle handle)
+{
+       struct device *dev;
+
+       dev = bus_find_device(&i2c_bus_type, NULL, handle,
+                             acpi_i2c_match_adapter);
+       return dev ? i2c_verify_adapter(dev) : NULL;
+}
+
+static struct i2c_client *acpi_i2c_find_client_by_adev(struct acpi_device *adev)
+{
+       struct device *dev;
+
+       dev = bus_find_device(&i2c_bus_type, NULL, adev, acpi_i2c_match_device);
+       return dev ? i2c_verify_client(dev) : NULL;
+}
+
+static int acpi_i2c_notify(struct notifier_block *nb, unsigned long value,
+                          void *arg)
+{
+       struct acpi_device *adev = arg;
+       struct i2c_board_info info;
+       acpi_handle adapter_handle;
+       struct i2c_adapter *adapter;
+       struct i2c_client *client;
+
+       switch (value) {
+       case ACPI_RECONFIG_DEVICE_ADD:
+               if (acpi_i2c_get_info(adev, &info, &adapter_handle))
+                       break;
+
+               adapter = acpi_i2c_find_adapter_by_handle(adapter_handle);
+               if (!adapter)
+                       break;
+
+               acpi_i2c_register_device(adapter, adev, &info);
+               break;
+       case ACPI_RECONFIG_DEVICE_REMOVE:
+               if (!acpi_device_enumerated(adev))
+                       break;
+
+               client = acpi_i2c_find_client_by_adev(adev);
+               if (!client)
+                       break;
+
+               i2c_unregister_device(client);
+               put_device(&client->dev);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block i2c_acpi_notifier = {
+       .notifier_call = acpi_i2c_notify,
+};
 #else /* CONFIG_ACPI */
 static inline void acpi_i2c_register_devices(struct i2c_adapter *adap) { }
+extern struct notifier_block i2c_acpi_notifier;
 #endif /* CONFIG_ACPI */
 
 #ifdef CONFIG_ACPI_I2C_OPREGION
@@ -1089,6 +1182,8 @@ void i2c_unregister_device(struct i2c_client *client)
 {
        if (client->dev.of_node)
                of_node_clear_flag(client->dev.of_node, OF_POPULATED);
+       if (ACPI_COMPANION(&client->dev))
+               acpi_device_clear_enumerated(ACPI_COMPANION(&client->dev));
        device_unregister(&client->dev);
 }
 EXPORT_SYMBOL_GPL(i2c_unregister_device);
@@ -2117,6 +2212,8 @@ static int __init i2c_init(void)
 
        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
+       if (IS_ENABLED(CONFIG_ACPI))
+               WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));
 
        return 0;
 
@@ -2132,6 +2229,8 @@ bus_err:
 
 static void __exit i2c_exit(void)
 {
+       if (IS_ENABLED(CONFIG_ACPI))
+               WARN_ON(acpi_reconfig_notifier_unregister(&i2c_acpi_notifier));
        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_unregister(&i2c_of_notifier));
        i2c_del_driver(&dummy_driver);
index 77e6e45..7589c8a 100644 (file)
@@ -622,6 +622,8 @@ void spi_unregister_device(struct spi_device *spi)
 
        if (spi->dev.of_node)
                of_node_clear_flag(spi->dev.of_node, OF_POPULATED);
+       if (ACPI_COMPANION(&spi->dev))
+               acpi_device_clear_enumerated(ACPI_COMPANION(&spi->dev));
        device_unregister(&spi->dev);
 }
 EXPORT_SYMBOL_GPL(spi_unregister_device);
@@ -1646,18 +1648,15 @@ static int acpi_spi_add_resource(struct acpi_resource *ares, void *data)
        return 1;
 }
 
-static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level,
-                                      void *data, void **return_value)
+static acpi_status acpi_register_spi_device(struct spi_master *master,
+                                           struct acpi_device *adev)
 {
-       struct spi_master *master = data;
        struct list_head resource_list;
-       struct acpi_device *adev;
        struct spi_device *spi;
        int ret;
 
-       if (acpi_bus_get_device(handle, &adev))
-               return AE_OK;
-       if (acpi_bus_get_status(adev) || !adev->status.present)
+       if (acpi_bus_get_status(adev) || !adev->status.present ||
+           acpi_device_enumerated(adev))
                return AE_OK;
 
        spi = spi_alloc_device(master);
@@ -1683,6 +1682,8 @@ static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level,
        if (spi->irq < 0)
                spi->irq = acpi_dev_gpio_irq_get(adev, 0);
 
+       acpi_device_set_enumerated(adev);
+
        adev->power.flags.ignore_parent = true;
        strlcpy(spi->modalias, acpi_device_hid(adev), sizeof(spi->modalias));
        if (spi_add_device(spi)) {
@@ -1695,6 +1696,18 @@ static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level,
        return AE_OK;
 }
 
+static acpi_status acpi_spi_add_device(acpi_handle handle, u32 level,
+                                      void *data, void **return_value)
+{
+       struct spi_master *master = data;
+       struct acpi_device *adev;
+
+       if (acpi_bus_get_device(handle, &adev))
+               return AE_OK;
+
+       return acpi_register_spi_device(master, adev);
+}
+
 static void acpi_register_spi_devices(struct spi_master *master)
 {
        acpi_status status;
@@ -3107,6 +3120,77 @@ static struct notifier_block spi_of_notifier = {
 extern struct notifier_block spi_of_notifier;
 #endif /* IS_ENABLED(CONFIG_OF_DYNAMIC) */
 
+#if IS_ENABLED(CONFIG_ACPI)
+static int spi_acpi_master_match(struct device *dev, const void *data)
+{
+       return ACPI_COMPANION(dev->parent) == data;
+}
+
+static int spi_acpi_device_match(struct device *dev, void *data)
+{
+       return ACPI_COMPANION(dev) == data;
+}
+
+static struct spi_master *acpi_spi_find_master_by_adev(struct acpi_device *adev)
+{
+       struct device *dev;
+
+       dev = class_find_device(&spi_master_class, NULL, adev,
+                               spi_acpi_master_match);
+       if (!dev)
+               return NULL;
+
+       return container_of(dev, struct spi_master, dev);
+}
+
+static struct spi_device *acpi_spi_find_device_by_adev(struct acpi_device *adev)
+{
+       struct device *dev;
+
+       dev = bus_find_device(&spi_bus_type, NULL, adev, spi_acpi_device_match);
+
+       return dev ? to_spi_device(dev) : NULL;
+}
+
+static int acpi_spi_notify(struct notifier_block *nb, unsigned long value,
+                          void *arg)
+{
+       struct acpi_device *adev = arg;
+       struct spi_master *master;
+       struct spi_device *spi;
+
+       switch (value) {
+       case ACPI_RECONFIG_DEVICE_ADD:
+               master = acpi_spi_find_master_by_adev(adev->parent);
+               if (!master)
+                       break;
+
+               acpi_register_spi_device(master, adev);
+               put_device(&master->dev);
+               break;
+       case ACPI_RECONFIG_DEVICE_REMOVE:
+               if (!acpi_device_enumerated(adev))
+                       break;
+
+               spi = acpi_spi_find_device_by_adev(adev);
+               if (!spi)
+                       break;
+
+               spi_unregister_device(spi);
+               put_device(&spi->dev);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block spi_acpi_notifier = {
+       .notifier_call = acpi_spi_notify,
+};
+#else
+extern struct notifier_block spi_acpi_notifier;
+#endif
+
 static int __init spi_init(void)
 {
        int     status;
@@ -3127,6 +3211,8 @@ static int __init spi_init(void)
 
        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
+       if (IS_ENABLED(CONFIG_ACPI))
+               WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));
 
        return 0;
 
index 9515db6..08235a6 100644 (file)
@@ -208,7 +208,6 @@ void acpi_boot_table_init (void);
 int acpi_mps_check (void);
 int acpi_numa_init (void);
 
-void early_acpi_table_init(void *data, size_t size);
 int acpi_table_init (void);
 int acpi_table_parse(char *id, acpi_tbl_table_handler handler);
 int __init acpi_parse_entries(char *id, unsigned long table_size,
@@ -546,6 +545,24 @@ void acpi_walk_dep_device_list(acpi_handle handle);
 struct platform_device *acpi_create_platform_device(struct acpi_device *);
 #define ACPI_PTR(_ptr) (_ptr)
 
+static inline void acpi_device_set_enumerated(struct acpi_device *adev)
+{
+       adev->flags.visited = true;
+}
+
+static inline void acpi_device_clear_enumerated(struct acpi_device *adev)
+{
+       adev->flags.visited = false;
+}
+
+enum acpi_reconfig_event  {
+       ACPI_RECONFIG_DEVICE_ADD = 0,
+       ACPI_RECONFIG_DEVICE_REMOVE,
+};
+
+int acpi_reconfig_notifier_register(struct notifier_block *nb);
+int acpi_reconfig_notifier_unregister(struct notifier_block *nb);
+
 #else  /* !CONFIG_ACPI */
 
 #define acpi_disabled 1
@@ -602,7 +619,6 @@ static inline const char *acpi_dev_name(struct acpi_device *adev)
        return NULL;
 }
 
-static inline void early_acpi_table_init(void *data, size_t size) { }
 static inline void acpi_early_init(void) { }
 static inline void acpi_subsystem_init(void) { }
 
@@ -692,6 +708,24 @@ static inline enum dev_dma_attr acpi_get_dma_attr(struct acpi_device *adev)
 
 #define ACPI_PTR(_ptr) (NULL)
 
+static inline void acpi_device_set_enumerated(struct acpi_device *adev)
+{
+}
+
+static inline void acpi_device_clear_enumerated(struct acpi_device *adev)
+{
+}
+
+static inline int acpi_reconfig_notifier_register(struct notifier_block *nb)
+{
+       return -EINVAL;
+}
+
+static inline int acpi_reconfig_notifier_unregister(struct notifier_block *nb)
+{
+       return -EINVAL;
+}
+
 #endif /* !CONFIG_ACPI */
 
 #ifdef CONFIG_ACPI
@@ -1011,4 +1045,10 @@ static inline struct fwnode_handle *acpi_get_next_subnode(struct device *dev,
 #define acpi_probe_device_table(t)     ({ int __r = 0; __r;})
 #endif
 
+#ifdef CONFIG_ACPI_TABLE_UPGRADE
+void acpi_table_upgrade(void);
+#else
+static inline void acpi_table_upgrade(void) { }
+#endif
+
 #endif /*_LINUX_ACPI_H*/