Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 33cc2c96 authored by Dan Williams's avatar Dan Williams
Browse files

acpi, nfit: Fix scrub idle detection



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: bc6ba808 ("nfit, address-range-scrub: rework and simplify ARS...")
Cc: <stable@vger.kernel.org>
Reported-by: default avatarVishal Verma <vishal.l.verma@intel.com>
Tested-by: default avatarVishal Verma <vishal.l.verma@intel.com>
Reported-by: default avatarLukasz Dorau <lukasz.dorau@intel.com>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
parent 1273c253
Loading
Loading
Loading
Loading
+33 −11
Original line number Diff line number Diff line
@@ -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);
+1 −0
Original line number Diff line number Diff line
@@ -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;