aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2010-03-03 19:52:58 -0500
committerLen Brown <len.brown@intel.com>2010-03-08 14:15:51 -0500
commitf6bb13aa1ea3bb26a4c783822347873f085b9000 (patch)
treee0350530685e0719d4318f052982aa4340a00650
parent60b341b778cc2929df16c0a504c91621b3c6a4ad (diff)
ACPI / EC / PM: Close race between EC and resume from hibernation
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>
-rw-r--r--drivers/acpi/ec.c33
-rw-r--r--drivers/acpi/internal.h2
-rw-r--r--drivers/acpi/sleep.c19
3 files changed, 48 insertions, 6 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index d6471bb6852f..19f93e114225 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -76,8 +76,9 @@ enum ec_command {
76enum { 76enum {
77 EC_FLAGS_QUERY_PENDING, /* Query is pending */ 77 EC_FLAGS_QUERY_PENDING, /* Query is pending */
78 EC_FLAGS_GPE_STORM, /* GPE storm detected */ 78 EC_FLAGS_GPE_STORM, /* GPE storm detected */
79 EC_FLAGS_HANDLERS_INSTALLED /* Handlers for GPE and 79 EC_FLAGS_HANDLERS_INSTALLED, /* Handlers for GPE and
80 * OpReg are installed */ 80 * OpReg are installed */
81 EC_FLAGS_FROZEN, /* Transactions are suspended */
81}; 82};
82 83
83/* If we find an EC via the ECDT, we need to keep a ptr to its context */ 84/* 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)
291 if (t->rdata) 292 if (t->rdata)
292 memset(t->rdata, 0, t->rlen); 293 memset(t->rdata, 0, t->rlen);
293 mutex_lock(&ec->lock); 294 mutex_lock(&ec->lock);
295 if (test_bit(EC_FLAGS_FROZEN, &ec->flags)) {
296 status = -EINVAL;
297 goto unlock;
298 }
294 if (ec->global_lock) { 299 if (ec->global_lock) {
295 status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk); 300 status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk);
296 if (ACPI_FAILURE(status)) { 301 if (ACPI_FAILURE(status)) {
@@ -445,6 +450,32 @@ int ec_transaction(u8 command,
445 450
446EXPORT_SYMBOL(ec_transaction); 451EXPORT_SYMBOL(ec_transaction);
447 452
453void acpi_ec_suspend_transactions(void)
454{
455 struct acpi_ec *ec = first_ec;
456
457 if (!ec)
458 return;
459
460 mutex_lock(&ec->lock);
461 /* Prevent transactions from being carried out */
462 set_bit(EC_FLAGS_FROZEN, &ec->flags);
463 mutex_unlock(&ec->lock);
464}
465
466void acpi_ec_resume_transactions(void)
467{
468 struct acpi_ec *ec = first_ec;
469
470 if (!ec)
471 return;
472
473 mutex_lock(&ec->lock);
474 /* Allow transactions to be carried out again */
475 clear_bit(EC_FLAGS_FROZEN, &ec->flags);
476 mutex_unlock(&ec->lock);
477}
478
448static int acpi_ec_query_unlocked(struct acpi_ec *ec, u8 * data) 479static int acpi_ec_query_unlocked(struct acpi_ec *ec, u8 * data)
449{ 480{
450 int result; 481 int result;
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index cb28e0502acc..78742460f1fc 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -51,6 +51,8 @@ void acpi_early_processor_set_pdc(void);
51int acpi_ec_init(void); 51int acpi_ec_init(void);
52int acpi_ec_ecdt_probe(void); 52int acpi_ec_ecdt_probe(void);
53int acpi_boot_ec_enable(void); 53int acpi_boot_ec_enable(void);
54void acpi_ec_suspend_transactions(void);
55void acpi_ec_resume_transactions(void);
54 56
55/*-------------------------------------------------------------------------- 57/*--------------------------------------------------------------------------
56 Suspend/Resume 58 Suspend/Resume
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
index 79d33d908b5a..f01f8e84fd3d 100644
--- a/drivers/acpi/sleep.c
+++ b/drivers/acpi/sleep.c
@@ -552,8 +552,17 @@ static void acpi_hibernation_leave(void)
552 hibernate_nvs_restore(); 552 hibernate_nvs_restore();
553} 553}
554 554
555static void acpi_pm_enable_gpes(void) 555static int acpi_pm_pre_restore(void)
556{ 556{
557 acpi_disable_all_gpes();
558 acpi_os_wait_events_complete(NULL);
559 acpi_ec_suspend_transactions();
560 return 0;
561}
562
563static void acpi_pm_restore_cleanup(void)
564{
565 acpi_ec_resume_transactions();
557 acpi_enable_all_runtime_gpes(); 566 acpi_enable_all_runtime_gpes();
558} 567}
559 568
@@ -565,8 +574,8 @@ static struct platform_hibernation_ops acpi_hibernation_ops = {
565 .prepare = acpi_pm_prepare, 574 .prepare = acpi_pm_prepare,
566 .enter = acpi_hibernation_enter, 575 .enter = acpi_hibernation_enter,
567 .leave = acpi_hibernation_leave, 576 .leave = acpi_hibernation_leave,
568 .pre_restore = acpi_pm_disable_gpes, 577 .pre_restore = acpi_pm_pre_restore,
569 .restore_cleanup = acpi_pm_enable_gpes, 578 .restore_cleanup = acpi_pm_restore_cleanup,
570}; 579};
571 580
572/** 581/**
@@ -618,8 +627,8 @@ static struct platform_hibernation_ops acpi_hibernation_ops_old = {
618 .prepare = acpi_pm_disable_gpes, 627 .prepare = acpi_pm_disable_gpes,
619 .enter = acpi_hibernation_enter, 628 .enter = acpi_hibernation_enter,
620 .leave = acpi_hibernation_leave, 629 .leave = acpi_hibernation_leave,
621 .pre_restore = acpi_pm_disable_gpes, 630 .pre_restore = acpi_pm_pre_restore,
622 .restore_cleanup = acpi_pm_enable_gpes, 631 .restore_cleanup = acpi_pm_restore_cleanup,
623 .recover = acpi_pm_finish, 632 .recover = acpi_pm_finish,
624}; 633};
625#endif /* CONFIG_HIBERNATION */ 634#endif /* CONFIG_HIBERNATION */