Skip to content

Commit 4f06d81

Browse files
committed
cxl: Defer dport allocation for switch ports
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>
1 parent 68d5d97 commit 4f06d81

6 files changed

Lines changed: 247 additions & 60 deletions

File tree

drivers/cxl/core/cdat.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ static int match_cxlrd_hb(struct device *dev, void *data)
338338

339339
guard(rwsem_read)(&cxl_rwsem.region);
340340
for (int i = 0; i < cxlsd->nr_targets; i++) {
341-
if (host_bridge == cxlsd->target[i]->dport_dev)
341+
if (cxlsd->target[i] && host_bridge == cxlsd->target[i]->dport_dev)
342342
return 1;
343343
}
344344

drivers/cxl/core/core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
146146
int cxl_ras_init(void);
147147
void cxl_ras_exit(void);
148148
int cxl_gpf_port_setup(struct cxl_dport *dport);
149+
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
150+
struct device *dport_dev);
149151

150152
struct cxl_hdm;
151153
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,

drivers/cxl/core/hdm.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld)
5252
static int devm_cxl_add_passthrough_decoder(struct cxl_port *port)
5353
{
5454
struct cxl_switch_decoder *cxlsd;
55-
struct cxl_dport *dport = NULL;
56-
unsigned long index;
5755
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
5856

5957
/*
@@ -69,10 +67,6 @@ static int devm_cxl_add_passthrough_decoder(struct cxl_port *port)
6967

7068
device_lock_assert(&port->dev);
7169

72-
xa_for_each(&port->dports, index, dport)
73-
break;
74-
cxlsd->cxld.target_map[0] = dport->port_id;
75-
7670
return add_hdm_decoder(port, &cxlsd->cxld);
7771
}
7872

drivers/cxl/core/pci.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,52 @@ static unsigned short media_ready_timeout = 60;
2424
module_param(media_ready_timeout, ushort, 0644);
2525
MODULE_PARM_DESC(media_ready_timeout, "seconds to wait for media ready");
2626

27+
static int pci_get_port_num(struct pci_dev *pdev)
28+
{
29+
u32 lnkcap;
30+
int type;
31+
32+
type = pci_pcie_type(pdev);
33+
if (type != PCI_EXP_TYPE_DOWNSTREAM && type != PCI_EXP_TYPE_ROOT_PORT)
34+
return -EINVAL;
35+
36+
if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
37+
&lnkcap))
38+
return -ENXIO;
39+
40+
return FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
41+
}
42+
43+
/**
44+
* devm_cxl_add_dport_by_dev - allocate a dport by the dport device
45+
* @port: cxl_port that hosts the dport
46+
* @dport_dev: 'struct device' of the dport
47+
*
48+
* Returns the allocated dport on success or ERR_PTR() of -errno on error
49+
*/
50+
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
51+
struct device *dport_dev)
52+
{
53+
struct cxl_register_map map;
54+
struct pci_dev *pdev;
55+
int port_num, rc;
56+
57+
if (!dev_is_pci(dport_dev))
58+
return ERR_PTR(-EINVAL);
59+
60+
pdev = to_pci_dev(dport_dev);
61+
port_num = pci_get_port_num(pdev);
62+
if (port_num < 0)
63+
return ERR_PTR(port_num);
64+
65+
rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
66+
if (rc)
67+
return ERR_PTR(rc);
68+
69+
device_lock_assert(&port->dev);
70+
return devm_cxl_add_dport(port, dport_dev, port_num, map.resource);
71+
}
72+
2773
struct cxl_walk_context {
2874
struct pci_bus *bus;
2975
struct cxl_port *port;

drivers/cxl/core/port.c

Lines changed: 195 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,21 +1357,6 @@ static struct cxl_port *find_cxl_port(struct device *dport_dev,
13571357
return port;
13581358
}
13591359

1360-
static struct cxl_port *find_cxl_port_at(struct cxl_port *parent_port,
1361-
struct device *dport_dev,
1362-
struct cxl_dport **dport)
1363-
{
1364-
struct cxl_find_port_ctx ctx = {
1365-
.dport_dev = dport_dev,
1366-
.parent_port = parent_port,
1367-
.dport = dport,
1368-
};
1369-
struct cxl_port *port;
1370-
1371-
port = __find_cxl_port(&ctx);
1372-
return port;
1373-
}
1374-
13751360
/*
13761361
* All users of grandparent() are using it to walk PCIe-like switch port
13771362
* hierarchy. A PCIe switch is comprised of a bridge device representing the
@@ -1547,13 +1532,154 @@ static resource_size_t find_component_registers(struct device *dev)
15471532
return map.resource;
15481533
}
15491534

1535+
static int match_port_by_uport(struct device *dev, const void *data)
1536+
{
1537+
const struct device *uport_dev = data;
1538+
struct cxl_port *port;
1539+
1540+
if (!is_cxl_port(dev))
1541+
return 0;
1542+
1543+
port = to_cxl_port(dev);
1544+
return uport_dev == port->uport_dev;
1545+
}
1546+
1547+
/*
1548+
* Function takes a device reference on the port device. Caller should do a
1549+
* put_device() when done.
1550+
*/
1551+
static struct cxl_port *find_cxl_port_by_uport(struct device *uport_dev)
1552+
{
1553+
struct device *dev;
1554+
1555+
dev = bus_find_device(&cxl_bus_type, NULL, uport_dev, match_port_by_uport);
1556+
if (dev)
1557+
return to_cxl_port(dev);
1558+
return NULL;
1559+
}
1560+
1561+
static int update_decoder_targets(struct device *dev, void *data)
1562+
{
1563+
struct cxl_dport *dport = data;
1564+
struct cxl_switch_decoder *cxlsd;
1565+
struct cxl_decoder *cxld;
1566+
int i;
1567+
1568+
if (!is_switch_decoder(dev))
1569+
return 0;
1570+
1571+
cxlsd = to_cxl_switch_decoder(dev);
1572+
cxld = &cxlsd->cxld;
1573+
guard(rwsem_write)(&cxl_rwsem.region);
1574+
1575+
for (i = 0; i < cxld->interleave_ways; i++) {
1576+
if (cxld->target_map[i] == dport->port_id) {
1577+
cxlsd->target[i] = dport;
1578+
dev_dbg(dev, "dport%d found in target list, index %d\n",
1579+
dport->port_id, i);
1580+
return 1;
1581+
}
1582+
}
1583+
1584+
return 0;
1585+
}
1586+
1587+
DEFINE_FREE(del_cxl_dport, struct cxl_dport *, if (!IS_ERR_OR_NULL(_T)) del_dport(_T))
1588+
static struct cxl_dport *cxl_port_add_dport(struct cxl_port *port,
1589+
struct device *dport_dev)
1590+
{
1591+
struct cxl_dport *dport;
1592+
int rc;
1593+
1594+
device_lock_assert(&port->dev);
1595+
if (!port->dev.driver)
1596+
return ERR_PTR(-ENXIO);
1597+
1598+
dport = cxl_find_dport_by_dev(port, dport_dev);
1599+
if (dport) {
1600+
dev_dbg(&port->dev, "dport%d:%s already exists\n",
1601+
dport->port_id, dev_name(dport_dev));
1602+
return ERR_PTR(-EBUSY);
1603+
}
1604+
1605+
struct cxl_dport *new_dport __free(del_cxl_dport) =
1606+
devm_cxl_add_dport_by_dev(port, dport_dev);
1607+
if (IS_ERR(new_dport))
1608+
return new_dport;
1609+
1610+
cxl_switch_parse_cdat(port);
1611+
1612+
if (ida_is_empty(&port->decoder_ida)) {
1613+
rc = devm_cxl_switch_port_decoders_setup(port);
1614+
if (rc)
1615+
return ERR_PTR(rc);
1616+
dev_dbg(&port->dev, "first dport%d:%s added with decoders\n",
1617+
new_dport->port_id, dev_name(dport_dev));
1618+
return no_free_ptr(new_dport);
1619+
}
1620+
1621+
/* New dport added, update the decoder targets */
1622+
device_for_each_child(&port->dev, new_dport, update_decoder_targets);
1623+
1624+
dev_dbg(&port->dev, "dport%d:%s added\n", new_dport->port_id,
1625+
dev_name(dport_dev));
1626+
1627+
return no_free_ptr(new_dport);
1628+
}
1629+
1630+
static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
1631+
struct cxl_port *parent_port,
1632+
struct cxl_dport *parent_dport,
1633+
struct device *uport_dev,
1634+
struct device *dport_dev)
1635+
{
1636+
resource_size_t component_reg_phys;
1637+
1638+
device_lock_assert(&parent_port->dev);
1639+
if (!parent_port->dev.driver) {
1640+
dev_warn(ep_dev,
1641+
"port %s:%s:%s disabled, failed to enumerate CXL.mem\n",
1642+
dev_name(&parent_port->dev), dev_name(uport_dev),
1643+
dev_name(dport_dev));
1644+
}
1645+
1646+
struct cxl_port *port __free(put_cxl_port) =
1647+
find_cxl_port_by_uport(uport_dev);
1648+
if (!port) {
1649+
component_reg_phys = find_component_registers(uport_dev);
1650+
port = devm_cxl_add_port(&parent_port->dev, uport_dev,
1651+
component_reg_phys, parent_dport);
1652+
if (IS_ERR(port))
1653+
return ERR_CAST(port);
1654+
1655+
/*
1656+
* retry to make sure a port is found. a port device
1657+
* reference is taken.
1658+
*/
1659+
port = find_cxl_port_by_uport(uport_dev);
1660+
if (!port)
1661+
return ERR_PTR(-ENODEV);
1662+
1663+
dev_dbg(ep_dev, "created port %s:%s\n",
1664+
dev_name(&port->dev), dev_name(port->uport_dev));
1665+
} else {
1666+
/*
1667+
* Port was created before right before this function is
1668+
* called. Signal the caller to deal with it.
1669+
*/
1670+
return ERR_PTR(-EAGAIN);
1671+
}
1672+
1673+
guard(device)(&port->dev);
1674+
return cxl_port_add_dport(port, dport_dev);
1675+
}
1676+
15501677
static int add_port_attach_ep(struct cxl_memdev *cxlmd,
15511678
struct device *uport_dev,
15521679
struct device *dport_dev)
15531680
{
15541681
struct device *dparent = grandparent(dport_dev);
15551682
struct cxl_dport *dport, *parent_dport;
1556-
resource_size_t component_reg_phys;
15571683
int rc;
15581684

15591685
if (is_cxl_host_bridge(dparent)) {
@@ -1568,42 +1694,31 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
15681694
}
15691695

15701696
struct cxl_port *parent_port __free(put_cxl_port) =
1571-
find_cxl_port(dparent, &parent_dport);
1697+
find_cxl_port_by_uport(dparent->parent);
15721698
if (!parent_port) {
15731699
/* iterate to create this parent_port */
15741700
return -EAGAIN;
15751701
}
15761702

1577-
/*
1578-
* Definition with __free() here to keep the sequence of
1579-
* dereferencing the device of the port before the parent_port releasing.
1580-
*/
1581-
struct cxl_port *port __free(put_cxl_port) = NULL;
15821703
scoped_guard(device, &parent_port->dev) {
1583-
if (!parent_port->dev.driver) {
1584-
dev_warn(&cxlmd->dev,
1585-
"port %s:%s disabled, failed to enumerate CXL.mem\n",
1586-
dev_name(&parent_port->dev), dev_name(uport_dev));
1587-
return -ENXIO;
1704+
parent_dport = cxl_find_dport_by_dev(parent_port, dparent);
1705+
if (!parent_dport) {
1706+
parent_dport = cxl_port_add_dport(parent_port, dparent);
1707+
if (IS_ERR(parent_dport))
1708+
return PTR_ERR(parent_dport);
15881709
}
15891710

1590-
port = find_cxl_port_at(parent_port, dport_dev, &dport);
1591-
if (!port) {
1592-
component_reg_phys = find_component_registers(uport_dev);
1593-
port = devm_cxl_add_port(&parent_port->dev, uport_dev,
1594-
component_reg_phys, parent_dport);
1595-
if (IS_ERR(port))
1596-
return PTR_ERR(port);
1597-
1598-
/* retry find to pick up the new dport information */
1599-
port = find_cxl_port_at(parent_port, dport_dev, &dport);
1600-
if (!port)
1601-
return -ENXIO;
1711+
dport = devm_cxl_create_port(&cxlmd->dev, parent_port,
1712+
parent_dport, uport_dev,
1713+
dport_dev);
1714+
if (IS_ERR(dport)) {
1715+
/* Port already exists, restart iteration */
1716+
if (PTR_ERR(dport) == -EAGAIN)
1717+
return 0;
1718+
return PTR_ERR(dport);
16021719
}
16031720
}
16041721

1605-
dev_dbg(&cxlmd->dev, "add to new port %s:%s\n",
1606-
dev_name(&port->dev), dev_name(port->uport_dev));
16071722
rc = cxl_add_ep(dport, &cxlmd->dev);
16081723
if (rc == -EBUSY) {
16091724
/*
@@ -1616,6 +1731,25 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
16161731
return rc;
16171732
}
16181733

1734+
static struct cxl_dport *find_or_add_dport(struct cxl_port *port,
1735+
struct device *dport_dev)
1736+
{
1737+
struct cxl_dport *dport;
1738+
1739+
device_lock_assert(&port->dev);
1740+
dport = cxl_find_dport_by_dev(port, dport_dev);
1741+
if (!dport) {
1742+
dport = cxl_port_add_dport(port, dport_dev);
1743+
if (IS_ERR(dport))
1744+
return dport;
1745+
1746+
/* New dport added, restart iteration */
1747+
return ERR_PTR(-EAGAIN);
1748+
}
1749+
1750+
return dport;
1751+
}
1752+
16191753
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
16201754
{
16211755
struct device *dev = &cxlmd->dev;
@@ -1658,12 +1792,26 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
16581792
dev_name(iter), dev_name(dport_dev),
16591793
dev_name(uport_dev));
16601794
struct cxl_port *port __free(put_cxl_port) =
1661-
find_cxl_port(dport_dev, &dport);
1795+
find_cxl_port_by_uport(uport_dev);
16621796
if (port) {
16631797
dev_dbg(&cxlmd->dev,
16641798
"found already registered port %s:%s\n",
16651799
dev_name(&port->dev),
16661800
dev_name(port->uport_dev));
1801+
1802+
/*
1803+
* RP port enumerated by cxl_acpi without dport will
1804+
* have the dport added here.
1805+
*/
1806+
scoped_guard(device, &port->dev) {
1807+
dport = find_or_add_dport(port, dport_dev);
1808+
if (IS_ERR(dport)) {
1809+
if (PTR_ERR(dport) == -EAGAIN)
1810+
goto retry;
1811+
return PTR_ERR(dport);
1812+
}
1813+
}
1814+
16671815
rc = cxl_add_ep(dport, &cxlmd->dev);
16681816

16691817
/*
@@ -1723,14 +1871,16 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
17231871
device_lock_assert(&port->dev);
17241872

17251873
if (xa_empty(&port->dports))
1726-
return -EINVAL;
1874+
return 0;
17271875

17281876
guard(rwsem_write)(&cxl_rwsem.region);
17291877
for (i = 0; i < cxlsd->cxld.interleave_ways; i++) {
17301878
struct cxl_dport *dport = find_dport(port, cxld->target_map[i]);
17311879

1732-
if (!dport)
1733-
return -ENXIO;
1880+
if (!dport) {
1881+
/* dport may be activated later */
1882+
continue;
1883+
}
17341884
cxlsd->target[i] = dport;
17351885
}
17361886

0 commit comments

Comments
 (0)