acpi, nfit: Fix scrub idle detection
authorDan Williams <dan.j.williams@intel.com>
Thu, 5 Jul 2018 21:58:49 +0000 (14:58 -0700)
committerDan Williams <dan.j.williams@intel.com>
Fri, 6 Jul 2018 02:33:53 +0000 (19:33 -0700)
The notification of scrub completion happens within the scrub workqueue.
That can clearly race someone running scrub_show() and work_busy()
before the workqueue has a chance to flush the recently completed work.
Add a flag to reliably indicate the idle vs busy state. Without this
change applications using poll(2) to wait for scrub-completion may
falsely wakeup and read ARS as being busy even though the thread is
going idle and then hang indefinitely.

Fixes: bc6ba8085842 ("nfit, address-range-scrub: rework and simplify ARS...")
Cc: <stable@vger.kernel.org>
Reported-by: Vishal Verma <vishal.l.verma@intel.com>
Tested-by: Vishal Verma <vishal.l.verma@intel.com>
Reported-by: Lukasz Dorau <lukasz.dorau@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
drivers/acpi/nfit/core.c
drivers/acpi/nfit/nfit.h

index 471402c..b8040fe 100644 (file)
@@ -1275,7 +1275,7 @@ static ssize_t scrub_show(struct device *dev,
 
                mutex_lock(&acpi_desc->init_mutex);
                rc = sprintf(buf, "%d%s", acpi_desc->scrub_count,
-                               work_busy(&acpi_desc->dwork.work)
+                               acpi_desc->scrub_busy
                                && !acpi_desc->cancel ? "+\n" : "\n");
                mutex_unlock(&acpi_desc->init_mutex);
        }
@@ -2941,6 +2941,32 @@ static unsigned int __acpi_nfit_scrub(struct acpi_nfit_desc *acpi_desc,
        return 0;
 }
 
+static void __sched_ars(struct acpi_nfit_desc *acpi_desc, unsigned int tmo)
+{
+       lockdep_assert_held(&acpi_desc->init_mutex);
+
+       acpi_desc->scrub_busy = 1;
+       /* note this should only be set from within the workqueue */
+       if (tmo)
+               acpi_desc->scrub_tmo = tmo;
+       queue_delayed_work(nfit_wq, &acpi_desc->dwork, tmo * HZ);
+}
+
+static void sched_ars(struct acpi_nfit_desc *acpi_desc)
+{
+       __sched_ars(acpi_desc, 0);
+}
+
+static void notify_ars_done(struct acpi_nfit_desc *acpi_desc)
+{
+       lockdep_assert_held(&acpi_desc->init_mutex);
+
+       acpi_desc->scrub_busy = 0;
+       acpi_desc->scrub_count++;
+       if (acpi_desc->scrub_count_state)
+               sysfs_notify_dirent(acpi_desc->scrub_count_state);
+}
+
 static void acpi_nfit_scrub(struct work_struct *work)
 {
        struct acpi_nfit_desc *acpi_desc;
@@ -2951,14 +2977,10 @@ static void acpi_nfit_scrub(struct work_struct *work)
        mutex_lock(&acpi_desc->init_mutex);
        query_rc = acpi_nfit_query_poison(acpi_desc);
        tmo = __acpi_nfit_scrub(acpi_desc, query_rc);
-       if (tmo) {
-               queue_delayed_work(nfit_wq, &acpi_desc->dwork, tmo * HZ);
-               acpi_desc->scrub_tmo = tmo;
-       } else {
-               acpi_desc->scrub_count++;
-               if (acpi_desc->scrub_count_state)
-                       sysfs_notify_dirent(acpi_desc->scrub_count_state);
-       }
+       if (tmo)
+               __sched_ars(acpi_desc, tmo);
+       else
+               notify_ars_done(acpi_desc);
        memset(acpi_desc->ars_status, 0, acpi_desc->max_ars);
        mutex_unlock(&acpi_desc->init_mutex);
 }
@@ -3039,7 +3061,7 @@ static int acpi_nfit_register_regions(struct acpi_nfit_desc *acpi_desc)
                        break;
                }
 
-       queue_delayed_work(nfit_wq, &acpi_desc->dwork, 0);
+       sched_ars(acpi_desc);
        return 0;
 }
 
@@ -3241,7 +3263,7 @@ int acpi_nfit_ars_rescan(struct acpi_nfit_desc *acpi_desc, unsigned long flags)
                }
        }
        if (scheduled) {
-               queue_delayed_work(nfit_wq, &acpi_desc->dwork, 0);
+               sched_ars(acpi_desc);
                dev_dbg(dev, "ars_scan triggered\n");
        }
        mutex_unlock(&acpi_desc->init_mutex);
index 7d15856..a97ff42 100644 (file)
@@ -203,6 +203,7 @@ struct acpi_nfit_desc {
        unsigned int max_ars;
        unsigned int scrub_count;
        unsigned int scrub_mode;
+       unsigned int scrub_busy:1;
        unsigned int cancel:1;
        unsigned long dimm_cmd_force_en;
        unsigned long bus_cmd_force_en;