18dd2bc0fSBen Widawsky // SPDX-License-Identifier: GPL-2.0-only
28dd2bc0fSBen Widawsky /* Copyright(c) 2022 Intel Corporation. All rights reserved. */
3cc2a4878SDan Williams #include <linux/debugfs.h>
48dd2bc0fSBen Widawsky #include <linux/device.h>
58dd2bc0fSBen Widawsky #include <linux/module.h>
68dd2bc0fSBen Widawsky #include <linux/pci.h>
78dd2bc0fSBen Widawsky
88dd2bc0fSBen Widawsky #include "cxlmem.h"
98dd2bc0fSBen Widawsky #include "cxlpci.h"
108dd2bc0fSBen Widawsky
118dd2bc0fSBen Widawsky /**
128dd2bc0fSBen Widawsky * DOC: cxl mem
138dd2bc0fSBen Widawsky *
148dd2bc0fSBen Widawsky * CXL memory endpoint devices and switches are CXL capable devices that are
158dd2bc0fSBen Widawsky * participating in CXL.mem protocol. Their functionality builds on top of the
168dd2bc0fSBen Widawsky * CXL.io protocol that allows enumerating and configuring components via
178dd2bc0fSBen Widawsky * standard PCI mechanisms.
188dd2bc0fSBen Widawsky *
198dd2bc0fSBen Widawsky * The cxl_mem driver owns kicking off the enumeration of this CXL.mem
208dd2bc0fSBen Widawsky * capability. With the detection of a CXL capable endpoint, the driver will
218dd2bc0fSBen Widawsky * walk up to find the platform specific port it is connected to, and determine
228dd2bc0fSBen Widawsky * if there are intervening switches in the path. If there are switches, a
238dd2bc0fSBen Widawsky * secondary action is to enumerate those (implemented in cxl_core). Finally the
248dd2bc0fSBen Widawsky * cxl_mem driver adds the device it is bound to as a CXL endpoint-port for use
258dd2bc0fSBen Widawsky * in higher level operations.
268dd2bc0fSBen Widawsky */
278dd2bc0fSBen Widawsky
enable_suspend(void * data)289ea4dcf4SDan Williams static void enable_suspend(void *data)
299ea4dcf4SDan Williams {
309ea4dcf4SDan Williams cxl_mem_active_dec();
319ea4dcf4SDan Williams }
329ea4dcf4SDan Williams
remove_debugfs(void * dentry)33cc2a4878SDan Williams static void remove_debugfs(void *dentry)
34cc2a4878SDan Williams {
35cc2a4878SDan Williams debugfs_remove_recursive(dentry);
36cc2a4878SDan Williams }
37cc2a4878SDan Williams
cxl_mem_dpa_show(struct seq_file * file,void * data)38cc2a4878SDan Williams static int cxl_mem_dpa_show(struct seq_file *file, void *data)
39cc2a4878SDan Williams {
40cc2a4878SDan Williams struct device *dev = file->private;
41cc2a4878SDan Williams struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
42cc2a4878SDan Williams
43cc2a4878SDan Williams cxl_dpa_debug(file, cxlmd->cxlds);
44cc2a4878SDan Williams
45cc2a4878SDan Williams return 0;
46cc2a4878SDan Williams }
47cc2a4878SDan Williams
devm_cxl_add_endpoint(struct device * host,struct cxl_memdev * cxlmd,struct cxl_dport * parent_dport)480a19bfc8SDan Williams static int devm_cxl_add_endpoint(struct device *host, struct cxl_memdev *cxlmd,
497592d935SDan Williams struct cxl_dport *parent_dport)
507592d935SDan Williams {
517592d935SDan Williams struct cxl_port *parent_port = parent_dport->port;
527592d935SDan Williams struct cxl_dev_state *cxlds = cxlmd->cxlds;
537592d935SDan Williams struct cxl_port *endpoint, *iter, *down;
540a19bfc8SDan Williams int rc;
557592d935SDan Williams
567592d935SDan Williams /*
577592d935SDan Williams * Now that the path to the root is established record all the
587592d935SDan Williams * intervening ports in the chain.
597592d935SDan Williams */
607592d935SDan Williams for (iter = parent_port, down = NULL; !is_cxl_root(iter);
617592d935SDan Williams down = iter, iter = to_cxl_port(iter->dev.parent)) {
627592d935SDan Williams struct cxl_ep *ep;
637592d935SDan Williams
647592d935SDan Williams ep = cxl_ep_load(iter, cxlmd);
657592d935SDan Williams ep->next = down;
667592d935SDan Williams }
677592d935SDan Williams
687592d935SDan Williams endpoint = devm_cxl_add_port(host, &cxlmd->dev,
690a19bfc8SDan Williams cxlds->component_reg_phys,
700a19bfc8SDan Williams parent_dport);
710a19bfc8SDan Williams if (IS_ERR(endpoint))
720a19bfc8SDan Williams return PTR_ERR(endpoint);
730a19bfc8SDan Williams
740a19bfc8SDan Williams rc = cxl_endpoint_autoremove(cxlmd, endpoint);
750a19bfc8SDan Williams if (rc)
760a19bfc8SDan Williams return rc;
770a19bfc8SDan Williams
780a19bfc8SDan Williams if (!endpoint->dev.driver) {
790a19bfc8SDan Williams dev_err(&cxlmd->dev, "%s failed probe\n",
800a19bfc8SDan Williams dev_name(&endpoint->dev));
817592d935SDan Williams return -ENXIO;
827592d935SDan Williams }
837592d935SDan Williams
847592d935SDan Williams return 0;
857592d935SDan Williams }
867592d935SDan Williams
cxl_debugfs_poison_inject(void * data,u64 dpa)877592d935SDan Williams static int cxl_debugfs_poison_inject(void *data, u64 dpa)
887592d935SDan Williams {
897592d935SDan Williams struct cxl_memdev *cxlmd = data;
907592d935SDan Williams
917592d935SDan Williams return cxl_inject_poison(cxlmd, dpa);
927592d935SDan Williams }
937592d935SDan Williams
947592d935SDan Williams DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_inject_fops, NULL,
957592d935SDan Williams cxl_debugfs_poison_inject, "%llx\n");
967592d935SDan Williams
cxl_debugfs_poison_clear(void * data,u64 dpa)9750d527f5SAlison Schofield static int cxl_debugfs_poison_clear(void *data, u64 dpa)
9850d527f5SAlison Schofield {
9950d527f5SAlison Schofield struct cxl_memdev *cxlmd = data;
10050d527f5SAlison Schofield
10150d527f5SAlison Schofield return cxl_clear_poison(cxlmd, dpa);
10250d527f5SAlison Schofield }
10350d527f5SAlison Schofield
10450d527f5SAlison Schofield DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_clear_fops, NULL,
10550d527f5SAlison Schofield cxl_debugfs_poison_clear, "%llx\n");
10650d527f5SAlison Schofield
cxl_mem_probe(struct device * dev)10750d527f5SAlison Schofield static int cxl_mem_probe(struct device *dev)
10850d527f5SAlison Schofield {
10950d527f5SAlison Schofield struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
11050d527f5SAlison Schofield struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
11150d527f5SAlison Schofield struct cxl_dev_state *cxlds = cxlmd->cxlds;
11250d527f5SAlison Schofield struct device *endpoint_parent;
11350d527f5SAlison Schofield struct cxl_port *parent_port;
11450d527f5SAlison Schofield struct cxl_dport *dport;
11550d527f5SAlison Schofield struct dentry *dentry;
11650d527f5SAlison Schofield int rc;
1178dd2bc0fSBen Widawsky
1188dd2bc0fSBen Widawsky if (!cxlds->media_ready)
1198dd2bc0fSBen Widawsky return -EBUSY;
120f17b558dSDan Williams
1210a19bfc8SDan Williams /*
1228dd2bc0fSBen Widawsky * Someone is trying to reattach this device after it lost its port
1231b58b4caSDan Williams * connection (an endpoint port previously registered by this memdev was
124cc2a4878SDan Williams * disabled). This racy check is ok because if the port is still gone,
1258dd2bc0fSBen Widawsky * no harm done, and if the port hierarchy comes back it will re-trigger
1268dd2bc0fSBen Widawsky * this probe. Port rescan and memdev detach work share the same
127*e764f122SDave Jiang * single-threaded workqueue.
128*e764f122SDave Jiang */
129*e764f122SDave Jiang if (work_pending(&cxlmd->detach_work))
1308dd2bc0fSBen Widawsky return -EBUSY;
1318dd2bc0fSBen Widawsky
1328dd2bc0fSBen Widawsky dentry = cxl_debugfs_create_dir(dev_name(dev));
1338dd2bc0fSBen Widawsky debugfs_create_devm_seqfile(dev, "dpamem", dentry, cxl_mem_dpa_show);
1348dd2bc0fSBen Widawsky
1358dd2bc0fSBen Widawsky if (test_bit(CXL_POISON_ENABLED_INJECT, mds->poison.enabled_cmds))
1368dd2bc0fSBen Widawsky debugfs_create_file("inject_poison", 0200, dentry, cxlmd,
1378dd2bc0fSBen Widawsky &cxl_poison_inject_fops);
1388dd2bc0fSBen Widawsky if (test_bit(CXL_POISON_ENABLED_CLEAR, mds->poison.enabled_cmds))
1398dd2bc0fSBen Widawsky debugfs_create_file("clear_poison", 0200, dentry, cxlmd,
1408dd2bc0fSBen Widawsky &cxl_poison_clear_fops);
141cc2a4878SDan Williams
142cc2a4878SDan Williams rc = devm_add_action_or_reset(dev, remove_debugfs, dentry);
14350d527f5SAlison Schofield if (rc)
14450d527f5SAlison Schofield return rc;
14550d527f5SAlison Schofield
14650d527f5SAlison Schofield rc = devm_cxl_enumerate_ports(cxlmd);
14750d527f5SAlison Schofield if (rc)
14850d527f5SAlison Schofield return rc;
14950d527f5SAlison Schofield
15050d527f5SAlison Schofield parent_port = cxl_mem_find_port(cxlmd, &dport);
151cc2a4878SDan Williams if (!parent_port) {
152cc2a4878SDan Williams dev_err(dev, "CXL port topology not found\n");
153cc2a4878SDan Williams return -ENXIO;
154cc2a4878SDan Williams }
1558dd2bc0fSBen Widawsky
1568dd2bc0fSBen Widawsky if (dport->rch)
1578dd2bc0fSBen Widawsky endpoint_parent = parent_port->uport_dev;
1588dd2bc0fSBen Widawsky else
1591b58b4caSDan Williams endpoint_parent = &parent_port->dev;
1608dd2bc0fSBen Widawsky
1618dd2bc0fSBen Widawsky device_lock(endpoint_parent);
1628dd2bc0fSBen Widawsky if (!endpoint_parent->driver) {
1638dd2bc0fSBen Widawsky dev_err(dev, "CXL port topology %s not enabled\n",
1648dd2bc0fSBen Widawsky dev_name(endpoint_parent));
1650a19bfc8SDan Williams rc = -ENXIO;
1660a19bfc8SDan Williams goto unlock;
1670a19bfc8SDan Williams }
1680a19bfc8SDan Williams
1690a19bfc8SDan Williams rc = devm_cxl_add_endpoint(endpoint_parent, cxlmd, dport);
1700a19bfc8SDan Williams unlock:
1710a19bfc8SDan Williams device_unlock(endpoint_parent);
1728dd2bc0fSBen Widawsky put_device(&parent_port->dev);
1730a19bfc8SDan Williams if (rc)
1748dd2bc0fSBen Widawsky return rc;
17576a4121eSDan Williams
1768dd2bc0fSBen Widawsky if (resource_size(&cxlds->pmem_res) && IS_ENABLED(CONFIG_CXL_PMEM)) {
1778dd2bc0fSBen Widawsky rc = devm_cxl_add_nvdimm(cxlmd);
1780a19bfc8SDan Williams if (rc == -ENODEV)
17976a4121eSDan Williams dev_info(dev, "PMEM disabled by platform\n");
1800a19bfc8SDan Williams else
1818dd2bc0fSBen Widawsky return rc;
18276a4121eSDan Williams }
18376a4121eSDan Williams
1849ea4dcf4SDan Williams /*
185f17b558dSDan Williams * The kernel may be operating out of CXL memory on this device,
186f17b558dSDan Williams * there is no spec defined way to determine whether this device
187f17b558dSDan Williams * preserves contents over suspend, and there is no simple way
188f17b558dSDan Williams * to arrange for the suspend image to avoid CXL memory which
189f17b558dSDan Williams * would setup a circular dependency between PCI resume and save
190f17b558dSDan Williams * state restoration.
191f17b558dSDan Williams *
192f17b558dSDan Williams * TODO: support suspend when all the regions this device is
19375b7ae29SDan Williams * hosting are locked and covered by the system address map,
1949ea4dcf4SDan Williams * i.e. platform firmware owns restoring the HDM configuration
1959ea4dcf4SDan Williams * that it locked.
1969ea4dcf4SDan Williams */
1979ea4dcf4SDan Williams cxl_mem_active_inc();
1989ea4dcf4SDan Williams return devm_add_action_or_reset(dev, enable_suspend, NULL);
1999ea4dcf4SDan Williams }
2009ea4dcf4SDan Williams
trigger_poison_list_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t len)2019ea4dcf4SDan Williams static ssize_t trigger_poison_list_store(struct device *dev,
2029ea4dcf4SDan Williams struct device_attribute *attr,
2039ea4dcf4SDan Williams const char *buf, size_t len)
2049ea4dcf4SDan Williams {
2059ea4dcf4SDan Williams bool trigger;
2069ea4dcf4SDan Williams int rc;
2079ea4dcf4SDan Williams
2088dd2bc0fSBen Widawsky if (kstrtobool(buf, &trigger) || !trigger)
2098dd2bc0fSBen Widawsky return -EINVAL;
2107ff6ad10SAlison Schofield
2117ff6ad10SAlison Schofield rc = cxl_trigger_poison_list(to_cxl_memdev(dev));
2127ff6ad10SAlison Schofield
2137ff6ad10SAlison Schofield return rc ? rc : len;
2147ff6ad10SAlison Schofield }
2157ff6ad10SAlison Schofield static DEVICE_ATTR_WO(trigger_poison_list);
2167ff6ad10SAlison Schofield
cxl_mem_visible(struct kobject * kobj,struct attribute * a,int n)2177ff6ad10SAlison Schofield static umode_t cxl_mem_visible(struct kobject *kobj, struct attribute *a, int n)
2187ff6ad10SAlison Schofield {
2197ff6ad10SAlison Schofield if (a == &dev_attr_trigger_poison_list.attr) {
2207ff6ad10SAlison Schofield struct device *dev = kobj_to_dev(kobj);
2217ff6ad10SAlison Schofield struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
2227ff6ad10SAlison Schofield struct cxl_memdev_state *mds =
2237ff6ad10SAlison Schofield to_cxl_memdev_state(cxlmd->cxlds);
2247ff6ad10SAlison Schofield
2257ff6ad10SAlison Schofield if (!test_bit(CXL_POISON_ENABLED_LIST,
2267ff6ad10SAlison Schofield mds->poison.enabled_cmds))
2277ff6ad10SAlison Schofield return 0;
2287ff6ad10SAlison Schofield }
2297ff6ad10SAlison Schofield return a->mode;
2307ff6ad10SAlison Schofield }
2317ff6ad10SAlison Schofield
2327ff6ad10SAlison Schofield static struct attribute *cxl_mem_attrs[] = {
2337ff6ad10SAlison Schofield &dev_attr_trigger_poison_list.attr,
2347ff6ad10SAlison Schofield NULL
2357ff6ad10SAlison Schofield };
2367ff6ad10SAlison Schofield
2377ff6ad10SAlison Schofield static struct attribute_group cxl_mem_group = {
2387ff6ad10SAlison Schofield .attrs = cxl_mem_attrs,
2397ff6ad10SAlison Schofield .is_visible = cxl_mem_visible,
2407ff6ad10SAlison Schofield };
2417ff6ad10SAlison Schofield
2427ff6ad10SAlison Schofield __ATTRIBUTE_GROUPS(cxl_mem);
2437ff6ad10SAlison Schofield
2447ff6ad10SAlison Schofield static struct cxl_driver cxl_mem_driver = {
2457ff6ad10SAlison Schofield .name = "cxl_mem",
2467ff6ad10SAlison Schofield .probe = cxl_mem_probe,
2477ff6ad10SAlison Schofield .id = CXL_DEVICE_MEMORY_EXPANDER,
2487ff6ad10SAlison Schofield .drv = {
2497ff6ad10SAlison Schofield .dev_groups = cxl_mem_groups,
2508dd2bc0fSBen Widawsky },
2518dd2bc0fSBen Widawsky };
2528dd2bc0fSBen Widawsky
2538dd2bc0fSBen Widawsky module_cxl_driver(cxl_mem_driver);
2547ff6ad10SAlison Schofield
2557ff6ad10SAlison Schofield MODULE_LICENSE("GPL v2");
2567ff6ad10SAlison Schofield MODULE_IMPORT_NS(CXL);
2578dd2bc0fSBen Widawsky MODULE_ALIAS_CXL(CXL_DEVICE_MEMORY_EXPANDER);
2588dd2bc0fSBen Widawsky /*
2598dd2bc0fSBen Widawsky * create_endpoint() wants to validate port driver attach immediately after
2608dd2bc0fSBen Widawsky * endpoint registration.
2618dd2bc0fSBen Widawsky */
2628dd2bc0fSBen Widawsky MODULE_SOFTDEP("pre: cxl_port");
2638dd2bc0fSBen Widawsky