Chuck Ebbert e00d736
From: Rafael J. Wysocki <rjw@sisk.pl>
Chuck Ebbert e00d736
Date: Thu, 8 Apr 2010 23:39:40 +0000 (+0200)
Chuck Ebbert e00d736
Subject: ACPI / EC / PM: Fix race between EC transactions and system suspend
Chuck Ebbert e00d736
X-Git-Tag: v2.6.35-rc2~39^2~3
Chuck Ebbert e00d736
X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=d5a64513c6a171262082c250592c062e97a2c693
Chuck Ebbert e00d736
Chuck Ebbert e00d736
ACPI / EC / PM: Fix race between EC transactions and system suspend
Chuck Ebbert e00d736
Chuck Ebbert e00d736
There still is a race that may result in suspending the system in
Chuck Ebbert e00d736
the middle of an EC transaction in progress, which leads to problems
Chuck Ebbert e00d736
(like the kernel thinking that the ACPI global lock is held during
Chuck Ebbert e00d736
resume while in fact it's not).
Chuck Ebbert e00d736
Chuck Ebbert e00d736
To remove the race condition, modify the ACPI platform suspend and
Chuck Ebbert e00d736
hibernate callbacks so that EC transactions are blocked right after
Chuck Ebbert e00d736
executing the _PTS global control method and are allowed to happen
Chuck Ebbert e00d736
again right after the low-level wakeup.
Chuck Ebbert e00d736
Chuck Ebbert e00d736
Introduce acpi_pm_freeze() that will disable GPEs, wait until the
Chuck Ebbert e00d736
event queues are empty and block EC transactions.  Use it wherever
Chuck Ebbert e00d736
GPEs are disabled in preparation for switching local interrupts off.
Chuck Ebbert e00d736
Introduce acpi_pm_thaw() that will allow EC transactions to happen
Chuck Ebbert e00d736
again and enable runtime GPEs.  Use it to balance acpi_pm_freeze()
Chuck Ebbert e00d736
wherever necessary.
Chuck Ebbert e00d736
Chuck Ebbert e00d736
In addition to that use acpi_ec_resume_transactions_early() to
Chuck Ebbert e00d736
unblock EC transactions as early as reasonably possible during
Chuck Ebbert e00d736
resume.  Also unblock EC transactions in acpi_hibernation_finish()
Chuck Ebbert e00d736
and in the analogous suspend routine to make sure that the EC
Chuck Ebbert e00d736
transactions are enabled in all error paths.
Chuck Ebbert e00d736
Chuck Ebbert e00d736
Fixes https://bugzilla.kernel.org/show_bug.cgi?id=14668
Chuck Ebbert e00d736
Chuck Ebbert e00d736
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Chuck Ebbert e00d736
Reported-and-tested-by: Maxim Levitsky <maximlevitsky@gmail.com>
Chuck Ebbert e00d736
Signed-off-by: Len Brown <len.brown@intel.com>
Chuck Ebbert e00d736
---
Chuck Ebbert e00d736
Chuck Ebbert e00d736
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
Chuck Ebbert e00d736
index f2234db..2c2b73a 100644
Chuck Ebbert e00d736
--- a/drivers/acpi/ec.c
Chuck Ebbert e00d736
+++ b/drivers/acpi/ec.c
Chuck Ebbert e00d736
@@ -485,6 +485,16 @@ void acpi_ec_resume_transactions(void)
Chuck Ebbert e00d736
 	mutex_unlock(&ec->lock);
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
+void acpi_ec_resume_transactions_early(void)
Chuck Ebbert e00d736
+{
Chuck Ebbert e00d736
+	/*
Chuck Ebbert e00d736
+	 * Allow transactions to happen again (this function is called from
Chuck Ebbert e00d736
+	 * atomic context during wakeup, so we don't need to acquire the mutex).
Chuck Ebbert e00d736
+	 */
Chuck Ebbert e00d736
+	if (first_ec)
Chuck Ebbert e00d736
+		clear_bit(EC_FLAGS_FROZEN, &first_ec->flags);
Chuck Ebbert e00d736
+}
Chuck Ebbert e00d736
+
Chuck Ebbert e00d736
 static int acpi_ec_query_unlocked(struct acpi_ec *ec, u8 * data)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
 	int result;
Chuck Ebbert e00d736
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
Chuck Ebbert e00d736
index e284113..0ec48c7 100644
Chuck Ebbert e00d736
--- a/drivers/acpi/internal.h
Chuck Ebbert e00d736
+++ b/drivers/acpi/internal.h
Chuck Ebbert e00d736
@@ -51,6 +51,7 @@ int acpi_ec_ecdt_probe(void);
Chuck Ebbert e00d736
 int acpi_boot_ec_enable(void);
Chuck Ebbert e00d736
 void acpi_ec_suspend_transactions(void);
Chuck Ebbert e00d736
 void acpi_ec_resume_transactions(void);
Chuck Ebbert e00d736
+void acpi_ec_resume_transactions_early(void);
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 /*--------------------------------------------------------------------------
Chuck Ebbert e00d736
                                   Suspend/Resume
Chuck Ebbert e00d736
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
Chuck Ebbert e00d736
index baa76bb..24741ac 100644
Chuck Ebbert e00d736
--- a/drivers/acpi/sleep.c
Chuck Ebbert e00d736
+++ b/drivers/acpi/sleep.c
Chuck Ebbert e00d736
@@ -110,11 +110,13 @@ void __init acpi_old_suspend_ordering(void)
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 /**
Chuck Ebbert e00d736
- *	acpi_pm_disable_gpes - Disable the GPEs.
Chuck Ebbert e00d736
+ * acpi_pm_freeze - Disable the GPEs and suspend EC transactions.
Chuck Ebbert e00d736
  */
Chuck Ebbert e00d736
-static int acpi_pm_disable_gpes(void)
Chuck Ebbert e00d736
+static int acpi_pm_freeze(void)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
 	acpi_disable_all_gpes();
Chuck Ebbert e00d736
+	acpi_os_wait_events_complete(NULL);
Chuck Ebbert e00d736
+	acpi_ec_suspend_transactions();
Chuck Ebbert e00d736
 	return 0;
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
@@ -142,7 +144,8 @@ static int acpi_pm_prepare(void)
Chuck Ebbert e00d736
 	int error = __acpi_pm_prepare();
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 	if (!error)
Chuck Ebbert e00d736
-		acpi_disable_all_gpes();
Chuck Ebbert e00d736
+		acpi_pm_freeze();
Chuck Ebbert e00d736
+
Chuck Ebbert e00d736
 	return error;
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
@@ -275,6 +278,8 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
Chuck Ebbert e00d736
 	 * acpi_leave_sleep_state will reenable specific GPEs later
Chuck Ebbert e00d736
 	 */
Chuck Ebbert e00d736
 	acpi_disable_all_gpes();
Chuck Ebbert e00d736
+	/* Allow EC transactions to happen. */
Chuck Ebbert e00d736
+	acpi_ec_resume_transactions_early();
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 	local_irq_restore(flags);
Chuck Ebbert e00d736
 	printk(KERN_DEBUG "Back to C!\n");
Chuck Ebbert e00d736
@@ -286,6 +291,12 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
Chuck Ebbert e00d736
 	return ACPI_SUCCESS(status) ? 0 : -EFAULT;
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
+static void acpi_suspend_finish(void)
Chuck Ebbert e00d736
+{
Chuck Ebbert e00d736
+	acpi_ec_resume_transactions();
Chuck Ebbert e00d736
+	acpi_pm_finish();
Chuck Ebbert e00d736
+}
Chuck Ebbert e00d736
+
Chuck Ebbert e00d736
 static int acpi_suspend_state_valid(suspend_state_t pm_state)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
 	u32 acpi_state;
Chuck Ebbert e00d736
@@ -307,7 +318,7 @@ static struct platform_suspend_ops acpi_suspend_ops = {
Chuck Ebbert e00d736
 	.begin = acpi_suspend_begin,
Chuck Ebbert e00d736
 	.prepare_late = acpi_pm_prepare,
Chuck Ebbert e00d736
 	.enter = acpi_suspend_enter,
Chuck Ebbert e00d736
-	.wake = acpi_pm_finish,
Chuck Ebbert e00d736
+	.wake = acpi_suspend_finish,
Chuck Ebbert e00d736
 	.end = acpi_pm_end,
Chuck Ebbert e00d736
 };
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
@@ -333,9 +344,9 @@ static int acpi_suspend_begin_old(suspend_state_t pm_state)
Chuck Ebbert e00d736
 static struct platform_suspend_ops acpi_suspend_ops_old = {
Chuck Ebbert e00d736
 	.valid = acpi_suspend_state_valid,
Chuck Ebbert e00d736
 	.begin = acpi_suspend_begin_old,
Chuck Ebbert e00d736
-	.prepare_late = acpi_pm_disable_gpes,
Chuck Ebbert e00d736
+	.prepare_late = acpi_pm_freeze,
Chuck Ebbert e00d736
 	.enter = acpi_suspend_enter,
Chuck Ebbert e00d736
-	.wake = acpi_pm_finish,
Chuck Ebbert e00d736
+	.wake = acpi_suspend_finish,
Chuck Ebbert e00d736
 	.end = acpi_pm_end,
Chuck Ebbert e00d736
 	.recover = acpi_pm_finish,
Chuck Ebbert e00d736
 };
Chuck Ebbert e00d736
@@ -586,6 +597,7 @@ static int acpi_hibernation_enter(void)
Chuck Ebbert e00d736
 static void acpi_hibernation_finish(void)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
 	hibernate_nvs_free();
Chuck Ebbert e00d736
+	acpi_ec_resume_transactions();
Chuck Ebbert e00d736
 	acpi_pm_finish();
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
@@ -606,17 +618,11 @@ static void acpi_hibernation_leave(void)
Chuck Ebbert e00d736
 	}
Chuck Ebbert e00d736
 	/* Restore the NVS memory area */
Chuck Ebbert e00d736
 	hibernate_nvs_restore();
Chuck Ebbert e00d736
+	/* Allow EC transactions to happen. */
Chuck Ebbert e00d736
+	acpi_ec_resume_transactions_early();
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
-static int acpi_pm_pre_restore(void)
Chuck Ebbert e00d736
-{
Chuck Ebbert e00d736
-	acpi_disable_all_gpes();
Chuck Ebbert e00d736
-	acpi_os_wait_events_complete(NULL);
Chuck Ebbert e00d736
-	acpi_ec_suspend_transactions();
Chuck Ebbert e00d736
-	return 0;
Chuck Ebbert e00d736
-}
Chuck Ebbert e00d736
-
Chuck Ebbert e00d736
-static void acpi_pm_restore_cleanup(void)
Chuck Ebbert e00d736
+static void acpi_pm_thaw(void)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
 	acpi_ec_resume_transactions();
Chuck Ebbert e00d736
 	acpi_enable_all_runtime_gpes();
Chuck Ebbert e00d736
@@ -630,8 +636,8 @@ static struct platform_hibernation_ops acpi_hibernation_ops = {
Chuck Ebbert e00d736
 	.prepare = acpi_pm_prepare,
Chuck Ebbert e00d736
 	.enter = acpi_hibernation_enter,
Chuck Ebbert e00d736
 	.leave = acpi_hibernation_leave,
Chuck Ebbert e00d736
-	.pre_restore = acpi_pm_pre_restore,
Chuck Ebbert e00d736
-	.restore_cleanup = acpi_pm_restore_cleanup,
Chuck Ebbert e00d736
+	.pre_restore = acpi_pm_freeze,
Chuck Ebbert e00d736
+	.restore_cleanup = acpi_pm_thaw,
Chuck Ebbert e00d736
 };
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 /**
Chuck Ebbert e00d736
@@ -663,12 +669,9 @@ static int acpi_hibernation_begin_old(void)
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 static int acpi_hibernation_pre_snapshot_old(void)
Chuck Ebbert e00d736
 {
Chuck Ebbert e00d736
-	int error = acpi_pm_disable_gpes();
Chuck Ebbert e00d736
-
Chuck Ebbert e00d736
-	if (!error)
Chuck Ebbert e00d736
-		hibernate_nvs_save();
Chuck Ebbert e00d736
-
Chuck Ebbert e00d736
-	return error;
Chuck Ebbert e00d736
+	acpi_pm_freeze();
Chuck Ebbert e00d736
+	hibernate_nvs_save();
Chuck Ebbert e00d736
+	return 0;
Chuck Ebbert e00d736
 }
Chuck Ebbert e00d736
 
Chuck Ebbert e00d736
 /*
Chuck Ebbert e00d736
@@ -680,11 +683,11 @@ static struct platform_hibernation_ops acpi_hibernation_ops_old = {
Chuck Ebbert e00d736
 	.end = acpi_pm_end,
Chuck Ebbert e00d736
 	.pre_snapshot = acpi_hibernation_pre_snapshot_old,
Chuck Ebbert e00d736
 	.finish = acpi_hibernation_finish,
Chuck Ebbert e00d736
-	.prepare = acpi_pm_disable_gpes,
Chuck Ebbert e00d736
+	.prepare = acpi_pm_freeze,
Chuck Ebbert e00d736
 	.enter = acpi_hibernation_enter,
Chuck Ebbert e00d736
 	.leave = acpi_hibernation_leave,
Chuck Ebbert e00d736
-	.pre_restore = acpi_pm_pre_restore,
Chuck Ebbert e00d736
-	.restore_cleanup = acpi_pm_restore_cleanup,
Chuck Ebbert e00d736
+	.pre_restore = acpi_pm_freeze,
Chuck Ebbert e00d736
+	.restore_cleanup = acpi_pm_thaw,
Chuck Ebbert e00d736
 	.recover = acpi_pm_finish,
Chuck Ebbert e00d736
 };
Chuck Ebbert e00d736
 #endif /* CONFIG_HIBERNATION */