// SPDX-License-Identifier: GPL-2.0-only /* Copyright(c) 2022 Intel Corporation. All rights reserved. */ #include #include #include #include "cxlmem.h" #include "core.h" /** * DOC: cxl core hdm * * Compute Express Link Host Managed Device Memory, starting with the * CXL 2.0 specification, is managed by an array of HDM Decoder register * instances per CXL port and per CXL endpoint. Define common helpers * for enumerating these registers and capabilities. */ static int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld, int *target_map) { int rc; rc = cxl_decoder_add_locked(cxld, target_map); if (rc) { put_device(&cxld->dev); dev_err(&port->dev, "Failed to add decoder\n"); return rc; } rc = cxl_decoder_autoremove(&port->dev, cxld); if (rc) return rc; dev_dbg(&cxld->dev, "Added to port %s\n", dev_name(&port->dev)); return 0; } /* * Per the CXL specification (8.2.5.12 CXL HDM Decoder Capability Structure) * single ported host-bridges need not publish a decoder capability when a * passthrough decode can be assumed, i.e. all transactions that the uport sees * are claimed and passed to the single dport. Disable the range until the first * CXL region is enumerated / activated. */ int devm_cxl_add_passthrough_decoder(struct cxl_port *port) { struct cxl_decoder *cxld; struct cxl_dport *dport; int single_port_map[1]; cxld = cxl_switch_decoder_alloc(port, 1); if (IS_ERR(cxld)) return PTR_ERR(cxld); device_lock_assert(&port->dev); dport = list_first_entry(&port->dports, typeof(*dport), list); single_port_map[0] = dport->port_id; return add_hdm_decoder(port, cxld, single_port_map); } EXPORT_SYMBOL_NS_GPL(devm_cxl_add_passthrough_decoder, CXL); static void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm) { u32 hdm_cap; hdm_cap = readl(cxlhdm->regs.hdm_decoder + CXL_HDM_DECODER_CAP_OFFSET); cxlhdm->decoder_count = cxl_hdm_decoder_count(hdm_cap); cxlhdm->target_count = FIELD_GET(CXL_HDM_DECODER_TARGET_COUNT_MASK, hdm_cap); if (FIELD_GET(CXL_HDM_DECODER_INTERLEAVE_11_8, hdm_cap)) cxlhdm->interleave_mask |= GENMASK(11, 8); if (FIELD_GET(CXL_HDM_DECODER_INTERLEAVE_14_12, hdm_cap)) cxlhdm->interleave_mask |= GENMASK(14, 12); } static void __iomem *map_hdm_decoder_regs(struct cxl_port *port, void __iomem *crb) { struct cxl_component_reg_map map; cxl_probe_component_regs(&port->dev, crb, &map); if (!map.hdm_decoder.valid) { dev_err(&port->dev, "HDM decoder registers invalid\n"); return IOMEM_ERR_PTR(-ENXIO); } return crb + map.hdm_decoder.offset; } /** * devm_cxl_setup_hdm - map HDM decoder component registers * @port: cxl_port to map */ struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port) { struct device *dev = &port->dev; void __iomem *crb, *hdm; struct cxl_hdm *cxlhdm; cxlhdm = devm_kzalloc(dev, sizeof(*cxlhdm), GFP_KERNEL); if (!cxlhdm) return ERR_PTR(-ENOMEM); cxlhdm->port = port; crb = devm_cxl_iomap_block(dev, port->component_reg_phys, CXL_COMPONENT_REG_BLOCK_SIZE); if (!crb) { dev_err(dev, "No component registers mapped\n"); return ERR_PTR(-ENXIO); } hdm = map_hdm_decoder_regs(port, crb); if (IS_ERR(hdm)) return ERR_CAST(hdm); cxlhdm->regs.hdm_decoder = hdm; parse_hdm_decoder_caps(cxlhdm); if (cxlhdm->decoder_count == 0) { dev_err(dev, "Spec violation. Caps invalid\n"); return ERR_PTR(-ENXIO); } return cxlhdm; } EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_hdm, CXL); static int to_interleave_granularity(u32 ctrl) { int val = FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl); return 256 << val; } static int to_interleave_ways(u32 ctrl) { int val = FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl); switch (val) { case 0 ... 4: return 1 << val; case 8 ... 10: return 3 << (val - 8); default: return 0; } } static int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld, int *target_map, void __iomem *hdm, int which) { u64 size, base; u32 ctrl; int i; union { u64 value; unsigned char target_id[8]; } target_list; ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which)); base = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which)); size = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which)); if (!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED)) size = 0; if (base == U64_MAX || size == U64_MAX) { dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n", port->id, cxld->id); return -ENXIO; } cxld->decoder_range = (struct range) { .start = base, .end = base + size - 1, }; /* switch decoders are always enabled if committed */ if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) { cxld->flags |= CXL_DECODER_F_ENABLE; if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) cxld->flags |= CXL_DECODER_F_LOCK; } cxld->interleave_ways = to_interleave_ways(ctrl); if (!cxld->interleave_ways) { dev_warn(&port->dev, "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n", port->id, cxld->id, ctrl); return -ENXIO; } cxld->interleave_granularity = to_interleave_granularity(ctrl); if (FIELD_GET(CXL_HDM_DECODER0_CTRL_TYPE, ctrl)) cxld->target_type = CXL_DECODER_EXPANDER; else cxld->target_type = CXL_DECODER_ACCELERATOR; if (is_cxl_endpoint(to_cxl_port(cxld->dev.parent))) return 0; target_list.value = ioread64_hi_lo(hdm + CXL_HDM_DECODER0_TL_LOW(which)); for (i = 0; i < cxld->interleave_ways; i++) target_map[i] = target_list.target_id[i]; return 0; } /** * devm_cxl_enumerate_decoders - add decoder objects per HDM register set * @cxlhdm: Structure to populate with HDM capabilities */ int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm) { void __iomem *hdm = cxlhdm->regs.hdm_decoder; struct cxl_port *port = cxlhdm->port; int i, committed, failed; u32 ctrl; /* * Since the register resource was recently claimed via request_region() * be careful about trusting the "not-committed" status until the commit * timeout has elapsed. The commit timeout is 10ms (CXL 2.0 * 8.2.5.12.20), but double it to be tolerant of any clock skew between * host and target. */ for (i = 0, committed = 0; i < cxlhdm->decoder_count; i++) { ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(i)); if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) committed++; } /* ensure that future checks of committed can be trusted */ if (committed != cxlhdm->decoder_count) msleep(20); for (i = 0, failed = 0; i < cxlhdm->decoder_count; i++) { int target_map[CXL_DECODER_MAX_INTERLEAVE] = { 0 }; int rc, target_count = cxlhdm->target_count; struct cxl_decoder *cxld; if (is_cxl_endpoint(port)) cxld = cxl_endpoint_decoder_alloc(port); else cxld = cxl_switch_decoder_alloc(port, target_count); if (IS_ERR(cxld)) { dev_warn(&port->dev, "Failed to allocate the decoder\n"); return PTR_ERR(cxld); } rc = init_hdm_decoder(port, cxld, target_map, cxlhdm->regs.hdm_decoder, i); if (rc) { put_device(&cxld->dev); failed++; continue; } rc = add_hdm_decoder(port, cxld, target_map); if (rc) { dev_warn(&port->dev, "Failed to add decoder to port\n"); return rc; } } if (failed == cxlhdm->decoder_count) { dev_err(&port->dev, "No valid decoders found\n"); return -ENXIO; } return 0; } EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_decoders, CXL);