/** * DOC: cxl core * * The CXL core provides a set of interfaces that can be consumed by CXL aware * drivers. The interfaces allow for creation, modification, and destruction of * regions, memory devices, ports, and decoders. CXL aware drivers must register * with the CXL core via these interfaces in order to be able to participate in * cross-device interleave coordination. The CXL core also establishes and * maintains the bridge to the nvdimm subsystem. * * CXL core introduces sysfs hierarchy to control the devices that are * instantiated by the core.
*/
/* * CXL root port's and the first level of ports are unregistered * under the platform firmware device lock, all other ports are * unregistered while holding their parent port lock.
*/ if (!parent)
lock_dev = port->uport_dev; elseif (is_cxl_root(parent))
lock_dev = parent->uport_dev; else
lock_dev = &parent->dev;
/* No parent_dport, root cxl_port */ if (!parent_dport) {
cxl_root = kzalloc(sizeof(*cxl_root), GFP_KERNEL); if (!cxl_root) return ERR_PTR(-ENOMEM);
} else {
_port = kzalloc(sizeof(*port), GFP_KERNEL); if (!_port) return ERR_PTR(-ENOMEM);
}
rc = ida_alloc(&cxl_port_ida, GFP_KERNEL); if (rc < 0) return ERR_PTR(rc);
if (cxl_root)
port = &no_free_ptr(cxl_root)->port; else
port = no_free_ptr(_port);
port->id = rc;
port->uport_dev = uport_dev;
/* * The top-level cxl_port "cxl_root" does not have a cxl_port as * its parent and it does not have any corresponding component * registers as its decode is described by a fixed platform * description.
*/
dev = &port->dev; if (parent_dport) { struct cxl_port *parent_port = parent_dport->port; struct cxl_port *iter;
/* * walk to the host bridge, or the first ancestor that knows * the host bridge
*/
iter = port; while (!iter->host_bridge &&
!is_cxl_root(to_cxl_port(iter->dev.parent)))
iter = to_cxl_port(iter->dev.parent); if (iter->host_bridge)
port->host_bridge = iter->host_bridge; elseif (parent_dport->rch)
port->host_bridge = parent_dport->dport_dev; else
port->host_bridge = iter->uport_dev;
dev_dbg(uport_dev, "host-bridge: %s\n",
dev_name(port->host_bridge));
} else
dev->parent = uport_dev;
/* * use @dport->dport_dev for the context for error messages during * register probing, and fixup @host after the fact, since @host may be * NULL.
*/
rc = cxl_setup_comp_regs(dport->dport_dev, &dport->reg_map,
component_reg_phys);
dport->reg_map.host = host; return rc;
}
/* * dport_dev needs to be a PCIe port for CXL 2.0+ ports because * EINJ expects a dport SBDF to be specified for 2.0 error injection.
*/ if (!dport->rch && !dev_is_pci(dport->dport_dev)) return;
dir = cxl_debugfs_create_dir(dev_name(dport->dport_dev));
rc = dev_set_name(dev, "endpoint%d", port->id); if (rc) return rc;
/* * The endpoint driver already enumerated the component and RAS * registers. Reuse that enumeration while prepping them to be * mapped by the cxl_port driver.
*/
port->reg_map = cxlds->reg_map;
port->reg_map.host = &port->dev;
cxlmd->endpoint = port;
} elseif (parent_dport) {
rc = dev_set_name(dev, "port%d", port->id); if (rc) return rc;
struct pci_bus *cxl_port_to_pci_bus(struct cxl_port *port)
{ /* There is no pci_bus associated with a CXL platform-root port */ if (is_cxl_root(port)) return NULL;
if (dev_is_pci(port->uport_dev)) { struct pci_dev *pdev = to_pci_dev(port->uport_dev);
device_lock_assert(&port->dev);
dup = find_dport(port, dport->port_id); if (dup) {
dev_err(&port->dev, "unable to add dport%d-%s non-unique port id (%s)\n",
dport->port_id, dev_name(dport->dport_dev),
dev_name(dup->dport_dev)); return -EBUSY;
}
rc = xa_insert(&port->dports, (unsignedlong)dport->dport_dev, dport,
GFP_KERNEL); if (rc) return rc;
port->nr_dports++; return 0;
}
/* * Since root-level CXL dports cannot be enumerated by PCI they are not * enumerated by the common port driver that acquires the port lock over * dport add/remove. Instead, root dports are manually added by a * platform driver and cond_cxl_root_lock() is used to take the missing * port lock in that case.
*/ staticvoid cond_cxl_root_lock(struct cxl_port *port)
{ if (is_cxl_root(port))
device_lock(&port->dev);
}
staticvoid cond_cxl_root_unlock(struct cxl_port *port)
{ if (is_cxl_root(port))
device_unlock(&port->dev);
}
if (rcrb == CXL_RESOURCE_NONE) {
rc = cxl_dport_setup_regs(&port->dev, dport,
component_reg_phys); if (rc) return ERR_PTR(rc);
} else {
dport->rcrb.base = rcrb;
component_reg_phys = __rcrb_to_component(dport_dev, &dport->rcrb,
CXL_RCRB_DOWNSTREAM); if (component_reg_phys == CXL_RESOURCE_NONE) {
dev_warn(dport_dev, "Invalid Component Registers in RCRB"); return ERR_PTR(-ENXIO);
}
/* * RCH @dport is not ready to map until associated with its * memdev
*/
rc = cxl_dport_setup_regs(NULL, dport, component_reg_phys); if (rc) return ERR_PTR(rc);
dport->rch = true;
}
if (component_reg_phys != CXL_RESOURCE_NONE)
dev_dbg(dport_dev, "Component Registers found for dport: %pa\n",
&component_reg_phys);
cond_cxl_root_lock(port);
rc = add_dport(port, dport);
cond_cxl_root_unlock(port); if (rc) return ERR_PTR(rc);
get_device(dport_dev);
rc = devm_add_action_or_reset(host, cxl_dport_remove, dport); if (rc) return ERR_PTR(rc);
rc = sysfs_create_link(&port->dev.kobj, &dport_dev->kobj, link_name); if (rc) return ERR_PTR(rc);
rc = devm_add_action_or_reset(host, cxl_dport_unlink, dport); if (rc) return ERR_PTR(rc);
if (dev_is_pci(dport_dev))
dport->link_latency = cxl_pci_get_latency(to_pci_dev(dport_dev));
cxl_debugfs_create_dport_dir(dport);
return dport;
}
/** * devm_cxl_add_dport - append VH downstream port data to a cxl_port * @port: the cxl_port that references this dport * @dport_dev: firmware or PCI device representing the dport * @port_id: identifier for this dport in a decoder's target list * @component_reg_phys: optional location of CXL component registers * * Note that dports are appended to the devm release action's of the * either the port's host (for root ports), or the port itself (for * switch ports)
*/ struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port, struct device *dport_dev, int port_id,
resource_size_t component_reg_phys)
{ struct cxl_dport *dport;
dport = __devm_cxl_add_dport(port, dport_dev, port_id,
component_reg_phys, CXL_RESOURCE_NONE); if (IS_ERR(dport)) {
dev_dbg(dport_dev, "failed to add dport to %s: %ld\n",
dev_name(&port->dev), PTR_ERR(dport));
} else {
dev_dbg(dport_dev, "dport added to %s\n",
dev_name(&port->dev));
}
/** * devm_cxl_add_rch_dport - append RCH downstream port data to a cxl_port * @port: the cxl_port that references this dport * @dport_dev: firmware or PCI device representing the dport * @port_id: identifier for this dport in a decoder's target list * @rcrb: mandatory location of a Root Complex Register Block * * See CXL 3.0 9.11.8 CXL Devices Attached to an RCH
*/ struct cxl_dport *devm_cxl_add_rch_dport(struct cxl_port *port, struct device *dport_dev, int port_id,
resource_size_t rcrb)
{ struct cxl_dport *dport;
if (rcrb == CXL_RESOURCE_NONE) {
dev_dbg(&port->dev, "failed to add RCH dport, missing RCRB\n"); return ERR_PTR(-EINVAL);
}
dport = __devm_cxl_add_dport(port, dport_dev, port_id,
CXL_RESOURCE_NONE, rcrb); if (IS_ERR(dport)) {
dev_dbg(dport_dev, "failed to add RCH dport to %s: %ld\n",
dev_name(&port->dev), PTR_ERR(dport));
} else {
dev_dbg(dport_dev, "RCH dport added to %s\n",
dev_name(&port->dev));
}
/** * cxl_add_ep - register an endpoint's interest in a port * @dport: the dport that routes to @ep_dev * @ep_dev: device representing the endpoint * * Intermediate CXL ports are scanned based on the arrival of endpoints. * When those endpoints depart the port can be destroyed once all * endpoints that care about that port have been removed.
*/ staticint cxl_add_ep(struct cxl_dport *dport, struct device *ep_dev)
{ struct cxl_ep *ep; int rc;
ep = kzalloc(sizeof(*ep), GFP_KERNEL); if (!ep) return -ENOMEM;
ep->ep = get_device(ep_dev);
ep->dport = dport;
rc = add_ep(ep); if (rc)
cxl_ep_release(ep); return rc;
}
/* * All users of grandparent() are using it to walk PCIe-like switch port * hierarchy. A PCIe switch is comprised of a bridge device representing the * upstream switch port and N bridges representing downstream switch ports. When * bridges stack the grand-parent of a downstream switch port is another * downstream switch port in the immediate ancestor switch.
*/ staticstruct device *grandparent(struct device *dev)
{ if (dev && dev->parent) return dev->parent->parent; return NULL;
}
/* * The natural end of life of a non-root 'cxl_port' is when its parent port goes * through a ->remove() event ("top-down" unregistration). The unnatural trigger * for a port to be unregistered is when all memdevs beneath that port have gone * through ->remove(). This "bottom-up" removal selectively removes individual * child ports manually. This depends on devm_cxl_add_port() to not change is * devm action registration order, and for dports to have already been * destroyed by reap_dports().
*/ staticvoid delete_switch_port(struct cxl_port *port)
{
devm_release_action(port->dev.parent, cxl_unlink_parent_dport, port);
devm_release_action(port->dev.parent, cxl_unlink_uport, port);
devm_release_action(port->dev.parent, unregister_port, port);
}
for (int i = cxlmd->depth - 1; i >= 1; i--) { struct cxl_port *port, *parent_port; struct detach_ctx ctx = {
.cxlmd = cxlmd,
.depth = i,
}; struct cxl_ep *ep; bool died = false;
struct device *dev __free(put_device) =
bus_find_device(&cxl_bus_type, NULL, &ctx, port_has_memdev); if (!dev) continue;
port = to_cxl_port(dev);
parent_port = to_cxl_port(port->dev.parent);
device_lock(&parent_port->dev);
device_lock(&port->dev);
ep = cxl_ep_load(port, cxlmd);
dev_dbg(&cxlmd->dev, "disconnect %s from %s\n",
ep ? dev_name(ep->ep) : "", dev_name(&port->dev));
cxl_ep_remove(port, ep); if (ep && !port->dead && xa_empty(&port->endpoints) &&
!is_cxl_root(parent_port) && parent_port->dev.driver) { /* * This was the last ep attached to a dynamically * enumerated port. Block new cxl_add_ep() and garbage * collect the port.
*/
died = true;
port->dead = true;
reap_dports(port);
}
device_unlock(&port->dev);
/* * Theoretically, CXL component registers can be hosted on a * non-PCI device, in practice, only cxl_test hits this case.
*/ if (!dev_is_pci(dev)) return CXL_RESOURCE_NONE;
if (!dparent) { /* * The iteration reached the topology root without finding the * CXL-root 'cxl_port' on a previous iteration, fail for now to * be re-probed after platform driver attaches.
*/
dev_dbg(&cxlmd->dev, "%s is a root dport\n",
dev_name(dport_dev)); return -ENXIO;
}
struct cxl_port *parent_port __free(put_cxl_port) =
find_cxl_port(dparent, &parent_dport); if (!parent_port) { /* iterate to create this parent_port */ return -EAGAIN;
}
/* * Definition with __free() here to keep the sequence of * dereferencing the device of the port before the parent_port releasing.
*/ struct cxl_port *port __free(put_cxl_port) = NULL;
scoped_guard(device, &parent_port->dev) { if (!parent_port->dev.driver) {
dev_warn(&cxlmd->dev, "port %s:%s disabled, failed to enumerate CXL.mem\n",
dev_name(&parent_port->dev), dev_name(uport_dev)); return -ENXIO;
}
port = find_cxl_port_at(parent_port, dport_dev, &dport); if (!port) {
component_reg_phys = find_component_registers(uport_dev);
port = devm_cxl_add_port(&parent_port->dev, uport_dev,
component_reg_phys, parent_dport); if (IS_ERR(port)) return PTR_ERR(port);
/* retry find to pick up the new dport information */
port = find_cxl_port_at(parent_port, dport_dev, &dport); if (!port) return -ENXIO;
}
}
dev_dbg(&cxlmd->dev, "add to new port %s:%s\n",
dev_name(&port->dev), dev_name(port->uport_dev));
rc = cxl_add_ep(dport, &cxlmd->dev); if (rc == -EBUSY) { /* * "can't" happen, but this error code means * something to the caller, so translate it.
*/
rc = -ENXIO;
}
return rc;
}
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
{ struct device *dev = &cxlmd->dev; struct device *iter; int rc;
/* * Skip intermediate port enumeration in the RCH case, there * are no ports in between a host bridge and an endpoint.
*/ if (cxlmd->cxlds->rcd) return 0;
rc = devm_add_action_or_reset(&cxlmd->dev, cxl_detach_ep, cxlmd); if (rc) return rc;
/* * Scan for and add all cxl_ports in this device's ancestry. * Repeat until no more ports are added. Abort if a port add * attempt fails.
*/
retry: for (iter = dev; iter; iter = grandparent(iter)) { struct device *dport_dev = grandparent(iter); struct device *uport_dev; struct cxl_dport *dport;
/* * The terminal "grandparent" in PCI is NULL and @platform_bus * for platform devices
*/ if (!dport_dev || dport_dev == &platform_bus) return 0;
uport_dev = dport_dev->parent; if (!uport_dev) {
dev_warn(dev, "at %s no parent for dport: %s\n",
dev_name(iter), dev_name(dport_dev)); return -ENXIO;
}
/* * If the endpoint already exists in the port's list, * that's ok, it was added on a previous pass. * Otherwise, retry in add_port_attach_ep() after taking * the parent_port lock as the current port may be being * reaped.
*/ if (rc && rc != -EBUSY) return rc;
cxl_gpf_port_setup(dport);
/* Any more ports to add between this one and the root? */ if (!dev_is_cxl_root_child(&port->dev)) continue;
return 0;
}
rc = add_port_attach_ep(cxlmd, uport_dev, dport_dev); /* port missing, try to add parent */ if (rc == -EAGAIN) continue; /* failed to add ep or port */ if (rc) return rc; /* port added, new descendants possible, start over */ goto retry;
}
staticint decoder_populate_targets(struct cxl_switch_decoder *cxlsd, struct cxl_port *port, int *target_map)
{ int i;
if (!target_map) return 0;
device_lock_assert(&port->dev);
if (xa_empty(&port->dports)) return -EINVAL;
guard(rwsem_write)(&cxl_rwsem.region); for (i = 0; i < cxlsd->cxld.interleave_ways; i++) { struct cxl_dport *dport = find_dport(port, target_map[i]);
if (!dport) return -ENXIO;
cxlsd->target[i] = dport;
}
return 0;
}
staticstruct lock_class_key cxl_decoder_key;
/** * cxl_decoder_init - Common decoder setup / initialization * @port: owning port of this decoder * @cxld: common decoder properties to initialize * * A port may contain one or more decoders. Each of those decoders * enable some address space for CXL.mem utilization. A decoder is * expected to be configured by the caller before registering via * cxl_decoder_add()
*/ staticint cxl_decoder_init(struct cxl_port *port, struct cxl_decoder *cxld)
{ struct device *dev; int rc;
rc = ida_alloc(&port->decoder_ida, GFP_KERNEL); if (rc < 0) return rc;
/* need parent to stick around to release the id */
get_device(&port->dev);
cxld->id = rc;
/** * cxl_root_decoder_alloc - Allocate a root level decoder * @port: owning CXL root of this decoder * @nr_targets: static number of downstream targets * * Return: A new cxl decoder to be registered by cxl_decoder_add(). A * 'CXL root' decoder is one that decodes from a top-level / static platform * firmware description of CXL resources into a CXL standard decode * topology.
*/ struct cxl_root_decoder *cxl_root_decoder_alloc(struct cxl_port *port, unsignedint nr_targets)
{ struct cxl_root_decoder *cxlrd; struct cxl_switch_decoder *cxlsd; struct cxl_decoder *cxld; int rc;
if (!is_cxl_root(port)) return ERR_PTR(-EINVAL);
cxlrd = kzalloc(struct_size(cxlrd, cxlsd.target, nr_targets),
GFP_KERNEL); if (!cxlrd) return ERR_PTR(-ENOMEM);
/** * cxl_switch_decoder_alloc - Allocate a switch level decoder * @port: owning CXL switch port of this decoder * @nr_targets: max number of dynamically addressable downstream targets * * Return: A new cxl decoder to be registered by cxl_decoder_add(). A * 'switch' decoder is any decoder that can be enumerated by PCIe * topology and the HDM Decoder Capability. This includes the decoders * that sit between Switch Upstream Ports / Switch Downstream Ports and * Host Bridges / Root Ports.
*/ struct cxl_switch_decoder *cxl_switch_decoder_alloc(struct cxl_port *port, unsignedint nr_targets)
{ struct cxl_switch_decoder *cxlsd; struct cxl_decoder *cxld; int rc;
if (is_cxl_root(port) || is_cxl_endpoint(port)) return ERR_PTR(-EINVAL);
cxlsd = kzalloc(struct_size(cxlsd, target, nr_targets), GFP_KERNEL); if (!cxlsd) return ERR_PTR(-ENOMEM);
/** * cxl_endpoint_decoder_alloc - Allocate an endpoint decoder * @port: owning port of this decoder * * Return: A new cxl decoder to be registered by cxl_decoder_add()
*/ struct cxl_endpoint_decoder *cxl_endpoint_decoder_alloc(struct cxl_port *port)
{ struct cxl_endpoint_decoder *cxled; struct cxl_decoder *cxld; int rc;
if (!is_cxl_endpoint(port)) return ERR_PTR(-EINVAL);
cxled = kzalloc(sizeof(*cxled), GFP_KERNEL); if (!cxled) return ERR_PTR(-ENOMEM);
/** * cxl_decoder_add_locked - Add a decoder with targets * @cxld: The cxl decoder allocated by cxl_<type>_decoder_alloc() * @target_map: A list of downstream ports that this decoder can direct memory * traffic to. These numbers should correspond with the port number * in the PCIe Link Capabilities structure. * * Certain types of decoders may not have any targets. The main example of this * is an endpoint device. A more awkward example is a hostbridge whose root * ports get hot added (technically possible, though unlikely). * * This is the locked variant of cxl_decoder_add(). * * Context: Process context. Expects the device lock of the port that owns the * @cxld to be held. * * Return: Negative error code if the decoder wasn't properly configured; else * returns 0.
*/ int cxl_decoder_add_locked(struct cxl_decoder *cxld, int *target_map)
{ struct cxl_port *port; struct device *dev; int rc;
if (WARN_ON_ONCE(!cxld)) return -EINVAL;
if (WARN_ON_ONCE(IS_ERR(cxld))) return PTR_ERR(cxld);
if (cxld->interleave_ways < 1) return -EINVAL;
dev = &cxld->dev;
port = to_cxl_port(cxld->dev.parent); if (!is_endpoint_decoder(dev)) { struct cxl_switch_decoder *cxlsd = to_cxl_switch_decoder(dev);
rc = decoder_populate_targets(cxlsd, port, target_map); if (rc && (cxld->flags & CXL_DECODER_F_ENABLE)) {
dev_err(&port->dev, "Failed to populate active decoder targets\n"); return rc;
}
}
rc = dev_set_name(dev, "decoder%d.%d", port->id, cxld->id); if (rc) return rc;
/** * cxl_decoder_add - Add a decoder with targets * @cxld: The cxl decoder allocated by cxl_<type>_decoder_alloc() * @target_map: A list of downstream ports that this decoder can direct memory * traffic to. These numbers should correspond with the port number * in the PCIe Link Capabilities structure. * * This is the unlocked variant of cxl_decoder_add_locked(). * See cxl_decoder_add_locked(). * * Context: Process context. Takes and releases the device lock of the port that * owns the @cxld.
*/ int cxl_decoder_add(struct cxl_decoder *cxld, int *target_map)
{ struct cxl_port *port;
if (WARN_ON_ONCE(!cxld)) return -EINVAL;
if (WARN_ON_ONCE(IS_ERR(cxld))) return PTR_ERR(cxld);
/* * Skip calculation for RCD. Expectation is HMAT already covers RCD case * since RCH does not support hotplug.
*/ if (cxlmd->cxlds->rcd) return 0;
/* * Exit the loop when the parent port of the current iter port is cxl * root. The iterative loop starts at the endpoint and gathers the * latency of the CXL link from the current device/port to the connected * downstream port each iteration.
*/ do {
dport = iter->parent_dport;
iter = to_cxl_port(iter->dev.parent);
is_cxl_root = parent_port_is_cxl_root(iter);
/* * There's no valid access_coordinate for a root port since RPs do not * have CDAT and therefore needs to be skipped.
*/ if (!is_cxl_root) { if (!coordinates_valid(dport->coord)) return -EINVAL;
cxl_coordinates_combine(c, c, dport->coord);
}
add_latency(c, dport->link_latency);
} while (!is_cxl_root);
/* Check this port is connected to a switch DSP and not an RP */ if (parent_port_is_cxl_root(to_cxl_port(port->dev.parent))) return -ENODEV;
if (!coordinates_valid(dport->coord)) return -EINVAL;
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
c[i].read_bandwidth = dport->coord[i].read_bandwidth;
c[i].write_bandwidth = dport->coord[i].write_bandwidth;
}
return 0;
}
/* for user tooling to ensure port disable work has completed */ static ssize_t flush_store(conststruct bus_type *bus, constchar *buf, size_t count)
{ if (sysfs_streq(buf, "1")) {
flush_workqueue(cxl_bus_wq); return count;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.