@@ -108,6 +108,24 @@ void syscore_resume(void)
trace_suspend_resume(TPS("syscore_resume"), 0, false);
}
EXPORT_SYMBOL_GPL(syscore_resume);
+
+/**
+ * syscore_quiesced - Execute callbacks that need a quiesced system
+ *
+ * This function is executed after all syscore_suspend() callbacks have
+ * completed successfully.
+ */
+void syscore_quiesced(void)
+{
+ struct syscore_ops *ops;
+
+ list_for_each_entry(ops, &syscore_ops_list, node) {
+ if (!ops->quiesced)
+ continue;
+ ops->quiesced();
+ }
+}
+
#endif /* CONFIG_PM_SLEEP */
/**
@@ -3,6 +3,7 @@
* Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/syscore_ops.h>
#include <linux/libnvdimm.h>
#include <linux/sched/mm.h>
#include <linux/vmalloc.h>
@@ -1289,6 +1290,33 @@ static const struct file_operations nvdimm_fops = {
.llseek = noop_llseek,
};
+static void trigger_fw_activate(struct nvdimm_bus_descriptor *nd_desc)
+{
+ if (!nd_desc->fw_ops)
+ return;
+ nd_desc->fw_ops->activate(nd_desc);
+}
+
+static void nvdimm_syscore_quiesced(void)
+{
+ struct nvdimm_bus *nvdimm_bus;
+
+ mutex_lock(&nvdimm_bus_list_mutex);
+ list_for_each_entry(nvdimm_bus, &nvdimm_bus_list, list) {
+ struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
+ struct device *dev = &nvdimm_bus->dev;
+
+ nvdimm_bus_lock(dev);
+ trigger_fw_activate(nd_desc);
+ nvdimm_bus_unlock(dev);
+ }
+ mutex_unlock(&nvdimm_bus_list_mutex);
+}
+
+static struct syscore_ops nvdimm_syscore_ops = {
+ .quiesced = nvdimm_syscore_quiesced,
+};
+
int __init nvdimm_bus_init(void)
{
int rc;
@@ -1317,6 +1345,8 @@ int __init nvdimm_bus_init(void)
if (rc)
goto err_nd_bus;
+ register_syscore_ops(&nvdimm_syscore_ops);
+
return 0;
err_nd_bus:
@@ -417,6 +417,9 @@ static ssize_t firmware_activate_store(struct device *dev,
if (!nd_desc->fw_ops)
return -EOPNOTSUPP;
+ if (!sysfs_streq(buf, "activate"))
+ return -EINVAL;
+
nvdimm_bus_lock(dev);
state = nd_desc->fw_ops->activate_state(nd_desc);
@@ -15,6 +15,7 @@ struct syscore_ops {
int (*suspend)(void);
void (*resume)(void);
void (*shutdown)(void);
+ void (*quiesced)(void);
};
extern void register_syscore_ops(struct syscore_ops *ops);
@@ -22,6 +23,7 @@ extern void unregister_syscore_ops(struct syscore_ops *ops);
#ifdef CONFIG_PM_SLEEP
extern int syscore_suspend(void);
extern void syscore_resume(void);
+extern void syscore_quiesced(void);
#endif
extern void syscore_shutdown(void);
@@ -414,6 +414,8 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
if (error)
goto Platform_wake;
+ syscore_quiesced();
+
if (suspend_test(TEST_PLATFORM))
goto Platform_wake;
The runtime firmware activation capability of Intel NVDIMM devices requires memory transactions to be disabled for 100s of microseconds. This timeout is large enough to cause in-flight DMA to fail and other application detectable timeouts. Arrange for firmware activation to be executed while the system is "quiesced" all suspend operations have completed successfully. Note that the placement of syscore_quiesced() before suspend_disable_secondary_cpus() and the "TEST_PLATFORM" early exit in suspend_enter(): if (suspend_test(TEST_PLATFORM)) goto Platform_wake; ...is a deliberate tradeoff. suspend_disable_secondary_cpus() causes violence to drivers with many interrupts allocated (server-class network adapters for example). So, allow for triggering firmware-activation without requiring all irq vectors to be routed (oversubscribed) to a single CPU. Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: "Rafael J. Wysocki" <rafael@kernel.org> Cc: Dan Williams <dan.j.williams@intel.com> Cc: Vishal Verma <vishal.l.verma@intel.com> Cc: Dave Jiang <dave.jiang@intel.com> Cc: Ira Weiny <ira.weiny@intel.com> Cc: Pavel Machek <pavel@ucw.cz> Cc: Len Brown <len.brown@intel.com> Signed-off-by: Dan Williams <dan.j.williams@intel.com> --- drivers/base/syscore.c | 18 ++++++++++++++++++ drivers/nvdimm/bus.c | 30 ++++++++++++++++++++++++++++++ drivers/nvdimm/core.c | 3 +++ include/linux/syscore_ops.h | 2 ++ kernel/power/suspend.c | 2 ++ 5 files changed, 55 insertions(+)