162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
362306a36Sopenharmony_ci#include <linux/device.h>
462306a36Sopenharmony_ci#include <linux/module.h>
562306a36Sopenharmony_ci#include <linux/slab.h>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include "cxlmem.h"
862306a36Sopenharmony_ci#include "cxlpci.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/**
1162306a36Sopenharmony_ci * DOC: cxl port
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * The port driver enumerates dport via PCI and scans for HDM
1462306a36Sopenharmony_ci * (Host-managed-Device-Memory) decoder resources via the
1562306a36Sopenharmony_ci * @component_reg_phys value passed in by the agent that registered the
1662306a36Sopenharmony_ci * port. All descendant ports of a CXL root port (described by platform
1762306a36Sopenharmony_ci * firmware) are managed in this drivers context. Each driver instance
1862306a36Sopenharmony_ci * is responsible for tearing down the driver context of immediate
1962306a36Sopenharmony_ci * descendant ports. The locking for this is validated by
2062306a36Sopenharmony_ci * CONFIG_PROVE_CXL_LOCKING.
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * The primary service this driver provides is presenting APIs to other
2362306a36Sopenharmony_ci * drivers to utilize the decoders, and indicating to userspace (via bind
2462306a36Sopenharmony_ci * status) the connectivity of the CXL.mem protocol throughout the
2562306a36Sopenharmony_ci * PCIe topology.
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void schedule_detach(void *cxlmd)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	schedule_cxl_memdev_detach(cxlmd);
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int discover_region(struct device *dev, void *root)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct cxl_endpoint_decoder *cxled;
3662306a36Sopenharmony_ci	int rc;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (!is_endpoint_decoder(dev))
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	cxled = to_cxl_endpoint_decoder(dev);
4262306a36Sopenharmony_ci	if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
4362306a36Sopenharmony_ci		return 0;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (cxled->state != CXL_DECODER_STATE_AUTO)
4662306a36Sopenharmony_ci		return 0;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/*
4962306a36Sopenharmony_ci	 * Region enumeration is opportunistic, if this add-event fails,
5062306a36Sopenharmony_ci	 * continue to the next endpoint decoder.
5162306a36Sopenharmony_ci	 */
5262306a36Sopenharmony_ci	rc = cxl_add_to_region(root, cxled);
5362306a36Sopenharmony_ci	if (rc)
5462306a36Sopenharmony_ci		dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
5562306a36Sopenharmony_ci			cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	return 0;
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int cxl_switch_port_probe(struct cxl_port *port)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct cxl_hdm *cxlhdm;
6362306a36Sopenharmony_ci	int rc;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	rc = devm_cxl_port_enumerate_dports(port);
6662306a36Sopenharmony_ci	if (rc < 0)
6762306a36Sopenharmony_ci		return rc;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	cxlhdm = devm_cxl_setup_hdm(port, NULL);
7062306a36Sopenharmony_ci	if (!IS_ERR(cxlhdm))
7162306a36Sopenharmony_ci		return devm_cxl_enumerate_decoders(cxlhdm, NULL);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (PTR_ERR(cxlhdm) != -ENODEV) {
7462306a36Sopenharmony_ci		dev_err(&port->dev, "Failed to map HDM decoder capability\n");
7562306a36Sopenharmony_ci		return PTR_ERR(cxlhdm);
7662306a36Sopenharmony_ci	}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (rc == 1) {
7962306a36Sopenharmony_ci		dev_dbg(&port->dev, "Fallback to passthrough decoder\n");
8062306a36Sopenharmony_ci		return devm_cxl_add_passthrough_decoder(port);
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	dev_err(&port->dev, "HDM decoder capability not found\n");
8462306a36Sopenharmony_ci	return -ENXIO;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic int cxl_endpoint_port_probe(struct cxl_port *port)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct cxl_endpoint_dvsec_info info = { .port = port };
9062306a36Sopenharmony_ci	struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
9162306a36Sopenharmony_ci	struct cxl_dev_state *cxlds = cxlmd->cxlds;
9262306a36Sopenharmony_ci	struct cxl_hdm *cxlhdm;
9362306a36Sopenharmony_ci	struct cxl_port *root;
9462306a36Sopenharmony_ci	int rc;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	rc = cxl_dvsec_rr_decode(cxlds->dev, cxlds->cxl_dvsec, &info);
9762306a36Sopenharmony_ci	if (rc < 0)
9862306a36Sopenharmony_ci		return rc;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	cxlhdm = devm_cxl_setup_hdm(port, &info);
10162306a36Sopenharmony_ci	if (IS_ERR(cxlhdm)) {
10262306a36Sopenharmony_ci		if (PTR_ERR(cxlhdm) == -ENODEV)
10362306a36Sopenharmony_ci			dev_err(&port->dev, "HDM decoder registers not found\n");
10462306a36Sopenharmony_ci		return PTR_ERR(cxlhdm);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* Cache the data early to ensure is_visible() works */
10862306a36Sopenharmony_ci	read_cdat_data(port);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	get_device(&cxlmd->dev);
11162306a36Sopenharmony_ci	rc = devm_add_action_or_reset(&port->dev, schedule_detach, cxlmd);
11262306a36Sopenharmony_ci	if (rc)
11362306a36Sopenharmony_ci		return rc;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	rc = cxl_hdm_decode_init(cxlds, cxlhdm, &info);
11662306a36Sopenharmony_ci	if (rc)
11762306a36Sopenharmony_ci		return rc;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	rc = devm_cxl_enumerate_decoders(cxlhdm, &info);
12062306a36Sopenharmony_ci	if (rc)
12162306a36Sopenharmony_ci		return rc;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/*
12462306a36Sopenharmony_ci	 * This can't fail in practice as CXL root exit unregisters all
12562306a36Sopenharmony_ci	 * descendant ports and that in turn synchronizes with cxl_port_probe()
12662306a36Sopenharmony_ci	 */
12762306a36Sopenharmony_ci	root = find_cxl_root(port);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/*
13062306a36Sopenharmony_ci	 * Now that all endpoint decoders are successfully enumerated, try to
13162306a36Sopenharmony_ci	 * assemble regions from committed decoders
13262306a36Sopenharmony_ci	 */
13362306a36Sopenharmony_ci	device_for_each_child(&port->dev, root, discover_region);
13462306a36Sopenharmony_ci	put_device(&root->dev);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int cxl_port_probe(struct device *dev)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct cxl_port *port = to_cxl_port(dev);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (is_cxl_endpoint(port))
14462306a36Sopenharmony_ci		return cxl_endpoint_port_probe(port);
14562306a36Sopenharmony_ci	return cxl_switch_port_probe(port);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
14962306a36Sopenharmony_ci			 struct bin_attribute *bin_attr, char *buf,
15062306a36Sopenharmony_ci			 loff_t offset, size_t count)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
15362306a36Sopenharmony_ci	struct cxl_port *port = to_cxl_port(dev);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (!port->cdat_available)
15662306a36Sopenharmony_ci		return -ENXIO;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (!port->cdat.table)
15962306a36Sopenharmony_ci		return 0;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return memory_read_from_buffer(buf, count, &offset,
16262306a36Sopenharmony_ci				       port->cdat.table,
16362306a36Sopenharmony_ci				       port->cdat.length);
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic BIN_ATTR_ADMIN_RO(CDAT, 0);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
16962306a36Sopenharmony_ci					    struct bin_attribute *attr, int i)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
17262306a36Sopenharmony_ci	struct cxl_port *port = to_cxl_port(dev);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if ((attr == &bin_attr_CDAT) && port->cdat_available)
17562306a36Sopenharmony_ci		return attr->attr.mode;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic struct bin_attribute *cxl_cdat_bin_attributes[] = {
18162306a36Sopenharmony_ci	&bin_attr_CDAT,
18262306a36Sopenharmony_ci	NULL,
18362306a36Sopenharmony_ci};
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic struct attribute_group cxl_cdat_attribute_group = {
18662306a36Sopenharmony_ci	.bin_attrs = cxl_cdat_bin_attributes,
18762306a36Sopenharmony_ci	.is_bin_visible = cxl_port_bin_attr_is_visible,
18862306a36Sopenharmony_ci};
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic const struct attribute_group *cxl_port_attribute_groups[] = {
19162306a36Sopenharmony_ci	&cxl_cdat_attribute_group,
19262306a36Sopenharmony_ci	NULL,
19362306a36Sopenharmony_ci};
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic struct cxl_driver cxl_port_driver = {
19662306a36Sopenharmony_ci	.name = "cxl_port",
19762306a36Sopenharmony_ci	.probe = cxl_port_probe,
19862306a36Sopenharmony_ci	.id = CXL_DEVICE_PORT,
19962306a36Sopenharmony_ci	.drv = {
20062306a36Sopenharmony_ci		.dev_groups = cxl_port_attribute_groups,
20162306a36Sopenharmony_ci	},
20262306a36Sopenharmony_ci};
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cimodule_cxl_driver(cxl_port_driver);
20562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
20662306a36Sopenharmony_ciMODULE_IMPORT_NS(CXL);
20762306a36Sopenharmony_ciMODULE_ALIAS_CXL(CXL_DEVICE_PORT);
208