RE: [PATCH] cxl/acpi: Verify CHBS consistency

From: Dan Williams
Date: Tue Jun 21 2022 - 17:10:03 EST


Davidlohr Bueso wrote:
> Similarly to verifying the cfwms, have a cxl_acpi_chbs_verify(),
> as described by the CXL T3 Memory Device Software Guide
> for CXL 2.0 platforms.
>
> Also while at it, tuck the rc check for nvdimm bridge into
> the pmem branch.
>
> Signed-off-by: Davidlohr Bueso <dave@xxxxxxxxxxxx>
> ---
>
> drivers/cxl/acpi.c | 64 +++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 61 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/cxl/acpi.c b/drivers/cxl/acpi.c
> index 40286f5df812..33b5f362c9f1 100644
> --- a/drivers/cxl/acpi.c
> +++ b/drivers/cxl/acpi.c
> @@ -187,14 +187,65 @@ static int add_host_bridge_uport(struct device *match, void *arg)
> struct cxl_chbs_context {
> struct device *dev;
> unsigned long long uid;
> + struct cxl_port *root_port;
> resource_size_t chbcr;
> };
>
> +static inline bool range_overlaps(struct range *r1, struct range *r2)
> +{
> + return r1->start <= r2->end && r2->start <= r1->end;
> +}
> +
> +static int cxl_acpi_chbs_verify(struct cxl_chbs_context *cxt,
> + struct acpi_cedt_chbs *chbs)
> +{
> + struct device *dev = cxt->dev;
> + struct cxl_dport *dport;
> + struct cxl_port *root_port = cxt->root_port;
> + struct range chbs_range = {
> + .start = chbs->base,
> + .end = chbs->base + chbs->length - 1,
> + };
> +
> + if (chbs->cxl_version > 1) {
> + dev_err(dev, "CHBS Unsupported CXL Version\n");
> + return -EINVAL;
> + }
> +
> + if (chbs->cxl_version == ACPI_CEDT_CHBS_VERSION_CXL11)
> + return 0;
> +
> + if (chbs->cxl_version == ACPI_CEDT_CHBS_VERSION_CXL20 &&
> + (chbs->length != ACPI_CEDT_CHBS_LENGTH_CXL20)) {
> + dev_err(dev, "Platform does not support CXL 2.0\n");
> + return -EINVAL;
> + }
> +
> + device_lock(&root_port->dev);
> + list_for_each_entry(dport, &root_port->dports, list) {
> + struct range dport_range = {
> + .start = dport->component_reg_phys,
> + .end = dport->component_reg_phys +
> + CXL_COMPONENT_REG_BLOCK_SIZE - 1,
> + };
> +
> + if (range_overlaps(&chbs_range, &dport_range)) {
> + device_unlock(&root_port->dev);
> + dev_err(dev, "CHBS overlapping Base and Length pair\n");
> + return -EINVAL;
> + }

For cxl_port objects this happens "for free" as a side effect of the:

crb = devm_cxl_iomap_block(dev, port->component_reg_phys,
CXL_COMPONENT_REG_BLOCK_SIZE);

...call in devm_cxl_setup_hdm(), where it tries to exclusively claim the
component register block for that cxl_port driver instance.

I.e. if the CHBS provides overlapping / duplicated ranges the failure is
localized to the cxl_port_probe() event for that port, and can be
debugged further by disabling one of the conflicts.

> + }
> + device_unlock(&root_port->dev);
> +
> + return 0;
> +}
> +
> static int cxl_get_chbcr(union acpi_subtable_headers *header, void *arg,
> const unsigned long end)
> {
> struct cxl_chbs_context *ctx = arg;
> struct acpi_cedt_chbs *chbs;
> + int ret;
>
> if (ctx->chbcr)
> return 0;
> @@ -203,6 +254,11 @@ static int cxl_get_chbcr(union acpi_subtable_headers *header, void *arg,
>
> if (ctx->uid != chbs->uid)
> return 0;
> +
> + ret = cxl_acpi_chbs_verify(ctx, chbs);
> + if (ret)
> + return ret;
> +
> ctx->chbcr = chbs->base;
>
> return 0;
> @@ -232,6 +288,7 @@ static int add_host_bridge_dport(struct device *match, void *arg)
> ctx = (struct cxl_chbs_context) {
> .dev = host,
> .uid = uid,
> + .root_port = root_port,
> };
> acpi_table_parse_cedt(ACPI_CEDT_TYPE_CHBS, cxl_get_chbcr, &ctx);
>
> @@ -321,11 +378,12 @@ static int cxl_acpi_probe(struct platform_device *pdev)
> if (rc < 0)
> return rc;
>
> - if (IS_ENABLED(CONFIG_CXL_PMEM))
> + if (IS_ENABLED(CONFIG_CXL_PMEM)) {
> rc = device_for_each_child(&root_port->dev, root_port,
> add_root_nvdimm_bridge);
> - if (rc < 0)
> - return rc;
> + if (rc < 0)
> + return rc;
> + }

No need to move this inside the "if (IS_ENABLED(CONFIG_CXL_PMEM))" that
I can see.