From mboxrd@z Thu Jan 1 00:00:00 1970 From: Lv Zheng Subject: [PATCH 4/7] ACPI/EC: Fix a race condition in ec_transaction_completed(). Date: Fri, 13 Jun 2014 14:51:40 +0800 Message-ID: <5e4475707be93ee91ef4f42a67556b070758673d.1402641700.git.lv.zheng@intel.com> References: Return-path: In-Reply-To: Sender: linux-kernel-owner@vger.kernel.org To: "Rafael J. Wysocki" , Len Brown Cc: Lv Zheng , Lv Zheng , linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org List-Id: linux-acpi@vger.kernel.org There is a race condition in ec_transaction_completed(). When ec_transaction_completed() is called in the GPE handler, it could return true because of (ec->curr == NULL). Then the wake_up() invocation could complete the next command unexpectedly since there is no lock between the 2 invocations. With the previous cleanup, we now needn't to handle IBF=0 waiter race. It's now safe for us to return a flag from advance_condition() to indicate the requirement of wakeup, the flag is returned from a locked context. The ec_transaction_completed() now is only invoked by the ec_poll() where the ec->curr is ensured to be !NULL. After cleaning up, the EVT_SCI=1 check should be moved out of the wakeup condition so that an EVT_SCI raised with (ec->curr == NULL) can trigger a QR_SC command. Buglink: https://bugzilla.kernel.org/show_bug.cgi?id=70891 Signed-off-by: Lv Zheng Reported-and-tested-by: Gareth Williams Tested-by: Steffen Weber [zetalog: first affected by:] Cc: # 2.6.11: 7c6db4e0: ACPI: EC: do transaction from interrupt context [zetalog: cleanly applying to:] Cc: # 3.14.x: 42b946bb: ACPI / EC: disable GPE before removing GPE handler --- drivers/acpi/ec.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index d016ea3..f1fa899 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -158,16 +158,17 @@ static int ec_transaction_completed(struct acpi_ec *ec) unsigned long flags; int ret = 0; spin_lock_irqsave(&ec->lock, flags); - if (!ec->curr || (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) + if (ec->curr && (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) ret = 1; spin_unlock_irqrestore(&ec->lock, flags); return ret; } -static void advance_transaction(struct acpi_ec *ec) +static bool advance_transaction(struct acpi_ec *ec) { struct transaction *t; u8 status; + bool wakeup = false; pr_debug("===== %s =====\n", in_interrupt() ? "IRQ" : "TASK"); status = acpi_ec_read_status(ec); @@ -183,21 +184,25 @@ static void advance_transaction(struct acpi_ec *ec) } else if (t->rlen > t->ri) { if ((status & ACPI_EC_FLAG_OBF) == 1) { t->rdata[t->ri++] = acpi_ec_read_data(ec); - if (t->rlen == t->ri) + if (t->rlen == t->ri) { t->flags |= ACPI_EC_COMMAND_COMPLETE; + wakeup = true; + } } else goto err; } else if (t->wlen == t->wi && - (status & ACPI_EC_FLAG_IBF) == 0) + (status & ACPI_EC_FLAG_IBF) == 0) { t->flags |= ACPI_EC_COMMAND_COMPLETE; - return; + wakeup = true; + } + return wakeup; } else { if ((status & ACPI_EC_FLAG_IBF) == 0) { acpi_ec_write_cmd(ec, t->command); t->flags |= ACPI_EC_COMMAND_POLL; } else goto err; - return; + return wakeup; } err: /* @@ -208,13 +213,14 @@ err: if (in_interrupt() && t) ++t->irq_count; } + return wakeup; } static void start_transaction(struct acpi_ec *ec) { ec->curr->irq_count = ec->curr->wi = ec->curr->ri = 0; ec->curr->flags = 0; - advance_transaction(ec); + (void)advance_transaction(ec); } static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data); @@ -248,7 +254,7 @@ static int ec_poll(struct acpi_ec *ec) return 0; } spin_lock_irqsave(&ec->lock, flags); - advance_transaction(ec); + (void)advance_transaction(ec); spin_unlock_irqrestore(&ec->lock, flags); } while (time_before(jiffies, delay)); pr_debug("controller reset, restart transaction\n"); @@ -625,14 +631,14 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device, { unsigned long flags; struct acpi_ec *ec = data; + bool wakeup; spin_lock_irqsave(&ec->lock, flags); - advance_transaction(ec); + wakeup = advance_transaction(ec); spin_unlock_irqrestore(&ec->lock, flags); - if (ec_transaction_completed(ec)) { + if (wakeup) wake_up(&ec->wait); - ec_check_sci(ec, acpi_ec_read_status(ec)); - } + ec_check_sci(ec, acpi_ec_read_status(ec)); return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; } -- 1.7.10 From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752927AbaFMGvr (ORCPT ); Fri, 13 Jun 2014 02:51:47 -0400 Received: from mga03.intel.com ([143.182.124.21]:35619 "EHLO mga03.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752897AbaFMGvp (ORCPT ); Fri, 13 Jun 2014 02:51:45 -0400 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.01,470,1400050800"; d="scan'208";a="445043355" From: Lv Zheng To: "Rafael J. Wysocki" , Len Brown Cc: Lv Zheng , Lv Zheng , , linux-acpi@vger.kernel.org Subject: [PATCH 4/7] ACPI/EC: Fix a race condition in ec_transaction_completed(). Date: Fri, 13 Jun 2014 14:51:40 +0800 Message-Id: <5e4475707be93ee91ef4f42a67556b070758673d.1402641700.git.lv.zheng@intel.com> X-Mailer: git-send-email 1.7.10 In-Reply-To: References: Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org There is a race condition in ec_transaction_completed(). When ec_transaction_completed() is called in the GPE handler, it could return true because of (ec->curr == NULL). Then the wake_up() invocation could complete the next command unexpectedly since there is no lock between the 2 invocations. With the previous cleanup, we now needn't to handle IBF=0 waiter race. It's now safe for us to return a flag from advance_condition() to indicate the requirement of wakeup, the flag is returned from a locked context. The ec_transaction_completed() now is only invoked by the ec_poll() where the ec->curr is ensured to be !NULL. After cleaning up, the EVT_SCI=1 check should be moved out of the wakeup condition so that an EVT_SCI raised with (ec->curr == NULL) can trigger a QR_SC command. Buglink: https://bugzilla.kernel.org/show_bug.cgi?id=70891 Signed-off-by: Lv Zheng Reported-and-tested-by: Gareth Williams Tested-by: Steffen Weber [zetalog: first affected by:] Cc: # 2.6.11: 7c6db4e0: ACPI: EC: do transaction from interrupt context [zetalog: cleanly applying to:] Cc: # 3.14.x: 42b946bb: ACPI / EC: disable GPE before removing GPE handler --- drivers/acpi/ec.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index d016ea3..f1fa899 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -158,16 +158,17 @@ static int ec_transaction_completed(struct acpi_ec *ec) unsigned long flags; int ret = 0; spin_lock_irqsave(&ec->lock, flags); - if (!ec->curr || (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) + if (ec->curr && (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) ret = 1; spin_unlock_irqrestore(&ec->lock, flags); return ret; } -static void advance_transaction(struct acpi_ec *ec) +static bool advance_transaction(struct acpi_ec *ec) { struct transaction *t; u8 status; + bool wakeup = false; pr_debug("===== %s =====\n", in_interrupt() ? "IRQ" : "TASK"); status = acpi_ec_read_status(ec); @@ -183,21 +184,25 @@ static void advance_transaction(struct acpi_ec *ec) } else if (t->rlen > t->ri) { if ((status & ACPI_EC_FLAG_OBF) == 1) { t->rdata[t->ri++] = acpi_ec_read_data(ec); - if (t->rlen == t->ri) + if (t->rlen == t->ri) { t->flags |= ACPI_EC_COMMAND_COMPLETE; + wakeup = true; + } } else goto err; } else if (t->wlen == t->wi && - (status & ACPI_EC_FLAG_IBF) == 0) + (status & ACPI_EC_FLAG_IBF) == 0) { t->flags |= ACPI_EC_COMMAND_COMPLETE; - return; + wakeup = true; + } + return wakeup; } else { if ((status & ACPI_EC_FLAG_IBF) == 0) { acpi_ec_write_cmd(ec, t->command); t->flags |= ACPI_EC_COMMAND_POLL; } else goto err; - return; + return wakeup; } err: /* @@ -208,13 +213,14 @@ err: if (in_interrupt() && t) ++t->irq_count; } + return wakeup; } static void start_transaction(struct acpi_ec *ec) { ec->curr->irq_count = ec->curr->wi = ec->curr->ri = 0; ec->curr->flags = 0; - advance_transaction(ec); + (void)advance_transaction(ec); } static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data); @@ -248,7 +254,7 @@ static int ec_poll(struct acpi_ec *ec) return 0; } spin_lock_irqsave(&ec->lock, flags); - advance_transaction(ec); + (void)advance_transaction(ec); spin_unlock_irqrestore(&ec->lock, flags); } while (time_before(jiffies, delay)); pr_debug("controller reset, restart transaction\n"); @@ -625,14 +631,14 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device, { unsigned long flags; struct acpi_ec *ec = data; + bool wakeup; spin_lock_irqsave(&ec->lock, flags); - advance_transaction(ec); + wakeup = advance_transaction(ec); spin_unlock_irqrestore(&ec->lock, flags); - if (ec_transaction_completed(ec)) { + if (wakeup) wake_up(&ec->wait); - ec_check_sci(ec, acpi_ec_read_status(ec)); - } + ec_check_sci(ec, acpi_ec_read_status(ec)); return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; } -- 1.7.10