linux-acpi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Len Brown <lenb@kernel.org>
To: linux-acpi@vger.kernel.org
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>, Len Brown <len.brown@intel.com>
Subject: [PATCH 53/60] ACPI / PM: Fix reference counting of power resources
Date: Mon, 25 Oct 2010 02:21:01 -0400	[thread overview]
Message-ID: <3e384ee6c687cb397581ee8f9440fc8220cfac80.1287987547.git.len.brown@intel.com> (raw)
In-Reply-To: <1287987668-17584-1-git-send-email-lenb@kernel.org>
In-Reply-To: <a210080195c95ebca2a517ee3057d71607aa65e0.1287987547.git.len.brown@intel.com>

From: Rafael J. Wysocki <rjw@sisk.pl>

The reference counting of ACPI power resources is currently broken
for a few reasons.  First, instead of using a simple reference
counter per power resource it uses a list of objects representing
refereces to the given power resource from devices.  This leads to
the second breakage, because it prevents power resources from
being referenced more than once by one device, which is necessary
if the device is configured to signal wakeup.  Namely, when putting
the device into a low power state we first call
acpi_enable_wakeup_device_power() that should reference count power
resources needed for signaling wakeup and then we call
acpi_power_transition() to power off the device.  The latter call
drops references to the device's power resources, possibly including
the ones added by acpi_enable_wakeup_device_power(), so the device
can't signal wakeup as a result.  Apart from this, the locking
in acpi_power_on() and acpi_power_off_device() doesn't prevent
all possible races from happening, which may be problematic for
runtime PM and asynchronous suspend and resume.

Fix the problem by using a counter for power resources reference
counting and putting the evaluation of ACPI _ON and _OFF methods
under the power resource mutex.

Reported-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Len Brown <len.brown@intel.com>
---
 drivers/acpi/power.c |  167 +++++++++++++++++++++-----------------------------
 1 files changed, 70 insertions(+), 97 deletions(-)

diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c
index 844c155..67dedee 100644
--- a/drivers/acpi/power.c
+++ b/drivers/acpi/power.c
@@ -80,18 +80,13 @@ static struct acpi_driver acpi_power_driver = {
 		},
 };
 
-struct acpi_power_reference {
-	struct list_head node;
-	struct acpi_device *device;
-};
-
 struct acpi_power_resource {
 	struct acpi_device * device;
 	acpi_bus_id name;
 	u32 system_level;
 	u32 order;
+	unsigned int ref_count;
 	struct mutex resource_lock;
-	struct list_head reference;
 };
 
 static struct list_head acpi_power_resource_list;
@@ -184,101 +179,89 @@ static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
 	return result;
 }
 
-static int acpi_power_on(acpi_handle handle, struct acpi_device *dev)
+static int __acpi_power_on(struct acpi_power_resource *resource)
 {
-	int result = 0;
-	int found = 0;
 	acpi_status status = AE_OK;
-	struct acpi_power_resource *resource = NULL;
-	struct list_head *node, *next;
-	struct acpi_power_reference *ref;
 
+	status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	/* Update the power resource's _device_ power state */
+	resource->device->power.state = ACPI_STATE_D0;
+
+	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Power resource [%s] turned on\n",
+			  resource->name));
+
+	return 0;
+}
+
+static int acpi_power_on(acpi_handle handle)
+{
+	int result = 0;
+	struct acpi_power_resource *resource = NULL;
 
 	result = acpi_power_get_context(handle, &resource);
 	if (result)
 		return result;
 
 	mutex_lock(&resource->resource_lock);
-	list_for_each_safe(node, next, &resource->reference) {
-		ref = container_of(node, struct acpi_power_reference, node);
-		if (dev->handle == ref->device->handle) {
-			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] already referenced by resource [%s]\n",
-				  dev->pnp.bus_id, resource->name));
-			found = 1;
-			break;
-		}
-	}
 
-	if (!found) {
-		ref = kmalloc(sizeof (struct acpi_power_reference),
-		    irqs_disabled() ? GFP_ATOMIC : GFP_KERNEL);
-		if (!ref) {
-			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "kmalloc() failed\n"));
-			mutex_unlock(&resource->resource_lock);
-			return -ENOMEM;
-		}
-		list_add_tail(&ref->node, &resource->reference);
-		ref->device = dev;
-		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] added to resource [%s] references\n",
-			  dev->pnp.bus_id, resource->name));
+	if (resource->ref_count++) {
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Power resource [%s] already on",
+				  resource->name));
+	} else {
+		result = __acpi_power_on(resource);
 	}
-	mutex_unlock(&resource->resource_lock);
 
-	status = acpi_evaluate_object(resource->device->handle, "_ON", NULL, NULL);
-	if (ACPI_FAILURE(status))
-		return -ENODEV;
-
-	/* Update the power resource's _device_ power state */
-	resource->device->power.state = ACPI_STATE_D0;
+	mutex_unlock(&resource->resource_lock);
 
-	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned on\n",
-			  resource->name));
 	return 0;
 }
 
-static int acpi_power_off_device(acpi_handle handle, struct acpi_device *dev)
+static int acpi_power_off_device(acpi_handle handle)
 {
 	int result = 0;
 	acpi_status status = AE_OK;
 	struct acpi_power_resource *resource = NULL;
-	struct list_head *node, *next;
-	struct acpi_power_reference *ref;
 
 	result = acpi_power_get_context(handle, &resource);
 	if (result)
 		return result;
 
 	mutex_lock(&resource->resource_lock);
-	list_for_each_safe(node, next, &resource->reference) {
-		ref = container_of(node, struct acpi_power_reference, node);
-		if (dev->handle == ref->device->handle) {
-			list_del(&ref->node);
-			kfree(ref);
-			ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] removed from resource [%s] references\n",
-			    dev->pnp.bus_id, resource->name));
-			break;
-		}
+
+	if (!resource->ref_count) {
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Power resource [%s] already off",
+				  resource->name));
+		goto unlock;
 	}
 
-	if (!list_empty(&resource->reference)) {
-		ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Cannot turn resource [%s] off - resource is in use\n",
-		    resource->name));
-		mutex_unlock(&resource->resource_lock);
-		return 0;
+	if (--resource->ref_count) {
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Power resource [%s] still in use\n",
+				  resource->name));
+		goto unlock;
 	}
-	mutex_unlock(&resource->resource_lock);
 
 	status = acpi_evaluate_object(resource->device->handle, "_OFF", NULL, NULL);
-	if (ACPI_FAILURE(status))
-		return -ENODEV;
+	if (ACPI_FAILURE(status)) {
+		result = -ENODEV;
+	} else {
+		/* Update the power resource's _device_ power state */
+		resource->device->power.state = ACPI_STATE_D3;
 
-	/* Update the power resource's _device_ power state */
-	resource->device->power.state = ACPI_STATE_D3;
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Power resource [%s] turned off\n",
+				  resource->name));
+	}
 
-	ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Resource [%s] turned off\n",
-			  resource->name));
+ unlock:
+	mutex_unlock(&resource->resource_lock);
 
-	return 0;
+	return result;
 }
 
 /**
@@ -364,7 +347,7 @@ int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state)
 
 	/* Open power resource */
 	for (i = 0; i < dev->wakeup.resources.count; i++) {
-		int ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
+		int ret = acpi_power_on(dev->wakeup.resources.handles[i]);
 		if (ret) {
 			printk(KERN_ERR PREFIX "Transition power state\n");
 			dev->wakeup.flags.valid = 0;
@@ -420,7 +403,7 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev)
 	/* Close power resource */
 	for (i = 0; i < dev->wakeup.resources.count; i++) {
 		int ret = acpi_power_off_device(
-				dev->wakeup.resources.handles[i], dev);
+				dev->wakeup.resources.handles[i]);
 		if (ret) {
 			printk(KERN_ERR PREFIX "Transition power state\n");
 			dev->wakeup.flags.valid = 0;
@@ -500,7 +483,7 @@ int acpi_power_transition(struct acpi_device *device, int state)
 	 * (e.g. so the device doesn't lose power while transitioning).
 	 */
 	for (i = 0; i < tl->count; i++) {
-		result = acpi_power_on(tl->handles[i], device);
+		result = acpi_power_on(tl->handles[i]);
 		if (result)
 			goto end;
 	}
@@ -513,7 +496,7 @@ int acpi_power_transition(struct acpi_device *device, int state)
 	 * Then we dereference all power resources used in the current list.
 	 */
 	for (i = 0; i < cl->count; i++) {
-		result = acpi_power_off_device(cl->handles[i], device);
+		result = acpi_power_off_device(cl->handles[i]);
 		if (result)
 			goto end;
 	}
@@ -551,7 +534,6 @@ static int acpi_power_add(struct acpi_device *device)
 
 	resource->device = device;
 	mutex_init(&resource->resource_lock);
-	INIT_LIST_HEAD(&resource->reference);
 	strcpy(resource->name, device->pnp.bus_id);
 	strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
 	strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
@@ -594,22 +576,14 @@ static int acpi_power_add(struct acpi_device *device)
 
 static int acpi_power_remove(struct acpi_device *device, int type)
 {
-	struct acpi_power_resource *resource = NULL;
-	struct list_head *node, *next;
+	struct acpi_power_resource *resource;
 
-
-	if (!device || !acpi_driver_data(device))
+	if (!device)
 		return -EINVAL;
 
 	resource = acpi_driver_data(device);
-
-	mutex_lock(&resource->resource_lock);
-	list_for_each_safe(node, next, &resource->reference) {
-		struct acpi_power_reference *ref = container_of(node, struct acpi_power_reference, node);
-		list_del(&ref->node);
-		kfree(ref);
-	}
-	mutex_unlock(&resource->resource_lock);
+	if (!resource)
+		return -EINVAL;
 
 	kfree(resource);
 
@@ -619,29 +593,28 @@ static int acpi_power_remove(struct acpi_device *device, int type)
 static int acpi_power_resume(struct acpi_device *device)
 {
 	int result = 0, state;
-	struct acpi_power_resource *resource = NULL;
-	struct acpi_power_reference *ref;
+	struct acpi_power_resource *resource;
 
-	if (!device || !acpi_driver_data(device))
+	if (!device)
 		return -EINVAL;
 
 	resource = acpi_driver_data(device);
+	if (!resource)
+		return -EINVAL;
+
+	mutex_lock(&resource->resource_lock);
 
 	result = acpi_power_get_state(device->handle, &state);
 	if (result)
-		return result;
+		goto unlock;
 
-	mutex_lock(&resource->resource_lock);
-	if (state == ACPI_POWER_RESOURCE_STATE_OFF &&
-	    !list_empty(&resource->reference)) {
-		ref = container_of(resource->reference.next, struct acpi_power_reference, node);
-		mutex_unlock(&resource->resource_lock);
-		result = acpi_power_on(device->handle, ref->device);
-		return result;
-	}
+	if (state == ACPI_POWER_RESOURCE_STATE_OFF && resource->ref_count)
+		result = __acpi_power_on(resource);
 
+ unlock:
 	mutex_unlock(&resource->resource_lock);
-	return 0;
+
+	return result;
 }
 
 int __init acpi_power_init(void)
-- 
1.7.3.2.90.gd4c43


  parent reply	other threads:[~2010-10-25  8:11 UTC|newest]

Thread overview: 62+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-10-25  6:20 ACPI patches for 2.6.37-merge Len Brown
2010-10-25  6:20 ` [PATCH 01/60] ACPI / ACPICA: Defer enabling of runtime GPEs (v3) Len Brown
2010-10-25  6:20   ` [PATCH 02/60] ACPICA: Fix acpi_os_read_pci_configuration prototype Len Brown
2010-10-25  6:20   ` [PATCH 03/60] ACPICA: Revert "Revert "Enable multi-byte EC transfers Len Brown
2010-10-25  6:20   ` [PATCH 04/60] ACPICA/ACPI: Add new host interfaces for _OSI support Len Brown
2010-10-25  6:20   ` [PATCH 05/60] ACPICA: Update version to 20100806 Len Brown
2010-10-25  6:20   ` [PATCH 06/60] ACPICA: Obsolete the acpi_os_derive_pci_id OSL interface Len Brown
2010-10-25  6:20   ` [PATCH 07/60] ACPICA: Add ACPI_INLINE configuration parameter Len Brown
2010-10-25  6:20   ` [PATCH 08/60] ACPICA: Make acpi_thread_id no longer configurable, always u64 Len Brown
2010-10-25  6:20   ` [PATCH 09/60] ACPICA: Update math module; no functional change Len Brown
2010-10-25  6:20   ` [PATCH 10/60] ACPICA: Make acpi_gbl_system_awake_and_running publically available Len Brown
2010-10-25  6:20   ` [PATCH 11/60] ACPICA: iASL/Disassembler: Write ACPI errors to stderr instead of output file Len Brown
2010-10-25  6:20   ` [PATCH 12/60] ACPICA: Add repair for _HID and _CID strings Len Brown
2010-10-25  6:20   ` [PATCH 13/60] ACPICA: Increase configurability of error messages Len Brown
2010-10-25  6:20   ` [PATCH 14/60] ACPICA: Update version to 20100915 Len Brown
2010-10-25  6:20   ` [PATCH 15/60] PNP: log PNP resources, as we do for PCI Len Brown
2010-10-25  6:20   ` [PATCH 16/60] PNPACPI: cope with invalid device IDs Len Brown
2010-10-25  6:20   ` [PATCH 17/60] ACPI: Remove unused #define ACPI_PROCESSOR_FILE_POWER Len Brown
2010-10-25  6:20   ` [PATCH 18/60] ACPI: Do not export hid/modalias sysfs file for ACPI objects without a HID Len Brown
2010-10-25  6:20   ` [PATCH 19/60] ACPI/PNP: A HID value of an object never changes -> make it const Len Brown
2010-10-25  6:20   ` [PATCH 20/60] acpi-cpufreq: fix a memleak when unloading driver Len Brown
2010-10-25  6:20   ` [PATCH 21/60] ACPI / PM: Fix problems with acpi_pm_device_sleep_state() Len Brown
2010-10-25  6:20   ` [PATCH 22/60] ACPI: add FW_BUG to OSI(Linux) message Len Brown
2010-10-25  6:20   ` [PATCH 23/60] ACPI ac/battery/sbs: sysfs I/F always built in, procfs I/F disabled by default Len Brown
2010-10-25  6:20   ` [PATCH 24/60] ACPI fan: remove deprecated procfs I/F Len Brown
2010-10-25  6:20   ` [PATCH 25/60] ACPI thermal: " Len Brown
2010-10-25  6:20   ` [PATCH 26/60] ACPI video: " Len Brown
2010-10-25  6:20   ` [PATCH 27/60] ACPI processor: make /proc/acpi/processor/*/throttle depends on CONFIG_ACPI_PROCFS Len Brown
2010-10-25  6:20   ` [PATCH 28/60] ACPI: remove unused declaration of proc_fs.h Len Brown
2010-10-25  6:20   ` [PATCH 29/60] ACPICA: Comment update; no functional change Len Brown
2010-10-25  6:20   ` [PATCH 30/60] ACPICA: Change type of _TZ from ThermalZone to Device Len Brown
2010-10-25  6:20   ` [PATCH 31/60] ACPICA: Eliminate duplicate code in acpi_ut_execute_* functions Len Brown
2010-10-25  6:20   ` [PATCH 32/60] ACPICA: Add Vista SP2 to supported _OSI strings Len Brown
2010-10-25  6:20   ` [PATCH 33/60] ACPICA: Clear PCIEXP_WAKE_STS when clearing ACPI events Len Brown
2010-10-25  6:20   ` [PATCH 34/60] ACPICA: Update version to 20101013 Len Brown
2010-10-25  6:20   ` [PATCH 35/60] ACPI: Only processor needs CPU_IDLE Len Brown
2010-10-25  6:20   ` [PATCH 36/60] ACPI: delete dedicated MAINTAINERS entries for ACPI EC and BATTERY drivers Len Brown
2010-10-25  6:20   ` [PATCH 37/60] ACPI: remove dead code Len Brown
2010-10-25  6:20   ` [PATCH 38/60] ACPI: static sleep_states[] and acpi_gts_bfs_check Len Brown
2010-10-25  6:20   ` [PATCH 39/60] ACPI: thermal: remove unused limit code Len Brown
2010-10-25  6:20   ` [PATCH 40/60] ACPI dock: move some functions to .init.text Len Brown
2010-10-25  6:20   ` [PATCH 41/60] ACPI: Make Embedded Controller command timeout delay configurable Len Brown
2010-10-25  6:20   ` [PATCH 42/60] ACPI battery: support percentage battery remaining capacity Len Brown
2010-10-25  6:20   ` [PATCH 43/60] Subject: [PATCH] ACPICA: Fix Scope() op in module level code Len Brown
2010-10-25  6:20   ` [PATCH 44/60] ACPI, APEI, Add ERST record ID cache Len Brown
2010-10-25  8:56     ` Ingo Molnar
2010-10-25  6:20   ` [PATCH 45/60] Add lock-less version of bitmap_set/clear Len Brown
2010-10-25  6:20   ` [PATCH 46/60] lock-less NULL terminated single list implementation Len Brown
2010-10-25  6:20   ` [PATCH 47/60] lock-less general memory allocator Len Brown
2010-10-25  6:20   ` [PATCH 48/60] Hardware error device core Len Brown
2010-10-25  6:20   ` [PATCH 49/60] Hardware error record persistent support Len Brown
2010-10-25  6:20   ` [PATCH 50/60] ACPI, APEI, Use ERST for hardware error persisting before panic Len Brown
2010-10-25  6:20   ` [PATCH 51/60] ACPI, APEI, Report GHES error record with hardware error device core Len Brown
2010-10-25  6:21   ` [PATCH 52/60] ACPI, APEI, Generic Hardware Error Source POLL/IRQ/NMI notification type support Len Brown
2010-10-25  6:21   ` Len Brown [this message]
2010-10-25  6:21   ` [PATCH 54/60] ACPI / Battery: Return -ENODEV for unknown values in get_property() Len Brown
2010-10-25  6:21   ` [PATCH 55/60] ACPI: Fix ioremap size for MMIO reads and writes Len Brown
2010-10-25  6:21   ` [PATCH 56/60] ACPI: Maintain a list of ACPI memory mapped I/O remappings Len Brown
2010-10-25  6:21   ` [PATCH 57/60] ACPI: Add interfaces for ioremapping/iounmapping ACPI registers Len Brown
2010-10-25  6:21   ` [PATCH 58/60] ACPI: Pre-map 'system event' related register blocks Len Brown
2010-10-25  6:21   ` [PATCH 59/60] ACPI: Convert simple locking to RCU based locking Len Brown
2010-10-25  6:21   ` [PATCH 60/60] ACPI: Page based coalescing of I/O remappings optimization Len Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3e384ee6c687cb397581ee8f9440fc8220cfac80.1287987547.git.len.brown@intel.com \
    --to=lenb@kernel.org \
    --cc=len.brown@intel.com \
    --cc=linux-acpi@vger.kernel.org \
    --cc=rjw@sisk.pl \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).