154cdbf84SBen Widawsky // SPDX-License-Identifier: GPL-2.0-only
254cdbf84SBen Widawsky /* Copyright(c) 2022 Intel Corporation. All rights reserved. */
354cdbf84SBen Widawsky #include <linux/device.h>
454cdbf84SBen Widawsky #include <linux/module.h>
554cdbf84SBen Widawsky #include <linux/slab.h>
654cdbf84SBen Widawsky
754cdbf84SBen Widawsky #include "cxlmem.h"
854cdbf84SBen Widawsky #include "cxlpci.h"
954cdbf84SBen Widawsky
1054cdbf84SBen Widawsky /**
1154cdbf84SBen Widawsky * DOC: cxl port
1254cdbf84SBen Widawsky *
1354cdbf84SBen Widawsky * The port driver enumerates dport via PCI and scans for HDM
1454cdbf84SBen Widawsky * (Host-managed-Device-Memory) decoder resources via the
1554cdbf84SBen Widawsky * @component_reg_phys value passed in by the agent that registered the
1654cdbf84SBen Widawsky * port. All descendant ports of a CXL root port (described by platform
1754cdbf84SBen Widawsky * firmware) are managed in this drivers context. Each driver instance
1854cdbf84SBen Widawsky * is responsible for tearing down the driver context of immediate
1954cdbf84SBen Widawsky * descendant ports. The locking for this is validated by
2054cdbf84SBen Widawsky * CONFIG_PROVE_CXL_LOCKING.
2154cdbf84SBen Widawsky *
2254cdbf84SBen Widawsky * The primary service this driver provides is presenting APIs to other
2354cdbf84SBen Widawsky * drivers to utilize the decoders, and indicating to userspace (via bind
2454cdbf84SBen Widawsky * status) the connectivity of the CXL.mem protocol throughout the
2554cdbf84SBen Widawsky * PCIe topology.
2654cdbf84SBen Widawsky */
2754cdbf84SBen Widawsky
schedule_detach(void * cxlmd)288dd2bc0fSBen Widawsky static void schedule_detach(void *cxlmd)
298dd2bc0fSBen Widawsky {
308dd2bc0fSBen Widawsky schedule_cxl_memdev_detach(cxlmd);
318dd2bc0fSBen Widawsky }
328dd2bc0fSBen Widawsky
discover_region(struct device * dev,void * root)33a32320b7SDan Williams static int discover_region(struct device *dev, void *root)
34a32320b7SDan Williams {
35a32320b7SDan Williams struct cxl_endpoint_decoder *cxled;
36a32320b7SDan Williams int rc;
37a32320b7SDan Williams
38a32320b7SDan Williams if (!is_endpoint_decoder(dev))
39a32320b7SDan Williams return 0;
40a32320b7SDan Williams
41a32320b7SDan Williams cxled = to_cxl_endpoint_decoder(dev);
42a32320b7SDan Williams if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
43a32320b7SDan Williams return 0;
44a32320b7SDan Williams
45a32320b7SDan Williams if (cxled->state != CXL_DECODER_STATE_AUTO)
46a32320b7SDan Williams return 0;
47a32320b7SDan Williams
48a32320b7SDan Williams /*
49a32320b7SDan Williams * Region enumeration is opportunistic, if this add-event fails,
50a32320b7SDan Williams * continue to the next endpoint decoder.
51a32320b7SDan Williams */
52a32320b7SDan Williams rc = cxl_add_to_region(root, cxled);
53a32320b7SDan Williams if (rc)
54a32320b7SDan Williams dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
55a32320b7SDan Williams cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
56a32320b7SDan Williams
57a32320b7SDan Williams return 0;
58a32320b7SDan Williams }
59a32320b7SDan Williams
cxl_switch_port_probe(struct cxl_port * port)6032ce3f18SDan Williams static int cxl_switch_port_probe(struct cxl_port *port)
6154cdbf84SBen Widawsky {
6254cdbf84SBen Widawsky struct cxl_hdm *cxlhdm;
63*eb0764b8SDan Williams int rc;
6454cdbf84SBen Widawsky
65*eb0764b8SDan Williams rc = devm_cxl_port_enumerate_dports(port);
66*eb0764b8SDan Williams if (rc < 0)
67*eb0764b8SDan Williams return rc;
6832ce3f18SDan Williams
69a5fcd228SDan Williams cxlhdm = devm_cxl_setup_hdm(port, NULL);
70*eb0764b8SDan Williams if (!IS_ERR(cxlhdm))
71*eb0764b8SDan Williams return devm_cxl_enumerate_decoders(cxlhdm, NULL);
72*eb0764b8SDan Williams
73*eb0764b8SDan Williams if (PTR_ERR(cxlhdm) != -ENODEV) {
747bba261eSDan Williams dev_err(&port->dev, "Failed to map HDM decoder capability\n");
75a5fcd228SDan Williams return PTR_ERR(cxlhdm);
767bba261eSDan Williams }
777bba261eSDan Williams
787bba261eSDan Williams if (rc == 1) {
797bba261eSDan Williams dev_dbg(&port->dev, "Fallback to passthrough decoder\n");
807bba261eSDan Williams return devm_cxl_add_passthrough_decoder(port);
817bba261eSDan Williams }
82*eb0764b8SDan Williams
837bba261eSDan Williams dev_err(&port->dev, "HDM decoder capability not found\n");
847bba261eSDan Williams return -ENXIO;
857bba261eSDan Williams }
867bba261eSDan Williams
cxl_endpoint_port_probe(struct cxl_port * port)877bba261eSDan Williams static int cxl_endpoint_port_probe(struct cxl_port *port)
887bba261eSDan Williams {
8932ce3f18SDan Williams struct cxl_endpoint_dvsec_info info = { .port = port };
9032ce3f18SDan Williams struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
9132ce3f18SDan Williams struct cxl_dev_state *cxlds = cxlmd->cxlds;
9232ce3f18SDan Williams struct cxl_hdm *cxlhdm;
93b70c2cf9SDan Williams struct cxl_port *root;
94fcfbc93cSDan Williams int rc;
95fcfbc93cSDan Williams
9632ce3f18SDan Williams rc = cxl_dvsec_rr_decode(cxlds->dev, cxlds->cxl_dvsec, &info);
97a32320b7SDan Williams if (rc < 0)
9832ce3f18SDan Williams return rc;
9932ce3f18SDan Williams
100a5fcd228SDan Williams cxlhdm = devm_cxl_setup_hdm(port, &info);
101a5fcd228SDan Williams if (IS_ERR(cxlhdm)) {
102a5fcd228SDan Williams if (PTR_ERR(cxlhdm) == -ENODEV)
103a5fcd228SDan Williams dev_err(&port->dev, "HDM decoder registers not found\n");
1044474ce56SDave Jiang return PTR_ERR(cxlhdm);
10532ce3f18SDan Williams }
10632ce3f18SDan Williams
107fcfbc93cSDan Williams /* Cache the data early to ensure is_visible() works */
108c9700604SIra Weiny read_cdat_data(port);
109c9700604SIra Weiny
110c9700604SIra Weiny get_device(&cxlmd->dev);
111fcfbc93cSDan Williams rc = devm_add_action_or_reset(&port->dev, schedule_detach, cxlmd);
11232ce3f18SDan Williams if (rc)
113fcfbc93cSDan Williams return rc;
114fcfbc93cSDan Williams
115fcfbc93cSDan Williams rc = cxl_hdm_decode_init(cxlds, cxlhdm, &info);
11659c3368bSDave Jiang if (rc)
117fcfbc93cSDan Williams return rc;
118fcfbc93cSDan Williams
119fcfbc93cSDan Williams rc = devm_cxl_enumerate_decoders(cxlhdm, &info);
120b777e9beSDave Jiang if (rc)
121a32320b7SDan Williams return rc;
122a32320b7SDan Williams
123a32320b7SDan Williams /*
124a32320b7SDan Williams * This can't fail in practice as CXL root exit unregisters all
125a32320b7SDan Williams * descendant ports and that in turn synchronizes with cxl_port_probe()
126a32320b7SDan Williams */
127a32320b7SDan Williams root = find_cxl_root(port);
128d35b495dSDan Williams
129a32320b7SDan Williams /*
130a32320b7SDan Williams * Now that all endpoint decoders are successfully enumerated, try to
131a32320b7SDan Williams * assemble regions from committed decoders
132a32320b7SDan Williams */
133a32320b7SDan Williams device_for_each_child(&port->dev, root, discover_region);
134a32320b7SDan Williams put_device(&root->dev);
135a32320b7SDan Williams
136a32320b7SDan Williams return 0;
137a32320b7SDan Williams }
13832ce3f18SDan Williams
cxl_port_probe(struct device * dev)13932ce3f18SDan Williams static int cxl_port_probe(struct device *dev)
14032ce3f18SDan Williams {
14132ce3f18SDan Williams struct cxl_port *port = to_cxl_port(dev);
14232ce3f18SDan Williams
14332ce3f18SDan Williams if (is_cxl_endpoint(port))
14432ce3f18SDan Williams return cxl_endpoint_port_probe(port);
14532ce3f18SDan Williams return cxl_switch_port_probe(port);
14632ce3f18SDan Williams }
14754cdbf84SBen Widawsky
CDAT_read(struct file * filp,struct kobject * kobj,struct bin_attribute * bin_attr,char * buf,loff_t offset,size_t count)14854cdbf84SBen Widawsky static ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
149c9700604SIra Weiny struct bin_attribute *bin_attr, char *buf,
150c9700604SIra Weiny loff_t offset, size_t count)
151c9700604SIra Weiny {
152c9700604SIra Weiny struct device *dev = kobj_to_dev(kobj);
153c9700604SIra Weiny struct cxl_port *port = to_cxl_port(dev);
154c9700604SIra Weiny
155c9700604SIra Weiny if (!port->cdat_available)
156c9700604SIra Weiny return -ENXIO;
157c9700604SIra Weiny
158c9700604SIra Weiny if (!port->cdat.table)
159c9700604SIra Weiny return 0;
160c9700604SIra Weiny
161c9700604SIra Weiny return memory_read_from_buffer(buf, count, &offset,
162c9700604SIra Weiny port->cdat.table,
163c9700604SIra Weiny port->cdat.length);
164c9700604SIra Weiny }
165c9700604SIra Weiny
166c9700604SIra Weiny static BIN_ATTR_ADMIN_RO(CDAT, 0);
167c9700604SIra Weiny
cxl_port_bin_attr_is_visible(struct kobject * kobj,struct bin_attribute * attr,int i)168c9700604SIra Weiny static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
169c9700604SIra Weiny struct bin_attribute *attr, int i)
170c9700604SIra Weiny {
171c9700604SIra Weiny struct device *dev = kobj_to_dev(kobj);
172c9700604SIra Weiny struct cxl_port *port = to_cxl_port(dev);
173c9700604SIra Weiny
174c9700604SIra Weiny if ((attr == &bin_attr_CDAT) && port->cdat_available)
175c9700604SIra Weiny return attr->attr.mode;
176c9700604SIra Weiny
177c9700604SIra Weiny return 0;
178c9700604SIra Weiny }
179c9700604SIra Weiny
180c9700604SIra Weiny static struct bin_attribute *cxl_cdat_bin_attributes[] = {
181c9700604SIra Weiny &bin_attr_CDAT,
182c9700604SIra Weiny NULL,
183c9700604SIra Weiny };
184c9700604SIra Weiny
185c9700604SIra Weiny static struct attribute_group cxl_cdat_attribute_group = {
186c9700604SIra Weiny .bin_attrs = cxl_cdat_bin_attributes,
187c9700604SIra Weiny .is_bin_visible = cxl_port_bin_attr_is_visible,
188c9700604SIra Weiny };
189c9700604SIra Weiny
190c9700604SIra Weiny static const struct attribute_group *cxl_port_attribute_groups[] = {
191c9700604SIra Weiny &cxl_cdat_attribute_group,
192c9700604SIra Weiny NULL,
193c9700604SIra Weiny };
194c9700604SIra Weiny
195c9700604SIra Weiny static struct cxl_driver cxl_port_driver = {
19654cdbf84SBen Widawsky .name = "cxl_port",
19754cdbf84SBen Widawsky .probe = cxl_port_probe,
19854cdbf84SBen Widawsky .id = CXL_DEVICE_PORT,
19954cdbf84SBen Widawsky .drv = {
200c9700604SIra Weiny .dev_groups = cxl_port_attribute_groups,
201c9700604SIra Weiny },
202c9700604SIra Weiny };
20354cdbf84SBen Widawsky
20454cdbf84SBen Widawsky module_cxl_driver(cxl_port_driver);
20554cdbf84SBen Widawsky MODULE_LICENSE("GPL v2");
20654cdbf84SBen Widawsky MODULE_IMPORT_NS(CXL);
20754cdbf84SBen Widawsky MODULE_ALIAS_CXL(CXL_DEVICE_PORT);
20854cdbf84SBen Widawsky