Discussion:
[RFC PATCH v2 00/16] ACPI based system device hotplug framework
(too old to reply)
Jiang Liu
2012-08-04 12:13:47 UTC
Permalink
From: Jiang Liu <***@gmail.com>

The patchset is based on v3.5-rc6 and you may pull them from:
git://github.com/jiangliu/linux.git acpihp

Modern high-end server may support advanced hotplug features for system
devices, including physical processor, memory board, IO extension board
and/or computer node. The ACPI specifications have provided standard
interfaces between firmware and OS to support device hotplug at runtime.
This patch series provide an ACPI based hotplug framework to support system
device hotplug at runtime, which will replace current existing ACPI device
driver based CPU/memory/CONTAINER hotplug mechanism.

The new ACPI based hotplug framework is modelled after PCI hotplug
architecture and target to achieve following goals:
1) Provide a mechanism to detect hotplug slots by checking ACPI _EJ0 method,
ACPI PRCT (platform RAS capabilities table) and other platform specific
mechanisms.

2) Unify the way to enumerate ACPI based hotplug slots. All hotplug slots
will be enumerated by the enumeration driver, instead of by ACPI device
drivers.

3) Dynamically create/destroy ACPI hotplug slots. For example, new ACPI
hotplug slots may be created when hot-adding a computer node if the node
contains some memory hotplug slots.

4) Unify the way to handle ACPI hotplug events. All ACPI hotplug events
for system devices will be handled by a generic ACPI hotplug driver,
instead of handled by ACPI device drivers.

5) Solve dependencies among hotplug slots. You need first to remove the
memory device before removing a physical processor if a hotpluggable memory
device is connected to a hotpluggable physical processor.

6) Provide better error handling and recover.

7) Provide interface to cancel ongoing hotplug operations. It may take a
very long time to remove a memory device, so provide interface to cancel
the operation.

8) Support new RAS features, such as socket/memory migration.

9) Provide better user interfaces to the hotplug functionalities.

The new hotplug framework includes four logical components.

The first is an ACPI hotplug slot enumerator, which enumerates ACPI hotplug
slots on load and provides callbacks to manage those hotplug slots. An ACPI
hotplug slot is an abstraction of receptacles, where a group of system
devices could be connected to.

The second is a device class for ACPI hotplug slots, named acpihp_slot_class.
All ACPI hotplug slot devices will be associated with acpihp_slot_class.

The third is a platform independent class driver for ACPI hotplug slots,
which registers itself onto acpihp_slot_class and manages all ACPI hotplug
slots in system. This hotplug driver handles ACPI hotplug events, processes
user requests and manages slot state machine accoring to user requests.

The fourth is a series of ACPI device drivers, for CPU, memory, PCI host
bridge, IOAPIC and ACPI CONTAINER. These ACPI device drivers provide device
specific callbacks for the hotplug driver to add/remove system devices at
runtime.

This patch set implements the first and second parts, which enumerates
hotplug slots and creates sysfs entries for each slot as below.

linux-drf:/sys/devices/LNXSYSTM:00/acpihp # ll
drwxr-xr-x 4 root root 0 Jul 28 16:00 NODE00
drwxr-xr-x 3 root root 0 Jul 28 16:00 NODE01
drwxr-xr-x 3 root root 0 Jul 28 16:00 NODE02

linux-drf:/sys/devices/LNXSYSTM:00/acpihp/NODE00 # ll
drwxr-xr-x 3 root root 0 Jul 28 16:00 IOX01
-r--r--r-- 1 root root 65536 Jul 28 16:01 capabilities
lrwxrwxrwx 1 root root 0 Jul 28 16:00 device -> ../../../LNXSYSTM:00
-r--r--r-- 1 root root 65536 Jul 28 16:01 object
drwxr-xr-x 2 root root 0 Jul 28 16:01 power
-r--r--r-- 1 root root 65536 Jul 28 16:01 state
-r--r--r-- 1 root root 65536 Jul 28 16:01 status
lrwxrwxrwx 1 root root 0 Jul 28 16:00 subsystem -> ../../../../class/acpihp
-r--r--r-- 1 root root 65536 Jul 28 16:01 type
-rw-r--r-- 1 root root 65536 Jul 28 16:01 uevent

linux-drf:/sys/bus/acpi/acpihp # ls
NODE00 NODE00.IOX01 NODE01 NODE02

linux-drf:/sys/bus/acpi/acpihp # ll
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE00 ->
../../../devices/LNXSYSTM:00/acpihp/NODE00
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE00.IOX01 ->
../../../devices/LNXSYSTM:00/acpihp/NODE00/IOX01
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE01 ->
../../../devices/LNXSYSTM:00/acpihp/NODE01
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE02 ->
../../../devices/LNXSYSTM:00/acpihp/NODE02

V1->V2 changes:
1) implement the ACPI hotplug driver
2) enhance ACPI container driver to support new hotplug framework
TODO:
1) handle ACPI hotplug events in ACPI hotplug driver
2) handle dependencies among slots by evaluating ACPI _EDL
3) enhance ACPI processor, memory and PCI root drivers to support new
hotplug framework
4) develop an ACPI driver for IOAPIC
5) thorough tests:)

Jiang Liu (16):
ACPIHP: introduce a framework for ACPI based system device hotplug
ACPIHP: ACPI system device hotplug slot enumerator
ACPIHP: detect ACPI hotplug slots by checking ACPI _EJ0 method
ACPI: introduce interfaces to manage ACPI device reference count
ACPIHP: scan and walk ACPI devices connecting to an ACPI hotplug slot
ACPIHP: group devices connecting to a hotplug slot according to
device types
ACPIHP: add callbacks into acpi_device_ops to support new hotplug
framework
ACPIHP: provide interfaces to associate driver specific data to
hotplug slots
ACPIHP: implement utility functions to support system device hotplug
ACPIHP: system device hotplug driver skeleton
ACPIHP: analyse dependences among ACPI system device hotplug slots
ACPIHP: cancel inprogress ACPI system device hotplug operations
ACPIHP: configure/unconfigure system devices connecting to a hotplug
slot
ACPIHP: implement the state machine for ACPI hotplug slots
ACPIHP: implement ACPI hotplug sysfs interfaces
ACPIHP: enhance ACPI container driver to support new hotplug
framework

drivers/acpi/Kconfig | 45 ++
drivers/acpi/Makefile | 2 +
drivers/acpi/bus.c | 22 +-
drivers/acpi/container.c | 201 ++------
drivers/acpi/hotplug/Makefile | 18 +
drivers/acpi/hotplug/acpihp_drv.h | 107 ++++
drivers/acpi/hotplug/cancel.c | 171 +++++++
drivers/acpi/hotplug/configure.c | 349 +++++++++++++
drivers/acpi/hotplug/core.c | 874 ++++++++++++++++++++++++++++++++
drivers/acpi/hotplug/dependency.c | 167 ++++++
drivers/acpi/hotplug/device.c | 193 +++++++
drivers/acpi/hotplug/drv_main.c | 337 ++++++++++++
drivers/acpi/hotplug/slot_enum.c | 469 +++++++++++++++++
drivers/acpi/hotplug/slot_enum_ej0.c | 113 +++++
drivers/acpi/hotplug/state_machine.c | 633 +++++++++++++++++++++++
drivers/acpi/hotplug/sysfs.c | 246 +++++++++
drivers/acpi/internal.h | 5 +
drivers/acpi/scan.c | 17 +-
drivers/gpu/drm/i915/intel_opregion.c | 2 +
drivers/gpu/drm/nouveau/nouveau_acpi.c | 1 +
drivers/pci/hotplug/acpiphp_glue.c | 9 +-
drivers/pci/hotplug/acpiphp_ibm.c | 5 +-
drivers/pci/hotplug/sgi_hotplug.c | 6 +-
drivers/platform/x86/thinkpad_acpi.c | 2 +
drivers/pnp/pnpacpi/core.c | 23 +-
include/acpi/acpi_bus.h | 21 +-
include/acpi/acpi_hotplug.h | 315 ++++++++++++
include/acpi/container.h | 12 -
28 files changed, 4161 insertions(+), 204 deletions(-)
create mode 100644 drivers/acpi/hotplug/Makefile
create mode 100644 drivers/acpi/hotplug/acpihp_drv.h
create mode 100644 drivers/acpi/hotplug/cancel.c
create mode 100644 drivers/acpi/hotplug/configure.c
create mode 100644 drivers/acpi/hotplug/core.c
create mode 100644 drivers/acpi/hotplug/dependency.c
create mode 100644 drivers/acpi/hotplug/device.c
create mode 100644 drivers/acpi/hotplug/drv_main.c
create mode 100644 drivers/acpi/hotplug/slot_enum.c
create mode 100644 drivers/acpi/hotplug/slot_enum_ej0.c
create mode 100644 drivers/acpi/hotplug/state_machine.c
create mode 100644 drivers/acpi/hotplug/sysfs.c
create mode 100644 include/acpi/acpi_hotplug.h
delete mode 100644 include/acpi/container.h
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:48 UTC
Permalink
From: Jiang Liu <***@huawei.com>

Modern high-end server may support advanced hotplug features for system
devices, including physical processor, memory board, IO extension board/box
and/or computer node. The ACPI specifications have provided standard
interfaces between firmware and OS to support device hotplug at runtime.
This patch series provide an ACPI based hotplug framework to support system
device hotplug at runtime, which will replace current existing ACPI device
driver based CPU/memory/CONTAINER hotplug mechanism.

An ACPI hotplug slot is an abstraction of receptacles, where a group of
system devices could be connected to. Every ACPI system device hotplug slot
will be associated with a device class named acpihp_slot_class. This device
class provides sysfs interfaces for each slot:
linux-drf:/sys/devices/LNXSYSTM:00/acpihp/NODE00 # ll
-r--r--r-- 1 root root 65536 Jul 28 16:01 capabilities
-r--r--r-- 1 root root 65536 Jul 28 16:01 object
-r--r--r-- 1 root root 65536 Jul 28 16:01 state
-r--r--r-- 1 root root 65536 Jul 28 16:01 status
-r--r--r-- 1 root root 65536 Jul 28 16:01 type

This patch also implements some utility functions to support ACPI hotplug
slot enumerator and ACPI hotplug driver.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
---
drivers/acpi/Kconfig | 12 +
drivers/acpi/Makefile | 2 +
drivers/acpi/hotplug/Makefile | 6 +
drivers/acpi/hotplug/core.c | 593 +++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_hotplug.h | 191 +++++++++++++
5 files changed, 804 insertions(+)
create mode 100644 drivers/acpi/hotplug/Makefile
create mode 100644 drivers/acpi/hotplug/core.c
create mode 100644 include/acpi/acpi_hotplug.h

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 8099895..e457d31 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -321,6 +321,18 @@ config X86_PM_TIMER
You should nearly always say Y here because many modern
systems require this timer.

+menuconfig ACPI_HOTPLUG
+ bool "ACPI Based System Device Hotplug (EXPERIMENTAL)"
+ depends on (X86 || IA64) && SYSFS && EXPERIMENTAL
+ default n
+ help
+ This option enables an framework for ACPI based system devices
+ hotplug, including physical processor, memory device, IO host
+ bridge and/or computer node etc.
+
+ If your hardware and firmware do not support adding or removing
+ of system devices at runtime, you need not to enable this option.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 47199e2..17bea6c 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -73,3 +73,5 @@ obj-$(CONFIG_ACPI_PROCESSOR_AGGREGATOR) += acpi_pad.o
obj-$(CONFIG_ACPI_IPMI) += acpi_ipmi.o

obj-$(CONFIG_ACPI_APEI) += apei/
+
+obj-$(CONFIG_ACPI_HOTPLUG) += hotplug/
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
new file mode 100644
index 0000000..5e7790f
--- /dev/null
+++ b/drivers/acpi/hotplug/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for ACPI based system device hotplug drivers
+#
+
+obj-$(CONFIG_ACPI_HOTPLUG) += acpihp.o
+acpihp-y = core.o
diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
new file mode 100644
index 0000000..6fac499
--- /dev/null
+++ b/drivers/acpi/hotplug/core.c
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Gaohuai Han <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/sem.h>
+#include <linux/version.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_hotplug.h>
+
+#define to_acpihp_slot(d) container_of(d, struct acpihp_slot, dev)
+
+extern struct acpi_device *acpi_root;
+
+static DEFINE_MUTEX(acpihp_mutex);
+static int acpihp_class_count;
+static struct kset *acpihp_slot_kset;
+
+static char *acpihp_slot_names[ACPIHP_SLOT_TYPE_MAX] = {
+ [ACPIHP_SLOT_TYPE_UNKNOWN] = "UNKNOWN",
+ [ACPIHP_SLOT_TYPE_COMMON] = "SLOT",
+ [ACPIHP_SLOT_TYPE_NODE] = "NODE",
+ [ACPIHP_SLOT_TYPE_SYSTEM_BOARD] = "SB",
+ [ACPIHP_SLOT_TYPE_CPU] = "CPU",
+ [ACPIHP_SLOT_TYPE_MEM] = "MEM",
+ [ACPIHP_SLOT_TYPE_IOX] = "IOX",
+};
+
+static char *acpihp_slot_descriptions[ACPIHP_SLOT_TYPE_MAX] = {
+ [ACPIHP_SLOT_TYPE_UNKNOWN] = "unknown slot",
+ [ACPIHP_SLOT_TYPE_COMMON] = "common slot",
+ [ACPIHP_SLOT_TYPE_NODE] = "computer node slot",
+ [ACPIHP_SLOT_TYPE_SYSTEM_BOARD] = "system board slot",
+ [ACPIHP_SLOT_TYPE_CPU] = "processor slot",
+ [ACPIHP_SLOT_TYPE_MEM] = "memory slot",
+ [ACPIHP_SLOT_TYPE_IOX] = "IO extension slot",
+};
+
+static char *acpihp_slot_states[] = {
+ [ACPIHP_SLOT_STATE_UNKNOWN] = "unknown",
+ [ACPIHP_SLOT_STATE_ABSENT] = "absent",
+ [ACPIHP_SLOT_STATE_PRESENT] = "present",
+ [ACPIHP_SLOT_STATE_POWERED] = "powered",
+ [ACPIHP_SLOT_STATE_CONNECTED] = "connected",
+ [ACPIHP_SLOT_STATE_CONFIGURED] = "configured",
+ [ACPIHP_SLOT_STATE_POWERING_ON] = "powering on",
+ [ACPIHP_SLOT_STATE_POWERING_OFF] = "powering off",
+ [ACPIHP_SLOT_STATE_CONNECTING] = "connecting",
+ [ACPIHP_SLOT_STATE_DISCONNECTING] = "disconneting",
+ [ACPIHP_SLOT_STATE_CONFIGURING] = "configuring",
+ [ACPIHP_SLOT_STATE_UNCONFIGURING] = "unconfiguring",
+};
+
+static char *acpihp_slot_status[] = {
+ "ok",
+ "irremovable",
+ "fault",
+ "irremovable, fault",
+};
+
+static char *acpihp_dev_container_ids[] = {
+ "ACPI0004",
+ "PNP0A05",
+ "PNP0A06",
+ NULL
+};
+
+static char *acpihp_dev_cpu_ids[] = {
+ "ACPI0007",
+ "LNXCPU",
+ NULL
+};
+
+static char *acpihp_dev_mem_ids[] = {
+ "PNP0C80",
+ NULL
+};
+
+static char *acpihp_dev_pcihb_ids[] = {
+ "PNP0A03",
+ "PNP0A08",
+ NULL
+};
+
+static char *acpihp_dev_ioapic_ids[] = {
+ "ACPI0009",
+ "ACPI000A",
+ "ACPI000B",
+ NULL
+};
+
+static void acpihp_slot_release(struct device *dev)
+{
+ struct acpihp_slot *slot = to_acpihp_slot(dev);
+
+ if (!list_empty(&slot->slot_list))
+ ACPIHP_ERROR("slot->slot_list is not empty.\n");
+
+ if (!list_empty(&slot->drvdata_list))
+ ACPIHP_ERROR("slot->drvdata_list is not empty.\n");
+
+ if (!list_empty(&slot->slot_id_list))
+ ACPIHP_ERROR("slot->slot_id_list is not empty.\n");
+
+ kfree(slot->full_path);
+ kfree(slot);
+}
+
+struct acpihp_slot *acpihp_create_slot(acpi_handle handle, char *name)
+{
+ struct acpihp_slot *slot;
+
+ if (name == NULL) {
+ ACPIHP_DEBUG("slot name is NULL.\n");
+ return NULL;
+ } else if (strlen(name) >= ACPIHP_SLOT_NAME_MAX_SIZE) {
+ ACPIHP_WARN("slot name %s is too big.\n", name);
+ return NULL;
+ }
+
+ slot = kzalloc(sizeof(*slot), GFP_KERNEL);
+ if (slot == NULL) {
+ ACPIHP_DEBUG("failed to allocate slot dev.\n");
+ return NULL;
+ }
+
+ slot->handle = handle;
+ INIT_LIST_HEAD(&slot->slot_list);
+ INIT_LIST_HEAD(&slot->drvdata_list);
+ INIT_LIST_HEAD(&slot->slot_id_list);
+ strncpy(slot->name, name, sizeof(slot->name) - 1);
+ mutex_init(&slot->slot_mutex);
+
+ slot->dev.class = &acpihp_slot_class;
+ device_initialize(&slot->dev);
+
+ return slot;
+}
+EXPORT_SYMBOL_GPL(acpihp_create_slot);
+
+static size_t acpihp_generate_link_name(struct acpihp_slot *slot,
+ char *name, size_t off)
+{
+ if (slot->parent)
+ off = acpihp_generate_link_name(slot->parent, name, off);
+ off += snprintf(name + off, PAGE_SIZE - off, "%s.", slot->name);
+ BUG_ON(off >= PAGE_SIZE);
+
+ return off;
+}
+
+int acpihp_register_slot(struct acpihp_slot *slot)
+{
+ int ret;
+ char *name;
+ size_t off;
+
+ if (!slot || !slot->slot_ops)
+ return -EINVAL;
+
+ /* Hook top level hotplug slots under ACPI root device */
+ if (slot->parent)
+ slot->dev.parent = &slot->parent->dev;
+ else
+ slot->dev.parent = &acpi_root->dev;
+
+ ret = device_add(&slot->dev);
+ if (!ret) {
+ slot->flags |= ACPIHP_SLOT_FLAG_REGISTERED;
+ name = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (name) {
+ off = acpihp_generate_link_name(slot, name, 0);
+ name[off - 1] = '\0';
+ slot->full_path = kstrdup(name, GFP_KERNEL);
+ if (slot->full_path)
+ kfree(name);
+ else
+ slot->full_path = name;
+
+ /* Fails to create symlink is non-fatal */
+ if (sysfs_create_link(&acpihp_slot_kset->kobj,
+ &slot->dev.kobj, slot->full_path))
+ ACPIHP_WARN("fails to create link for %s.\n",
+ slot->name);
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_register_slot);
+
+void acpihp_unregister_slot(struct acpihp_slot *slot)
+{
+ if (slot && (slot->flags & ACPIHP_SLOT_FLAG_REGISTERED)) {
+ sysfs_remove_link(&acpihp_slot_kset->kobj, slot->full_path);
+ device_del(&slot->dev);
+ }
+}
+EXPORT_SYMBOL_GPL(acpihp_unregister_slot);
+
+struct acpihp_slot *acpihp_slot_get(struct acpihp_slot *slot)
+{
+ if (slot)
+ get_device(&slot->dev);
+
+ return slot;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_get);
+
+void acpihp_slot_put(struct acpihp_slot *slot)
+{
+ if (slot)
+ put_device(&slot->dev);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_put);
+
+static void acpihp_slot_data_handler(acpi_handle handle, void *context)
+{
+ return;
+}
+
+acpi_status acpihp_mark_slot(acpi_handle handle, struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ mutex_lock(&acpihp_mutex);
+ status = acpi_attach_data(handle, &acpihp_slot_data_handler, slot);
+ mutex_unlock(&acpihp_mutex);
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(acpihp_mark_slot);
+
+acpi_status acpihp_unmark_slot(acpi_handle handle)
+{
+ acpi_status result;
+
+ mutex_lock(&acpihp_mutex);
+ result = acpi_detach_data(handle, &acpihp_slot_data_handler);
+ if (result == AE_NOT_FOUND)
+ result = AE_OK;
+ BUG_ON(result != AE_OK);
+ mutex_unlock(&acpihp_mutex);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(acpihp_unmark_slot);
+
+bool acpihp_check_slot(acpi_handle handle)
+{
+ acpi_status result;
+ void *data = NULL;
+
+ result = acpi_get_data(handle, &acpihp_slot_data_handler, &data);
+ BUG_ON(result != AE_OK && result != AE_NOT_FOUND);
+
+ return (result == AE_OK);
+}
+EXPORT_SYMBOL_GPL(acpihp_check_slot);
+
+bool acpihp_get_slot(acpi_handle handle, struct acpihp_slot **slot)
+{
+ acpi_status result;
+ void *data = NULL;
+
+ mutex_lock(&acpihp_mutex);
+ result = acpi_get_data(handle, &acpihp_slot_data_handler, &data);
+ if (slot) {
+ if (result == AE_OK) {
+ *slot = data;
+ acpihp_slot_get(*slot);
+ } else
+ *slot = NULL;
+ }
+ mutex_unlock(&acpihp_mutex);
+
+ return (result == AE_OK);
+}
+EXPORT_SYMBOL_GPL(acpihp_get_slot);
+
+bool acpihp_dev_match_ids(struct acpi_device_info *infop, char **ids)
+{
+ int i, j;
+ struct acpica_device_id_list *cid_list;
+
+ if (infop == NULL || ids == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return false;
+ }
+
+ if (infop->valid & ACPI_VALID_HID) {
+ for (i = 0; ids[i]; i++) {
+ if (strncmp(ids[i], infop->hardware_id.string,
+ infop->hardware_id.length) == 0) {
+ return true;
+ }
+ }
+ }
+
+ if (!(infop->valid & ACPI_VALID_CID)) {
+ for (i = 0; ids[i]; i++) {
+ cid_list = &infop->compatible_id_list;
+ for (j = 0; j < cid_list->count; j++) {
+ if (strncmp(ids[i],
+ cid_list->ids[j].string,
+ cid_list->ids[j].length) == 0) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(acpihp_dev_match_ids);
+
+int acpihp_dev_get_type(acpi_handle handle, enum acpihp_dev_type *type)
+{
+ struct acpi_device_info *infop = NULL;
+
+ if (handle == NULL || type == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return -EINVAL;
+ }
+ if (ACPI_FAILURE(acpi_get_object_info(handle, &infop)))
+ return -ENODEV;
+
+ *type = ACPIHP_DEV_TYPE_UNKNOWN;
+ if (infop->type == ACPI_TYPE_PROCESSOR) {
+ *type = ACPIHP_DEV_TYPE_CPU;
+ } else if (infop->type == ACPI_TYPE_DEVICE) {
+ if (acpihp_dev_match_ids(infop, acpihp_dev_container_ids))
+ *type = ACPIHP_DEV_TYPE_CONTAINER;
+ else if (acpihp_dev_match_ids(infop, acpihp_dev_cpu_ids))
+ *type = ACPIHP_DEV_TYPE_CPU;
+ else if (acpihp_dev_match_ids(infop, acpihp_dev_mem_ids))
+ *type = ACPIHP_DEV_TYPE_MEM;
+ else if (acpihp_dev_match_ids(infop, acpihp_dev_pcihb_ids))
+ *type = ACPIHP_DEV_TYPE_HOST_BRIDGE;
+ else if (acpihp_dev_match_ids(infop, acpihp_dev_ioapic_ids))
+ *type = ACPIHP_DEV_TYPE_IOAPIC;
+ else if ((infop->valid & (ACPI_VALID_ADR | ACPI_VALID_HID |
+ ACPI_VALID_CID)) == ACPI_VALID_ADR)
+ *type = ACPIHP_DEV_TYPE_MAX;
+ }
+ kfree(infop);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(acpihp_dev_get_type);
+
+char *acpihp_get_slot_type_name(enum acpihp_slot_type type)
+{
+ if (type < 0 || type >= ACPIHP_SLOT_TYPE_MAX) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return "UNKNOWN";
+ } else {
+ return acpihp_slot_names[type];
+ }
+}
+EXPORT_SYMBOL_GPL(acpihp_get_slot_type_name);
+
+/*
+ * slot_ops should be valid during the life cycle of a slot, so no protection.
+ */
+acpi_status
+acpihp_slot_get_capabilities(struct acpihp_slot *slot, u32 *capability)
+{
+ if (slot == NULL || capability == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return AE_BAD_PARAMETER;
+ } else if (slot->slot_ops == NULL ||
+ slot->slot_ops->get_capabilities == NULL) {
+ ACPIHP_DEBUG("operation not supported.\n");
+ return AE_SUPPORT;
+ }
+
+ return slot->slot_ops->get_capabilities(slot->handle, capability);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_get_capabilities);
+
+acpi_status acpihp_slot_poweron(struct acpihp_slot *slot)
+{
+ if (slot == NULL) {
+ ACPIHP_DEBUG("invalid parameter.\n");
+ return AE_BAD_PARAMETER;
+ } else if (slot->slot_ops == NULL || slot->slot_ops->poweron == NULL) {
+ ACPIHP_DEBUG("operation not supported.\n");
+ return AE_SUPPORT;
+ }
+
+ return slot->slot_ops->poweron(slot->handle);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_poweron);
+
+acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot)
+{
+ if (slot == NULL) {
+ ACPIHP_DEBUG("invalid parameter.\n");
+ return AE_BAD_PARAMETER;
+ } else if (slot->slot_ops == NULL || slot->slot_ops->poweroff == NULL) {
+ ACPIHP_DEBUG("operation not supported.\n");
+ return AE_SUPPORT;
+ }
+
+ return slot->slot_ops->poweroff(slot->handle);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_poweroff);
+
+/* SYSFS interfaces */
+static ssize_t acpihp_slot_object_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t sz;
+ struct acpihp_slot *slot = to_acpihp_slot(d);
+ struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL};
+
+ sz = acpi_get_name(slot->handle, ACPI_FULL_PATHNAME, &path);
+ if (sz)
+ goto end;
+
+ sz = sprintf(buf, "%s\n", (char*)path.pointer);
+ kfree(path.pointer);
+end:
+ return sz;
+}
+
+static ssize_t acpihp_slot_type_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ enum acpihp_slot_type type;
+ struct acpihp_slot *slot = to_acpihp_slot(d);
+
+ type = slot->type < 0 || slot->type >= ACPIHP_SLOT_TYPE_MAX ?
+ ACPIHP_SLOT_TYPE_UNKNOWN : slot->type;
+
+ return snprintf(buf, PAGE_SIZE, "%s: %s\n",
+ acpihp_get_slot_type_name(type),
+ acpihp_slot_descriptions[type]);
+}
+
+static ssize_t acpihp_slot_state_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ enum acpihp_slot_state state;
+ acpi_status status;
+ unsigned long long sta;
+ struct acpihp_slot *slot = to_acpihp_slot(d);
+
+ state = slot->state < 0 || slot->state >= ACPIHP_SLOT_STATE_MAX ?
+ ACPIHP_SLOT_STATE_UNKNOWN : slot->state;
+
+ if ((ACPIHP_SLOT_STATE_ABSENT == state) ||
+ (ACPIHP_SLOT_STATE_PRESENT == state)) {
+ status = acpi_evaluate_integer(slot->handle, "_STA", NULL,
+ &sta);
+ if (ACPI_SUCCESS(status)) {
+ if ((sta & ACPI_STA_DEVICE_PRESENT) ==
+ ACPI_STA_DEVICE_PRESENT)
+ state = ACPIHP_SLOT_STATE_PRESENT;
+ else
+ state = ACPIHP_SLOT_STATE_ABSENT;
+ }
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", acpihp_slot_states[state]);
+}
+
+static ssize_t acpihp_slot_status_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpihp_slot *slot = to_acpihp_slot(d);
+ u32 status = slot->flags & ACPIHP_SLOT_STATUS_MASK;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", acpihp_slot_status[status]);
+}
+
+static ssize_t acpihp_slot_capabilities_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t sz;
+ struct acpihp_slot *slot = to_acpihp_slot(d);
+ u32 cap = slot->capabilities;
+
+ sz = snprintf(buf, PAGE_SIZE, "%s%s%s%s%s%s",
+ (cap & ACPIHP_SLOT_CAP_ONLINE) ? "online," : "",
+ (cap & ACPIHP_SLOT_CAP_OFFLINE) ? "offline," : "",
+ (cap & ACPIHP_SLOT_CAP_POWERON) ? "poweron," : "",
+ (cap & ACPIHP_SLOT_CAP_POWEROFF) ? "poweroff," : "",
+ (cap & ACPIHP_SLOT_CAP_HOTPLUG) ? "hotplug," : "",
+ (cap & ACPIHP_SLOT_CAP_MIGRATE) ? "migrate," : "");
+ BUG_ON(sz == 0);
+ if (sz)
+ buf[--sz] = '\n';
+
+ return sz + 1;
+}
+
+struct device_attribute acpihp_slot_dev_attrs[] = {
+ __ATTR(object, S_IRUGO, acpihp_slot_object_show, NULL),
+ __ATTR(type, S_IRUGO, acpihp_slot_type_show, NULL),
+ __ATTR(state, S_IRUGO, acpihp_slot_state_show, NULL),
+ __ATTR(status, S_IRUGO, acpihp_slot_status_show, NULL),
+ __ATTR(capabilities, S_IRUGO, acpihp_slot_capabilities_show, NULL),
+ __ATTR_NULL
+};
+
+/*
+ * The device class to support ACPI hotplug slots.
+ * The hotplug slot enumerator should associate all discovered slots with
+ * this class and all hotplug drivers should register onto this class too.
+ */
+struct class acpihp_slot_class = {
+ .name = "acpihp",
+ .dev_release = &acpihp_slot_release,
+ .dev_attrs = acpihp_slot_dev_attrs,
+};
+EXPORT_SYMBOL_GPL(acpihp_slot_class);
+
+int acpihp_register_class(void)
+{
+ int retval = 0;
+ struct kset *acpi_bus_kset;
+
+ mutex_lock(&acpihp_mutex);
+ if (acpihp_class_count == 0) {
+ acpi_bus_kset = bus_get_kset(&acpi_bus_type);
+ acpihp_slot_kset = kset_create_and_add("acpihp", NULL,
+ &acpi_bus_kset->kobj);
+ if (!acpihp_slot_kset) {
+ ACPIHP_WARN("fails to create kset.\n");
+ retval = -ENOMEM;
+ goto out_unlock;
+ }
+
+ retval = class_register(&acpihp_slot_class);
+ if (retval) {
+ ACPIHP_WARN("fails to register acpihp_slot_class.\n");
+ kset_unregister(acpihp_slot_kset);
+ goto out_unlock;
+ }
+ }
+ acpihp_class_count++;
+out_unlock:
+ mutex_unlock(&acpihp_mutex);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(acpihp_register_class);
+
+void acpihp_unregister_class(void)
+{
+ mutex_lock(&acpihp_mutex);
+ BUG_ON(acpihp_class_count == 0);
+ --acpihp_class_count;
+ if (acpihp_class_count == 0) {
+ class_unregister(&acpihp_slot_class);
+ kset_unregister(acpihp_slot_kset);
+ acpihp_slot_kset = NULL;
+ }
+ mutex_unlock(&acpihp_mutex);
+}
+EXPORT_SYMBOL_GPL(acpihp_unregister_class);
+
+int acpihp_debug;
+EXPORT_SYMBOL_GPL(acpihp_debug);
+module_param_named(debug, acpihp_debug, int, S_IRUGO | S_IWUSR);
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
new file mode 100644
index 0000000..09c935e
--- /dev/null
+++ b/include/acpi/acpi_hotplug.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Gaohuai Han <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __ACPI_HOTPLUG_H__
+#define __ACPI_HOTPLUG_H__
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/klist.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_drivers.h>
+
+#ifdef CONFIG_ACPI_HOTPLUG
+
+#define ACPIHP_SLOT_NAME_MAX_SIZE 16
+
+/* Types of system devices supported by the ACPI hotplug framework. */
+enum acpihp_dev_type {
+ ACPIHP_DEV_TYPE_UNKNOWN = 0, /* Unknown device type */
+ ACPIHP_DEV_TYPE_CONTAINER, /* ACPI container device */
+ ACPIHP_DEV_TYPE_MEM, /* Memory device */
+ ACPIHP_DEV_TYPE_CPU, /* Logical CPU device */
+ ACPIHP_DEV_TYPE_IOAPIC, /* IOAPIC/IOxAPIC/IOSAPIC */
+ ACPIHP_DEV_TYPE_HOST_BRIDGE, /* PCI/PCIe host bridge */
+ ACPIHP_DEV_TYPE_MAX
+};
+
+/*
+ * ACPI hotplug slot is an abstraction of receptacles where a group of
+ * system devices could be attached, just like PCI slot in PCI hotplug.
+ */
+enum acpihp_slot_type {
+ ACPIHP_SLOT_TYPE_UNKNOWN = 0, /* Unknown slot type */
+ ACPIHP_SLOT_TYPE_COMMON, /* Generic hotplug slot */
+ ACPIHP_SLOT_TYPE_NODE, /* Node hosts CPU, memory & IO device */
+ ACPIHP_SLOT_TYPE_SYSTEM_BOARD, /* System board hosts CPU & memory */
+ ACPIHP_SLOT_TYPE_CPU, /* CPU board */
+ ACPIHP_SLOT_TYPE_MEM, /* Memory board */
+ ACPIHP_SLOT_TYPE_IOX, /* IO eXtension board */
+ ACPIHP_SLOT_TYPE_MAX
+};
+
+/*
+ * States of ACPI hotplug slot:
+ * (POWERON) (CONNECT) (CONFIGURE)
+ * [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED]
+ * (POWEROFF) (DISCONNECT) (UNCONFIGURE)
+ */
+enum acpihp_slot_state {
+ ACPIHP_SLOT_STATE_UNKNOWN = 0,
+ ACPIHP_SLOT_STATE_ABSENT, /* slot is empty. */
+ ACPIHP_SLOT_STATE_PRESENT, /* slot is populated. */
+ ACPIHP_SLOT_STATE_POWERED, /* attached devices are powered. */
+ ACPIHP_SLOT_STATE_CONNECTED, /* ACPI device nodes created. */
+ ACPIHP_SLOT_STATE_CONFIGURED, /* attached devices are configured. */
+ ACPIHP_SLOT_STATE_POWERING_ON, /* powering devices on */
+ ACPIHP_SLOT_STATE_POWERING_OFF, /* powering devices off */
+ ACPIHP_SLOT_STATE_CONNECTING, /* creating ACPI device nodes */
+ ACPIHP_SLOT_STATE_DISCONNECTING,/* destroying ACPI device nodes */
+ ACPIHP_SLOT_STATE_CONFIGURING, /* configuring devices */
+ ACPIHP_SLOT_STATE_UNCONFIGURING,/* unconfigure devices */
+ ACPIHP_SLOT_STATE_MAX
+};
+
+#define ACPIHP_SLOT_FLAG_IRREMOVABLE 0x1 /* Attached devices can't be
+ * removed.
+ */
+#define ACPIHP_SLOT_FLAG_FAULT 0x2 /* Attached devices have
+ * encountered serious problem.
+ */
+#define ACPIHP_SLOT_STATUS_MASK 0x3
+#define ACPIHP_SLOT_FLAG_REGISTERED 0x80000000 /* Slot device registered */
+
+/*
+ * ACPI hotplug slot capabilites.
+ */
+#define ACPIHP_SLOT_CAP_ONLINE 0x1 /* Logical online */
+#define ACPIHP_SLOT_CAP_OFFLINE 0x2 /* Logical offline */
+#define ACPIHP_SLOT_CAP_POWERON 0x4 /* Powering on */
+#define ACPIHP_SLOT_CAP_POWEROFF 0x8 /* Powering off */
+#define ACPIHP_SLOT_CAP_HOTPLUG 0x10 /* Physical hotplug */
+#define ACPIHP_SLOT_CAP_MIGRATE 0x20 /* Memory migration */
+
+/*
+ * Callback hooks provided by the platform dependent slot driver for the
+ * platform independent ACPI hotplug driver to manage ACPI hotplug slots.
+ */
+struct acpihp_slot_ops {
+ acpi_status (*init)(void);
+ void (*fini)(void);
+ acpi_status (*check)(acpi_handle handle);
+ acpi_status (*get_capabilities)(acpi_handle handle, u32 *capability);
+ acpi_status (*poweron)(acpi_handle handle);
+ acpi_status (*poweroff)(acpi_handle handle);
+ struct module *owner;
+ char *desc;
+};
+
+/* Device structure for ACPI hotplug slots. */
+struct acpihp_slot {
+ struct device dev;
+ acpi_handle handle;
+ enum acpihp_slot_type type;
+ enum acpihp_slot_state state;
+ u32 flags;
+ u32 capabilities;
+ struct acpihp_slot *parent;
+ struct acpihp_slot_ops *slot_ops;
+ struct mutex slot_mutex;
+ struct list_head slot_list;
+ struct list_head slot_id_list;
+ struct list_head drvdata_list;
+ struct klist dev_lists[ACPIHP_DEV_TYPE_MAX];
+ char *full_path;
+ char name[ACPIHP_SLOT_NAME_MAX_SIZE];
+};
+
+/* Device class for ACPI hotplug slots */
+extern struct class acpihp_slot_class;
+
+/* Register the ACPI hotplug slot class driver */
+extern int acpihp_register_class(void);
+
+/* Unregister the ACPI hotplug slot class driver */
+extern void acpihp_unregister_class(void);
+
+/* Utility routines */
+extern int acpihp_dev_get_type(acpi_handle handle, enum acpihp_dev_type *type);
+extern bool acpihp_dev_match_ids(struct acpi_device_info *infop, char **ids);
+extern char *acpihp_get_slot_type_name(enum acpihp_slot_type type);
+
+/* Mark/unmark an ACPI object as an ACPI hotplug slot. */
+extern acpi_status acpihp_mark_slot(acpi_handle handle,
+ struct acpihp_slot *slot);
+extern acpi_status acpihp_unmark_slot(acpi_handle handle);
+
+/* Check whether the ACPI object is a hotplug slot. */
+extern bool acpihp_check_slot(acpi_handle handle);
+
+/* Interfaces to manage ACPI hotplug slots */
+extern struct acpihp_slot *acpihp_create_slot(acpi_handle handle, char *name);
+extern int acpihp_register_slot(struct acpihp_slot *slot);
+extern void acpihp_unregister_slot(struct acpihp_slot *slot);
+extern struct acpihp_slot *acpihp_slot_get(struct acpihp_slot *slot);
+extern void acpihp_slot_put(struct acpihp_slot *slot);
+extern bool acpihp_get_slot(acpi_handle handle, struct acpihp_slot **slot);
+
+/* Platform dependent hotplug hooks */
+extern acpi_status acpihp_slot_get_capabilities(struct acpihp_slot *slot,
+ u32 *capability);
+extern acpi_status acpihp_slot_poweron(struct acpihp_slot *slot);
+extern acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot);
+
+extern int acpihp_debug;
+
+#define ACPIHP_DEBUG(fmt, ...) \
+ do { \
+ if (acpihp_debug & 0x01) \
+ pr_warn("acpihp@%s: " fmt, __func__, ##__VA_ARGS__); \
+ } while (0)
+
+#define ACPIHP_INFO(fmt, ...) pr_info("acpihp: " fmt, ##__VA_ARGS__)
+#define ACPIHP_WARN(fmt, ...) pr_warn("acpihp: " fmt, ##__VA_ARGS__)
+#define ACPIHP_ERROR(fmt, ...) pr_err("acpihp: " fmt, ##__VA_ARGS__)
+
+#endif /* CONFIG_ACPI_HOTPLUG */
+
+#endif /* __ACPI_HOTPLUG_H__ */
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:14:02 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements ACPI hotplug sysfs interfaces for system device hotplug.

enable: (It should be named 'power', but that causes name conflicts.)
echo 1 > enable: power on the hotplug slot.
echo 0 > enable: power off the hotplug slot.

connect:
echo 1 > connect: create ACPI devices for system devices connecting to a slot
echo 0 > connect: remove ACPI devices for system devices connecting to a slot

configure:
echo 1 > configure: add system devices into running system
echo 0 > configure: remove system devices from running system

dependency:
cat dependency: show slots have dependency on this slot if the slot is powered,
otherwise show slots this slot depends on.

Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/acpihp_drv.h | 3 +
drivers/acpi/hotplug/drv_main.c | 6 +
drivers/acpi/hotplug/sysfs.c | 246 +++++++++++++++++++++++++++++++++++++
4 files changed, 256 insertions(+)
create mode 100644 drivers/acpi/hotplug/sysfs.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 9383320..88c7595 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -15,3 +15,4 @@ acpihp_drv-y += dependency.o
acpihp_drv-y += cancel.o
acpihp_drv-y += configure.o
acpihp_drv-y += state_machine.o
+acpihp_drv-y += sysfs.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index 3b6dbdd..d339f9a 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -101,4 +101,7 @@ int acpihp_drv_unconfigure(struct list_head *list);
/* The heart of the ACPI system device hotplug driver */
int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd);

+int acpihp_drv_create_sysfs(struct acpihp_slot *slot);
+void acpihp_drv_remove_sysfs(struct acpihp_slot *slot);
+
#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c
index 538772d..9858fdd 100644
--- a/drivers/acpi/hotplug/drv_main.c
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -280,6 +280,11 @@ static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
return -ENOMEM;
}

+ if (acpihp_drv_create_sysfs(slot))
+ ACPIHP_DEBUG("fails to create sysfs interfaces for slot %s, "
+ "some function will not be available to user.\n",
+ slot->name);
+
ACPIHP_INFO("found hotplug slot %s.\n", slot->full_path);

return 0;
@@ -294,6 +299,7 @@ static void acpihp_drv_intf_remove(struct device *dev,

ACPIHP_INFO("remove hotplug slot %s.\n", slot->full_path);

+ acpihp_drv_remove_sysfs(slot);
acpihp_drv_uninstall_handler(slot);
acpihp_drv_remove_devices(slot);
acpihp_slot_detach_drv_data(slot, intf, (void **)&drv_data);
diff --git a/drivers/acpi/hotplug/sysfs.c b/drivers/acpi/hotplug/sysfs.c
new file mode 100644
index 0000000..62c1892
--- /dev/null
+++ b/drivers/acpi/hotplug/sysfs.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+static ssize_t acpihp_drv_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count,
+ enum acpihp_drv_cmd op_set, enum acpihp_drv_cmd op_clr)
+{
+ unsigned long val;
+ ssize_t result = kstrtoul(buf, 0, &val);
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ if (result < 0)
+ return result;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (val)
+ result = acpihp_drv_change_state(slot, op_set);
+ else
+ result = acpihp_drv_change_state(slot, op_clr);
+
+ return result < 0 ? result : count;
+}
+
+static inline ssize_t acpihp_drv_print_bool(char *page, bool flag)
+{
+ return snprintf(page, PAGE_SIZE, flag ? "1\n" : "0\n");
+}
+
+static ssize_t acpihp_drv_power_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ bool flag;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ acpihp_drv_update_slot_state(slot);
+ mutex_lock(&slot->slot_mutex);
+ switch (slot->state) {
+ case ACPIHP_SLOT_STATE_POWERED:
+ case ACPIHP_SLOT_STATE_CONNECTED:
+ case ACPIHP_SLOT_STATE_CONFIGURED:
+ case ACPIHP_SLOT_STATE_POWERING_ON:
+ case ACPIHP_SLOT_STATE_POWERING_OFF:
+ case ACPIHP_SLOT_STATE_CONNECTING:
+ case ACPIHP_SLOT_STATE_DISCONNECTING:
+ case ACPIHP_SLOT_STATE_CONFIGURING:
+ case ACPIHP_SLOT_STATE_UNCONFIGURING:
+ flag = true;
+ break;
+ default:
+ flag = false;
+ break;
+ }
+ mutex_unlock(&slot->slot_mutex);
+
+ return acpihp_drv_print_bool(page, flag);
+}
+
+static ssize_t acpihp_drv_power_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return acpihp_drv_store(dev, attr, buf, count,
+ ACPIHP_DRV_CMD_POWERON, ACPIHP_DRV_CMD_POWEROFF);
+}
+
+/* It should be named as 'power', but that will cause name conflics. */
+DEVICE_ATTR(enable, S_IRUSR | S_IWUSR,
+ &acpihp_drv_power_show, &acpihp_drv_power_store);
+
+static ssize_t acpihp_drv_connect_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ bool flag;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ acpihp_drv_update_slot_state(slot);
+ mutex_lock(&slot->slot_mutex);
+ switch (slot->state) {
+ case ACPIHP_SLOT_STATE_CONNECTED:
+ case ACPIHP_SLOT_STATE_CONFIGURED:
+ case ACPIHP_SLOT_STATE_CONNECTING:
+ case ACPIHP_SLOT_STATE_DISCONNECTING:
+ case ACPIHP_SLOT_STATE_CONFIGURING:
+ case ACPIHP_SLOT_STATE_UNCONFIGURING:
+ flag = true;
+ break;
+ default:
+ flag = false;
+ break;
+ }
+ mutex_unlock(&slot->slot_mutex);
+
+ return acpihp_drv_print_bool(page, flag);
+}
+
+static ssize_t acpihp_drv_connect_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return acpihp_drv_store(dev, attr, buf, count,
+ ACPIHP_DRV_CMD_CONNECT, ACPIHP_DRV_CMD_DISCONNECT);
+}
+
+DEVICE_ATTR(connect, S_IRUSR | S_IWUSR,
+ &acpihp_drv_connect_show, &acpihp_drv_connect_store);
+
+static ssize_t acpihp_drv_configure_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ bool flag;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ acpihp_drv_update_slot_state(slot);
+ mutex_lock(&slot->slot_mutex);
+ switch (slot->state) {
+ case ACPIHP_SLOT_STATE_CONFIGURED:
+ case ACPIHP_SLOT_STATE_CONFIGURING:
+ case ACPIHP_SLOT_STATE_UNCONFIGURING:
+ flag = true;
+ break;
+ default:
+ flag = false;
+ break;
+ }
+ mutex_unlock(&slot->slot_mutex);
+
+ return acpihp_drv_print_bool(page, flag);
+}
+
+static ssize_t acpihp_drv_configure_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return acpihp_drv_store(dev, attr, buf, count,
+ ACPIHP_DRV_CMD_CONFIGURE, ACPIHP_DRV_CMD_UNCONFIGURE);
+}
+
+DEVICE_ATTR(configure, S_IRUSR | S_IWUSR,
+ &acpihp_drv_configure_show, &acpihp_drv_configure_store);
+
+static ssize_t acpihp_drv_dependency_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ int ret;
+ char *p, *end;
+ struct list_head list;
+ enum acpihp_drv_cmd cmd;
+ struct acpihp_slot_dependency *dep;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ INIT_LIST_HEAD(&list);
+ mutex_lock(&state_machine_mutex);
+ cmd = acpihp_slot_powered(slot) ? ACPIHP_DRV_CMD_POWEROFF :
+ ACPIHP_DRV_CMD_POWERON;
+ ret = acpihp_drv_generate_dependency_list(slot, &list, cmd);
+ if (ret) {
+ ret = -ENXIO;
+ } else {
+ p = page;
+ end = page + PAGE_SIZE;
+
+ list_for_each_entry(dep, &list, node) {
+ if (dep->slot == slot)
+ continue;
+ if (p + strlen(dep->slot->full_path) + 2 >= end)
+ break;
+ p += snprintf(p, end - p, "%s\n", dep->slot->full_path);
+ }
+
+ acpihp_drv_destroy_dependency_list(&list);
+ ret = p - page;
+ }
+ mutex_unlock(&state_machine_mutex);
+
+ return ret;
+}
+
+static DEVICE_ATTR(dependency, S_IRUSR | S_IWUSR,
+ &acpihp_drv_dependency_show, NULL);
+
+int acpihp_drv_create_sysfs(struct acpihp_slot *slot)
+{
+ int retval;
+ struct device *dev = &slot->dev;
+
+ retval = device_create_file(dev, &dev_attr_enable);
+ if (retval)
+ goto out;
+ retval = device_create_file(dev, &dev_attr_connect);
+ if (retval)
+ goto out1;
+ retval = device_create_file(dev, &dev_attr_configure);
+ if (retval)
+ goto out2;
+ retval = device_create_file(dev, &dev_attr_dependency);
+ if (retval)
+ goto out3;
+
+ return 0;
+
+out3:
+ device_remove_file(dev, &dev_attr_configure);
+out2:
+ device_remove_file(dev, &dev_attr_connect);
+out1:
+ device_remove_file(dev, &dev_attr_enable);
+out:
+ ACPIHP_DEBUG("fails to create sysfs interfaces for slot %s.\n",
+ slot->name);
+ return retval;
+}
+
+void acpihp_drv_remove_sysfs(struct acpihp_slot *slot)
+{
+ struct device *dev = &slot->dev;
+
+ device_remove_file(dev, &dev_attr_dependency);
+ device_remove_file(dev, &dev_attr_configure);
+ device_remove_file(dev, &dev_attr_connect);
+ device_remove_file(dev, &dev_attr_enable);
+}
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:13:53 UTC
Permalink
From: Jiang Liu <***@huawei.com>

ACPI devices connecting to an ACPI hotplug slot are divided into groups
according to device types. Those devices will be configured/unconfigured
in order of device types when hot-adding/hot-removing system devices.

For example, when hot-adding a computer node with CPUs, memory, PCI host
bridges, IOAPICs, the order optimized for performance should be:
memory -> CPU -> IOAPIC -> PCI host bridge.

Signed-off-by: Hanjun Guo <***@huawei.com>
Signed-off-by: Jiang Liu <***@huawei.com>
Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/hotplug/core.c | 113 +++++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_hotplug.h | 27 +++++++++++
2 files changed, 140 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index 6fac499..14b3d61 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -115,6 +115,23 @@ static char *acpihp_dev_ioapic_ids[] = {
NULL
};

+static void acpihp_dev_node_get(struct klist_node *lp)
+{
+ struct acpihp_dev_node *dp;
+
+ dp = container_of(lp, struct acpihp_dev_node, node);
+ get_device(dp->dev);
+}
+
+static void acpihp_dev_node_put(struct klist_node *lp)
+{
+ struct acpihp_dev_node *dp;
+
+ dp = container_of(lp, struct acpihp_dev_node, node);
+ put_device(dp->dev);
+ kfree(dp);
+}
+
static void acpihp_slot_release(struct device *dev)
{
struct acpihp_slot *slot = to_acpihp_slot(dev);
@@ -134,6 +151,7 @@ static void acpihp_slot_release(struct device *dev)

struct acpihp_slot *acpihp_create_slot(acpi_handle handle, char *name)
{
+ int i;
struct acpihp_slot *slot;

if (name == NULL) {
@@ -154,6 +172,9 @@ struct acpihp_slot *acpihp_create_slot(acpi_handle handle, char *name)
INIT_LIST_HEAD(&slot->slot_list);
INIT_LIST_HEAD(&slot->drvdata_list);
INIT_LIST_HEAD(&slot->slot_id_list);
+ for (i = ACPIHP_DEV_TYPE_UNKNOWN; i < ACPIHP_DEV_TYPE_MAX; i++)
+ klist_init(&slot->dev_lists[i],
+ &acpihp_dev_node_get, &acpihp_dev_node_put);
strncpy(slot->name, name, sizeof(slot->name) - 1);
mutex_init(&slot->slot_mutex);

@@ -433,6 +454,98 @@ acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot)
}
EXPORT_SYMBOL_GPL(acpihp_slot_poweroff);

+/* Insert a device onto a hotplug slot's device klist. */
+int acpihp_slot_add_device(struct acpihp_slot *slot, enum acpihp_dev_type type,
+ enum acpihp_dev_state state, struct device *dev)
+{
+ struct acpihp_dev_node *np;
+
+ if (type < ACPIHP_DEV_TYPE_UNKNOWN || type >= ACPIHP_DEV_TYPE_MAX) {
+ ACPIHP_DEBUG("device type %d is invalid.\n", type);
+ return -EINVAL;
+ } else if (slot == NULL) {
+ ACPIHP_DEBUG("invalid parameter, slot is NULL.\n");
+ return -EINVAL;
+ } else if (dev == NULL) {
+ ACPIHP_DEBUG("invalid parameter, dev is NULL.\n");
+ return -EINVAL;
+ }
+
+ np = kzalloc(sizeof(*np), GFP_KERNEL);
+ if (np == NULL)
+ return -ENOMEM;
+
+ np->dev = dev;
+ np->state = state;
+ mutex_init(&np->lock);
+ klist_add_tail(&np->node, &slot->dev_lists[type]);
+ ACPIHP_DEBUG("add device %s to slot %s.\n",
+ dev_name(dev), slot->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_add_device);
+
+/* Remove a device from a hotplug slot's device klist. */
+int acpihp_slot_remove_device(struct acpihp_slot *slot,
+ enum acpihp_dev_type type, struct device *dev)
+{
+ int ret = -ENOENT;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+
+ if (type < ACPIHP_DEV_TYPE_UNKNOWN || type >= ACPIHP_DEV_TYPE_MAX) {
+ ACPIHP_DEBUG("device type %d is invalid.\n", type);
+ return -EINVAL;
+ } else if (slot == NULL) {
+ ACPIHP_DEBUG("invalid parameter, slot is NULL.\n");
+ return -EINVAL;
+ } else if (dev == NULL) {
+ ACPIHP_DEBUG("invalid parameter, dev is NULL.\n");
+ return -EINVAL;
+ }
+
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ if (np->dev == dev) {
+ ACPIHP_DEBUG("remove device %s from slot %s.\n",
+ dev_name(dev), slot->name);
+ klist_del(&np->node);
+ ret = 0;
+ break;
+ }
+ }
+ klist_iter_exit(&iter);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_remove_device);
+
+/* Remove all devices from the klist */
+int acpihp_remove_device_list(struct klist *dev_list)
+{
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+
+ if (dev_list == NULL) {
+ ACPIHP_DEBUG("invalid parameter, dev_list is NULL.\n");
+ return -EINVAL;
+ }
+
+ klist_iter_init(dev_list, &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ klist_del(&np->node);
+ }
+ klist_iter_exit(&iter);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(acpihp_remove_device_list);
+
/* SYSFS interfaces */
static ssize_t acpihp_slot_object_show(struct device *d,
struct device_attribute *attr, char *buf)
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index b81d934..cd8dd99 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -48,6 +48,24 @@ enum acpihp_dev_type {
ACPIHP_DEV_TYPE_MAX
};

+enum acpihp_dev_state {
+ DEVICE_STATE_UNKOWN = 0x00,
+ DEVICE_STATE_CONNECTED,
+ DEVICE_STATE_PRE_CONFIGURE,
+ DEVICE_STATE_CONFIGURED,
+ DEVICE_STATE_PRE_RELEASE,
+ DEVICE_STATE_RELEASED,
+ DEVICE_STATE_PRE_UNCONFIGURE,
+ DEVICE_STATE_MAX
+};
+
+struct acpihp_dev_node {
+ struct device *dev;
+ enum acpihp_dev_state state;
+ struct mutex lock;
+ struct klist_node node;
+};
+
/*
* ACPI hotplug slot is an abstraction of receptacles where a group of
* system devices could be attached, just like PCI slot in PCI hotplug.
@@ -190,6 +208,15 @@ typedef acpi_status (*acpihp_walk_device_cb)(struct acpi_device *acpi_device,
extern int acpihp_walk_devices(acpi_handle handle, acpihp_walk_device_cb cb,
void *argp);

+extern int acpihp_slot_add_device(struct acpihp_slot *slot,
+ enum acpihp_dev_type type,
+ enum acpihp_dev_state state,
+ struct device *dev);
+extern int acpihp_slot_remove_device(struct acpihp_slot *slot,
+ enum acpihp_dev_type type,
+ struct device *dev);
+extern int acpihp_remove_device_list(struct klist *dev_list);
+
extern int acpihp_debug;

#define ACPIHP_DEBUG(fmt, ...) \
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:50 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements a default mechanism to detect and manage ACPI system
device hotplug slots based on standard ACPI interfaces.
1) Detech hotplug slot by checking existence of _EJ0 and _STA methods.
2) Power off a slot by excuting _EJ0 method.

It's the default hotplug slot enumerating mechanism, platform specifc drivers
may provide advanced implementation to override the default implementation.

Signed-off-by: Jiang Liu <***@huawei.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/Kconfig | 10 +++
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/slot_enum.c | 3 +
drivers/acpi/hotplug/slot_enum_ej0.c | 113 ++++++++++++++++++++++++++++++++++
4 files changed, 127 insertions(+)
create mode 100644 drivers/acpi/hotplug/slot_enum_ej0.c

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 711e18e..c9f7918 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -344,6 +344,16 @@ config ACPI_HOTPLUG_ENUM
To compile this driver as a module, choose M here:
the module will be called acpihp_enum.

+config ACPI_HOTPLUG_ENUM_EJ0
+ bool "Detecting ACPI Hotplug Slot by Checking _EJ0 Method"
+ depends on ACPI_HOTPLUG_ENUM
+ default y
+ help
+ Enable detecting ACPI hotplug slots by checking ACPI _EJ0 method
+ under ACPI device objects.
+
+ It's the default method to detect ACPI hotplug slots.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 41c0da9..23dfa93 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -7,3 +7,4 @@ acpihp-y = core.o

obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
acpihp_enum-y = slot_enum.o
+acpihp_enum-y += slot_enum_ej0.o
diff --git a/drivers/acpi/hotplug/slot_enum.c b/drivers/acpi/hotplug/slot_enum.c
index 80396a3..a251ecf 100644
--- a/drivers/acpi/hotplug/slot_enum.c
+++ b/drivers/acpi/hotplug/slot_enum.c
@@ -40,11 +40,14 @@ struct acpihp_slot_id {

static struct acpihp_slot_ops *slot_ops_curr;

+extern struct acpihp_slot_ops acpihp_enum_ej0;
+
/*
* Array of platform specific enumeration methods.
* Entries in the array should be sorted by descending priority order.
*/
static struct acpihp_slot_ops *slot_ops_array[] = {
+ &acpihp_enum_ej0,
NULL
};

diff --git a/drivers/acpi/hotplug/slot_enum_ej0.c b/drivers/acpi/hotplug/slot_enum_ej0.c
new file mode 100644
index 0000000..2788860
--- /dev/null
+++ b/drivers/acpi/hotplug/slot_enum_ej0.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Gaohuai Han <***@huawei.com>
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/acpi.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_hotplug.h>
+
+static acpi_status acpihp_enum_ej0_init(void)
+{
+ return AE_OK;
+}
+
+static void acpihp_enum_ej0_fini(void)
+{
+}
+
+static acpi_status acpihp_enum_ej0_check(acpi_handle handle)
+{
+ acpi_handle tmp;
+
+ /* Assume a device object with _EJ0 and _STA has a hotplug slot. */
+ if (ACPI_FAILURE(acpi_get_handle(handle, "_EJ0", &tmp)))
+ return AE_ERROR;
+ if (ACPI_FAILURE(acpi_get_handle(handle, METHOD_NAME__STA, &tmp)))
+ return AE_ERROR;
+
+ return AE_OK;
+}
+
+static acpi_status
+acpihp_enum_ej0_get_capabilities(acpi_handle handle, u32 *capability)
+{
+ /*
+ * Assume device objects with _EJ0 are capable of:
+ * ONLINE, OFFLINE, POWEROFF, HOTPLUG
+ */
+ *capability = ACPIHP_SLOT_CAP_ONLINE | ACPIHP_SLOT_CAP_OFFLINE |
+ ACPIHP_SLOT_CAP_POWEROFF | ACPIHP_SLOT_CAP_HOTPLUG;
+
+ return AE_OK;
+}
+
+static acpi_status acpihp_enum_ej0_poweron(acpi_handle handle)
+{
+ return AE_SUPPORT;
+}
+
+static acpi_status acpihp_enum_ej0_poweroff(acpi_handle handle)
+{
+ acpi_status status;
+ unsigned long long sta;
+ union acpi_object arg;
+ struct acpi_object_list arg_list;
+
+ if (!handle)
+ return AE_BAD_PARAMETER;
+
+ arg_list.count = 1;
+ arg_list.pointer = &arg;
+ arg.type = ACPI_TYPE_INTEGER;
+ arg.integer.value = 1;
+ status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to power off object %p.\n", handle);
+ return status;
+ }
+
+ status = acpi_evaluate_integer(handle, METHOD_NAME__STA, NULL, &sta);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to evaluate _STA method of object %p\n",
+ handle);
+ return status;
+ }
+
+ if (sta & (ACPI_STA_DEVICE_FUNCTIONING | ACPI_STA_DEVICE_ENABLED)) {
+ ACPIHP_WARN("object %p is still active after executing _EJ0.\n",
+ handle);
+ return AE_ERROR;
+ }
+
+ return AE_OK;
+}
+
+struct acpihp_slot_ops acpihp_enum_ej0 = {
+ .init = acpihp_enum_ej0_init,
+ .fini = acpihp_enum_ej0_fini,
+ .check = acpihp_enum_ej0_check,
+ .get_capabilities = acpihp_enum_ej0_get_capabilities,
+ .poweron = acpihp_enum_ej0_poweron,
+ .poweroff = acpihp_enum_ej0_poweroff,
+ .owner = THIS_MODULE,
+ .desc = "ACPI _EJ0 Based Hotplug Slot Enumerator"
+};
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:58 UTC
Permalink
From: Jiang Liu <***@huawei.com>

Some ACPI hotplug slots may have dependencies on other ACPI hotplug slots.
For example, if a hotpluggable memory board is connected to a hotpluggble
physical processor, the physical processor must be powered on before
powering the memory board on.

We could get dependency relationships by analyze ACPI namespace topology
and evaluate ACPI _EDL method. So this patch implements several functions
to analyze dependencies among ACPI hotplug slots.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
---
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/acpihp_drv.h | 14 ++++
drivers/acpi/hotplug/dependency.c | 167 +++++++++++++++++++++++++++++++++++++
3 files changed, 182 insertions(+)
create mode 100644 drivers/acpi/hotplug/dependency.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index d69832f..8477c71 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -11,3 +11,4 @@ acpihp_enum-y += slot_enum_ej0.o

obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
acpihp_drv-y = drv_main.o
+acpihp_drv-y += dependency.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index 18330f7..c4ff91c 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -53,10 +53,24 @@ struct acpihp_slot_drv {
struct acpihp_cancel_context cancel_ctx;
};

+struct acpihp_slot_dependency {
+ struct list_head node;
+ struct acpihp_slot *slot;
+ u32 opcodes;
+};
+
void acpihp_drv_get_data(struct acpihp_slot *slot,
struct acpihp_slot_drv **data);
int acpihp_drv_enumerate_devices(struct acpihp_slot *slot);
void acpihp_drv_update_slot_state(struct acpihp_slot *slot);
int acpihp_drv_update_slot_status(struct acpihp_slot *slot);

+int acpihp_drv_add_slot_to_dependency_list(struct acpihp_slot *slot,
+ struct list_head *slot_list);
+void acpihp_drv_destroy_dependency_list(struct list_head *slot_list);
+int acpihp_drv_filter_dependency_list(struct list_head *old_head,
+ struct list_head *new_head, u32 opcode);
+int acpihp_drv_generate_dependency_list(struct acpihp_slot *slot,
+ struct list_head *slot_list, enum acpihp_drv_cmd cmd);
+
#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/dependency.c b/drivers/acpi/hotplug/dependency.c
new file mode 100644
index 0000000..d2d7dbb
--- /dev/null
+++ b/drivers/acpi/hotplug/dependency.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+/* Insert a slot into the dependency list at the head */
+int acpihp_drv_add_slot_to_dependency_list(struct acpihp_slot *slot,
+ struct list_head *slot_list)
+{
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, slot_list, node)
+ if (dep->slot == slot)
+ return 0;
+
+ dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ if (!dep) {
+ ACPIHP_DEBUG("fails to allocate memory for %s.\n", slot->name);
+ return -ENOMEM;
+ }
+ dep->slot = slot;
+ list_add(&dep->node, slot_list);
+
+ return 0;
+}
+
+static int acpihp_drv_get_online_dependency(struct acpihp_slot *slot,
+ struct list_head *slot_list)
+{
+ int ret = 0;
+ struct acpihp_slot *temp;
+
+ for (temp = slot; temp && ret == 0; temp = temp->parent)
+ ret = acpihp_drv_add_slot_to_dependency_list(temp, slot_list);
+
+ return ret;
+}
+
+static int acpihp_drv_for_each_edl(struct acpihp_slot *slot, void *argp,
+ int(*cb)(struct device *dev, void *argp))
+{
+ /* TODO */
+ return 0;
+}
+
+static int acpihp_drv_add_offline_dependency(struct device *dev, void *argp)
+{
+ int ret;
+ struct acpihp_slot *slot;
+ struct list_head *list = argp;
+
+ slot = container_of(dev, struct acpihp_slot, dev);
+ ret = acpihp_drv_add_slot_to_dependency_list(slot, list);
+ if (!ret)
+ ret = device_for_each_child(&slot->dev, argp,
+ &acpihp_drv_add_offline_dependency);
+ if (!ret)
+ ret = acpihp_drv_for_each_edl(slot, argp,
+ &acpihp_drv_add_offline_dependency);
+
+ return ret;
+}
+
+static int acpihp_drv_get_offline_dependency(struct acpihp_slot *slot,
+ struct list_head *slot_list)
+{
+ return acpihp_drv_add_offline_dependency(&slot->dev, slot_list);
+}
+
+int acpihp_drv_generate_dependency_list(struct acpihp_slot *slot,
+ struct list_head *slot_list, enum acpihp_drv_cmd cmd)
+{
+ int retval;
+
+ switch (cmd) {
+ case ACPIHP_DRV_CMD_POWERON:
+ /* fall through */
+ case ACPIHP_DRV_CMD_CONNECT:
+ /* fall through */
+ case ACPIHP_DRV_CMD_CONFIGURE:
+ retval = acpihp_drv_get_online_dependency(slot, slot_list);
+ break;
+
+ case ACPIHP_DRV_CMD_POWEROFF:
+ /* fall through */
+ case ACPIHP_DRV_CMD_DISCONNECT:
+ /* fall through */
+ case ACPIHP_DRV_CMD_UNCONFIGURE:
+ retval = acpihp_drv_get_offline_dependency(slot, slot_list);
+ break;
+
+ default:
+ retval = -EINVAL;
+ break;
+ }
+
+ return retval;
+}
+
+/*
+ * Generate a new list with slots from the old list which need to
+ * execute a specific operation.
+ */
+int acpihp_drv_filter_dependency_list(struct list_head *old_head,
+ struct list_head *new_head, u32 opcode)
+{
+ struct acpihp_slot_dependency *old_dep, *new_dep;
+
+ /* Initialize new list to empty */
+ INIT_LIST_HEAD(new_head);
+
+ list_for_each_entry(old_dep, old_head, node) {
+ /* Skip if the specified operation is not needed. */
+ if (!(old_dep->opcodes & opcode))
+ continue;
+
+ new_dep = kzalloc(sizeof(*new_dep), GFP_KERNEL);
+ if (!new_dep) {
+ ACPIHP_DEBUG("fails to filter depend list.\n");
+ acpihp_drv_destroy_dependency_list(new_head);
+ return -ENOMEM;
+ }
+
+ new_dep->slot = old_dep->slot;
+ new_dep->opcodes = old_dep->opcodes;
+ list_add_tail(&new_dep->node, new_head);
+ }
+
+ return 0;
+}
+
+void acpihp_drv_destroy_dependency_list(struct list_head *slot_list)
+{
+ struct acpihp_slot_dependency *asd, *temp;
+
+ list_for_each_entry_safe(asd, temp, slot_list, node) {
+ list_del(&asd->node);
+ kfree(asd);
+ }
+}
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:57 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements a skeleton for ACPI based system device hotplug driver.
This device class driver will be bound to and manage ACPI hotplug slots.

This is the default hotplug driver for ACPI based system device hotplug.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
---
drivers/acpi/Kconfig | 12 ++
drivers/acpi/hotplug/Makefile | 3 +
drivers/acpi/hotplug/acpihp_drv.h | 62 +++++++
drivers/acpi/hotplug/drv_main.c | 331 +++++++++++++++++++++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 drivers/acpi/hotplug/acpihp_drv.h
create mode 100644 drivers/acpi/hotplug/drv_main.c

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index c9f7918..89047a3 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -354,6 +354,18 @@ config ACPI_HOTPLUG_ENUM_EJ0

It's the default method to detect ACPI hotplug slots.

+config ACPI_HOTPLUG_DRIVER
+ tristate "ACPI Based System Device Hotplug Driver"
+ depends on ACPI_HOTPLUG
+ default y
+ help
+ This driver enables ACPI based system device hotplug, including
+ physical processor, memory device, IO host bridge and computer
+ node etc.
+
+ To compile this driver as a module, choose M here:
+ the module will be called acpihp_drv.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 25fac24..d69832f 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -8,3 +8,6 @@ acpihp-y = core.o device.o
obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
acpihp_enum-y = slot_enum.o
acpihp_enum-y += slot_enum_ej0.o
+
+obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
+acpihp_drv-y = drv_main.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
new file mode 100644
index 0000000..18330f7
--- /dev/null
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __ACPIHP_DRV_H__
+#define __ACPIHP_DRV_H__
+
+/* Commands to change state of a hotplug slot */
+enum acpihp_drv_cmd {
+ ACPIHP_DRV_CMD_NOOP = 0,
+ ACPIHP_DRV_CMD_POWERON = 0x1,
+ ACPIHP_DRV_CMD_CONNECT = 0x2,
+ ACPIHP_DRV_CMD_CONFIGURE = 0x4,
+ ACPIHP_DRV_CMD_UNCONFIGURE = 0x8,
+ ACPIHP_DRV_CMD_DISCONNECT = 0x10,
+ ACPIHP_DRV_CMD_POWEROFF = 0x20,
+ ACPIHP_DRV_CMD_CANCEL = 0x40,
+ ACPIHP_DRV_CMD_MAX
+};
+
+/* Hotplug operations may be triggered by firmware or OS */
+enum acpihp_dev_event {
+ ACPIHP_DRV_EVENT_FROM_FW,
+ ACPIHP_DRV_EVENT_FROM_OS
+};
+
+struct acpihp_slot_drv {
+ enum acpihp_dev_event event_flag;
+ struct mutex op_mutex; /* Prevent concurrent hotplugs. */
+ struct list_head depend_list; /* Dependency relationship */
+ atomic_t cancel_status;
+ atomic_t cancel_flag;
+ struct acpihp_cancel_context cancel_ctx;
+};
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data);
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot);
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot);
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot);
+
+#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c
new file mode 100644
index 0000000..538772d
--- /dev/null
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+static struct class_interface acpihp_drv_interface;
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data)
+{
+ *data = NULL;
+ acpihp_slot_get_drv_data(slot, &acpihp_drv_interface, (void **)data);
+}
+
+/* Update slot state according to state of devices connecting to it. */
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+ enum acpihp_slot_state state;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *dp;
+ bool connected = false;
+ bool configured = false;
+
+ if (!acpihp_slot_present(slot)) {
+ state = ACPIHP_SLOT_STATE_ABSENT;
+ goto out;
+ } else if (!acpihp_slot_powered(slot)) {
+ state = ACPIHP_SLOT_STATE_PRESENT;
+ goto out;
+ }
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN;
+ type < ACPIHP_DEV_TYPE_MAX && !configured;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ connected = true;
+ dp = container_of(ip, struct acpihp_dev_node, node);
+ if (dp->state == DEVICE_STATE_CONFIGURED) {
+ configured = true;
+ break;
+ }
+ }
+ klist_iter_exit(&iter);
+ }
+
+ if (configured)
+ state = ACPIHP_SLOT_STATE_CONFIGURED;
+ else if (connected)
+ state = ACPIHP_SLOT_STATE_CONNECTED;
+ else
+ state = ACPIHP_SLOT_STATE_POWERED;
+
+out:
+ acpihp_slot_change_state(slot, state);
+}
+
+/* Update slot state according to status of devices connecting to it. */
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot)
+{
+ int ret = 0;
+ enum acpihp_dev_type type;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+ struct acpi_device *dev;
+ struct acpihp_dev_info *info;
+
+ if (!slot)
+ return -EINVAL;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ for (type = ACPIHP_DEV_TYPE_MEM; type <= ACPIHP_DEV_TYPE_HOST_BRIDGE;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ dev = container_of(np->dev, struct acpi_device, dev);
+ ret = acpihp_dev_get_info(dev, info);
+ if (ret) {
+ ACPIHP_DEBUG("fails to get info about %s.\n",
+ dev_name(&dev->dev));
+ goto out;
+ }
+
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ }
+ }
+
+out:
+ kfree(info);
+
+ return ret;
+}
+EXPORT_SYMBOL(acpihp_drv_update_slot_status);
+
+/* Add ACPI device to hotplug slot's device list */
+static acpi_status acpihp_drv_enum_device(struct acpi_device *dev, void *argp)
+{
+ int ret = -ENOMEM;
+ acpi_status rv = AE_OK;
+ enum acpihp_dev_type type;
+ enum acpihp_dev_state state;
+ struct acpihp_dev_info *info;
+ struct acpihp_slot *slot = (struct acpihp_slot *)argp;
+
+ if (acpihp_dev_get_type(dev->handle, &type)) {
+ ACPIHP_DEBUG("fails to get device type of %s.\n",
+ dev_name(&dev->dev));
+ return AE_ERROR;
+ } else if (type == ACPIHP_DEV_TYPE_MAX) {
+ /*
+ * Some ACPI objects for IO devices, such as PCI/IDE etc, only
+ * implement _ADR instead of _HID/_CID, skip them.
+ */
+ return AE_CTRL_DEPTH;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info)
+ ret = acpihp_dev_get_info(dev, info);
+
+ if (!ret) {
+ if (info->status & ACPIHP_DEV_STATUS_STARTED)
+ state = DEVICE_STATE_CONFIGURED;
+ else
+ state = DEVICE_STATE_CONNECTED;
+
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+
+ if (acpihp_slot_add_device(slot, type, state, &dev->dev)) {
+ ACPIHP_DEBUG("fails to add %s to slot %s.\n",
+ dev_name(&dev->dev), slot->name);
+ rv = AE_ERROR;
+ }
+ } else {
+ ACPIHP_DEBUG("fails to query device info of %s.\n",
+ dev_name(&dev->dev));
+ }
+
+ kfree(info);
+
+ return rv;
+}
+
+/*
+ * Enumerator all devices connecting to a slot and add them onto slot's
+ * device lists.
+ */
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot)
+{
+ return acpihp_walk_devices(slot->handle, acpihp_drv_enum_device, slot);
+}
+
+static void acpihp_drv_remove_devices(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN; type < ACPIHP_DEV_TYPE_MAX; type++)
+ acpihp_remove_device_list(&slot->dev_lists[type]);
+}
+
+/* Callback function for ACPI system event notification. */
+static void acpihp_drv_event_handler(acpi_handle handle, u32 event,
+ void *context)
+{
+ /* TODO: handle ACPI hotplug events */
+}
+
+static acpi_status acpihp_drv_install_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_install_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler, slot);
+ ACPIHP_DEBUG("%s to install event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+
+ return status;
+}
+
+static void acpihp_drv_uninstall_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_remove_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler);
+ ACPIHP_DEBUG("%s to uninstall event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+}
+
+static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ /*
+ * Try to hold a reference to the slot_ops structure to prevent
+ * the platform specific enumerator driver from unloading.
+ */
+ if (!slot->slot_ops || !try_module_get(slot->slot_ops->owner)) {
+ ACPIHP_DEBUG("fails to get reference to slot_ops for %s.\n",
+ slot->name);
+ return -EINVAL;
+ }
+
+ /* install ACPI event notification handler for slot */
+ if (ACPI_FAILURE(acpihp_drv_install_handler(slot))) {
+ ACPIHP_DEBUG("fails to install event handler for %s.\n",
+ slot->name);
+ module_put(slot->slot_ops->owner);
+ return -EBUSY;
+ }
+
+ /* Enumerate all devices if slot is already powered. */
+ if (!acpihp_slot_powered(slot))
+ ACPIHP_DEBUG("slot %s is powered off.\n", slot->name);
+ else if (acpihp_drv_enumerate_devices(slot))
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE);
+
+ acpihp_drv_update_slot_state(slot);
+
+ drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+ if (drv_data) {
+ drv_data->event_flag = ACPIHP_DRV_EVENT_FROM_FW;
+ mutex_init(&drv_data->op_mutex);
+ INIT_LIST_HEAD(&drv_data->depend_list);
+ }
+ if (drv_data == NULL ||
+ acpihp_slot_attach_drv_data(slot, intf, (void *)drv_data)) {
+ ACPIHP_DEBUG("fails to attach driver data for %s.\n",
+ slot->name);
+ acpihp_drv_remove_devices(slot);
+ module_put(slot->slot_ops->owner);
+ kfree(drv_data);
+ return -ENOMEM;
+ }
+
+ ACPIHP_INFO("found hotplug slot %s.\n", slot->full_path);
+
+ return 0;
+}
+
+static void acpihp_drv_intf_remove(struct device *dev,
+ struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data = NULL;
+ struct acpihp_slot *slot =
+ container_of(dev, struct acpihp_slot, dev);
+
+ ACPIHP_INFO("remove hotplug slot %s.\n", slot->full_path);
+
+ acpihp_drv_uninstall_handler(slot);
+ acpihp_drv_remove_devices(slot);
+ acpihp_slot_detach_drv_data(slot, intf, (void **)&drv_data);
+ if (drv_data != NULL)
+ kfree(drv_data);
+
+ module_put(slot->slot_ops->owner);
+}
+
+/*
+ * register a class driver onto the acpihp_slot_class to manage all system
+ * device hotplug slots.
+ */
+static struct class_interface acpihp_drv_interface = {
+ .class = &acpihp_slot_class,
+ .add_dev = acpihp_drv_slot_add,
+ .remove_dev = acpihp_drv_intf_remove,
+};
+
+static int __init acpihp_drv_init(void)
+{
+ return class_interface_register(&acpihp_drv_interface);
+}
+
+static void __exit acpihp_drv_exit(void)
+{
+ class_interface_unregister(&acpihp_drv_interface);
+}
+
+module_init(acpihp_drv_init);
+module_exit(acpihp_drv_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jiang Liu <***@huawei.com>");
+MODULE_AUTHOR("Hanjun Guo <***@huawei.com>");
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tang Chen
2012-08-09 07:12:35 UTC
Permalink
Hi Liu~

I compiled this driver as a module, acpihp_drv. And when I loaded this module, it
gave the following error message:

# modprobe acpihp_drv
(the command hangs up, no return after 10 min)

#dmesg
......
[ 126.643350] BUG: unable to handle kernel NULL pointer dereference at 0000000000000078
[ 126.644007] IP: [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] PGD 105277a067 PUD 104f823067 PMD 0
[ 126.644007] Oops: 0002 [#1] SMP
[ 126.644007] Modules linked in: acpihp_drv(+) ebtable_nat ebtables ipt_MASQUERADE iptable_nat nf_nat iptable_mangle bridge stp llc sunrpc cpufreq_ondemand acpi_cpufreq freq_table mperf ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables ipv6 vhost_net macvtap macvlan tun uinput iTCO_wdt iTCO_vendor_support coretemp kvm_intel kvm crc32c_intel microcode lpc_ich mfd_core pcspkr i2c_i801 i2c_core ioatdma e1000e acpi_memhotplug i7core_edac edac_core igb dca mptsas mptscsih mptbase scsi_transport_sas
[ 126.644007] CPU 10
[ 126.644007] Pid: 2821, comm: modprobe Tainted: G A 3.6.0-rc1+ #6 FUJITSU-SV PRIMEQUEST 1800E/SB
[ 126.644007] RIP: 0010:[<ffffffff814c0cd3>] [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP: 0018:ffff8810589a9de8 EFLAGS: 00010246
[ 126.644007] RAX: 0000000000000000 RBX: 0000000000000078 RCX: 0000000000000000
[ 126.644007] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000078
[ 126.644007] RBP: ffff8810589a9e08 R08: 0000000000000000 R09: ffff8810589a9d88
[ 126.644007] R10: 00000000000013e5 R11: 00000000000013e5 R12: ffffffffa01460d0
[ 126.644007] R13: 0000000000000000 R14: ffffffffa014732b R15: 00000000000000bf
[ 126.644007] FS: 00007fecb1802700(0000) GS:ffff88105e640000(0000) knlGS:0000000000000000
[ 126.644007] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 126.644007] CR2: 0000000000000078 CR3: 0000001052772000 CR4: 00000000000007e0
[ 126.644007] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 126.644007] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 126.644007] Process modprobe (pid: 2821, threadinfo ffff8810589a8000, task ffff8810592f8000)
[ 126.644007] Stack:
[ 126.644007] ffff8810589a9e08 ffffffff810be37f ffffffffa0146220 ffffffff81a7b390
[ 126.644007] ffff8810589a9e58 ffffffff81317eb3 ffff8810589a9e48 0000000000000000
[ 126.644007] ffffffff81a342c0 ffffffff81a342e0 0000000000000000 ffffffffa0146220
[ 126.644007] Call Trace:
[ 126.644007] [<ffffffff810be37f>] ? tracepoint_module_notify+0xd9/0x14a
[ 126.644007] [<ffffffff81317eb3>] class_interface_register+0x4a/0xbc
[ 126.644007] [<ffffffffa00b8000>] ? 0xffffffffa00b7fff
[ 126.644007] [<ffffffffa00b8010>] acpihp_drv_init+0x10/0x12 [acpihp_drv]
[ 126.644007] [<ffffffff8100207f>] do_one_initcall+0x7f/0x139
[ 126.644007] [<ffffffff81093414>] sys_init_module+0x12d3/0x14e3
[ 126.644007] [<ffffffff81264b0d>] ? ddebug_dyndbg_boot_param_cb+0x45/0x45
[ 126.644007] [<ffffffff814c9469>] system_call_fastpath+0x16/0x1b
[ 126.644007] Code: 48 8b 04 25 80 c6 00 00 48 89 43 18 31 c0 5b 5b c9 c3 55 48 89 e5 53 48 83 ec 18 66 66 66 66 90 48 89 fb e8 5a 0c 00 00 48 89 df <f0> ff 0f 79 05 e8 06 ff ff ff 65 48 8b 04 25 80 c6 00 00 48 89
[ 126.644007] RIP [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP <ffff8810589a9de8>
[ 126.644007] CR2: 0000000000000078
[ 129.981335] ---[ end trace da17e9c9de8dd560 ]---
[ 139.085895] nr_pdflush_threads exported in /proc is scheduled for removal
[ 139.167394] sysctl: The scan_unevictable_pages sysctl/node-interface has been disabled for lack of a legitimate use case. If you have one, please send an email to linux-***@kvack.org.

Looks like it dereferenced a NULL pointer here.
May be it was my mistake that I didn't configure the environment correctly.
Would you please give me some advice ?

Thanks. :)
Post by Jiang Liu
This patch implements a skeleton for ACPI based system device hotplug driver.
This device class driver will be bound to and manage ACPI hotplug slots.
This is the default hotplug driver for ACPI based system device hotplug.
---
drivers/acpi/Kconfig | 12 ++
drivers/acpi/hotplug/Makefile | 3 +
drivers/acpi/hotplug/acpihp_drv.h | 62 +++++++
drivers/acpi/hotplug/drv_main.c | 331 +++++++++++++++++++++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 drivers/acpi/hotplug/acpihp_drv.h
create mode 100644 drivers/acpi/hotplug/drv_main.c
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index c9f7918..89047a3 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -354,6 +354,18 @@ config ACPI_HOTPLUG_ENUM_EJ0
It's the default method to detect ACPI hotplug slots.
+config ACPI_HOTPLUG_DRIVER
+ tristate "ACPI Based System Device Hotplug Driver"
+ depends on ACPI_HOTPLUG
+ default y
+ help
+ This driver enables ACPI based system device hotplug, including
+ physical processor, memory device, IO host bridge and computer
+ node etc.
+
+ the module will be called acpihp_drv.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 25fac24..d69832f 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -8,3 +8,6 @@ acpihp-y = core.o device.o
obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
acpihp_enum-y = slot_enum.o
acpihp_enum-y += slot_enum_ej0.o
+
+obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
+acpihp_drv-y = drv_main.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
new file mode 100644
index 0000000..18330f7
--- /dev/null
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __ACPIHP_DRV_H__
+#define __ACPIHP_DRV_H__
+
+/* Commands to change state of a hotplug slot */
+enum acpihp_drv_cmd {
+ ACPIHP_DRV_CMD_NOOP = 0,
+ ACPIHP_DRV_CMD_POWERON = 0x1,
+ ACPIHP_DRV_CMD_CONNECT = 0x2,
+ ACPIHP_DRV_CMD_CONFIGURE = 0x4,
+ ACPIHP_DRV_CMD_UNCONFIGURE = 0x8,
+ ACPIHP_DRV_CMD_DISCONNECT = 0x10,
+ ACPIHP_DRV_CMD_POWEROFF = 0x20,
+ ACPIHP_DRV_CMD_CANCEL = 0x40,
+ ACPIHP_DRV_CMD_MAX
+};
+
+/* Hotplug operations may be triggered by firmware or OS */
+enum acpihp_dev_event {
+ ACPIHP_DRV_EVENT_FROM_FW,
+ ACPIHP_DRV_EVENT_FROM_OS
+};
+
+struct acpihp_slot_drv {
+ enum acpihp_dev_event event_flag;
+ struct mutex op_mutex; /* Prevent concurrent hotplugs. */
+ struct list_head depend_list; /* Dependency relationship */
+ atomic_t cancel_status;
+ atomic_t cancel_flag;
+ struct acpihp_cancel_context cancel_ctx;
+};
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data);
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot);
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot);
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot);
+
+#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c
new file mode 100644
index 0000000..538772d
--- /dev/null
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+static struct class_interface acpihp_drv_interface;
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data)
+{
+ *data = NULL;
+ acpihp_slot_get_drv_data(slot, &acpihp_drv_interface, (void **)data);
+}
+
+/* Update slot state according to state of devices connecting to it. */
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+ enum acpihp_slot_state state;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *dp;
+ bool connected = false;
+ bool configured = false;
+
+ if (!acpihp_slot_present(slot)) {
+ state = ACPIHP_SLOT_STATE_ABSENT;
+ goto out;
+ } else if (!acpihp_slot_powered(slot)) {
+ state = ACPIHP_SLOT_STATE_PRESENT;
+ goto out;
+ }
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN;
+ type < ACPIHP_DEV_TYPE_MAX && !configured;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ connected = true;
+ dp = container_of(ip, struct acpihp_dev_node, node);
+ if (dp->state == DEVICE_STATE_CONFIGURED) {
+ configured = true;
+ break;
+ }
+ }
+ klist_iter_exit(&iter);
+ }
+
+ if (configured)
+ state = ACPIHP_SLOT_STATE_CONFIGURED;
+ else if (connected)
+ state = ACPIHP_SLOT_STATE_CONNECTED;
+ else
+ state = ACPIHP_SLOT_STATE_POWERED;
+
+ acpihp_slot_change_state(slot, state);
+}
+
+/* Update slot state according to status of devices connecting to it. */
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot)
+{
+ int ret = 0;
+ enum acpihp_dev_type type;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+ struct acpi_device *dev;
+ struct acpihp_dev_info *info;
+
+ if (!slot)
+ return -EINVAL;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ for (type = ACPIHP_DEV_TYPE_MEM; type <= ACPIHP_DEV_TYPE_HOST_BRIDGE;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ dev = container_of(np->dev, struct acpi_device, dev);
+ ret = acpihp_dev_get_info(dev, info);
+ if (ret) {
+ ACPIHP_DEBUG("fails to get info about %s.\n",
+ dev_name(&dev->dev));
+ goto out;
+ }
+
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ }
+ }
+
+ kfree(info);
+
+ return ret;
+}
+EXPORT_SYMBOL(acpihp_drv_update_slot_status);
+
+/* Add ACPI device to hotplug slot's device list */
+static acpi_status acpihp_drv_enum_device(struct acpi_device *dev, void *argp)
+{
+ int ret = -ENOMEM;
+ acpi_status rv = AE_OK;
+ enum acpihp_dev_type type;
+ enum acpihp_dev_state state;
+ struct acpihp_dev_info *info;
+ struct acpihp_slot *slot = (struct acpihp_slot *)argp;
+
+ if (acpihp_dev_get_type(dev->handle, &type)) {
+ ACPIHP_DEBUG("fails to get device type of %s.\n",
+ dev_name(&dev->dev));
+ return AE_ERROR;
+ } else if (type == ACPIHP_DEV_TYPE_MAX) {
+ /*
+ * Some ACPI objects for IO devices, such as PCI/IDE etc, only
+ * implement _ADR instead of _HID/_CID, skip them.
+ */
+ return AE_CTRL_DEPTH;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info)
+ ret = acpihp_dev_get_info(dev, info);
+
+ if (!ret) {
+ if (info->status & ACPIHP_DEV_STATUS_STARTED)
+ state = DEVICE_STATE_CONFIGURED;
+ else
+ state = DEVICE_STATE_CONNECTED;
+
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+
+ if (acpihp_slot_add_device(slot, type, state, &dev->dev)) {
+ ACPIHP_DEBUG("fails to add %s to slot %s.\n",
+ dev_name(&dev->dev), slot->name);
+ rv = AE_ERROR;
+ }
+ } else {
+ ACPIHP_DEBUG("fails to query device info of %s.\n",
+ dev_name(&dev->dev));
+ }
+
+ kfree(info);
+
+ return rv;
+}
+
+/*
+ * Enumerator all devices connecting to a slot and add them onto slot's
+ * device lists.
+ */
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot)
+{
+ return acpihp_walk_devices(slot->handle, acpihp_drv_enum_device, slot);
+}
+
+static void acpihp_drv_remove_devices(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN; type < ACPIHP_DEV_TYPE_MAX; type++)
+ acpihp_remove_device_list(&slot->dev_lists[type]);
+}
+
+/* Callback function for ACPI system event notification. */
+static void acpihp_drv_event_handler(acpi_handle handle, u32 event,
+ void *context)
+{
+ /* TODO: handle ACPI hotplug events */
+}
+
+static acpi_status acpihp_drv_install_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_install_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler, slot);
+ ACPIHP_DEBUG("%s to install event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+
+ return status;
+}
+
+static void acpihp_drv_uninstall_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_remove_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler);
+ ACPIHP_DEBUG("%s to uninstall event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+}
+
+static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ /*
+ * Try to hold a reference to the slot_ops structure to prevent
+ * the platform specific enumerator driver from unloading.
+ */
+ if (!slot->slot_ops || !try_module_get(slot->slot_ops->owner)) {
+ ACPIHP_DEBUG("fails to get reference to slot_ops for %s.\n",
+ slot->name);
+ return -EINVAL;
+ }
+
+ /* install ACPI event notification handler for slot */
+ if (ACPI_FAILURE(acpihp_drv_install_handler(slot))) {
+ ACPIHP_DEBUG("fails to install event handler for %s.\n",
+ slot->name);
+ module_put(slot->slot_ops->owner);
+ return -EBUSY;
+ }
+
+ /* Enumerate all devices if slot is already powered. */
+ if (!acpihp_slot_powered(slot))
+ ACPIHP_DEBUG("slot %s is powered off.\n", slot->name);
+ else if (acpihp_drv_enumerate_devices(slot))
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE);
+
+ acpihp_drv_update_slot_state(slot);
+
+ drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+ if (drv_data) {
+ drv_data->event_flag = ACPIHP_DRV_EVENT_FROM_FW;
+ mutex_init(&drv_data->op_mutex);
+ INIT_LIST_HEAD(&drv_data->depend_list);
+ }
+ if (drv_data == NULL ||
+ acpihp_slot_attach_drv_data(slot, intf, (void *)drv_data)) {
+ ACPIHP_DEBUG("fails to attach driver data for %s.\n",
+ slot->name);
+ acpihp_drv_remove_devices(slot);
+ module_put(slot->slot_ops->owner);
+ kfree(drv_data);
+ return -ENOMEM;
+ }
+
+ ACPIHP_INFO("found hotplug slot %s.\n", slot->full_path);
+
+ return 0;
+}
+
+static void acpihp_drv_intf_remove(struct device *dev,
+ struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data = NULL;
+ struct acpihp_slot *slot =
+ container_of(dev, struct acpihp_slot, dev);
+
+ ACPIHP_INFO("remove hotplug slot %s.\n", slot->full_path);
+
+ acpihp_drv_uninstall_handler(slot);
+ acpihp_drv_remove_devices(slot);
+ acpihp_slot_detach_drv_data(slot, intf, (void **)&drv_data);
+ if (drv_data != NULL)
+ kfree(drv_data);
+
+ module_put(slot->slot_ops->owner);
+}
+
+/*
+ * register a class driver onto the acpihp_slot_class to manage all system
+ * device hotplug slots.
+ */
+static struct class_interface acpihp_drv_interface = {
+ .class = &acpihp_slot_class,
+ .add_dev = acpihp_drv_slot_add,
+ .remove_dev = acpihp_drv_intf_remove,
+};
+
+static int __init acpihp_drv_init(void)
+{
+ return class_interface_register(&acpihp_drv_interface);
+}
+
+static void __exit acpihp_drv_exit(void)
+{
+ class_interface_unregister(&acpihp_drv_interface);
+}
+
+module_init(acpihp_drv_init);
+module_exit(acpihp_drv_exit);
+
+MODULE_LICENSE("GPL v2");
--
Best Regards,
Tang chen
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-09 07:40:40 UTC
Permalink
Hi Tang,
Thanks for testing.
Currently there's a limitation that you need to insert acpihp_enum driver first.
Will fix this issue in next version.
Regards!
Gerry
Post by Tang Chen
Hi Liu~
I compiled this driver as a module, acpihp_drv. And when I loaded this module, it
# modprobe acpihp_drv
(the command hangs up, no return after 10 min)
#dmesg
......
[ 126.643350] BUG: unable to handle kernel NULL pointer dereference at 0000000000000078
[ 126.644007] IP: [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] PGD 105277a067 PUD 104f823067 PMD 0
[ 126.644007] Oops: 0002 [#1] SMP
[ 126.644007] Modules linked in: acpihp_drv(+) ebtable_nat ebtables ipt_MASQUERADE iptable_nat nf_nat iptable_mangle bridge stp llc sunrpc cpufreq_ondemand acpi_cpufreq freq_table mperf ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables ipv6 vhost_net macvtap macvlan tun uinput iTCO_wdt iTCO_vendor_support coretemp kvm_intel kvm crc32c_intel microcode lpc_ich mfd_core pcspkr i2c_i801 i2c_core ioatdma e1000e acpi_memhotplug i7core_edac edac_core igb dca mptsas mptscsih mptbase scsi_transport_sas
[ 126.644007] CPU 10
[ 126.644007] Pid: 2821, comm: modprobe Tainted: G A 3.6.0-rc1+ #6 FUJITSU-SV PRIMEQUEST 1800E/SB
[ 126.644007] RIP: 0010:[<ffffffff814c0cd3>] [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP: 0018:ffff8810589a9de8 EFLAGS: 00010246
[ 126.644007] RAX: 0000000000000000 RBX: 0000000000000078 RCX: 0000000000000000
[ 126.644007] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000078
[ 126.644007] RBP: ffff8810589a9e08 R08: 0000000000000000 R09: ffff8810589a9d88
[ 126.644007] R10: 00000000000013e5 R11: 00000000000013e5 R12: ffffffffa01460d0
[ 126.644007] R13: 0000000000000000 R14: ffffffffa014732b R15: 00000000000000bf
[ 126.644007] FS: 00007fecb1802700(0000) GS:ffff88105e640000(0000) knlGS:0000000000000000
[ 126.644007] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 126.644007] CR2: 0000000000000078 CR3: 0000001052772000 CR4: 00000000000007e0
[ 126.644007] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 126.644007] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 126.644007] Process modprobe (pid: 2821, threadinfo ffff8810589a8000, task ffff8810592f8000)
[ 126.644007] ffff8810589a9e08 ffffffff810be37f ffffffffa0146220 ffffffff81a7b390
[ 126.644007] ffff8810589a9e58 ffffffff81317eb3 ffff8810589a9e48 0000000000000000
[ 126.644007] ffffffff81a342c0 ffffffff81a342e0 0000000000000000 ffffffffa0146220
[ 126.644007] [<ffffffff810be37f>] ? tracepoint_module_notify+0xd9/0x14a
[ 126.644007] [<ffffffff81317eb3>] class_interface_register+0x4a/0xbc
[ 126.644007] [<ffffffffa00b8000>] ? 0xffffffffa00b7fff
[ 126.644007] [<ffffffffa00b8010>] acpihp_drv_init+0x10/0x12 [acpihp_drv]
[ 126.644007] [<ffffffff8100207f>] do_one_initcall+0x7f/0x139
[ 126.644007] [<ffffffff81093414>] sys_init_module+0x12d3/0x14e3
[ 126.644007] [<ffffffff81264b0d>] ? ddebug_dyndbg_boot_param_cb+0x45/0x45
[ 126.644007] [<ffffffff814c9469>] system_call_fastpath+0x16/0x1b
[ 126.644007] Code: 48 8b 04 25 80 c6 00 00 48 89 43 18 31 c0 5b 5b c9 c3 55 48 89 e5 53 48 83 ec 18 66 66 66 66 90 48 89 fb e8 5a 0c 00 00 48 89 df <f0> ff 0f 79 05 e8 06 ff ff ff 65 48 8b 04 25 80 c6 00 00 48 89
[ 126.644007] RIP [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP <ffff8810589a9de8>
[ 126.644007] CR2: 0000000000000078
[ 129.981335] ---[ end trace da17e9c9de8dd560 ]---
[ 139.085895] nr_pdflush_threads exported in /proc is scheduled for removal
Looks like it dereferenced a NULL pointer here.
May be it was my mistake that I didn't configure the environment correctly.
Would you please give me some advice ?
Thanks. :)
Post by Jiang Liu
This patch implements a skeleton for ACPI based system device hotplug driver.
This device class driver will be bound to and manage ACPI hotplug slots.
This is the default hotplug driver for ACPI based system device hotplug.
---
drivers/acpi/Kconfig | 12 ++
drivers/acpi/hotplug/Makefile | 3 +
drivers/acpi/hotplug/acpihp_drv.h | 62 +++++++
drivers/acpi/hotplug/drv_main.c | 331 +++++++++++++++++++++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 drivers/acpi/hotplug/acpihp_drv.h
create mode 100644 drivers/acpi/hotplug/drv_main.c
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index c9f7918..89047a3 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -354,6 +354,18 @@ config ACPI_HOTPLUG_ENUM_EJ0
It's the default method to detect ACPI hotplug slots.
+config ACPI_HOTPLUG_DRIVER
+ tristate "ACPI Based System Device Hotplug Driver"
+ depends on ACPI_HOTPLUG
+ default y
+ help
+ This driver enables ACPI based system device hotplug, including
+ physical processor, memory device, IO host bridge and computer
+ node etc.
+
+ the module will be called acpihp_drv.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 25fac24..d69832f 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -8,3 +8,6 @@ acpihp-y = core.o device.o
obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
acpihp_enum-y = slot_enum.o
acpihp_enum-y += slot_enum_ej0.o
+
+obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
+acpihp_drv-y = drv_main.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
new file mode 100644
index 0000000..18330f7
--- /dev/null
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#ifndef __ACPIHP_DRV_H__
+#define __ACPIHP_DRV_H__
+
+/* Commands to change state of a hotplug slot */
+enum acpihp_drv_cmd {
+ ACPIHP_DRV_CMD_NOOP = 0,
+ ACPIHP_DRV_CMD_POWERON = 0x1,
+ ACPIHP_DRV_CMD_CONNECT = 0x2,
+ ACPIHP_DRV_CMD_CONFIGURE = 0x4,
+ ACPIHP_DRV_CMD_UNCONFIGURE = 0x8,
+ ACPIHP_DRV_CMD_DISCONNECT = 0x10,
+ ACPIHP_DRV_CMD_POWEROFF = 0x20,
+ ACPIHP_DRV_CMD_CANCEL = 0x40,
+ ACPIHP_DRV_CMD_MAX
+};
+
+/* Hotplug operations may be triggered by firmware or OS */
+enum acpihp_dev_event {
+ ACPIHP_DRV_EVENT_FROM_FW,
+ ACPIHP_DRV_EVENT_FROM_OS
+};
+
+struct acpihp_slot_drv {
+ enum acpihp_dev_event event_flag;
+ struct mutex op_mutex; /* Prevent concurrent hotplugs. */
+ struct list_head depend_list; /* Dependency relationship */
+ atomic_t cancel_status;
+ atomic_t cancel_flag;
+ struct acpihp_cancel_context cancel_ctx;
+};
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data);
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot);
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot);
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot);
+
+#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c
new file mode 100644
index 0000000..538772d
--- /dev/null
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+static struct class_interface acpihp_drv_interface;
+
+void acpihp_drv_get_data(struct acpihp_slot *slot,
+ struct acpihp_slot_drv **data)
+{
+ *data = NULL;
+ acpihp_slot_get_drv_data(slot, &acpihp_drv_interface, (void **)data);
+}
+
+/* Update slot state according to state of devices connecting to it. */
+void acpihp_drv_update_slot_state(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+ enum acpihp_slot_state state;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *dp;
+ bool connected = false;
+ bool configured = false;
+
+ if (!acpihp_slot_present(slot)) {
+ state = ACPIHP_SLOT_STATE_ABSENT;
+ goto out;
+ } else if (!acpihp_slot_powered(slot)) {
+ state = ACPIHP_SLOT_STATE_PRESENT;
+ goto out;
+ }
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN;
+ type < ACPIHP_DEV_TYPE_MAX && !configured;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ connected = true;
+ dp = container_of(ip, struct acpihp_dev_node, node);
+ if (dp->state == DEVICE_STATE_CONFIGURED) {
+ configured = true;
+ break;
+ }
+ }
+ klist_iter_exit(&iter);
+ }
+
+ if (configured)
+ state = ACPIHP_SLOT_STATE_CONFIGURED;
+ else if (connected)
+ state = ACPIHP_SLOT_STATE_CONNECTED;
+ else
+ state = ACPIHP_SLOT_STATE_POWERED;
+
+ acpihp_slot_change_state(slot, state);
+}
+
+/* Update slot state according to status of devices connecting to it. */
+int acpihp_drv_update_slot_status(struct acpihp_slot *slot)
+{
+ int ret = 0;
+ enum acpihp_dev_type type;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+ struct acpi_device *dev;
+ struct acpihp_dev_info *info;
+
+ if (!slot)
+ return -EINVAL;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ for (type = ACPIHP_DEV_TYPE_MEM; type <= ACPIHP_DEV_TYPE_HOST_BRIDGE;
+ type++) {
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ dev = container_of(np->dev, struct acpi_device, dev);
+ ret = acpihp_dev_get_info(dev, info);
+ if (ret) {
+ ACPIHP_DEBUG("fails to get info about %s.\n",
+ dev_name(&dev->dev));
+ goto out;
+ }
+
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ }
+ }
+
+ kfree(info);
+
+ return ret;
+}
+EXPORT_SYMBOL(acpihp_drv_update_slot_status);
+
+/* Add ACPI device to hotplug slot's device list */
+static acpi_status acpihp_drv_enum_device(struct acpi_device *dev, void *argp)
+{
+ int ret = -ENOMEM;
+ acpi_status rv = AE_OK;
+ enum acpihp_dev_type type;
+ enum acpihp_dev_state state;
+ struct acpihp_dev_info *info;
+ struct acpihp_slot *slot = (struct acpihp_slot *)argp;
+
+ if (acpihp_dev_get_type(dev->handle, &type)) {
+ ACPIHP_DEBUG("fails to get device type of %s.\n",
+ dev_name(&dev->dev));
+ return AE_ERROR;
+ } else if (type == ACPIHP_DEV_TYPE_MAX) {
+ /*
+ * Some ACPI objects for IO devices, such as PCI/IDE etc, only
+ * implement _ADR instead of _HID/_CID, skip them.
+ */
+ return AE_CTRL_DEPTH;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info)
+ ret = acpihp_dev_get_info(dev, info);
+
+ if (!ret) {
+ if (info->status & ACPIHP_DEV_STATUS_STARTED)
+ state = DEVICE_STATE_CONFIGURED;
+ else
+ state = DEVICE_STATE_CONNECTED;
+
+ if (info->status & ACPIHP_DEV_STATUS_IRREMOVABLE)
+ acpihp_slot_set_flag(slot,
+ ACPIHP_SLOT_FLAG_IRREMOVABLE);
+ if (info->status & ACPIHP_DEV_STATUS_FAULT)
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+
+ if (acpihp_slot_add_device(slot, type, state, &dev->dev)) {
+ ACPIHP_DEBUG("fails to add %s to slot %s.\n",
+ dev_name(&dev->dev), slot->name);
+ rv = AE_ERROR;
+ }
+ } else {
+ ACPIHP_DEBUG("fails to query device info of %s.\n",
+ dev_name(&dev->dev));
+ }
+
+ kfree(info);
+
+ return rv;
+}
+
+/*
+ * Enumerator all devices connecting to a slot and add them onto slot's
+ * device lists.
+ */
+int acpihp_drv_enumerate_devices(struct acpihp_slot *slot)
+{
+ return acpihp_walk_devices(slot->handle, acpihp_drv_enum_device, slot);
+}
+
+static void acpihp_drv_remove_devices(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+
+ for (type = ACPIHP_DEV_TYPE_UNKNOWN; type < ACPIHP_DEV_TYPE_MAX; type++)
+ acpihp_remove_device_list(&slot->dev_lists[type]);
+}
+
+/* Callback function for ACPI system event notification. */
+static void acpihp_drv_event_handler(acpi_handle handle, u32 event,
+ void *context)
+{
+ /* TODO: handle ACPI hotplug events */
+}
+
+static acpi_status acpihp_drv_install_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_install_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler, slot);
+ ACPIHP_DEBUG("%s to install event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+
+ return status;
+}
+
+static void acpihp_drv_uninstall_handler(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ status = acpi_remove_notify_handler(slot->handle, ACPI_SYSTEM_NOTIFY,
+ acpihp_drv_event_handler);
+ ACPIHP_DEBUG("%s to uninstall event handler for %s.\n",
+ ACPI_SUCCESS(status) ? "succeeds" : "fails", slot->name);
+}
+
+static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+ /*
+ * Try to hold a reference to the slot_ops structure to prevent
+ * the platform specific enumerator driver from unloading.
+ */
+ if (!slot->slot_ops || !try_module_get(slot->slot_ops->owner)) {
+ ACPIHP_DEBUG("fails to get reference to slot_ops for %s.\n",
+ slot->name);
+ return -EINVAL;
+ }
+
+ /* install ACPI event notification handler for slot */
+ if (ACPI_FAILURE(acpihp_drv_install_handler(slot))) {
+ ACPIHP_DEBUG("fails to install event handler for %s.\n",
+ slot->name);
+ module_put(slot->slot_ops->owner);
+ return -EBUSY;
+ }
+
+ /* Enumerate all devices if slot is already powered. */
+ if (!acpihp_slot_powered(slot))
+ ACPIHP_DEBUG("slot %s is powered off.\n", slot->name);
+ else if (acpihp_drv_enumerate_devices(slot))
+ acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE);
+
+ acpihp_drv_update_slot_state(slot);
+
+ drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+ if (drv_data) {
+ drv_data->event_flag = ACPIHP_DRV_EVENT_FROM_FW;
+ mutex_init(&drv_data->op_mutex);
+ INIT_LIST_HEAD(&drv_data->depend_list);
+ }
+ if (drv_data == NULL ||
+ acpihp_slot_attach_drv_data(slot, intf, (void *)drv_data)) {
+ ACPIHP_DEBUG("fails to attach driver data for %s.\n",
+ slot->name);
+ acpihp_drv_remove_devices(slot);
+ module_put(slot->slot_ops->owner);
+ kfree(drv_data);
+ return -ENOMEM;
+ }
+
+ ACPIHP_INFO("found hotplug slot %s.\n", slot->full_path);
+
+ return 0;
+}
+
+static void acpihp_drv_intf_remove(struct device *dev,
+ struct class_interface *intf)
+{
+ struct acpihp_slot_drv *drv_data = NULL;
+ struct acpihp_slot *slot =
+ container_of(dev, struct acpihp_slot, dev);
+
+ ACPIHP_INFO("remove hotplug slot %s.\n", slot->full_path);
+
+ acpihp_drv_uninstall_handler(slot);
+ acpihp_drv_remove_devices(slot);
+ acpihp_slot_detach_drv_data(slot, intf, (void **)&drv_data);
+ if (drv_data != NULL)
+ kfree(drv_data);
+
+ module_put(slot->slot_ops->owner);
+}
+
+/*
+ * register a class driver onto the acpihp_slot_class to manage all system
+ * device hotplug slots.
+ */
+static struct class_interface acpihp_drv_interface = {
+ .class = &acpihp_slot_class,
+ .add_dev = acpihp_drv_slot_add,
+ .remove_dev = acpihp_drv_intf_remove,
+};
+
+static int __init acpihp_drv_init(void)
+{
+ return class_interface_register(&acpihp_drv_interface);
+}
+
+static void __exit acpihp_drv_exit(void)
+{
+ class_interface_unregister(&acpihp_drv_interface);
+}
+
+module_init(acpihp_drv_init);
+module_exit(acpihp_drv_exit);
+
+MODULE_LICENSE("GPL v2");
Tang Chen
2012-08-09 08:41:36 UTC
Permalink
Hi Liu ~
Post by Jiang Liu
Hi Tang,
Thanks for testing.
Currently there's a limitation that you need to insert acpihp_enum driver first.
Sorry, I didn't make it clear. I did load acpihp_enum module first, and then load acpihp_drv.

And I just tried it some more times. It just hung up, but dmesg had no output.
Like this:

# modprobe acpihp_enum
(OK, and sysfs interfaces have been created)
# modprobe acpihp_drv
(hang up)

# dmesg
(nothing)

The "modprobe acpihp_drv" process's call trace shows that it hung at the following function:
#0 0x00000032836aab80 in __nanosleep_nocancel () from /lib64/libc.so.6
#1 0x00000032836deb64 in usleep () from /lib64/libc.so.6
......

I have tried several times and I cannot reproduce the situation I just said.
Maybe my box has something different with yours. And I'll try to find out why.

Thanks for your advice. :)
Post by Jiang Liu
Will fix this issue in next version.
Regards!
Gerry
Post by Tang Chen
Hi Liu~
I compiled this driver as a module, acpihp_drv. And when I loaded this module, it
# modprobe acpihp_drv
(the command hangs up, no return after 10 min)
#dmesg
......
[ 126.643350] BUG: unable to handle kernel NULL pointer dereference at 0000000000000078
[ 126.644007] IP: [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] PGD 105277a067 PUD 104f823067 PMD 0
[ 126.644007] Oops: 0002 [#1] SMP
[ 126.644007] Modules linked in: acpihp_drv(+) ebtable_nat ebtables ipt_MASQUERADE iptable_nat nf_nat iptable_mangle bridge stp llc sunrpc cpufreq_ondemand acpi_cpufreq freq_table mperf ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables ipv6 vhost_net macvtap macvlan tun uinput iTCO_wdt iTCO_vendor_support coretemp kvm_intel kvm crc32c_intel microcode lpc_ich mfd_core pcspkr i2c_i801 i2c_core ioatdma e1000e acpi_memhotplug i7core_edac edac_core igb dca mptsas mptscsih mptbase scsi_transport_sas
[ 126.644007] CPU 10
[ 126.644007] Pid: 2821, comm: modprobe Tainted: G A 3.6.0-rc1+ #6 FUJITSU-SV PRIMEQUEST 1800E/SB
[ 126.644007] RIP: 0010:[<ffffffff814c0cd3>] [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP: 0018:ffff8810589a9de8 EFLAGS: 00010246
[ 126.644007] RAX: 0000000000000000 RBX: 0000000000000078 RCX: 0000000000000000
[ 126.644007] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000078
[ 126.644007] RBP: ffff8810589a9e08 R08: 0000000000000000 R09: ffff8810589a9d88
[ 126.644007] R10: 00000000000013e5 R11: 00000000000013e5 R12: ffffffffa01460d0
[ 126.644007] R13: 0000000000000000 R14: ffffffffa014732b R15: 00000000000000bf
[ 126.644007] FS: 00007fecb1802700(0000) GS:ffff88105e640000(0000) knlGS:0000000000000000
[ 126.644007] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 126.644007] CR2: 0000000000000078 CR3: 0000001052772000 CR4: 00000000000007e0
[ 126.644007] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 126.644007] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 126.644007] Process modprobe (pid: 2821, threadinfo ffff8810589a8000, task ffff8810592f8000)
[ 126.644007] ffff8810589a9e08 ffffffff810be37f ffffffffa0146220 ffffffff81a7b390
[ 126.644007] ffff8810589a9e58 ffffffff81317eb3 ffff8810589a9e48 0000000000000000
[ 126.644007] ffffffff81a342c0 ffffffff81a342e0 0000000000000000 ffffffffa0146220
[ 126.644007] [<ffffffff810be37f>] ? tracepoint_module_notify+0xd9/0x14a
[ 126.644007] [<ffffffff81317eb3>] class_interface_register+0x4a/0xbc
[ 126.644007] [<ffffffffa00b8000>] ? 0xffffffffa00b7fff
[ 126.644007] [<ffffffffa00b8010>] acpihp_drv_init+0x10/0x12 [acpihp_drv]
[ 126.644007] [<ffffffff8100207f>] do_one_initcall+0x7f/0x139
[ 126.644007] [<ffffffff81093414>] sys_init_module+0x12d3/0x14e3
[ 126.644007] [<ffffffff81264b0d>] ? ddebug_dyndbg_boot_param_cb+0x45/0x45
[ 126.644007] [<ffffffff814c9469>] system_call_fastpath+0x16/0x1b
[ 126.644007] Code: 48 8b 04 25 80 c6 00 00 48 89 43 18 31 c0 5b 5b c9 c3 55 48 89 e5 53 48 83 ec 18 66 66 66 66 90 48 89 fb e8 5a 0c 00 00 48 89 df <f0> ff 0f 79 05 e8 06 ff ff ff 65 48 8b 04 25 80 c6 00 00 48 89
[ 126.644007] RIP [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP <ffff8810589a9de8>
[ 126.644007] CR2: 0000000000000078
[ 129.981335] ---[ end trace da17e9c9de8dd560 ]---
[ 139.085895] nr_pdflush_threads exported in /proc is scheduled for removal
Looks like it dereferenced a NULL pointer here.
May be it was my mistake that I didn't configure the environment correctly.
Would you please give me some advice ?
Thanks. :)
--
Best Regards,
Tang chen
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-09 09:36:22 UTC
Permalink
Post by Tang Chen
Hi Liu ~
Post by Jiang Liu
Hi Tang,
Thanks for testing.
Currently there's a limitation that you need to insert acpihp_enum driver first.
Sorry, I didn't make it clear. I did load acpihp_enum module first, and then load acpihp_drv.
And I just tried it some more times. It just hung up, but dmesg had no output.
# modprobe acpihp_enum
(OK, and sysfs interfaces have been created)
# modprobe acpihp_drv
(hang up)
# dmesg
(nothing)
#0 0x00000032836aab80 in __nanosleep_nocancel () from /lib64/libc.so.6
#1 0x00000032836deb64 in usleep () from /lib64/libc.so.6
......
I have tried several times and I cannot reproduce the situation I just said.
You can reproduce it by loading acpihp_drv without acpihp_enum driver, I guess.
The acpihp_drv module_init() should call acpihp_register_class() to initialize the core.
Post by Tang Chen
Maybe my box has something different with yours. And I'll try to find out why.
Thanks for your advice. :)
Post by Jiang Liu
Will fix this issue in next version.
Regards!
Gerry
Post by Tang Chen
Hi Liu~
I compiled this driver as a module, acpihp_drv. And when I loaded this module, it
# modprobe acpihp_drv
(the command hangs up, no return after 10 min)
#dmesg
......
[ 126.643350] BUG: unable to handle kernel NULL pointer dereference at 0000000000000078
[ 126.644007] IP: [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] PGD 105277a067 PUD 104f823067 PMD 0
[ 126.644007] Oops: 0002 [#1] SMP
[ 126.644007] Modules linked in: acpihp_drv(+) ebtable_nat ebtables ipt_MASQUERADE iptable_nat nf_nat iptable_mangle bridge stp llc sunrpc cpufreq_ondemand acpi_cpufreq freq_table mperf ip6t_REJECT nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables ipv6 vhost_net macvtap macvlan tun uinput iTCO_wdt iTCO_vendor_support coretemp kvm_intel kvm crc32c_intel microcode lpc_ich mfd_core pcspkr i2c_i801 i2c_core ioatdma e1000e acpi_memhotplug i7core_edac edac_core igb dca mptsas mptscsih mptbase scsi_transport_sas
[ 126.644007] CPU 10
[ 126.644007] Pid: 2821, comm: modprobe Tainted: G A 3.6.0-rc1+ #6 FUJITSU-SV PRIMEQUEST 1800E/SB
[ 126.644007] RIP: 0010:[<ffffffff814c0cd3>] [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP: 0018:ffff8810589a9de8 EFLAGS: 00010246
[ 126.644007] RAX: 0000000000000000 RBX: 0000000000000078 RCX: 0000000000000000
[ 126.644007] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000078
[ 126.644007] RBP: ffff8810589a9e08 R08: 0000000000000000 R09: ffff8810589a9d88
[ 126.644007] R10: 00000000000013e5 R11: 00000000000013e5 R12: ffffffffa01460d0
[ 126.644007] R13: 0000000000000000 R14: ffffffffa014732b R15: 00000000000000bf
[ 126.644007] FS: 00007fecb1802700(0000) GS:ffff88105e640000(0000) knlGS:0000000000000000
[ 126.644007] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 126.644007] CR2: 0000000000000078 CR3: 0000001052772000 CR4: 00000000000007e0
[ 126.644007] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 126.644007] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[ 126.644007] Process modprobe (pid: 2821, threadinfo ffff8810589a8000, task ffff8810592f8000)
[ 126.644007] ffff8810589a9e08 ffffffff810be37f ffffffffa0146220 ffffffff81a7b390
[ 126.644007] ffff8810589a9e58 ffffffff81317eb3 ffff8810589a9e48 0000000000000000
[ 126.644007] ffffffff81a342c0 ffffffff81a342e0 0000000000000000 ffffffffa0146220
[ 126.644007] [<ffffffff810be37f>] ? tracepoint_module_notify+0xd9/0x14a
[ 126.644007] [<ffffffff81317eb3>] class_interface_register+0x4a/0xbc
[ 126.644007] [<ffffffffa00b8000>] ? 0xffffffffa00b7fff
[ 126.644007] [<ffffffffa00b8010>] acpihp_drv_init+0x10/0x12 [acpihp_drv]
[ 126.644007] [<ffffffff8100207f>] do_one_initcall+0x7f/0x139
[ 126.644007] [<ffffffff81093414>] sys_init_module+0x12d3/0x14e3
[ 126.644007] [<ffffffff81264b0d>] ? ddebug_dyndbg_boot_param_cb+0x45/0x45
[ 126.644007] [<ffffffff814c9469>] system_call_fastpath+0x16/0x1b
[ 126.644007] Code: 48 8b 04 25 80 c6 00 00 48 89 43 18 31 c0 5b 5b c9 c3 55 48 89 e5 53 48 83 ec 18 66 66 66 66 90 48 89 fb e8 5a 0c 00 00 48 89 df <f0> ff 0f 79 05 e8 06 ff ff ff 65 48 8b 04 25 80 c6 00 00 48 89
[ 126.644007] RIP [<ffffffff814c0cd3>] mutex_lock+0x19/0x37
[ 126.644007] RSP <ffff8810589a9de8>
[ 126.644007] CR2: 0000000000000078
[ 129.981335] ---[ end trace da17e9c9de8dd560 ]---
[ 139.085895] nr_pdflush_threads exported in /proc is scheduled for removal
Looks like it dereferenced a NULL pointer here.
May be it was my mistake that I didn't configure the environment correctly.
Would you please give me some advice ?
Thanks. :)
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tang Chen
2012-08-10 04:39:52 UTC
Permalink
Post by Jiang Liu
Post by Tang Chen
And I just tried it some more times. It just hung up, but dmesg had no output.
# modprobe acpihp_enum
(OK, and sysfs interfaces have been created)
# modprobe acpihp_drv
(hang up)
# dmesg
(nothing)
#0 0x00000032836aab80 in __nanosleep_nocancel () from /lib64/libc.so.6
#1 0x00000032836deb64 in usleep () from /lib64/libc.so.6
......
I have tried several times and I cannot reproduce the situation I just said.
You can reproduce it by loading acpihp_drv without acpihp_enum driver, I guess.
The acpihp_drv module_init() should call acpihp_register_class() to initialize the core.
Hi~

True. Thanks for your comments. :)
Since I'm new in PCI related area, if you don't mind, would you please give me some more advice about the following problem ?
Thanks. :)

"modprobe acpihp_drv" command failed, but acpihp_drv was loaded successfully, and always in use.
It cannot be removed. Is it a problem ?

[***@DP tangchen]# lsmod | grep acpi
acpi_cpufreq 9542 0
freq_table 5030 2 cpufreq_ondemand,acpi_cpufreq
mperf 1391 1 acpi_cpufreq
acpi_memhotplug 4414 0

[***@DP tangchen]# modprobe acpihp_drv
Killed (NOTE: The NULL pointer problem happened here.)

[***@DP tangchen]# echo $?
137

[***@DP tangchen]# lsmod | grep acpi
acpihp_drv 24925 1 (NOTE: Here, the module is loaded.)
acpi_cpufreq 9542 0
freq_table 5030 2 cpufreq_ondemand,acpi_cpufreq
mperf 1391 1 acpi_cpufreq
acpi_memhotplug 4414 0

[***@DP tangchen]# rmmod acpihp_drv
ERROR: Module acpihp_drv is in use


The core.c file has been compiled into kernel because of my configuration "CONFIG_ACPI_HOTPLUG=y".
As my colleague said, in this case, there is no dependency between acpihp_enum and acpihp_drv.
So I think, do we need to compile core.c into acpihp_enum module, or simply check if acpihp_enum
has been loaded in acpihp_drv_init() ?
I am not sure if it is a good idea to move acpihp_slot_class definition and all related API to
acpihp_enum module.

Thanks again for your comments and patient. :)
--
Best Regards,
Tang chen
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:13:52 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch enhances existing acpi_bus_scan() to create ACPI devices for
hot-added system devices connecting to an ACPI hotplug slot.
It also introduces a new interface to walk all ACPI devices connecting to
an ACPI hotplug slot.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
---
drivers/acpi/hotplug/Makefile | 2 +-
drivers/acpi/hotplug/device.c | 121 +++++++++++++++++++++++++++++++++++++++++
drivers/acpi/internal.h | 2 +
drivers/acpi/scan.c | 11 +++-
include/acpi/acpi_bus.h | 2 +
include/acpi/acpi_hotplug.h | 16 ++++++
6 files changed, 152 insertions(+), 2 deletions(-)
create mode 100644 drivers/acpi/hotplug/device.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 23dfa93..25fac24 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -3,7 +3,7 @@
#

obj-$(CONFIG_ACPI_HOTPLUG) += acpihp.o
-acpihp-y = core.o
+acpihp-y = core.o device.o

obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
acpihp_enum-y = slot_enum.o
diff --git a/drivers/acpi/hotplug/device.c b/drivers/acpi/hotplug/device.c
new file mode 100644
index 0000000..1795939
--- /dev/null
+++ b/drivers/acpi/hotplug/device.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/types.h>
+#include <linux/device.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_hotplug.h>
+#include "../internal.h"
+
+/*
+ * When creating ACPI devices for hot-added system devices connecting to slot,
+ * we shouldn't cross the slot boundary. Otherwise it will cause inconsistence
+ * to other slots.
+ */
+static acpi_status acpihp_filter_device(acpi_handle hdl, u32 level, void *arg)
+{
+ /* Skip if the handle corresponds to a child hotplug slot. */
+ if (level > 0 && acpihp_check_slot(hdl) != 0)
+ return AE_CTRL_DEPTH;
+
+ return AE_OK;
+}
+
+/* Create ACPI devices for hot-added system devices connecting to a slot. */
+int acpihp_add_devices(acpi_handle handle, struct acpi_device **child)
+{
+ struct acpi_bus_ops ops;
+
+ memset(&ops, 0, sizeof(ops));
+ ops.acpi_op_add = 1;
+ ops.acpi_op_filter_fn = &acpihp_filter_device;
+ ops.acpi_op_filter_arg = NULL;
+
+ return acpi_bus_scan(handle, &ops, child);
+}
+EXPORT_SYMBOL_GPL(acpihp_add_devices);
+
+struct acpihp_walk_arg {
+ acpihp_walk_device_cb cb;
+ void *arg;
+ acpi_status status;
+};
+
+static int acpihp_walk_cb(struct device *dev, void *data)
+{
+ acpi_status status;
+ struct acpihp_walk_arg *argp = (struct acpihp_walk_arg *)data;
+ struct acpi_device *acpi_dev = container_of(dev,
+ struct acpi_device, dev);
+
+ /* Skip if the handle corresponds to a child hotplug slot. */
+ if (acpihp_check_slot(acpi_dev->handle))
+ return 0;
+
+ status = argp->cb(acpi_dev, argp->arg);
+ if (status == AE_OK) {
+ return device_for_each_child(dev, data, &acpihp_walk_cb);
+ } else if (status == AE_CTRL_DEPTH || status == AE_CTRL_TERMINATE ||
+ status == AE_CTRL_SKIP) {
+ return 0;
+ } else {
+ argp->status = status;
+ return -1;
+ }
+}
+
+/*
+ * Walk all ACPI devices connecting to a hotplug slot, and don't cross the
+ * hotplug slot boundary.
+ */
+int acpihp_walk_devices(acpi_handle handle, acpihp_walk_device_cb cb,
+ void *argp)
+{
+ acpi_status status;
+ struct acpi_device *device;
+ struct acpihp_walk_arg arg;
+
+ if (acpi_bus_get_device(handle, &device))
+ return -ENODEV;
+
+ status = (*cb)(device, argp);
+ if (ACPI_SUCCESS(status)) {
+ arg.cb = cb;
+ arg.arg = argp;
+ arg.status = AE_OK;
+ (void) device_for_each_child(&device->dev, &arg,
+ &acpihp_walk_cb);
+ status = arg.status;
+ }
+
+ if (status == AE_CTRL_DEPTH || status == AE_CTRL_TERMINATE ||
+ status == AE_CTRL_SKIP)
+ status = AE_OK;
+ else if (ACPI_FAILURE(status))
+ ACPIHP_DEBUG("fails to walk devices under %p.\n", handle);
+
+ acpi_device_put(device);
+
+ return status == AE_OK ? 0 : -ENODEV;
+}
+EXPORT_SYMBOL_GPL(acpihp_walk_devices);
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index 70843c0..6f0632d 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -95,5 +95,7 @@ static inline void suspend_nvs_restore(void) {}

extern struct rw_semaphore acpi_device_data_handler_sem;
void acpi_bus_data_handler(acpi_handle handle, void *context);
+int acpi_bus_scan(acpi_handle handle, struct acpi_bus_ops *ops,
+ struct acpi_device **child);

#endif /* _ACPI_INTERNAL_H_ */
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 28408af..4586373 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1443,6 +1443,14 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl,
return AE_CTRL_DEPTH;
}

+ /* Hooks for ACPI based system device hotplug */
+ if (ops->acpi_op_filter_fn != NULL) {
+ result = ops->acpi_op_filter_fn(handle, lvl,
+ ops->acpi_op_filter_arg);
+ if (result != AE_OK)
+ return result;
+ }
+
/*
* We may already have an acpi_device from a previous enumeration. If
* so, we needn't add it again, but we may still have to start it.
@@ -1466,7 +1474,7 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl,
return AE_OK;
}

-static int acpi_bus_scan(acpi_handle handle, struct acpi_bus_ops *ops,
+int acpi_bus_scan(acpi_handle handle, struct acpi_bus_ops *ops,
struct acpi_device **child)
{
acpi_status status;
@@ -1624,6 +1632,7 @@ int __init acpi_scan_init(void)
memset(&ops, 0, sizeof(ops));
ops.acpi_op_add = 1;
ops.acpi_op_start = 1;
+ ops.acpi_op_filter_fn = NULL;

result = bus_register(&acpi_bus_type);
if (result) {
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index e2a3eb0..81b4c3f 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -127,6 +127,8 @@ typedef void (*acpi_op_notify) (struct acpi_device * device, u32 event);
struct acpi_bus_ops {
u32 acpi_op_add:1;
u32 acpi_op_start:1;
+ acpi_status (*acpi_op_filter_fn)(acpi_handle, u32, void *);
+ void *acpi_op_filter_arg;
};

struct acpi_device_ops {
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index 09c935e..b81d934 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -174,6 +174,22 @@ extern acpi_status acpihp_slot_get_capabilities(struct acpihp_slot *slot,
extern acpi_status acpihp_slot_poweron(struct acpihp_slot *slot);
extern acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot);

+/*
+ * Add devices for ACPI objects connecting to an ACPI hotplug slot,
+ * and don't cross the hotplug slot boundary.
+ */
+extern int acpihp_add_devices(acpi_handle handle, struct acpi_device **child);
+
+typedef acpi_status (*acpihp_walk_device_cb)(struct acpi_device *acpi_device,
+ void *argp);
+
+/*
+ * Walk all ACPI devices connecting to an ACPI hotplug slot,
+ * and don't cross the hotplug slot boundary.
+ */
+extern int acpihp_walk_devices(acpi_handle handle, acpihp_walk_device_cb cb,
+ void *argp);
+
extern int acpihp_debug;

#define ACPIHP_DEBUG(fmt, ...) \
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:59 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements functions to cancel inprogress ACPI system device
hotplug operations.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
---
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/acpihp_drv.h | 18 +++-
drivers/acpi/hotplug/cancel.c | 171 +++++++++++++++++++++++++++++++++++++
3 files changed, 189 insertions(+), 1 deletion(-)
create mode 100644 drivers/acpi/hotplug/cancel.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 8477c71..7e10f69 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -12,3 +12,4 @@ acpihp_enum-y += slot_enum_ej0.o
obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
acpihp_drv-y = drv_main.o
acpihp_drv-y += dependency.o
+acpihp_drv-y += cancel.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index c4ff91c..5d69272 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -44,12 +44,21 @@ enum acpihp_dev_event {
ACPIHP_DRV_EVENT_FROM_OS
};

+enum acpihp_drv_cancel_state {
+ ACPIHP_DRV_CANCEL_INIT = 0,
+ ACPIHP_DRV_CANCEL_STARTED,
+ ACPIHP_DRV_CANCEL_OK,
+ ACPIHP_DRV_CANCEL_FAILED,
+ ACPIHP_DRV_CANCEL_MISSED,
+ ACPIHP_DRV_CANCEL_FINISHED
+};
+
struct acpihp_slot_drv {
enum acpihp_dev_event event_flag;
struct mutex op_mutex; /* Prevent concurrent hotplugs. */
struct list_head depend_list; /* Dependency relationship */
atomic_t cancel_status;
- atomic_t cancel_flag;
+ atomic_t cancel_users;
struct acpihp_cancel_context cancel_ctx;
};

@@ -73,4 +82,11 @@ int acpihp_drv_filter_dependency_list(struct list_head *old_head,
int acpihp_drv_generate_dependency_list(struct acpihp_slot *slot,
struct list_head *slot_list, enum acpihp_drv_cmd cmd);

+void acpihp_drv_cancel_init(struct list_head *list);
+void acpihp_drv_cancel_notify(struct acpihp_slot *slot,
+ enum acpihp_drv_cancel_state state);
+void acpihp_drv_cancel_fini(struct list_head *list);
+int acpihp_drv_cancel_start(struct list_head *list);
+int acpihp_drv_cancel_wait(struct list_head *list);
+
#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/cancel.c b/drivers/acpi/hotplug/cancel.c
new file mode 100644
index 0000000..6c4d95c
--- /dev/null
+++ b/drivers/acpi/hotplug/cancel.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/wait.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+/*
+ * Implements interfaces to cancel inprogress hotplug operations.
+ * Currently only CONFIGURE and RELEASE operation stages support cancellation.
+ * Caller must serialize calls to functions by holding state_machine_mutex:
+ * acpihp_drv_cancel_init()
+ * acpihp_drv_cancel_fini()
+ * acpihp_drv_cancel_start()
+ */
+static DECLARE_WAIT_QUEUE_HEAD(acpihp_drv_cancel_queue);
+
+static int acpihp_drv_check_cancel(struct acpihp_cancel_context *ctx)
+{
+ struct acpihp_slot_drv *drv_data;
+
+ BUG_ON(ctx == NULL);
+ drv_data = container_of(ctx, struct acpihp_slot_drv, cancel_ctx);
+
+ return atomic_read(&drv_data->cancel_status) != ACPIHP_DRV_CANCEL_INIT;
+}
+
+void acpihp_drv_cancel_init(struct list_head *list)
+{
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot_dependency *dep;
+
+ /* Wait for all cancellation threads to exit */
+ list_for_each_entry(dep, list, node) {
+ acpihp_drv_get_data(dep->slot, &drv_data);
+ drv_data->cancel_ctx.check_cancel = acpihp_drv_check_cancel;
+ atomic_set(&drv_data->cancel_status, ACPIHP_DRV_CANCEL_INIT);
+ atomic_set(&drv_data->cancel_users, 0);
+ }
+}
+
+void acpihp_drv_cancel_notify(struct acpihp_slot *slot,
+ enum acpihp_drv_cancel_state state)
+{
+ struct acpihp_slot_drv *drv_data;
+
+ acpihp_drv_get_data(slot, &drv_data);
+ if (atomic_cmpxchg(&drv_data->cancel_status, ACPIHP_DRV_CANCEL_FINISHED,
+ ACPIHP_DRV_CANCEL_INIT) != ACPIHP_DRV_CANCEL_INIT) {
+ atomic_set(&drv_data->cancel_status, state);
+ wake_up_all(&acpihp_drv_cancel_queue);
+ }
+}
+
+/*
+ * Wait for all cancellation threads to give up their reference count.
+ *
+ * Caller must provide mechanism to avoid currently running
+ * acpihp_drv_cancel_start() and acpihp_drv_cancel_fini()
+ * on the same list.
+ */
+void acpihp_drv_cancel_fini(struct list_head *list)
+{
+ int state;
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ acpihp_drv_get_data(dep->slot, &drv_data);
+
+ /*
+ * Wake up all cancellation threads if they are still
+ * STARTED state.
+ */
+ state = atomic_cmpxchg(&drv_data->cancel_status,
+ ACPIHP_DRV_CANCEL_MISSED,
+ ACPIHP_DRV_CANCEL_STARTED);
+ if (state == ACPIHP_DRV_CANCEL_STARTED)
+ wake_up_all(&acpihp_drv_cancel_queue);
+
+ /* Wait for all cancellation threads to exit */
+ wait_event(acpihp_drv_cancel_queue,
+ !atomic_read(&drv_data->cancel_users));
+ }
+}
+
+/*
+ * Start cancellation on a list of hotplug slots.
+ *
+ * Caller must provide mechanism to avoid currently running
+ * acpihp_drv_cancel_start() and acpihp_drv_cancel_fini()
+ * on the same list.
+ */
+int acpihp_drv_cancel_start(struct list_head *list)
+{
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot_dependency *dep;
+
+ if (list_empty(list)) {
+ ACPIHP_DEBUG("dependency list is empty.\n");
+ return -ENODEV;
+ }
+
+ /* Start cancellation on all slots. */
+ list_for_each_entry(dep, list, node) {
+ acpihp_drv_get_data(dep->slot, &drv_data);
+ atomic_inc(&drv_data->cancel_users);
+ atomic_cmpxchg(&drv_data->cancel_status,
+ ACPIHP_DRV_CANCEL_STARTED,
+ ACPIHP_DRV_CANCEL_INIT);
+ }
+
+ return 0;
+}
+
+/*
+ * Wait for all slots on the list to reach a stable state and then check
+ * cancellation result.
+ */
+int acpihp_drv_cancel_wait(struct list_head *list)
+{
+ int status, result = 0;
+ struct acpihp_slot_drv *drv_data;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ acpihp_drv_get_data(dep->slot, &drv_data);
+ wait_event(acpihp_drv_cancel_queue,
+ atomic_read(&drv_data->cancel_status)
+ != ACPIHP_DRV_CANCEL_STARTED);
+
+ status = atomic_read(&drv_data->cancel_status);
+ if (status == ACPIHP_DRV_CANCEL_FAILED) {
+ ACPIHP_DEBUG("fails to cancel operatin for %s.\n",
+ dep->slot->name);
+ result = result ? : -EBUSY;
+ } else if (status == ACPIHP_DRV_CANCEL_MISSED) {
+ ACPIHP_DEBUG("misses to cancel operatin for %s.\n",
+ dep->slot->name);
+ result = result ? : -EBUSY;
+ }
+
+ atomic_set(&drv_data->cancel_status,
+ ACPIHP_DRV_CANCEL_FINISHED);
+ atomic_dec(&drv_data->cancel_users);
+ wake_up_all(&acpihp_drv_cancel_queue);
+ }
+
+ return result;
+}
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:14:01 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements the core of the new ACPI hotplug framework, it drivers
the state machine for ACPI hotplug slots according to user commands. It also
resolve dependencies among slots when transit the state machine.

Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/acpihp_drv.h | 9 +
drivers/acpi/hotplug/state_machine.c | 633 ++++++++++++++++++++++++++++++++++
3 files changed, 643 insertions(+)
create mode 100644 drivers/acpi/hotplug/state_machine.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 55d559a..9383320 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -14,3 +14,4 @@ acpihp_drv-y = drv_main.o
acpihp_drv-y += dependency.o
acpihp_drv-y += cancel.o
acpihp_drv-y += configure.o
+acpihp_drv-y += state_machine.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index eca2036..3b6dbdd 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -25,6 +25,9 @@
#ifndef __ACPIHP_DRV_H__
#define __ACPIHP_DRV_H__

+/* Timeout value to wait for firmware to power on the slot */
+#define ACPIHP_EVENT_WAIT_TIME (100 * HZ)
+
/* Commands to change state of a hotplug slot */
enum acpihp_drv_cmd {
ACPIHP_DRV_CMD_NOOP = 0,
@@ -68,6 +71,9 @@ struct acpihp_slot_dependency {
u32 opcodes;
};

+extern struct mutex state_machine_mutex;
+extern wait_queue_head_t acpihp_drv_event_wq;
+
void acpihp_drv_get_data(struct acpihp_slot *slot,
struct acpihp_slot_drv **data);
int acpihp_drv_enumerate_devices(struct acpihp_slot *slot);
@@ -92,4 +98,7 @@ int acpihp_drv_cancel_wait(struct list_head *list);
int acpihp_drv_configure(struct list_head *list);
int acpihp_drv_unconfigure(struct list_head *list);

+/* The heart of the ACPI system device hotplug driver */
+int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd);
+
#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/state_machine.c b/drivers/acpi/hotplug/state_machine.c
new file mode 100644
index 0000000..9f61804
--- /dev/null
+++ b/drivers/acpi/hotplug/state_machine.c
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+/*
+ * Global lock to serialize manipulating of dependency list among hotplug slots
+ * to avoid deadlock among slots. The lock order is:
+ * 1) acquire state_machine_mutex;
+ * 2) acquire drv_data->op_mutex;
+ * 3) acquire slot->slot_mutex;
+ */
+DEFINE_MUTEX(state_machine_mutex);
+
+DECLARE_WAIT_QUEUE_HEAD(acpihp_drv_event_wq);
+
+static int acpihp_drv_lock_slot(struct acpihp_slot *slot)
+{
+ int retval = 0;
+ struct acpihp_slot_drv *drv_data;
+
+ acpihp_drv_get_data(slot, &drv_data);
+ if (!mutex_trylock(&drv_data->op_mutex)) {
+ ACPIHP_DEBUG("slot %s is busy in state %d.\n",
+ slot->name, slot->state);
+ retval = -EBUSY;
+ }
+
+ return retval;
+}
+
+static void acpihp_drv_unlock_slot(struct acpihp_slot *slot)
+{
+ struct acpihp_slot_drv *drv_data;
+
+ acpihp_drv_get_data(slot, &drv_data);
+ BUG_ON(!mutex_is_locked(&drv_data->op_mutex));
+ mutex_unlock(&drv_data->op_mutex);
+}
+
+/*
+ * Lock all slots in the dependency list to serialize concurrent operations.
+ * Caller must hold state_machine_mutex.
+ */
+static int acpihp_drv_lock_slots(struct list_head *list,
+ struct acpihp_slot *slot)
+{
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node)
+ if (acpihp_drv_lock_slot(dep->slot))
+ goto unlock;
+
+ return 0;
+
+unlock:
+ list_for_each_entry_continue_reverse(dep, list, node)
+ acpihp_drv_unlock_slot(dep->slot);
+
+ return -EBUSY;
+}
+
+static void acpihp_drv_unlock_slots(struct list_head *list)
+{
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node)
+ acpihp_drv_unlock_slot(dep->slot);
+}
+
+static bool acpihp_drv_is_ancestor(struct acpihp_slot *ancestor,
+ struct acpihp_slot *slot)
+{
+ while (slot) {
+ if (slot->parent == ancestor)
+ return true;
+ slot = slot->parent;
+ };
+
+ return false;
+}
+
+/*
+ * Check whether the command is valid according to current slot state,
+ * and get required operations for this slot if command is valid.
+ */
+static int acpihp_drv_validate_transition(struct acpihp_slot_dependency *dep,
+ struct acpihp_slot *target,
+ enum acpihp_drv_cmd cmd)
+{
+ u32 op1, op2;
+ struct acpihp_slot *slot = dep->slot;
+
+ if (slot->state <= ACPIHP_SLOT_STATE_UNKNOWN ||
+ slot->state >= ACPIHP_SLOT_STATE_MAX) {
+ ACPIHP_DEBUG("invalid slot state %d.\n", slot->state);
+ return -EINVAL;
+ } else if (slot->state >= ACPIHP_SLOT_STATE_POWERING_ON) {
+ /*
+ * This shouldn't happen, transcendant states are protected
+ * by slot->op_mutex.
+ */
+ BUG_ON(slot->state);
+ return -EBUSY;
+ }
+
+ op1 = op2 = ACPIHP_DRV_CMD_NOOP;
+ dep->opcodes = ACPIHP_DRV_CMD_NOOP;
+
+ /*
+ * To be compatible with legacy OSes, the PCI host bridges built into
+ * physical processor may be hosted directly under \\__SB instead of
+ * under the CONTAINER device corresponding to physical processor.
+ * That's really a corner case to deal with.
+ */
+ switch (cmd) {
+ case ACPIHP_DRV_CMD_POWERON:
+ if (slot->state == ACPIHP_SLOT_STATE_ABSENT)
+ return -EINVAL;
+ else if (slot->state == ACPIHP_SLOT_STATE_PRESENT)
+ dep->opcodes = ACPIHP_DRV_CMD_POWERON;
+ break;
+
+ case ACPIHP_DRV_CMD_CONNECT:
+ /*
+ * Its parent must have already been connected when connecting
+ * a slot, otherwise the device tree topology becomes incorrect.
+ */
+ if (target == slot || acpihp_drv_is_ancestor(slot, target))
+ op2 = ACPIHP_DRV_CMD_CONNECT;
+
+ if (slot->state == ACPIHP_SLOT_STATE_ABSENT)
+ return -EINVAL;
+ else if (slot->state == ACPIHP_SLOT_STATE_PRESENT)
+ dep->opcodes = ACPIHP_DRV_CMD_POWERON | op2;
+ else if (slot->state == ACPIHP_SLOT_STATE_POWERED)
+ dep->opcodes = op2;
+ break;
+
+ case ACPIHP_DRV_CMD_CONFIGURE:
+ /* Only CONFIGURE the requested slot */
+ if (slot == target)
+ op1 = ACPIHP_DRV_CMD_CONFIGURE;
+ /*
+ * Its parent must have already been connected when configuring
+ * a slot, otherwise the device tree topology becomes incorrect.
+ */
+ if (target == slot || acpihp_drv_is_ancestor(slot, target))
+ op2 = ACPIHP_DRV_CMD_CONNECT;
+
+ if (slot->state == ACPIHP_SLOT_STATE_ABSENT)
+ return -EINVAL;
+ else if (slot->state == ACPIHP_SLOT_STATE_PRESENT)
+ dep->opcodes = ACPIHP_DRV_CMD_POWERON | op1 | op2;
+ else if (slot->state == ACPIHP_SLOT_STATE_POWERED)
+ dep->opcodes = op1 | op2;
+ else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED)
+ dep->opcodes = op1;
+ break;
+
+ case ACPIHP_DRV_CMD_UNCONFIGURE:
+ /* Only UNCONFIGURE the requested slot */
+ if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED &&
+ slot == target)
+ dep->opcodes = ACPIHP_DRV_CMD_UNCONFIGURE;
+ break;
+
+ case ACPIHP_DRV_CMD_DISCONNECT:
+ /*
+ * all descedant slots must be unconfigured/disconnected
+ * when disconnecting a slot.
+ */
+ if (target == slot || acpihp_drv_is_ancestor(target, slot)) {
+ op1 = ACPIHP_DRV_CMD_UNCONFIGURE;
+ op2 = ACPIHP_DRV_CMD_DISCONNECT;
+ }
+
+ if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED)
+ dep->opcodes = op1 | op2;
+ else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED)
+ dep->opcodes = op2;
+ break;
+
+ case ACPIHP_DRV_CMD_POWEROFF:
+ /*
+ * All slots have dependency on the target slot must be
+ * powered off when powering the target slot off.
+ */
+ if (slot->state == ACPIHP_SLOT_STATE_CONFIGURED)
+ dep->opcodes = ACPIHP_DRV_CMD_UNCONFIGURE |
+ ACPIHP_DRV_CMD_DISCONNECT |
+ ACPIHP_DRV_CMD_POWEROFF;
+ else if (slot->state == ACPIHP_SLOT_STATE_CONNECTED)
+ dep->opcodes = ACPIHP_DRV_CMD_DISCONNECT |
+ ACPIHP_DRV_CMD_POWEROFF;
+ else if (slot->state == ACPIHP_SLOT_STATE_POWERED)
+ dep->opcodes = ACPIHP_DRV_CMD_POWEROFF;
+ break;
+
+ default:
+ ACPIHP_DEBUG("invalid command %d.\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int acpihp_drv_validate_command(struct list_head *list,
+ struct acpihp_slot *target, enum acpihp_drv_cmd cmd)
+{
+ int retval;
+ struct acpihp_slot *slot;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ slot = dep->slot;
+ acpihp_drv_update_slot_status(slot);
+
+ retval = acpihp_drv_validate_transition(dep, target, cmd);
+ if (retval) {
+ ACPIHP_DEBUG("Invalid cmd for slot %s in state %d.\n",
+ slot->name, slot->state);
+ return retval;
+ }
+
+ /*
+ * Check whether the slot is in good shape if we need to
+ * operate on it.
+ */
+ if (dep->opcodes &&
+ acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_FAULT)) {
+ ACPIHP_WARN("slot %s has been marked as faulty.\n",
+ slot->name);
+ return -ENXIO;
+ } else if ((dep->opcodes & ACPIHP_DRV_CMD_UNCONFIGURE) &&
+ acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE)) {
+ ACPIHP_WARN("slot %s is busy.\n", slot->name);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+static int acpihp_drv_pre_execute(struct acpihp_slot *slot,
+ enum acpihp_drv_cmd cmd,
+ struct list_head **head)
+{
+ int retval;
+ struct list_head *list;
+ struct acpihp_slot_drv *drv_data;
+
+ mutex_lock(&state_machine_mutex);
+
+ acpihp_drv_get_data(slot, &drv_data);
+ *head = list = &drv_data->depend_list;
+
+ if (cmd == ACPIHP_DRV_CMD_CANCEL) {
+ /*
+ * Set cancellation flags on all affected slots.
+ * All affected slots should already be on drv_data->depend_list
+ * if there's inprogress operation for the slot.
+ *
+ * state_machine_mutex must be held to serialize calls to
+ * acpihp_drv_cancel_init(),
+ * acpihp_drv_cancel_start(),
+ * acpihp_drv_cancel_fini(),
+ */
+ retval = acpihp_drv_cancel_start(&drv_data->depend_list);
+ goto out;
+ }
+
+ if (mutex_is_locked(&drv_data->op_mutex)) {
+ ACPIHP_DEBUG("slot %s is busy.\n", slot->name);
+ retval = -EBUSY;
+ goto out;
+ }
+
+ INIT_LIST_HEAD(list);
+ retval = acpihp_drv_generate_dependency_list(slot, list, cmd);
+ if (retval) {
+ ACPIHP_DEBUG("fails to get dependency list for slot %s.\n",
+ slot->name);
+ goto out;
+ }
+
+ retval = acpihp_drv_lock_slots(list, slot);
+ if (retval) {
+ acpihp_drv_destroy_dependency_list(list);
+ goto out;
+ }
+
+ retval = acpihp_drv_validate_command(list, slot, cmd);
+ if (retval) {
+ acpihp_drv_unlock_slots(list);
+ acpihp_drv_destroy_dependency_list(list);
+ goto out;
+ }
+
+ acpihp_drv_cancel_init(list);
+
+out:
+ mutex_unlock(&state_machine_mutex);
+
+ return retval;
+}
+
+static void acpihp_drv_post_execute(struct list_head *list,
+ enum acpihp_drv_cmd cmd)
+{
+ if (cmd == ACPIHP_DRV_CMD_CANCEL)
+ return;
+
+ mutex_lock(&state_machine_mutex);
+ if (list && !list_empty(list)) {
+ acpihp_drv_cancel_fini(list);
+ acpihp_drv_unlock_slots(list);
+ acpihp_drv_destroy_dependency_list(list);
+ }
+ mutex_unlock(&state_machine_mutex);
+}
+
+static int acpihp_drv_poweron_slot(struct acpihp_slot *slot)
+{
+ acpi_status status;
+ struct acpihp_slot_drv *drv_data;
+
+ if (acpihp_slot_powered(slot))
+ return 0;
+
+ acpihp_drv_get_data(slot, &drv_data);
+ drv_data->event_flag = ACPIHP_DRV_EVENT_FROM_OS;
+
+ status = acpihp_slot_poweron(slot);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to power on slot %s.\n", slot->name);
+ if (status == AE_SUPPORT)
+ return -ENOSYS;
+ else
+ return -ENXIO;
+ }
+
+ wait_event_timeout(acpihp_drv_event_wq, acpihp_slot_powered(slot),
+ ACPIHP_EVENT_WAIT_TIME);
+ if (!acpihp_slot_powered(slot)) {
+ ACPIHP_WARN("fails to power on slot %s.\n", slot->name);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int acpihp_drv_poweron(struct list_head *list)
+{
+ int retval = 0;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ if (!(dep->opcodes & ACPIHP_DRV_CMD_POWERON))
+ continue;
+
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_POWERING_ON);
+ retval = acpihp_drv_poweron_slot(dep->slot);
+ if (!retval) {
+ ACPIHP_DEBUG("succeed to power on slot %s!\n",
+ dep->slot->name);
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_POWERED);
+ } else {
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_PRESENT);
+ if (retval != -ENOSYS)
+ acpihp_slot_set_flag(dep->slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_poweroff_slot(struct acpihp_slot *slot)
+{
+ acpi_status status;
+
+ if (acpihp_slot_powered(slot))
+ return 0;
+
+ status = acpihp_slot_poweroff(slot);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to power off slot %s.\n", slot->name);
+ if (status == AE_SUPPORT)
+ return -ENOSYS;
+ else
+ return -ENXIO;
+ }
+
+ if (acpihp_slot_powered(slot)) {
+ ACPIHP_WARN("fails to power off slot %s.\n", slot->name);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int acpihp_drv_poweroff(struct list_head *list)
+{
+ int retval = 0;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ if (!(dep->opcodes & ACPIHP_DRV_CMD_POWEROFF))
+ continue;
+
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_POWERING_OFF);
+ retval = acpihp_drv_poweroff_slot(dep->slot);
+ if (!retval) {
+ ACPIHP_DEBUG("succeed to power off slot %s!\n",
+ dep->slot->name);
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_PRESENT);
+ } else {
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_POWERED);
+ if (retval != -ENOSYS)
+ acpihp_slot_set_flag(dep->slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_connect_slot(struct acpihp_slot *slot)
+{
+ int retval;
+
+ retval = acpihp_add_devices(slot->handle, NULL);
+ if (retval)
+ ACPIHP_DEBUG("fails to add ACPI devices for slot %s.\n",
+ slot->name);
+ else {
+ retval = acpihp_drv_enumerate_devices(slot);
+ if (retval)
+ ACPIHP_DEBUG("fails to enumerate device for slot %s.\n",
+ slot->name);
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_connect(struct list_head *list)
+{
+ int retval = 0;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ if (!(dep->opcodes & ACPIHP_DRV_CMD_CONNECT))
+ continue;
+
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_CONNECTING);
+ retval = acpihp_drv_connect_slot(dep->slot);
+ if (!retval) {
+ ACPIHP_DEBUG("succeed to connect slot %s!\n",
+ dep->slot->name);
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_CONNECTED);
+ } else {
+ acpihp_drv_update_slot_state(dep->slot);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_disconnect_slot(struct acpihp_slot *slot)
+{
+ int retval = 0;
+ enum acpihp_dev_type i;
+ struct acpi_device *device = NULL;
+
+ /* remove all devices connecting to slot */
+ for (i = ACPIHP_DEV_TYPE_UNKNOWN; i < ACPIHP_DEV_TYPE_MAX; i++) {
+ retval = acpihp_remove_device_list(&slot->dev_lists[i]);
+ if (retval) {
+ ACPIHP_DEBUG("fails to remove devices under slot %s.\n",
+ slot->name);
+ return retval;
+ }
+ }
+
+ /* remove ACPI devices */
+ retval = acpi_bus_get_device(slot->handle, &device);
+ if (!retval && device) {
+ retval = acpi_bus_trim(device, 1);
+ if (retval)
+ ACPIHP_WARN("fails to remove ACPI devices for %s.\n",
+ slot->name);
+ } else {
+ ACPIHP_WARN("fails to get ACPI device for slot %s.\n",
+ slot->name);
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_disconnect(struct list_head *list)
+{
+ int retval = 0;
+ struct acpihp_slot_dependency *dep;
+
+ list_for_each_entry(dep, list, node) {
+ if (!(dep->opcodes & ACPIHP_DRV_CMD_DISCONNECT))
+ continue;
+
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_DISCONNECTING);
+ retval = acpihp_drv_disconnect_slot(dep->slot);
+ if (!retval) {
+ ACPIHP_DEBUG("succeed to disconnect slot %s!\n",
+ dep->slot->name);
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_POWERED);
+ } else {
+ acpihp_drv_update_slot_state(dep->slot);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_execute(struct list_head *list, enum acpihp_drv_cmd cmd)
+{
+ int retval = 0;
+ bool connect = false, configure = false;
+ bool disconnect = false, poweroff = false;
+
+ if (!list || list_empty(list)) {
+ ACPIHP_DEBUG("slot dependency list is NULL or empty!\n");
+ retval = -EINVAL;
+ goto out;
+ }
+
+ switch (cmd) {
+ case ACPIHP_DRV_CMD_CONFIGURE:
+ configure = true;
+ /* fall through */
+ case ACPIHP_DRV_CMD_CONNECT:
+ connect = true;
+ /* fall through */
+ case ACPIHP_DRV_CMD_POWERON:
+ retval = acpihp_drv_poweron(list);
+ if (!retval && connect)
+ retval = acpihp_drv_connect(list);
+ if (!retval && configure)
+ retval = acpihp_drv_configure(list);
+ break;
+
+ case ACPIHP_DRV_CMD_POWEROFF:
+ poweroff = true;
+ /* fall through */
+ case ACPIHP_DRV_CMD_DISCONNECT:
+ disconnect = true;
+ /* fall through */
+ case ACPIHP_DRV_CMD_UNCONFIGURE:
+ retval = acpihp_drv_unconfigure(list);
+ if (!retval && disconnect)
+ retval = acpihp_drv_disconnect(list);
+ if (!retval && poweroff)
+ retval = acpihp_drv_poweroff(list);
+ break;
+
+ case ACPIHP_DRV_CMD_CANCEL:
+ retval = acpihp_drv_cancel_wait(list);
+ break;
+
+ default:
+ ACPIHP_DEBUG("unsupported command %d.\n", cmd);
+ retval = -EINVAL;
+ break;
+ }
+
+out:
+ return retval;
+}
+
+/*
+ * The heart of ACPI based system device hotplug driver, which drivers the
+ * slot state machine according to commands.
+ */
+int acpihp_drv_change_state(struct acpihp_slot *slot, enum acpihp_drv_cmd cmd)
+{
+ int retval;
+ struct list_head *list = NULL;
+
+ retval = acpihp_drv_pre_execute(slot, cmd, &list);
+ if (!retval) {
+ retval = acpihp_drv_execute(list, cmd);
+ acpihp_drv_post_execute(list, cmd);
+ }
+
+ return retval;
+}
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:54 UTC
Permalink
From: Jiang Liu <***@huawei.com>

Add new callbacks into struct acpi_device_ops to provide better error handling,
error recover and operation cancellation for ACPI based system device hotplug.

There are three major operations and each major operation is divided into
three steps.
1) pre_configure, configure, post_configure
add an ACPI device into running system and rollback if error happens
cancelled by user.
2) pre_release, release, post_release
reclaim an ACPI device from running system and rollback if error
happens or cancelled by user. It's important to privode a mechanism
to cancel memory hot-removal operations because it's may take very
long or even endless time to reclaim a memory device.
3) pre_unconfigure, unconfigure, post_unconfigure
remove an ACPI device from running system and release all resources
associated with it.

There's another callback to query status and information about the ACPI device.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
---
drivers/acpi/hotplug/device.c | 72 +++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_bus.h | 3 ++
include/acpi/acpi_hotplug.h | 64 ++++++++++++++++++++++++++++++++++++
3 files changed, 139 insertions(+)

diff --git a/drivers/acpi/hotplug/device.c b/drivers/acpi/hotplug/device.c
index 1795939..19a9683 100644
--- a/drivers/acpi/hotplug/device.c
+++ b/drivers/acpi/hotplug/device.c
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2011 Huawei Tech. Co., Ltd.
* Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
@@ -27,6 +28,77 @@
#include <acpi/acpi_hotplug.h>
#include "../internal.h"

+int acpihp_dev_get_info(struct acpi_device *device,
+ struct acpihp_dev_info *info)
+{
+ int ret = -ENOSYS;
+
+ acpihp_dev_get_type(device->handle, &info->type);
+
+ mutex_lock(&device->dev.mutex);
+ if (device->driver && device->driver->ops.hp_ops &&
+ device->driver->ops.hp_ops->get_info)
+ ret = device->driver->ops.hp_ops->get_info(device, info);
+ else
+#if 0
+ /* Turn on this once all system devices have been converted
+ * to the new hotplug framework
+ */
+ info->status |= ACPIHP_DEV_STATUS_IRREMOVABLE;
+#else
+ ret = 0;
+#endif
+
+ if (device->driver)
+ info->status |= ACPIHP_DEV_STATUS_ATTACHED;
+ mutex_unlock(&device->dev.mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_dev_get_info);
+
+#define ACPIHP_DEFINE_FUNC(method, def, err) \
+int acpihp_dev_##method(struct acpi_device *device) \
+{ \
+ int ret; \
+ BUG_ON(device == NULL); \
+ if (!device->driver || !device->driver->ops.hp_ops) \
+ ret = (err); \
+ else if (!device->driver->ops.hp_ops->method) \
+ ret = (def); \
+ else \
+ ret = device->driver->ops.hp_ops->method(device);\
+ return ret; \
+} \
+EXPORT_SYMBOL_GPL(acpihp_dev_##method)
+
+#define ACPIHP_DEFINE_FUNC1(method, def, err, type) \
+int acpihp_dev_##method(struct acpi_device *device, type val) \
+{ \
+ int ret; \
+ BUG_ON(device == NULL); \
+ if (!device->driver || !device->driver->ops.hp_ops) \
+ ret = (err); \
+ else if (!device->driver->ops.hp_ops->method) \
+ ret = (def); \
+ else \
+ ret = device->driver->ops.hp_ops->method(device, val); \
+ return ret; \
+} \
+EXPORT_SYMBOL_GPL(acpihp_dev_##method)
+
+ACPIHP_DEFINE_FUNC1(pre_configure, 0, 0, struct acpihp_cancel_context *);
+ACPIHP_DEFINE_FUNC1(configure, 0, -ENOSYS, struct acpihp_cancel_context *);
+ACPIHP_DEFINE_FUNC1(post_configure, 0, 0, enum acpihp_dev_post_cmd);
+
+ACPIHP_DEFINE_FUNC1(pre_release, 0, 0, struct acpihp_cancel_context *);
+ACPIHP_DEFINE_FUNC1(release, 0, 0, struct acpihp_cancel_context *);
+ACPIHP_DEFINE_FUNC1(post_release, 0, 0, enum acpihp_dev_post_cmd);
+
+ACPIHP_DEFINE_FUNC(pre_unconfigure, 0, 0);
+ACPIHP_DEFINE_FUNC(unconfigure, 0, -ENOSYS);
+ACPIHP_DEFINE_FUNC(post_unconfigure, 0, 0);
+
/*
* When creating ACPI devices for hot-added system devices connecting to slot,
* we shouldn't cross the slot boundary. Otherwise it will cause inconsistence
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 81b4c3f..09bfa80 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -140,6 +140,9 @@ struct acpi_device_ops {
acpi_op_bind bind;
acpi_op_unbind unbind;
acpi_op_notify notify;
+#ifdef CONFIG_ACPI_HOTPLUG
+ struct acpihp_dev_ops *hp_ops;
+#endif /* CONFIG_ACPI_HOTPLUG */
};

#define ACPI_DRIVER_ALL_NOTIFY_EVENTS 0x1 /* system AND device events */
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index cd8dd99..2589ccb 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -66,6 +66,51 @@ struct acpihp_dev_node {
struct klist_node node;
};

+/* Status of system devices. */
+#define ACPIHP_DEV_STATUS_ATTACHED 0x1 /* Device driver attached */
+#define ACPIHP_DEV_STATUS_STARTED 0x2 /* Device started */
+#define ACPIHP_DEV_STATUS_IRREMOVABLE 0x10000 /* Device can't be removed */
+#define ACPIHP_DEV_STATUS_FAULT 0x20000 /* Device in fault status */
+
+struct acpihp_dev_info {
+ enum acpihp_dev_type type;
+ uint32_t status;
+};
+
+/* Rollback or commit changes in post_{confiure|release} */
+enum acpihp_dev_post_cmd {
+ ACPIHP_DEV_POST_CMD_ROLLBACK,
+ ACPIHP_DEV_POST_CMD_COMMIT
+};
+
+/*
+ * ACPI system device drivers may check cancellations of hotplug operations
+ * by invoking the callback.
+ */
+struct acpihp_cancel_context {
+ int (*check_cancel)(struct acpihp_cancel_context *ctx);
+};
+
+/*
+ * Callback hooks provided by ACPI device drivers to support system device
+ * hotplug. To support hotplug, an ACPI system device driver must implement
+ * configure(), unconfigure() and get_info() at least.
+ */
+struct acpihp_dev_ops {
+ int (*get_info)(struct acpi_device *, struct acpihp_dev_info *info);
+ int (*pre_configure)(struct acpi_device *,
+ struct acpihp_cancel_context *);
+ int (*configure)(struct acpi_device *, struct acpihp_cancel_context *);
+ int (*post_configure)(struct acpi_device *, enum acpihp_dev_post_cmd);
+ int (*pre_release)(struct acpi_device *,
+ struct acpihp_cancel_context *);
+ int (*release)(struct acpi_device *, struct acpihp_cancel_context *);
+ int (*post_release)(struct acpi_device *, enum acpihp_dev_post_cmd);
+ int (*pre_unconfigure)(struct acpi_device *);
+ int (*unconfigure)(struct acpi_device *);
+ int (*post_unconfigure)(struct acpi_device *);
+};
+
/*
* ACPI hotplug slot is an abstraction of receptacles where a group of
* system devices could be attached, just like PCI slot in PCI hotplug.
@@ -165,6 +210,25 @@ extern int acpihp_register_class(void);
/* Unregister the ACPI hotplug slot class driver */
extern void acpihp_unregister_class(void);

+/* Interfaces to invoke the ACPI device driver's hotplug hooks. */
+extern int acpihp_dev_get_info(struct acpi_device *device,
+ struct acpihp_dev_info *info);
+extern int acpihp_dev_pre_configure(struct acpi_device *device,
+ struct acpihp_cancel_context *ctx);
+extern int acpihp_dev_configure(struct acpi_device *device,
+ struct acpihp_cancel_context *ctx);
+extern int acpihp_dev_post_configure(struct acpi_device *device,
+ enum acpihp_dev_post_cmd cmd);
+extern int acpihp_dev_pre_release(struct acpi_device *device,
+ struct acpihp_cancel_context *ctx);
+extern int acpihp_dev_release(struct acpi_device *device,
+ struct acpihp_cancel_context *ctx);
+extern int acpihp_dev_post_release(struct acpi_device *device,
+ enum acpihp_dev_post_cmd cmd);
+extern int acpihp_dev_pre_unconfigure(struct acpi_device *device);
+extern int acpihp_dev_unconfigure(struct acpi_device *device);
+extern int acpihp_dev_post_unconfigure(struct acpi_device *device);
+
/* Utility routines */
extern int acpihp_dev_get_type(acpi_handle handle, enum acpihp_dev_type *type);
extern bool acpihp_dev_match_ids(struct acpi_device_info *infop, char **ids);
--
1.7.9.5
Jiang Liu
2012-08-04 12:14:03 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch enhances the ACPI container driver to support the new hotplug
framework:
1) remove code to handle ACPI hotplug event
2) add callbacks to support new hotplug framework

Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/container.c | 201 +++++++---------------------------------------
include/acpi/container.h | 12 ---
2 files changed, 31 insertions(+), 182 deletions(-)
delete mode 100644 include/acpi/container.h

diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c
index 45cd03b..9633648 100644
--- a/drivers/acpi/container.c
+++ b/drivers/acpi/container.c
@@ -34,16 +34,13 @@
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
-#include <acpi/container.h>
+#include <acpi/acpi_hotplug.h>

#define PREFIX "ACPI: "

#define ACPI_CONTAINER_DEVICE_NAME "ACPI container device"
#define ACPI_CONTAINER_CLASS "container"

-#define INSTALL_NOTIFY_HANDLER 1
-#define UNINSTALL_NOTIFY_HANDLER 2
-
#define _COMPONENT ACPI_CONTAINER_COMPONENT
ACPI_MODULE_NAME("container");

@@ -54,6 +51,20 @@ MODULE_LICENSE("GPL");
static int acpi_container_add(struct acpi_device *device);
static int acpi_container_remove(struct acpi_device *device, int type);

+#ifdef CONFIG_ACPI_HOTPLUG
+static int acpihp_container_get_devinfo(struct acpi_device *device,
+ struct acpihp_dev_info *info);
+static int acpihp_container_configure(struct acpi_device *device,
+ struct acpihp_cancel_context *argp);
+static int acpihp_container_unconfigure(struct acpi_device *device);
+
+struct acpihp_dev_ops acpihp_container_ops = {
+ .get_info = acpihp_container_get_devinfo,
+ .configure = acpihp_container_configure,
+ .unconfigure = acpihp_container_unconfigure
+};
+#endif
+
static const struct acpi_device_id container_device_ids[] = {
{"ACPI0004", 0},
{"PNP0A05", 0},
@@ -69,49 +80,21 @@ static struct acpi_driver acpi_container_driver = {
.ops = {
.add = acpi_container_add,
.remove = acpi_container_remove,
+#ifdef CONFIG_ACPI_HOTPLUG
+ .hp_ops = &acpihp_container_ops,
+#endif /* CONFIG_ACPI_HOTPLUG */
},
};

-/*******************************************************************/
-
-static int is_device_present(acpi_handle handle)
-{
- acpi_handle temp;
- acpi_status status;
- unsigned long long sta;
-
-
- status = acpi_get_handle(handle, "_STA", &temp);
- if (ACPI_FAILURE(status))
- return 1; /* _STA not found, assume device present */
-
- status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
- if (ACPI_FAILURE(status))
- return 0; /* Firmware error */
-
- return ((sta & ACPI_STA_DEVICE_PRESENT) == ACPI_STA_DEVICE_PRESENT);
-}
-
-/*******************************************************************/
static int acpi_container_add(struct acpi_device *device)
{
- struct acpi_container *container;
-
-
if (!device) {
printk(KERN_ERR PREFIX "device is NULL\n");
return -EINVAL;
}

- container = kzalloc(sizeof(struct acpi_container), GFP_KERNEL);
- if (!container)
- return -ENOMEM;
-
- container->handle = device->handle;
strcpy(acpi_device_name(device), ACPI_CONTAINER_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_CONTAINER_CLASS);
- device->driver_data = container;
-
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device <%s> bid <%s>\n",
acpi_device_name(device), acpi_device_bid(device)));

@@ -120,163 +103,41 @@ static int acpi_container_add(struct acpi_device *device)

static int acpi_container_remove(struct acpi_device *device, int type)
{
- acpi_status status = AE_OK;
struct acpi_container *pc = NULL;

pc = acpi_driver_data(device);
kfree(pc);
- return status;
+
+ return AE_OK;
}

-static int container_device_add(struct acpi_device **device, acpi_handle handle)
+#ifdef CONFIG_ACPI_HOTPLUG
+static int acpihp_container_get_devinfo(struct acpi_device *device,
+ struct acpihp_dev_info *info)
{
- acpi_handle phandle;
- struct acpi_device *pdev;
- int result;
-
-
- if (acpi_get_parent(handle, &phandle)) {
- return -ENODEV;
- }
-
- if (acpi_bus_get_device(phandle, &pdev)) {
- return -ENODEV;
- }
-
- if (acpi_bus_add(device, pdev, handle, ACPI_BUS_TYPE_DEVICE)) {
- return -ENODEV;
- }
-
- result = acpi_bus_start(*device);
-
- return result;
+ return 0;
}

-static void container_notify_cb(acpi_handle handle, u32 type, void *context)
+static int acpihp_container_configure(struct acpi_device *device,
+ struct acpihp_cancel_context *argp)
{
- struct acpi_device *device = NULL;
- int result;
- int present;
- acpi_status status;
-
-
- present = is_device_present(handle);
-
- switch (type) {
- case ACPI_NOTIFY_BUS_CHECK:
- /* Fall through */
- case ACPI_NOTIFY_DEVICE_CHECK:
- printk(KERN_WARNING "Container driver received %s event\n",
- (type == ACPI_NOTIFY_BUS_CHECK) ?
- "ACPI_NOTIFY_BUS_CHECK" : "ACPI_NOTIFY_DEVICE_CHECK");
- status = acpi_bus_get_device(handle, &device);
- if (present) {
- if (ACPI_FAILURE(status) || !device) {
- result = container_device_add(&device, handle);
- if (!result)
- kobject_uevent(&device->dev.kobj,
- KOBJ_ONLINE);
- else
- printk(KERN_WARNING
- "Failed to add container\n");
- }
- } else {
- if (ACPI_SUCCESS(status)) {
- /* device exist and this is a remove request */
- kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
- }
- }
- break;
- case ACPI_NOTIFY_EJECT_REQUEST:
- if (!acpi_bus_get_device(handle, &device) && device) {
- kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE);
- }
- break;
- default:
- break;
- }
- return;
+ return acpi_container_add(device);
}

-static acpi_status
-container_walk_namespace_cb(acpi_handle handle,
- u32 lvl, void *context, void **rv)
+static int acpihp_container_unconfigure(struct acpi_device *device)
{
- char *hid = NULL;
- struct acpi_device_info *info;
- acpi_status status;
- int *action = context;
-
- status = acpi_get_object_info(handle, &info);
- if (ACPI_FAILURE(status)) {
- return AE_OK;
- }
-
- if (info->valid & ACPI_VALID_HID)
- hid = info->hardware_id.string;
-
- if (hid == NULL) {
- goto end;
- }
-
- if (strcmp(hid, "ACPI0004") && strcmp(hid, "PNP0A05") &&
- strcmp(hid, "PNP0A06")) {
- goto end;
- }
-
- switch (*action) {
- case INSTALL_NOTIFY_HANDLER:
- acpi_install_notify_handler(handle,
- ACPI_SYSTEM_NOTIFY,
- container_notify_cb, NULL);
- break;
- case UNINSTALL_NOTIFY_HANDLER:
- acpi_remove_notify_handler(handle,
- ACPI_SYSTEM_NOTIFY,
- container_notify_cb);
- break;
- default:
- break;
- }
-
- end:
- kfree(info);
-
- return AE_OK;
+ return acpi_container_remove(device, 0);
}
+#endif /* CONFIG_ACPI_HOTPLUG */

static int __init acpi_container_init(void)
{
- int result = 0;
- int action = INSTALL_NOTIFY_HANDLER;
-
- result = acpi_bus_register_driver(&acpi_container_driver);
- if (result < 0) {
- return (result);
- }
-
- /* register notify handler to every container device */
- acpi_walk_namespace(ACPI_TYPE_DEVICE,
- ACPI_ROOT_OBJECT,
- ACPI_UINT32_MAX,
- container_walk_namespace_cb, NULL, &action, NULL);
-
- return (0);
+ return acpi_bus_register_driver(&acpi_container_driver);
}

static void __exit acpi_container_exit(void)
{
- int action = UNINSTALL_NOTIFY_HANDLER;
-
-
- acpi_walk_namespace(ACPI_TYPE_DEVICE,
- ACPI_ROOT_OBJECT,
- ACPI_UINT32_MAX,
- container_walk_namespace_cb, NULL, &action, NULL);
-
acpi_bus_unregister_driver(&acpi_container_driver);
-
- return;
}

module_init(acpi_container_init);
diff --git a/include/acpi/container.h b/include/acpi/container.h
deleted file mode 100644
index a703f14..0000000
--- a/include/acpi/container.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef __ACPI_CONTAINER_H
-#define __ACPI_CONTAINER_H
-
-#include <linux/kernel.h>
-
-struct acpi_container {
- acpi_handle handle;
- unsigned long sun;
- int state;
-};
-
-#endif /* __ACPI_CONTAINER_H */
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:13:49 UTC
Permalink
From: Jiang Liu <***@huawei.com>

The first is an ACPI hotplug slot enumerator, which enumerates ACPI hotplug
slots on load and provides callbacks to manage those hotplug slots.
An ACPI hotplug slot is an abstraction of receptacles, where a group of
system devices could be connected to. This patch implements the skeleton for
ACPI system device hotplug slot enumerator. On loading, the driver scans the
whole ACPI namespace for hotplug slots and creates a device node for each
hotplug slots. Every slot is associated with a device class named
acpihp_slot_class and will be managed by ACPI hotplug drivers.

The hotplug enumerator will create following sysfs entries for hotplug slots:

linux-drf:/sys/devices/LNXSYSTM:00/acpihp # ll
drwxr-xr-x 4 root root 0 Jul 28 16:00 NODE00
drwxr-xr-x 3 root root 0 Jul 28 16:00 NODE01
drwxr-xr-x 3 root root 0 Jul 28 16:00 NODE02

linux-drf:/sys/devices/LNXSYSTM:00/acpihp/NODE00 # ll
drwxr-xr-x 3 root root 0 Jul 28 16:00 IOX01
-r--r--r-- 1 root root 65536 Jul 28 16:01 capabilities
lrwxrwxrwx 1 root root 0 Jul 28 16:00 device -> ../../../LNXSYSTM:00
-r--r--r-- 1 root root 65536 Jul 28 16:01 object
drwxr-xr-x 2 root root 0 Jul 28 16:01 power
-r--r--r-- 1 root root 65536 Jul 28 16:01 state
-r--r--r-- 1 root root 65536 Jul 28 16:01 status
lrwxrwxrwx 1 root root 0 Jul 28 16:00 subsystem -> ../../../../class/acpihp
-r--r--r-- 1 root root 65536 Jul 28 16:01 type
-rw-r--r-- 1 root root 65536 Jul 28 16:01 uevent

linux-drf:/sys/bus/acpi/acpihp # ls
NODE00 NODE00.IOX01 NODE01 NODE02

linux-drf:/sys/bus/acpi/acpihp # ll
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE00 ->
../../../devices/LNXSYSTM:00/acpihp/NODE00
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE00.IOX01 ->
../../../devices/LNXSYSTM:00/acpihp/NODE00/IOX01
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE01 ->
../../../devices/LNXSYSTM:00/acpihp/NODE01
lrwxrwxrwx 1 root root 0 Jul 28 16:03 NODE02 ->
../../../devices/LNXSYSTM:00/acpihp/NODE02

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
---
drivers/acpi/Kconfig | 11 +
drivers/acpi/hotplug/Makefile | 3 +
drivers/acpi/hotplug/slot_enum.c | 466 ++++++++++++++++++++++++++++++++++++++
3 files changed, 480 insertions(+)
create mode 100644 drivers/acpi/hotplug/slot_enum.c

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index e457d31..711e18e 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -333,6 +333,17 @@ menuconfig ACPI_HOTPLUG
If your hardware and firmware do not support adding or removing
of system devices at runtime, you need not to enable this option.

+config ACPI_HOTPLUG_ENUM
+ tristate "ACPI Hotplug Slot Enumerator"
+ depends on ACPI_HOTPLUG
+ default y
+ help
+ This driver enumerates ACPI hotplug slots for ACPI based system
+ device hotplug.
+
+ To compile this driver as a module, choose M here:
+ the module will be called acpihp_enum.
+
config ACPI_CONTAINER
tristate "Container and Module Devices (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 5e7790f..41c0da9 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -4,3 +4,6 @@

obj-$(CONFIG_ACPI_HOTPLUG) += acpihp.o
acpihp-y = core.o
+
+obj-$(CONFIG_ACPI_HOTPLUG_ENUM) += acpihp_enum.o
+acpihp_enum-y = slot_enum.o
diff --git a/drivers/acpi/hotplug/slot_enum.c b/drivers/acpi/hotplug/slot_enum.c
new file mode 100644
index 0000000..80396a3
--- /dev/null
+++ b/drivers/acpi/hotplug/slot_enum.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Gaohuai Han <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <acpi/acpi.h>
+#include <acpi/acpi_hotplug.h>
+
+static LIST_HEAD(slot_list);
+static LIST_HEAD(slot_id_list);
+
+struct acpihp_slot_id {
+ struct list_head node;
+ unsigned long instance_id;
+ enum acpihp_slot_type type;
+};
+
+static struct acpihp_slot_ops *slot_ops_curr;
+
+/*
+ * Array of platform specific enumeration methods.
+ * Entries in the array should be sorted by descending priority order.
+ */
+static struct acpihp_slot_ops *slot_ops_array[] = {
+ NULL
+};
+
+static void acpihp_enum_cleanup_slots(void);
+
+static int __init acpihp_get_parent_slot(struct acpihp_slot *slot)
+{
+ acpi_handle handle, root_handle;
+ struct acpihp_slot *tmp;
+
+ slot->parent = NULL;
+ handle = slot->handle;
+ if (ACPI_FAILURE(acpi_get_handle(NULL, ACPI_NS_ROOT_PATH,
+ &root_handle))) {
+ ACPIHP_WARN("fails to get ACPI root device.\n");
+ return -EINVAL;
+ }
+
+ do {
+ if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) {
+ ACPIHP_DEBUG("fails to get parent device handle.\n");
+ return -ENODEV;
+ }
+ list_for_each_entry(tmp, &slot_list, slot_list)
+ if (tmp->handle == handle) {
+ slot->parent = tmp;
+ return 0;
+ }
+ } while (handle != root_handle);
+
+ return 0;
+}
+
+static int __init acpihp_get_slot_state(struct acpihp_slot *slot)
+{
+ unsigned long long sta;
+
+ /* An hotplug slot must implement _STA method. */
+ if (ACPI_FAILURE(acpi_evaluate_integer(slot->handle, METHOD_NAME__STA,
+ NULL, &sta))) {
+ ACPIHP_DEBUG("fails to execute _STA method.\n");
+ return -EINVAL;
+ }
+
+ if (!(sta & ACPI_STA_DEVICE_PRESENT))
+ slot->state = ACPIHP_SLOT_STATE_ABSENT;
+ else if ((sta & ACPI_STA_DEVICE_ENABLED) ||
+ (sta & ACPI_STA_DEVICE_FUNCTIONING))
+ slot->state = ACPIHP_SLOT_STATE_POWERED;
+ else
+ slot->state = ACPIHP_SLOT_STATE_PRESENT;
+
+ return 0;
+}
+
+static int __init acpihp_enum_create_slot(acpi_handle handle)
+{
+ struct acpihp_slot *slot;
+
+ slot = acpihp_create_slot(handle, "TEMP");
+ if (!slot) {
+ ACPIHP_DEBUG("fails to allocate memory for hotplug slot.\n");
+ return -ENOMEM;
+ }
+
+ slot->slot_ops = slot_ops_curr;
+
+ if (acpihp_get_parent_slot(slot))
+ goto out;
+ if (acpihp_get_slot_state(slot))
+ goto out;
+ if (ACPI_FAILURE(acpihp_slot_get_capabilities(slot,
+ &slot->capabilities))) {
+ ACPIHP_DEBUG("fails to get slot capabilities.\n");
+ goto out;
+ }
+ if (ACPI_FAILURE(acpihp_mark_slot(handle, slot))) {
+ ACPIHP_DEBUG("fails to attach slot to ACPI device object.\n");
+ goto out;
+ }
+
+ list_add_tail(&slot->slot_list, &slot_list);
+
+ return 0;
+out:
+ acpihp_slot_put(slot);
+ return -EINVAL;
+}
+
+/*
+ * Scan hotplug slots for ACPI based system device hotplug.
+ * We only care about processor, memory, PCI host bridge and CONTAINER.
+ */
+static acpi_status __init acpihp_enum_scan_slot(acpi_handle handle, u32 lvl,
+ void *context, void **rv)
+{
+ enum acpihp_dev_type type;
+
+ if (acpihp_dev_get_type(handle, &type) ||
+ type == ACPIHP_DEV_TYPE_UNKNOWN)
+ return AE_OK;
+
+ if (ACPI_SUCCESS(slot_ops_curr->check(handle)))
+ acpihp_enum_create_slot(handle);
+
+ /*
+ * Don't scan hotplug slots under PCI host bridges, they should be
+ * handled by acpiphp or pciehp drivers.
+ */
+ if (type == ACPIHP_DEV_TYPE_HOST_BRIDGE)
+ return AE_CTRL_DEPTH;
+
+ return AE_OK;
+}
+
+/*
+ * Get types of child devices connected to this slot.
+ * We only care about CPU, memory, PCI host bridge and CONTAINER here.
+ * Values used here must be in consistence with acpihp_enum_get_slot_type().
+ */
+static acpi_status __init
+acpihp_enum_get_dev_type(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+ acpi_status status = AE_OK;
+ enum acpihp_dev_type type;
+ u32 *tp = (u32 *)rv;
+
+ if (!acpihp_dev_get_type(handle, &type)) {
+ switch (type) {
+ case ACPIHP_DEV_TYPE_CPU:
+ *tp |= 0x0001;
+ status = AE_CTRL_DEPTH;
+ break;
+ case ACPIHP_DEV_TYPE_MEM:
+ *tp |= 0x0002;
+ status = AE_CTRL_DEPTH;
+ break;
+ case ACPIHP_DEV_TYPE_HOST_BRIDGE:
+ *tp |= 0x0004;
+ status = AE_CTRL_DEPTH;
+ break;
+ case ACPIHP_DEV_TYPE_CONTAINER:
+ *tp |= 0x0008;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * Guess type of a hotplug slot according to child devices connecting to it.
+ */
+static enum acpihp_slot_type __init acpihp_enum_get_slot_type(u32 dev_types)
+{
+ BUG_ON(dev_types > 15);
+
+ switch (dev_types) {
+ case 0:
+ /* Generic CONTAINER */
+ return ACPIHP_SLOT_TYPE_COMMON;
+ case 1:
+ /* Physical processor with logical CPUs */
+ return ACPIHP_SLOT_TYPE_CPU;
+ case 2:
+ /* Memory board/box with memory devices */
+ return ACPIHP_SLOT_TYPE_MEM;
+ case 3:
+ /* Physical processor with CPUs and memory controllers */
+ return ACPIHP_SLOT_TYPE_CPU;
+ case 4:
+ /* IO eXtension board/box with IO host bridges */
+ return ACPIHP_SLOT_TYPE_IOX;
+ case 7:
+ /* Physical processor with CPUs, IO host bridges and MCs. */
+ return ACPIHP_SLOT_TYPE_CPU;
+ case 8:
+ /* Generic CONTAINER */
+ return ACPIHP_SLOT_TYPE_COMMON;
+ case 9:
+ /* System board with physical processors */
+ return ACPIHP_SLOT_TYPE_SYSTEM_BOARD;
+ case 11:
+ /* System board with physical processors and memory */
+ return ACPIHP_SLOT_TYPE_SYSTEM_BOARD;
+ case 15:
+ /* Node with processor, memory and IO host bridge */
+ return ACPIHP_SLOT_TYPE_NODE;
+ default:
+ return ACPIHP_SLOT_TYPE_UNKNOWN;
+ }
+}
+
+/*
+ * Guess type of a hotplug slot according to the device type of the
+ * corresponding ACPI object itself.
+ */
+static enum acpihp_slot_type __init
+acpihp_enum_check_slot_self(struct acpihp_slot *slot)
+{
+ enum acpihp_dev_type type;
+
+ if (acpihp_dev_get_type(slot->handle, &type))
+ return ACPIHP_SLOT_TYPE_UNKNOWN;
+
+ switch (type) {
+ case ACPIHP_DEV_TYPE_CPU:
+ /* Logical CPU used in virtualization environment */
+ return ACPIHP_SLOT_TYPE_CPU;
+ case ACPIHP_DEV_TYPE_MEM:
+ /* Memory board with single memory device */
+ return ACPIHP_SLOT_TYPE_MEM;
+ case ACPIHP_DEV_TYPE_HOST_BRIDGE:
+ /* IO eXtension board/box with single IO host bridge */
+ return ACPIHP_SLOT_TYPE_IOX;
+ default:
+ return ACPIHP_SLOT_TYPE_UNKNOWN;
+ }
+}
+
+static int __init acpihp_enum_generate_slot_name(struct acpihp_slot *slot)
+{
+ int found = 0;
+ struct list_head *list;
+ struct acpihp_slot_id *slot_id;
+ unsigned long long uid;
+
+ /* Respect firmware settings if _UID return an integer. */
+ if (ACPI_SUCCESS(acpi_evaluate_integer(slot->handle, METHOD_NAME__UID,
+ NULL, &uid)))
+ goto set_name;
+
+ if (slot->parent)
+ list = &slot->parent->slot_id_list;
+ else
+ list = &slot_id_list;
+
+ list_for_each_entry(slot_id, list, node)
+ if (slot_id->type == slot->type) {
+ found = 1;
+ break;
+ }
+ if (!found) {
+ slot_id = kzalloc(sizeof(struct acpihp_slot_id), GFP_KERNEL);
+ if (!slot_id) {
+ ACPIHP_DEBUG("fails to allocate slot instance ID.\n");
+ return -ENOMEM;
+ }
+ slot_id->type = slot->type;
+ list_add_tail(&slot_id->node, list);
+ }
+
+ uid = slot_id->instance_id++;
+
+set_name:
+ snprintf(slot->name, sizeof(slot->name) - 1, "%s%02llx",
+ acpihp_get_slot_type_name(slot->type), uid);
+ dev_set_name(&slot->dev, "%s", slot->name);
+
+ return 0;
+}
+
+/*
+ * Generate a meaningful name for the slot according to devices connecting
+ * to this slot
+ */
+static int __init acpihp_enum_rename_slot(struct acpihp_slot *slot)
+{
+ u32 child_types = 0;
+
+ slot->type = acpihp_enum_check_slot_self(slot);
+ if (slot->type == ACPIHP_SLOT_TYPE_UNKNOWN) {
+ acpi_walk_namespace(ACPI_TYPE_DEVICE, slot->handle,
+ ACPI_UINT32_MAX, acpihp_enum_get_dev_type,
+ NULL, NULL, (void **)&child_types);
+ acpi_walk_namespace(ACPI_TYPE_PROCESSOR, slot->handle,
+ ACPI_UINT32_MAX, acpihp_enum_get_dev_type,
+ NULL, NULL, (void **)&child_types);
+ slot->type = acpihp_enum_get_slot_type(child_types);
+ }
+
+ if (acpihp_enum_generate_slot_name(slot))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __init acpihp_enum_rename_and_register_slots(void)
+{
+ struct acpihp_slot *slot;
+
+ list_for_each_entry(slot, &slot_list, slot_list) {
+ /* generate a meaningful name for this slot */
+ if (acpihp_enum_rename_slot(slot))
+ continue;
+
+ if (acpihp_register_slot(slot))
+ ACPIHP_DEBUG("fails to register slot %s.\n",
+ slot->name);
+ }
+}
+
+static int __init acpihp_enum_generate_slots(void)
+{
+ acpi_status status;
+
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX, acpihp_enum_scan_slot,
+ NULL, NULL, NULL);
+ if (!ACPI_SUCCESS(status))
+ goto out_err;
+
+ status = acpi_walk_namespace(ACPI_TYPE_PROCESSOR, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX, acpihp_enum_scan_slot,
+ NULL, NULL, NULL);
+ if (!ACPI_SUCCESS(status))
+ goto out_err;
+
+ acpihp_enum_rename_and_register_slots();
+
+ return 0;
+
+out_err:
+ ACPIHP_DEBUG("fails to scan hotplug slots.\n");
+ acpihp_enum_cleanup_slots();
+
+ return -ENOTSUPP;
+}
+
+static void acpihp_enum_unregister_slots(void)
+{
+ struct acpihp_slot *slot, *tmp;
+ struct acpihp_slot_id *slot_id, *slot_id_safe;
+
+ list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) {
+ acpihp_unregister_slot(slot);
+ list_del_init(&slot->slot_list);
+ acpihp_unmark_slot(slot->handle);
+ list_for_each_entry_safe(slot_id, slot_id_safe,
+ &slot->slot_id_list, node) {
+ list_del(&slot_id->node);
+ kfree(slot_id);
+ }
+ acpihp_slot_put(slot);
+ }
+}
+
+static void acpihp_enum_cleanup_slots(void)
+{
+ struct acpihp_slot_id *slot_id, *tmp;
+
+ acpihp_enum_unregister_slots();
+ list_for_each_entry_safe(slot_id, tmp, &slot_id_list, node) {
+ list_del(&slot_id->node);
+ kfree(slot_id);
+ }
+}
+
+static int __init acpihp_enum_init(void)
+{
+ int i;
+ int retval;
+
+ /* probe for suitable enumerator. */
+ for (i = 0; slot_ops_array[i]; i++)
+ if (ACPI_SUCCESS(slot_ops_array[i]->init())) {
+ slot_ops_curr = slot_ops_array[i];
+ break;
+ }
+ if (slot_ops_curr == NULL) {
+ ACPIHP_DEBUG("no ACPI hotplug slot found.\n");
+ return -ENXIO;
+ }
+
+ retval = acpihp_register_class();
+ if (retval != 0) {
+ ACPIHP_DEBUG("fails to register ACPI hotplug slot class.\n");
+ goto out_fini;
+ }
+
+ retval = acpihp_enum_generate_slots();
+ if (retval != 0) {
+ ACPIHP_DEBUG("fails to enumerate ACPI hotplug slots.\n");
+ goto out_unregister_class;
+ }
+
+ /* Back out if no ACPI hotplug slot found. */
+ if (list_empty(&slot_list)) {
+ ACPIHP_DEBUG("no ACPI hotplug slot found.\n");
+ retval = -ENODEV;
+ goto out_unregister_class;
+ }
+
+ return 0;
+
+out_unregister_class:
+ acpihp_unregister_class();
+out_fini:
+ slot_ops_curr->fini();
+ ACPIHP_DEBUG("fails to initialize hotplug slot enumerator.\n");
+
+ return retval;
+}
+
+static void __exit acpihp_enum_exit(void)
+{
+ acpihp_enum_cleanup_slots();
+ acpihp_unregister_class();
+ slot_ops_curr->fini();
+}
+
+module_init(acpihp_enum_init);
+module_exit(acpihp_enum_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jiang Liu <***@huawei.com>");
+MODULE_AUTHOR("Gaohuai Han <***@huawei.com>");
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:51 UTC
Permalink
From: Jiang Liu <***@huawei.com>

Function acpi_bus_get_device() return an ACPI device without holding a
reference count the device, which is not safe to ACPI hotplug operations.

So change acpi_bus_get_device() to hold a reference count on the returned
ACPI device and introduces acpi_get_device()/acpi_put_device() to manage
ACPI device reference count.

This patch is still incomplete, need to check and modify all callers of
acpi_bus_get_device().

Signed-off-by: Jiang Liu <***@gmail.com>
---
drivers/acpi/bus.c | 22 +++++++++++++++++++---
drivers/acpi/internal.h | 3 +++
drivers/acpi/scan.c | 6 ++++++
drivers/gpu/drm/i915/intel_opregion.c | 2 ++
drivers/gpu/drm/nouveau/nouveau_acpi.c | 1 +
drivers/pci/hotplug/acpiphp_glue.c | 9 +++++++--
drivers/pci/hotplug/acpiphp_ibm.c | 5 ++++-
drivers/pci/hotplug/sgi_hotplug.c | 6 +++++-
drivers/platform/x86/thinkpad_acpi.c | 2 ++
drivers/pnp/pnpacpi/core.c | 23 ++++++++++-------------
include/acpi/acpi_bus.h | 16 +++++++++++++++-
11 files changed, 74 insertions(+), 21 deletions(-)

diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
index adceafd..e13dcbc 100644
--- a/drivers/acpi/bus.c
+++ b/drivers/acpi/bus.c
@@ -89,26 +89,42 @@ static struct dmi_system_id dsdt_dmi_table[] __initdata = {
Device Management
-------------------------------------------------------------------------- */

-int acpi_bus_get_device(acpi_handle handle, struct acpi_device **device)
+bool acpi_bus_has_device(acpi_handle handle)
{
- acpi_status status = AE_OK;
+ acpi_status status;
+ struct acpi_device *device = NULL;
+
+ down_read(&acpi_device_data_handler_sem);
+ status = acpi_get_data(handle, acpi_bus_data_handler, (void **)&device);
+ up_read(&acpi_device_data_handler_sem);
+
+ return ACPI_SUCCESS(status) && device;
+}
+EXPORT_SYMBOL(acpi_bus_has_device);

+int acpi_bus_get_device(acpi_handle handle, struct acpi_device **device)
+{
+ acpi_status status;

if (!device)
return -EINVAL;

/* TBD: Support fixed-feature devices */

+ down_read(&acpi_device_data_handler_sem);
status = acpi_get_data(handle, acpi_bus_data_handler, (void **)device);
if (ACPI_FAILURE(status) || !*device) {
+ up_read(&acpi_device_data_handler_sem);
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No context for object [%p]\n",
handle));
return -ENODEV;
+ } else {
+ get_device(&(*device)->dev);
+ up_read(&acpi_device_data_handler_sem);
}

return 0;
}
-
EXPORT_SYMBOL(acpi_bus_get_device);

acpi_status acpi_bus_get_status_handle(acpi_handle handle,
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index ca75b9c..70843c0 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -93,4 +93,7 @@ static inline int suspend_nvs_save(void) { return 0; }
static inline void suspend_nvs_restore(void) {}
#endif

+extern struct rw_semaphore acpi_device_data_handler_sem;
+void acpi_bus_data_handler(acpi_handle handle, void *context);
+
#endif /* _ACPI_INTERNAL_H_ */
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 459593a..28408af 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -10,6 +10,7 @@
#include <linux/signal.h>
#include <linux/kthread.h>
#include <linux/dmi.h>
+#include <linux/sem.h>

#include <acpi/acpi_drivers.h>
#include <linux/idr.h>
@@ -33,6 +34,7 @@ static LIST_HEAD(acpi_device_list);
static LIST_HEAD(acpi_bus_id_list);
DEFINE_MUTEX(acpi_device_lock);
LIST_HEAD(acpi_wakeup_device_list);
+DECLARE_RWSEM(acpi_device_data_handler_sem);

struct acpi_device_bus_id{
char bus_id[15];
@@ -564,7 +566,9 @@ static void acpi_device_unregister(struct acpi_device *device, int type)
acpi_device_release_id(device);
mutex_unlock(&acpi_device_lock);

+ down_write(&acpi_device_data_handler_sem);
acpi_detach_data(device->handle, acpi_bus_data_handler);
+ up_write(&acpi_device_data_handler_sem);

acpi_device_remove_files(device);
device_unregister(&device->dev);
@@ -1227,8 +1231,10 @@ static int acpi_device_set_context(struct acpi_device *device)
if (!device->handle)
return 0;

+ down_write(&acpi_device_data_handler_sem);
status = acpi_attach_data(device->handle,
acpi_bus_data_handler, device);
+ up_write(&acpi_device_data_handler_sem);
if (ACPI_SUCCESS(status))
return 0;

diff --git a/drivers/gpu/drm/i915/intel_opregion.c b/drivers/gpu/drm/i915/intel_opregion.c
index 18bd0af..da7dd48 100644
--- a/drivers/gpu/drm/i915/intel_opregion.c
+++ b/drivers/gpu/drm/i915/intel_opregion.c
@@ -359,6 +359,8 @@ static void intel_didl_outputs(struct drm_device *dev)
}
}

+ acpi_device_put(acpi_dev);
+
if (!acpi_video_bus) {
pr_warn("No ACPI video bus found\n");
return;
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index fc841e8..f83efe3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -423,6 +423,7 @@ nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector)
return -ENODEV;

ret = acpi_video_get_edid(acpidev, type, -1, &edid);
+ acpi_device_put(acpidev);
if (ret < 0)
return ret;

diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c
index 806c44f..78cffba4 100644
--- a/drivers/pci/hotplug/acpiphp_glue.c
+++ b/drivers/pci/hotplug/acpiphp_glue.c
@@ -724,11 +724,13 @@ static int acpiphp_bus_add(struct acpiphp_func *func)
* the bus then re-add it...
*/
ret_val = acpi_bus_trim(device, 1);
+ acpi_device_put(device);
dbg("acpi_bus_trim return %x\n", ret_val);
}

ret_val = acpi_bus_add(&device, pdevice, func->handle,
ACPI_BUS_TYPE_DEVICE);
+ acpi_device_put(pdevice);
if (ret_val) {
dbg("error adding bus, %x\n",
-ret_val);
@@ -760,6 +762,8 @@ static int acpiphp_bus_trim(acpi_handle handle)
if (retval)
err("cannot remove from acpi list\n");

+ acpi_device_put(device);
+
return retval;
}

@@ -1114,8 +1118,10 @@ static void handle_bridge_insertion(acpi_handle handle, u32 type)
}
if (acpi_bus_add(&device, pdevice, handle, ACPI_BUS_TYPE_DEVICE)) {
err("cannot add bridge to acpi list\n");
+ acpi_device_put(pdevice);
return;
}
+ acpi_device_put(pdevice);
if (!acpiphp_configure_bridge(handle) &&
!acpi_bus_start(device))
add_bridge(handle);
@@ -1192,7 +1198,6 @@ static void _handle_hotplug_event_bridge(struct work_struct *work)
char objname[64];
struct acpi_buffer buffer = { .length = sizeof(objname),
.pointer = objname };
- struct acpi_device *device;
int num_sub_bridges = 0;
struct acpiphp_hp_work *hp_work;
acpi_handle handle;
@@ -1202,7 +1207,7 @@ static void _handle_hotplug_event_bridge(struct work_struct *work)
handle = hp_work->handle;
type = hp_work->type;

- if (acpi_bus_get_device(handle, &device)) {
+ if (!acpi_bus_has_device(handle)) {
/* This bridge must have just been physically inserted */
handle_bridge_insertion(handle, type);
goto out;
diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c
index c35e8ad..ecd4b8d 100644
--- a/drivers/pci/hotplug/acpiphp_ibm.c
+++ b/drivers/pci/hotplug/acpiphp_ibm.c
@@ -450,7 +450,7 @@ static int __init ibm_acpiphp_init(void)
}
if (acpiphp_register_attention(&ibm_attention_info)) {
retval = -ENODEV;
- goto init_return;
+ goto put_device;
}

ibm_note.device = device;
@@ -471,6 +471,8 @@ static int __init ibm_acpiphp_init(void)

init_cleanup:
acpiphp_unregister_attention(&ibm_attention_info);
+put_device:
+ acpi_device_put(device);
init_return:
return retval;
}
@@ -493,6 +495,7 @@ static void __exit ibm_acpiphp_exit(void)
err("%s: Notification handler removal failed\n", __func__);
/* remove the /sys entries */
sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr);
+ acpi_device_put(ibm_note.device);
}

module_init(ibm_acpiphp_init);
diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c
index de57311..ad120f1 100644
--- a/drivers/pci/hotplug/sgi_hotplug.c
+++ b/drivers/pci/hotplug/sgi_hotplug.c
@@ -464,6 +464,8 @@ static int enable_slot(struct hotplug_slot *bss_hotplug_slot)
}
}
}
+
+ acpi_device_put(pdevice);
}

/* Call the driver for the new device */
@@ -540,8 +542,10 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot)

ret = acpi_bus_get_device(chandle,
&device);
- if (ACPI_SUCCESS(ret))
+ if (ACPI_SUCCESS(ret)) {
acpi_bus_trim(device, 1);
+ acpi_device_put(device);
+ }
}
}

diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 8b5610d..285eac5f 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -782,6 +782,7 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
pr_err("acpi_install_notify_handler(%s) failed: %s\n",
ibm->name, acpi_format_exception(status));
}
+ acpi_device_put(ibm->acpi->device);
return -ENODEV;
}
ibm->flags.acpi_notify_installed = 1;
@@ -8461,6 +8462,7 @@ static void ibm_exit(struct ibm_struct *ibm)
acpi_remove_notify_handler(*ibm->acpi->handle,
ibm->acpi->type,
dispatch_acpi_notify);
+ acpi_device_put(ibm->acpi->device);
ibm->flags.acpi_notify_installed = 0;
}

diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c
index d21e8f5..684b462 100644
--- a/drivers/pnp/pnpacpi/core.c
+++ b/drivers/pnp/pnpacpi/core.c
@@ -82,7 +82,6 @@ static int pnpacpi_get_resources(struct pnp_dev *dev)

static int pnpacpi_set_resources(struct pnp_dev *dev)
{
- struct acpi_device *acpi_dev;
acpi_handle handle;
struct acpi_buffer buffer;
int ret;
@@ -90,7 +89,7 @@ static int pnpacpi_set_resources(struct pnp_dev *dev)
pnp_dbg(&dev->dev, "set resources\n");

handle = DEVICE_ACPI_HANDLE(&dev->dev);
- if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &acpi_dev))) {
+ if (!handle || !acpi_bus_has_device(handle)) {
dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__);
return -ENODEV;
}
@@ -113,14 +112,13 @@ static int pnpacpi_set_resources(struct pnp_dev *dev)

static int pnpacpi_disable_resources(struct pnp_dev *dev)
{
- struct acpi_device *acpi_dev;
acpi_handle handle;
int ret;

dev_dbg(&dev->dev, "disable resources\n");

handle = DEVICE_ACPI_HANDLE(&dev->dev);
- if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &acpi_dev))) {
+ if (!handle || !acpi_bus_has_device(handle)) {
dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__);
return 0;
}
@@ -138,11 +136,10 @@ static int pnpacpi_disable_resources(struct pnp_dev *dev)
#ifdef CONFIG_ACPI_SLEEP
static bool pnpacpi_can_wakeup(struct pnp_dev *dev)
{
- struct acpi_device *acpi_dev;
acpi_handle handle;

handle = DEVICE_ACPI_HANDLE(&dev->dev);
- if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &acpi_dev))) {
+ if (!handle || !acpi_bus_has_device(handle)) {
dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__);
return false;
}
@@ -152,12 +149,11 @@ static bool pnpacpi_can_wakeup(struct pnp_dev *dev)

static int pnpacpi_suspend(struct pnp_dev *dev, pm_message_t state)
{
- struct acpi_device *acpi_dev;
acpi_handle handle;
int error = 0;

handle = DEVICE_ACPI_HANDLE(&dev->dev);
- if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &acpi_dev))) {
+ if (!handle || !acpi_bus_has_device(handle)) {
dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__);
return 0;
}
@@ -190,11 +186,10 @@ static int pnpacpi_suspend(struct pnp_dev *dev, pm_message_t state)

static int pnpacpi_resume(struct pnp_dev *dev)
{
- struct acpi_device *acpi_dev;
acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
int error = 0;

- if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &acpi_dev))) {
+ if (!handle || !acpi_bus_has_device(handle)) {
dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__);
return -ENODEV;
}
@@ -310,10 +305,12 @@ static acpi_status __init pnpacpi_add_device_handler(acpi_handle handle,
{
struct acpi_device *device;

- if (!acpi_bus_get_device(handle, &device))
- pnpacpi_add_device(device);
- else
+ if (acpi_bus_get_device(handle, &device))
return AE_CTRL_DEPTH;
+
+ pnpacpi_add_device(device);
+ acpi_device_put(device);
+
return AE_OK;
}

diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 9e6e1c6..e2a3eb0 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -345,8 +345,22 @@ extern void unregister_acpi_bus_notifier(struct notifier_block *nb);
* External Functions
*/

+static inline struct acpi_device *acpi_device_get(struct acpi_device *d)
+{
+ if (d)
+ get_device(&d->dev);
+
+ return d;
+}
+
+static inline void acpi_device_put(struct acpi_device *d)
+{
+ if (d)
+ put_device(&d->dev);
+}
+
+bool acpi_bus_has_device(acpi_handle handle);
int acpi_bus_get_device(acpi_handle handle, struct acpi_device **device);
-void acpi_bus_data_handler(acpi_handle handle, void *context);
acpi_status acpi_bus_get_status_handle(acpi_handle handle,
unsigned long long *sta);
int acpi_bus_get_status(struct acpi_device *device);
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:14:00 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements functions to configure/unconfigure system devices
connecting to a hotplug slot.

To support better error recover and cancellation, configuration operations are
splitted into three steps and unconfiguration operations are splitted into six
steps as below:
CONFIGURE
1) pre_configure(): allocate required resources
2) configure(): add devices into system
3) pos_configure(): rollback if cancelled or failed to add devices
UNCONFIGURE
1) pre_release(): optional
2) release(): reclaim devices from system
3) post_release(): rollback if cancelled or failed to reclaim devices
4) pre_unconfigure(): optional
5) unconfigure(): remove devices from system
6) post_unconfigure(): free resources used by devices

And all devices are unconfigured in reverse order to solve failures caused
by dependencies.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
---
drivers/acpi/hotplug/Makefile | 1 +
drivers/acpi/hotplug/acpihp_drv.h | 3 +
drivers/acpi/hotplug/configure.c | 349 +++++++++++++++++++++++++++++++++++++
3 files changed, 353 insertions(+)
create mode 100644 drivers/acpi/hotplug/configure.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 7e10f69..55d559a 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_ACPI_HOTPLUG_DRIVER) += acpihp_drv.o
acpihp_drv-y = drv_main.o
acpihp_drv-y += dependency.o
acpihp_drv-y += cancel.o
+acpihp_drv-y += configure.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index 5d69272..eca2036 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -89,4 +89,7 @@ void acpihp_drv_cancel_fini(struct list_head *list);
int acpihp_drv_cancel_start(struct list_head *list);
int acpihp_drv_cancel_wait(struct list_head *list);

+int acpihp_drv_configure(struct list_head *list);
+int acpihp_drv_unconfigure(struct list_head *list);
+
#endif /* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/configure.c b/drivers/acpi/hotplug/configure.c
new file mode 100644
index 0000000..5002cf4
--- /dev/null
+++ b/drivers/acpi/hotplug/configure.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2011 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2011 Jiang Liu <***@huawei.com>
+ * Copyright (C) 2011 Hanjun Guo <***@huawei.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+enum config_op_code {
+ DRV_OP_PRE_CONFIGURE,
+ DRV_OP_CONFIGURE,
+ DRV_OP_POST_CONFIGURE,
+ DRV_OP_PRE_RELEASE,
+ DRV_OP_RELEASE,
+ DRV_OP_POST_RELEASE,
+ DRV_OP_PRE_UNCONFIGURE,
+ DRV_OP_UNCONFIGURE,
+ DRV_OP_POST_UNCONFIGURE
+};
+
+/* All devices will be configured in the order. */
+static enum acpihp_dev_type acpihp_drv_dev_types[] = {
+ ACPIHP_DEV_TYPE_CONTAINER,
+ ACPIHP_DEV_TYPE_MEM,
+ ACPIHP_DEV_TYPE_CPU,
+ ACPIHP_DEV_TYPE_IOAPIC,
+ ACPIHP_DEV_TYPE_HOST_BRIDGE,
+};
+
+/* All devices will be unconfigured in the order. */
+static enum acpihp_dev_type acpihp_drv_dev_types_reverse[] = {
+ ACPIHP_DEV_TYPE_HOST_BRIDGE,
+ ACPIHP_DEV_TYPE_IOAPIC,
+ ACPIHP_DEV_TYPE_CPU,
+ ACPIHP_DEV_TYPE_MEM,
+ ACPIHP_DEV_TYPE_CONTAINER,
+};
+
+static void acpihp_drv_update_dev_state(struct acpihp_dev_node *dev,
+ enum acpihp_dev_state state)
+{
+ BUG_ON(state <= DEVICE_STATE_UNKOWN || state >= DEVICE_STATE_MAX);
+ mutex_lock(&dev->lock);
+ dev->state = state;
+ mutex_unlock(&dev->lock);
+}
+
+static int acpihp_drv_invoke_method(enum config_op_code opcode,
+ struct acpihp_slot *slot,
+ struct acpi_device *dev, int post)
+{
+ struct acpihp_slot_drv *drv_data;
+
+ acpihp_drv_get_data(slot, &drv_data);
+
+ switch (opcode) {
+ case DRV_OP_PRE_CONFIGURE:
+ return acpihp_dev_pre_configure(dev, &drv_data->cancel_ctx);
+ case DRV_OP_CONFIGURE:
+ return acpihp_dev_configure(dev, &drv_data->cancel_ctx);
+ case DRV_OP_POST_CONFIGURE:
+ return acpihp_dev_post_configure(dev, post);
+ case DRV_OP_PRE_RELEASE:
+ return acpihp_dev_pre_release(dev, &drv_data->cancel_ctx);
+ case DRV_OP_RELEASE:
+ return acpihp_dev_release(dev, &drv_data->cancel_ctx);
+ case DRV_OP_POST_RELEASE:
+ return acpihp_dev_post_release(dev, post);
+ case DRV_OP_PRE_UNCONFIGURE:
+ return acpihp_dev_pre_unconfigure(dev);
+ case DRV_OP_UNCONFIGURE:
+ return acpihp_dev_unconfigure(dev);
+ case DRV_OP_POST_UNCONFIGURE:
+ return acpihp_dev_post_unconfigure(dev);
+ default:
+ BUG_ON(opcode);
+ return -ENOSYS;
+ }
+}
+
+static int acpihp_drv_call_method(enum config_op_code opcode,
+ struct acpihp_slot *slot,
+ enum acpihp_dev_type type,
+ enum acpihp_dev_state state)
+{
+ int result = 0;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+ struct acpi_device *acpi_dev;
+
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ acpi_dev = container_of(np->dev, struct acpi_device, dev);
+
+ result = acpihp_drv_invoke_method(opcode, slot, acpi_dev, 0);
+ if (result) {
+ BUG_ON(opcode == DRV_OP_POST_UNCONFIGURE);
+ break;
+ }
+
+ acpihp_drv_update_dev_state(np, state);
+ }
+ klist_iter_exit(&iter);
+
+ return result;
+}
+
+static int acpihp_drv_call_method_post(enum config_op_code opcode,
+ struct acpihp_slot *slot,
+ enum acpihp_dev_type type,
+ enum acpihp_dev_state state,
+ enum acpihp_dev_post_cmd post)
+{
+ int retval = 0;
+ int result;
+ struct klist_iter iter;
+ struct klist_node *ip;
+ struct acpihp_dev_node *np;
+ struct acpi_device *acpi_dev;
+
+ klist_iter_init(&slot->dev_lists[type], &iter);
+ while ((ip = klist_next(&iter)) != NULL) {
+ np = container_of(ip, struct acpihp_dev_node, node);
+ acpi_dev = container_of(np->dev, struct acpi_device, dev);
+ if (np->state == state && post == ACPIHP_DEV_POST_CMD_ROLLBACK)
+ continue;
+
+ result = acpihp_drv_invoke_method(opcode, slot, acpi_dev, post);
+ if (result)
+ retval = -EIO;
+ else if (post == ACPIHP_DEV_POST_CMD_ROLLBACK)
+ acpihp_drv_update_dev_state(np, state);
+ }
+ klist_iter_exit(&iter);
+
+ return retval;
+}
+
+static int acpihp_drv_walk_devs(struct list_head *slot_list,
+ enum config_op_code opcode,
+ enum acpihp_dev_state state,
+ bool reverse)
+{
+ int i, retval = 0;
+ enum acpihp_dev_type *tp;
+ struct acpihp_slot_dependency *dep;
+ int count = ARRAY_SIZE(acpihp_drv_dev_types);
+
+ tp = reverse ? acpihp_drv_dev_types_reverse : acpihp_drv_dev_types;
+ for (i = 0; i < count; i++)
+ list_for_each_entry(dep, slot_list, node) {
+ retval = acpihp_drv_call_method(opcode, dep->slot,
+ tp[i], state);
+ if (retval)
+ return retval;
+ }
+
+ return 0;
+}
+
+static int acpihp_drv_walk_devs_post(struct list_head *slot_list,
+ enum config_op_code opcode,
+ enum acpihp_dev_state state,
+ enum acpihp_dev_post_cmd post,
+ bool reverse)
+{
+ int i, rv2, retval = 0;
+ enum acpihp_dev_type *tp;
+ struct acpihp_slot_dependency *dep;
+ int count = ARRAY_SIZE(acpihp_drv_dev_types);
+
+ tp = reverse ? acpihp_drv_dev_types_reverse : acpihp_drv_dev_types;
+ for (i = 0; i < count; i++)
+ list_for_each_entry(dep, slot_list, node) {
+ rv2 = acpihp_drv_call_method_post(opcode, dep->slot,
+ tp[i], state, post);
+ if (rv2) {
+ /*
+ * Mark slot as fault and don't touch it
+ * anymore.
+ */
+ acpihp_slot_set_flag(dep->slot,
+ ACPIHP_SLOT_FLAG_FAULT);
+ retval = rv2;
+ }
+ }
+
+ return retval;
+}
+
+static int acpihp_drv_sync_cancel(struct list_head *list, int result,
+ enum acpihp_dev_post_cmd post)
+{
+ int cancel;
+ struct acpihp_slot_dependency *dep;
+
+ if (post == ACPIHP_DEV_POST_CMD_COMMIT)
+ cancel = ACPIHP_DRV_CANCEL_MISSED;
+ else if (result)
+ cancel = ACPIHP_DRV_CANCEL_FAILED;
+ else
+ cancel = ACPIHP_DRV_CANCEL_OK;
+ list_for_each_entry(dep, list, node) {
+ acpihp_drv_cancel_notify(dep->slot, cancel);
+ acpihp_drv_update_slot_state(dep->slot);
+ }
+
+ if (!result && post == ACPIHP_DEV_POST_CMD_ROLLBACK)
+ result = -ECANCELED;
+
+ return result;
+}
+
+/*
+ * To support better error recover and cancellation, configure operations
+ * are splitted into three steps:
+ * 1) pre_configure(): allocate required resources
+ * 2) configure(): add devices into system
+ * 3) pos_configure(): rollback if cancelled or failed to add devices
+ */
+int acpihp_drv_configure(struct list_head *list)
+{
+ int result;
+ struct list_head head;
+ struct acpihp_slot_dependency *dep;
+ enum acpihp_dev_post_cmd post = ACPIHP_DEV_POST_CMD_COMMIT;
+
+ result = acpihp_drv_filter_dependency_list(list, &head,
+ DRV_OP_CONFIGURE);
+ if (result) {
+ ACPIHP_DEBUG("fails to filter dependency list.\n");
+ return -ENOMEM;
+ }
+
+ list_for_each_entry(dep, &head, node)
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_CONFIGURING);
+
+ result = acpihp_drv_walk_devs(&head, DRV_OP_PRE_CONFIGURE,
+ DEVICE_STATE_PRE_CONFIGURE, false);
+ if (!result)
+ result = acpihp_drv_walk_devs(&head, DRV_OP_CONFIGURE,
+ DEVICE_STATE_CONFIGURED, false);
+ if (result)
+ post = ACPIHP_DEV_POST_CMD_ROLLBACK;
+ result = acpihp_drv_walk_devs_post(&head, DRV_OP_POST_CONFIGURE,
+ DEVICE_STATE_CONNECTED, post, false);
+
+ result = acpihp_drv_sync_cancel(&head, result, post);
+ acpihp_drv_destroy_dependency_list(&head);
+
+ return result;
+}
+
+static int acpihp_drv_release(struct list_head *list)
+{
+ int result;
+ enum acpihp_dev_post_cmd post = ACPIHP_DEV_POST_CMD_COMMIT;
+
+ result = acpihp_drv_walk_devs(list, DRV_OP_PRE_RELEASE,
+ DEVICE_STATE_PRE_RELEASE, true);
+ if (!result)
+ result = acpihp_drv_walk_devs(list, DRV_OP_RELEASE,
+ DEVICE_STATE_RELEASED, true);
+ if (result)
+ post = ACPIHP_DEV_POST_CMD_ROLLBACK;
+ result = acpihp_drv_walk_devs_post(list, DRV_OP_POST_RELEASE,
+ DEVICE_STATE_CONFIGURED, post, true);
+
+ return acpihp_drv_sync_cancel(list, result, post);
+}
+
+static void __acpihp_drv_unconfigure(struct list_head *list)
+{
+ int result;
+
+ result = acpihp_drv_walk_devs(list, DRV_OP_PRE_UNCONFIGURE,
+ DEVICE_STATE_PRE_UNCONFIGURE, true);
+ BUG_ON(result);
+ result = acpihp_drv_walk_devs(list, DRV_OP_UNCONFIGURE,
+ DEVICE_STATE_CONNECTED, true);
+ BUG_ON(result);
+ result = acpihp_drv_walk_devs(list, DRV_OP_POST_UNCONFIGURE,
+ DEVICE_STATE_CONNECTED, true);
+ BUG_ON(result);
+}
+
+/*
+ * To support better error recover and cancellation, unconfigure operations
+ * are splitted into three steps:
+ * 1) pre_release(): optional
+ * 2) release(): reclaim devices from system
+ * 3) post_release(): rollback if cancelled or failed to reclaim devices
+ * 4) pre_unconfigure(): optional
+ * 5) unconfigure(): remove devices from system
+ * 6) post_unconfigure(): free resources used by devices
+ */
+int acpihp_drv_unconfigure(struct list_head *list)
+{
+ int result;
+ struct list_head head;
+ struct acpihp_slot_dependency *dep;
+
+ result = acpihp_drv_filter_dependency_list(list, &head,
+ ACPIHP_DRV_CMD_UNCONFIGURE);
+ if (result) {
+ ACPIHP_DEBUG("fails to filter dependency list.\n");
+ return -ENOMEM;
+ }
+
+ list_for_each_entry(dep, &head, node)
+ acpihp_slot_change_state(dep->slot,
+ ACPIHP_SLOT_STATE_UNCONFIGURING);
+
+ result = acpihp_drv_release(&head);
+ if (!result)
+ __acpihp_drv_unconfigure(&head);
+
+ list_for_each_entry(dep, &head, node)
+ acpihp_drv_update_slot_state(dep->slot);
+
+ acpihp_drv_destroy_dependency_list(&head);
+
+ return result;
+}
--
1.7.9.5
Jiang Liu
2012-08-04 12:13:56 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch implements some utility funcitons to support system device hotplug.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Hanjun Guo <***@huawei.com>
---
drivers/acpi/hotplug/core.c | 80 +++++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_hotplug.h | 9 +++++
2 files changed, 89 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index 5e78867..230375e 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -634,6 +634,86 @@ int acpihp_remove_device_list(struct klist *dev_list)
}
EXPORT_SYMBOL_GPL(acpihp_remove_device_list);

+bool acpihp_slot_present(struct acpihp_slot *slot)
+{
+ acpi_status status;
+ unsigned long long sta;
+
+ /* A hotplug slot must implement ACPI _STA method */
+ status = acpi_evaluate_integer(slot->handle,
+ METHOD_NAME__STA, NULL, &sta);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to evaluate _STA for %p.\n", slot->name);
+ return false;
+ }
+
+ return !!(sta & ACPI_STA_DEVICE_PRESENT);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_present);
+
+bool acpihp_slot_powered(struct acpihp_slot *slot)
+{
+ acpi_status status;
+ unsigned long long sta;
+
+ /* hotplug slot must implement _STA method */
+ status = acpi_evaluate_integer(slot->handle,
+ METHOD_NAME__STA, NULL, &sta);
+ if (ACPI_FAILURE(status)) {
+ ACPIHP_WARN("fails to evaluate _STA for %p.\n", slot->name);
+ return false;
+ }
+
+ if ((sta & ACPI_STA_DEVICE_PRESENT) &&
+ ((sta & ACPI_STA_DEVICE_ENABLED) ||
+ (sta & ACPI_STA_DEVICE_FUNCTIONING)))
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_powered);
+
+void acpihp_slot_set_flag(struct acpihp_slot *slot, u32 flags)
+{
+ mutex_lock(&slot->slot_mutex);
+ slot->flags |= flags;
+ mutex_unlock(&slot->slot_mutex);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_set_flag);
+
+void acpihp_slot_clear_flag(struct acpihp_slot *slot, u32 flags)
+{
+ mutex_lock(&slot->slot_mutex);
+ slot->flags &= ~flags;
+ mutex_unlock(&slot->slot_mutex);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_clear_flag);
+
+u32 acpihp_slot_get_flag(struct acpihp_slot *slot, u32 flags)
+{
+ mutex_lock(&slot->slot_mutex);
+ flags &= slot->flags;
+ mutex_unlock(&slot->slot_mutex);
+
+ return flags;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_get_flag);
+
+void acpihp_slot_change_state(struct acpihp_slot *slot,
+ enum acpihp_slot_state state)
+{
+ if (state < ACPIHP_SLOT_STATE_UNKNOWN ||
+ state > ACPIHP_SLOT_STATE_MAX) {
+ ACPIHP_WARN("slot state %d is invalid.\n", state);
+ BUG_ON(state);
+ }
+
+ mutex_lock(&slot->slot_mutex);
+ slot->state = state;
+ mutex_unlock(&slot->slot_mutex);
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_change_state);
+
/* SYSFS interfaces */
static ssize_t acpihp_slot_object_show(struct device *d,
struct device_attribute *attr, char *buf)
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index 1d43079..a13d9f0 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -289,6 +289,15 @@ extern int acpihp_slot_remove_device(struct acpihp_slot *slot,
struct device *dev);
extern int acpihp_remove_device_list(struct klist *dev_list);

+/* Utility Functions */
+extern bool acpihp_slot_present(struct acpihp_slot *slot);
+extern bool acpihp_slot_powered(struct acpihp_slot *slot);
+extern void acpihp_slot_set_flag(struct acpihp_slot *slot, u32 flags);
+extern void acpihp_slot_clear_flag(struct acpihp_slot *slot, u32 flags);
+extern u32 acpihp_slot_get_flag(struct acpihp_slot *slot, u32 flags);
+extern void acpihp_slot_change_state(struct acpihp_slot *slot,
+ enum acpihp_slot_state state);
+
extern int acpihp_debug;

#define ACPIHP_DEBUG(fmt, ...) \
--
1.7.9.5


--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jiang Liu
2012-08-04 12:13:55 UTC
Permalink
From: Jiang Liu <***@huawei.com>

This patch provides interfaces to attach/detach/get class driver specific data
to/from ACPI hotplug slots.

Signed-off-by: Jiang Liu <***@gmail.com>
Signed-off-by: Gaohuai Han <***@huawei.com>
---
drivers/acpi/hotplug/core.c | 88 +++++++++++++++++++++++++++++++++++++++++++
include/acpi/acpi_hotplug.h | 8 ++++
2 files changed, 96 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index 14b3d61..5e78867 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -34,11 +34,18 @@
#include <acpi/acpi_bus.h>
#include <acpi/acpi_hotplug.h>

+struct acpihp_drv_data {
+ struct list_head node;
+ struct class_interface *key;
+ void *data;
+};
+
#define to_acpihp_slot(d) container_of(d, struct acpihp_slot, dev)

extern struct acpi_device *acpi_root;

static DEFINE_MUTEX(acpihp_mutex);
+static DEFINE_MUTEX(acpihp_drvdata_mutex);
static int acpihp_class_count;
static struct kset *acpihp_slot_kset;

@@ -407,6 +414,87 @@ char *acpihp_get_slot_type_name(enum acpihp_slot_type type)
}
EXPORT_SYMBOL_GPL(acpihp_get_slot_type_name);

+int acpihp_slot_attach_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void *data)
+{
+ struct acpihp_drv_data *dp, *cp;
+
+ if (slot == NULL || drv == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return -EINVAL;
+ }
+
+ dp = kzalloc(sizeof(*dp), GFP_KERNEL);
+ if (dp == NULL)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&dp->node);
+ dp->key = drv;
+ dp->data = data;
+
+ mutex_lock(&acpihp_drvdata_mutex);
+ list_for_each_entry(cp, &slot->drvdata_list, node)
+ if (cp->key == drv) {
+ mutex_unlock(&acpihp_drvdata_mutex);
+ kfree(dp);
+ return -EEXIST;
+ }
+ list_add(&dp->node, &slot->drvdata_list);
+ mutex_unlock(&acpihp_drvdata_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_attach_drv_data);
+
+int acpihp_slot_detach_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void **data)
+{
+ struct acpihp_drv_data *cp;
+
+ if (slot == NULL || drv == NULL || data == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&acpihp_drvdata_mutex);
+ list_for_each_entry(cp, &slot->drvdata_list, node)
+ if (cp->key == drv) {
+ list_del(&cp->node);
+ *data = cp->data;
+ mutex_unlock(&acpihp_drvdata_mutex);
+ kfree(cp);
+ return 0;
+ }
+ mutex_unlock(&acpihp_drvdata_mutex);
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_detach_drv_data);
+
+int acpihp_slot_get_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void **data)
+{
+ int ret = -ENOENT;
+ struct acpihp_drv_data *cp;
+
+ if (slot == NULL || drv == NULL || data == NULL) {
+ ACPIHP_DEBUG("invalid parameters.\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&acpihp_drvdata_mutex);
+ list_for_each_entry(cp, &slot->drvdata_list, node)
+ if (cp->key == drv) {
+ *data = cp->data;
+ ret = 0;
+ break;
+ }
+ mutex_unlock(&acpihp_drvdata_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_get_drv_data);
+
/*
* slot_ops should be valid during the life cycle of a slot, so no protection.
*/
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index 2589ccb..1d43079 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -256,6 +256,14 @@ extern acpi_status acpihp_slot_get_capabilities(struct acpihp_slot *slot,
extern acpi_status acpihp_slot_poweron(struct acpihp_slot *slot);
extern acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot);

+/* Help routines to assoicate driver data with hotplug slot devices. */
+extern int acpihp_slot_attach_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void *data);
+extern int acpihp_slot_detach_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void **data);
+extern int acpihp_slot_get_drv_data(struct acpihp_slot *slot,
+ struct class_interface *drv, void **data);
+
/*
* Add devices for ACPI objects connecting to an ACPI hotplug slot,
* and don't cross the hotplug slot boundary.
--
1.7.9.5
Jiang Liu
2012-08-08 15:44:40 UTC
Permalink
Post by Jiang Liu
git://github.com/jiangliu/linux.git acpihp
Modern high-end server may support advanced hotplug features for system
devices, including physical processor, memory board, IO extension board
and/or computer node. The ACPI specifications have provided standard
interfaces between firmware and OS to support device hotplug at runtime.
This patch series provide an ACPI based hotplug framework to support system
device hotplug at runtime, which will replace current existing ACPI device
driver based CPU/memory/CONTAINER hotplug mechanism.
The new ACPI based hotplug framework is modelled after PCI hotplug
Hi Jiang,
It is nice to see such infrastructure work! I have some high-level
questions / comments below. So far, I have only looked at part of the
changes briefly, so please correct me if I missed something.
Hi Toshi,
Thanks for your time to review these patches!
Post by Jiang Liu
1) Provide a mechanism to detect hotplug slots by checking ACPI _EJ0 method,
ACPI PRCT (platform RAS capabilities table) and other platform specific
mechanisms.
Does this mean that hot-plug device must support both hot-add &
hot-delete operations? Some platforms may choose to only support
hot-add operations to increase the resource on-line (since it requires
less effort and Windows does not support hot-remove, either).
This is a good question. By default, the framework detects hotplug slot
by checking _EJ0 method. If a system does support hot-add only components,
some static ACPI tables, like PRCT, may be used to describe hotplug slots
available in the system.

Basically ACPI PRCT table contains tuples of (device type, uid, RAS capabilities).
Post by Jiang Liu
2) Unify the way to enumerate ACPI based hotplug slots. All hotplug slots
will be enumerated by the enumeration driver, instead of by ACPI device
drivers.
It is nice to see redundant ACPI namespace walks removed from the ACPI
drivers. But why do you need to add a new enumerator to create the
acpihp_slot tree, in addition to the current acpi_device tree? I'd
prefer hotplug features to be generally integrated into the current ACPI
core code and data structures, instead of adding a new layer on top of
it.
The idea comes from PCI hotplug framework, which has an concepts of PCI
hotplug slot and PCI device. For system device hotplug, we could follow
the same model as PCI by abstracting control points as slots. By introducing
of hotplug slot, we could:

1) Report all hotplug slots and slot's capabilities to user, no matter whether
there are devices connecting to a slot. If we integrate hotplug functionality
into current ACPI device tree, the slot (or device) is only visible when the
connected devices are enabled.

2) Provide interfaces for software to control hotplug slots. With current ACPI
definition, we could only trigger ACPI hotplug events by pressing hotplug button
or through some OOB device management system. To support RAS features like
memory power management, memory migration, dynamic resource management etc, we
need to trigger hotplug events through in-band interfaces.
Also, acpihp_dev_get_type() in core.c relies on PNP IDs that is embedded
in the file. This does not seem very flexible / extendable. One should
be able to add a handler for a new or vendor-specific PNP ID without
changing the core code. struct acpi_driver allows such extension today.
Good catch. That's a design limitation currently. If the need arise, we could
extend the core to support platform specific extensions. But that may be a
little hard because all devices connecting to a slot will be configured/unconfigured
in order of device types. If we introduce a platform specific device type,
we need to change that logic too.
Post by Jiang Liu
3) Dynamically create/destroy ACPI hotplug slots. For example, new ACPI
hotplug slots may be created when hot-adding a computer node if the node
contains some memory hotplug slots.
This is good, but it is necessary because you added the slots...
Currently all ACPI hotplug drivers has an assumption that the ACPI namespace is
constructed from static ACPI tables, or in other words, the ACPI namespace is
static. Dynamically create/destroy ACPI hotplug slot is to get rid of such an
assumption. If it's unnecessary, we may not support dynamic creating/destroying
of ACPI hotplug slots.
Post by Jiang Liu
4) Unify the way to handle ACPI hotplug events. All ACPI hotplug events
for system devices will be handled by a generic ACPI hotplug driver,
instead of handled by ACPI device drivers.
It seems that acpihp_drv_slot_add() registers an ACPI notify handler
through .add_dev interface. Does it mean that a device must be marked
as present prior to hot-add operation..?
acpihp_drv_slot_add() is called to bind the hotplug driver to a hotplug slot.
So the ACPI hotplug event handler will be installed once the hotplug driver
has been bound to a slot, no matter whether there are devices connecting to
the slot.
Post by Jiang Liu
5) Solve dependencies among hotplug slots. You need first to remove the
memory device before removing a physical processor if a hotpluggable memory
device is connected to a hotpluggable physical processor.
This is nice, but in your example, I'd expect that a container object
(as a node or socket) is used to generally represent such dependency
with topology. Such container object contains processor and memory
devices. When user needs to eject the whole, i.e. both processor and
memory, an eject request can be sent to the container object.
There are two ways to get dependency relationships among hotplug slots. One is
by analyzing ACPI namespace topology, as you have described. The other is by
evaluating ACPI _EDL method. Some dependency relationships must be reported by
ACPI _EDL method because the ACPI namespace topology can't reflect those dependencies.
For examples:
1) To be compatible with Windows, the PCI host bridge (IIO) embedded in a physical
processor may be present under _SB instead under the module device for the physical
processor.

2) For NHM-EX/Boxboro chipset based platform, a Boxboro chipset is connected to
two physical processors. So the Boxboro chipset must be removed first if you want
to remove those two processor together. The ACPI namespace may be something like:
\SB\
|- SCK0
|- SCK1
|- PCI0

3) For a big system with partially connected topology, you may need to use ACPI _EDL
to report dependencies among nodes. An possible topology as below:
Node A <--->Node B
^ ^
| |
v v
Node C <--->Node D

Regards!
Gerry
Bjorn Helgaas
2012-08-08 16:27:07 UTC
Permalink
Post by Jiang Liu
It is nice to see redundant ACPI namespace walks removed from the ACPI
drivers. But why do you need to add a new enumerator to create the
acpihp_slot tree, in addition to the current acpi_device tree? I'd
prefer hotplug features to be generally integrated into the current ACPI
core code and data structures, instead of adding a new layer on top of
it.
The idea comes from PCI hotplug framework, which has an concepts of PCI
hotplug slot and PCI device. For system device hotplug, we could follow
the same model as PCI by abstracting control points as slots. By introducing
1) Report all hotplug slots and slot's capabilities to user, no matter whether
there are devices connecting to a slot. If we integrate hotplug functionality
into current ACPI device tree, the slot (or device) is only visible when the
connected devices are enabled.
In PCI, the idea of a slot is a pretty explicit -- you can look at the
capabilities of a bridge device and see whether it supports hot-add of
a device below it. Is it the same way in ACPI? My impression is that
it is not: there will be a parent ACPI device under which a new device
can be added, but you might not be able to tell by looking at the
parent device that hot-add is possible. I thought the platform could
just give us a Notify event on the parent, asking us to rescan the
namespace below it and potentially discover new devices.
Jiang Liu
2012-08-09 15:24:47 UTC
Permalink
Post by Bjorn Helgaas
Post by Jiang Liu
It is nice to see redundant ACPI namespace walks removed from the ACPI
drivers. But why do you need to add a new enumerator to create the
acpihp_slot tree, in addition to the current acpi_device tree? I'd
prefer hotplug features to be generally integrated into the current ACPI
core code and data structures, instead of adding a new layer on top of
it.
The idea comes from PCI hotplug framework, which has an concepts of PCI
hotplug slot and PCI device. For system device hotplug, we could follow
the same model as PCI by abstracting control points as slots. By introducing
1) Report all hotplug slots and slot's capabilities to user, no matter whether
there are devices connecting to a slot. If we integrate hotplug functionality
into current ACPI device tree, the slot (or device) is only visible when the
connected devices are enabled.
In PCI, the idea of a slot is a pretty explicit -- you can look at the
capabilities of a bridge device and see whether it supports hot-add of
a device below it. Is it the same way in ACPI? My impression is that
it is not: there will be a parent ACPI device under which a new device
can be added, but you might not be able to tell by looking at the
parent device that hot-add is possible. I thought the platform could
just give us a Notify event on the parent, asking us to rescan the
namespace below it and potentially discover new devices.
Hi Bjorn,
You are right. With current ACPI V5 specification, we can't get the slot
information from the ACPI namespace, and could only guess that a device with _EJ0
supports hot-add/hot-remove.
Realized that limitation, there's ongoing effort to provide ACPI hotplug
slot (or platform RAS capabilities) information to OS through static ACPI tables,
so OS could enable hotplug and reserve enough resources for system device hotplug.
If that static ACPI table is available, we could construct ACPI hotplug slots from
it.
Regards!
Gerry
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Toshi Kani
2012-08-08 21:05:16 UTC
Permalink
Post by Jiang Liu
Post by Jiang Liu
git://github.com/jiangliu/linux.git acpihp
Modern high-end server may support advanced hotplug features for system
devices, including physical processor, memory board, IO extension board
and/or computer node. The ACPI specifications have provided standard
interfaces between firmware and OS to support device hotplug at runtime.
This patch series provide an ACPI based hotplug framework to support system
device hotplug at runtime, which will replace current existing ACPI device
driver based CPU/memory/CONTAINER hotplug mechanism.
The new ACPI based hotplug framework is modelled after PCI hotplug
Hi Jiang,
It is nice to see such infrastructure work! I have some high-level
questions / comments below. So far, I have only looked at part of the
changes briefly, so please correct me if I missed something.
Hi Toshi,
Thanks for your time to review these patches!
Post by Jiang Liu
1) Provide a mechanism to detect hotplug slots by checking ACPI _EJ0 method,
ACPI PRCT (platform RAS capabilities table) and other platform specific
mechanisms.
Does this mean that hot-plug device must support both hot-add &
hot-delete operations? Some platforms may choose to only support
hot-add operations to increase the resource on-line (since it requires
less effort and Windows does not support hot-remove, either).
This is a good question. By default, the framework detects hotplug slot
by checking _EJ0 method. If a system does support hot-add only components,
some static ACPI tables, like PRCT, may be used to describe hotplug slots
available in the system.
Basically ACPI PRCT table contains tuples of (device type, uid, RAS capabilities).
I am not familiar with the PRCT table. Can you point me to the spec? I
did not find it in the ACPI spec.
Post by Jiang Liu
Post by Jiang Liu
2) Unify the way to enumerate ACPI based hotplug slots. All hotplug slots
will be enumerated by the enumeration driver, instead of by ACPI device
drivers.
It is nice to see redundant ACPI namespace walks removed from the ACPI
drivers. But why do you need to add a new enumerator to create the
acpihp_slot tree, in addition to the current acpi_device tree? I'd
prefer hotplug features to be generally integrated into the current ACPI
core code and data structures, instead of adding a new layer on top of
it.
The idea comes from PCI hotplug framework, which has an concepts of PCI
hotplug slot and PCI device. For system device hotplug, we could follow
the same model as PCI by abstracting control points as slots. By introducing
Yes, I understand that. Using the slot concept on PCI hotplug makes
sense as it has PCI slot objects in ACPI. For non-PCI, however, you are
kind of faking slots and introducing a new slot tree on top of the
existing ACPI device tree, so I am not sure if it is a good idea...
Post by Jiang Liu
1) Report all hotplug slots and slot's capabilities to user, no matter whether
there are devices connecting to a slot. If we integrate hotplug functionality
into current ACPI device tree, the slot (or device) is only visible when the
connected devices are enabled.
We need to think about both physical and virtual machines. Devices can
be virtualized and there can be many of them. For example, let's say,
1TB of physical memory is logically sliced up with 1024 * 1GB memory
objects for guests, so that they can add / delete memory by 1GB. If a
guest has only 2GB of memory assigned, it has other 1022 devices
disabled. In this case, showing 1022 empty memory slots may not be very
helpful for users.
Post by Jiang Liu
2) Provide interfaces for software to control hotplug slots. With current ACPI
definition, we could only trigger ACPI hotplug events by pressing hotplug button
or through some OOB device management system. To support RAS features like
memory power management, memory migration, dynamic resource management etc, we
need to trigger hotplug events through in-band interfaces.
acpi_device objects also have sysfs entries. Can they be used for such
in-bound interfaces?
Post by Jiang Liu
Also, acpihp_dev_get_type() in core.c relies on PNP IDs that is embedded
in the file. This does not seem very flexible / extendable. One should
be able to add a handler for a new or vendor-specific PNP ID without
changing the core code. struct acpi_driver allows such extension today.
Good catch. That's a design limitation currently. If the need arise, we could
extend the core to support platform specific extensions. But that may be a
little hard because all devices connecting to a slot will be configured/unconfigured
in order of device types. If we introduce a platform specific device type,
we need to change that logic too.
Right. This design does not allow someone to support a new device with
a loadable module without recompiling the kernel. This may be a problem
in some case.
Post by Jiang Liu
Post by Jiang Liu
3) Dynamically create/destroy ACPI hotplug slots. For example, new ACPI
hotplug slots may be created when hot-adding a computer node if the node
contains some memory hotplug slots.
This is good, but it is necessary because you added the slots...
Currently all ACPI hotplug drivers has an assumption that the ACPI namespace is
constructed from static ACPI tables, or in other words, the ACPI namespace is
static. Dynamically create/destroy ACPI hotplug slot is to get rid of such an
assumption. If it's unnecessary, we may not support dynamic creating/destroying
of ACPI hotplug slots.
Oh, I see. I agree that we need to support dynamic ACPI namespace. I
was confused since you stated this goal as the slot object being
dynamic.
Post by Jiang Liu
Post by Jiang Liu
4) Unify the way to handle ACPI hotplug events. All ACPI hotplug events
for system devices will be handled by a generic ACPI hotplug driver,
instead of handled by ACPI device drivers.
It seems that acpihp_drv_slot_add() registers an ACPI notify handler
through .add_dev interface. Does it mean that a device must be marked
as present prior to hot-add operation..?
acpihp_drv_slot_add() is called to bind the hotplug driver to a hotplug slot.
So the ACPI hotplug event handler will be installed once the hotplug driver
has been bound to a slot, no matter whether there are devices connecting to
the slot.
acpi_bus_check_add() simply returns if a device is marked as !present
& !functioning. So, I thought acpihp_drv_slot_add() won't be called in
this case.
Post by Jiang Liu
Post by Jiang Liu
5) Solve dependencies among hotplug slots. You need first to remove the
memory device before removing a physical processor if a hotpluggable memory
device is connected to a hotpluggable physical processor.
This is nice, but in your example, I'd expect that a container object
(as a node or socket) is used to generally represent such dependency
with topology. Such container object contains processor and memory
devices. When user needs to eject the whole, i.e. both processor and
memory, an eject request can be sent to the container object.
There are two ways to get dependency relationships among hotplug slots. One is
by analyzing ACPI namespace topology, as you have described. The other is by
evaluating ACPI _EDL method. Some dependency relationships must be reported by
ACPI _EDL method because the ACPI namespace topology can't reflect those dependencies.
1) To be compatible with Windows, the PCI host bridge (IIO) embedded in a physical
processor may be present under _SB instead under the module device for the physical
processor.
I believe the current version of Windows supports the module device
object. I remember there was such issue in the past, though. I am not
against of supporting _EDL, but it would be nice if we can avoid
additional complexity.
Post by Jiang Liu
2) For NHM-EX/Boxboro chipset based platform, a Boxboro chipset is connected to
two physical processors. So the Boxboro chipset must be removed first if you want
\SB\
|- SCK0
|- SCK1
|- PCI0
You should be able to use a module object containing SCK0, SCK1 and PCI0
in this case.
Post by Jiang Liu
3) For a big system with partially connected topology, you may need to use ACPI _EDL
Node A <--->Node B
^ ^
| |
v v
Node C <--->Node D
If the arrows represent their dependency, it seems a circular dependency
to me. I am not sure how _EDL can address this case.

Thanks,
-Toshi
Post by Jiang Liu
Regards!
Gerry
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Loading...