mirror of
https://github.com/torvalds/linux.git
synced 2025-12-01 07:26:02 +07:00
The current implementation enumerates the dports during the cxl_port driver probe. Without an endpoint connected, the dport may not be active during port probe. This scheme may prevent a valid hardware dport id to be retrieved and MMIO registers to be read when an endpoint is hot-plugged. Move the dport allocation and setup to behind memdev probe so the endpoint is guaranteed to be connected. In the original enumeration behavior, there are 3 phases (or 2 if no CXL switches) for port creation. cxl_acpi() creates a Root Port (RP) from the ACPI0017.N device. Through that it enumerates downstream ports composed of ACPI0016.N devices through add_host_bridge_dport(). Once done, it uses add_host_bridge_uport() to create the ports that enumerate the PCI RPs as the dports of these ports. Every time a port is created, the port driver is attached, cxl_switch_porbe_probe() is called and devm_cxl_port_enumerate_dports() is invoked to enumerate and probe the dports. The second phase is if there are any CXL switches. When the pci endpoint device driver (cxl_pci) calls probe, it will add a mem device and triggers the cxl_mem_probe(). cxl_mem_probe() calls devm_cxl_enumerate_ports() and attempts to discovery and create all the ports represent CXL switches. During this phase, a port is created per switch and the attached dports are also enumerated and probed. The last phase is creating endpoint port which happens for all endpoint devices. The new sequence is instead of creating all possible dports at initial port creation, defer port instantiation until a memdev beneath that dport arrives. Introduce devm_cxl_create_or_extend_port() to centralize the creation and extension of ports with new dports as memory devices arrive. As part of this rework, switch decoder target list is amended at runtime as dports show up. While the decoders are allocated during the port driver probe, The decoders must also be updated since previously they were setup when all the dports are setup. Now every time a dport is setup per endpoint, the switch target listing need to be updated with new dport. A guard(rwsem_write) is used to update decoder targets. This is similar to when decoder_populate_target() is called and the decoder programming must be protected. Also the port registers are probed the first time when the first dport shows up. This ensures that the CXL link is established when the port registers are probed. [dj] Use ERR_CAST() (Jonathan) Link: https://lore.kernel.org/linux-cxl/20250305100123.3077031-1-rrichter@amd.com/ Reviewed-by: Alison Schofield <alison.schofield@intel.com> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Signed-off-by: Dave Jiang <dave.jiang@intel.com>
183 lines
4.4 KiB
C
183 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "cxlmem.h"
|
|
#include "cxlpci.h"
|
|
|
|
/**
|
|
* DOC: cxl port
|
|
*
|
|
* The port driver enumerates dport via PCI and scans for HDM
|
|
* (Host-managed-Device-Memory) decoder resources via the
|
|
* @component_reg_phys value passed in by the agent that registered the
|
|
* port. All descendant ports of a CXL root port (described by platform
|
|
* firmware) are managed in this drivers context. Each driver instance
|
|
* is responsible for tearing down the driver context of immediate
|
|
* descendant ports. The locking for this is validated by
|
|
* CONFIG_PROVE_CXL_LOCKING.
|
|
*
|
|
* The primary service this driver provides is presenting APIs to other
|
|
* drivers to utilize the decoders, and indicating to userspace (via bind
|
|
* status) the connectivity of the CXL.mem protocol throughout the
|
|
* PCIe topology.
|
|
*/
|
|
|
|
static void schedule_detach(void *cxlmd)
|
|
{
|
|
schedule_cxl_memdev_detach(cxlmd);
|
|
}
|
|
|
|
static int discover_region(struct device *dev, void *unused)
|
|
{
|
|
struct cxl_endpoint_decoder *cxled;
|
|
int rc;
|
|
|
|
if (!is_endpoint_decoder(dev))
|
|
return 0;
|
|
|
|
cxled = to_cxl_endpoint_decoder(dev);
|
|
if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
|
|
return 0;
|
|
|
|
if (cxled->state != CXL_DECODER_STATE_AUTO)
|
|
return 0;
|
|
|
|
/*
|
|
* Region enumeration is opportunistic, if this add-event fails,
|
|
* continue to the next endpoint decoder.
|
|
*/
|
|
rc = cxl_add_to_region(cxled);
|
|
if (rc)
|
|
dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
|
|
cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cxl_switch_port_probe(struct cxl_port *port)
|
|
{
|
|
/* Reset nr_dports for rebind of driver */
|
|
port->nr_dports = 0;
|
|
|
|
/* Cache the data early to ensure is_visible() works */
|
|
read_cdat_data(port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cxl_endpoint_port_probe(struct cxl_port *port)
|
|
{
|
|
struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
|
|
int rc;
|
|
|
|
/* Cache the data early to ensure is_visible() works */
|
|
read_cdat_data(port);
|
|
cxl_endpoint_parse_cdat(port);
|
|
|
|
get_device(&cxlmd->dev);
|
|
rc = devm_add_action_or_reset(&port->dev, schedule_detach, cxlmd);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = devm_cxl_endpoint_decoders_setup(port);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Now that all endpoint decoders are successfully enumerated, try to
|
|
* assemble regions from committed decoders
|
|
*/
|
|
device_for_each_child(&port->dev, NULL, discover_region);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cxl_port_probe(struct device *dev)
|
|
{
|
|
struct cxl_port *port = to_cxl_port(dev);
|
|
|
|
if (is_cxl_endpoint(port))
|
|
return cxl_endpoint_port_probe(port);
|
|
return cxl_switch_port_probe(port);
|
|
}
|
|
|
|
static ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
|
|
const struct bin_attribute *bin_attr, char *buf,
|
|
loff_t offset, size_t count)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct cxl_port *port = to_cxl_port(dev);
|
|
|
|
if (!port->cdat_available)
|
|
return -ENXIO;
|
|
|
|
if (!port->cdat.table)
|
|
return 0;
|
|
|
|
return memory_read_from_buffer(buf, count, &offset,
|
|
port->cdat.table,
|
|
port->cdat.length);
|
|
}
|
|
|
|
static const BIN_ATTR_ADMIN_RO(CDAT, 0);
|
|
|
|
static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
|
|
const struct bin_attribute *attr, int i)
|
|
{
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
struct cxl_port *port = to_cxl_port(dev);
|
|
|
|
if ((attr == &bin_attr_CDAT) && port->cdat_available)
|
|
return attr->attr.mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct bin_attribute *const cxl_cdat_bin_attributes[] = {
|
|
&bin_attr_CDAT,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group cxl_cdat_attribute_group = {
|
|
.bin_attrs = cxl_cdat_bin_attributes,
|
|
.is_bin_visible = cxl_port_bin_attr_is_visible,
|
|
};
|
|
|
|
static const struct attribute_group *cxl_port_attribute_groups[] = {
|
|
&cxl_cdat_attribute_group,
|
|
NULL,
|
|
};
|
|
|
|
static struct cxl_driver cxl_port_driver = {
|
|
.name = "cxl_port",
|
|
.probe = cxl_port_probe,
|
|
.id = CXL_DEVICE_PORT,
|
|
.drv = {
|
|
.dev_groups = cxl_port_attribute_groups,
|
|
},
|
|
};
|
|
|
|
static int __init cxl_port_init(void)
|
|
{
|
|
return cxl_driver_register(&cxl_port_driver);
|
|
}
|
|
/*
|
|
* Be ready to immediately enable ports emitted by the platform CXL root
|
|
* (e.g. cxl_acpi) when CONFIG_CXL_PORT=y.
|
|
*/
|
|
subsys_initcall(cxl_port_init);
|
|
|
|
static void __exit cxl_port_exit(void)
|
|
{
|
|
cxl_driver_unregister(&cxl_port_driver);
|
|
}
|
|
module_exit(cxl_port_exit);
|
|
|
|
MODULE_DESCRIPTION("CXL: Port enumeration and services");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_IMPORT_NS("CXL");
|
|
MODULE_ALIAS_CXL(CXL_DEVICE_PORT);
|