]> bbs.cooldavid.org Git - net-next-2.6.git/commitdiff
ACPI / EC / PM: Close race between EC and resume from hibernation
authorRafael J. Wysocki <rjw@sisk.pl>
Thu, 4 Mar 2010 00:52:58 +0000 (01:52 +0100)
committerLen Brown <len.brown@intel.com>
Mon, 8 Mar 2010 19:15:51 +0000 (14:15 -0500)
There is a race between resume from hibernation and the EC driver
that may result in restoring the hibernation image in the middle of
an EC transaction in progress, which in turn may lead to
unpredictable behavior of the platform.

To remove that race condition, add a helpers for suspending and
resuming EC transactions in a safe way to be executed by the ACPI
platform hibernate pre-restore and restore cleanup callbacks.

http://bugzilla.kernel.org/show_bug.cgi?id=14668

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Reported-and-tested-by: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
drivers/acpi/ec.c
drivers/acpi/internal.h
drivers/acpi/sleep.c

index d6471bb6852fb58802a5c9153dcb63fb2c642429..19f93e114225f7cca2d712a532fc2f8286625f45 100644 (file)
@@ -76,8 +76,9 @@ enum ec_command {
 enum {
        EC_FLAGS_QUERY_PENDING,         /* Query is pending */
        EC_FLAGS_GPE_STORM,             /* GPE storm detected */
-       EC_FLAGS_HANDLERS_INSTALLED     /* Handlers for GPE and
+       EC_FLAGS_HANDLERS_INSTALLED,    /* Handlers for GPE and
                                         * OpReg are installed */
+       EC_FLAGS_FROZEN,                /* Transactions are suspended */
 };
 
 /* If we find an EC via the ECDT, we need to keep a ptr to its context */
@@ -291,6 +292,10 @@ static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t)
        if (t->rdata)
                memset(t->rdata, 0, t->rlen);
        mutex_lock(&ec->lock);
+       if (test_bit(EC_FLAGS_FROZEN, &ec->flags)) {
+               status = -EINVAL;
+               goto unlock;
+       }
        if (ec->global_lock) {
                status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
                if (ACPI_FAILURE(status)) {
@@ -445,6 +450,32 @@ int ec_transaction(u8 command,
 
 EXPORT_SYMBOL(ec_transaction);
 
+void acpi_ec_suspend_transactions(void)
+{
+       struct acpi_ec *ec = first_ec;
+
+       if (!ec)
+               return;
+
+       mutex_lock(&ec->lock);
+       /* Prevent transactions from being carried out */
+       set_bit(EC_FLAGS_FROZEN, &ec->flags);
+       mutex_unlock(&ec->lock);
+}
+
+void acpi_ec_resume_transactions(void)
+{
+       struct acpi_ec *ec = first_ec;
+
+       if (!ec)
+               return;
+
+       mutex_lock(&ec->lock);
+       /* Allow transactions to be carried out again */
+       clear_bit(EC_FLAGS_FROZEN, &ec->flags);
+       mutex_unlock(&ec->lock);
+}
+
 static int acpi_ec_query_unlocked(struct acpi_ec *ec, u8 * data)
 {
        int result;
index cb28e0502acc346af5233ebeaa195b723a995713..78742460f1fcffd788d6ab4b711866331a7cb6c6 100644 (file)
@@ -51,6 +51,8 @@ void acpi_early_processor_set_pdc(void);
 int acpi_ec_init(void);
 int acpi_ec_ecdt_probe(void);
 int acpi_boot_ec_enable(void);
+void acpi_ec_suspend_transactions(void);
+void acpi_ec_resume_transactions(void);
 
 /*--------------------------------------------------------------------------
                                   Suspend/Resume
index 79d33d908b5a1d2dc586a6536107ffea393c467d..f01f8e84fd3dbe8bf370a9942af79a159ad865f5 100644 (file)
@@ -552,8 +552,17 @@ static void acpi_hibernation_leave(void)
        hibernate_nvs_restore();
 }
 
-static void acpi_pm_enable_gpes(void)
+static int acpi_pm_pre_restore(void)
 {
+       acpi_disable_all_gpes();
+       acpi_os_wait_events_complete(NULL);
+       acpi_ec_suspend_transactions();
+       return 0;
+}
+
+static void acpi_pm_restore_cleanup(void)
+{
+       acpi_ec_resume_transactions();
        acpi_enable_all_runtime_gpes();
 }
 
@@ -565,8 +574,8 @@ static struct platform_hibernation_ops acpi_hibernation_ops = {
        .prepare = acpi_pm_prepare,
        .enter = acpi_hibernation_enter,
        .leave = acpi_hibernation_leave,
-       .pre_restore = acpi_pm_disable_gpes,
-       .restore_cleanup = acpi_pm_enable_gpes,
+       .pre_restore = acpi_pm_pre_restore,
+       .restore_cleanup = acpi_pm_restore_cleanup,
 };
 
 /**
@@ -618,8 +627,8 @@ static struct platform_hibernation_ops acpi_hibernation_ops_old = {
        .prepare = acpi_pm_disable_gpes,
        .enter = acpi_hibernation_enter,
        .leave = acpi_hibernation_leave,
-       .pre_restore = acpi_pm_disable_gpes,
-       .restore_cleanup = acpi_pm_enable_gpes,
+       .pre_restore = acpi_pm_pre_restore,
+       .restore_cleanup = acpi_pm_restore_cleanup,
        .recover = acpi_pm_finish,
 };
 #endif /* CONFIG_HIBERNATION */