Commit 9e197219 authored by Alexey Starikovskiy's avatar Alexey Starikovskiy Committed by Len Brown

ACPI: ec: fix race in status register access

Delay the read of the EC status register until
after the event that caused it occurs -- otherwise
it is possible to read and act on stale status that was
associated with the previous event.

Do this with a perpetually incrementing "event_count" to detect
when a new event occurs and it is safe to read status.

There is no workaround for polling mode -- it is inherently
exposed to reading and acting on stale status, since it
doesn't have an interrupt to tell it the event completed.

http://bugzilla.kernel.org/show_bug.cgi?id=8110Signed-off-by: default avatarAlexey Starikovskiy <alexey.y.starikovskiy@intel.com>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent 08e15e81
...@@ -100,6 +100,7 @@ static struct acpi_ec { ...@@ -100,6 +100,7 @@ static struct acpi_ec {
unsigned long global_lock; unsigned long global_lock;
struct mutex lock; struct mutex lock;
atomic_t query_pending; atomic_t query_pending;
atomic_t event_count;
atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */ atomic_t leaving_burst; /* 0 : No, 1 : Yes, 2: abort */
wait_queue_head_t wait; wait_queue_head_t wait;
} *ec_ecdt; } *ec_ecdt;
...@@ -131,10 +132,12 @@ static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data) ...@@ -131,10 +132,12 @@ static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data)
outb(data, ec->data_addr); outb(data, ec->data_addr);
} }
static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event) static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event,
unsigned old_count)
{ {
u8 status = acpi_ec_read_status(ec); u8 status = acpi_ec_read_status(ec);
if (old_count == atomic_read(&ec->event_count))
return 0;
if (event == ACPI_EC_EVENT_OBF_1) { if (event == ACPI_EC_EVENT_OBF_1) {
if (status & ACPI_EC_FLAG_OBF) if (status & ACPI_EC_FLAG_OBF)
return 1; return 1;
...@@ -146,19 +149,19 @@ static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event) ...@@ -146,19 +149,19 @@ static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event)
return 0; return 0;
} }
static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event) static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, unsigned count)
{ {
if (acpi_ec_mode == EC_POLL) { if (acpi_ec_mode == EC_POLL) {
unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY);
while (time_before(jiffies, delay)) { while (time_before(jiffies, delay)) {
if (acpi_ec_check_status(ec, event)) if (acpi_ec_check_status(ec, event, 0))
return 0; return 0;
} }
} else { } else {
if (wait_event_timeout(ec->wait, if (wait_event_timeout(ec->wait,
acpi_ec_check_status(ec, event), acpi_ec_check_status(ec, event, count),
msecs_to_jiffies(ACPI_EC_DELAY)) || msecs_to_jiffies(ACPI_EC_DELAY)) ||
acpi_ec_check_status(ec, event)) { acpi_ec_check_status(ec, event, 0)) {
return 0; return 0;
} else { } else {
printk(KERN_ERR PREFIX "acpi_ec_wait timeout," printk(KERN_ERR PREFIX "acpi_ec_wait timeout,"
...@@ -225,21 +228,22 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, ...@@ -225,21 +228,22 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command,
u8 * rdata, unsigned rdata_len) u8 * rdata, unsigned rdata_len)
{ {
int result = 0; int result = 0;
unsigned count = atomic_read(&ec->event_count);
acpi_ec_write_cmd(ec, command); acpi_ec_write_cmd(ec, command);
for (; wdata_len > 0; --wdata_len) { for (; wdata_len > 0; --wdata_len) {
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count);
if (result) { if (result) {
printk(KERN_ERR PREFIX printk(KERN_ERR PREFIX
"write_cmd timeout, command = %d\n", command); "write_cmd timeout, command = %d\n", command);
goto end; goto end;
} }
count = atomic_read(&ec->event_count);
acpi_ec_write_data(ec, *(wdata++)); acpi_ec_write_data(ec, *(wdata++));
} }
if (!rdata_len) { if (!rdata_len) {
result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count);
if (result) { if (result) {
printk(KERN_ERR PREFIX printk(KERN_ERR PREFIX
"finish-write timeout, command = %d\n", command); "finish-write timeout, command = %d\n", command);
...@@ -250,13 +254,13 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, ...@@ -250,13 +254,13 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command,
} }
for (; rdata_len > 0; --rdata_len) { for (; rdata_len > 0; --rdata_len) {
result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1); result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, count);
if (result) { if (result) {
printk(KERN_ERR PREFIX "read timeout, command = %d\n", printk(KERN_ERR PREFIX "read timeout, command = %d\n",
command); command);
goto end; goto end;
} }
count = atomic_read(&ec->event_count);
*(rdata++) = acpi_ec_read_data(ec); *(rdata++) = acpi_ec_read_data(ec);
} }
end: end:
...@@ -288,7 +292,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, ...@@ -288,7 +292,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command,
/* Make sure GPE is enabled before doing transaction */ /* Make sure GPE is enabled before doing transaction */
acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR);
status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0); status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0);
if (status) { if (status) {
printk(KERN_DEBUG PREFIX printk(KERN_DEBUG PREFIX
"input buffer is not empty, aborting transaction\n"); "input buffer is not empty, aborting transaction\n");
...@@ -369,8 +373,8 @@ int ec_write(u8 addr, u8 val) ...@@ -369,8 +373,8 @@ int ec_write(u8 addr, u8 val)
EXPORT_SYMBOL(ec_write); EXPORT_SYMBOL(ec_write);
int ec_transaction(u8 command, int ec_transaction(u8 command,
const u8 * wdata, unsigned wdata_len, const u8 * wdata, unsigned wdata_len,
u8 * rdata, unsigned rdata_len) u8 * rdata, unsigned rdata_len)
{ {
struct acpi_ec *ec; struct acpi_ec *ec;
...@@ -435,7 +439,7 @@ static u32 acpi_ec_gpe_handler(void *data) ...@@ -435,7 +439,7 @@ static u32 acpi_ec_gpe_handler(void *data)
acpi_status status = AE_OK; acpi_status status = AE_OK;
u8 value; u8 value;
struct acpi_ec *ec = (struct acpi_ec *)data; struct acpi_ec *ec = (struct acpi_ec *)data;
atomic_inc(&ec->event_count);
if (acpi_ec_mode == EC_INTR) { if (acpi_ec_mode == EC_INTR) {
wake_up(&ec->wait); wake_up(&ec->wait);
} }
...@@ -633,6 +637,7 @@ static int acpi_ec_add(struct acpi_device *device) ...@@ -633,6 +637,7 @@ static int acpi_ec_add(struct acpi_device *device)
ec->uid = -1; ec->uid = -1;
mutex_init(&ec->lock); mutex_init(&ec->lock);
atomic_set(&ec->query_pending, 0); atomic_set(&ec->query_pending, 0);
atomic_set(&ec->event_count, 1);
if (acpi_ec_mode == EC_INTR) { if (acpi_ec_mode == EC_INTR) {
atomic_set(&ec->leaving_burst, 1); atomic_set(&ec->leaving_burst, 1);
init_waitqueue_head(&ec->wait); init_waitqueue_head(&ec->wait);
...@@ -807,6 +812,7 @@ acpi_fake_ecdt_callback(acpi_handle handle, ...@@ -807,6 +812,7 @@ acpi_fake_ecdt_callback(acpi_handle handle,
acpi_status status; acpi_status status;
mutex_init(&ec_ecdt->lock); mutex_init(&ec_ecdt->lock);
atomic_set(&ec_ecdt->event_count, 1);
if (acpi_ec_mode == EC_INTR) { if (acpi_ec_mode == EC_INTR) {
init_waitqueue_head(&ec_ecdt->wait); init_waitqueue_head(&ec_ecdt->wait);
} }
...@@ -888,6 +894,7 @@ static int __init acpi_ec_get_real_ecdt(void) ...@@ -888,6 +894,7 @@ static int __init acpi_ec_get_real_ecdt(void)
return -ENOMEM; return -ENOMEM;
mutex_init(&ec_ecdt->lock); mutex_init(&ec_ecdt->lock);
atomic_set(&ec_ecdt->event_count, 1);
if (acpi_ec_mode == EC_INTR) { if (acpi_ec_mode == EC_INTR) {
init_waitqueue_head(&ec_ecdt->wait); init_waitqueue_head(&ec_ecdt->wait);
} }
...@@ -1016,8 +1023,7 @@ static int __init acpi_ec_set_intr_mode(char *str) ...@@ -1016,8 +1023,7 @@ static int __init acpi_ec_set_intr_mode(char *str)
acpi_ec_mode = EC_POLL; acpi_ec_mode = EC_POLL;
} }
acpi_ec_driver.ops.add = acpi_ec_add; acpi_ec_driver.ops.add = acpi_ec_add;
printk(KERN_NOTICE PREFIX "%s mode.\n", printk(KERN_NOTICE PREFIX "%s mode.\n", intr ? "interrupt" : "polling");
intr ? "interrupt" : "polling");
return 1; return 1;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment