diff options
author | Lv Zheng <lv.zheng@intel.com> | 2014-06-14 20:42:07 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-07-07 06:55:32 -0400 |
commit | c0d653412fc8450370167a3268b78fc772ff9c87 (patch) | |
tree | 043cf8a830a99c488aa7ce67bd5ff7d7d5831727 /drivers/acpi | |
parent | 9b80f0f73ae1583c22325ede341c74195847618c (diff) |
ACPI / EC: Fix race condition in ec_transaction_completed()
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, the IBF=0 waiter race need
not be handled any more. It's now safe 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() is now only invoked by the ec_poll() where
the ec->curr is ensured to be different from 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.
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>
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/ec.c | 30 |
1 files changed, 17 insertions, 13 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index d016ea31b8e9..49d89909b4ed 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c | |||
@@ -158,16 +158,17 @@ static int ec_transaction_completed(struct acpi_ec *ec) | |||
158 | unsigned long flags; | 158 | unsigned long flags; |
159 | int ret = 0; | 159 | int ret = 0; |
160 | spin_lock_irqsave(&ec->lock, flags); | 160 | spin_lock_irqsave(&ec->lock, flags); |
161 | if (!ec->curr || (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) | 161 | if (ec->curr && (ec->curr->flags & ACPI_EC_COMMAND_COMPLETE)) |
162 | ret = 1; | 162 | ret = 1; |
163 | spin_unlock_irqrestore(&ec->lock, flags); | 163 | spin_unlock_irqrestore(&ec->lock, flags); |
164 | return ret; | 164 | return ret; |
165 | } | 165 | } |
166 | 166 | ||
167 | static void advance_transaction(struct acpi_ec *ec) | 167 | static bool advance_transaction(struct acpi_ec *ec) |
168 | { | 168 | { |
169 | struct transaction *t; | 169 | struct transaction *t; |
170 | u8 status; | 170 | u8 status; |
171 | bool wakeup = false; | ||
171 | 172 | ||
172 | pr_debug("===== %s =====\n", in_interrupt() ? "IRQ" : "TASK"); | 173 | pr_debug("===== %s =====\n", in_interrupt() ? "IRQ" : "TASK"); |
173 | status = acpi_ec_read_status(ec); | 174 | status = acpi_ec_read_status(ec); |
@@ -183,21 +184,25 @@ static void advance_transaction(struct acpi_ec *ec) | |||
183 | } else if (t->rlen > t->ri) { | 184 | } else if (t->rlen > t->ri) { |
184 | if ((status & ACPI_EC_FLAG_OBF) == 1) { | 185 | if ((status & ACPI_EC_FLAG_OBF) == 1) { |
185 | t->rdata[t->ri++] = acpi_ec_read_data(ec); | 186 | t->rdata[t->ri++] = acpi_ec_read_data(ec); |
186 | if (t->rlen == t->ri) | 187 | if (t->rlen == t->ri) { |
187 | t->flags |= ACPI_EC_COMMAND_COMPLETE; | 188 | t->flags |= ACPI_EC_COMMAND_COMPLETE; |
189 | wakeup = true; | ||
190 | } | ||
188 | } else | 191 | } else |
189 | goto err; | 192 | goto err; |
190 | } else if (t->wlen == t->wi && | 193 | } else if (t->wlen == t->wi && |
191 | (status & ACPI_EC_FLAG_IBF) == 0) | 194 | (status & ACPI_EC_FLAG_IBF) == 0) { |
192 | t->flags |= ACPI_EC_COMMAND_COMPLETE; | 195 | t->flags |= ACPI_EC_COMMAND_COMPLETE; |
193 | return; | 196 | wakeup = true; |
197 | } | ||
198 | return wakeup; | ||
194 | } else { | 199 | } else { |
195 | if ((status & ACPI_EC_FLAG_IBF) == 0) { | 200 | if ((status & ACPI_EC_FLAG_IBF) == 0) { |
196 | acpi_ec_write_cmd(ec, t->command); | 201 | acpi_ec_write_cmd(ec, t->command); |
197 | t->flags |= ACPI_EC_COMMAND_POLL; | 202 | t->flags |= ACPI_EC_COMMAND_POLL; |
198 | } else | 203 | } else |
199 | goto err; | 204 | goto err; |
200 | return; | 205 | return wakeup; |
201 | } | 206 | } |
202 | err: | 207 | err: |
203 | /* | 208 | /* |
@@ -208,13 +213,14 @@ err: | |||
208 | if (in_interrupt() && t) | 213 | if (in_interrupt() && t) |
209 | ++t->irq_count; | 214 | ++t->irq_count; |
210 | } | 215 | } |
216 | return wakeup; | ||
211 | } | 217 | } |
212 | 218 | ||
213 | static void start_transaction(struct acpi_ec *ec) | 219 | static void start_transaction(struct acpi_ec *ec) |
214 | { | 220 | { |
215 | ec->curr->irq_count = ec->curr->wi = ec->curr->ri = 0; | 221 | ec->curr->irq_count = ec->curr->wi = ec->curr->ri = 0; |
216 | ec->curr->flags = 0; | 222 | ec->curr->flags = 0; |
217 | advance_transaction(ec); | 223 | (void)advance_transaction(ec); |
218 | } | 224 | } |
219 | 225 | ||
220 | static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data); | 226 | static int acpi_ec_sync_query(struct acpi_ec *ec, u8 *data); |
@@ -248,7 +254,7 @@ static int ec_poll(struct acpi_ec *ec) | |||
248 | return 0; | 254 | return 0; |
249 | } | 255 | } |
250 | spin_lock_irqsave(&ec->lock, flags); | 256 | spin_lock_irqsave(&ec->lock, flags); |
251 | advance_transaction(ec); | 257 | (void)advance_transaction(ec); |
252 | spin_unlock_irqrestore(&ec->lock, flags); | 258 | spin_unlock_irqrestore(&ec->lock, flags); |
253 | } while (time_before(jiffies, delay)); | 259 | } while (time_before(jiffies, delay)); |
254 | pr_debug("controller reset, restart transaction\n"); | 260 | pr_debug("controller reset, restart transaction\n"); |
@@ -627,12 +633,10 @@ static u32 acpi_ec_gpe_handler(acpi_handle gpe_device, | |||
627 | struct acpi_ec *ec = data; | 633 | struct acpi_ec *ec = data; |
628 | 634 | ||
629 | spin_lock_irqsave(&ec->lock, flags); | 635 | spin_lock_irqsave(&ec->lock, flags); |
630 | advance_transaction(ec); | 636 | if (advance_transaction(ec)) |
631 | spin_unlock_irqrestore(&ec->lock, flags); | ||
632 | if (ec_transaction_completed(ec)) { | ||
633 | wake_up(&ec->wait); | 637 | wake_up(&ec->wait); |
634 | ec_check_sci(ec, acpi_ec_read_status(ec)); | 638 | spin_unlock_irqrestore(&ec->lock, flags); |
635 | } | 639 | ec_check_sci(ec, acpi_ec_read_status(ec)); |
636 | return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; | 640 | return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; |
637 | } | 641 | } |
638 | 642 | ||