Merge branch 'for-7.4/cxl-fixes' into cxl-for-next

perf coordinates fixes:
cxl/acpi: Convert QTG _DSM latencies to picoseconds
cxl: Account for link width in latency calculation
cxl/port: Convert PCIe link latency to nanoseconds

GPF path fixes:
cxl/pci: Update only the Port GPF timeout fields
cxl/pci: Program the Port GPF timeouts before caching the DVSEC
cxl/pci: Use the Device GPF DVSEC for restricted endpoints

dport fixes:
cxl/port: Attach endpoints to a dport under the lock that pins it
cxl/core: Hold the dport host lock across dport lookup and use
cxl/port: Clear cached dport pointers when a dport is removed
This commit is contained in:
Dave Jiang 2026-10-09 14:11:10 -07:00
commit 91320b8409
10 changed files with 250 additions and 126 deletions

View File

@ -231,9 +231,10 @@ cxl_acpi_evaluate_qtg_dsm(acpi_handle handle, struct access_coordinate *coord,
int entries, int *qos_class)
{
union acpi_object *out_obj, *out_buf, *obj;
/* QTG _DSM expects latencies in picoseconds. */
union acpi_object in_array[4] = {
[0].integer = { ACPI_TYPE_INTEGER, coord->read_latency },
[1].integer = { ACPI_TYPE_INTEGER, coord->write_latency },
[0].integer = { ACPI_TYPE_INTEGER, coord->read_latency * 1000ULL },
[1].integer = { ACPI_TYPE_INTEGER, coord->write_latency * 1000ULL },
[2].integer = { ACPI_TYPE_INTEGER, coord->read_bandwidth },
[3].integer = { ACPI_TYPE_INTEGER, coord->write_bandwidth },
};
@ -251,6 +252,11 @@ cxl_acpi_evaluate_qtg_dsm(acpi_handle handle, struct access_coordinate *coord,
if (!entries)
return -EINVAL;
/* Latency inputs must fit in the DWORD fields defined by the spec. */
if (coord->read_latency > U32_MAX / 1000 ||
coord->write_latency > U32_MAX / 1000)
return -ERANGE;
out_obj = acpi_evaluate_dsm(handle, &acpi_cxl_qtg_id_guid, 1, 1, &in_obj);
if (!out_obj)
return -ENXIO;

View File

@ -178,11 +178,7 @@ static inline struct device *port_to_host(struct cxl_port *port)
static inline struct device *dport_to_host(struct cxl_dport *dport)
{
struct cxl_port *port = dport->port;
if (is_cxl_root(port))
return port->uport_dev;
return &port->dev;
return cxl_port_dport_host(dport->port);
}
#ifdef CONFIG_CXL_RAS
void cxl_ras_init(void);

View File

@ -660,8 +660,8 @@ long cxl_pci_get_latency(struct pci_dev *pdev)
{
long bw;
bw = pcie_link_speed_mbps(pdev);
if (bw < 0)
bw = pcie_link_bandwidth_mbps(pdev);
if (bw <= 0)
return 0;
bw /= BITS_PER_BYTE;
@ -743,10 +743,16 @@ int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
if (rc && type == CXL_REGLOC_RBI_COMPONENT && is_cxl_restricted(pdev)) {
struct cxl_dport *dport;
struct cxl_port *port __free(put_cxl_port) =
cxl_pci_find_port(pdev, &dport);
cxl_pci_find_port(pdev);
if (!port)
return -EPROBE_DEFER;
guard(device)(cxl_port_dport_host(port));
dport = cxl_pci_find_dport(pdev, port);
if (!dport)
return -EPROBE_DEFER;
rc = cxl_rcrb_get_comp_regs(pdev, map, dport);
if (rc)
return rc;
@ -765,18 +771,12 @@ EXPORT_SYMBOL_NS_GPL(cxl_pci_setup_regs, "CXL");
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c)
{
int speed, bw;
u16 lnksta;
u32 width;
int bw;
speed = pcie_link_speed_mbps(pdev);
if (speed < 0)
return speed;
speed /= BITS_PER_BYTE;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta);
width = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta);
bw = speed * width;
bw = pcie_link_bandwidth_mbps(pdev);
if (bw < 0)
return bw;
bw /= BITS_PER_BYTE;
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
c[i].read_bandwidth = bw;
@ -813,7 +813,8 @@ u16 cxl_gpf_get_dvsec(struct device *dev)
return 0;
pdev = to_pci_dev(dev);
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT)
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT ||
is_cxl_restricted(pdev))
is_port = false;
dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
@ -848,21 +849,27 @@ static int update_gpf_port_dvsec(struct pci_dev *pdev, int dvsec, int phase)
rc = pci_read_config_word(pdev, dvsec + offset, &ctrl);
if (rc)
return rc;
return pcibios_err_to_errno(rc);
if (FIELD_GET(base, ctrl) == GPF_TIMEOUT_BASE_MAX &&
FIELD_GET(scale, ctrl) == GPF_TIMEOUT_SCALE_MAX)
return 0;
ctrl = FIELD_PREP(base, GPF_TIMEOUT_BASE_MAX);
ctrl |= FIELD_PREP(scale, GPF_TIMEOUT_SCALE_MAX);
FIELD_MODIFY(base, &ctrl, GPF_TIMEOUT_BASE_MAX);
FIELD_MODIFY(scale, &ctrl, GPF_TIMEOUT_SCALE_MAX);
rc = pci_write_config_word(pdev, dvsec + offset, ctrl);
if (!rc)
pci_dbg(pdev, "Port GPF phase %d timeout: %d0 secs\n",
phase, GPF_TIMEOUT_BASE_MAX);
if (rc) {
rc = pcibios_err_to_errno(rc);
pci_warn(pdev, "Port GPF phase %d timeout write failed: %d\n",
phase, rc);
return rc;
}
return rc;
pci_dbg(pdev, "Port GPF phase %d timeout: %d0 secs\n",
phase, GPF_TIMEOUT_BASE_MAX);
return 0;
}
int cxl_gpf_port_setup(struct cxl_dport *dport)
@ -872,16 +879,22 @@ int cxl_gpf_port_setup(struct cxl_dport *dport)
if (!dport->gpf_dvsec) {
struct pci_dev *pdev;
int dvsec;
int dvsec, rc;
dvsec = cxl_gpf_get_dvsec(dport->dport_dev);
if (!dvsec)
return -EINVAL;
dport->gpf_dvsec = dvsec;
pdev = to_pci_dev(dport->dport_dev);
update_gpf_port_dvsec(pdev, dport->gpf_dvsec, 1);
update_gpf_port_dvsec(pdev, dport->gpf_dvsec, 2);
rc = update_gpf_port_dvsec(pdev, dvsec, 1);
if (rc)
return rc;
rc = update_gpf_port_dvsec(pdev, dvsec, 2);
if (rc)
return rc;
dport->gpf_dvsec = dvsec;
}
return 0;

View File

@ -1106,11 +1106,40 @@ static void cond_cxl_root_unlock(struct cxl_port *port)
device_unlock(&port->dev);
}
static int clear_decoder_target(struct device *dev, void *data)
{
struct cxl_dport *dport = data;
struct cxl_switch_decoder *cxlsd;
if (!is_switch_decoder(dev))
return 0;
cxlsd = to_cxl_switch_decoder(dev);
guard(rwsem_write)(&cxl_rwsem.region);
/*
* A dport can occupy more than one position of an interleave, so
* scan the whole target list rather than stopping at the first hit.
*/
for (int i = 0; i < cxlsd->nr_targets; i++) {
if (cxlsd->target[i] != dport)
continue;
cxlsd->target[i] = NULL;
dev_dbg(dev, "dport%d removed from target list, index %d\n",
dport->port_id, i);
}
return 0;
}
static void cxl_dport_remove(void *data)
{
struct cxl_dport *dport = data;
struct cxl_port *port = dport->port;
/* counterpart of cxl_port_update_decoder_targets() */
device_for_each_child(&port->dev, dport, clear_decoder_target);
port->nr_dports--;
xa_erase(&port->dports, (unsigned long) dport->dport_dev);
put_device(dport->dport_dev);
@ -1337,7 +1366,7 @@ static int add_ep(struct cxl_ep *new)
{
struct cxl_port *port = new->dport->port;
guard(device)(&port->dev);
device_lock_assert(&port->dev);
if (port->dead)
return -ENXIO;
@ -1353,6 +1382,11 @@ static int add_ep(struct cxl_ep *new)
* 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.
*
* Context: Caller must hold the device lock of @dport->port, which pins
* @dport, across the lookup of @dport and this call. @dport->port
* is never the CXL root port, whose dports are pinned by its
* uport_dev instead.
*/
static int cxl_add_ep(struct cxl_dport *dport, struct device *ep_dev)
{
@ -1375,13 +1409,11 @@ static int cxl_add_ep(struct cxl_dport *dport, struct device *ep_dev)
struct cxl_find_port_ctx {
const struct device *dport_dev;
const struct cxl_port *parent_port;
struct cxl_dport **dport;
};
static int match_port_by_dport(struct device *dev, const void *data)
{
const struct cxl_find_port_ctx *ctx = data;
struct cxl_dport *dport;
struct cxl_port *port;
if (!is_cxl_port(dev))
@ -1390,10 +1422,7 @@ static int match_port_by_dport(struct device *dev, const void *data)
return 0;
port = to_cxl_port(dev);
dport = cxl_find_dport_by_dev(port, ctx->dport_dev);
if (ctx->dport)
*ctx->dport = dport;
return dport != NULL;
return !!cxl_find_dport_by_dev(port, ctx->dport_dev);
}
static struct cxl_port *__find_cxl_port_by_dport(struct cxl_find_port_ctx *ctx)
@ -1412,22 +1441,21 @@ static struct cxl_port *__find_cxl_port_by_dport(struct cxl_find_port_ctx *ctx)
/**
* find_cxl_port_by_dport - find a cxl_port by one of its targets
* @dport_dev: device representing the dport target
* @dport: optional output of the 'struct cxl_dport' companion of the @dport_dev
*
* Return a 'struct cxl_port' with an elevated reference if found. Use
* __free(put_cxl_port) to release.
*
* The port reference does not pin the dport, which is a devm allocation of
* cxl_port_dport_host(). Use cxl_pci_find_dport() or cxl_mem_find_dport()
* under that device's lock to get it.
*/
static struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev,
struct cxl_dport **dport)
static struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev)
{
struct cxl_find_port_ctx ctx = {
.dport_dev = dport_dev,
.dport = dport,
};
struct cxl_port *port;
port = __find_cxl_port_by_dport(&ctx);
return port;
return __find_cxl_port_by_dport(&ctx);
}
/*
@ -1690,13 +1718,15 @@ static struct cxl_dport *probe_dport(struct cxl_port *port,
return drv->add_dport(port, dport_dev);
}
static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
struct cxl_port *parent_port,
struct cxl_dport *parent_dport,
struct device *uport_dev,
struct device *dport_dev)
static int devm_cxl_create_port(struct device *ep_dev,
struct cxl_port *parent_port,
struct cxl_dport *parent_dport,
struct device *uport_dev,
struct device *dport_dev)
{
resource_size_t component_reg_phys;
struct cxl_dport *dport;
int rc;
device_lock_assert(&parent_port->dev);
if (!parent_port->dev.driver) {
@ -1713,7 +1743,7 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
port = devm_cxl_add_port(&parent_port->dev, uport_dev,
component_reg_phys, parent_dport);
if (IS_ERR(port))
return ERR_CAST(port);
return PTR_ERR(port);
/*
* retry to make sure a port is found. a port device
@ -1721,7 +1751,7 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
*/
port = find_cxl_port_by_uport(uport_dev);
if (!port)
return ERR_PTR(-ENODEV);
return -ENODEV;
dev_dbg(ep_dev, "created port %s:%s\n",
dev_name(&port->dev), dev_name(port->uport_dev));
@ -1730,11 +1760,25 @@ static struct cxl_dport *devm_cxl_create_port(struct device *ep_dev,
* Port was created before right before this function is
* called. Signal the caller to deal with it.
*/
return ERR_PTR(-EAGAIN);
return -EAGAIN;
}
/* @dport is only valid while @port's lock is held */
guard(device)(&port->dev);
return probe_dport(port, dport_dev);
dport = probe_dport(port, dport_dev);
if (IS_ERR(dport))
return PTR_ERR(dport);
rc = cxl_add_ep(dport, ep_dev);
if (rc == -EBUSY) {
/*
* "can't" happen, but this error code means
* something to the caller, so translate it.
*/
rc = -ENXIO;
}
return rc;
}
static int add_port_attach_ep(struct cxl_memdev *cxlmd,
@ -1742,7 +1786,7 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
struct device *dport_dev)
{
struct device *dparent = grandparent(dport_dev);
struct cxl_dport *dport, *parent_dport;
struct cxl_dport *parent_dport;
int rc;
if (is_cxl_host_bridge(dparent)) {
@ -1771,33 +1815,23 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd,
return PTR_ERR(parent_dport);
}
dport = devm_cxl_create_port(&cxlmd->dev, parent_port,
parent_dport, uport_dev,
dport_dev);
if (IS_ERR(dport)) {
/* Port or dport already exists, restart iteration */
if (PTR_ERR(dport) == -EAGAIN || PTR_ERR(dport) == -EBUSY)
return 0;
return PTR_ERR(dport);
}
rc = devm_cxl_create_port(&cxlmd->dev, parent_port,
parent_dport, uport_dev, dport_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;
}
/* Port or dport already exists, restart iteration */
if (rc == -EAGAIN || rc == -EBUSY)
return 0;
return rc;
}
static struct cxl_dport *find_or_add_dport(struct cxl_port *port,
struct device *dport_dev)
static int find_or_add_dport_attach_ep(struct cxl_memdev *cxlmd,
struct cxl_port *port,
struct device *dport_dev)
{
struct cxl_dport *dport;
int rc;
/*
* The port is already visible in CXL hierarchy, but it may still
@ -1813,13 +1847,28 @@ static struct cxl_dport *find_or_add_dport(struct cxl_port *port,
if (!dport) {
dport = probe_dport(port, dport_dev);
if (IS_ERR(dport))
return dport;
return PTR_ERR(dport);
/* New dport added, restart iteration */
return ERR_PTR(-EAGAIN);
return -EAGAIN;
}
return dport;
/* @dport is only valid while @port's lock is held */
rc = cxl_add_ep(dport, &cxlmd->dev);
/*
* 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);
return 0;
}
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
@ -1848,7 +1897,6 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
for (iter = dev; iter; iter = grandparent(iter)) {
struct device *dport_dev = grandparent(iter);
struct device *uport_dev;
struct cxl_dport *dport;
if (is_cxl_host_bridge(dport_dev))
return 0;
@ -1875,27 +1923,13 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
* RP port enumerated by cxl_acpi without dport will
* have the dport added here.
*/
dport = find_or_add_dport(port, dport_dev);
if (IS_ERR(dport)) {
if (PTR_ERR(dport) == -EAGAIN)
goto retry;
return PTR_ERR(dport);
}
rc = cxl_add_ep(dport, &cxlmd->dev);
/*
* 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)
rc = find_or_add_dport_attach_ep(cxlmd, port,
dport_dev);
if (rc == -EAGAIN)
goto retry;
if (rc)
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;
@ -1918,20 +1952,50 @@ int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd)
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_ports, "CXL");
struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev,
struct cxl_dport **dport)
struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev)
{
return find_cxl_port_by_dport(pdev->dev.parent, dport);
return find_cxl_port_by_dport(pdev->dev.parent);
}
EXPORT_SYMBOL_NS_GPL(cxl_pci_find_port, "CXL");
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
struct cxl_dport **dport)
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd)
{
return find_cxl_port_by_dport(grandparent(&cxlmd->dev), dport);
return find_cxl_port_by_dport(grandparent(&cxlmd->dev));
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_find_port, "CXL");
/**
* cxl_pci_find_dport - find the dport of @port that @pdev is below
* @pdev: PCI device below the dport
* @port: port to search, typically from cxl_pci_find_port()
*
* Context: Caller must hold the cxl_port_dport_host() lock of @port, and
* must not use the result after dropping it.
*/
struct cxl_dport *cxl_pci_find_dport(struct pci_dev *pdev,
struct cxl_port *port)
{
device_lock_assert(cxl_port_dport_host(port));
return cxl_find_dport_by_dev(port, pdev->dev.parent);
}
EXPORT_SYMBOL_NS_GPL(cxl_pci_find_dport, "CXL");
/**
* cxl_mem_find_dport - find the dport of @port that @cxlmd is below
* @cxlmd: memdev below the dport
* @port: port to search, typically from cxl_mem_find_port()
*
* Context: Caller must hold the cxl_port_dport_host() lock of @port, and
* must not use the result after dropping it.
*/
struct cxl_dport *cxl_mem_find_dport(struct cxl_memdev *cxlmd,
struct cxl_port *port)
{
device_lock_assert(cxl_port_dport_host(port));
return cxl_find_dport_by_dev(port, grandparent(&cxlmd->dev));
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_find_dport, "CXL");
static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
struct cxl_port *port)
{
@ -2339,6 +2403,9 @@ EXPORT_SYMBOL_NS_GPL(schedule_cxl_memdev_detach, "CXL");
static void add_latency(struct access_coordinate *c, long latency)
{
/* Convert link latency from picoseconds to nanoseconds. */
latency = DIV_ROUND_UP(latency, 1000);
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
c[i].write_latency += latency;
c[i].read_latency += latency;

View File

@ -118,10 +118,20 @@ void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
int severity;
struct cxl_port *port __free(put_cxl_port) =
cxl_pci_find_port(pdev, &dport);
cxl_pci_find_port(pdev);
if (!port)
return;
/*
* The dport may have been freed since the lookup above, so take the
* lock that pins it and find it again.
*/
guard(device)(cxl_port_dport_host(port));
dport = cxl_pci_find_dport(pdev, port);
if (!dport)
return;
if (!cxl_rch_get_aer_info(dport->regs.dport_aer, &aer_regs))
return;

View File

@ -713,6 +713,18 @@ static inline bool is_cxl_root(struct cxl_port *port)
return port->uport_dev == port->dev.parent;
}
/*
* Return the owner device of the devm allocations of @port's dports. They
* are freed when that device unbinds, so hold device_lock() on the returned
* device across any lookup and use of a 'struct cxl_dport'.
*/
static inline struct device *cxl_port_dport_host(struct cxl_port *port)
{
if (is_cxl_root(port))
return port->uport_dev;
return &port->dev;
}
/* Address translation functions exported to cxl_translate test module only */
int cxl_validate_translation_params(u8 eiw, u16 eig, int pos);
u64 cxl_calculate_hpa_offset(u64 dpa_offset, int pos, u8 eiw, u16 eig);
@ -759,10 +771,12 @@ DEFINE_FREE(put_cxl_dax_region, struct cxl_dax_region *, if (!IS_ERR_OR_NULL(_T)
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
void cxl_bus_rescan(void);
void cxl_bus_drain(void);
struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev,
struct cxl_dport **dport);
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
struct cxl_dport **dport);
struct cxl_dport *cxl_pci_find_dport(struct pci_dev *pdev,
struct cxl_port *port);
struct cxl_dport *cxl_mem_find_dport(struct cxl_memdev *cxlmd,
struct cxl_port *port);
struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev);
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd);
bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd);
struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port,

View File

@ -139,7 +139,7 @@ static int cxl_mem_probe(struct device *dev)
return rc;
struct cxl_port *parent_port __free(put_cxl_port) =
cxl_mem_find_port(cxlmd, &dport);
cxl_mem_find_port(cxlmd);
if (!parent_port) {
dev_err(dev, "CXL port topology not found\n");
return -ENXIO;
@ -154,10 +154,7 @@ static int cxl_mem_probe(struct device *dev)
}
}
if (dport->rch)
endpoint_parent = parent_port->uport_dev;
else
endpoint_parent = &parent_port->dev;
endpoint_parent = cxl_port_dport_host(parent_port);
scoped_guard(device, endpoint_parent) {
if (!endpoint_parent->driver) {
@ -166,6 +163,13 @@ static int cxl_mem_probe(struct device *dev)
return -ENXIO;
}
/* @dport is only valid while @endpoint_parent stays bound */
dport = cxl_mem_find_dport(cxlmd, parent_port);
if (!dport) {
dev_err(dev, "CXL topology dport not found\n");
return -ENXIO;
}
rc = devm_cxl_add_endpoint(endpoint_parent, cxlmd, dport);
if (rc)
return rc;

View File

@ -709,22 +709,23 @@ static ssize_t rcd_pcie_cap_emit(struct device *dev, u16 offset, char *buf, size
struct device *root_dev;
struct cxl_dport *dport;
struct cxl_port *root __free(put_cxl_port) =
cxl_mem_find_port(cxlmd, &dport);
cxl_mem_find_port(cxlmd);
if (!root)
return -ENXIO;
root_dev = root->uport_dev;
root_dev = cxl_port_dport_host(root);
if (!root_dev)
return -ENXIO;
if (!dport->regs.rcd_pcie_cap)
return -ENXIO;
guard(device)(root_dev);
if (!root_dev->driver)
return -ENXIO;
dport = cxl_mem_find_dport(cxlmd, root);
if (!dport || !dport->regs.rcd_pcie_cap)
return -ENXIO;
switch (width) {
case 2:
return sysfs_emit(buf, "%#x\n",

View File

@ -5980,18 +5980,31 @@ static enum pci_bus_speed to_pcie_link_speed(u16 lnksta)
return pcie_link_speed[FIELD_GET(PCI_EXP_LNKSTA_CLS, lnksta)];
}
int pcie_link_speed_mbps(struct pci_dev *pdev)
/**
* pcie_link_bandwidth_mbps - get the current bandwidth of a single PCIe link
* @pdev: PCI device to query
*
* The bandwidth includes all negotiated lanes and does not account for
* encoding overhead. A negotiated width of zero gives zero bandwidth.
*
* Return: Bandwidth in Mb/s, or a negative errno on failure.
*/
int pcie_link_bandwidth_mbps(struct pci_dev *pdev)
{
u16 lnksta;
int err;
int speed, err;
err = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta);
if (err)
return err;
return pcibios_err_to_errno(err);
return pcie_dev_speed_mbps(to_pcie_link_speed(lnksta));
speed = pcie_dev_speed_mbps(to_pcie_link_speed(lnksta));
if (speed < 0)
return speed;
return speed * FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta);
}
EXPORT_SYMBOL(pcie_link_speed_mbps);
EXPORT_SYMBOL(pcie_link_bandwidth_mbps);
/**
* pcie_bandwidth_available - determine minimum link settings of a PCIe

View File

@ -1477,7 +1477,7 @@ int pcie_set_mps(struct pci_dev *dev, int mps);
u32 pcie_bandwidth_available(struct pci_dev *dev, struct pci_dev **limiting_dev,
enum pci_bus_speed *speed,
enum pcie_link_width *width);
int pcie_link_speed_mbps(struct pci_dev *pdev);
int pcie_link_bandwidth_mbps(struct pci_dev *pdev);
void pcie_print_link_status(struct pci_dev *dev);
int pcie_reset_flr(struct pci_dev *dev, bool probe);
int pcie_flr(struct pci_dev *dev);