162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Copyright(c) 2022 Intel Corporation. All rights reserved. */ 362306a36Sopenharmony_ci#include <linux/seq_file.h> 462306a36Sopenharmony_ci#include <linux/device.h> 562306a36Sopenharmony_ci#include <linux/delay.h> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include "cxlmem.h" 862306a36Sopenharmony_ci#include "core.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci/** 1162306a36Sopenharmony_ci * DOC: cxl core hdm 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * Compute Express Link Host Managed Device Memory, starting with the 1462306a36Sopenharmony_ci * CXL 2.0 specification, is managed by an array of HDM Decoder register 1562306a36Sopenharmony_ci * instances per CXL port and per CXL endpoint. Define common helpers 1662306a36Sopenharmony_ci * for enumerating these registers and capabilities. 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ciDECLARE_RWSEM(cxl_dpa_rwsem); 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic int add_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld, 2262306a36Sopenharmony_ci int *target_map) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci int rc; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci rc = cxl_decoder_add_locked(cxld, target_map); 2762306a36Sopenharmony_ci if (rc) { 2862306a36Sopenharmony_ci put_device(&cxld->dev); 2962306a36Sopenharmony_ci dev_err(&port->dev, "Failed to add decoder\n"); 3062306a36Sopenharmony_ci return rc; 3162306a36Sopenharmony_ci } 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci rc = cxl_decoder_autoremove(&port->dev, cxld); 3462306a36Sopenharmony_ci if (rc) 3562306a36Sopenharmony_ci return rc; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci dev_dbg(&cxld->dev, "Added to port %s\n", dev_name(&port->dev)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci return 0; 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci/* 4362306a36Sopenharmony_ci * Per the CXL specification (8.2.5.12 CXL HDM Decoder Capability Structure) 4462306a36Sopenharmony_ci * single ported host-bridges need not publish a decoder capability when a 4562306a36Sopenharmony_ci * passthrough decode can be assumed, i.e. all transactions that the uport sees 4662306a36Sopenharmony_ci * are claimed and passed to the single dport. Disable the range until the first 4762306a36Sopenharmony_ci * CXL region is enumerated / activated. 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ciint devm_cxl_add_passthrough_decoder(struct cxl_port *port) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci struct cxl_switch_decoder *cxlsd; 5262306a36Sopenharmony_ci struct cxl_dport *dport = NULL; 5362306a36Sopenharmony_ci int single_port_map[1]; 5462306a36Sopenharmony_ci unsigned long index; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci cxlsd = cxl_switch_decoder_alloc(port, 1); 5762306a36Sopenharmony_ci if (IS_ERR(cxlsd)) 5862306a36Sopenharmony_ci return PTR_ERR(cxlsd); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci device_lock_assert(&port->dev); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci xa_for_each(&port->dports, index, dport) 6362306a36Sopenharmony_ci break; 6462306a36Sopenharmony_ci single_port_map[0] = dport->port_id; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci return add_hdm_decoder(port, &cxlsd->cxld, single_port_map); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_add_passthrough_decoder, CXL); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic void parse_hdm_decoder_caps(struct cxl_hdm *cxlhdm) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci u32 hdm_cap; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci hdm_cap = readl(cxlhdm->regs.hdm_decoder + CXL_HDM_DECODER_CAP_OFFSET); 7562306a36Sopenharmony_ci cxlhdm->decoder_count = cxl_hdm_decoder_count(hdm_cap); 7662306a36Sopenharmony_ci cxlhdm->target_count = 7762306a36Sopenharmony_ci FIELD_GET(CXL_HDM_DECODER_TARGET_COUNT_MASK, hdm_cap); 7862306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER_INTERLEAVE_11_8, hdm_cap)) 7962306a36Sopenharmony_ci cxlhdm->interleave_mask |= GENMASK(11, 8); 8062306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER_INTERLEAVE_14_12, hdm_cap)) 8162306a36Sopenharmony_ci cxlhdm->interleave_mask |= GENMASK(14, 12); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int map_hdm_decoder_regs(struct cxl_port *port, void __iomem *crb, 8562306a36Sopenharmony_ci struct cxl_component_regs *regs) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct cxl_register_map map = { 8862306a36Sopenharmony_ci .host = &port->dev, 8962306a36Sopenharmony_ci .resource = port->component_reg_phys, 9062306a36Sopenharmony_ci .base = crb, 9162306a36Sopenharmony_ci .max_size = CXL_COMPONENT_REG_BLOCK_SIZE, 9262306a36Sopenharmony_ci }; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci cxl_probe_component_regs(&port->dev, crb, &map.component_map); 9562306a36Sopenharmony_ci if (!map.component_map.hdm_decoder.valid) { 9662306a36Sopenharmony_ci dev_dbg(&port->dev, "HDM decoder registers not implemented\n"); 9762306a36Sopenharmony_ci /* unique error code to indicate no HDM decoder capability */ 9862306a36Sopenharmony_ci return -ENODEV; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return cxl_map_component_regs(&map, regs, BIT(CXL_CM_CAP_CAP_ID_HDM)); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic bool should_emulate_decoders(struct cxl_endpoint_dvsec_info *info) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct cxl_hdm *cxlhdm; 10762306a36Sopenharmony_ci void __iomem *hdm; 10862306a36Sopenharmony_ci u32 ctrl; 10962306a36Sopenharmony_ci int i; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (!info) 11262306a36Sopenharmony_ci return false; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci cxlhdm = dev_get_drvdata(&info->port->dev); 11562306a36Sopenharmony_ci hdm = cxlhdm->regs.hdm_decoder; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (!hdm) 11862306a36Sopenharmony_ci return true; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* 12162306a36Sopenharmony_ci * If HDM decoders are present and the driver is in control of 12262306a36Sopenharmony_ci * Mem_Enable skip DVSEC based emulation 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci if (!info->mem_enabled) 12562306a36Sopenharmony_ci return false; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* 12862306a36Sopenharmony_ci * If any decoders are committed already, there should not be any 12962306a36Sopenharmony_ci * emulated DVSEC decoders. 13062306a36Sopenharmony_ci */ 13162306a36Sopenharmony_ci for (i = 0; i < cxlhdm->decoder_count; i++) { 13262306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(i)); 13362306a36Sopenharmony_ci dev_dbg(&info->port->dev, 13462306a36Sopenharmony_ci "decoder%d.%d: committed: %ld base: %#x_%.8x size: %#x_%.8x\n", 13562306a36Sopenharmony_ci info->port->id, i, 13662306a36Sopenharmony_ci FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl), 13762306a36Sopenharmony_ci readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(i)), 13862306a36Sopenharmony_ci readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(i)), 13962306a36Sopenharmony_ci readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(i)), 14062306a36Sopenharmony_ci readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(i))); 14162306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl)) 14262306a36Sopenharmony_ci return false; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return true; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci/** 14962306a36Sopenharmony_ci * devm_cxl_setup_hdm - map HDM decoder component registers 15062306a36Sopenharmony_ci * @port: cxl_port to map 15162306a36Sopenharmony_ci * @info: cached DVSEC range register info 15262306a36Sopenharmony_ci */ 15362306a36Sopenharmony_cistruct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port, 15462306a36Sopenharmony_ci struct cxl_endpoint_dvsec_info *info) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct device *dev = &port->dev; 15762306a36Sopenharmony_ci struct cxl_hdm *cxlhdm; 15862306a36Sopenharmony_ci void __iomem *crb; 15962306a36Sopenharmony_ci int rc; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci cxlhdm = devm_kzalloc(dev, sizeof(*cxlhdm), GFP_KERNEL); 16262306a36Sopenharmony_ci if (!cxlhdm) 16362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 16462306a36Sopenharmony_ci cxlhdm->port = port; 16562306a36Sopenharmony_ci dev_set_drvdata(dev, cxlhdm); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci crb = ioremap(port->component_reg_phys, CXL_COMPONENT_REG_BLOCK_SIZE); 16862306a36Sopenharmony_ci if (!crb && info && info->mem_enabled) { 16962306a36Sopenharmony_ci cxlhdm->decoder_count = info->ranges; 17062306a36Sopenharmony_ci return cxlhdm; 17162306a36Sopenharmony_ci } else if (!crb) { 17262306a36Sopenharmony_ci dev_err(dev, "No component registers mapped\n"); 17362306a36Sopenharmony_ci return ERR_PTR(-ENXIO); 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci rc = map_hdm_decoder_regs(port, crb, &cxlhdm->regs); 17762306a36Sopenharmony_ci iounmap(crb); 17862306a36Sopenharmony_ci if (rc) 17962306a36Sopenharmony_ci return ERR_PTR(rc); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci parse_hdm_decoder_caps(cxlhdm); 18262306a36Sopenharmony_ci if (cxlhdm->decoder_count == 0) { 18362306a36Sopenharmony_ci dev_err(dev, "Spec violation. Caps invalid\n"); 18462306a36Sopenharmony_ci return ERR_PTR(-ENXIO); 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci /* 18862306a36Sopenharmony_ci * Now that the hdm capability is parsed, decide if range 18962306a36Sopenharmony_ci * register emulation is needed and fixup cxlhdm accordingly. 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_ci if (should_emulate_decoders(info)) { 19262306a36Sopenharmony_ci dev_dbg(dev, "Fallback map %d range register%s\n", info->ranges, 19362306a36Sopenharmony_ci info->ranges > 1 ? "s" : ""); 19462306a36Sopenharmony_ci cxlhdm->decoder_count = info->ranges; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return cxlhdm; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_setup_hdm, CXL); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic void __cxl_dpa_debug(struct seq_file *file, struct resource *r, int depth) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci unsigned long long start = r->start, end = r->end; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci seq_printf(file, "%*s%08llx-%08llx : %s\n", depth * 2, "", start, end, 20662306a36Sopenharmony_ci r->name); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_civoid cxl_dpa_debug(struct seq_file *file, struct cxl_dev_state *cxlds) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct resource *p1, *p2; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci down_read(&cxl_dpa_rwsem); 21462306a36Sopenharmony_ci for (p1 = cxlds->dpa_res.child; p1; p1 = p1->sibling) { 21562306a36Sopenharmony_ci __cxl_dpa_debug(file, p1, 0); 21662306a36Sopenharmony_ci for (p2 = p1->child; p2; p2 = p2->sibling) 21762306a36Sopenharmony_ci __cxl_dpa_debug(file, p2, 1); 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci up_read(&cxl_dpa_rwsem); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_dpa_debug, CXL); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci/* 22462306a36Sopenharmony_ci * Must be called in a context that synchronizes against this decoder's 22562306a36Sopenharmony_ci * port ->remove() callback (like an endpoint decoder sysfs attribute) 22662306a36Sopenharmony_ci */ 22762306a36Sopenharmony_cistatic void __cxl_dpa_release(struct cxl_endpoint_decoder *cxled) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 23062306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 23162306a36Sopenharmony_ci struct cxl_dev_state *cxlds = cxlmd->cxlds; 23262306a36Sopenharmony_ci struct resource *res = cxled->dpa_res; 23362306a36Sopenharmony_ci resource_size_t skip_start; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci lockdep_assert_held_write(&cxl_dpa_rwsem); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci /* save @skip_start, before @res is released */ 23862306a36Sopenharmony_ci skip_start = res->start - cxled->skip; 23962306a36Sopenharmony_ci __release_region(&cxlds->dpa_res, res->start, resource_size(res)); 24062306a36Sopenharmony_ci if (cxled->skip) 24162306a36Sopenharmony_ci __release_region(&cxlds->dpa_res, skip_start, cxled->skip); 24262306a36Sopenharmony_ci cxled->skip = 0; 24362306a36Sopenharmony_ci cxled->dpa_res = NULL; 24462306a36Sopenharmony_ci put_device(&cxled->cxld.dev); 24562306a36Sopenharmony_ci port->hdm_end--; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic void cxl_dpa_release(void *cxled) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci down_write(&cxl_dpa_rwsem); 25162306a36Sopenharmony_ci __cxl_dpa_release(cxled); 25262306a36Sopenharmony_ci up_write(&cxl_dpa_rwsem); 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci/* 25662306a36Sopenharmony_ci * Must be called from context that will not race port device 25762306a36Sopenharmony_ci * unregistration, like decoder sysfs attribute methods 25862306a36Sopenharmony_ci */ 25962306a36Sopenharmony_cistatic void devm_cxl_dpa_release(struct cxl_endpoint_decoder *cxled) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci lockdep_assert_held_write(&cxl_dpa_rwsem); 26462306a36Sopenharmony_ci devm_remove_action(&port->dev, cxl_dpa_release, cxled); 26562306a36Sopenharmony_ci __cxl_dpa_release(cxled); 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic int __cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled, 26962306a36Sopenharmony_ci resource_size_t base, resource_size_t len, 27062306a36Sopenharmony_ci resource_size_t skipped) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 27362306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 27462306a36Sopenharmony_ci struct cxl_dev_state *cxlds = cxlmd->cxlds; 27562306a36Sopenharmony_ci struct device *dev = &port->dev; 27662306a36Sopenharmony_ci struct resource *res; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci lockdep_assert_held_write(&cxl_dpa_rwsem); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (!len) { 28162306a36Sopenharmony_ci dev_warn(dev, "decoder%d.%d: empty reservation attempted\n", 28262306a36Sopenharmony_ci port->id, cxled->cxld.id); 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (cxled->dpa_res) { 28762306a36Sopenharmony_ci dev_dbg(dev, "decoder%d.%d: existing allocation %pr assigned\n", 28862306a36Sopenharmony_ci port->id, cxled->cxld.id, cxled->dpa_res); 28962306a36Sopenharmony_ci return -EBUSY; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (port->hdm_end + 1 != cxled->cxld.id) { 29362306a36Sopenharmony_ci /* 29462306a36Sopenharmony_ci * Assumes alloc and commit order is always in hardware instance 29562306a36Sopenharmony_ci * order per expectations from 8.2.5.12.20 Committing Decoder 29662306a36Sopenharmony_ci * Programming that enforce decoder[m] committed before 29762306a36Sopenharmony_ci * decoder[m+1] commit start. 29862306a36Sopenharmony_ci */ 29962306a36Sopenharmony_ci dev_dbg(dev, "decoder%d.%d: expected decoder%d.%d\n", port->id, 30062306a36Sopenharmony_ci cxled->cxld.id, port->id, port->hdm_end + 1); 30162306a36Sopenharmony_ci return -EBUSY; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (skipped) { 30562306a36Sopenharmony_ci res = __request_region(&cxlds->dpa_res, base - skipped, skipped, 30662306a36Sopenharmony_ci dev_name(&cxled->cxld.dev), 0); 30762306a36Sopenharmony_ci if (!res) { 30862306a36Sopenharmony_ci dev_dbg(dev, 30962306a36Sopenharmony_ci "decoder%d.%d: failed to reserve skipped space\n", 31062306a36Sopenharmony_ci port->id, cxled->cxld.id); 31162306a36Sopenharmony_ci return -EBUSY; 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci res = __request_region(&cxlds->dpa_res, base, len, 31562306a36Sopenharmony_ci dev_name(&cxled->cxld.dev), 0); 31662306a36Sopenharmony_ci if (!res) { 31762306a36Sopenharmony_ci dev_dbg(dev, "decoder%d.%d: failed to reserve allocation\n", 31862306a36Sopenharmony_ci port->id, cxled->cxld.id); 31962306a36Sopenharmony_ci if (skipped) 32062306a36Sopenharmony_ci __release_region(&cxlds->dpa_res, base - skipped, 32162306a36Sopenharmony_ci skipped); 32262306a36Sopenharmony_ci return -EBUSY; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci cxled->dpa_res = res; 32562306a36Sopenharmony_ci cxled->skip = skipped; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci if (resource_contains(&cxlds->pmem_res, res)) 32862306a36Sopenharmony_ci cxled->mode = CXL_DECODER_PMEM; 32962306a36Sopenharmony_ci else if (resource_contains(&cxlds->ram_res, res)) 33062306a36Sopenharmony_ci cxled->mode = CXL_DECODER_RAM; 33162306a36Sopenharmony_ci else { 33262306a36Sopenharmony_ci dev_dbg(dev, "decoder%d.%d: %pr mixed\n", port->id, 33362306a36Sopenharmony_ci cxled->cxld.id, cxled->dpa_res); 33462306a36Sopenharmony_ci cxled->mode = CXL_DECODER_MIXED; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci port->hdm_end++; 33862306a36Sopenharmony_ci get_device(&cxled->cxld.dev); 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ciint devm_cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled, 34362306a36Sopenharmony_ci resource_size_t base, resource_size_t len, 34462306a36Sopenharmony_ci resource_size_t skipped) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 34762306a36Sopenharmony_ci int rc; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci down_write(&cxl_dpa_rwsem); 35062306a36Sopenharmony_ci rc = __cxl_dpa_reserve(cxled, base, len, skipped); 35162306a36Sopenharmony_ci up_write(&cxl_dpa_rwsem); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (rc) 35462306a36Sopenharmony_ci return rc; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled); 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_dpa_reserve, CXL); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ciresource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci resource_size_t size = 0; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci down_read(&cxl_dpa_rwsem); 36562306a36Sopenharmony_ci if (cxled->dpa_res) 36662306a36Sopenharmony_ci size = resource_size(cxled->dpa_res); 36762306a36Sopenharmony_ci up_read(&cxl_dpa_rwsem); 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci return size; 37062306a36Sopenharmony_ci} 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ciresource_size_t cxl_dpa_resource_start(struct cxl_endpoint_decoder *cxled) 37362306a36Sopenharmony_ci{ 37462306a36Sopenharmony_ci resource_size_t base = -1; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci lockdep_assert_held(&cxl_dpa_rwsem); 37762306a36Sopenharmony_ci if (cxled->dpa_res) 37862306a36Sopenharmony_ci base = cxled->dpa_res->start; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci return base; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ciint cxl_dpa_free(struct cxl_endpoint_decoder *cxled) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 38662306a36Sopenharmony_ci struct device *dev = &cxled->cxld.dev; 38762306a36Sopenharmony_ci int rc; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci down_write(&cxl_dpa_rwsem); 39062306a36Sopenharmony_ci if (!cxled->dpa_res) { 39162306a36Sopenharmony_ci rc = 0; 39262306a36Sopenharmony_ci goto out; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci if (cxled->cxld.region) { 39562306a36Sopenharmony_ci dev_dbg(dev, "decoder assigned to: %s\n", 39662306a36Sopenharmony_ci dev_name(&cxled->cxld.region->dev)); 39762306a36Sopenharmony_ci rc = -EBUSY; 39862306a36Sopenharmony_ci goto out; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) { 40162306a36Sopenharmony_ci dev_dbg(dev, "decoder enabled\n"); 40262306a36Sopenharmony_ci rc = -EBUSY; 40362306a36Sopenharmony_ci goto out; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci if (cxled->cxld.id != port->hdm_end) { 40662306a36Sopenharmony_ci dev_dbg(dev, "expected decoder%d.%d\n", port->id, 40762306a36Sopenharmony_ci port->hdm_end); 40862306a36Sopenharmony_ci rc = -EBUSY; 40962306a36Sopenharmony_ci goto out; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci devm_cxl_dpa_release(cxled); 41262306a36Sopenharmony_ci rc = 0; 41362306a36Sopenharmony_ciout: 41462306a36Sopenharmony_ci up_write(&cxl_dpa_rwsem); 41562306a36Sopenharmony_ci return rc; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ciint cxl_dpa_set_mode(struct cxl_endpoint_decoder *cxled, 41962306a36Sopenharmony_ci enum cxl_decoder_mode mode) 42062306a36Sopenharmony_ci{ 42162306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 42262306a36Sopenharmony_ci struct cxl_dev_state *cxlds = cxlmd->cxlds; 42362306a36Sopenharmony_ci struct device *dev = &cxled->cxld.dev; 42462306a36Sopenharmony_ci int rc; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci switch (mode) { 42762306a36Sopenharmony_ci case CXL_DECODER_RAM: 42862306a36Sopenharmony_ci case CXL_DECODER_PMEM: 42962306a36Sopenharmony_ci break; 43062306a36Sopenharmony_ci default: 43162306a36Sopenharmony_ci dev_dbg(dev, "unsupported mode: %d\n", mode); 43262306a36Sopenharmony_ci return -EINVAL; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci down_write(&cxl_dpa_rwsem); 43662306a36Sopenharmony_ci if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) { 43762306a36Sopenharmony_ci rc = -EBUSY; 43862306a36Sopenharmony_ci goto out; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci /* 44262306a36Sopenharmony_ci * Only allow modes that are supported by the current partition 44362306a36Sopenharmony_ci * configuration 44462306a36Sopenharmony_ci */ 44562306a36Sopenharmony_ci if (mode == CXL_DECODER_PMEM && !resource_size(&cxlds->pmem_res)) { 44662306a36Sopenharmony_ci dev_dbg(dev, "no available pmem capacity\n"); 44762306a36Sopenharmony_ci rc = -ENXIO; 44862306a36Sopenharmony_ci goto out; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci if (mode == CXL_DECODER_RAM && !resource_size(&cxlds->ram_res)) { 45162306a36Sopenharmony_ci dev_dbg(dev, "no available ram capacity\n"); 45262306a36Sopenharmony_ci rc = -ENXIO; 45362306a36Sopenharmony_ci goto out; 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci cxled->mode = mode; 45762306a36Sopenharmony_ci rc = 0; 45862306a36Sopenharmony_ciout: 45962306a36Sopenharmony_ci up_write(&cxl_dpa_rwsem); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci return rc; 46262306a36Sopenharmony_ci} 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ciint cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, unsigned long long size) 46562306a36Sopenharmony_ci{ 46662306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 46762306a36Sopenharmony_ci resource_size_t free_ram_start, free_pmem_start; 46862306a36Sopenharmony_ci struct cxl_port *port = cxled_to_port(cxled); 46962306a36Sopenharmony_ci struct cxl_dev_state *cxlds = cxlmd->cxlds; 47062306a36Sopenharmony_ci struct device *dev = &cxled->cxld.dev; 47162306a36Sopenharmony_ci resource_size_t start, avail, skip; 47262306a36Sopenharmony_ci struct resource *p, *last; 47362306a36Sopenharmony_ci int rc; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci down_write(&cxl_dpa_rwsem); 47662306a36Sopenharmony_ci if (cxled->cxld.region) { 47762306a36Sopenharmony_ci dev_dbg(dev, "decoder attached to %s\n", 47862306a36Sopenharmony_ci dev_name(&cxled->cxld.region->dev)); 47962306a36Sopenharmony_ci rc = -EBUSY; 48062306a36Sopenharmony_ci goto out; 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci if (cxled->cxld.flags & CXL_DECODER_F_ENABLE) { 48462306a36Sopenharmony_ci dev_dbg(dev, "decoder enabled\n"); 48562306a36Sopenharmony_ci rc = -EBUSY; 48662306a36Sopenharmony_ci goto out; 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci for (p = cxlds->ram_res.child, last = NULL; p; p = p->sibling) 49062306a36Sopenharmony_ci last = p; 49162306a36Sopenharmony_ci if (last) 49262306a36Sopenharmony_ci free_ram_start = last->end + 1; 49362306a36Sopenharmony_ci else 49462306a36Sopenharmony_ci free_ram_start = cxlds->ram_res.start; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci for (p = cxlds->pmem_res.child, last = NULL; p; p = p->sibling) 49762306a36Sopenharmony_ci last = p; 49862306a36Sopenharmony_ci if (last) 49962306a36Sopenharmony_ci free_pmem_start = last->end + 1; 50062306a36Sopenharmony_ci else 50162306a36Sopenharmony_ci free_pmem_start = cxlds->pmem_res.start; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (cxled->mode == CXL_DECODER_RAM) { 50462306a36Sopenharmony_ci start = free_ram_start; 50562306a36Sopenharmony_ci avail = cxlds->ram_res.end - start + 1; 50662306a36Sopenharmony_ci skip = 0; 50762306a36Sopenharmony_ci } else if (cxled->mode == CXL_DECODER_PMEM) { 50862306a36Sopenharmony_ci resource_size_t skip_start, skip_end; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci start = free_pmem_start; 51162306a36Sopenharmony_ci avail = cxlds->pmem_res.end - start + 1; 51262306a36Sopenharmony_ci skip_start = free_ram_start; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci /* 51562306a36Sopenharmony_ci * If some pmem is already allocated, then that allocation 51662306a36Sopenharmony_ci * already handled the skip. 51762306a36Sopenharmony_ci */ 51862306a36Sopenharmony_ci if (cxlds->pmem_res.child && 51962306a36Sopenharmony_ci skip_start == cxlds->pmem_res.child->start) 52062306a36Sopenharmony_ci skip_end = skip_start - 1; 52162306a36Sopenharmony_ci else 52262306a36Sopenharmony_ci skip_end = start - 1; 52362306a36Sopenharmony_ci skip = skip_end - skip_start + 1; 52462306a36Sopenharmony_ci } else { 52562306a36Sopenharmony_ci dev_dbg(dev, "mode not set\n"); 52662306a36Sopenharmony_ci rc = -EINVAL; 52762306a36Sopenharmony_ci goto out; 52862306a36Sopenharmony_ci } 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci if (size > avail) { 53162306a36Sopenharmony_ci dev_dbg(dev, "%pa exceeds available %s capacity: %pa\n", &size, 53262306a36Sopenharmony_ci cxled->mode == CXL_DECODER_RAM ? "ram" : "pmem", 53362306a36Sopenharmony_ci &avail); 53462306a36Sopenharmony_ci rc = -ENOSPC; 53562306a36Sopenharmony_ci goto out; 53662306a36Sopenharmony_ci } 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci rc = __cxl_dpa_reserve(cxled, start, size, skip); 53962306a36Sopenharmony_ciout: 54062306a36Sopenharmony_ci up_write(&cxl_dpa_rwsem); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if (rc) 54362306a36Sopenharmony_ci return rc; 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci return devm_add_action_or_reset(&port->dev, cxl_dpa_release, cxled); 54662306a36Sopenharmony_ci} 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic void cxld_set_interleave(struct cxl_decoder *cxld, u32 *ctrl) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci u16 eig; 55162306a36Sopenharmony_ci u8 eiw; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci /* 55462306a36Sopenharmony_ci * Input validation ensures these warns never fire, but otherwise 55562306a36Sopenharmony_ci * suppress unititalized variable usage warnings. 55662306a36Sopenharmony_ci */ 55762306a36Sopenharmony_ci if (WARN_ONCE(ways_to_eiw(cxld->interleave_ways, &eiw), 55862306a36Sopenharmony_ci "invalid interleave_ways: %d\n", cxld->interleave_ways)) 55962306a36Sopenharmony_ci return; 56062306a36Sopenharmony_ci if (WARN_ONCE(granularity_to_eig(cxld->interleave_granularity, &eig), 56162306a36Sopenharmony_ci "invalid interleave_granularity: %d\n", 56262306a36Sopenharmony_ci cxld->interleave_granularity)) 56362306a36Sopenharmony_ci return; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci u32p_replace_bits(ctrl, eig, CXL_HDM_DECODER0_CTRL_IG_MASK); 56662306a36Sopenharmony_ci u32p_replace_bits(ctrl, eiw, CXL_HDM_DECODER0_CTRL_IW_MASK); 56762306a36Sopenharmony_ci *ctrl |= CXL_HDM_DECODER0_CTRL_COMMIT; 56862306a36Sopenharmony_ci} 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_cistatic void cxld_set_type(struct cxl_decoder *cxld, u32 *ctrl) 57162306a36Sopenharmony_ci{ 57262306a36Sopenharmony_ci u32p_replace_bits(ctrl, 57362306a36Sopenharmony_ci !!(cxld->target_type == CXL_DECODER_HOSTONLYMEM), 57462306a36Sopenharmony_ci CXL_HDM_DECODER0_CTRL_HOSTONLY); 57562306a36Sopenharmony_ci} 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_cistatic void cxlsd_set_targets(struct cxl_switch_decoder *cxlsd, u64 *tgt) 57862306a36Sopenharmony_ci{ 57962306a36Sopenharmony_ci struct cxl_dport **t = &cxlsd->target[0]; 58062306a36Sopenharmony_ci int ways = cxlsd->cxld.interleave_ways; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci *tgt = FIELD_PREP(GENMASK(7, 0), t[0]->port_id); 58362306a36Sopenharmony_ci if (ways > 1) 58462306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK(15, 8), t[1]->port_id); 58562306a36Sopenharmony_ci if (ways > 2) 58662306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK(23, 16), t[2]->port_id); 58762306a36Sopenharmony_ci if (ways > 3) 58862306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK(31, 24), t[3]->port_id); 58962306a36Sopenharmony_ci if (ways > 4) 59062306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK_ULL(39, 32), t[4]->port_id); 59162306a36Sopenharmony_ci if (ways > 5) 59262306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK_ULL(47, 40), t[5]->port_id); 59362306a36Sopenharmony_ci if (ways > 6) 59462306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK_ULL(55, 48), t[6]->port_id); 59562306a36Sopenharmony_ci if (ways > 7) 59662306a36Sopenharmony_ci *tgt |= FIELD_PREP(GENMASK_ULL(63, 56), t[7]->port_id); 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci/* 60062306a36Sopenharmony_ci * Per CXL 2.0 8.2.5.12.20 Committing Decoder Programming, hardware must set 60162306a36Sopenharmony_ci * committed or error within 10ms, but just be generous with 20ms to account for 60262306a36Sopenharmony_ci * clock skew and other marginal behavior 60362306a36Sopenharmony_ci */ 60462306a36Sopenharmony_ci#define COMMIT_TIMEOUT_MS 20 60562306a36Sopenharmony_cistatic int cxld_await_commit(void __iomem *hdm, int id) 60662306a36Sopenharmony_ci{ 60762306a36Sopenharmony_ci u32 ctrl; 60862306a36Sopenharmony_ci int i; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci for (i = 0; i < COMMIT_TIMEOUT_MS; i++) { 61162306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id)); 61262306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMIT_ERROR, ctrl)) { 61362306a36Sopenharmony_ci ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT; 61462306a36Sopenharmony_ci writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id)); 61562306a36Sopenharmony_ci return -EIO; 61662306a36Sopenharmony_ci } 61762306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl)) 61862306a36Sopenharmony_ci return 0; 61962306a36Sopenharmony_ci fsleep(1000); 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci return -ETIMEDOUT; 62362306a36Sopenharmony_ci} 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_cistatic int cxl_decoder_commit(struct cxl_decoder *cxld) 62662306a36Sopenharmony_ci{ 62762306a36Sopenharmony_ci struct cxl_port *port = to_cxl_port(cxld->dev.parent); 62862306a36Sopenharmony_ci struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev); 62962306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 63062306a36Sopenharmony_ci int id = cxld->id, rc; 63162306a36Sopenharmony_ci u64 base, size; 63262306a36Sopenharmony_ci u32 ctrl; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci if (cxld->flags & CXL_DECODER_F_ENABLE) 63562306a36Sopenharmony_ci return 0; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci if (cxl_num_decoders_committed(port) != id) { 63862306a36Sopenharmony_ci dev_dbg(&port->dev, 63962306a36Sopenharmony_ci "%s: out of order commit, expected decoder%d.%d\n", 64062306a36Sopenharmony_ci dev_name(&cxld->dev), port->id, 64162306a36Sopenharmony_ci cxl_num_decoders_committed(port)); 64262306a36Sopenharmony_ci return -EBUSY; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci /* 64662306a36Sopenharmony_ci * For endpoint decoders hosted on CXL memory devices that 64762306a36Sopenharmony_ci * support the sanitize operation, make sure sanitize is not in-flight. 64862306a36Sopenharmony_ci */ 64962306a36Sopenharmony_ci if (is_endpoint_decoder(&cxld->dev)) { 65062306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled = 65162306a36Sopenharmony_ci to_cxl_endpoint_decoder(&cxld->dev); 65262306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 65362306a36Sopenharmony_ci struct cxl_memdev_state *mds = 65462306a36Sopenharmony_ci to_cxl_memdev_state(cxlmd->cxlds); 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci if (mds && mds->security.sanitize_active) { 65762306a36Sopenharmony_ci dev_dbg(&cxlmd->dev, 65862306a36Sopenharmony_ci "attempted to commit %s during sanitize\n", 65962306a36Sopenharmony_ci dev_name(&cxld->dev)); 66062306a36Sopenharmony_ci return -EBUSY; 66162306a36Sopenharmony_ci } 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci down_read(&cxl_dpa_rwsem); 66562306a36Sopenharmony_ci /* common decoder settings */ 66662306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id)); 66762306a36Sopenharmony_ci cxld_set_interleave(cxld, &ctrl); 66862306a36Sopenharmony_ci cxld_set_type(cxld, &ctrl); 66962306a36Sopenharmony_ci base = cxld->hpa_range.start; 67062306a36Sopenharmony_ci size = range_len(&cxld->hpa_range); 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci writel(upper_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id)); 67362306a36Sopenharmony_ci writel(lower_32_bits(base), hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id)); 67462306a36Sopenharmony_ci writel(upper_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id)); 67562306a36Sopenharmony_ci writel(lower_32_bits(size), hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id)); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci if (is_switch_decoder(&cxld->dev)) { 67862306a36Sopenharmony_ci struct cxl_switch_decoder *cxlsd = 67962306a36Sopenharmony_ci to_cxl_switch_decoder(&cxld->dev); 68062306a36Sopenharmony_ci void __iomem *tl_hi = hdm + CXL_HDM_DECODER0_TL_HIGH(id); 68162306a36Sopenharmony_ci void __iomem *tl_lo = hdm + CXL_HDM_DECODER0_TL_LOW(id); 68262306a36Sopenharmony_ci u64 targets; 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci cxlsd_set_targets(cxlsd, &targets); 68562306a36Sopenharmony_ci writel(upper_32_bits(targets), tl_hi); 68662306a36Sopenharmony_ci writel(lower_32_bits(targets), tl_lo); 68762306a36Sopenharmony_ci } else { 68862306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled = 68962306a36Sopenharmony_ci to_cxl_endpoint_decoder(&cxld->dev); 69062306a36Sopenharmony_ci void __iomem *sk_hi = hdm + CXL_HDM_DECODER0_SKIP_HIGH(id); 69162306a36Sopenharmony_ci void __iomem *sk_lo = hdm + CXL_HDM_DECODER0_SKIP_LOW(id); 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci writel(upper_32_bits(cxled->skip), sk_hi); 69462306a36Sopenharmony_ci writel(lower_32_bits(cxled->skip), sk_lo); 69562306a36Sopenharmony_ci } 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id)); 69862306a36Sopenharmony_ci up_read(&cxl_dpa_rwsem); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci port->commit_end++; 70162306a36Sopenharmony_ci rc = cxld_await_commit(hdm, cxld->id); 70262306a36Sopenharmony_ci if (rc) { 70362306a36Sopenharmony_ci dev_dbg(&port->dev, "%s: error %d committing decoder\n", 70462306a36Sopenharmony_ci dev_name(&cxld->dev), rc); 70562306a36Sopenharmony_ci cxld->reset(cxld); 70662306a36Sopenharmony_ci return rc; 70762306a36Sopenharmony_ci } 70862306a36Sopenharmony_ci cxld->flags |= CXL_DECODER_F_ENABLE; 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci return 0; 71162306a36Sopenharmony_ci} 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_cistatic int cxl_decoder_reset(struct cxl_decoder *cxld) 71462306a36Sopenharmony_ci{ 71562306a36Sopenharmony_ci struct cxl_port *port = to_cxl_port(cxld->dev.parent); 71662306a36Sopenharmony_ci struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev); 71762306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 71862306a36Sopenharmony_ci int id = cxld->id; 71962306a36Sopenharmony_ci u32 ctrl; 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0) 72262306a36Sopenharmony_ci return 0; 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci if (port->commit_end != id) { 72562306a36Sopenharmony_ci dev_dbg(&port->dev, 72662306a36Sopenharmony_ci "%s: out of order reset, expected decoder%d.%d\n", 72762306a36Sopenharmony_ci dev_name(&cxld->dev), port->id, port->commit_end); 72862306a36Sopenharmony_ci return -EBUSY; 72962306a36Sopenharmony_ci } 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci down_read(&cxl_dpa_rwsem); 73262306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id)); 73362306a36Sopenharmony_ci ctrl &= ~CXL_HDM_DECODER0_CTRL_COMMIT; 73462306a36Sopenharmony_ci writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id)); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci writel(0, hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(id)); 73762306a36Sopenharmony_ci writel(0, hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(id)); 73862306a36Sopenharmony_ci writel(0, hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(id)); 73962306a36Sopenharmony_ci writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id)); 74062306a36Sopenharmony_ci up_read(&cxl_dpa_rwsem); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci port->commit_end--; 74362306a36Sopenharmony_ci cxld->flags &= ~CXL_DECODER_F_ENABLE; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci /* Userspace is now responsible for reconfiguring this decoder */ 74662306a36Sopenharmony_ci if (is_endpoint_decoder(&cxld->dev)) { 74762306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled; 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci cxled = to_cxl_endpoint_decoder(&cxld->dev); 75062306a36Sopenharmony_ci cxled->state = CXL_DECODER_STATE_MANUAL; 75162306a36Sopenharmony_ci } 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci return 0; 75462306a36Sopenharmony_ci} 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_cistatic int cxl_setup_hdm_decoder_from_dvsec( 75762306a36Sopenharmony_ci struct cxl_port *port, struct cxl_decoder *cxld, u64 *dpa_base, 75862306a36Sopenharmony_ci int which, struct cxl_endpoint_dvsec_info *info) 75962306a36Sopenharmony_ci{ 76062306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled; 76162306a36Sopenharmony_ci u64 len; 76262306a36Sopenharmony_ci int rc; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci if (!is_cxl_endpoint(port)) 76562306a36Sopenharmony_ci return -EOPNOTSUPP; 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci cxled = to_cxl_endpoint_decoder(&cxld->dev); 76862306a36Sopenharmony_ci len = range_len(&info->dvsec_range[which]); 76962306a36Sopenharmony_ci if (!len) 77062306a36Sopenharmony_ci return -ENOENT; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_HOSTONLYMEM; 77362306a36Sopenharmony_ci cxld->commit = NULL; 77462306a36Sopenharmony_ci cxld->reset = NULL; 77562306a36Sopenharmony_ci cxld->hpa_range = info->dvsec_range[which]; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci /* 77862306a36Sopenharmony_ci * Set the emulated decoder as locked pending additional support to 77962306a36Sopenharmony_ci * change the range registers at run time. 78062306a36Sopenharmony_ci */ 78162306a36Sopenharmony_ci cxld->flags |= CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK; 78262306a36Sopenharmony_ci port->commit_end = cxld->id; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci rc = devm_cxl_dpa_reserve(cxled, *dpa_base, len, 0); 78562306a36Sopenharmony_ci if (rc) { 78662306a36Sopenharmony_ci dev_err(&port->dev, 78762306a36Sopenharmony_ci "decoder%d.%d: Failed to reserve DPA range %#llx - %#llx\n (%d)", 78862306a36Sopenharmony_ci port->id, cxld->id, *dpa_base, *dpa_base + len - 1, rc); 78962306a36Sopenharmony_ci return rc; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci *dpa_base += len; 79262306a36Sopenharmony_ci cxled->state = CXL_DECODER_STATE_AUTO; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci return 0; 79562306a36Sopenharmony_ci} 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_cistatic int init_hdm_decoder(struct cxl_port *port, struct cxl_decoder *cxld, 79862306a36Sopenharmony_ci int *target_map, void __iomem *hdm, int which, 79962306a36Sopenharmony_ci u64 *dpa_base, struct cxl_endpoint_dvsec_info *info) 80062306a36Sopenharmony_ci{ 80162306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled = NULL; 80262306a36Sopenharmony_ci u64 size, base, skip, dpa_size, lo, hi; 80362306a36Sopenharmony_ci bool committed; 80462306a36Sopenharmony_ci u32 remainder; 80562306a36Sopenharmony_ci int i, rc; 80662306a36Sopenharmony_ci u32 ctrl; 80762306a36Sopenharmony_ci union { 80862306a36Sopenharmony_ci u64 value; 80962306a36Sopenharmony_ci unsigned char target_id[8]; 81062306a36Sopenharmony_ci } target_list; 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci if (should_emulate_decoders(info)) 81362306a36Sopenharmony_ci return cxl_setup_hdm_decoder_from_dvsec(port, cxld, dpa_base, 81462306a36Sopenharmony_ci which, info); 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which)); 81762306a36Sopenharmony_ci lo = readl(hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(which)); 81862306a36Sopenharmony_ci hi = readl(hdm + CXL_HDM_DECODER0_BASE_HIGH_OFFSET(which)); 81962306a36Sopenharmony_ci base = (hi << 32) + lo; 82062306a36Sopenharmony_ci lo = readl(hdm + CXL_HDM_DECODER0_SIZE_LOW_OFFSET(which)); 82162306a36Sopenharmony_ci hi = readl(hdm + CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(which)); 82262306a36Sopenharmony_ci size = (hi << 32) + lo; 82362306a36Sopenharmony_ci committed = !!(ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED); 82462306a36Sopenharmony_ci cxld->commit = cxl_decoder_commit; 82562306a36Sopenharmony_ci cxld->reset = cxl_decoder_reset; 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci if (!committed) 82862306a36Sopenharmony_ci size = 0; 82962306a36Sopenharmony_ci if (base == U64_MAX || size == U64_MAX) { 83062306a36Sopenharmony_ci dev_warn(&port->dev, "decoder%d.%d: Invalid resource range\n", 83162306a36Sopenharmony_ci port->id, cxld->id); 83262306a36Sopenharmony_ci return -ENXIO; 83362306a36Sopenharmony_ci } 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci if (info) 83662306a36Sopenharmony_ci cxled = to_cxl_endpoint_decoder(&cxld->dev); 83762306a36Sopenharmony_ci cxld->hpa_range = (struct range) { 83862306a36Sopenharmony_ci .start = base, 83962306a36Sopenharmony_ci .end = base + size - 1, 84062306a36Sopenharmony_ci }; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci /* decoders are enabled if committed */ 84362306a36Sopenharmony_ci if (committed) { 84462306a36Sopenharmony_ci cxld->flags |= CXL_DECODER_F_ENABLE; 84562306a36Sopenharmony_ci if (ctrl & CXL_HDM_DECODER0_CTRL_LOCK) 84662306a36Sopenharmony_ci cxld->flags |= CXL_DECODER_F_LOCK; 84762306a36Sopenharmony_ci if (FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl)) 84862306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_HOSTONLYMEM; 84962306a36Sopenharmony_ci else 85062306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_DEVMEM; 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci guard(rwsem_write)(&cxl_region_rwsem); 85362306a36Sopenharmony_ci if (cxld->id != cxl_num_decoders_committed(port)) { 85462306a36Sopenharmony_ci dev_warn(&port->dev, 85562306a36Sopenharmony_ci "decoder%d.%d: Committed out of order\n", 85662306a36Sopenharmony_ci port->id, cxld->id); 85762306a36Sopenharmony_ci return -ENXIO; 85862306a36Sopenharmony_ci } 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci if (size == 0) { 86162306a36Sopenharmony_ci dev_warn(&port->dev, 86262306a36Sopenharmony_ci "decoder%d.%d: Committed with zero size\n", 86362306a36Sopenharmony_ci port->id, cxld->id); 86462306a36Sopenharmony_ci return -ENXIO; 86562306a36Sopenharmony_ci } 86662306a36Sopenharmony_ci port->commit_end = cxld->id; 86762306a36Sopenharmony_ci } else { 86862306a36Sopenharmony_ci if (cxled) { 86962306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxled_to_memdev(cxled); 87062306a36Sopenharmony_ci struct cxl_dev_state *cxlds = cxlmd->cxlds; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci /* 87362306a36Sopenharmony_ci * Default by devtype until a device arrives that needs 87462306a36Sopenharmony_ci * more precision. 87562306a36Sopenharmony_ci */ 87662306a36Sopenharmony_ci if (cxlds->type == CXL_DEVTYPE_CLASSMEM) 87762306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_HOSTONLYMEM; 87862306a36Sopenharmony_ci else 87962306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_DEVMEM; 88062306a36Sopenharmony_ci } else { 88162306a36Sopenharmony_ci /* To be overridden by region type at commit time */ 88262306a36Sopenharmony_ci cxld->target_type = CXL_DECODER_HOSTONLYMEM; 88362306a36Sopenharmony_ci } 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci if (!FIELD_GET(CXL_HDM_DECODER0_CTRL_HOSTONLY, ctrl) && 88662306a36Sopenharmony_ci cxld->target_type == CXL_DECODER_HOSTONLYMEM) { 88762306a36Sopenharmony_ci ctrl |= CXL_HDM_DECODER0_CTRL_HOSTONLY; 88862306a36Sopenharmony_ci writel(ctrl, hdm + CXL_HDM_DECODER0_CTRL_OFFSET(which)); 88962306a36Sopenharmony_ci } 89062306a36Sopenharmony_ci } 89162306a36Sopenharmony_ci rc = eiw_to_ways(FIELD_GET(CXL_HDM_DECODER0_CTRL_IW_MASK, ctrl), 89262306a36Sopenharmony_ci &cxld->interleave_ways); 89362306a36Sopenharmony_ci if (rc) { 89462306a36Sopenharmony_ci dev_warn(&port->dev, 89562306a36Sopenharmony_ci "decoder%d.%d: Invalid interleave ways (ctrl: %#x)\n", 89662306a36Sopenharmony_ci port->id, cxld->id, ctrl); 89762306a36Sopenharmony_ci return rc; 89862306a36Sopenharmony_ci } 89962306a36Sopenharmony_ci rc = eig_to_granularity(FIELD_GET(CXL_HDM_DECODER0_CTRL_IG_MASK, ctrl), 90062306a36Sopenharmony_ci &cxld->interleave_granularity); 90162306a36Sopenharmony_ci if (rc) 90262306a36Sopenharmony_ci return rc; 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci dev_dbg(&port->dev, "decoder%d.%d: range: %#llx-%#llx iw: %d ig: %d\n", 90562306a36Sopenharmony_ci port->id, cxld->id, cxld->hpa_range.start, cxld->hpa_range.end, 90662306a36Sopenharmony_ci cxld->interleave_ways, cxld->interleave_granularity); 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci if (!cxled) { 90962306a36Sopenharmony_ci lo = readl(hdm + CXL_HDM_DECODER0_TL_LOW(which)); 91062306a36Sopenharmony_ci hi = readl(hdm + CXL_HDM_DECODER0_TL_HIGH(which)); 91162306a36Sopenharmony_ci target_list.value = (hi << 32) + lo; 91262306a36Sopenharmony_ci for (i = 0; i < cxld->interleave_ways; i++) 91362306a36Sopenharmony_ci target_map[i] = target_list.target_id[i]; 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci return 0; 91662306a36Sopenharmony_ci } 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci if (!committed) 91962306a36Sopenharmony_ci return 0; 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci dpa_size = div_u64_rem(size, cxld->interleave_ways, &remainder); 92262306a36Sopenharmony_ci if (remainder) { 92362306a36Sopenharmony_ci dev_err(&port->dev, 92462306a36Sopenharmony_ci "decoder%d.%d: invalid committed configuration size: %#llx ways: %d\n", 92562306a36Sopenharmony_ci port->id, cxld->id, size, cxld->interleave_ways); 92662306a36Sopenharmony_ci return -ENXIO; 92762306a36Sopenharmony_ci } 92862306a36Sopenharmony_ci lo = readl(hdm + CXL_HDM_DECODER0_SKIP_LOW(which)); 92962306a36Sopenharmony_ci hi = readl(hdm + CXL_HDM_DECODER0_SKIP_HIGH(which)); 93062306a36Sopenharmony_ci skip = (hi << 32) + lo; 93162306a36Sopenharmony_ci rc = devm_cxl_dpa_reserve(cxled, *dpa_base + skip, dpa_size, skip); 93262306a36Sopenharmony_ci if (rc) { 93362306a36Sopenharmony_ci dev_err(&port->dev, 93462306a36Sopenharmony_ci "decoder%d.%d: Failed to reserve DPA range %#llx - %#llx\n (%d)", 93562306a36Sopenharmony_ci port->id, cxld->id, *dpa_base, 93662306a36Sopenharmony_ci *dpa_base + dpa_size + skip - 1, rc); 93762306a36Sopenharmony_ci return rc; 93862306a36Sopenharmony_ci } 93962306a36Sopenharmony_ci *dpa_base += dpa_size + skip; 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci cxled->state = CXL_DECODER_STATE_AUTO; 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci return 0; 94462306a36Sopenharmony_ci} 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_cistatic void cxl_settle_decoders(struct cxl_hdm *cxlhdm) 94762306a36Sopenharmony_ci{ 94862306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 94962306a36Sopenharmony_ci int committed, i; 95062306a36Sopenharmony_ci u32 ctrl; 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci if (!hdm) 95362306a36Sopenharmony_ci return; 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci /* 95662306a36Sopenharmony_ci * Since the register resource was recently claimed via request_region() 95762306a36Sopenharmony_ci * be careful about trusting the "not-committed" status until the commit 95862306a36Sopenharmony_ci * timeout has elapsed. The commit timeout is 10ms (CXL 2.0 95962306a36Sopenharmony_ci * 8.2.5.12.20), but double it to be tolerant of any clock skew between 96062306a36Sopenharmony_ci * host and target. 96162306a36Sopenharmony_ci */ 96262306a36Sopenharmony_ci for (i = 0, committed = 0; i < cxlhdm->decoder_count; i++) { 96362306a36Sopenharmony_ci ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(i)); 96462306a36Sopenharmony_ci if (ctrl & CXL_HDM_DECODER0_CTRL_COMMITTED) 96562306a36Sopenharmony_ci committed++; 96662306a36Sopenharmony_ci } 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci /* ensure that future checks of committed can be trusted */ 96962306a36Sopenharmony_ci if (committed != cxlhdm->decoder_count) 97062306a36Sopenharmony_ci msleep(20); 97162306a36Sopenharmony_ci} 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci/** 97462306a36Sopenharmony_ci * devm_cxl_enumerate_decoders - add decoder objects per HDM register set 97562306a36Sopenharmony_ci * @cxlhdm: Structure to populate with HDM capabilities 97662306a36Sopenharmony_ci * @info: cached DVSEC range register info 97762306a36Sopenharmony_ci */ 97862306a36Sopenharmony_ciint devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, 97962306a36Sopenharmony_ci struct cxl_endpoint_dvsec_info *info) 98062306a36Sopenharmony_ci{ 98162306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 98262306a36Sopenharmony_ci struct cxl_port *port = cxlhdm->port; 98362306a36Sopenharmony_ci int i; 98462306a36Sopenharmony_ci u64 dpa_base = 0; 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci cxl_settle_decoders(cxlhdm); 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci for (i = 0; i < cxlhdm->decoder_count; i++) { 98962306a36Sopenharmony_ci int target_map[CXL_DECODER_MAX_INTERLEAVE] = { 0 }; 99062306a36Sopenharmony_ci int rc, target_count = cxlhdm->target_count; 99162306a36Sopenharmony_ci struct cxl_decoder *cxld; 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci if (is_cxl_endpoint(port)) { 99462306a36Sopenharmony_ci struct cxl_endpoint_decoder *cxled; 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci cxled = cxl_endpoint_decoder_alloc(port); 99762306a36Sopenharmony_ci if (IS_ERR(cxled)) { 99862306a36Sopenharmony_ci dev_warn(&port->dev, 99962306a36Sopenharmony_ci "Failed to allocate decoder%d.%d\n", 100062306a36Sopenharmony_ci port->id, i); 100162306a36Sopenharmony_ci return PTR_ERR(cxled); 100262306a36Sopenharmony_ci } 100362306a36Sopenharmony_ci cxld = &cxled->cxld; 100462306a36Sopenharmony_ci } else { 100562306a36Sopenharmony_ci struct cxl_switch_decoder *cxlsd; 100662306a36Sopenharmony_ci 100762306a36Sopenharmony_ci cxlsd = cxl_switch_decoder_alloc(port, target_count); 100862306a36Sopenharmony_ci if (IS_ERR(cxlsd)) { 100962306a36Sopenharmony_ci dev_warn(&port->dev, 101062306a36Sopenharmony_ci "Failed to allocate decoder%d.%d\n", 101162306a36Sopenharmony_ci port->id, i); 101262306a36Sopenharmony_ci return PTR_ERR(cxlsd); 101362306a36Sopenharmony_ci } 101462306a36Sopenharmony_ci cxld = &cxlsd->cxld; 101562306a36Sopenharmony_ci } 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_ci rc = init_hdm_decoder(port, cxld, target_map, hdm, i, 101862306a36Sopenharmony_ci &dpa_base, info); 101962306a36Sopenharmony_ci if (rc) { 102062306a36Sopenharmony_ci dev_warn(&port->dev, 102162306a36Sopenharmony_ci "Failed to initialize decoder%d.%d\n", 102262306a36Sopenharmony_ci port->id, i); 102362306a36Sopenharmony_ci put_device(&cxld->dev); 102462306a36Sopenharmony_ci return rc; 102562306a36Sopenharmony_ci } 102662306a36Sopenharmony_ci rc = add_hdm_decoder(port, cxld, target_map); 102762306a36Sopenharmony_ci if (rc) { 102862306a36Sopenharmony_ci dev_warn(&port->dev, 102962306a36Sopenharmony_ci "Failed to add decoder%d.%d\n", port->id, i); 103062306a36Sopenharmony_ci return rc; 103162306a36Sopenharmony_ci } 103262306a36Sopenharmony_ci } 103362306a36Sopenharmony_ci 103462306a36Sopenharmony_ci return 0; 103562306a36Sopenharmony_ci} 103662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_decoders, CXL); 1037