From d8d031a605bff183b76611e0d18e2ca7021fb99f Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Fri, 15 May 2015 14:16:42 +0800 Subject: ACPI / EC: Fix and clean up register access guarding logics. In the polling mode, EC driver shouldn't access the EC registers too frequently. Though this statement is concluded from the non-root caused bugs (see links below), we've maintained the register access guarding logics in the current EC driver. The guarding logics can be found here and there, makes it hard to root cause real timing issues. This patch collects the guarding logics into one single function so that all hidden logics related to this can be seen clearly. The current guarding related code also has several issues: 1. Per-transaction timestamp prevents inter-transaction guarding from being implemented in the same place. We have an inter-transaction udelay() in acpi_ec_transaction_unblocked(), this logic can be merged into ec_poll() if we can use per-device timestamp. This patch completes such merge to form a new ec_guard() function and collects all guarding related hidden logics in it. One hidden logic is: there is no inter-transaction guarding performed for non MSI quirk (wait polling mode), this patch skips inter-transaction guarding before wait_event_timeout() for the wait polling mode to reveal the hidden logic. The other hidden logic is: there is msleep() inter-transaction guarding performed when the GPE storming is observed. As after merging this commit: Commit: e1d4d90fc0313d3d58cbd7912c90f8ef24df45ff Subject: ACPI / EC: Refine command storm prevention support EC_FLAGS_COMMAND_STORM is ensured to be cleared after invoking acpi_ec_transaction_unlocked(), the msleep() guard logic will never happen now. Since no one complains such change, this logic is likely added during the old times where the EC race issues are not fixed and the bugs are false root-caused to the timing issue. This patch simply removes the out-dated logic. We can restore it by stop skipping inter-transaction guarding for wait polling mode. Two different delay values are defined for msleep() and udelay() while they are merged in this patch to 550us. 2. time_after() causes additional delay in the polling mode (can only be observed in noirq suspend/resume processes where polling mode is always used) before advance_transaction() is invoked ("wait polling" log is added before wait_event_timeout()). We can see 2 wait_event_timeout() invocations. This is because time_after() ensures a ">" validation while we only need a ">=" validation here: [ 86.739909] ACPI: Waking up from system sleep state S3 [ 86.742857] ACPI : EC: 2: Increase command [ 86.742859] ACPI : EC: ***** Command(RD_EC) started ***** [ 86.742861] ACPI : EC: ===== TASK (0) ===== [ 86.742871] ACPI : EC: EC_SC(R) = 0x20 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=0 [ 86.742873] ACPI : EC: EC_SC(W) = 0x80 [ 86.742876] ACPI : EC: ***** Event started ***** [ 86.742880] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 86.743972] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 86.747966] ACPI : EC: ===== TASK (0) ===== [ 86.747977] ACPI : EC: EC_SC(R) = 0x20 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=0 [ 86.747978] ACPI : EC: EC_DATA(W) = 0x06 [ 86.747981] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 86.751971] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 86.755969] ACPI : EC: ===== TASK (0) ===== [ 86.755991] ACPI : EC: EC_SC(R) = 0x21 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=1 [ 86.755993] ACPI : EC: EC_DATA(R) = 0x03 [ 86.755994] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 86.755995] ACPI : EC: ***** Command(RD_EC) stopped ***** [ 86.755996] ACPI : EC: 1: Decrease command This patch corrects this by using time_before() instead in ec_guard(): [ 54.283146] ACPI: Waking up from system sleep state S3 [ 54.285414] ACPI : EC: 2: Increase command [ 54.285415] ACPI : EC: ***** Command(RD_EC) started ***** [ 54.285416] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 54.285417] ACPI : EC: ===== TASK (0) ===== [ 54.285424] ACPI : EC: EC_SC(R) = 0x20 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=0 [ 54.285425] ACPI : EC: EC_SC(W) = 0x80 [ 54.285427] ACPI : EC: ***** Event started ***** [ 54.285429] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 54.287209] ACPI : EC: ===== TASK (0) ===== [ 54.287218] ACPI : EC: EC_SC(R) = 0x20 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=0 [ 54.287219] ACPI : EC: EC_DATA(W) = 0x06 [ 54.287222] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 54.291190] ACPI : EC: ===== TASK (0) ===== [ 54.291210] ACPI : EC: EC_SC(R) = 0x21 SCI_EVT=1 BURST=0 CMD=0 IBF=0 OBF=1 [ 54.291213] ACPI : EC: EC_DATA(R) = 0x03 [ 54.291214] ACPI : EC: ~~~~~ wait polling ~~~~~ [ 54.291215] ACPI : EC: ***** Command(RD_EC) stopped ***** [ 54.291216] ACPI : EC: 1: Decrease command After cleaning up all guarding logics, we have one single function ec_guard() collecting all old, non-root-caused, hidden logics. Then we can easily tune the logics in one place to respond to the bug reports. Except the time_before() change, all other changes do not change the behavior of the EC driver. Link: https://bugzilla.kernel.org/show_bug.cgi?id=12011 Link: https://bugzilla.kernel.org/show_bug.cgi?id=20242 Link: https://bugzilla.kernel.org/show_bug.cgi?id=77431 Signed-off-by: Lv Zheng Signed-off-by: Rafael J. Wysocki --- drivers/acpi/internal.h | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/acpi/internal.h') diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index ba4a61e964be..61cb50674359 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -138,6 +138,7 @@ struct acpi_ec { struct transaction *curr; spinlock_t lock; struct work_struct work; + unsigned long timestamp; }; extern struct acpi_ec *first_ec; -- cgit 1.4.1 From 9d8993be2d9149bc8b3132dad030ff5960f5abcc Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Thu, 11 Jun 2015 13:21:32 +0800 Subject: ACPI / EC: Convert event handling work queue into loop style. During the period that a work queue is scheduled (queued up for run) but hasn't been run, second schedule_work() could fail. This may not lead to the loss of queries because QR_EC is always ensured to be submitted after the work queue has been in the running state. The event handling work queue can be changed into the loop style to allow us to control the code in a more flexible way: 1. Makes it possible to add event=0x00 termination condition in the loop. 2. Increases the thoughput of the QR_EC transactions as the 2nd+ QR_EC transactions may be handled in the same work item used for the 1st QR_EC transaction, thus the delay caused by the 2nd+ work item scheduling can be eliminated. Except the logging message changes and the throughput improvement, this patch is just a funcitonal no-op. Signed-off-by: Lv Zheng Tested-by: Gabriele Mazzotta Tested-by: Tigran Gabrielyan Tested-by: Adrien D Signed-off-by: Rafael J. Wysocki --- drivers/acpi/ec.c | 33 ++++++++++++++++++++++++--------- drivers/acpi/internal.h | 1 + 2 files changed, 25 insertions(+), 9 deletions(-) (limited to 'drivers/acpi/internal.h') diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 0ce8b6e8c3e8..824f3e85023e 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -384,7 +384,9 @@ static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec) static void acpi_ec_submit_query(struct acpi_ec *ec) { if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) { - ec_dbg_req("Event started"); + ec_dbg_evt("Command(%s) submitted/blocked", + acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY)); + ec->nr_pending_queries++; schedule_work(&ec->work); } } @@ -393,7 +395,8 @@ static void acpi_ec_complete_query(struct acpi_ec *ec) { if (test_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) { clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); - ec_dbg_req("Event stopped"); + ec_dbg_evt("Command(%s) unblocked", + acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY)); } } @@ -460,8 +463,8 @@ static void advance_transaction(struct acpi_ec *ec) if (t->rlen == t->ri) { ec_transaction_transition(ec, ACPI_EC_COMMAND_COMPLETE); if (t->command == ACPI_EC_COMMAND_QUERY) - ec_dbg_req("Command(%s) hardware completion", - acpi_ec_cmd_string(t->command)); + ec_dbg_evt("Command(%s) completed by hardware", + acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY)); wakeup = true; } } else @@ -479,8 +482,8 @@ static void advance_transaction(struct acpi_ec *ec) ec_transaction_transition(ec, ACPI_EC_COMMAND_POLL); t->rdata[t->ri++] = 0x00; ec_transaction_transition(ec, ACPI_EC_COMMAND_COMPLETE); - ec_dbg_req("Command(%s) software completion", - acpi_ec_cmd_string(t->command)); + ec_dbg_evt("Command(%s) completed by software", + acpi_ec_cmd_string(ACPI_EC_COMMAND_QUERY)); wakeup = true; } else if ((status & ACPI_EC_FLAG_IBF) == 0) { acpi_ec_write_cmd(ec, t->command); @@ -961,11 +964,23 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 *data) return result; } -static void acpi_ec_gpe_poller(struct work_struct *work) +static void acpi_ec_event_handler(struct work_struct *work) { + unsigned long flags; struct acpi_ec *ec = container_of(work, struct acpi_ec, work); - acpi_ec_query(ec, NULL); + ec_dbg_evt("Event started"); + + spin_lock_irqsave(&ec->lock, flags); + while (ec->nr_pending_queries) { + spin_unlock_irqrestore(&ec->lock, flags); + (void)acpi_ec_query(ec, NULL); + spin_lock_irqsave(&ec->lock, flags); + ec->nr_pending_queries--; + } + spin_unlock_irqrestore(&ec->lock, flags); + + ec_dbg_evt("Event stopped"); } static u32 acpi_ec_gpe_handler(acpi_handle gpe_device, @@ -1040,7 +1055,7 @@ static struct acpi_ec *make_acpi_ec(void) init_waitqueue_head(&ec->wait); INIT_LIST_HEAD(&ec->list); spin_lock_init(&ec->lock); - INIT_WORK(&ec->work, acpi_ec_gpe_poller); + INIT_WORK(&ec->work, acpi_ec_event_handler); ec->timestamp = jiffies; return ec; } diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 61cb50674359..b09756abc6e8 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -139,6 +139,7 @@ struct acpi_ec { spinlock_t lock; struct work_struct work; unsigned long timestamp; + unsigned long nr_pending_queries; }; extern struct acpi_ec *first_ec; -- cgit 1.4.1