linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver
@ 2012-11-04 12:50 Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 01/13] ACPIHP: introduce interfaces to scan and walk ACPI devices attached to a slot Jiang Liu
                   ` (12 more replies)
  0 siblings, 13 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

This is the second part of the new ACPI based system device hotplug
framework, which implements the ACPI based system device hotplug
driver (acpihp_drv). For an introduction of the new framework,
please refer to:
https://lkml.org/lkml/2012/11/3/143
https://github.com/downloads/jiangliu/linux/ACPI%20Based%20System%20Device%20Dynamic%20Reconfiguration.pdf

And you may pull from:
https://github.com/jiangliu/linux.git acpihp_drv

The hotplug driver provides following features:
1) Configure/unconfigure affected system devices in optimal order
2) Provide sysfs interfaces for user to trigger hotplug operations
3) Provide interface to cancel ongoing hotplug opertions
4) Resolve dependencies among hotplug slots
5) Better error handling and recovery

This patch set implements the core of the new ACPI hotplug framework,
a state machine for ACPI hotplug slots. The state machine is:

       (plug in)     (power on)    (connect)      (configure)
 [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED]
       (plug out)   (power off)   (disconnect)   (unconfigure)

[...]: state
(...): action
(connect): create ACPI devices and bind ACPI device drivers
(disconnect): unbind ACPI device drivers and destroy ACPI devices
(configure): allocate resources and put system devices into working
(unconfigure): stop system devices from working and free resources

It depends on the ACPI hotplug slot enumeration driver to control each
slot in platform specific ways, and also depends on ACPI device drivers
for processor, memory, PCI host bridge and container to configure/
unconfigure each system device.

For example, Intel Emerald Ridge/Quantum S4R platform has
1) 4 hotpluggable physical processors
2) 8 hotpluggable memory boards (each processor has two memory boards)
3) 1 hotpluggable IOH and 1 non-hotpluggable legacy IOH
Following command sequence shows how to hot-remove and then hot-add
a physical processor with two memory boards attached to it.

Intel-server:~ # cd /sys/devices/LNXSYSTM\:00/acpihp/CPU03/
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # lscpu
......
CPU socket(s):         4
NUMA node(s):          4
......
NUMA node0 CPU(s):     0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76
NUMA node1 CPU(s):     2,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,66,70,74,78
NUMA node2 CPU(s):     1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77
NUMA node3 CPU(s):     3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # free
             total       used       free     shared    buffers     cached
Mem:      57507896     506988   57000908          0       7520     151604
-/+ buffers/cache:     347864   57160032
Swap:      2096124          0    2096124
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # echo disconnect > control
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # lscpu
......
CPU socket(s):         3
NUMA node(s):          3
......
NUMA node0 CPU(s):     0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76
NUMA node1 CPU(s):     2,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,66,70,74,78
NUMA node2 CPU(s):     1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # free
             total       used       free     shared    buffers     cached
Mem:      40730680     419024   40311656          0       7648     144020
-/+ buffers/cache:     267356   40463324
Swap:      2096124          0    2096124
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # echo configure > control
Intel-server:/sys/devices/LNXSYSTM:00/acpihp/CPU03 # lscpu
......
CPU socket(s):         4
NUMA node(s):          4
Vendor ID:             GenuineIntel
......
NUMA node0 CPU(s):     0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76
NUMA node1 CPU(s):     2,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,66,70,74,78
NUMA node2 CPU(s):     1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77
NUMA node3 CPU(s):     3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79

And following patch sets will enhance ACPI container, processor, memory
and PCI host bridge drivers to support the new hotplug framework.

Jiang Liu (13):
  ACPIHP: introduce interfaces to scan and walk ACPI devices attached
    to a slot
  ACPIHP: use klist to manage ACPI devices attached to a slot
  ACPIHP: add callbacks into acpi_device_ops to support new hotplug
    framework
  ACPIHP: provide interfaces to manage driver data associated with
    hotplug slots
  ACPIHP: implement utility interfaces to support system device hotplug
  ACPIHP: implement ACPI system device hotplug driver skeleton
  ACPIHP: analyse dependencies among ACPI hotplug slots
  ACPIHP: provide interface to cancel inprogress hotplug operations
  ACPIHP: configure/unconfigure system devices attached to a hotplug
    slot
  ACPIHP: implement the core state machine to manage hotplug slots
  ACPIHP: block ACPI device driver from unloading when doing hotplug
  ACPIHP: implement sysfs interfaces for system device hotplug
  ACPIHP: handle ACPI device hotplug events

 drivers/acpi/Kconfig                 |   15 +
 drivers/acpi/hotplug/Makefile        |   11 +-
 drivers/acpi/hotplug/acpihp.h        |    1 +
 drivers/acpi/hotplug/acpihp_drv.h    |  100 ++++++
 drivers/acpi/hotplug/cancel.c        |  174 +++++++++
 drivers/acpi/hotplug/configure.c     |  355 +++++++++++++++++++
 drivers/acpi/hotplug/core.c          |  281 +++++++++++++++
 drivers/acpi/hotplug/dependency.c    |  245 +++++++++++++
 drivers/acpi/hotplug/device.c        |  208 +++++++++++
 drivers/acpi/hotplug/drv_main.c      |  343 ++++++++++++++++++
 drivers/acpi/hotplug/event.c         |  163 +++++++++
 drivers/acpi/hotplug/state_machine.c |  639 ++++++++++++++++++++++++++++++++++
 drivers/acpi/hotplug/sysfs.c         |  181 ++++++++++
 drivers/acpi/internal.h              |    3 +
 drivers/acpi/scan.c                  |   12 +-
 include/acpi/acpi_bus.h              |    5 +
 include/acpi/acpi_hotplug.h          |  121 +++++++
 17 files changed, 2854 insertions(+), 3 deletions(-)
 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/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/event.c
 create mode 100644 drivers/acpi/hotplug/state_machine.c
 create mode 100644 drivers/acpi/hotplug/sysfs.c

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 01/13] ACPIHP: introduce interfaces to scan and walk ACPI devices attached to a slot
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 02/13] ACPIHP: use klist to manage " Jiang Liu
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm, Gaohuai Han

This patch enhances acpi_bus_scan() to implement acpihp_add_devices(),
which creates ACPI devices for hot-added system devices attached to
an ACPI hotplug slot, but don't cross the slot boundary.

It also introduces a new interface to walk all ACPI devices attached
to an ACPI hotplug slot.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
Signed-off-by: Gaohuai Han <hangaohuai@huawei.com>
---
 drivers/acpi/hotplug/Makefile |    2 +-
 drivers/acpi/hotplug/acpihp.h |    1 +
 drivers/acpi/hotplug/device.c |  121 +++++++++++++++++++++++++++++++++++++++++
 drivers/acpi/internal.h       |    3 +
 drivers/acpi/scan.c           |   12 +++-
 include/acpi/acpi_bus.h       |    2 +
 include/acpi/acpi_hotplug.h   |   15 +++++
 7 files changed, 153 insertions(+), 3 deletions(-)
 create mode 100644 drivers/acpi/hotplug/device.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index c19b350..6e5daf6 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_SLOT)			+= acpihp_slot.o
 acpihp_slot-y					= slot.o
diff --git a/drivers/acpi/hotplug/acpihp.h b/drivers/acpi/hotplug/acpihp.h
index 7c49eab..54d9d95 100644
--- a/drivers/acpi/hotplug/acpihp.h
+++ b/drivers/acpi/hotplug/acpihp.h
@@ -26,6 +26,7 @@
 #include <acpi/acpi.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_hotplug.h>
+#include "../internal.h"
 
 extern struct acpi_device *acpi_root;
 extern struct acpihp_slot_ops acpihp_slot_ej0;
diff --git a/drivers/acpi/hotplug/device.c b/drivers/acpi/hotplug/device.c
new file mode 100644
index 0000000..2dcdd83
--- /dev/null
+++ b/drivers/acpi/hotplug/device.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <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 "acpihp.h"
+
+/*
+ * When creating ACPI devices for hot-added system devices connecting to
+ * a slot, don't cross the slot boundary. Otherwise it will cause
+ * inconsistence to other hotplug 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_is_slot(hdl))
+		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 = &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;
+
+	if (dev->bus != &acpi_bus_type)
+		return 0;
+
+	/* Skip if the handle corresponds to a child hotplug slot. */
+	acpi_dev = container_of(dev, struct acpi_device, dev);
+	if (acpihp_is_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_SKIP) {
+		return 0;
+	} else {
+		argp->status = status;
+		return -1;
+	}
+}
+
+/*
+ * Walk all ACPI devices connecting to a hotplug slot, but 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);
+
+	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 ca75b9c..d2c4d83 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
 
+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 1fcb867..f621333 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1477,6 +1477,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) {
+		result = ops->acpi_op_filter(handle, lvl,
+					     ops->acpi_op_filter_arg);
+		if (ACPI_FAILURE(result))
+			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.
@@ -1500,8 +1508,8 @@ 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,
-			 struct acpi_device **child)
+int acpi_bus_scan(acpi_handle handle, struct acpi_bus_ops *ops,
+		  struct acpi_device **child)
 {
 	acpi_status status;
 	void *device = NULL;
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 0daa0fb..361a5ea 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -98,6 +98,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)(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 298f679..d39dece 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -183,6 +183,21 @@ extern acpi_status acpihp_slot_get_status(struct acpihp_slot *slot,
 extern acpi_status acpihp_slot_poweron(struct acpihp_slot *slot);
 extern acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot);
 
+/*
+ * Scan and create ACPI device objects for devices attached to the handle,
+ * but 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 device objects attached to an ACPI hotplug slot,
+ * but 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_WARN(fmt, ...) \
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 02/13] ACPIHP: use klist to manage ACPI devices attached to a slot
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 01/13] ACPIHP: introduce interfaces to scan and walk ACPI devices attached to a slot Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 03/13] ACPIHP: add callbacks into acpi_device_ops to support new hotplug framework Jiang Liu
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

To achieve best performance for hot-adding and resolve dependencies when
hot-removing, system devices should be configured/unconfigured in
specific order. The optimal order for hot-adding should be "container ->
memory -> CPU -> PCI host bridge" and it should be in reverse order for
hot-removing.

So classify system devices into groups according to types of devices,
and use klist to manage devices belonging to the same group.

Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
---
 drivers/acpi/hotplug/core.c |  116 +++++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpi_hotplug.h |   26 ++++++++++
 2 files changed, 142 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index c835a97..7ef8f9b 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -96,6 +96,23 @@ static char *acpihp_dev_pcihb_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);
@@ -113,6 +130,7 @@ static void acpihp_slot_release(struct device *dev)
  */
 struct acpihp_slot *acpihp_alloc_slot(acpi_handle handle, char *name)
 {
+	int i;
 	struct acpihp_slot *slot;
 
 	if (name && strlen(name) >= ACPIHP_SLOT_NAME_MAX_SIZE) {
@@ -129,6 +147,9 @@ struct acpihp_slot *acpihp_alloc_slot(acpi_handle handle, char *name)
 	slot->handle = handle;
 	INIT_LIST_HEAD(&slot->slot_list);
 	INIT_LIST_HEAD(&slot->drvdata_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);
 	if (name)
 		strncpy(slot->name, name, sizeof(slot->name) - 1);
 	mutex_init(&slot->slot_mutex);
@@ -387,6 +408,101 @@ acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot)
 }
 EXPORT_SYMBOL_GPL(acpihp_slot_poweroff);
 
+/* Insert an ACPI device onto a hotplug slot's device list. */
+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 (slot == NULL) {
+		ACPIHP_DEBUG("invalid parameter, slot is NULL.\n");
+		return -EINVAL;
+	} else if (dev == NULL) {
+		ACPIHP_SLOT_DEBUG(slot, "invalid parameter, dev is NULL.\n");
+		return -EINVAL;
+	} else if (type < ACPIHP_DEV_TYPE_UNKNOWN ||
+		   type >= ACPIHP_DEV_TYPE_MAX) {
+		ACPIHP_SLOT_DEBUG(slot, "device type %d is invalid.\n", type);
+		return -EINVAL;
+	}
+
+	np = kzalloc(sizeof(*np), GFP_KERNEL);
+	if (np == NULL) {
+		ACPIHP_SLOT_WARN(slot, "fails to allocate memory.\n");
+		return -ENOMEM;
+	}
+
+	np->dev = dev;
+	np->state = state;
+	klist_add_tail(&np->node, &slot->dev_lists[type]);
+	ACPIHP_SLOT_DEBUG(slot, "add device %s to klist.\n", dev_name(dev));
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_add_device);
+
+/* Remove an ACPI device from a hotplug slot's device list. */
+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 (slot == NULL) {
+		ACPIHP_DEBUG("invalid parameter, slot is NULL.\n");
+		return -EINVAL;
+	} else if (dev == NULL) {
+		ACPIHP_SLOT_DEBUG(slot, "invalid parameter, dev is NULL.\n");
+		return -EINVAL;
+	} else if (type < ACPIHP_DEV_TYPE_UNKNOWN ||
+		   type >= ACPIHP_DEV_TYPE_MAX) {
+		ACPIHP_SLOT_DEBUG(slot, "device type %d is invalid.\n", type);
+		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_SLOT_DEBUG(slot,
+					  "remove device %s from klist.\n",
+					  dev_name(dev));
+			klist_del(&np->node);
+			ret = 0;
+			break;
+		}
+	}
+	klist_iter_exit(&iter);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_slot_remove_device);
+
+/* Remove all ACPI devices from the list */
+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 d39dece..d733f7f 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -46,6 +46,23 @@ 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 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.
@@ -198,6 +215,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_WARN(fmt, ...) \
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 03/13] ACPIHP: add callbacks into acpi_device_ops to support new hotplug framework
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 01/13] ACPIHP: introduce interfaces to scan and walk ACPI devices attached to a slot Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 02/13] ACPIHP: use klist to manage " Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 04/13] ACPIHP: provide interfaces to manage driver data associated with hotplug slots Jiang Liu
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm, Gaohuai Han

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

There are three major operations and each major operation is divided
into three minor steps.
1) pre_configure, configure, post_configure
	Add an ACPI device into running system and rollback if error
	happens or has been cancelled.
2) pre_release, release, post_release
	Reclaim an ACPI device from running system and rollback if error
	happens or has been cancelled. It's very important to privode a
	mechanism to cancel ongoing 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 also another callback to query status and information about an
ACPI device.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
Signed-off-by: Gaohuai Han <hangaohuai@huawei.com>
---
 drivers/acpi/hotplug/device.c |   93 +++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpi_bus.h       |    3 ++
 include/acpi/acpi_hotplug.h   |   64 ++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+)

diff --git a/drivers/acpi/hotplug/device.c b/drivers/acpi/hotplug/device.c
index 2dcdd83..c9d550f 100644
--- a/drivers/acpi/hotplug/device.c
+++ b/drivers/acpi/hotplug/device.c
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2012 Huawei Tech. Co., Ltd.
  * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@huawei.com>
  *
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  *
@@ -27,6 +28,98 @@
 #include <acpi/acpi_hotplug.h>
 #include "acpihp.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);
+
+	device_lock(&device->dev);
+	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;
+	device_unlock(&device->dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(acpihp_dev_get_info);
+
+#define	ACPIHP_DEFINE_FUNC1(method, def, err, type) \
+int acpihp_dev_##method(struct acpi_device *device, type val) \
+{ \
+	int ret; \
+	BUG_ON(device == NULL); \
+	device_lock(&device->dev); \
+	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); \
+	device_unlock(&device->dev); \
+	return ret; \
+} \
+EXPORT_SYMBOL_GPL(acpihp_dev_##method)
+
+#define	ACPIHP_DEFINE_FUNC2(method, def, err, type) \
+int acpihp_dev_##method(struct acpi_device *device, type val) \
+{ \
+	int ret = 0; \
+	BUG_ON(device == NULL); \
+	device_lock(&device->dev); \
+	if (!device->driver || !device->driver->ops.hp_ops) \
+		ret = (err); \
+	else if (!device->driver->ops.hp_ops->method) \
+		ret = (def); \
+	else \
+		device->driver->ops.hp_ops->method(device, val); \
+	device_unlock(&device->dev); \
+	return ret; \
+} \
+EXPORT_SYMBOL_GPL(acpihp_dev_##method)
+
+#define	ACPIHP_DEFINE_FUNC3(method, def, err) \
+int acpihp_dev_##method(struct acpi_device *device) \
+{ \
+	int ret = 0; \
+	BUG_ON(device == NULL); \
+	device_lock(&device->dev); \
+	if (!device->driver || !device->driver->ops.hp_ops) \
+		ret = (err); \
+	else if (!device->driver->ops.hp_ops->method) \
+		ret = (def); \
+	else \
+		device->driver->ops.hp_ops->method(device);\
+	device_unlock(&device->dev); \
+	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_FUNC2(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_FUNC2(post_release, 0, 0, enum acpihp_dev_post_cmd);
+
+ACPIHP_DEFINE_FUNC3(pre_unconfigure, 0, 0);
+ACPIHP_DEFINE_FUNC3(unconfigure, 0, -ENOSYS);
+ACPIHP_DEFINE_FUNC3(post_unconfigure, 0, 0);
+
 /*
  * When creating ACPI devices for hot-added system devices connecting to
  * a slot, don't cross the slot boundary. Otherwise it will cause
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 361a5ea..7c15521 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -109,6 +109,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 d733f7f..35f15a8 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -63,6 +63,51 @@ struct acpihp_dev_node {
 	struct klist_node	node;
 };
 
+/* Status of ACPI 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 state */
+
+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 should implement
+ * configure(), unconfigure() and get_info() at a minimal.
+ */
+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 *);
+	void (*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 *);
+	void (*post_release)(struct acpi_device *, enum acpihp_dev_post_cmd);
+	void (*pre_unconfigure)(struct acpi_device *);
+	void (*unconfigure)(struct acpi_device *);
+	void (*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.
@@ -173,6 +218,25 @@ extern int acpihp_core_init(void);
 /* Deinitialize the ACPI based system device hotplug core logic */
 extern void acpihp_core_fini(void);
 
+/* Interfaces to invoke ACPI device driver's hotplug callbacks. */
+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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 04/13] ACPIHP: provide interfaces to manage driver data associated with hotplug slots
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (2 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 03/13] ACPIHP: add callbacks into acpi_device_ops to support new hotplug framework Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 05/13] ACPIHP: implement utility interfaces to support system device hotplug Jiang Liu
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm, Gaohuai Han

This patch implements interfaces to manage driver data associated with
ACPI hotplug slots.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Gaohuai Han <hangaohuai@huawei.com>
---
 drivers/acpi/hotplug/core.c |   88 +++++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpi_hotplug.h |    7 ++++
 2 files changed, 95 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index 7ef8f9b..bad8e99 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -35,9 +35,16 @@
 #include <acpi/acpi_hotplug.h>
 #include "acpihp.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)
 
 static DEFINE_MUTEX(acpihp_mutex);
+static DEFINE_MUTEX(acpihp_drvdata_mutex);
 static int acpihp_class_count;
 static struct kset *acpihp_slot_kset;
 
@@ -355,6 +362,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);
+
 acpi_status acpihp_slot_get_status(struct acpihp_slot *slot, u64 *status)
 {
 	acpi_status rc;
diff --git a/include/acpi/acpi_hotplug.h b/include/acpi/acpi_hotplug.h
index 35f15a8..9d466ea 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -264,6 +264,13 @@ extern acpi_status acpihp_slot_get_status(struct acpihp_slot *slot,
 extern acpi_status acpihp_slot_poweron(struct acpihp_slot *slot);
 extern acpi_status acpihp_slot_poweroff(struct acpihp_slot *slot);
 
+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);
+
 /*
  * Scan and create ACPI device objects for devices attached to the handle,
  * but don't cross the hotplug slot boundary.
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 05/13] ACPIHP: implement utility interfaces to support system device hotplug
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (3 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 04/13] ACPIHP: provide interfaces to manage driver data associated with hotplug slots Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 06/13] ACPIHP: implement ACPI system device hotplug driver skeleton Jiang Liu
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

This patch implements some utility interfaces to support ACPI based
system device hotplug.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/core.c |   77 +++++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpi_hotplug.h |    9 +++++
 2 files changed, 86 insertions(+)

diff --git a/drivers/acpi/hotplug/core.c b/drivers/acpi/hotplug/core.c
index bad8e99..139b9c2 100644
--- a/drivers/acpi/hotplug/core.c
+++ b/drivers/acpi/hotplug/core.c
@@ -591,6 +591,83 @@ 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;
+
+	status = acpihp_slot_get_status(slot, &sta);
+	if (ACPI_FAILURE(status)) {
+		ACPIHP_SLOT_WARN(slot, "fails to get status.\n");
+		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 = acpihp_slot_get_status(slot, &sta);
+	if (ACPI_FAILURE(status)) {
+		ACPIHP_SLOT_WARN(slot, "fails to get status.\n");
+		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_SLOT_WARN(slot, "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 9d466ea..3297f51 100644
--- a/include/acpi/acpi_hotplug.h
+++ b/include/acpi/acpi_hotplug.h
@@ -295,6 +295,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 Interfaces */
+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_WARN(fmt, ...) \
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 06/13] ACPIHP: implement ACPI system device hotplug driver skeleton
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (4 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 05/13] ACPIHP: implement utility interfaces to support system device hotplug Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 07/13] ACPIHP: analyse dependencies among ACPI hotplug slots Jiang Liu
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

The ACPI based system device hotplug driver is a platform independent
driver to manage all ACPI hotplug slots. It implements a state machine
for hotplug slots and drives state transition according to hotplug
event from firmware or user request from sysfs interfaces.

The hotplug driver will provides following features:
1) Configure/unconfigure affected system devices in optimal order
2) Provide sysfs interfaces for user to trigger hotplug operations
3) Provide interface to cancel ongoing hotplug opertions
4) Resolve dependencies among hotplug slots
5) Better error handling and recovery

The driver depends on the ACPI hotplug slot enumeration driver to
control each slot in platform specific ways, and also depends on
ACPI device drivers for processor, memory, PCI host bridge and
container to configure/unconfigure each system device.

This patch implements the skeleton of the hotplug driver, and following
patches will fulfill all hotplug functionalities.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/Kconfig              |   15 ++
 drivers/acpi/hotplug/Makefile     |    3 +
 drivers/acpi/hotplug/acpihp_drv.h |   38 ++++
 drivers/acpi/hotplug/drv_main.c   |  344 +++++++++++++++++++++++++++++++++++++
 4 files changed, 400 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 4d15b49..185ab1d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -363,6 +363,21 @@ config ACPI_HOTPLUG_SLOT_FAKE
 
 	  Pass parameter "fake_slot=0xf" to enable this function.
 
+config ACPI_HOTPLUG_DRIVER
+	tristate "System Device Hotplug Manager"
+	depends on ACPI_HOTPLUG
+	default m
+	help
+	  This driver implements a framework to manage system device hotplug
+	  slots, which could be used to support hotplug of processor, memory,
+	  PCI host bridge and computner node etc.
+
+	  It depends on ACPI container, processor, memory and PCI host bridge
+	  drivers to configure/unconfigure individual ACPI devices.
+
+	  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 6e5daf6..6257047 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -9,3 +9,6 @@ obj-$(CONFIG_ACPI_HOTPLUG_SLOT)			+= acpihp_slot.o
 acpihp_slot-y					= slot.o
 acpihp_slot-y					+= slot_ej0.o
 acpihp_slot-$(CONFIG_ACPI_HOTPLUG_SLOT_FAKE)	+= slot_fake.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..769ee74
--- /dev/null
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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__
+
+struct acpihp_slot_drv {
+	struct mutex		op_mutex;
+};
+
+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..8ab298a
--- /dev/null
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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 status 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;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	for (type = ACPIHP_DEV_TYPE_CONTAINER;
+	     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_SLOT_DEBUG(slot,
+					"fails to get info about %s.\n",
+					dev_name(&dev->dev));
+				klist_iter_exit(&iter);
+				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);
+		}
+		klist_iter_exit(&iter);
+	}
+
+out:
+	kfree(info);
+
+	return ret;
+}
+EXPORT_SYMBOL(acpihp_drv_update_slot_status);
+
+/* Add ACPI device onto 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_ERROR;
+	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_SLOT_DEBUG(slot, "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_SLOT_DEBUG(slot, "fails to add device %s.\n",
+					  dev_name(&dev->dev));
+			acpihp_slot_set_flag(slot,
+					     ACPIHP_SLOT_FLAG_IRREMOVABLE);
+		} else
+			rv = AE_OK;
+	} else {
+		ACPIHP_SLOT_DEBUG(slot, "fails to query device info of %s.\n",
+				  dev_name(&dev->dev));
+		acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE);
+	}
+
+	kfree(info);
+
+	return rv;
+}
+
+/*
+ * Enumerate all ACPI devices attached to the 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]);
+}
+
+/* Handle ACPI device hotplug notifications */
+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_SLOT_DEBUG(slot, "%s to install event handler.\n",
+			  ACPI_SUCCESS(status) ? "succeeds" : "fails");
+
+	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_SLOT_DEBUG(slot, "%s to uninstall event handler.\n",
+			  ACPI_SUCCESS(status) ? "succeeds" : "fails");
+}
+
+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_SLOT_DEBUG(slot,
+				  "fails to get reference to slot_ops.\n");
+		return -EINVAL;
+	}
+
+	/* Install ACPI event notification handler */
+	if (ACPI_FAILURE(acpihp_drv_install_handler(slot))) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to install event handler.\n");
+		module_put(slot->slot_ops->owner);
+		return -EBUSY;
+	}
+
+	/*
+	 * Enumerate all ACPI devices attached to the hotplug slot if
+	 * it has been already powered.
+	 */
+	if (!acpihp_slot_powered(slot))
+		ACPIHP_SLOT_DEBUG(slot, "is powered off.\n");
+	else if (acpihp_drv_enumerate_devices(slot))
+		acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE);
+
+	acpihp_drv_update_slot_state(slot);
+	acpihp_drv_update_slot_status(slot);
+
+	drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+	if (drv_data) {
+		mutex_init(&drv_data->op_mutex);
+	}
+	if (drv_data == NULL ||
+	    acpihp_slot_attach_drv_data(slot, intf, (void *)drv_data)) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to attach driver data.\n");
+		acpihp_drv_remove_devices(slot);
+		module_put(slot->slot_ops->owner);
+		kfree(drv_data);
+		return -ENOMEM;
+	}
+
+	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_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);
+}
+
+/*
+ * Class driver to bound to ACPI system device hotplug slot devices.
+ */
+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)
+{
+	int retval;
+
+	retval = acpihp_core_init();
+	if (retval) {
+		ACPIHP_DEBUG("fails to initialize ACPIHP core.\n");
+		return retval;
+	}
+
+	retval = class_interface_register(&acpihp_drv_interface);
+	if (retval) {
+		ACPIHP_DEBUG("fails to register ACPI hotplug slot driver.\n");
+		acpihp_core_fini();
+	}
+
+	return retval;
+}
+
+static void __exit acpihp_drv_exit(void)
+{
+	class_interface_unregister(&acpihp_drv_interface);
+	acpihp_core_fini();
+}
+
+module_init(acpihp_drv_init);
+module_exit(acpihp_drv_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Jiang Liu <jiang.liu@huawei.com>");
+MODULE_AUTHOR("Hanjun Guo <guohanjun@huawei.com>");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 07/13] ACPIHP: analyse dependencies among ACPI hotplug slots
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (5 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 06/13] ACPIHP: implement ACPI system device hotplug driver skeleton Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 08/13] ACPIHP: provide interface to cancel inprogress hotplug operations Jiang Liu
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

Due to hardware constraints, an ACPI hotplug slot 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.

According to physical and device tree topology constraints, we need to
consider following dependency relationships:
1) The parent slot must be powered on before powering a child slot on.
2) All child slots must be powered off before powering a parent slot off.
3) All devices in a slot's _EDL list must be powered off before powering
   a slot off.
4) The parent ACPI device topology must be created before creating ACPI
   devices for devices connecting to a child slot
5) All ACPI devices connecting to child slots must be destroyed before
   destroying ACPI device topology for a parent slot.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/Makefile     |    1 +
 drivers/acpi/hotplug/acpihp_drv.h |   27 ++++
 drivers/acpi/hotplug/dependency.c |  245 +++++++++++++++++++++++++++++++++++++
 3 files changed, 273 insertions(+)
 create mode 100644 drivers/acpi/hotplug/dependency.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 6257047..bfb677f 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -12,3 +12,4 @@ acpihp_slot-$(CONFIG_ACPI_HOTPLUG_SLOT_FAKE)	+= slot_fake.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 769ee74..32ea054 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -25,14 +25,41 @@
 #ifndef	__ACPIHP_DRV_H__
 #define	__ACPIHP_DRV_H__
 
+/* Commands to drive hotplug slot state machine */
+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
+};
+
 struct acpihp_slot_drv {
 	struct mutex		op_mutex;
 };
 
+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..c2992f4
--- /dev/null
+++ b/drivers/acpi/hotplug/dependency.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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"
+
+#define	ACPI_METHOD_NAME__EDL	"_EDL"
+
+/*
+ * Insert a slot onto the dependency list in FILO order.
+ * Caller needs to protect from concurrent accesses to the dependency list.
+ */
+int acpihp_drv_add_slot_to_dependency_list(struct acpihp_slot *slot,
+					   struct list_head *dep_list)
+{
+	struct acpihp_slot_dependency *dep;
+
+	/*
+	 * A dependent slot may be encountered when both analyzing the array
+	 * returned by _EDL method and walking ACPI namespace topology.
+	 * Should we move the slot to the list head? May need more work
+	 * here on platforms with complex topology.
+	 */
+	list_for_each_entry(dep, dep_list, node)
+		if (dep->slot == slot)
+			return 0;
+
+	dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+	if (!dep) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to allocate memory.\n");
+		return -ENOMEM;
+	}
+
+	dep->slot = slot;
+	list_add(&dep->node, dep_list);
+
+	return 0;
+}
+
+static int acpihp_drv_get_online_dependency(struct acpihp_slot *slot,
+					    struct list_head *dep_list)
+{
+	int ret = 0;
+	struct acpihp_slot *temp;
+
+	/*
+	 * When enabling a hotplug slot, all its ancestors must be enabled
+	 * first.
+	 */
+	for (temp = slot; temp && ret == 0; temp = temp->parent)
+		ret = acpihp_drv_add_slot_to_dependency_list(temp, dep_list);
+
+	return ret;
+}
+
+/*
+ * Analyze dependency relationships by evaulating ACPI _EDL method
+ * when disabling a hotplug slot.
+ */
+static int acpihp_drv_for_each_edl(struct acpihp_slot *slot, void *argp,
+	int(*cb)(struct device *dev, void *argp))
+{
+	int i;
+	acpi_status rc;
+	struct acpi_buffer buf;
+	union acpi_object *obj, *elem;
+	struct acpihp_slot *tmp;
+
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	rc = acpi_evaluate_object_typed(slot->handle, ACPI_METHOD_NAME__EDL,
+					NULL, &buf, ACPI_TYPE_PACKAGE);
+	if (rc == AE_NOT_FOUND) {
+		/* ACPI _EDL method is optional. */
+		return 0;
+	} else if (ACPI_FAILURE(rc)) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to evaluate _EDL.\n");
+		return -EINVAL;
+	}
+	obj = buf.pointer;
+
+	/* validate the returned package object. */
+	for (i = 0, elem = obj->package.elements;
+	     i < obj->package.count; i++, elem++)
+		if (elem->type != ACPI_TYPE_LOCAL_REFERENCE ||
+		    elem->reference.actual_type != ACPI_TYPE_DEVICE ||
+		    elem->reference.handle == NULL) {
+			ACPIHP_SLOT_DEBUG(slot,
+					  "invalid return from _EDL method.\n");
+			rc = AE_ERROR;
+			goto out;
+		}
+
+	/*
+	 * The dependency list will be handled in FILO order, so walk the array
+	 * in reverse order to keep the same order as returned by _EDL.
+	 */
+	for (i = 0, elem--; i < obj->package.count && ACPI_SUCCESS(rc);
+	     i++, elem--) {
+		tmp = acpihp_get_slot(elem->reference.handle);
+		if (tmp) {
+			rc = (*cb)(&tmp->dev, argp);
+			if (rc == AE_CTRL_DEPTH || rc == AE_CTRL_TERMINATE)
+				rc = AE_OK;
+		/*
+		 * ACPI _EDL method may return PCI slots for a hotpluggable
+		 * PCI host bridge, skip such cases. Only bail out if it's
+		 * an ACPI hotplug slot for system devices.
+		 */
+		} else if (acpihp_is_slot(elem->reference.handle)) {
+			ACPIHP_SLOT_WARN(slot,
+					 "fails to get device for slot.\n");
+			rc = AE_ERROR;
+		}
+	}
+
+out:
+	ACPI_FREE(buf.pointer);
+
+	return ACPI_SUCCESS(rc) ? 0 : -EINVAL;
+}
+
+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);
+
+	/* All child slots must be handled first when hot-removing. */
+	if (!ret)
+		ret = device_for_each_child(&slot->dev, argp,
+					    &acpihp_drv_add_offline_dependency);
+
+	/* Add all slots from the _EDL list onto the dependency list */
+	if (!ret)
+		ret = acpihp_drv_for_each_edl(slot, argp,
+				&acpihp_drv_add_offline_dependency);
+
+	return ret;
+}
+
+/*
+ * Genereate dependency list for a given slot according to command.
+ * Caller needs to clean up the returned list if error happens.
+ */
+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_add_offline_dependency(&slot->dev,
+							   slot_list);
+		break;
+
+	default:
+		retval = -EINVAL;
+		break;
+	}
+
+	return retval;
+}
+
+/*
+ * Generate a new dependency list from the old list by filtering out slots
+ * which don't 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 *dep, *temp;
+
+	list_for_each_entry_safe(dep, temp, slot_list, node) {
+		list_del(&dep->node);
+		kfree(dep);
+	}
+}
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 08/13] ACPIHP: provide interface to cancel inprogress hotplug operations
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (6 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 07/13] ACPIHP: analyse dependencies among ACPI hotplug slots Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 09/13] ACPIHP: configure/unconfigure system devices attached to a hotplug slot Jiang Liu
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

Some hotplug operations, such as hot-removal of memory device, may take
very long or even infinite time. One possible solution is to time out
and retry, but it's sub-optimal.

This patch implements interfaces to cancel inprogress ACPI system device
hotplug operations, so user could cancel a long-standing hotplug request
on demand.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/Makefile     |    1 +
 drivers/acpi/hotplug/acpihp_drv.h |   19 ++++
 drivers/acpi/hotplug/cancel.c     |  174 +++++++++++++++++++++++++++++++++++++
 3 files changed, 194 insertions(+)
 create mode 100644 drivers/acpi/hotplug/cancel.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index bfb677f..f72f2c3 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -13,3 +13,4 @@ acpihp_slot-$(CONFIG_ACPI_HOTPLUG_SLOT_FAKE)	+= slot_fake.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 32ea054..dd8ea92 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -38,8 +38,20 @@ enum acpihp_drv_cmd {
 	ACPIHP_DRV_CMD_MAX
 };
 
+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 {
 	struct mutex		op_mutex;
+	atomic_t		cancel_state;
+	atomic_t		cancel_users;
+	struct acpihp_cancel_context	cancel_ctx;
 };
 
 struct acpihp_slot_dependency {
@@ -62,4 +74,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..c515c28
--- /dev/null
+++ b/drivers/acpi/hotplug/cancel.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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"
+
+/*
+ * Implement interfaces to cancel inprogress hotplug operations.
+ * Currently only CONFIGURE and RELEASE operation stages support cancellation.
+ * Caller must serialize calls to following functions by holding the
+ * state_machine_mutex lock:
+ *	acpihp_drv_cancel_init()
+ *	acpihp_drv_cancel_start()
+ *	acpihp_drv_cancel_fini()
+ */
+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_state) != ACPIHP_DRV_CANCEL_INIT;
+}
+
+void acpihp_drv_cancel_init(struct list_head *list)
+{
+	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);
+		drv_data->cancel_ctx.check_cancel = acpihp_drv_check_cancel;
+		atomic_set(&drv_data->cancel_state, ACPIHP_DRV_CANCEL_INIT);
+		atomic_set(&drv_data->cancel_users, 0);
+	}
+}
+
+/*
+ * 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 0;
+	}
+
+	/* 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_state,
+			       ACPIHP_DRV_CANCEL_INIT,
+			       ACPIHP_DRV_CANCEL_STARTED);
+	}
+
+	return 0;
+}
+
+/* Notify that the slot reaches a stable state */
+void acpihp_drv_cancel_notify(struct acpihp_slot *slot,
+			      enum acpihp_drv_cancel_state state)
+{
+	int old;
+	struct acpihp_slot_drv *drv_data;
+
+	acpihp_drv_get_data(slot, &drv_data);
+	old = atomic_cmpxchg(&drv_data->cancel_state, ACPIHP_DRV_CANCEL_INIT,
+			     ACPIHP_DRV_CANCEL_FINISHED);
+	if (old != ACPIHP_DRV_CANCEL_INIT) {
+		atomic_set(&drv_data->cancel_state, state);
+		wake_up_all(&acpihp_drv_cancel_queue);
+	}
+}
+
+/*
+ * 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 state, 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_state)
+				!= ACPIHP_DRV_CANCEL_STARTED);
+
+		state = atomic_read(&drv_data->cancel_state);
+		if (state == ACPIHP_DRV_CANCEL_FAILED) {
+			ACPIHP_SLOT_DEBUG(dep->slot,
+					  "fails to cancel operation.\n");
+			result = result ? : -EBUSY;
+		} else if (state == ACPIHP_DRV_CANCEL_MISSED) {
+			ACPIHP_SLOT_DEBUG(dep->slot,
+					  "misses to cancel operation.\n");
+			result = result ? : -EBUSY;
+		}
+
+		atomic_set(&drv_data->cancel_state,
+			   ACPIHP_DRV_CANCEL_FINISHED);
+		atomic_dec(&drv_data->cancel_users);
+		wake_up_all(&acpihp_drv_cancel_queue);
+	}
+
+	return result;
+}
+
+/*
+ * 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_state,
+				       ACPIHP_DRV_CANCEL_STARTED,
+				       ACPIHP_DRV_CANCEL_MISSED);
+		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));
+	}
+}
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 09/13] ACPIHP: configure/unconfigure system devices attached to a hotplug slot
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (7 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 08/13] ACPIHP: provide interface to cancel inprogress hotplug operations Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 10/13] ACPIHP: implement the core state machine to manage hotplug slots Jiang Liu
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

This patch implements interfaces to configure/unconfigure system devices
attached to a hotplug slot. Configuring is to add system devices into
running system and put then into use, unconfiguring is to remove system
devices from running system.

To support better error recover and cancellation, device configuration
operations are splitted into three steps and unconfiguration operations
are splitted into six steps as below:
CONFIGURE
 1) pre_configure(): optional step to pre-allocate special resources
 2) configure(): allocate required resources
 3) pos_configure(): add devices into system or rollback
UNCONFIGURE
 1) pre_release(): optional step to mark device going to be removed/busy
 2) release(): reclaim device from running system
 3) post_release(): rollback if cancelled by user or error happened
 4) pre_unconfigure(): optional step to solve possible dependency issue
 5) unconfigure(): remove devices from running system
 6) post_unconfigure(): free resources used by devices

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/Makefile     |    1 +
 drivers/acpi/hotplug/acpihp_drv.h |    4 +
 drivers/acpi/hotplug/configure.c  |  341 +++++++++++++++++++++++++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 drivers/acpi/hotplug/configure.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index f72f2c3..6cb6aa1 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -14,3 +14,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 dd8ea92..aa239f6 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -58,6 +58,7 @@ struct acpihp_slot_dependency {
 	struct list_head		node;
 	struct acpihp_slot		*slot;
 	u32				opcodes;
+	u32				execute_stages;
 };
 
 void acpihp_drv_get_data(struct acpihp_slot *slot,
@@ -81,4 +82,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..3209d14b
--- /dev/null
+++ b/drivers/acpi/hotplug/configure.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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 following 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_HOST_BRIDGE,
+};
+
+/* All devices will be unconfigured in following order. */
+static enum acpihp_dev_type acpihp_drv_dev_types_reverse[] = {
+	ACPIHP_DEV_TYPE_HOST_BRIDGE,
+	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);
+	dev->state = state;
+}
+
+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:
+		if (drv_data->cancel_ctx.check_cancel(&drv_data->cancel_ctx))
+			return -ECANCELED;
+		return acpihp_dev_pre_configure(dev, &drv_data->cancel_ctx);
+	case DRV_OP_CONFIGURE:
+		if (drv_data->cancel_ctx.check_cancel(&drv_data->cancel_ctx))
+			return -ECANCELED;
+		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:
+		if (drv_data->cancel_ctx.check_cancel(&drv_data->cancel_ctx))
+			return -ECANCELED;
+		return acpihp_dev_pre_release(dev, &drv_data->cancel_ctx);
+	case DRV_OP_RELEASE:
+		if (drv_data->cancel_ctx.check_cancel(&drv_data->cancel_ctx))
+			return -ECANCELED;
+		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)
+			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;
+			dep->execute_stages |= 1 << opcode;
+		}
+
+	return 0;
+}
+
+static void 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;
+	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) {
+			if (!dep->execute_stages)
+				continue;
+			if (acpihp_drv_call_method_post(opcode, dep->slot,
+							tp[i], state, post))
+				ACPIHP_SLOT_WARN(dep->slot,
+					"fails to commit or rollback.\n");
+		}
+}
+
+static void acpihp_drv_sync_cancel(struct list_head *list, int result)
+{
+	int cancel;
+	struct acpihp_slot_dependency *dep;
+
+	if (!result)
+		cancel = ACPIHP_DRV_CANCEL_MISSED;
+	else if (result == -ECANCELED)
+		cancel = ACPIHP_DRV_CANCEL_OK;
+	else
+		cancel = ACPIHP_DRV_CANCEL_FAILED;
+
+	list_for_each_entry(dep, list, node) {
+		acpihp_drv_cancel_notify(dep->slot, cancel);
+		acpihp_drv_update_slot_state(dep->slot);
+	}
+}
+
+/*
+ * To support better error recover and cancellation, configure operations
+ * are splitted into three steps:
+ * 1) pre_configure(): optional step to setup special resources
+ * 2) configure(): allocate required resources and initialize device
+ * 3) post_configure(): add device into system or rollback
+ */
+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,
+						   ACPIHP_DRV_CMD_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;
+	acpihp_drv_walk_devs_post(&head, DRV_OP_POST_CONFIGURE,
+				  DEVICE_STATE_CONNECTED, post, false);
+
+	list_for_each_entry(dep, &head, node)
+		acpihp_drv_update_slot_state(dep->slot);
+	acpihp_drv_sync_cancel(&head, result);
+	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;
+	acpihp_drv_walk_devs_post(list, DRV_OP_POST_RELEASE,
+				  DEVICE_STATE_CONFIGURED, post, true);
+	acpihp_drv_sync_cancel(list, result);
+
+	return result;
+}
+
+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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 10/13] ACPIHP: implement the core state machine to manage hotplug slots
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (8 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 09/13] ACPIHP: configure/unconfigure system devices attached to a hotplug slot Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 11/13] ACPIHP: block ACPI device driver from unloading when doing hotplug Jiang Liu
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

This patch implements the core of the new ACPI hotplug framework,
a state machine for ACPI hotplug slots. The state machine is:

       (plug in)     (power on)    (connect)      (configure)
 [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED]
       (plug out)   (power off)   (disconnect)   (unconfigure)

[...]: state
(...): action
(connect): create ACPI devices and bind ACPI device drivers
(disconnect): unbind ACPI device drivers and destroy ACPI devices
(configure): allocate resources and add system device into running system
(unconfigure): remove system device from running system and free resources

It provides a simple interface to drive the state machine:
int acpihp_drv_change_state(struct acpihp_slot *slot,
			    enum acpihp_drv_cmd cmd);

It glues all hotplug logic together, including resolving dependencies
among slots, cancalling inprogress hotplug operations and configuring/
unconfigure system devices etc.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/Makefile        |    1 +
 drivers/acpi/hotplug/acpihp_drv.h    |    7 +
 drivers/acpi/hotplug/drv_main.c      |    1 +
 drivers/acpi/hotplug/state_machine.c |  639 ++++++++++++++++++++++++++++++++++
 4 files changed, 648 insertions(+)
 create mode 100644 drivers/acpi/hotplug/state_machine.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 6cb6aa1..0f43933 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -15,3 +15,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 aa239f6..175ef81 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -49,6 +49,7 @@ enum acpihp_drv_cancel_state {
 
 struct acpihp_slot_drv {
 	struct mutex		op_mutex;
+	struct list_head	depend_list;
 	atomic_t		cancel_state;
 	atomic_t		cancel_users;
 	struct acpihp_cancel_context	cancel_ctx;
@@ -61,6 +62,9 @@ struct acpihp_slot_dependency {
 	u32				execute_stages;
 };
 
+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);
@@ -85,4 +89,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/drv_main.c b/drivers/acpi/hotplug/drv_main.c
index 8ab298a..5a919e7 100644
--- a/drivers/acpi/hotplug/drv_main.c
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -273,6 +273,7 @@ static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
 	drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
 	if (drv_data) {
 		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)) {
diff --git a/drivers/acpi/hotplug/state_machine.c b/drivers/acpi/hotplug/state_machine.c
new file mode 100644
index 0000000..7604da1
--- /dev/null
+++ b/drivers/acpi/hotplug/state_machine.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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's device lock
+ */
+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_SLOT_DEBUG(slot, "slot is busy in state %d.\n",
+				  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_SLOT_DEBUG(slot, "invalid state %d.\n", slot->state);
+		return -EINVAL;
+	} else if (slot->state >= ACPIHP_SLOT_STATE_POWERING_ON) {
+		/*
+		 * It shouldn't happen, transcendant states are protected
+		 * by slot->op_mutex.
+		 */
+		BUG_ON(slot->state);
+		return -EINVAL;
+	}
+
+	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 -ENODEV;
+		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 -ENODEV;
+		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 -ENODEV;
+		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_SLOT_DEBUG(slot, "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_SLOT_DEBUG(slot,
+				"invalid transition for slot in state %d.\n",
+				slot->state);
+			return retval;
+		}
+
+		/* Check whether the slot is in good shape */
+		if (dep->opcodes &&
+		    acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_FAULT)) {
+			ACPIHP_SLOT_WARN(slot,
+					 "slot has been marked as faulty.\n");
+			return -EINVAL;
+		} else if ((dep->opcodes & ACPIHP_DRV_CMD_UNCONFIGURE) &&
+		    acpihp_slot_get_flag(slot, ACPIHP_SLOT_FLAG_IRREMOVABLE)) {
+			ACPIHP_SLOT_WARN(slot, "slot is busy.\n");
+			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;
+
+	acpihp_drv_get_data(slot, &drv_data);
+	mutex_lock(&state_machine_mutex);
+	*head = list = &drv_data->depend_list;
+
+	/*
+	 * 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(),
+	 */
+	if (cmd == ACPIHP_DRV_CMD_CANCEL) {
+		retval = acpihp_drv_cancel_start(list);
+		goto out;
+	}
+
+	/* bail out if slot has already been locked to avoid deadlock */
+	if (mutex_is_locked(&drv_data->op_mutex)) {
+		ACPIHP_SLOT_DEBUG(slot, "is busy.\n");
+		retval = -EBUSY;
+		goto out;
+	}
+
+	BUG_ON(!list_empty(list));
+	retval = acpihp_drv_generate_dependency_list(slot, list, cmd);
+	if (retval) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to get dependency lists.\n");
+		goto out;
+	}
+
+	retval = acpihp_drv_lock_slots(list, slot);
+	if (retval) {
+		ACPIHP_SLOT_DEBUG(slot, "fails to lock slots.\n");
+		acpihp_drv_destroy_dependency_list(list);
+		goto out;
+	}
+
+	retval = acpihp_drv_validate_command(list, slot, cmd);
+	if (retval) {
+		ACPIHP_SLOT_DEBUG(slot, "invalid command.\n");
+		acpihp_drv_unlock_slots(list);
+		acpihp_drv_destroy_dependency_list(list);
+	} else {
+		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);
+
+	status = acpihp_slot_poweron(slot);
+	if (ACPI_FAILURE(status)) {
+		if (status == AE_SUPPORT)
+			return -ENOSYS;
+		else {
+			ACPIHP_SLOT_WARN(slot, "fails to power on slot.\n");
+			acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+			return -ENXIO;
+		}
+	}
+
+	if (!acpihp_slot_powered(slot)) {
+		ACPIHP_SLOT_WARN(slot, "fails to power on.\n");
+		acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+		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_SLOT_DEBUG(dep->slot, "succeed to power on!\n");
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_POWERED);
+		} else {
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_PRESENT);
+			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)) {
+		if (status == AE_SUPPORT)
+			return -ENOSYS;
+		else {
+			ACPIHP_SLOT_WARN(slot, "fails to power off slot.\n");
+			acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+			return -ENXIO;
+		}
+	}
+
+	if (acpihp_slot_powered(slot)) {
+		ACPIHP_SLOT_WARN(slot, "fails to power off slot.\n");
+		acpihp_slot_set_flag(slot, ACPIHP_SLOT_FLAG_FAULT);
+		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_SLOT_DEBUG(dep->slot,
+					  "succeed to power off slot!\n");
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_PRESENT);
+		} else {
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_POWERED);
+			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_SLOT_DEBUG(slot,
+				  "fails to add ACPI devices for slot.\n");
+	else {
+		retval = acpihp_drv_enumerate_devices(slot);
+		if (retval)
+			ACPIHP_SLOT_DEBUG(slot,
+				"fails to enumerate device for slot.\n");
+	}
+
+	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_SLOT_DEBUG(dep->slot,
+					  "succeed to connect slot!\n");
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_CONNECTED);
+		} else {
+			acpihp_slot_set_flag(dep->slot, ACPIHP_SLOT_FLAG_FAULT);
+			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 attached to a 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_SLOT_DEBUG(slot,
+					  "fails to remove ACPI devices.\n");
+			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_SLOT_WARN(slot,
+					 "fails to remove ACPI devices.\n");
+	} else {
+		ACPIHP_SLOT_WARN(slot, "fails to get ACPI device for slot.\n");
+		retval = -ENXIO;
+	}
+
+	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_SLOT_DEBUG(dep->slot,
+					  "succeed to disconnect slot!\n");
+			acpihp_slot_change_state(dep->slot,
+						 ACPIHP_SLOT_STATE_POWERED);
+		} else {
+			acpihp_slot_set_flag(dep->slot, ACPIHP_SLOT_FLAG_FAULT);
+			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 implements
+ * a state machine as below for hotplug slots.
+ *
+ *       (plug in)     (power on)    (connect)      (configure)
+ * [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED]
+ *       (plug out)   (power off)   (disconnect)   (unconfigure)
+ *
+ * [...]: state
+ * (...): action
+ * (connect): create ACPI devices and bind ACPI device drivers
+ * (disconnect): unbind ACPI device drivers and destroy ACPI devices
+ * (configure): allocate resources and add system device into running system
+ * (unconfigure): remove system device from running system and free resources
+ */
+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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 11/13] ACPIHP: block ACPI device driver from unloading when doing hotplug
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (9 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 10/13] ACPIHP: implement the core state machine to manage hotplug slots Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 12/13] ACPIHP: implement sysfs interfaces for system device hotplug Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 13/13] ACPIHP: handle ACPI device hotplug events Jiang Liu
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

The ACPI hotplug driver depends on ACPI device drivers to configure or
unconfigure ACPI devices. The hotplug driver will be confused if ACPI
device drivers are unloaded when there are ongoing hotplug operations.
So block ACPI device driver unbinding/unloading by holding the device
lock when executing hotplug operations.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
---
 drivers/acpi/hotplug/configure.c |   18 ++++++++++++++++--
 drivers/acpi/hotplug/device.c    |    6 ------
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/drivers/acpi/hotplug/configure.c b/drivers/acpi/hotplug/configure.c
index 3209d14b..9f3ea97 100644
--- a/drivers/acpi/hotplug/configure.c
+++ b/drivers/acpi/hotplug/configure.c
@@ -37,7 +37,9 @@ enum config_op_code {
 	DRV_OP_POST_RELEASE,
 	DRV_OP_PRE_UNCONFIGURE,
 	DRV_OP_UNCONFIGURE,
-	DRV_OP_POST_UNCONFIGURE
+	DRV_OP_POST_UNCONFIGURE,
+	DRV_OP_LOCK_DEVICE,
+	DRV_OP_UNLOCK_DEVICE,
 };
 
 /* All devices will be configured in following order. */
@@ -98,6 +100,12 @@ static int acpihp_drv_invoke_method(enum config_op_code  opcode,
 		return acpihp_dev_unconfigure(dev);
 	case DRV_OP_POST_UNCONFIGURE:
 		return acpihp_dev_post_unconfigure(dev);
+	case DRV_OP_LOCK_DEVICE:
+		device_lock(&dev->dev);
+		return 0;
+	case DRV_OP_UNLOCK_DEVICE:
+		device_unlock(&dev->dev);
+		return 0;
 	default:
 		BUG_ON(opcode);
 		return -ENOSYS;
@@ -122,7 +130,9 @@ static int acpihp_drv_call_method(enum config_op_code opcode,
 		result = acpihp_drv_invoke_method(opcode, slot, acpi_dev, 0);
 		if (result)
 			break;
-		acpihp_drv_update_dev_state(np, state);
+		if (opcode != DRV_OP_LOCK_DEVICE &&
+		    opcode != DRV_OP_UNLOCK_DEVICE)
+			acpihp_drv_update_dev_state(np, state);
 	}
 	klist_iter_exit(&iter);
 
@@ -249,6 +259,7 @@ int acpihp_drv_configure(struct list_head *list)
 		acpihp_slot_change_state(dep->slot,
 					 ACPIHP_SLOT_STATE_CONFIGURING);
 
+	acpihp_drv_walk_devs(&head, DRV_OP_LOCK_DEVICE, 0, false);
 	result = acpihp_drv_walk_devs(&head, DRV_OP_PRE_CONFIGURE,
 				      DEVICE_STATE_PRE_CONFIGURE, false);
 	if (!result)
@@ -258,6 +269,7 @@ int acpihp_drv_configure(struct list_head *list)
 		post = ACPIHP_DEV_POST_CMD_ROLLBACK;
 	acpihp_drv_walk_devs_post(&head, DRV_OP_POST_CONFIGURE,
 				  DEVICE_STATE_CONNECTED, post, false);
+	acpihp_drv_walk_devs(&head, DRV_OP_UNLOCK_DEVICE, 0, false);
 
 	list_for_each_entry(dep, &head, node)
 		acpihp_drv_update_slot_state(dep->slot);
@@ -328,9 +340,11 @@ int acpihp_drv_unconfigure(struct list_head *list)
 		acpihp_slot_change_state(dep->slot,
 					 ACPIHP_SLOT_STATE_UNCONFIGURING);
 
+	acpihp_drv_walk_devs(&head, DRV_OP_LOCK_DEVICE, 0, false);
 	result = acpihp_drv_release(&head);
 	if (!result)
 		__acpihp_drv_unconfigure(&head);
+	acpihp_drv_walk_devs(&head, DRV_OP_UNLOCK_DEVICE, 0, false);
 
 	list_for_each_entry(dep, &head, node)
 		acpihp_drv_update_slot_state(dep->slot);
diff --git a/drivers/acpi/hotplug/device.c b/drivers/acpi/hotplug/device.c
index c9d550f..6d60bb0 100644
--- a/drivers/acpi/hotplug/device.c
+++ b/drivers/acpi/hotplug/device.c
@@ -62,14 +62,12 @@ int acpihp_dev_##method(struct acpi_device *device, type val) \
 { \
 	int ret; \
 	BUG_ON(device == NULL); \
-	device_lock(&device->dev); \
 	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); \
-	device_unlock(&device->dev); \
 	return ret; \
 } \
 EXPORT_SYMBOL_GPL(acpihp_dev_##method)
@@ -79,14 +77,12 @@ int acpihp_dev_##method(struct acpi_device *device, type val) \
 { \
 	int ret = 0; \
 	BUG_ON(device == NULL); \
-	device_lock(&device->dev); \
 	if (!device->driver || !device->driver->ops.hp_ops) \
 		ret = (err); \
 	else if (!device->driver->ops.hp_ops->method) \
 		ret = (def); \
 	else \
 		device->driver->ops.hp_ops->method(device, val); \
-	device_unlock(&device->dev); \
 	return ret; \
 } \
 EXPORT_SYMBOL_GPL(acpihp_dev_##method)
@@ -96,14 +92,12 @@ int acpihp_dev_##method(struct acpi_device *device) \
 { \
 	int ret = 0; \
 	BUG_ON(device == NULL); \
-	device_lock(&device->dev); \
 	if (!device->driver || !device->driver->ops.hp_ops) \
 		ret = (err); \
 	else if (!device->driver->ops.hp_ops->method) \
 		ret = (def); \
 	else \
 		device->driver->ops.hp_ops->method(device);\
-	device_unlock(&device->dev); \
 	return ret; \
 } \
 EXPORT_SYMBOL_GPL(acpihp_dev_##method)
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 12/13] ACPIHP: implement sysfs interfaces for system device hotplug
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (10 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 11/13] ACPIHP: block ACPI device driver from unloading when doing hotplug Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  2012-11-04 12:50 ` [ACPIHP PATCH part2 13/13] ACPIHP: handle ACPI device hotplug events Jiang Liu
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

This patch implements sysfs interfaces to access system device hotplug
functionalities. These sysfs interfaces are mainly used to drive the
hotplug slot state machine as below:
 [ABSENT] <-> [PRESENT] <-> [POWERED] <-> [CONNECTED] <-> [CONFIGURED]

[ABSENT]: no devices attached to the hotplug slot
[PRESENT]: devices physically attached to the hotplug slot
[POWERED]: devices attached to the hotplug slot are powered
[CONNECTED]: ACPI device objects have been created for devices attached
	     to the hotplug slot
[CONFIGURED]: devices attached to the slot are in use by system

Two sysfs interfaces have been implemented as below:
SLOT/dependency: show dependency relationship among hotplug slots
SLOT/control: trigger system device hotplug operations
	read from control sysfs file gives some help messages and you may
	write following commands to it:
	poweron: transit to POWERED from PRESENT
	connect: transit to CONNECTED from PRESENT/POWERED
	configure: transit to CONFIGURED from PRESENT/POWERED/CONNECTED
	unconfigure: transit to CONNECTED from CONFIGURED
	disconnect: transit to POWERED from CONFIGURED/CONNECTED
	poweroff: transit PRESENT from CONFIGURED/CONNECTED/POWERED
	enable, 1: the same as configure
	disable, 0: the same as poweroff
	cancel: cancel inprogress hotplug operations

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Hanjun Guo <guohanjun@huawei.com>
---
 drivers/acpi/hotplug/Makefile     |    1 +
 drivers/acpi/hotplug/acpihp_drv.h |    3 +
 drivers/acpi/hotplug/drv_main.c   |    5 +
 drivers/acpi/hotplug/sysfs.c      |  181 +++++++++++++++++++++++++++++++++++++
 4 files changed, 190 insertions(+)
 create mode 100644 drivers/acpi/hotplug/sysfs.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 0f43933..57e348a 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -16,3 +16,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 175ef81..2ec2547 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -92,4 +92,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 5a919e7..bd5c97c 100644
--- a/drivers/acpi/hotplug/drv_main.c
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -284,6 +284,10 @@ static int acpihp_drv_slot_add(struct device *dev, struct class_interface *intf)
 		return -ENOMEM;
 	}
 
+	if (acpihp_drv_create_sysfs(slot))
+		ACPIHP_SLOT_DEBUG(slot,
+			"fails to create sysfs interfaces, some functions will not be available to user.\n");
+
 	return 0;
 }
 
@@ -294,6 +298,7 @@ static void acpihp_drv_intf_remove(struct device *dev,
 	struct acpihp_slot *slot =
 			container_of(dev, struct acpihp_slot, dev);
 
+	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..4519eea
--- /dev/null
+++ b/drivers/acpi/hotplug/sysfs.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ * Copyright (C) 2012 Hanjun Guo <guohanjun@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_control_show(struct device *dev,
+		struct device_attribute *attr, char *page)
+{
+	ssize_t off;
+	struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+	off = snprintf(page, PAGE_SIZE, "supported commands:\n");
+	if (slot->capabilities & ACPIHP_SLOT_CAP_POWERON)
+		off += snprintf(page + off, PAGE_SIZE - off,
+				"\tpoweron: power on the hotplug slot\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tconnect: create ACPI device nodes and bind ACPI device drivers\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tconfigure: put system devices into running state\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tunconfigure: stop system devices from running state\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tdisconnect: unbind ACPI device drivers and destroy ACPI device nodes\n");
+	if (slot->capabilities & ACPIHP_SLOT_CAP_POWEROFF)
+		off += snprintf(page + off, PAGE_SIZE - off,
+				"\tpoweroff: power off the hotplug slot\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tcancel: cancel inprogress hotplug operations\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tenable, 1: the same as configure\n");
+	off += snprintf(page + off, PAGE_SIZE - off,
+			"\tdisable, 0: the same as poweroff\n");
+
+	return off;
+}
+
+static ssize_t acpihp_drv_control_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	int result = -EINVAL;
+	char *temp, *pos, *token;
+	enum acpihp_drv_cmd cmd = ACPIHP_DRV_CMD_NOOP;
+	struct acpihp_slot *slot = container_of(dev, struct acpihp_slot, dev);
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	temp = pos = kstrndup(buf, PAGE_SIZE - 1, GFP_KERNEL);
+	if (!temp)
+		return -ENOMEM;
+
+	token = strsep(&pos, " \t\r\n");
+	if (!token)
+		goto out;
+
+	if (!strcmp(token, "enable"))
+		cmd = ACPIHP_DRV_CMD_CONFIGURE;
+	else if (!strcmp(token, "1"))
+		cmd = ACPIHP_DRV_CMD_CONFIGURE;
+	else if (!strcmp(token, "disable"))
+		cmd = ACPIHP_DRV_CMD_POWEROFF;
+	else if (!strcmp(token, "0"))
+		cmd = ACPIHP_DRV_CMD_POWEROFF;
+	else if (!strcmp(token, "connect"))
+		cmd = ACPIHP_DRV_CMD_CONNECT;
+	else if (!strcmp(token, "configure"))
+		cmd = ACPIHP_DRV_CMD_CONFIGURE;
+	else if (!strcmp(token, "unconfigure"))
+		cmd = ACPIHP_DRV_CMD_UNCONFIGURE;
+	else if (!strcmp(token, "disconnect"))
+		cmd = ACPIHP_DRV_CMD_DISCONNECT;
+	else if (!strcmp(token, "cancel"))
+		cmd = ACPIHP_DRV_CMD_CANCEL;
+	else if (!strcmp(token, "poweron")) {
+		if (slot->capabilities & ACPIHP_SLOT_CAP_POWERON)
+			cmd = ACPIHP_DRV_CMD_POWERON;
+	} else if (!strcmp(token, "poweroff")) {
+		if (slot->capabilities & ACPIHP_SLOT_CAP_POWEROFF)
+			cmd = ACPIHP_DRV_CMD_POWEROFF;
+	}
+
+	if (cmd != ACPIHP_DRV_CMD_NOOP)
+		result = acpihp_drv_change_state(slot, cmd);
+out:
+	kfree(temp);
+
+	return result < 0 ? result : count;
+}
+
+static DEVICE_ATTR(control, S_IRUGO | S_IWUSR,
+	    &acpihp_drv_control_show, &acpihp_drv_control_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->name) + 2 >= end)
+				break;
+			p += snprintf(p, end - p, "%s\n", dep->slot->name);
+		}
+
+		acpihp_drv_destroy_dependency_list(&list);
+		ret = p - page;
+	}
+	mutex_unlock(&state_machine_mutex);
+
+	return ret;
+}
+
+static DEVICE_ATTR(dependency, S_IRUGO,
+		   &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_control);
+	if (retval)
+		goto out;
+	retval = device_create_file(dev, &dev_attr_dependency);
+	if (!retval)
+		return 0;
+
+	device_remove_file(dev, &dev_attr_control);
+out:
+	ACPIHP_SLOT_DEBUG(slot, "fails to create sysfs interfaces for slot.\n");
+	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_control);
+}
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [ACPIHP PATCH part2 13/13] ACPIHP: handle ACPI device hotplug events
  2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
                   ` (11 preceding siblings ...)
  2012-11-04 12:50 ` [ACPIHP PATCH part2 12/13] ACPIHP: implement sysfs interfaces for system device hotplug Jiang Liu
@ 2012-11-04 12:50 ` Jiang Liu
  12 siblings, 0 replies; 14+ messages in thread
From: Jiang Liu @ 2012-11-04 12:50 UTC (permalink / raw)
  To: Rafael J . Wysocki, Yinghai Lu, Tony Luck, Yasuaki Ishimatsu,
	Wen Congyang, Tang Chen, Taku Izumi, Bjorn Helgaas
  Cc: Jiang Liu, Kenji Kaneshige, Huang Ying, Bob Moore, Len Brown,
	Srivatsa S . Bhat, Yijing Wang, Hanjun Guo, Jiang Liu,
	linux-kernel, linux-acpi, linux-pci, linux-mm

Implement an event handler for ACPI system device hotplug events.
The handler will relay hotplug events to userspace helper if it's
configured to do so. Otherwise it will queue the hotplug event
onto kacpi_hotplug_wq, which will then invoke acpihp_drv_change_state()
to handle the hotplug event.

Signed-off-by: Jiang Liu <jiang.liu@huawei.com>
---
 drivers/acpi/hotplug/Makefile     |    1 +
 drivers/acpi/hotplug/acpihp_drv.h |    2 +
 drivers/acpi/hotplug/drv_main.c   |   11 +--
 drivers/acpi/hotplug/event.c      |  163 +++++++++++++++++++++++++++++++++++++
 4 files changed, 168 insertions(+), 9 deletions(-)
 create mode 100644 drivers/acpi/hotplug/event.c

diff --git a/drivers/acpi/hotplug/Makefile b/drivers/acpi/hotplug/Makefile
index 57e348a..640a625 100644
--- a/drivers/acpi/hotplug/Makefile
+++ b/drivers/acpi/hotplug/Makefile
@@ -17,3 +17,4 @@ acpihp_drv-y					+= cancel.o
 acpihp_drv-y					+= configure.o
 acpihp_drv-y					+= state_machine.o
 acpihp_drv-y					+= sysfs.o
+acpihp_drv-y					+= event.o
diff --git a/drivers/acpi/hotplug/acpihp_drv.h b/drivers/acpi/hotplug/acpihp_drv.h
index 2ec2547..aa64c91 100644
--- a/drivers/acpi/hotplug/acpihp_drv.h
+++ b/drivers/acpi/hotplug/acpihp_drv.h
@@ -95,4 +95,6 @@ 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);
 
+void acpihp_drv_handle_event(acpi_handle handle, u32 event, void *context);
+
 #endif	/* __ACPIHP_DRV_H__ */
diff --git a/drivers/acpi/hotplug/drv_main.c b/drivers/acpi/hotplug/drv_main.c
index bd5c97c..1935357 100644
--- a/drivers/acpi/hotplug/drv_main.c
+++ b/drivers/acpi/hotplug/drv_main.c
@@ -207,19 +207,12 @@ static void acpihp_drv_remove_devices(struct acpihp_slot *slot)
 		acpihp_remove_device_list(&slot->dev_lists[type]);
 }
 
-/* Handle ACPI device hotplug notifications */
-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_drv_handle_event, slot);
 	ACPIHP_SLOT_DEBUG(slot, "%s to install event handler.\n",
 			  ACPI_SUCCESS(status) ? "succeeds" : "fails");
 
@@ -231,7 +224,7 @@ 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_drv_handle_event);
 	ACPIHP_SLOT_DEBUG(slot, "%s to uninstall event handler.\n",
 			  ACPI_SUCCESS(status) ? "succeeds" : "fails");
 }
diff --git a/drivers/acpi/hotplug/event.c b/drivers/acpi/hotplug/event.c
new file mode 100644
index 0000000..a401b10
--- /dev/null
+++ b/drivers/acpi/hotplug/event.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 Huawei Tech. Co., Ltd.
+ * Copyright (C) 2012 Jiang Liu <jiang.liu@huawei.com>
+ *
+ * This file is based on pci_root_hp.c from Yinghai Lu <yinghai@kernel.org>
+ * and modified by Jiang Liu <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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_hotplug.h>
+#include "acpihp_drv.h"
+
+static bool acpihp_notify_userspace;
+module_param_named(notify_userspace, acpihp_notify_userspace, bool,
+		   S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(notify_userspace, "relay hotplug event to userspace helper");
+
+struct acpihp_hotplug_work {
+	u32 event;
+	struct acpihp_slot *slot;
+	struct acpihp_slot_drv *data;
+	struct work_struct work;
+	struct module *owner;
+};
+
+/*
+ * Queue the event handler onto the kacpi_hotplug_wq, otherwise it may
+ * cause deadlock.
+ */
+static int acpihp_alloc_hotplug_work(struct acpihp_slot *slot,
+				     struct acpihp_slot_drv *data, u32 event,
+				     void (*func)(struct work_struct *work))
+{
+	int ret = -ENOMEM;
+	struct acpihp_hotplug_work *hp_work;
+
+	hp_work = kzalloc(sizeof(*hp_work), GFP_KERNEL);
+	if (hp_work) {
+		hp_work->slot = slot;
+		hp_work->data = data;
+		hp_work->event = event;
+		hp_work->owner = THIS_MODULE;
+		__module_get(hp_work->owner);
+
+		INIT_WORK(&hp_work->work, func);
+		if (queue_work(kacpi_hotplug_wq, &hp_work->work)) {
+			ret = 0;
+		} else {
+			module_put(hp_work->owner);
+			kfree(hp_work);
+		}
+	}
+
+	return ret;
+}
+
+static void acpihp_drv_event_handler(struct work_struct *work)
+{
+	u32 event;
+	struct acpihp_slot *slot;
+	struct acpihp_slot_drv *data;
+	struct acpihp_hotplug_work *hp_work;
+	enum acpihp_drv_cmd cmd = ACPIHP_DRV_CMD_NOOP;
+
+	hp_work = container_of(work, struct acpihp_hotplug_work, work);
+	slot = hp_work->slot;
+	data = hp_work->data;
+	event = hp_work->event;
+
+	switch (event) {
+	case ACPI_NOTIFY_BUS_CHECK:
+		/* bus enumerate */
+		ACPIHP_SLOT_DEBUG(slot, "Bus check notification.\n");
+		cmd = ACPIHP_DRV_CMD_CONFIGURE;
+		break;
+
+	case ACPI_NOTIFY_DEVICE_CHECK:
+		/* device check */
+		ACPIHP_SLOT_DEBUG(slot, "Device check notification.\n");
+		cmd = ACPIHP_DRV_CMD_CONFIGURE;
+		break;
+
+	case ACPI_NOTIFY_EJECT_REQUEST:
+		/* request device eject */
+		ACPIHP_SLOT_DEBUG(slot, "Device eject notification.\n");
+		cmd = ACPIHP_DRV_CMD_POWEROFF;
+		break;
+
+	default:
+		BUG_ON(event);
+		break;
+	}
+
+	if (acpihp_drv_change_state(slot, cmd))
+		ACPIHP_SLOT_WARN(slot,
+			"fails to handle hotplug event 0x%x.\n", event);
+
+	module_put(hp_work->owner);
+	kfree(hp_work);
+}
+
+void acpihp_drv_handle_event(acpi_handle handle, u32 event, void *context)
+{
+	int ret;
+	struct acpihp_slot *slot = context;
+	struct acpihp_slot_drv *data = NULL;
+	char objname[64];
+	struct acpi_buffer buffer = { .length = sizeof(objname),
+				      .pointer = objname };
+
+	acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
+	if (event != ACPI_NOTIFY_BUS_CHECK &&
+	    event != ACPI_NOTIFY_DEVICE_CHECK &&
+	    event != ACPI_NOTIFY_EJECT_REQUEST) {
+		ACPIHP_DEBUG("unsupported system event type 0x%x for %s.\n",
+			     event, objname);
+		return;
+	}
+
+	acpihp_drv_get_data(slot, &data);
+	BUG_ON(data == NULL);
+
+	/*
+	 * Send hotplug events to userspace helper, so they could
+	 * be handled more flexibly.
+	 */
+	if (acpihp_notify_userspace) {
+		ret = acpi_bus_generate_netlink_event("LNXSLOT", slot->name,
+						      event, 0);
+		if (ret)
+			ACPIHP_SLOT_WARN(slot,
+				"fails to send hotplug event to userspace.\n");
+		return;
+	}
+
+	/* Queue event onto kacpi_hotplug_wq */
+	if (acpihp_alloc_hotplug_work(slot, data, event,
+				      acpihp_drv_event_handler))
+		ACPIHP_WARN("fails to queue hotplug event 0x%x for %s.\n",
+			    event, objname);
+}
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2012-11-04 12:53 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-11-04 12:50 [ACPIHP PATCH part2 00/13] introduce ACPI based system device hotplug driver Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 01/13] ACPIHP: introduce interfaces to scan and walk ACPI devices attached to a slot Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 02/13] ACPIHP: use klist to manage " Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 03/13] ACPIHP: add callbacks into acpi_device_ops to support new hotplug framework Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 04/13] ACPIHP: provide interfaces to manage driver data associated with hotplug slots Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 05/13] ACPIHP: implement utility interfaces to support system device hotplug Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 06/13] ACPIHP: implement ACPI system device hotplug driver skeleton Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 07/13] ACPIHP: analyse dependencies among ACPI hotplug slots Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 08/13] ACPIHP: provide interface to cancel inprogress hotplug operations Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 09/13] ACPIHP: configure/unconfigure system devices attached to a hotplug slot Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 10/13] ACPIHP: implement the core state machine to manage hotplug slots Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 11/13] ACPIHP: block ACPI device driver from unloading when doing hotplug Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 12/13] ACPIHP: implement sysfs interfaces for system device hotplug Jiang Liu
2012-11-04 12:50 ` [ACPIHP PATCH part2 13/13] ACPIHP: handle ACPI device hotplug events Jiang Liu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).