Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /* Copyright(c) 2021 Intel Corporation. All rights reserved. */
0003 #include <linux/platform_device.h>
0004 #include <linux/module.h>
0005 #include <linux/device.h>
0006 #include <linux/kernel.h>
0007 #include <linux/acpi.h>
0008 #include <linux/pci.h>
0009 #include "cxlpci.h"
0010 #include "cxl.h"
0011 
0012 static unsigned long cfmws_to_decoder_flags(int restrictions)
0013 {
0014     unsigned long flags = CXL_DECODER_F_ENABLE;
0015 
0016     if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE2)
0017         flags |= CXL_DECODER_F_TYPE2;
0018     if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE3)
0019         flags |= CXL_DECODER_F_TYPE3;
0020     if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE)
0021         flags |= CXL_DECODER_F_RAM;
0022     if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM)
0023         flags |= CXL_DECODER_F_PMEM;
0024     if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED)
0025         flags |= CXL_DECODER_F_LOCK;
0026 
0027     return flags;
0028 }
0029 
0030 static int cxl_acpi_cfmws_verify(struct device *dev,
0031                  struct acpi_cedt_cfmws *cfmws)
0032 {
0033     int rc, expected_len;
0034     unsigned int ways;
0035 
0036     if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
0037         dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
0038         return -EINVAL;
0039     }
0040 
0041     if (!IS_ALIGNED(cfmws->base_hpa, SZ_256M)) {
0042         dev_err(dev, "CFMWS Base HPA not 256MB aligned\n");
0043         return -EINVAL;
0044     }
0045 
0046     if (!IS_ALIGNED(cfmws->window_size, SZ_256M)) {
0047         dev_err(dev, "CFMWS Window Size not 256MB aligned\n");
0048         return -EINVAL;
0049     }
0050 
0051     rc = cxl_to_ways(cfmws->interleave_ways, &ways);
0052     if (rc) {
0053         dev_err(dev, "CFMWS Interleave Ways (%d) invalid\n",
0054             cfmws->interleave_ways);
0055         return -EINVAL;
0056     }
0057 
0058     expected_len = struct_size(cfmws, interleave_targets, ways);
0059 
0060     if (cfmws->header.length < expected_len) {
0061         dev_err(dev, "CFMWS length %d less than expected %d\n",
0062             cfmws->header.length, expected_len);
0063         return -EINVAL;
0064     }
0065 
0066     if (cfmws->header.length > expected_len)
0067         dev_dbg(dev, "CFMWS length %d greater than expected %d\n",
0068             cfmws->header.length, expected_len);
0069 
0070     return 0;
0071 }
0072 
0073 struct cxl_cfmws_context {
0074     struct device *dev;
0075     struct cxl_port *root_port;
0076     struct resource *cxl_res;
0077     int id;
0078 };
0079 
0080 static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
0081                const unsigned long end)
0082 {
0083     int target_map[CXL_DECODER_MAX_INTERLEAVE];
0084     struct cxl_cfmws_context *ctx = arg;
0085     struct cxl_port *root_port = ctx->root_port;
0086     struct resource *cxl_res = ctx->cxl_res;
0087     struct cxl_root_decoder *cxlrd;
0088     struct device *dev = ctx->dev;
0089     struct acpi_cedt_cfmws *cfmws;
0090     struct cxl_decoder *cxld;
0091     unsigned int ways, i, ig;
0092     struct resource *res;
0093     int rc;
0094 
0095     cfmws = (struct acpi_cedt_cfmws *) header;
0096 
0097     rc = cxl_acpi_cfmws_verify(dev, cfmws);
0098     if (rc) {
0099         dev_err(dev, "CFMWS range %#llx-%#llx not registered\n",
0100             cfmws->base_hpa,
0101             cfmws->base_hpa + cfmws->window_size - 1);
0102         return 0;
0103     }
0104 
0105     rc = cxl_to_ways(cfmws->interleave_ways, &ways);
0106     if (rc)
0107         return rc;
0108     rc = cxl_to_granularity(cfmws->granularity, &ig);
0109     if (rc)
0110         return rc;
0111     for (i = 0; i < ways; i++)
0112         target_map[i] = cfmws->interleave_targets[i];
0113 
0114     res = kzalloc(sizeof(*res), GFP_KERNEL);
0115     if (!res)
0116         return -ENOMEM;
0117 
0118     res->name = kasprintf(GFP_KERNEL, "CXL Window %d", ctx->id++);
0119     if (!res->name)
0120         goto err_name;
0121 
0122     res->start = cfmws->base_hpa;
0123     res->end = cfmws->base_hpa + cfmws->window_size - 1;
0124     res->flags = IORESOURCE_MEM;
0125 
0126     /* add to the local resource tracking to establish a sort order */
0127     rc = insert_resource(cxl_res, res);
0128     if (rc)
0129         goto err_insert;
0130 
0131     cxlrd = cxl_root_decoder_alloc(root_port, ways);
0132     if (IS_ERR(cxlrd))
0133         return 0;
0134 
0135     cxld = &cxlrd->cxlsd.cxld;
0136     cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
0137     cxld->target_type = CXL_DECODER_EXPANDER;
0138     cxld->hpa_range = (struct range) {
0139         .start = res->start,
0140         .end = res->end,
0141     };
0142     cxld->interleave_ways = ways;
0143     /*
0144      * Minimize the x1 granularity to advertise support for any
0145      * valid region granularity
0146      */
0147     if (ways == 1)
0148         ig = CXL_DECODER_MIN_GRANULARITY;
0149     cxld->interleave_granularity = ig;
0150 
0151     rc = cxl_decoder_add(cxld, target_map);
0152     if (rc)
0153         put_device(&cxld->dev);
0154     else
0155         rc = cxl_decoder_autoremove(dev, cxld);
0156     if (rc) {
0157         dev_err(dev, "Failed to add decode range [%#llx - %#llx]\n",
0158             cxld->hpa_range.start, cxld->hpa_range.end);
0159         return 0;
0160     }
0161     dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n",
0162         dev_name(&cxld->dev),
0163         phys_to_target_node(cxld->hpa_range.start),
0164         cxld->hpa_range.start, cxld->hpa_range.end);
0165 
0166     return 0;
0167 
0168 err_insert:
0169     kfree(res->name);
0170 err_name:
0171     kfree(res);
0172     return -ENOMEM;
0173 }
0174 
0175 __mock struct acpi_device *to_cxl_host_bridge(struct device *host,
0176                           struct device *dev)
0177 {
0178     struct acpi_device *adev = to_acpi_device(dev);
0179 
0180     if (!acpi_pci_find_root(adev->handle))
0181         return NULL;
0182 
0183     if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0)
0184         return adev;
0185     return NULL;
0186 }
0187 
0188 /*
0189  * A host bridge is a dport to a CFMWS decode and it is a uport to the
0190  * dport (PCIe Root Ports) in the host bridge.
0191  */
0192 static int add_host_bridge_uport(struct device *match, void *arg)
0193 {
0194     struct cxl_port *root_port = arg;
0195     struct device *host = root_port->dev.parent;
0196     struct acpi_device *bridge = to_cxl_host_bridge(host, match);
0197     struct acpi_pci_root *pci_root;
0198     struct cxl_dport *dport;
0199     struct cxl_port *port;
0200     int rc;
0201 
0202     if (!bridge)
0203         return 0;
0204 
0205     dport = cxl_find_dport_by_dev(root_port, match);
0206     if (!dport) {
0207         dev_dbg(host, "host bridge expected and not found\n");
0208         return 0;
0209     }
0210 
0211     /*
0212      * Note that this lookup already succeeded in
0213      * to_cxl_host_bridge(), so no need to check for failure here
0214      */
0215     pci_root = acpi_pci_find_root(bridge->handle);
0216     rc = devm_cxl_register_pci_bus(host, match, pci_root->bus);
0217     if (rc)
0218         return rc;
0219 
0220     port = devm_cxl_add_port(host, match, dport->component_reg_phys, dport);
0221     if (IS_ERR(port))
0222         return PTR_ERR(port);
0223     dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev));
0224 
0225     return 0;
0226 }
0227 
0228 struct cxl_chbs_context {
0229     struct device *dev;
0230     unsigned long long uid;
0231     resource_size_t chbcr;
0232 };
0233 
0234 static int cxl_get_chbcr(union acpi_subtable_headers *header, void *arg,
0235              const unsigned long end)
0236 {
0237     struct cxl_chbs_context *ctx = arg;
0238     struct acpi_cedt_chbs *chbs;
0239 
0240     if (ctx->chbcr)
0241         return 0;
0242 
0243     chbs = (struct acpi_cedt_chbs *) header;
0244 
0245     if (ctx->uid != chbs->uid)
0246         return 0;
0247     ctx->chbcr = chbs->base;
0248 
0249     return 0;
0250 }
0251 
0252 static int add_host_bridge_dport(struct device *match, void *arg)
0253 {
0254     acpi_status status;
0255     unsigned long long uid;
0256     struct cxl_dport *dport;
0257     struct cxl_chbs_context ctx;
0258     struct cxl_port *root_port = arg;
0259     struct device *host = root_port->dev.parent;
0260     struct acpi_device *bridge = to_cxl_host_bridge(host, match);
0261 
0262     if (!bridge)
0263         return 0;
0264 
0265     status = acpi_evaluate_integer(bridge->handle, METHOD_NAME__UID, NULL,
0266                        &uid);
0267     if (status != AE_OK) {
0268         dev_err(host, "unable to retrieve _UID of %s\n",
0269             dev_name(match));
0270         return -ENODEV;
0271     }
0272 
0273     ctx = (struct cxl_chbs_context) {
0274         .dev = host,
0275         .uid = uid,
0276     };
0277     acpi_table_parse_cedt(ACPI_CEDT_TYPE_CHBS, cxl_get_chbcr, &ctx);
0278 
0279     if (ctx.chbcr == 0) {
0280         dev_warn(host, "No CHBS found for Host Bridge: %s\n",
0281              dev_name(match));
0282         return 0;
0283     }
0284 
0285     dport = devm_cxl_add_dport(root_port, match, uid, ctx.chbcr);
0286     if (IS_ERR(dport)) {
0287         dev_err(host, "failed to add downstream port: %s\n",
0288             dev_name(match));
0289         return PTR_ERR(dport);
0290     }
0291     dev_dbg(host, "add dport%llu: %s\n", uid, dev_name(match));
0292     return 0;
0293 }
0294 
0295 static int add_root_nvdimm_bridge(struct device *match, void *data)
0296 {
0297     struct cxl_decoder *cxld;
0298     struct cxl_port *root_port = data;
0299     struct cxl_nvdimm_bridge *cxl_nvb;
0300     struct device *host = root_port->dev.parent;
0301 
0302     if (!is_root_decoder(match))
0303         return 0;
0304 
0305     cxld = to_cxl_decoder(match);
0306     if (!(cxld->flags & CXL_DECODER_F_PMEM))
0307         return 0;
0308 
0309     cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port);
0310     if (IS_ERR(cxl_nvb)) {
0311         dev_dbg(host, "failed to register pmem\n");
0312         return PTR_ERR(cxl_nvb);
0313     }
0314     dev_dbg(host, "%s: add: %s\n", dev_name(&root_port->dev),
0315         dev_name(&cxl_nvb->dev));
0316     return 1;
0317 }
0318 
0319 static struct lock_class_key cxl_root_key;
0320 
0321 static void cxl_acpi_lock_reset_class(void *dev)
0322 {
0323     device_lock_reset_class(dev);
0324 }
0325 
0326 static void del_cxl_resource(struct resource *res)
0327 {
0328     kfree(res->name);
0329     kfree(res);
0330 }
0331 
0332 static void cxl_set_public_resource(struct resource *priv, struct resource *pub)
0333 {
0334     priv->desc = (unsigned long) pub;
0335 }
0336 
0337 static struct resource *cxl_get_public_resource(struct resource *priv)
0338 {
0339     return (struct resource *) priv->desc;
0340 }
0341 
0342 static void remove_cxl_resources(void *data)
0343 {
0344     struct resource *res, *next, *cxl = data;
0345 
0346     for (res = cxl->child; res; res = next) {
0347         struct resource *victim = cxl_get_public_resource(res);
0348 
0349         next = res->sibling;
0350         remove_resource(res);
0351 
0352         if (victim) {
0353             remove_resource(victim);
0354             kfree(victim);
0355         }
0356 
0357         del_cxl_resource(res);
0358     }
0359 }
0360 
0361 /**
0362  * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource
0363  * @cxl_res: A standalone resource tree where each CXL window is a sibling
0364  *
0365  * Walk each CXL window in @cxl_res and add it to iomem_resource potentially
0366  * expanding its boundaries to ensure that any conflicting resources become
0367  * children. If a window is expanded it may then conflict with a another window
0368  * entry and require the window to be truncated or trimmed. Consider this
0369  * situation:
0370  *
0371  * |-- "CXL Window 0" --||----- "CXL Window 1" -----|
0372  * |--------------- "System RAM" -------------|
0373  *
0374  * ...where platform firmware has established as System RAM resource across 2
0375  * windows, but has left some portion of window 1 for dynamic CXL region
0376  * provisioning. In this case "Window 0" will span the entirety of the "System
0377  * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
0378  * of that "System RAM" resource.
0379  */
0380 static int add_cxl_resources(struct resource *cxl_res)
0381 {
0382     struct resource *res, *new, *next;
0383 
0384     for (res = cxl_res->child; res; res = next) {
0385         new = kzalloc(sizeof(*new), GFP_KERNEL);
0386         if (!new)
0387             return -ENOMEM;
0388         new->name = res->name;
0389         new->start = res->start;
0390         new->end = res->end;
0391         new->flags = IORESOURCE_MEM;
0392         new->desc = IORES_DESC_CXL;
0393 
0394         /*
0395          * Record the public resource in the private cxl_res tree for
0396          * later removal.
0397          */
0398         cxl_set_public_resource(res, new);
0399 
0400         insert_resource_expand_to_fit(&iomem_resource, new);
0401 
0402         next = res->sibling;
0403         while (next && resource_overlaps(new, next)) {
0404             if (resource_contains(new, next)) {
0405                 struct resource *_next = next->sibling;
0406 
0407                 remove_resource(next);
0408                 del_cxl_resource(next);
0409                 next = _next;
0410             } else
0411                 next->start = new->end + 1;
0412         }
0413     }
0414     return 0;
0415 }
0416 
0417 static int pair_cxl_resource(struct device *dev, void *data)
0418 {
0419     struct resource *cxl_res = data;
0420     struct resource *p;
0421 
0422     if (!is_root_decoder(dev))
0423         return 0;
0424 
0425     for (p = cxl_res->child; p; p = p->sibling) {
0426         struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
0427         struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
0428         struct resource res = {
0429             .start = cxld->hpa_range.start,
0430             .end = cxld->hpa_range.end,
0431             .flags = IORESOURCE_MEM,
0432         };
0433 
0434         if (resource_contains(p, &res)) {
0435             cxlrd->res = cxl_get_public_resource(p);
0436             break;
0437         }
0438     }
0439 
0440     return 0;
0441 }
0442 
0443 static int cxl_acpi_probe(struct platform_device *pdev)
0444 {
0445     int rc;
0446     struct resource *cxl_res;
0447     struct cxl_port *root_port;
0448     struct device *host = &pdev->dev;
0449     struct acpi_device *adev = ACPI_COMPANION(host);
0450     struct cxl_cfmws_context ctx;
0451 
0452     device_lock_set_class(&pdev->dev, &cxl_root_key);
0453     rc = devm_add_action_or_reset(&pdev->dev, cxl_acpi_lock_reset_class,
0454                       &pdev->dev);
0455     if (rc)
0456         return rc;
0457 
0458     cxl_res = devm_kzalloc(host, sizeof(*cxl_res), GFP_KERNEL);
0459     if (!cxl_res)
0460         return -ENOMEM;
0461     cxl_res->name = "CXL mem";
0462     cxl_res->start = 0;
0463     cxl_res->end = -1;
0464     cxl_res->flags = IORESOURCE_MEM;
0465 
0466     root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
0467     if (IS_ERR(root_port))
0468         return PTR_ERR(root_port);
0469     dev_dbg(host, "add: %s\n", dev_name(&root_port->dev));
0470 
0471     rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
0472                   add_host_bridge_dport);
0473     if (rc < 0)
0474         return rc;
0475 
0476     rc = devm_add_action_or_reset(host, remove_cxl_resources, cxl_res);
0477     if (rc)
0478         return rc;
0479 
0480     ctx = (struct cxl_cfmws_context) {
0481         .dev = host,
0482         .root_port = root_port,
0483         .cxl_res = cxl_res,
0484     };
0485     rc = acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx);
0486     if (rc < 0)
0487         return -ENXIO;
0488 
0489     rc = add_cxl_resources(cxl_res);
0490     if (rc)
0491         return rc;
0492 
0493     /*
0494      * Populate the root decoders with their related iomem resource,
0495      * if present
0496      */
0497     device_for_each_child(&root_port->dev, cxl_res, pair_cxl_resource);
0498 
0499     /*
0500      * Root level scanned with host-bridge as dports, now scan host-bridges
0501      * for their role as CXL uports to their CXL-capable PCIe Root Ports.
0502      */
0503     rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
0504                   add_host_bridge_uport);
0505     if (rc < 0)
0506         return rc;
0507 
0508     if (IS_ENABLED(CONFIG_CXL_PMEM))
0509         rc = device_for_each_child(&root_port->dev, root_port,
0510                        add_root_nvdimm_bridge);
0511     if (rc < 0)
0512         return rc;
0513 
0514     /* In case PCI is scanned before ACPI re-trigger memdev attach */
0515     return cxl_bus_rescan();
0516 }
0517 
0518 static const struct acpi_device_id cxl_acpi_ids[] = {
0519     { "ACPI0017" },
0520     { },
0521 };
0522 MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
0523 
0524 static const struct platform_device_id cxl_test_ids[] = {
0525     { "cxl_acpi" },
0526     { },
0527 };
0528 MODULE_DEVICE_TABLE(platform, cxl_test_ids);
0529 
0530 static struct platform_driver cxl_acpi_driver = {
0531     .probe = cxl_acpi_probe,
0532     .driver = {
0533         .name = KBUILD_MODNAME,
0534         .acpi_match_table = cxl_acpi_ids,
0535     },
0536     .id_table = cxl_test_ids,
0537 };
0538 
0539 module_platform_driver(cxl_acpi_driver);
0540 MODULE_LICENSE("GPL v2");
0541 MODULE_IMPORT_NS(CXL);
0542 MODULE_IMPORT_NS(ACPI);