ACPI / EC: Add query flushing support
authorLv Zheng <lv.zheng@intel.com>
Fri, 6 Feb 2015 00:58:10 +0000 (08:58 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Fri, 6 Feb 2015 14:48:10 +0000 (15:48 +0100)
This patch implementes the QR_EC flushing support.

Grace periods are implemented from the detection of an SCI_EVT to the
submission/completion of the QR_EC transaction. During this period, all
EC command transactions are allowed to be submitted.

Note that query periods and event periods are intentionally distiguished to
allow further improvements.
1. Query period: from the detection of an SCI_EVT to the sumission of the
   QR_EC command. This period is used for storming prevention, as currently
   QR_EC is deferred to a work queue rather than directly issued from the
   IRQ context even there is no other transactions pending, so malicous
   SCI_EVT GPE can act like "level triggered" to trigger a GPE storm. We
   need to be prepared for this. And in the future, we may change it to be
   a part of the advance_transaction() where we will try QR_EC submission
   in appropriate positions to avoid such GPE storming.
2. Event period: from the detection of an SCI_EVT to the completion of the
   QR_EC command. We may extend it to the completion of _Qxx evaluation.
   This is actually a grace period for event flushing, but we only flush
   queries due to the reason stated in known issue 1. That's also why we
   use EC_FLAGS_EVENT_xxx. During this period, QR_EC transactions need to
   pass the flushable submission check.

In this patch, the following flags are implemented:
1. EC_FLAGS_EVENT_ENABLED: this is derived from the old
   EC_FLAGS_QUERY_PENDING flag which can block SCI_EVT handlings.
   With this flag, the logics implemented by the original flag are
   extended:
   1. Old logic: unless both of the flags are set, the event poller will
                 not be scheduled, and
   2. New logic: as soon as both of the flags are set, the evet poller will
                 be scheduled.
2. EC_FLAGS_EVENT_DETECTED: this is also derived from the old
   EC_FLAGS_QUERY_PENDING flag which can block SCI_EVT detection. It thus
   can be used to indicate the storming prevention period for query
   submission.
   acpi_ec_submit_request()/acpi_ec_complete_request() are invoked to
   implement this period so that acpi_set_gpe() can be invoked under the
   "reference count > 0" condition.
3. EC_FLAGS_EVENT_PENDING: this is newly added to indicate the grace period
   for event flushing (query flushing for now).
   acpi_ec_submit_request()/acpi_ec_complete_request() are invoked to
   implement this period so that the flushing process can wait until the
   event handling (query transaction for now) to be completed.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=82611
Link: https://bugzilla.kernel.org/show_bug.cgi?id=77431
Signed-off-by: Lv Zheng <lv.zheng@intel.com>
Tested-by: Ortwin Glück <odi@odi.ch>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/ec.c

index 982b67faaaf32c0de360aa35449a699075a6550a..40002ae7db2b07d66fc4fe3711b6350aa778e220 100644 (file)
@@ -76,7 +76,9 @@ enum ec_command {
                                         * when trying to clear the EC */
 
 enum {
-       EC_FLAGS_QUERY_PENDING,         /* Query is pending */
+       EC_FLAGS_EVENT_ENABLED,         /* Event is enabled */
+       EC_FLAGS_EVENT_PENDING,         /* Event is pending */
+       EC_FLAGS_EVENT_DETECTED,        /* Event is detected */
        EC_FLAGS_HANDLERS_INSTALLED,    /* Handlers for GPE and
                                         * OpReg are installed */
        EC_FLAGS_STARTED,               /* Driver is started */
@@ -151,6 +153,12 @@ static bool acpi_ec_flushed(struct acpi_ec *ec)
        return ec->reference_count == 1;
 }
 
+static bool acpi_ec_has_pending_event(struct acpi_ec *ec)
+{
+       return test_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags) ||
+              test_bit(EC_FLAGS_EVENT_PENDING, &ec->flags);
+}
+
 /* --------------------------------------------------------------------------
  *                           EC Registers
  * -------------------------------------------------------------------------- */
@@ -318,36 +326,93 @@ static void acpi_ec_clear_storm(struct acpi_ec *ec, u8 flag)
  *                                      the flush operation is not in
  *                                      progress
  * @ec: the EC device
+ * @allow_event: whether event should be handled
  *
  * This function must be used before taking a new action that should hold
  * the reference count.  If this function returns false, then the action
  * must be discarded or it will prevent the flush operation from being
  * completed.
+ *
+ * During flushing, QR_EC command need to pass this check when there is a
+ * pending event, so that the reference count held for the pending event
+ * can be decreased by the completion of the QR_EC command.
  */
-static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec)
+static bool acpi_ec_submit_flushable_request(struct acpi_ec *ec,
+                                            bool allow_event)
 {
-       if (!acpi_ec_started(ec))
-               return false;
+       if (!acpi_ec_started(ec)) {
+               if (!allow_event || !acpi_ec_has_pending_event(ec))
+                       return false;
+       }
        acpi_ec_submit_request(ec);
        return true;
 }
 
-static void acpi_ec_submit_query(struct acpi_ec *ec)
+static void acpi_ec_submit_event(struct acpi_ec *ec)
 {
-       if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) {
-               pr_debug("***** Event started *****\n");
+       if (!test_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags) ||
+           !test_bit(EC_FLAGS_EVENT_ENABLED, &ec->flags))
+               return;
+       /* Hold reference for pending event */
+       if (!acpi_ec_submit_flushable_request(ec, true))
+               return;
+       if (!test_and_set_bit(EC_FLAGS_EVENT_PENDING, &ec->flags)) {
+               pr_debug("***** Event query started *****\n");
                schedule_work(&ec->work);
+               return;
+       }
+       acpi_ec_complete_request(ec);
+}
+
+static void acpi_ec_complete_event(struct acpi_ec *ec)
+{
+       if (ec->curr->command == ACPI_EC_COMMAND_QUERY) {
+               clear_bit(EC_FLAGS_EVENT_PENDING, &ec->flags);
+               pr_debug("***** Event query stopped *****\n");
+               /* Unhold reference for pending event */
+               acpi_ec_complete_request(ec);
+               /* Check if there is another SCI_EVT detected */
+               acpi_ec_submit_event(ec);
+       }
+}
+
+static void acpi_ec_submit_detection(struct acpi_ec *ec)
+{
+       /* Hold reference for query submission */
+       if (!acpi_ec_submit_flushable_request(ec, false))
+               return;
+       if (!test_and_set_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags)) {
+               pr_debug("***** Event detection blocked *****\n");
+               acpi_ec_submit_event(ec);
+               return;
        }
+       acpi_ec_complete_request(ec);
 }
 
-static void acpi_ec_complete_query(struct acpi_ec *ec)
+static void acpi_ec_complete_detection(struct acpi_ec *ec)
 {
        if (ec->curr->command == ACPI_EC_COMMAND_QUERY) {
-               clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags);
-               pr_debug("***** Event stopped *****\n");
+               clear_bit(EC_FLAGS_EVENT_DETECTED, &ec->flags);
+               pr_debug("***** Event detetion unblocked *****\n");
+               /* Unhold reference for query submission */
+               acpi_ec_complete_request(ec);
        }
 }
 
+static void acpi_ec_enable_event(struct acpi_ec *ec)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&ec->lock, flags);
+       set_bit(EC_FLAGS_EVENT_ENABLED, &ec->flags);
+       /*
+        * An event may be pending even with SCI_EVT=0, so QR_EC should
+        * always be issued right after started.
+        */
+       acpi_ec_submit_detection(ec);
+       spin_unlock_irqrestore(&ec->lock, flags);
+}
+
 static int ec_transaction_completed(struct acpi_ec *ec)
 {
        unsigned long flags;
@@ -389,6 +454,7 @@ static void advance_transaction(struct acpi_ec *ec)
                                t->rdata[t->ri++] = acpi_ec_read_data(ec);
                                if (t->rlen == t->ri) {
                                        t->flags |= ACPI_EC_COMMAND_COMPLETE;
+                                       acpi_ec_complete_event(ec);
                                        if (t->command == ACPI_EC_COMMAND_QUERY)
                                                pr_debug("***** Command(%s) hardware completion *****\n",
                                                         acpi_ec_cmd_string(t->command));
@@ -399,6 +465,7 @@ static void advance_transaction(struct acpi_ec *ec)
                } else if (t->wlen == t->wi &&
                           (status & ACPI_EC_FLAG_IBF) == 0) {
                        t->flags |= ACPI_EC_COMMAND_COMPLETE;
+                       acpi_ec_complete_event(ec);
                        wakeup = true;
                }
                goto out;
@@ -407,16 +474,17 @@ static void advance_transaction(struct acpi_ec *ec)
                    !(status & ACPI_EC_FLAG_SCI) &&
                    (t->command == ACPI_EC_COMMAND_QUERY)) {
                        t->flags |= ACPI_EC_COMMAND_POLL;
-                       acpi_ec_complete_query(ec);
+                       acpi_ec_complete_detection(ec);
                        t->rdata[t->ri++] = 0x00;
                        t->flags |= ACPI_EC_COMMAND_COMPLETE;
+                       acpi_ec_complete_event(ec);
                        pr_debug("***** Command(%s) software completion *****\n",
                                 acpi_ec_cmd_string(t->command));
                        wakeup = true;
                } else if ((status & ACPI_EC_FLAG_IBF) == 0) {
                        acpi_ec_write_cmd(ec, t->command);
                        t->flags |= ACPI_EC_COMMAND_POLL;
-                       acpi_ec_complete_query(ec);
+                       acpi_ec_complete_detection(ec);
                } else
                        goto err;
                goto out;
@@ -437,7 +505,7 @@ err:
        }
 out:
        if (status & ACPI_EC_FLAG_SCI)
-               acpi_ec_submit_query(ec);
+               acpi_ec_submit_detection(ec);
        if (wakeup && in_interrupt())
                wake_up(&ec->wait);
 }
@@ -498,7 +566,7 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec,
        /* start transaction */
        spin_lock_irqsave(&ec->lock, tmp);
        /* Enable GPE for command processing (IBF=0/OBF=1) */
-       if (!acpi_ec_submit_flushable_request(ec)) {
+       if (!acpi_ec_submit_flushable_request(ec, true)) {
                ret = -EINVAL;
                goto unlock;
        }
@@ -879,7 +947,9 @@ static void acpi_ec_gpe_poller(struct work_struct *work)
 {
        struct acpi_ec *ec = container_of(work, struct acpi_ec, work);
 
+       pr_debug("***** Event poller started *****\n");
        acpi_ec_query(ec, NULL);
+       pr_debug("***** Event poller stopped *****\n");
 }
 
 static u32 acpi_ec_gpe_handler(acpi_handle gpe_device,
@@ -949,7 +1019,6 @@ static struct acpi_ec *make_acpi_ec(void)
 
        if (!ec)
                return NULL;
-       ec->flags = 1 << EC_FLAGS_QUERY_PENDING;
        mutex_init(&ec->mutex);
        init_waitqueue_head(&ec->wait);
        INIT_LIST_HEAD(&ec->list);
@@ -1100,7 +1169,7 @@ static int acpi_ec_add(struct acpi_device *device)
        ret = ec_install_handlers(ec);
 
        /* EC is fully operational, allow queries */
-       clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags);
+       acpi_ec_enable_event(ec);
 
        /* Clear stale _Q events if hardware might require that */
        if (EC_FLAGS_CLEAR_ON_RESUME)