From 080e412cc0bdb9ef8e7a983d5e008537e1c4d36c Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:18:30 +0400 Subject: ACPI: EC: Replace atomic variables with bits Number of flags is about to be increased, so it is better to put them all into bits. No functional changes. Signed-off-by: Alexey Starikovskiy Acked-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/ec.c | 79 ++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 41 deletions(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 7b4178393e3..08fbe62a2db 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -65,7 +65,7 @@ enum ec_command { /* EC events */ enum ec_event { ACPI_EC_EVENT_OBF_1 = 1, /* Output buffer full */ - ACPI_EC_EVENT_IBF_0, /* Input buffer empty */ + ACPI_EC_EVENT_IBF_0, /* Input buffer empty */ }; #define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */ @@ -76,6 +76,11 @@ static enum ec_mode { EC_POLL, /* Input buffer empty */ } acpi_ec_mode = EC_INTR; +enum { + EC_FLAGS_WAIT_GPE = 0, /* Don't check status until GPE arrives */ + EC_FLAGS_QUERY_PENDING, /* Query is pending */ +}; + static int acpi_ec_remove(struct acpi_device *device, int type); static int acpi_ec_start(struct acpi_device *device); static int acpi_ec_stop(struct acpi_device *device, int type); @@ -116,9 +121,8 @@ static struct acpi_ec { unsigned long command_addr; unsigned long data_addr; unsigned long global_lock; + unsigned long flags; struct mutex lock; - atomic_t query_pending; - atomic_t event_count; wait_queue_head_t wait; struct list_head list; u8 handlers_installed; @@ -148,45 +152,42 @@ static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data) outb(data, ec->data_addr); } -static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event, - unsigned old_count) +static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event) { - u8 status = acpi_ec_read_status(ec); - if (old_count == atomic_read(&ec->event_count)) + if (test_bit(EC_FLAGS_WAIT_GPE, &ec->flags)) return 0; if (event == ACPI_EC_EVENT_OBF_1) { - if (status & ACPI_EC_FLAG_OBF) + if (acpi_ec_read_status(ec) & ACPI_EC_FLAG_OBF) return 1; } else if (event == ACPI_EC_EVENT_IBF_0) { - if (!(status & ACPI_EC_FLAG_IBF)) + if (!(acpi_ec_read_status(ec) & ACPI_EC_FLAG_IBF)) return 1; } return 0; } -static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, - unsigned count, int force_poll) +static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) { if (unlikely(force_poll) || acpi_ec_mode == EC_POLL) { unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); + clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); while (time_before(jiffies, delay)) { - if (acpi_ec_check_status(ec, event, 0)) + if (acpi_ec_check_status(ec, event)) return 0; } } else { - if (wait_event_timeout(ec->wait, - acpi_ec_check_status(ec, event, count), - msecs_to_jiffies(ACPI_EC_DELAY)) || - acpi_ec_check_status(ec, event, 0)) { + if (wait_event_timeout(ec->wait, acpi_ec_check_status(ec, event), + msecs_to_jiffies(ACPI_EC_DELAY))) + return 0; + clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); + if (acpi_ec_check_status(ec, event)) { return 0; - } else { - printk(KERN_ERR PREFIX "acpi_ec_wait timeout," - " status = %d, expect_event = %d\n", - acpi_ec_read_status(ec), event); } } - + printk(KERN_ERR PREFIX "acpi_ec_wait timeout," + " status = %d, expect_event = %d\n", + acpi_ec_read_status(ec), event); return -ETIME; } @@ -196,39 +197,38 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, int force_poll) { int result = 0; - unsigned count = atomic_read(&ec->event_count); + set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); acpi_ec_write_cmd(ec, command); for (; wdata_len > 0; --wdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count, force_poll); + result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, force_poll); if (result) { printk(KERN_ERR PREFIX "write_cmd timeout, command = %d\n", command); goto end; } - count = atomic_read(&ec->event_count); + set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); acpi_ec_write_data(ec, *(wdata++)); } if (!rdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, count, force_poll); + result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, force_poll); if (result) { printk(KERN_ERR PREFIX "finish-write timeout, command = %d\n", command); goto end; } - } else if (command == ACPI_EC_COMMAND_QUERY) { - atomic_set(&ec->query_pending, 0); - } + } else if (command == ACPI_EC_COMMAND_QUERY) + clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); for (; rdata_len > 0; --rdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, count, force_poll); + result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, force_poll); if (result) { printk(KERN_ERR PREFIX "read timeout, command = %d\n", command); goto end; } - count = atomic_read(&ec->event_count); + set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); *(rdata++) = acpi_ec_read_data(ec); } end: @@ -261,7 +261,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, /* Make sure GPE is enabled before doing transaction */ acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0, 0); + status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0); if (status) { printk(KERN_ERR PREFIX "input buffer is not empty, aborting transaction\n"); @@ -476,20 +476,18 @@ static void acpi_ec_gpe_query(void *ec_cxt) static u32 acpi_ec_gpe_handler(void *data) { acpi_status status = AE_OK; - u8 value; struct acpi_ec *ec = data; - atomic_inc(&ec->event_count); + clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); if (acpi_ec_mode == EC_INTR) { wake_up(&ec->wait); } - value = acpi_ec_read_status(ec); - if ((value & ACPI_EC_FLAG_SCI) && !atomic_read(&ec->query_pending)) { - atomic_set(&ec->query_pending, 1); - status = - acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query, ec); + if (acpi_ec_read_status(ec) & ACPI_EC_FLAG_SCI) { + if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) + status = acpi_os_execute(OSL_EC_BURST_HANDLER, + acpi_ec_gpe_query, ec); } return status == AE_OK ? @@ -642,8 +640,7 @@ static struct acpi_ec *make_acpi_ec(void) if (!ec) return NULL; - atomic_set(&ec->query_pending, 1); - atomic_set(&ec->event_count, 1); + ec->flags = 1 << EC_FLAGS_QUERY_PENDING; mutex_init(&ec->lock); init_waitqueue_head(&ec->wait); INIT_LIST_HEAD(&ec->list); @@ -833,7 +830,7 @@ static int acpi_ec_start(struct acpi_device *device) ret = ec_install_handlers(ec); /* EC is fully operational, allow queries */ - atomic_set(&ec->query_pending, 0); + clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); return ret; } -- cgit v1.2.3-70-g09d2 From 0c5d31f48e54b2e56e9cef8d49ffedaef1e0ea52 Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:18:36 +0400 Subject: ACPI: EC: Don't expect interrupt after last read There is no interrupt after last read according to spec, so don't set bit that we are expecting one. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown --- drivers/acpi/ec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 08fbe62a2db..5ce90ce22b5 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -228,7 +228,9 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, command); goto end; } - set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); + /* Don't expect GPE after last read */ + if (rdata_len > 1) + set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); *(rdata++) = acpi_ec_read_data(ec); } end: -- cgit v1.2.3-70-g09d2 From 7843932ac42899b936085beaea8620d4489b8b3f Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:18:43 +0400 Subject: ACPI: EC: auto select interrupt mode Start in POLL mode, and if we receive confirmation GPE, switch to INT mode. If confirmations are not sent, switch back to POLL. Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown --- Documentation/kernel-parameters.txt | 5 ---- drivers/acpi/ec.c | 51 ++++++++++++------------------------- 2 files changed, 16 insertions(+), 40 deletions(-) (limited to 'drivers/acpi/ec.c') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index a13d69b2217..727cc08f0f3 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -586,11 +586,6 @@ and is between 256 and 4096 characters. It is defined in the file eata= [HW,SCSI] - ec_intr= [HW,ACPI] ACPI Embedded Controller interrupt mode - Format: - 0: polling mode - non-0: interrupt mode (default) - edd= [EDD] Format: {"of[f]" | "sk[ipmbr]"} See comment in arch/i386/boot/edd.S diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 5ce90ce22b5..50d55fe71a3 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -71,14 +71,10 @@ enum ec_event { #define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */ #define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */ -static enum ec_mode { - EC_INTR = 1, /* Output buffer full */ - EC_POLL, /* Input buffer empty */ -} acpi_ec_mode = EC_INTR; - enum { EC_FLAGS_WAIT_GPE = 0, /* Don't check status until GPE arrives */ EC_FLAGS_QUERY_PENDING, /* Query is pending */ + EC_FLAGS_GPE_MODE, /* Expect GPE to be sent for status change */ }; static int acpi_ec_remove(struct acpi_device *device, int type); @@ -169,21 +165,23 @@ static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event) static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) { - if (unlikely(force_poll) || acpi_ec_mode == EC_POLL) { - unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); - clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - while (time_before(jiffies, delay)) { - if (acpi_ec_check_status(ec, event)) - return 0; - } - } else { + if (likely(test_bit(EC_FLAGS_GPE_MODE, &ec->flags)) && + likely(!force_poll)) { if (wait_event_timeout(ec->wait, acpi_ec_check_status(ec, event), msecs_to_jiffies(ACPI_EC_DELAY))) return 0; clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); if (acpi_ec_check_status(ec, event)) { + clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); return 0; } + } else { + unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); + clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); + while (time_before(jiffies, delay)) { + if (acpi_ec_check_status(ec, event)) + return 0; + } } printk(KERN_ERR PREFIX "acpi_ec_wait timeout," " status = %d, expect_event = %d\n", @@ -481,18 +479,17 @@ static u32 acpi_ec_gpe_handler(void *data) struct acpi_ec *ec = data; clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - - if (acpi_ec_mode == EC_INTR) { + if (test_bit(EC_FLAGS_GPE_MODE, &ec->flags)) wake_up(&ec->wait); - } if (acpi_ec_read_status(ec) & ACPI_EC_FLAG_SCI) { if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) status = acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query, ec); - } + } else if (unlikely(!test_bit(EC_FLAGS_GPE_MODE, &ec->flags))) + set_bit(EC_FLAGS_GPE_MODE, &ec->flags); - return status == AE_OK ? + return ACPI_SUCCESS(status) ? ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED; } @@ -923,20 +920,4 @@ static void __exit acpi_ec_exit(void) return; } -#endif /* 0 */ - -static int __init acpi_ec_set_intr_mode(char *str) -{ - int intr; - - if (!get_option(&str, &intr)) - return 0; - - acpi_ec_mode = (intr) ? EC_INTR : EC_POLL; - - printk(KERN_NOTICE PREFIX "%s mode.\n", intr ? "interrupt" : "polling"); - - return 1; -} - -__setup("ec_intr=", acpi_ec_set_intr_mode); +#endif /* 0 */ -- cgit v1.2.3-70-g09d2 From 1c55053c21706ccf1fdb26b4bb6d05c4a2782ffe Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:18:50 +0400 Subject: ACPI: EC: Don't re-enable GPE for each transaction. With the auto selection of operation mode, absence of GPEs does not really degrade performance, so let PM code to handle enabling/disabling GPEs. This is a revert of 5d57a6a55ec0bdcb952dbcd3f8ffcde8a3ee9413, which was meant to be temporary. Reference: http://bugzilla.kernel.org/show_bug.cgi?id=7977 Signed-off-by: Alexey Starikovskiy Acked-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/ec.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 50d55fe71a3..41a21fcdbcb 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -258,9 +258,6 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, } } - /* Make sure GPE is enabled before doing transaction */ - acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0); if (status) { printk(KERN_ERR PREFIX @@ -638,12 +635,10 @@ static struct acpi_ec *make_acpi_ec(void) struct acpi_ec *ec = kzalloc(sizeof(struct acpi_ec), GFP_KERNEL); if (!ec) return NULL; - ec->flags = 1 << EC_FLAGS_QUERY_PENDING; mutex_init(&ec->lock); init_waitqueue_head(&ec->wait); INIT_LIST_HEAD(&ec->list); - return ec; } -- cgit v1.2.3-70-g09d2 From 66c5f4e7367b0085652931b2f3366de29e7ff5ec Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:18:56 +0400 Subject: ACPI: EC: Add workaround for "optimized" controllers Some controllers do not send interrupts for OBF=1 event, but send them for IBF=0. Add workaround for them. Reference: http://bugzilla.kernel.org/show_bug.cgi?id=8459 Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown --- drivers/acpi/ec.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 41a21fcdbcb..202db575d5d 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -75,6 +75,7 @@ enum { EC_FLAGS_WAIT_GPE = 0, /* Don't check status until GPE arrives */ EC_FLAGS_QUERY_PENDING, /* Query is pending */ EC_FLAGS_GPE_MODE, /* Expect GPE to be sent for status change */ + EC_FLAGS_ONLY_IBF_GPE, /* Expect GPE only for IBF = 0 event */ }; static int acpi_ec_remove(struct acpi_device *device, int type); @@ -172,7 +173,12 @@ static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) return 0; clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); if (acpi_ec_check_status(ec, event)) { - clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); + if (event == ACPI_EC_EVENT_OBF_1) + /* miss OBF = 1 GPE, don't expect it anymore */ + set_bit(EC_FLAGS_ONLY_IBF_GPE, &ec->flags); + else + /* missing GPEs, switch back to poll mode */ + clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); return 0; } } else { @@ -220,6 +226,8 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); for (; rdata_len > 0; --rdata_len) { + if (test_bit(EC_FLAGS_ONLY_IBF_GPE, &ec->flags)) + force_poll = 1; result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, force_poll); if (result) { printk(KERN_ERR PREFIX "read timeout, command = %d\n", -- cgit v1.2.3-70-g09d2 From 95b937e3f52a7f5546c4bffe29886fe400bad1d1 Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Mon, 22 Oct 2007 14:19:03 +0400 Subject: ACPI: EC: Output changes to operational mode Insert printk() for every change in operational mode. Signed-off-by: Alexey Starikovskiy Acked-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/ec.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 202db575d5d..bf60b24ebf5 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -173,12 +173,17 @@ static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) return 0; clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); if (acpi_ec_check_status(ec, event)) { - if (event == ACPI_EC_EVENT_OBF_1) + if (event == ACPI_EC_EVENT_OBF_1) { /* miss OBF = 1 GPE, don't expect it anymore */ + printk(KERN_INFO PREFIX "missing OBF_1 confirmation," + "switching to degraded mode.\n"); set_bit(EC_FLAGS_ONLY_IBF_GPE, &ec->flags); - else + } else { /* missing GPEs, switch back to poll mode */ + printk(KERN_INFO PREFIX "missing IBF_1 confirmations," + "switch off interrupt mode.\n"); clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); + } return 0; } } else { @@ -491,8 +496,12 @@ static u32 acpi_ec_gpe_handler(void *data) if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) status = acpi_os_execute(OSL_EC_BURST_HANDLER, acpi_ec_gpe_query, ec); - } else if (unlikely(!test_bit(EC_FLAGS_GPE_MODE, &ec->flags))) + } else if (unlikely(!test_bit(EC_FLAGS_GPE_MODE, &ec->flags))) { + /* this is non-query, must be confirmation */ + printk(KERN_INFO PREFIX "non-query interrupt received," + " switching to interrupt mode\n"); set_bit(EC_FLAGS_GPE_MODE, &ec->flags); + } return ACPI_SUCCESS(status) ? ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED; @@ -740,6 +749,8 @@ static int acpi_ec_add(struct acpi_device *device) acpi_ec_add_fs(device); printk(KERN_INFO PREFIX "GPE = 0x%lx, I/O: command/status = 0x%lx, data = 0x%lx\n", ec->gpe, ec->command_addr, ec->data_addr); + printk(KERN_INFO PREFIX "driver started in %s mode\n", + (test_bit(EC_FLAGS_GPE_MODE, &ec->flags))?"interrupt":"poll"); return 0; } -- cgit v1.2.3-70-g09d2 From 1544fdbc857cbe8afca16a521d3254346befeb06 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Wed, 24 Oct 2007 18:26:00 +0200 Subject: ACPI: EC: fix use-after-free This patch fixes a use-after-free introduced by commit 30c08574da0ead1a47797ce028218ce5b2de61c7 (ACPI: EC: Add new query handler to list head) Spotted by the Coverity checker. Signed-off-by: Adrian Bunk Acked-by: Alexey Starikovskiy Signed-off-by: Len Brown --- drivers/acpi/ec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/acpi/ec.c') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index bf60b24ebf5..06b78e5e33a 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -445,9 +445,9 @@ EXPORT_SYMBOL_GPL(acpi_ec_add_query_handler); void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit) { - struct acpi_ec_query_handler *handler; + struct acpi_ec_query_handler *handler, *tmp; mutex_lock(&ec->lock); - list_for_each_entry(handler, &ec->list, node) { + list_for_each_entry_safe(handler, tmp, &ec->list, node) { if (query_bit == handler->query_bit) { list_del(&handler->node); kfree(handler); -- cgit v1.2.3-70-g09d2