aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLv Zheng <lv.zheng@intel.com>2014-06-14 20:41:17 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2014-07-07 06:50:25 -0400
commit66b42b78bc1e816f92b662e8888c89195e4199e1 (patch)
tree6e84a63d75aba9c01d34485df3ef8f8b2e3452ad
parentcd3de83f147601356395b57a8673e9c5ff1e59d1 (diff)
ACPI / EC: Avoid race condition related to advance_transaction()
The advance_transaction() will be invoked from the IRQ context GPE handler and the task context ec_poll(). The handling of this function is locked so that the EC state machine are ensured to be advanced sequentially. But there is a problem. Before invoking advance_transaction(), EC_SC(R) is read. Then for advance_transaction(), there could be race condition around the lock from both contexts. The first one reading the register could fail this race and when it passes the stale register value to the state machine advancement code, the hardware condition is totally different from when the register is read. And the hardware accesses determined from the wrong hardware status can break the EC state machine. And there could be cases that the functionalities of the platform firmware are seriously affected. For example: 1. When 2 EC_DATA(W) writes compete the IBF=0, the 2nd EC_DATA(W) write may be invalid due to IBF=1 after the 1st EC_DATA(W) write. Then the hardware will either refuse to respond a next EC_SC(W) write of the next command or discard the current WR_EC command when it receives a EC_SC(W) write of the next command. 2. When 1 EC_SC(W) write and 1 EC_DATA(W) write compete the IBF=0, the EC_DATA(W) write may be invalid due to IBF=1 after the EC_SC(W) write. The next EC_DATA(R) could never be responded by the hardware. This is the root cause of the reported issue. Fix this issue by moving the EC_SC(R) access into the lock so that we can ensure that the state machine is advanced consistently. Link: https://bugzilla.kernel.org/show_bug.cgi?id=70891 Link: https://bugzilla.kernel.org/show_bug.cgi?id=63931 Link: https://bugzilla.kernel.org/show_bug.cgi?id=59911 Reported-and-tested-by: Gareth Williams <gareth@garethwilliams.me.uk> Reported-and-tested-by: Hans de Goede <jwrdegoede@fedoraproject.org> Reported-by: Barton Xu <tank.xuhan@gmail.com> Tested-by: Steffen Weber <steffen.weber@gmail.com> Tested-by: Arthur Chen <axchen@nvidia.com> Signed-off-by: Lv Zheng <lv.zheng@intel.com> Cc: All applicable <stable@vger.kernel.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--drivers/acpi/ec.c12
1 files changed, 6 insertions, 6 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index ad11ba4a412d..762b4cc9d7b1 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -168,12 +168,15 @@ static void start_transaction(struct acpi_ec *ec)
168 acpi_ec_write_cmd(ec, ec->curr->command); 168 acpi_ec_write_cmd(ec, ec->curr->command);
169} 169}
170 170
171static void advance_transaction(struct acpi_ec *ec, u8 status) 171static void advance_transaction(struct acpi_ec *ec)
172{ 172{
173 unsigned long flags; 173 unsigned long flags;
174 struct transaction *t; 174 struct transaction *t;
175 u8 status;
175 176
176 spin_lock_irqsave(&ec->lock, flags); 177 spin_lock_irqsave(&ec->lock, flags);
178 pr_debug("===== %s =====\n", in_interrupt() ? "IRQ" : "TASK");
179 status = acpi_ec_read_status(ec);
177 t = ec->curr; 180 t = ec->curr;
178 if (!t) 181 if (!t)
179 goto unlock; 182 goto unlock;
@@ -236,7 +239,7 @@ static int ec_poll(struct acpi_ec *ec)
236 msecs_to_jiffies(1))) 239 msecs_to_jiffies(1)))
237 return 0; 240 return 0;
238 } 241 }
239 advance_transaction(ec, acpi_ec_read_status(ec)); 242 advance_transaction(ec);
240 } while (time_before(jiffies, delay)); 243 } while (time_before(jiffies, delay));
241 pr_debug("controller reset, restart transaction\n"); 244 pr_debug("controller reset, restart transaction\n");
242 spin_lock_irqsave(&ec->lock, flags); 245 spin_lock_irqsave(&ec->lock, flags);
@@ -635,11 +638,8 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device,
635 u32 gpe_number, void *data) 638 u32 gpe_number, void *data)
636{ 639{
637 struct acpi_ec *ec = data; 640 struct acpi_ec *ec = data;
638 u8 status = acpi_ec_read_status(ec);
639
640 pr_debug("~~~> interrupt, status:0x%02x\n", status);
641 641
642 advance_transaction(ec, status); 642 advance_transaction(ec);
643 if (ec_transaction_done(ec) && 643 if (ec_transaction_done(ec) &&
644 (acpi_ec_read_status(ec) & ACPI_EC_FLAG_IBF) == 0) { 644 (acpi_ec_read_status(ec) & ACPI_EC_FLAG_IBF) == 0) {
645 wake_up(&ec->wait); 645 wake_up(&ec->wait);