Skip to content

Commit

Permalink
ACPI / EC / PM: Close race between EC and resume from hibernation
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reported-and-tested-by: Maxim Levitsky <[email protected]>
Signed-off-by: Len Brown <[email protected]>
  • Loading branch information
rjwysocki authored and lenb committed Mar 8, 2010
1 parent 60b341b commit f6bb13a
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 6 deletions.
33 changes: 32 additions & 1 deletion drivers/acpi/ec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions drivers/acpi/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 14 additions & 5 deletions drivers/acpi/sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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,
};

/**
Expand Down Expand Up @@ -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 */
Expand Down

0 comments on commit f6bb13a

Please sign in to comment.