PM: CXL: Disable suspend
authorDan Williams <dan.j.williams@intel.com>
Fri, 22 Apr 2022 22:58:11 +0000 (15:58 -0700)
committerDan Williams <dan.j.williams@intel.com>
Fri, 22 Apr 2022 23:09:42 +0000 (16:09 -0700)
The CXL specification claims S3 support at a hardware level, but at a
system software level there are some missing pieces. Section 9.4 (CXL
2.0) rightly claims that "CXL mem adapters may need aux power to retain
memory context across S3", but there is no enumeration mechanism for the
OS to determine if a given adapter has that support. Moreover the save
state and resume image for the system may inadvertantly end up in a CXL
device that needs to be restored before the save state is recoverable.
I.e. a circular dependency that is not resolvable without a third party
save-area.

Arrange for the cxl_mem driver to fail S3 attempts. This still nominaly
allows for suspend, but requires unbinding all CXL memory devices before
the suspend to ensure the typical DRAM flow is taken. The cxl_mem unbind
flow is intended to also tear down all CXL memory regions associated
with a given cxl_memdev.

It is reasonable to assume that any device participating in a System RAM
range published in the EFI memory map is covered by aux power and
save-area outside the device itself. So this restriction can be
minimized in the future once pre-existing region enumeration support
arrives, and perhaps a spec update to clarify if the EFI memory map is
sufficent for determining the range of devices managed by
platform-firmware for S3 support.

Per Rafael, if the CXL configuration prevents suspend then it should
fail early before tasks are frozen, and mem_sleep should stop showing
'mem' as an option [1]. Effectively CXL augments the platform suspend
->valid() op since, for example, the ACPI ops are not aware of the CXL /
PCI dependencies. Given the split role of platform firmware vs OS
provisioned CXL memory it is up to the cxl_mem driver to determine if
the CXL configuration has elements that platform firmware may not be
prepared to restore.

Link: https://lore.kernel.org/r/CAJZ5v0hGVN_=3iU8OLpHY3Ak35T5+JcBM-qs8SbojKrpd0VXsA@mail.gmail.com
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Pavel Machek <pavel@ucw.cz>
Cc: Len Brown <len.brown@intel.com>
Reviewed-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Link: https://lore.kernel.org/r/165066828317.3907920.5690432272182042556.stgit@dwillia2-desk3.amr.corp.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
drivers/Makefile
drivers/cxl/Kconfig
drivers/cxl/Makefile
drivers/cxl/core/Makefile
drivers/cxl/core/suspend.c [new file with mode: 0644]
drivers/cxl/cxlmem.h
drivers/cxl/mem.c
include/linux/pm.h
kernel/power/hibernate.c
kernel/power/main.c
kernel/power/suspend.c

index 020780b6b4d221c4dd18cd0af9ec21b4590715ad..f735c49551432a442e3de4cb06884f9c2b42ff03 100644 (file)
@@ -72,9 +72,9 @@ obj-$(CONFIG_PARPORT)         += parport/
 obj-y                          += base/ block/ misc/ mfd/ nfc/
 obj-$(CONFIG_LIBNVDIMM)                += nvdimm/
 obj-$(CONFIG_DAX)              += dax/
-obj-$(CONFIG_CXL_BUS)          += cxl/
 obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf/
 obj-$(CONFIG_NUBUS)            += nubus/
+obj-y                          += cxl/
 obj-y                          += macintosh/
 obj-y                          += scsi/
 obj-y                          += nvme/
index b88ab956bb7cfe0c3276557a1bfc965d9b1dd1b3..f64e3984689ffad92fcd68af4e0066ca09476604 100644 (file)
@@ -98,4 +98,8 @@ config CXL_PORT
        default CXL_BUS
        tristate
 
+config CXL_SUSPEND
+       def_bool y
+       depends on SUSPEND && CXL_MEM
+
 endif
index ce267ef11d93c062fb3063f2dd6639debebc900f..a78270794150d1bb432fd7c5820cd8f5f50d0a5e 100644 (file)
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_CXL_BUS) += core/
+obj-y += core/
 obj-$(CONFIG_CXL_PCI) += cxl_pci.o
 obj-$(CONFIG_CXL_MEM) += cxl_mem.o
 obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
index 6d37cd78b1518e03b2386267d719e0b5169c561a..9d35085d25aff8503574b51ef696dc18d4258752 100644 (file)
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_CXL_BUS) += cxl_core.o
+obj-$(CONFIG_CXL_SUSPEND) += suspend.o
 
 ccflags-y += -I$(srctree)/drivers/cxl
 cxl_core-y := port.o
diff --git a/drivers/cxl/core/suspend.c b/drivers/cxl/core/suspend.c
new file mode 100644 (file)
index 0000000..a5984d9
--- /dev/null
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
+#include <linux/atomic.h>
+#include <linux/export.h>
+#include "cxlmem.h"
+
+static atomic_t mem_active;
+
+bool cxl_mem_active(void)
+{
+       return atomic_read(&mem_active) != 0;
+}
+
+void cxl_mem_active_inc(void)
+{
+       atomic_inc(&mem_active);
+}
+EXPORT_SYMBOL_NS_GPL(cxl_mem_active_inc, CXL);
+
+void cxl_mem_active_dec(void)
+{
+       atomic_dec(&mem_active);
+}
+EXPORT_SYMBOL_NS_GPL(cxl_mem_active_dec, CXL);
index 243dd86a8b46b8065398b25809e121f4d54db7ef..7235d2f976e5cae5f85a17fe68d9d430d9ea7def 100644 (file)
@@ -353,6 +353,17 @@ int cxl_mem_create_range_info(struct cxl_dev_state *cxlds);
 struct cxl_dev_state *cxl_dev_state_create(struct device *dev);
 void set_exclusive_cxl_commands(struct cxl_dev_state *cxlds, unsigned long *cmds);
 void clear_exclusive_cxl_commands(struct cxl_dev_state *cxlds, unsigned long *cmds);
+#ifdef CONFIG_CXL_SUSPEND
+void cxl_mem_active_inc(void);
+void cxl_mem_active_dec(void);
+#else
+static inline void cxl_mem_active_inc(void)
+{
+}
+static inline void cxl_mem_active_dec(void)
+{
+}
+#endif
 
 struct cxl_hdm {
        struct cxl_component_regs regs;
index 43e73d259207c9ef06413f8f42e3b41080fd66d7..1bd2e0b67f59facca2bcb3e7a8499deb0b3d967d 100644 (file)
@@ -138,6 +138,11 @@ out:
        return retval;
 }
 
+static void enable_suspend(void *data)
+{
+       cxl_mem_active_dec();
+}
+
 static int cxl_mem_probe(struct device *dev)
 {
        struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
@@ -194,7 +199,22 @@ static int cxl_mem_probe(struct device *dev)
 out:
        cxl_device_unlock(&parent_port->dev);
        put_device(&parent_port->dev);
-       return rc;
+
+       /*
+        * The kernel may be operating out of CXL memory on this device,
+        * there is no spec defined way to determine whether this device
+        * preserves contents over suspend, and there is no simple way
+        * to arrange for the suspend image to avoid CXL memory which
+        * would setup a circular dependency between PCI resume and save
+        * state restoration.
+        *
+        * TODO: support suspend when all the regions this device is
+        * hosting are locked and covered by the system address map,
+        * i.e. platform firmware owns restoring the HDM configuration
+        * that it locked.
+        */
+       cxl_mem_active_inc();
+       return devm_add_action_or_reset(dev, enable_suspend, NULL);
 }
 
 static struct cxl_driver cxl_mem_driver = {
index e65b3ab28377bf7ee89096fa88d6fe0b1cdb087e..7911c4c9a7be6e9c5ecee7c08f585e466dc3b495 100644 (file)
@@ -36,6 +36,15 @@ static inline void pm_vt_switch_unregister(struct device *dev)
 }
 #endif /* CONFIG_VT_CONSOLE_SLEEP */
 
+#ifdef CONFIG_CXL_SUSPEND
+bool cxl_mem_active(void);
+#else
+static inline bool cxl_mem_active(void)
+{
+       return false;
+}
+#endif
+
 /*
  * Device power management
  */
index 938d5c78b42115d75e340dd3a01e279d4483dbd6..20a66bf9f465926bca80c99716142db16fca9792 100644 (file)
@@ -83,7 +83,7 @@ bool hibernation_available(void)
 {
        return nohibernate == 0 &&
                !security_locked_down(LOCKDOWN_HIBERNATION) &&
-               !secretmem_active();
+               !secretmem_active() && !cxl_mem_active();
 }
 
 /**
index 7e646079fbeb2f3ca7a5f4bcea0cfb7ecdcb036d..3e6be1c33e0b2b9fe2b191e4c1d6f287b7280d43 100644 (file)
@@ -127,7 +127,9 @@ static ssize_t mem_sleep_show(struct kobject *kobj, struct kobj_attribute *attr,
        char *s = buf;
        suspend_state_t i;
 
-       for (i = PM_SUSPEND_MIN; i < PM_SUSPEND_MAX; i++)
+       for (i = PM_SUSPEND_MIN; i < PM_SUSPEND_MAX; i++) {
+               if (i >= PM_SUSPEND_MEM && cxl_mem_active())
+                       continue;
                if (mem_sleep_states[i]) {
                        const char *label = mem_sleep_states[i];
 
@@ -136,6 +138,7 @@ static ssize_t mem_sleep_show(struct kobject *kobj, struct kobj_attribute *attr,
                        else
                                s += sprintf(s, "%s ", label);
                }
+       }
 
        /* Convert the last space to a newline if needed. */
        if (s != buf)
index 6fcdee7e87a57f32187b36156d90256ee34c2f17..827075944d28357950b3bf55ec22d53987bbd920 100644 (file)
@@ -236,7 +236,8 @@ EXPORT_SYMBOL_GPL(suspend_valid_only_mem);
 
 static bool sleep_state_supported(suspend_state_t state)
 {
-       return state == PM_SUSPEND_TO_IDLE || valid_state(state);
+       return state == PM_SUSPEND_TO_IDLE ||
+              (valid_state(state) && !cxl_mem_active());
 }
 
 static int platform_suspend_prepare(suspend_state_t state)