// SPDX-License-Identifier: GPL-2.0-only /* Copyright(c) 2023 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include "cxlpci.h" #include "cxlmem.h" #include "core.h" #include "cxl.h" struct dsmas_entry { struct range dpa_range; u8 handle; struct access_coordinate coord[ACCESS_COORDINATE_MAX]; struct access_coordinate cdat_coord[ACCESS_COORDINATE_MAX]; int entries; int qos_class; }; static u32 cdat_normalize(u16 entry, u64 base, u8 type) { u32 value; /* * Check for invalid and overflow values */ if (entry == 0xffff || !entry) return 0; else if (base > (UINT_MAX / (entry))) return 0; /* * CDAT fields follow the format of HMAT fields. See table 5 Device * Scoped Latency and Bandwidth Information Structure in Coherent Device * Attribute Table (CDAT) Specification v1.01. */ value = entry * base; switch (type) { case ACPI_HMAT_ACCESS_LATENCY: case ACPI_HMAT_READ_LATENCY: case ACPI_HMAT_WRITE_LATENCY: value = DIV_ROUND_UP(value, 1000); break; default: break; } return value; } static int cdat_dsmas_handler(union acpi_subtable_headers *header, void *arg, const unsigned long end) { struct acpi_cdat_header *hdr = &header->cdat; struct acpi_cdat_dsmas *dsmas; int size = sizeof(*hdr) + sizeof(*dsmas); struct xarray *dsmas_xa = arg; struct dsmas_entry *dent; u16 len; int rc; len = le16_to_cpu((__force __le16)hdr->length); if (len != size || (unsigned long)hdr + len > end) { pr_warn("Malformed DSMAS table length: (%u:%u)\n", size, len); return -EINVAL; } /* Skip common header */ dsmas = (struct acpi_cdat_dsmas *)(hdr + 1); dent = kzalloc(sizeof(*dent), GFP_KERNEL); if (!dent) return -ENOMEM; dent->handle = dsmas->dsmad_handle; dent->dpa_range.start = le64_to_cpu((__force __le64)dsmas->dpa_base_address); dent->dpa_range.end = le64_to_cpu((__force __le64)dsmas->dpa_base_address) + le64_to_cpu((__force __le64)dsmas->dpa_length) - 1; rc = xa_insert(dsmas_xa, dent->handle, dent, GFP_KERNEL); if (rc) { kfree(dent); return rc; } return 0; } static void __cxl_access_coordinate_set(struct access_coordinate *coord, int access, unsigned int val) { switch (access) { case ACPI_HMAT_ACCESS_LATENCY: coord->read_latency = val; coord->write_latency = val; break; case ACPI_HMAT_READ_LATENCY: coord->read_latency = val; break; case ACPI_HMAT_WRITE_LATENCY: coord->write_latency = val; break; case ACPI_HMAT_ACCESS_BANDWIDTH: coord->read_bandwidth = val; coord->write_bandwidth = val; break; case ACPI_HMAT_READ_BANDWIDTH: coord->read_bandwidth = val; break; case ACPI_HMAT_WRITE_BANDWIDTH: coord->write_bandwidth = val; break; } } static void cxl_access_coordinate_set(struct access_coordinate *coord, int access, unsigned int val) { for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) __cxl_access_coordinate_set(&coord[i], access, val); } static int cdat_dslbis_handler(union acpi_subtable_headers *header, void *arg, const unsigned long end) { struct acpi_cdat_header *hdr = &header->cdat; struct acpi_cdat_dslbis *dslbis; int size = sizeof(*hdr) + sizeof(*dslbis); struct xarray *dsmas_xa = arg; struct dsmas_entry *dent; __le64 le_base; __le16 le_val; u64 val; u16 len; len = le16_to_cpu((__force __le16)hdr->length); if (len != size || (unsigned long)hdr + len > end) { pr_warn("Malformed DSLBIS table length: (%u:%u)\n", size, len); return -EINVAL; } /* Skip common header */ dslbis = (struct acpi_cdat_dslbis *)(hdr + 1); /* Skip unrecognized data type */ if (dslbis->data_type > ACPI_HMAT_WRITE_BANDWIDTH) return 0; /* Not a memory type, skip */ if ((dslbis->flags & ACPI_HMAT_MEMORY_HIERARCHY) != ACPI_HMAT_MEMORY) return 0; dent = xa_load(dsmas_xa, dslbis->handle); if (!dent) { pr_warn("No matching DSMAS entry for DSLBIS entry.\n"); return 0; } le_base = (__force __le64)dslbis->entry_base_unit; le_val = (__force __le16)dslbis->entry[0]; val = cdat_normalize(le16_to_cpu(le_val), le64_to_cpu(le_base), dslbis->data_type); cxl_access_coordinate_set(dent->cdat_coord, dslbis->data_type, val); return 0; } static int cdat_table_parse_output(int rc) { if (rc < 0) return rc; if (rc == 0) return -ENOENT; return 0; } static int cxl_cdat_endpoint_process(struct cxl_port *port, struct xarray *dsmas_xa) { int rc; rc = cdat_table_parse(ACPI_CDAT_TYPE_DSMAS, cdat_dsmas_handler, dsmas_xa, port->cdat.table, port->cdat.length); rc = cdat_table_parse_output(rc); if (rc) return rc; rc = cdat_table_parse(ACPI_CDAT_TYPE_DSLBIS, cdat_dslbis_handler, dsmas_xa, port->cdat.table, port->cdat.length); return cdat_table_parse_output(rc); } static int cxl_port_perf_data_calculate(struct cxl_port *port, struct xarray *dsmas_xa) { struct access_coordinate ep_c[ACCESS_COORDINATE_MAX]; struct dsmas_entry *dent; int valid_entries = 0; unsigned long index; int rc; rc = cxl_endpoint_get_perf_coordinates(port, ep_c); if (rc) { dev_dbg(&port->dev, "Failed to retrieve ep perf coordinates.\n"); return rc; } struct cxl_root *cxl_root __free(put_cxl_root) = find_cxl_root(port); if (!cxl_root) return -ENODEV; if (!cxl_root->ops || !cxl_root->ops->qos_class) return -EOPNOTSUPP; xa_for_each(dsmas_xa, index, dent) { int qos_class; cxl_coordinates_combine(dent->coord, dent->cdat_coord, ep_c); dent->entries = 1; rc = cxl_root->ops->qos_class(cxl_root, &dent->coord[ACCESS_COORDINATE_CPU], 1, &qos_class); if (rc != 1) continue; valid_entries++; dent->qos_class = qos_class; } if (!valid_entries) return -ENOENT; return 0; } static void update_perf_entry(struct device *dev, struct dsmas_entry *dent, struct cxl_dpa_perf *dpa_perf) { for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) { dpa_perf->coord[i] = dent->coord[i]; dpa_perf->cdat_coord[i] = dent->cdat_coord[i]; } dpa_perf->dpa_range = dent->dpa_range; dpa_perf->qos_class = dent->qos_class; dev_dbg(dev, "DSMAS: dpa: %#llx qos: %d read_bw: %d write_bw %d read_lat: %d write_lat: %d\n", dent->dpa_range.start, dpa_perf->qos_class, dent->coord[ACCESS_COORDINATE_CPU].read_bandwidth, dent->coord[ACCESS_COORDINATE_CPU].write_bandwidth, dent->coord[ACCESS_COORDINATE_CPU].read_latency, dent->coord[ACCESS_COORDINATE_CPU].write_latency); } static void cxl_memdev_set_qos_class(struct cxl_dev_state *cxlds, struct xarray *dsmas_xa) { struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); struct device *dev = cxlds->dev; struct range pmem_range = { .start = cxlds->pmem_res.start, .end = cxlds->pmem_res.end, }; struct range ram_range = { .start = cxlds->ram_res.start, .end = cxlds->ram_res.end, }; struct dsmas_entry *dent; unsigned long index; xa_for_each(dsmas_xa, index, dent) { if (resource_size(&cxlds->ram_res) && range_contains(&ram_range, &dent->dpa_range)) update_perf_entry(dev, dent, &mds->ram_perf); else if (resource_size(&cxlds->pmem_res) && range_contains(&pmem_range, &dent->dpa_range)) update_perf_entry(dev, dent, &mds->pmem_perf); else dev_dbg(dev, "no partition for dsmas dpa: %#llx\n", dent->dpa_range.start); } } static int match_cxlrd_qos_class(struct device *dev, void *data) { int dev_qos_class = *(int *)data; struct cxl_root_decoder *cxlrd; if (!is_root_decoder(dev)) return 0; cxlrd = to_cxl_root_decoder(dev); if (cxlrd->qos_class == CXL_QOS_CLASS_INVALID) return 0; if (cxlrd->qos_class == dev_qos_class) return 1; return 0; } static void reset_dpa_perf(struct cxl_dpa_perf *dpa_perf) { *dpa_perf = (struct cxl_dpa_perf) { .qos_class = CXL_QOS_CLASS_INVALID, }; } static bool cxl_qos_match(struct cxl_port *root_port, struct cxl_dpa_perf *dpa_perf) { if (dpa_perf->qos_class == CXL_QOS_CLASS_INVALID) return false; if (!device_for_each_child(&root_port->dev, &dpa_perf->qos_class, match_cxlrd_qos_class)) return false; return true; } static int match_cxlrd_hb(struct device *dev, void *data) { struct device *host_bridge = data; struct cxl_switch_decoder *cxlsd; struct cxl_root_decoder *cxlrd; if (!is_root_decoder(dev)) return 0; cxlrd = to_cxl_root_decoder(dev); cxlsd = &cxlrd->cxlsd; guard(rwsem_read)(&cxl_region_rwsem); for (int i = 0; i < cxlsd->nr_targets; i++) { if (host_bridge == cxlsd->target[i]->dport_dev) return 1; } return 0; } static int cxl_qos_class_verify(struct cxl_memdev *cxlmd) { struct cxl_dev_state *cxlds = cxlmd->cxlds; struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); struct cxl_port *root_port; int rc; struct cxl_root *cxl_root __free(put_cxl_root) = find_cxl_root(cxlmd->endpoint); if (!cxl_root) return -ENODEV; root_port = &cxl_root->port; /* Check that the QTG IDs are all sane between end device and root decoders */ if (!cxl_qos_match(root_port, &mds->ram_perf)) reset_dpa_perf(&mds->ram_perf); if (!cxl_qos_match(root_port, &mds->pmem_perf)) reset_dpa_perf(&mds->pmem_perf); /* Check to make sure that the device's host bridge is under a root decoder */ rc = device_for_each_child(&root_port->dev, cxlmd->endpoint->host_bridge, match_cxlrd_hb); if (!rc) { reset_dpa_perf(&mds->ram_perf); reset_dpa_perf(&mds->pmem_perf); } return rc; } static void discard_dsmas(struct xarray *xa) { unsigned long index; void *ent; xa_for_each(xa, index, ent) { xa_erase(xa, index); kfree(ent); } xa_destroy(xa); } DEFINE_FREE(dsmas, struct xarray *, if (_T) discard_dsmas(_T)) void cxl_endpoint_parse_cdat(struct cxl_port *port) { struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev); struct cxl_dev_state *cxlds = cxlmd->cxlds; struct xarray __dsmas_xa; struct xarray *dsmas_xa __free(dsmas) = &__dsmas_xa; int rc; xa_init(&__dsmas_xa); if (!port->cdat.table) return; rc = cxl_cdat_endpoint_process(port, dsmas_xa); if (rc < 0) { dev_dbg(&port->dev, "Failed to parse CDAT: %d\n", rc); return; } rc = cxl_port_perf_data_calculate(port, dsmas_xa); if (rc) { dev_dbg(&port->dev, "Failed to do perf coord calculations.\n"); return; } cxl_memdev_set_qos_class(cxlds, dsmas_xa); cxl_qos_class_verify(cxlmd); cxl_memdev_update_perf(cxlmd); } EXPORT_SYMBOL_NS_GPL(cxl_endpoint_parse_cdat, CXL); static int cdat_sslbis_handler(union acpi_subtable_headers *header, void *arg, const unsigned long end) { struct acpi_cdat_sslbis_table { struct acpi_cdat_header header; struct acpi_cdat_sslbis sslbis_header; struct acpi_cdat_sslbe entries[]; } *tbl = (struct acpi_cdat_sslbis_table *)header; int size = sizeof(header->cdat) + sizeof(tbl->sslbis_header); struct acpi_cdat_sslbis *sslbis; struct cxl_port *port = arg; struct device *dev = &port->dev; int remain, entries, i; u16 len; len = le16_to_cpu((__force __le16)header->cdat.length); remain = len - size; if (!remain || remain % sizeof(tbl->entries[0]) || (unsigned long)header + len > end) { dev_warn(dev, "Malformed SSLBIS table length: (%u)\n", len); return -EINVAL; } sslbis = &tbl->sslbis_header; /* Unrecognized data type, we can skip */ if (sslbis->data_type > ACPI_HMAT_WRITE_BANDWIDTH) return 0; entries = remain / sizeof(tbl->entries[0]); if (struct_size(tbl, entries, entries) != len) return -EINVAL; for (i = 0; i < entries; i++) { u16 x = le16_to_cpu((__force __le16)tbl->entries[i].portx_id); u16 y = le16_to_cpu((__force __le16)tbl->entries[i].porty_id); __le64 le_base; __le16 le_val; struct cxl_dport *dport; unsigned long index; u16 dsp_id; u64 val; switch (x) { case ACPI_CDAT_SSLBIS_US_PORT: dsp_id = y; break; case ACPI_CDAT_SSLBIS_ANY_PORT: switch (y) { case ACPI_CDAT_SSLBIS_US_PORT: dsp_id = x; break; case ACPI_CDAT_SSLBIS_ANY_PORT: dsp_id = ACPI_CDAT_SSLBIS_ANY_PORT; break; default: dsp_id = y; break; } break; default: dsp_id = x; break; } le_base = (__force __le64)tbl->sslbis_header.entry_base_unit; le_val = (__force __le16)tbl->entries[i].latency_or_bandwidth; val = cdat_normalize(le16_to_cpu(le_val), le64_to_cpu(le_base), sslbis->data_type); xa_for_each(&port->dports, index, dport) { if (dsp_id == ACPI_CDAT_SSLBIS_ANY_PORT || dsp_id == dport->port_id) { cxl_access_coordinate_set(dport->coord, sslbis->data_type, val); } } } return 0; } void cxl_switch_parse_cdat(struct cxl_port *port) { int rc; if (!port->cdat.table) return; rc = cdat_table_parse(ACPI_CDAT_TYPE_SSLBIS, cdat_sslbis_handler, port, port->cdat.table, port->cdat.length); rc = cdat_table_parse_output(rc); if (rc) dev_dbg(&port->dev, "Failed to parse SSLBIS: %d\n", rc); } EXPORT_SYMBOL_NS_GPL(cxl_switch_parse_cdat, CXL); static void __cxl_coordinates_combine(struct access_coordinate *out, struct access_coordinate *c1, struct access_coordinate *c2) { if (c1->write_bandwidth && c2->write_bandwidth) out->write_bandwidth = min(c1->write_bandwidth, c2->write_bandwidth); out->write_latency = c1->write_latency + c2->write_latency; if (c1->read_bandwidth && c2->read_bandwidth) out->read_bandwidth = min(c1->read_bandwidth, c2->read_bandwidth); out->read_latency = c1->read_latency + c2->read_latency; } /** * cxl_coordinates_combine - Combine the two input coordinates * * @out: Output coordinate of c1 and c2 combined * @c1: input coordinates * @c2: input coordinates */ void cxl_coordinates_combine(struct access_coordinate *out, struct access_coordinate *c1, struct access_coordinate *c2) { for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) __cxl_coordinates_combine(&out[i], &c1[i], &c2[i]); } MODULE_IMPORT_NS(CXL); static void cxl_bandwidth_add(struct access_coordinate *coord, struct access_coordinate *c1, struct access_coordinate *c2) { for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) { coord[i].read_bandwidth = c1[i].read_bandwidth + c2[i].read_bandwidth; coord[i].write_bandwidth = c1[i].write_bandwidth + c2[i].write_bandwidth; } } static bool dpa_perf_contains(struct cxl_dpa_perf *perf, struct resource *dpa_res) { struct range dpa = { .start = dpa_res->start, .end = dpa_res->end, }; return range_contains(&perf->dpa_range, &dpa); } static struct cxl_dpa_perf *cxled_get_dpa_perf(struct cxl_endpoint_decoder *cxled, enum cxl_decoder_mode mode) { struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds); struct cxl_dpa_perf *perf; switch (mode) { case CXL_DECODER_RAM: perf = &mds->ram_perf; break; case CXL_DECODER_PMEM: perf = &mds->pmem_perf; break; default: return ERR_PTR(-EINVAL); } if (!dpa_perf_contains(perf, cxled->dpa_res)) return ERR_PTR(-EINVAL); return perf; } /* * Transient context for containing the current calculation of bandwidth when * doing walking the port hierarchy to deal with shared upstream link. */ struct cxl_perf_ctx { struct access_coordinate coord[ACCESS_COORDINATE_MAX]; struct cxl_port *port; }; /** * cxl_endpoint_gather_bandwidth - collect all the endpoint bandwidth in an xarray * @cxlr: CXL region for the bandwidth calculation * @cxled: endpoint decoder to start on * @usp_xa: (output) the xarray that collects all the bandwidth coordinates * indexed by the upstream device with data of 'struct cxl_perf_ctx'. * @gp_is_root: (output) bool of whether the grandparent is cxl root. * * Return: 0 for success or -errno * * Collects aggregated endpoint bandwidth and store the bandwidth in * an xarray indexed by the upstream device of the switch or the RP * device. Each endpoint consists the minimum of the bandwidth from DSLBIS * from the endpoint CDAT, the endpoint upstream link bandwidth, and the * bandwidth from the SSLBIS of the switch CDAT for the switch upstream port to * the downstream port that's associated with the endpoint. If the * device is directly connected to a RP, then no SSLBIS is involved. */ static int cxl_endpoint_gather_bandwidth(struct cxl_region *cxlr, struct cxl_endpoint_decoder *cxled, struct xarray *usp_xa, bool *gp_is_root) { struct cxl_port *endpoint = to_cxl_port(cxled->cxld.dev.parent); struct cxl_port *parent_port = to_cxl_port(endpoint->dev.parent); struct cxl_port *gp_port = to_cxl_port(parent_port->dev.parent); struct access_coordinate pci_coord[ACCESS_COORDINATE_MAX]; struct access_coordinate sw_coord[ACCESS_COORDINATE_MAX]; struct access_coordinate ep_coord[ACCESS_COORDINATE_MAX]; struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); struct cxl_dev_state *cxlds = cxlmd->cxlds; struct pci_dev *pdev = to_pci_dev(cxlds->dev); struct cxl_perf_ctx *perf_ctx; struct cxl_dpa_perf *perf; unsigned long index; void *ptr; int rc; if (cxlds->rcd) return -ENODEV; perf = cxled_get_dpa_perf(cxled, cxlr->mode); if (IS_ERR(perf)) return PTR_ERR(perf); gp_port = to_cxl_port(parent_port->dev.parent); *gp_is_root = is_cxl_root(gp_port); /* * If the grandparent is cxl root, then index is the root port, * otherwise it's the parent switch upstream device. */ if (*gp_is_root) index = (unsigned long)endpoint->parent_dport->dport_dev; else index = (unsigned long)parent_port->uport_dev; perf_ctx = xa_load(usp_xa, index); if (!perf_ctx) { struct cxl_perf_ctx *c __free(kfree) = kzalloc(sizeof(*perf_ctx), GFP_KERNEL); if (!c) return -ENOMEM; ptr = xa_store(usp_xa, index, c, GFP_KERNEL); if (xa_is_err(ptr)) return xa_err(ptr); perf_ctx = no_free_ptr(c); perf_ctx->port = parent_port; } /* Direct upstream link from EP bandwidth */ rc = cxl_pci_get_bandwidth(pdev, pci_coord); if (rc < 0) return rc; /* * Min of upstream link bandwidth and Endpoint CDAT bandwidth from * DSLBIS. */ cxl_coordinates_combine(ep_coord, pci_coord, perf->cdat_coord); /* * If grandparent port is root, then there's no switch involved and * the endpoint is connected to a root port. */ if (!*gp_is_root) { /* * Retrieve the switch SSLBIS for switch downstream port * associated with the endpoint bandwidth. */ rc = cxl_port_get_switch_dport_bandwidth(endpoint, sw_coord); if (rc) return rc; /* * Min of the earlier coordinates with the switch SSLBIS * bandwidth */ cxl_coordinates_combine(ep_coord, ep_coord, sw_coord); } /* * Aggregate the computed bandwidth with the current aggregated bandwidth * of the endpoints with the same switch upstream device or RP. */ cxl_bandwidth_add(perf_ctx->coord, perf_ctx->coord, ep_coord); return 0; } static void free_perf_xa(struct xarray *xa) { struct cxl_perf_ctx *ctx; unsigned long index; if (!xa) return; xa_for_each(xa, index, ctx) kfree(ctx); xa_destroy(xa); kfree(xa); } DEFINE_FREE(free_perf_xa, struct xarray *, if (_T) free_perf_xa(_T)) /** * cxl_switch_gather_bandwidth - collect all the bandwidth at switch level in an xarray * @cxlr: The region being operated on * @input_xa: xarray indexed by upstream device of a switch with data of 'struct * cxl_perf_ctx' * @gp_is_root: (output) bool of whether the grandparent is cxl root. * * Return: a xarray of resulting cxl_perf_ctx per parent switch or root port * or ERR_PTR(-errno) * * Iterate through the xarray. Take the minimum of the downstream calculated * bandwidth, the upstream link bandwidth, and the SSLBIS of the upstream * switch if exists. Sum the resulting bandwidth under the switch upstream * device or a RP device. The function can be iterated over multiple switches * if the switches are present. */ static struct xarray *cxl_switch_gather_bandwidth(struct cxl_region *cxlr, struct xarray *input_xa, bool *gp_is_root) { struct xarray *res_xa __free(free_perf_xa) = kzalloc(sizeof(*res_xa), GFP_KERNEL); struct access_coordinate coords[ACCESS_COORDINATE_MAX]; struct cxl_perf_ctx *ctx, *us_ctx; unsigned long index, us_index; int dev_count = 0; int gp_count = 0; void *ptr; int rc; if (!res_xa) return ERR_PTR(-ENOMEM); xa_init(res_xa); xa_for_each(input_xa, index, ctx) { struct device *dev = (struct device *)index; struct cxl_port *port = ctx->port; struct cxl_port *parent_port = to_cxl_port(port->dev.parent); struct cxl_port *gp_port = to_cxl_port(parent_port->dev.parent); struct cxl_dport *dport = port->parent_dport; bool is_root = false; dev_count++; if (is_cxl_root(gp_port)) { is_root = true; gp_count++; } /* * If the grandparent is cxl root, then index is the root port, * otherwise it's the parent switch upstream device. */ if (is_root) us_index = (unsigned long)port->parent_dport->dport_dev; else us_index = (unsigned long)parent_port->uport_dev; us_ctx = xa_load(res_xa, us_index); if (!us_ctx) { struct cxl_perf_ctx *n __free(kfree) = kzalloc(sizeof(*n), GFP_KERNEL); if (!n) return ERR_PTR(-ENOMEM); ptr = xa_store(res_xa, us_index, n, GFP_KERNEL); if (xa_is_err(ptr)) return ERR_PTR(xa_err(ptr)); us_ctx = no_free_ptr(n); us_ctx->port = parent_port; } /* * If the device isn't an upstream PCIe port, there's something * wrong with the topology. */ if (!dev_is_pci(dev)) return ERR_PTR(-EINVAL); /* Retrieve the upstream link bandwidth */ rc = cxl_pci_get_bandwidth(to_pci_dev(dev), coords); if (rc) return ERR_PTR(-ENXIO); /* * Take the min of downstream bandwidth and the upstream link * bandwidth. */ cxl_coordinates_combine(coords, coords, ctx->coord); /* * Take the min of the calculated bandwdith and the upstream * switch SSLBIS bandwidth if there's a parent switch */ if (!is_root) cxl_coordinates_combine(coords, coords, dport->coord); /* * Aggregate the calculated bandwidth common to an upstream * switch. */ cxl_bandwidth_add(us_ctx->coord, us_ctx->coord, coords); } /* Asymmetric topology detected. */ if (gp_count) { if (gp_count != dev_count) { dev_dbg(&cxlr->dev, "Asymmetric hierarchy detected, bandwidth not updated\n"); return ERR_PTR(-EOPNOTSUPP); } *gp_is_root = true; } return no_free_ptr(res_xa); } /** * cxl_rp_gather_bandwidth - handle the root port level bandwidth collection * @xa: the xarray that holds the cxl_perf_ctx that has the bandwidth calculated * below each root port device. * * Return: xarray that holds cxl_perf_ctx per host bridge or ERR_PTR(-errno) */ static struct xarray *cxl_rp_gather_bandwidth(struct xarray *xa) { struct xarray *hb_xa __free(free_perf_xa) = kzalloc(sizeof(*hb_xa), GFP_KERNEL); struct cxl_perf_ctx *ctx; unsigned long index; if (!hb_xa) return ERR_PTR(-ENOMEM); xa_init(hb_xa); xa_for_each(xa, index, ctx) { struct cxl_port *port = ctx->port; unsigned long hb_index = (unsigned long)port->uport_dev; struct cxl_perf_ctx *hb_ctx; void *ptr; hb_ctx = xa_load(hb_xa, hb_index); if (!hb_ctx) { struct cxl_perf_ctx *n __free(kfree) = kzalloc(sizeof(*n), GFP_KERNEL); if (!n) return ERR_PTR(-ENOMEM); ptr = xa_store(hb_xa, hb_index, n, GFP_KERNEL); if (xa_is_err(ptr)) return ERR_PTR(xa_err(ptr)); hb_ctx = no_free_ptr(n); hb_ctx->port = port; } cxl_bandwidth_add(hb_ctx->coord, hb_ctx->coord, ctx->coord); } return no_free_ptr(hb_xa); } /** * cxl_hb_gather_bandwidth - handle the host bridge level bandwidth collection * @xa: the xarray that holds the cxl_perf_ctx that has the bandwidth calculated * below each host bridge. * * Return: xarray that holds cxl_perf_ctx per ACPI0017 device or ERR_PTR(-errno) */ static struct xarray *cxl_hb_gather_bandwidth(struct xarray *xa) { struct xarray *mw_xa __free(free_perf_xa) = kzalloc(sizeof(*mw_xa), GFP_KERNEL); struct cxl_perf_ctx *ctx; unsigned long index; if (!mw_xa) return ERR_PTR(-ENOMEM); xa_init(mw_xa); xa_for_each(xa, index, ctx) { struct cxl_port *port = ctx->port; struct cxl_port *parent_port; struct cxl_perf_ctx *mw_ctx; struct cxl_dport *dport; unsigned long mw_index; void *ptr; parent_port = to_cxl_port(port->dev.parent); mw_index = (unsigned long)parent_port->uport_dev; mw_ctx = xa_load(mw_xa, mw_index); if (!mw_ctx) { struct cxl_perf_ctx *n __free(kfree) = kzalloc(sizeof(*n), GFP_KERNEL); if (!n) return ERR_PTR(-ENOMEM); ptr = xa_store(mw_xa, mw_index, n, GFP_KERNEL); if (xa_is_err(ptr)) return ERR_PTR(xa_err(ptr)); mw_ctx = no_free_ptr(n); } dport = port->parent_dport; cxl_coordinates_combine(ctx->coord, ctx->coord, dport->coord); cxl_bandwidth_add(mw_ctx->coord, mw_ctx->coord, ctx->coord); } return no_free_ptr(mw_xa); } /** * cxl_region_update_bandwidth - Update the bandwidth access coordinates of a region * @cxlr: The region being operated on * @input_xa: xarray holds cxl_perf_ctx wht calculated bandwidth per ACPI0017 instance */ static void cxl_region_update_bandwidth(struct cxl_region *cxlr, struct xarray *input_xa) { struct access_coordinate coord[ACCESS_COORDINATE_MAX]; struct cxl_perf_ctx *ctx; unsigned long index; memset(coord, 0, sizeof(coord)); xa_for_each(input_xa, index, ctx) cxl_bandwidth_add(coord, coord, ctx->coord); for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) { cxlr->coord[i].read_bandwidth = coord[i].read_bandwidth; cxlr->coord[i].write_bandwidth = coord[i].write_bandwidth; } } /** * cxl_region_shared_upstream_bandwidth_update - Recalculate the bandwidth for * the region * @cxlr: the cxl region to recalculate * * The function walks the topology from bottom up and calculates the bandwidth. It * starts at the endpoints, processes at the switches if any, processes at the rootport * level, at the host bridge level, and finally aggregates at the region. */ void cxl_region_shared_upstream_bandwidth_update(struct cxl_region *cxlr) { struct xarray *working_xa; int root_count = 0; bool is_root; int rc; lockdep_assert_held(&cxl_dpa_rwsem); struct xarray *usp_xa __free(free_perf_xa) = kzalloc(sizeof(*usp_xa), GFP_KERNEL); if (!usp_xa) return; xa_init(usp_xa); /* Collect bandwidth data from all the endpoints. */ for (int i = 0; i < cxlr->params.nr_targets; i++) { struct cxl_endpoint_decoder *cxled = cxlr->params.targets[i]; is_root = false; rc = cxl_endpoint_gather_bandwidth(cxlr, cxled, usp_xa, &is_root); if (rc) return; root_count += is_root; } /* Detect asymmetric hierarchy with some direct attached endpoints. */ if (root_count && root_count != cxlr->params.nr_targets) { dev_dbg(&cxlr->dev, "Asymmetric hierarchy detected, bandwidth not updated\n"); return; } /* * Walk up one or more switches to deal with the bandwidth of the * switches if they exist. Endpoints directly attached to RPs skip * over this part. */ if (!root_count) { do { working_xa = cxl_switch_gather_bandwidth(cxlr, usp_xa, &is_root); if (IS_ERR(working_xa)) return; free_perf_xa(usp_xa); usp_xa = working_xa; } while (!is_root); } /* Handle the bandwidth at the root port of the hierarchy */ working_xa = cxl_rp_gather_bandwidth(usp_xa); if (IS_ERR(working_xa)) return; free_perf_xa(usp_xa); usp_xa = working_xa; /* Handle the bandwidth at the host bridge of the hierarchy */ working_xa = cxl_hb_gather_bandwidth(usp_xa); if (IS_ERR(working_xa)) return; free_perf_xa(usp_xa); usp_xa = working_xa; /* * Aggregate all the bandwidth collected per CFMWS (ACPI0017) and * update the region bandwidth with the final calculated values. */ cxl_region_update_bandwidth(cxlr, usp_xa); } void cxl_region_perf_data_calculate(struct cxl_region *cxlr, struct cxl_endpoint_decoder *cxled) { struct cxl_dpa_perf *perf; lockdep_assert_held(&cxl_dpa_rwsem); perf = cxled_get_dpa_perf(cxled, cxlr->mode); if (IS_ERR(perf)) return; for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) { /* Get total bandwidth and the worst latency for the cxl region */ cxlr->coord[i].read_latency = max_t(unsigned int, cxlr->coord[i].read_latency, perf->coord[i].read_latency); cxlr->coord[i].write_latency = max_t(unsigned int, cxlr->coord[i].write_latency, perf->coord[i].write_latency); cxlr->coord[i].read_bandwidth += perf->coord[i].read_bandwidth; cxlr->coord[i].write_bandwidth += perf->coord[i].write_bandwidth; } } int cxl_update_hmat_access_coordinates(int nid, struct cxl_region *cxlr, enum access_coordinate_class access) { return hmat_update_target_coordinates(nid, &cxlr->coord[access], access); } bool cxl_need_node_perf_attrs_update(int nid) { return !acpi_node_backed_by_real_pxm(nid); }