162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Copyright(c) 2021 Intel Corporation. All rights reserved. */ 362306a36Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h> 462306a36Sopenharmony_ci#include <linux/device.h> 562306a36Sopenharmony_ci#include <linux/delay.h> 662306a36Sopenharmony_ci#include <linux/pci.h> 762306a36Sopenharmony_ci#include <linux/pci-doe.h> 862306a36Sopenharmony_ci#include <cxlpci.h> 962306a36Sopenharmony_ci#include <cxlmem.h> 1062306a36Sopenharmony_ci#include <cxl.h> 1162306a36Sopenharmony_ci#include "core.h" 1262306a36Sopenharmony_ci#include "trace.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/** 1562306a36Sopenharmony_ci * DOC: cxl core pci 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Compute Express Link protocols are layered on top of PCIe. CXL core provides 1862306a36Sopenharmony_ci * a set of helpers for CXL interactions which occur via PCIe. 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic unsigned short media_ready_timeout = 60; 2262306a36Sopenharmony_cimodule_param(media_ready_timeout, ushort, 0644); 2362306a36Sopenharmony_ciMODULE_PARM_DESC(media_ready_timeout, "seconds to wait for media ready"); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct cxl_walk_context { 2662306a36Sopenharmony_ci struct pci_bus *bus; 2762306a36Sopenharmony_ci struct cxl_port *port; 2862306a36Sopenharmony_ci int type; 2962306a36Sopenharmony_ci int error; 3062306a36Sopenharmony_ci int count; 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic int match_add_dports(struct pci_dev *pdev, void *data) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct cxl_walk_context *ctx = data; 3662306a36Sopenharmony_ci struct cxl_port *port = ctx->port; 3762306a36Sopenharmony_ci int type = pci_pcie_type(pdev); 3862306a36Sopenharmony_ci struct cxl_register_map map; 3962306a36Sopenharmony_ci struct cxl_dport *dport; 4062306a36Sopenharmony_ci u32 lnkcap, port_num; 4162306a36Sopenharmony_ci int rc; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci if (pdev->bus != ctx->bus) 4462306a36Sopenharmony_ci return 0; 4562306a36Sopenharmony_ci if (!pci_is_pcie(pdev)) 4662306a36Sopenharmony_ci return 0; 4762306a36Sopenharmony_ci if (type != ctx->type) 4862306a36Sopenharmony_ci return 0; 4962306a36Sopenharmony_ci if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP, 5062306a36Sopenharmony_ci &lnkcap)) 5162306a36Sopenharmony_ci return 0; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map); 5462306a36Sopenharmony_ci if (rc) 5562306a36Sopenharmony_ci dev_dbg(&port->dev, "failed to find component registers\n"); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci port_num = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap); 5862306a36Sopenharmony_ci dport = devm_cxl_add_dport(port, &pdev->dev, port_num, map.resource); 5962306a36Sopenharmony_ci if (IS_ERR(dport)) { 6062306a36Sopenharmony_ci ctx->error = PTR_ERR(dport); 6162306a36Sopenharmony_ci return PTR_ERR(dport); 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci ctx->count++; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return 0; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/** 6962306a36Sopenharmony_ci * devm_cxl_port_enumerate_dports - enumerate downstream ports of the upstream port 7062306a36Sopenharmony_ci * @port: cxl_port whose ->uport_dev is the upstream of dports to be enumerated 7162306a36Sopenharmony_ci * 7262306a36Sopenharmony_ci * Returns a positive number of dports enumerated or a negative error 7362306a36Sopenharmony_ci * code. 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_ciint devm_cxl_port_enumerate_dports(struct cxl_port *port) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct pci_bus *bus = cxl_port_to_pci_bus(port); 7862306a36Sopenharmony_ci struct cxl_walk_context ctx; 7962306a36Sopenharmony_ci int type; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!bus) 8262306a36Sopenharmony_ci return -ENXIO; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (pci_is_root_bus(bus)) 8562306a36Sopenharmony_ci type = PCI_EXP_TYPE_ROOT_PORT; 8662306a36Sopenharmony_ci else 8762306a36Sopenharmony_ci type = PCI_EXP_TYPE_DOWNSTREAM; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci ctx = (struct cxl_walk_context) { 9062306a36Sopenharmony_ci .port = port, 9162306a36Sopenharmony_ci .bus = bus, 9262306a36Sopenharmony_ci .type = type, 9362306a36Sopenharmony_ci }; 9462306a36Sopenharmony_ci pci_walk_bus(bus, match_add_dports, &ctx); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (ctx.count == 0) 9762306a36Sopenharmony_ci return -ENODEV; 9862306a36Sopenharmony_ci if (ctx.error) 9962306a36Sopenharmony_ci return ctx.error; 10062306a36Sopenharmony_ci return ctx.count; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_cxl_port_enumerate_dports, CXL); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic int cxl_dvsec_mem_range_valid(struct cxl_dev_state *cxlds, int id) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(cxlds->dev); 10762306a36Sopenharmony_ci int d = cxlds->cxl_dvsec; 10862306a36Sopenharmony_ci bool valid = false; 10962306a36Sopenharmony_ci int rc, i; 11062306a36Sopenharmony_ci u32 temp; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (id > CXL_DVSEC_RANGE_MAX) 11362306a36Sopenharmony_ci return -EINVAL; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* Check MEM INFO VALID bit first, give up after 1s */ 11662306a36Sopenharmony_ci i = 1; 11762306a36Sopenharmony_ci do { 11862306a36Sopenharmony_ci rc = pci_read_config_dword(pdev, 11962306a36Sopenharmony_ci d + CXL_DVSEC_RANGE_SIZE_LOW(id), 12062306a36Sopenharmony_ci &temp); 12162306a36Sopenharmony_ci if (rc) 12262306a36Sopenharmony_ci return rc; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci valid = FIELD_GET(CXL_DVSEC_MEM_INFO_VALID, temp); 12562306a36Sopenharmony_ci if (valid) 12662306a36Sopenharmony_ci break; 12762306a36Sopenharmony_ci msleep(1000); 12862306a36Sopenharmony_ci } while (i--); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (!valid) { 13162306a36Sopenharmony_ci dev_err(&pdev->dev, 13262306a36Sopenharmony_ci "Timeout awaiting memory range %d valid after 1s.\n", 13362306a36Sopenharmony_ci id); 13462306a36Sopenharmony_ci return -ETIMEDOUT; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int cxl_dvsec_mem_range_active(struct cxl_dev_state *cxlds, int id) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(cxlds->dev); 14362306a36Sopenharmony_ci int d = cxlds->cxl_dvsec; 14462306a36Sopenharmony_ci bool active = false; 14562306a36Sopenharmony_ci int rc, i; 14662306a36Sopenharmony_ci u32 temp; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (id > CXL_DVSEC_RANGE_MAX) 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* Check MEM ACTIVE bit, up to 60s timeout by default */ 15262306a36Sopenharmony_ci for (i = media_ready_timeout; i; i--) { 15362306a36Sopenharmony_ci rc = pci_read_config_dword( 15462306a36Sopenharmony_ci pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(id), &temp); 15562306a36Sopenharmony_ci if (rc) 15662306a36Sopenharmony_ci return rc; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci active = FIELD_GET(CXL_DVSEC_MEM_ACTIVE, temp); 15962306a36Sopenharmony_ci if (active) 16062306a36Sopenharmony_ci break; 16162306a36Sopenharmony_ci msleep(1000); 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (!active) { 16562306a36Sopenharmony_ci dev_err(&pdev->dev, 16662306a36Sopenharmony_ci "timeout awaiting memory active after %d seconds\n", 16762306a36Sopenharmony_ci media_ready_timeout); 16862306a36Sopenharmony_ci return -ETIMEDOUT; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci/* 17562306a36Sopenharmony_ci * Wait up to @media_ready_timeout for the device to report memory 17662306a36Sopenharmony_ci * active. 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_ciint cxl_await_media_ready(struct cxl_dev_state *cxlds) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(cxlds->dev); 18162306a36Sopenharmony_ci int d = cxlds->cxl_dvsec; 18262306a36Sopenharmony_ci int rc, i, hdm_count; 18362306a36Sopenharmony_ci u64 md_status; 18462306a36Sopenharmony_ci u16 cap; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci rc = pci_read_config_word(pdev, 18762306a36Sopenharmony_ci d + CXL_DVSEC_CAP_OFFSET, &cap); 18862306a36Sopenharmony_ci if (rc) 18962306a36Sopenharmony_ci return rc; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap); 19262306a36Sopenharmony_ci for (i = 0; i < hdm_count; i++) { 19362306a36Sopenharmony_ci rc = cxl_dvsec_mem_range_valid(cxlds, i); 19462306a36Sopenharmony_ci if (rc) 19562306a36Sopenharmony_ci return rc; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci for (i = 0; i < hdm_count; i++) { 19962306a36Sopenharmony_ci rc = cxl_dvsec_mem_range_active(cxlds, i); 20062306a36Sopenharmony_ci if (rc) 20162306a36Sopenharmony_ci return rc; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET); 20562306a36Sopenharmony_ci if (!CXLMDEV_READY(md_status)) 20662306a36Sopenharmony_ci return -EIO; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci return 0; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, CXL); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int wait_for_valid(struct pci_dev *pdev, int d) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci u32 val; 21562306a36Sopenharmony_ci int rc; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* 21862306a36Sopenharmony_ci * Memory_Info_Valid: When set, indicates that the CXL Range 1 Size high 21962306a36Sopenharmony_ci * and Size Low registers are valid. Must be set within 1 second of 22062306a36Sopenharmony_ci * deassertion of reset to CXL device. Likely it is already set by the 22162306a36Sopenharmony_ci * time this runs, but otherwise give a 1.5 second timeout in case of 22262306a36Sopenharmony_ci * clock skew. 22362306a36Sopenharmony_ci */ 22462306a36Sopenharmony_ci rc = pci_read_config_dword(pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(0), &val); 22562306a36Sopenharmony_ci if (rc) 22662306a36Sopenharmony_ci return rc; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (val & CXL_DVSEC_MEM_INFO_VALID) 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci msleep(1500); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci rc = pci_read_config_dword(pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(0), &val); 23462306a36Sopenharmony_ci if (rc) 23562306a36Sopenharmony_ci return rc; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (val & CXL_DVSEC_MEM_INFO_VALID) 23862306a36Sopenharmony_ci return 0; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci return -ETIMEDOUT; 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(cxlds->dev); 24662306a36Sopenharmony_ci int d = cxlds->cxl_dvsec; 24762306a36Sopenharmony_ci u16 ctrl; 24862306a36Sopenharmony_ci int rc; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl); 25162306a36Sopenharmony_ci if (rc < 0) 25262306a36Sopenharmony_ci return rc; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if ((ctrl & CXL_DVSEC_MEM_ENABLE) == val) 25562306a36Sopenharmony_ci return 1; 25662306a36Sopenharmony_ci ctrl &= ~CXL_DVSEC_MEM_ENABLE; 25762306a36Sopenharmony_ci ctrl |= val; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci rc = pci_write_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, ctrl); 26062306a36Sopenharmony_ci if (rc < 0) 26162306a36Sopenharmony_ci return rc; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic void clear_mem_enable(void *cxlds) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci cxl_set_mem_enable(cxlds, 0); 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic int devm_cxl_enable_mem(struct device *host, struct cxl_dev_state *cxlds) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci int rc; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci rc = cxl_set_mem_enable(cxlds, CXL_DVSEC_MEM_ENABLE); 27662306a36Sopenharmony_ci if (rc < 0) 27762306a36Sopenharmony_ci return rc; 27862306a36Sopenharmony_ci if (rc > 0) 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci return devm_add_action_or_reset(host, clear_mem_enable, cxlds); 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci/* require dvsec ranges to be covered by a locked platform window */ 28462306a36Sopenharmony_cistatic int dvsec_range_allowed(struct device *dev, void *arg) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct range *dev_range = arg; 28762306a36Sopenharmony_ci struct cxl_decoder *cxld; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (!is_root_decoder(dev)) 29062306a36Sopenharmony_ci return 0; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci cxld = to_cxl_decoder(dev); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (!(cxld->flags & CXL_DECODER_F_RAM)) 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci return range_contains(&cxld->hpa_range, dev_range); 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic void disable_hdm(void *_cxlhdm) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci u32 global_ctrl; 30362306a36Sopenharmony_ci struct cxl_hdm *cxlhdm = _cxlhdm; 30462306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET); 30762306a36Sopenharmony_ci writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE, 30862306a36Sopenharmony_ci hdm + CXL_HDM_DECODER_CTRL_OFFSET); 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic int devm_cxl_enable_hdm(struct device *host, struct cxl_hdm *cxlhdm) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 31462306a36Sopenharmony_ci u32 global_ctrl; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET); 31762306a36Sopenharmony_ci writel(global_ctrl | CXL_HDM_DECODER_ENABLE, 31862306a36Sopenharmony_ci hdm + CXL_HDM_DECODER_CTRL_OFFSET); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return devm_add_action_or_reset(host, disable_hdm, cxlhdm); 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ciint cxl_dvsec_rr_decode(struct device *dev, int d, 32462306a36Sopenharmony_ci struct cxl_endpoint_dvsec_info *info) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci struct pci_dev *pdev = to_pci_dev(dev); 32762306a36Sopenharmony_ci int hdm_count, rc, i, ranges = 0; 32862306a36Sopenharmony_ci u16 cap, ctrl; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci if (!d) { 33162306a36Sopenharmony_ci dev_dbg(dev, "No DVSEC Capability\n"); 33262306a36Sopenharmony_ci return -ENXIO; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci rc = pci_read_config_word(pdev, d + CXL_DVSEC_CAP_OFFSET, &cap); 33662306a36Sopenharmony_ci if (rc) 33762306a36Sopenharmony_ci return rc; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl); 34062306a36Sopenharmony_ci if (rc) 34162306a36Sopenharmony_ci return rc; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (!(cap & CXL_DVSEC_MEM_CAPABLE)) { 34462306a36Sopenharmony_ci dev_dbg(dev, "Not MEM Capable\n"); 34562306a36Sopenharmony_ci return -ENXIO; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci /* 34962306a36Sopenharmony_ci * It is not allowed by spec for MEM.capable to be set and have 0 legacy 35062306a36Sopenharmony_ci * HDM decoders (values > 2 are also undefined as of CXL 2.0). As this 35162306a36Sopenharmony_ci * driver is for a spec defined class code which must be CXL.mem 35262306a36Sopenharmony_ci * capable, there is no point in continuing to enable CXL.mem. 35362306a36Sopenharmony_ci */ 35462306a36Sopenharmony_ci hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap); 35562306a36Sopenharmony_ci if (!hdm_count || hdm_count > 2) 35662306a36Sopenharmony_ci return -EINVAL; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci rc = wait_for_valid(pdev, d); 35962306a36Sopenharmony_ci if (rc) { 36062306a36Sopenharmony_ci dev_dbg(dev, "Failure awaiting MEM_INFO_VALID (%d)\n", rc); 36162306a36Sopenharmony_ci return rc; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* 36562306a36Sopenharmony_ci * The current DVSEC values are moot if the memory capability is 36662306a36Sopenharmony_ci * disabled, and they will remain moot after the HDM Decoder 36762306a36Sopenharmony_ci * capability is enabled. 36862306a36Sopenharmony_ci */ 36962306a36Sopenharmony_ci info->mem_enabled = FIELD_GET(CXL_DVSEC_MEM_ENABLE, ctrl); 37062306a36Sopenharmony_ci if (!info->mem_enabled) 37162306a36Sopenharmony_ci return 0; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci for (i = 0; i < hdm_count; i++) { 37462306a36Sopenharmony_ci u64 base, size; 37562306a36Sopenharmony_ci u32 temp; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci rc = pci_read_config_dword( 37862306a36Sopenharmony_ci pdev, d + CXL_DVSEC_RANGE_SIZE_HIGH(i), &temp); 37962306a36Sopenharmony_ci if (rc) 38062306a36Sopenharmony_ci return rc; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci size = (u64)temp << 32; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci rc = pci_read_config_dword( 38562306a36Sopenharmony_ci pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(i), &temp); 38662306a36Sopenharmony_ci if (rc) 38762306a36Sopenharmony_ci return rc; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci size |= temp & CXL_DVSEC_MEM_SIZE_LOW_MASK; 39062306a36Sopenharmony_ci if (!size) { 39162306a36Sopenharmony_ci info->dvsec_range[i] = (struct range) { 39262306a36Sopenharmony_ci .start = 0, 39362306a36Sopenharmony_ci .end = CXL_RESOURCE_NONE, 39462306a36Sopenharmony_ci }; 39562306a36Sopenharmony_ci continue; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci rc = pci_read_config_dword( 39962306a36Sopenharmony_ci pdev, d + CXL_DVSEC_RANGE_BASE_HIGH(i), &temp); 40062306a36Sopenharmony_ci if (rc) 40162306a36Sopenharmony_ci return rc; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci base = (u64)temp << 32; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci rc = pci_read_config_dword( 40662306a36Sopenharmony_ci pdev, d + CXL_DVSEC_RANGE_BASE_LOW(i), &temp); 40762306a36Sopenharmony_ci if (rc) 40862306a36Sopenharmony_ci return rc; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci base |= temp & CXL_DVSEC_MEM_BASE_LOW_MASK; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci info->dvsec_range[i] = (struct range) { 41362306a36Sopenharmony_ci .start = base, 41462306a36Sopenharmony_ci .end = base + size - 1 41562306a36Sopenharmony_ci }; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci ranges++; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci info->ranges = ranges; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci return 0; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_dvsec_rr_decode, CXL); 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci/** 42762306a36Sopenharmony_ci * cxl_hdm_decode_init() - Setup HDM decoding for the endpoint 42862306a36Sopenharmony_ci * @cxlds: Device state 42962306a36Sopenharmony_ci * @cxlhdm: Mapped HDM decoder Capability 43062306a36Sopenharmony_ci * @info: Cached DVSEC range registers info 43162306a36Sopenharmony_ci * 43262306a36Sopenharmony_ci * Try to enable the endpoint's HDM Decoder Capability 43362306a36Sopenharmony_ci */ 43462306a36Sopenharmony_ciint cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm, 43562306a36Sopenharmony_ci struct cxl_endpoint_dvsec_info *info) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci void __iomem *hdm = cxlhdm->regs.hdm_decoder; 43862306a36Sopenharmony_ci struct cxl_port *port = cxlhdm->port; 43962306a36Sopenharmony_ci struct device *dev = cxlds->dev; 44062306a36Sopenharmony_ci struct cxl_port *root; 44162306a36Sopenharmony_ci int i, rc, allowed; 44262306a36Sopenharmony_ci u32 global_ctrl = 0; 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci if (hdm) 44562306a36Sopenharmony_ci global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci /* 44862306a36Sopenharmony_ci * If the HDM Decoder Capability is already enabled then assume 44962306a36Sopenharmony_ci * that some other agent like platform firmware set it up. 45062306a36Sopenharmony_ci */ 45162306a36Sopenharmony_ci if (global_ctrl & CXL_HDM_DECODER_ENABLE || (!hdm && info->mem_enabled)) 45262306a36Sopenharmony_ci return devm_cxl_enable_mem(&port->dev, cxlds); 45362306a36Sopenharmony_ci else if (!hdm) 45462306a36Sopenharmony_ci return -ENODEV; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci root = to_cxl_port(port->dev.parent); 45762306a36Sopenharmony_ci while (!is_cxl_root(root) && is_cxl_port(root->dev.parent)) 45862306a36Sopenharmony_ci root = to_cxl_port(root->dev.parent); 45962306a36Sopenharmony_ci if (!is_cxl_root(root)) { 46062306a36Sopenharmony_ci dev_err(dev, "Failed to acquire root port for HDM enable\n"); 46162306a36Sopenharmony_ci return -ENODEV; 46262306a36Sopenharmony_ci } 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci for (i = 0, allowed = 0; info->mem_enabled && i < info->ranges; i++) { 46562306a36Sopenharmony_ci struct device *cxld_dev; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci cxld_dev = device_find_child(&root->dev, &info->dvsec_range[i], 46862306a36Sopenharmony_ci dvsec_range_allowed); 46962306a36Sopenharmony_ci if (!cxld_dev) { 47062306a36Sopenharmony_ci dev_dbg(dev, "DVSEC Range%d denied by platform\n", i); 47162306a36Sopenharmony_ci continue; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci dev_dbg(dev, "DVSEC Range%d allowed by platform\n", i); 47462306a36Sopenharmony_ci put_device(cxld_dev); 47562306a36Sopenharmony_ci allowed++; 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci if (!allowed && info->mem_enabled) { 47962306a36Sopenharmony_ci dev_err(dev, "Range register decodes outside platform defined CXL ranges.\n"); 48062306a36Sopenharmony_ci return -ENXIO; 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci /* 48462306a36Sopenharmony_ci * Per CXL 2.0 Section 8.1.3.8.3 and 8.1.3.8.4 DVSEC CXL Range 1 Base 48562306a36Sopenharmony_ci * [High,Low] when HDM operation is enabled the range register values 48662306a36Sopenharmony_ci * are ignored by the device, but the spec also recommends matching the 48762306a36Sopenharmony_ci * DVSEC Range 1,2 to HDM Decoder Range 0,1. So, non-zero info->ranges 48862306a36Sopenharmony_ci * are expected even though Linux does not require or maintain that 48962306a36Sopenharmony_ci * match. If at least one DVSEC range is enabled and allowed, skip HDM 49062306a36Sopenharmony_ci * Decoder Capability Enable. 49162306a36Sopenharmony_ci */ 49262306a36Sopenharmony_ci if (info->mem_enabled) 49362306a36Sopenharmony_ci return 0; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci rc = devm_cxl_enable_hdm(&port->dev, cxlhdm); 49662306a36Sopenharmony_ci if (rc) 49762306a36Sopenharmony_ci return rc; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci return devm_cxl_enable_mem(&port->dev, cxlds); 50062306a36Sopenharmony_ci} 50162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, CXL); 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_REQ_CODE 0x000000ff 50462306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_REQ_CODE_READ 0 50562306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE 0x0000ff00 50662306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA 0 50762306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE 0xffff0000 50862306a36Sopenharmony_ci#define CXL_DOE_TABLE_ACCESS_LAST_ENTRY 0xffff 50962306a36Sopenharmony_ci#define CXL_DOE_PROTOCOL_TABLE_ACCESS 2 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci#define CDAT_DOE_REQ(entry_handle) cpu_to_le32 \ 51262306a36Sopenharmony_ci (FIELD_PREP(CXL_DOE_TABLE_ACCESS_REQ_CODE, \ 51362306a36Sopenharmony_ci CXL_DOE_TABLE_ACCESS_REQ_CODE_READ) | \ 51462306a36Sopenharmony_ci FIELD_PREP(CXL_DOE_TABLE_ACCESS_TABLE_TYPE, \ 51562306a36Sopenharmony_ci CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA) | \ 51662306a36Sopenharmony_ci FIELD_PREP(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, (entry_handle))) 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_cistatic int cxl_cdat_get_length(struct device *dev, 51962306a36Sopenharmony_ci struct pci_doe_mb *cdat_doe, 52062306a36Sopenharmony_ci size_t *length) 52162306a36Sopenharmony_ci{ 52262306a36Sopenharmony_ci __le32 request = CDAT_DOE_REQ(0); 52362306a36Sopenharmony_ci __le32 response[2]; 52462306a36Sopenharmony_ci int rc; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci rc = pci_doe(cdat_doe, PCI_DVSEC_VENDOR_ID_CXL, 52762306a36Sopenharmony_ci CXL_DOE_PROTOCOL_TABLE_ACCESS, 52862306a36Sopenharmony_ci &request, sizeof(request), 52962306a36Sopenharmony_ci &response, sizeof(response)); 53062306a36Sopenharmony_ci if (rc < 0) { 53162306a36Sopenharmony_ci dev_err(dev, "DOE failed: %d", rc); 53262306a36Sopenharmony_ci return rc; 53362306a36Sopenharmony_ci } 53462306a36Sopenharmony_ci if (rc < sizeof(response)) 53562306a36Sopenharmony_ci return -EIO; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci *length = le32_to_cpu(response[1]); 53862306a36Sopenharmony_ci dev_dbg(dev, "CDAT length %zu\n", *length); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci return 0; 54162306a36Sopenharmony_ci} 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_cistatic int cxl_cdat_read_table(struct device *dev, 54462306a36Sopenharmony_ci struct pci_doe_mb *cdat_doe, 54562306a36Sopenharmony_ci void *cdat_table, size_t *cdat_length) 54662306a36Sopenharmony_ci{ 54762306a36Sopenharmony_ci size_t length = *cdat_length + sizeof(__le32); 54862306a36Sopenharmony_ci __le32 *data = cdat_table; 54962306a36Sopenharmony_ci int entry_handle = 0; 55062306a36Sopenharmony_ci __le32 saved_dw = 0; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci do { 55362306a36Sopenharmony_ci __le32 request = CDAT_DOE_REQ(entry_handle); 55462306a36Sopenharmony_ci struct cdat_entry_header *entry; 55562306a36Sopenharmony_ci size_t entry_dw; 55662306a36Sopenharmony_ci int rc; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci rc = pci_doe(cdat_doe, PCI_DVSEC_VENDOR_ID_CXL, 55962306a36Sopenharmony_ci CXL_DOE_PROTOCOL_TABLE_ACCESS, 56062306a36Sopenharmony_ci &request, sizeof(request), 56162306a36Sopenharmony_ci data, length); 56262306a36Sopenharmony_ci if (rc < 0) { 56362306a36Sopenharmony_ci dev_err(dev, "DOE failed: %d", rc); 56462306a36Sopenharmony_ci return rc; 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci /* 1 DW Table Access Response Header + CDAT entry */ 56862306a36Sopenharmony_ci entry = (struct cdat_entry_header *)(data + 1); 56962306a36Sopenharmony_ci if ((entry_handle == 0 && 57062306a36Sopenharmony_ci rc != sizeof(__le32) + sizeof(struct cdat_header)) || 57162306a36Sopenharmony_ci (entry_handle > 0 && 57262306a36Sopenharmony_ci (rc < sizeof(__le32) + sizeof(*entry) || 57362306a36Sopenharmony_ci rc != sizeof(__le32) + le16_to_cpu(entry->length)))) 57462306a36Sopenharmony_ci return -EIO; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci /* Get the CXL table access header entry handle */ 57762306a36Sopenharmony_ci entry_handle = FIELD_GET(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, 57862306a36Sopenharmony_ci le32_to_cpu(data[0])); 57962306a36Sopenharmony_ci entry_dw = rc / sizeof(__le32); 58062306a36Sopenharmony_ci /* Skip Header */ 58162306a36Sopenharmony_ci entry_dw -= 1; 58262306a36Sopenharmony_ci /* 58362306a36Sopenharmony_ci * Table Access Response Header overwrote the last DW of 58462306a36Sopenharmony_ci * previous entry, so restore that DW 58562306a36Sopenharmony_ci */ 58662306a36Sopenharmony_ci *data = saved_dw; 58762306a36Sopenharmony_ci length -= entry_dw * sizeof(__le32); 58862306a36Sopenharmony_ci data += entry_dw; 58962306a36Sopenharmony_ci saved_dw = *data; 59062306a36Sopenharmony_ci } while (entry_handle != CXL_DOE_TABLE_ACCESS_LAST_ENTRY); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci /* Length in CDAT header may exceed concatenation of CDAT entries */ 59362306a36Sopenharmony_ci *cdat_length -= length - sizeof(__le32); 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci return 0; 59662306a36Sopenharmony_ci} 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci/** 59962306a36Sopenharmony_ci * read_cdat_data - Read the CDAT data on this port 60062306a36Sopenharmony_ci * @port: Port to read data from 60162306a36Sopenharmony_ci * 60262306a36Sopenharmony_ci * This call will sleep waiting for responses from the DOE mailbox. 60362306a36Sopenharmony_ci */ 60462306a36Sopenharmony_civoid read_cdat_data(struct cxl_port *port) 60562306a36Sopenharmony_ci{ 60662306a36Sopenharmony_ci struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev); 60762306a36Sopenharmony_ci struct device *host = cxlmd->dev.parent; 60862306a36Sopenharmony_ci struct device *dev = &port->dev; 60962306a36Sopenharmony_ci struct pci_doe_mb *cdat_doe; 61062306a36Sopenharmony_ci size_t cdat_length; 61162306a36Sopenharmony_ci void *cdat_table; 61262306a36Sopenharmony_ci int rc; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci if (!dev_is_pci(host)) 61562306a36Sopenharmony_ci return; 61662306a36Sopenharmony_ci cdat_doe = pci_find_doe_mailbox(to_pci_dev(host), 61762306a36Sopenharmony_ci PCI_DVSEC_VENDOR_ID_CXL, 61862306a36Sopenharmony_ci CXL_DOE_PROTOCOL_TABLE_ACCESS); 61962306a36Sopenharmony_ci if (!cdat_doe) { 62062306a36Sopenharmony_ci dev_dbg(dev, "No CDAT mailbox\n"); 62162306a36Sopenharmony_ci return; 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci port->cdat_available = true; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci if (cxl_cdat_get_length(dev, cdat_doe, &cdat_length)) { 62762306a36Sopenharmony_ci dev_dbg(dev, "No CDAT length\n"); 62862306a36Sopenharmony_ci return; 62962306a36Sopenharmony_ci } 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci cdat_table = devm_kzalloc(dev, cdat_length + sizeof(__le32), 63262306a36Sopenharmony_ci GFP_KERNEL); 63362306a36Sopenharmony_ci if (!cdat_table) 63462306a36Sopenharmony_ci return; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci rc = cxl_cdat_read_table(dev, cdat_doe, cdat_table, &cdat_length); 63762306a36Sopenharmony_ci if (rc) { 63862306a36Sopenharmony_ci /* Don't leave table data allocated on error */ 63962306a36Sopenharmony_ci devm_kfree(dev, cdat_table); 64062306a36Sopenharmony_ci dev_err(dev, "CDAT data read error\n"); 64162306a36Sopenharmony_ci return; 64262306a36Sopenharmony_ci } 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci port->cdat.table = cdat_table + sizeof(__le32); 64562306a36Sopenharmony_ci port->cdat.length = cdat_length; 64662306a36Sopenharmony_ci} 64762306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(read_cdat_data, CXL); 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_civoid cxl_cor_error_detected(struct pci_dev *pdev) 65062306a36Sopenharmony_ci{ 65162306a36Sopenharmony_ci struct cxl_dev_state *cxlds = pci_get_drvdata(pdev); 65262306a36Sopenharmony_ci void __iomem *addr; 65362306a36Sopenharmony_ci u32 status; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci if (!cxlds->regs.ras) 65662306a36Sopenharmony_ci return; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci addr = cxlds->regs.ras + CXL_RAS_CORRECTABLE_STATUS_OFFSET; 65962306a36Sopenharmony_ci status = readl(addr); 66062306a36Sopenharmony_ci if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) { 66162306a36Sopenharmony_ci writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr); 66262306a36Sopenharmony_ci trace_cxl_aer_correctable_error(cxlds->cxlmd, status); 66362306a36Sopenharmony_ci } 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, CXL); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci/* CXL spec rev3.0 8.2.4.16.1 */ 66862306a36Sopenharmony_cistatic void header_log_copy(struct cxl_dev_state *cxlds, u32 *log) 66962306a36Sopenharmony_ci{ 67062306a36Sopenharmony_ci void __iomem *addr; 67162306a36Sopenharmony_ci u32 *log_addr; 67262306a36Sopenharmony_ci int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32); 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci addr = cxlds->regs.ras + CXL_RAS_HEADER_LOG_OFFSET; 67562306a36Sopenharmony_ci log_addr = log; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci for (i = 0; i < log_u32_size; i++) { 67862306a36Sopenharmony_ci *log_addr = readl(addr); 67962306a36Sopenharmony_ci log_addr++; 68062306a36Sopenharmony_ci addr += sizeof(u32); 68162306a36Sopenharmony_ci } 68262306a36Sopenharmony_ci} 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci/* 68562306a36Sopenharmony_ci * Log the state of the RAS status registers and prepare them to log the 68662306a36Sopenharmony_ci * next error status. Return 1 if reset needed. 68762306a36Sopenharmony_ci */ 68862306a36Sopenharmony_cistatic bool cxl_report_and_clear(struct cxl_dev_state *cxlds) 68962306a36Sopenharmony_ci{ 69062306a36Sopenharmony_ci u32 hl[CXL_HEADERLOG_SIZE_U32]; 69162306a36Sopenharmony_ci void __iomem *addr; 69262306a36Sopenharmony_ci u32 status; 69362306a36Sopenharmony_ci u32 fe; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (!cxlds->regs.ras) 69662306a36Sopenharmony_ci return false; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci addr = cxlds->regs.ras + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET; 69962306a36Sopenharmony_ci status = readl(addr); 70062306a36Sopenharmony_ci if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK)) 70162306a36Sopenharmony_ci return false; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci /* If multiple errors, log header points to first error from ctrl reg */ 70462306a36Sopenharmony_ci if (hweight32(status) > 1) { 70562306a36Sopenharmony_ci void __iomem *rcc_addr = 70662306a36Sopenharmony_ci cxlds->regs.ras + CXL_RAS_CAP_CONTROL_OFFSET; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK, 70962306a36Sopenharmony_ci readl(rcc_addr))); 71062306a36Sopenharmony_ci } else { 71162306a36Sopenharmony_ci fe = status; 71262306a36Sopenharmony_ci } 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci header_log_copy(cxlds, hl); 71562306a36Sopenharmony_ci trace_cxl_aer_uncorrectable_error(cxlds->cxlmd, status, fe, hl); 71662306a36Sopenharmony_ci writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci return true; 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cipci_ers_result_t cxl_error_detected(struct pci_dev *pdev, 72262306a36Sopenharmony_ci pci_channel_state_t state) 72362306a36Sopenharmony_ci{ 72462306a36Sopenharmony_ci struct cxl_dev_state *cxlds = pci_get_drvdata(pdev); 72562306a36Sopenharmony_ci struct cxl_memdev *cxlmd = cxlds->cxlmd; 72662306a36Sopenharmony_ci struct device *dev = &cxlmd->dev; 72762306a36Sopenharmony_ci bool ue; 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci /* 73062306a36Sopenharmony_ci * A frozen channel indicates an impending reset which is fatal to 73162306a36Sopenharmony_ci * CXL.mem operation, and will likely crash the system. On the off 73262306a36Sopenharmony_ci * chance the situation is recoverable dump the status of the RAS 73362306a36Sopenharmony_ci * capability registers and bounce the active state of the memdev. 73462306a36Sopenharmony_ci */ 73562306a36Sopenharmony_ci ue = cxl_report_and_clear(cxlds); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci switch (state) { 73862306a36Sopenharmony_ci case pci_channel_io_normal: 73962306a36Sopenharmony_ci if (ue) { 74062306a36Sopenharmony_ci device_release_driver(dev); 74162306a36Sopenharmony_ci return PCI_ERS_RESULT_NEED_RESET; 74262306a36Sopenharmony_ci } 74362306a36Sopenharmony_ci return PCI_ERS_RESULT_CAN_RECOVER; 74462306a36Sopenharmony_ci case pci_channel_io_frozen: 74562306a36Sopenharmony_ci dev_warn(&pdev->dev, 74662306a36Sopenharmony_ci "%s: frozen state error detected, disable CXL.mem\n", 74762306a36Sopenharmony_ci dev_name(dev)); 74862306a36Sopenharmony_ci device_release_driver(dev); 74962306a36Sopenharmony_ci return PCI_ERS_RESULT_NEED_RESET; 75062306a36Sopenharmony_ci case pci_channel_io_perm_failure: 75162306a36Sopenharmony_ci dev_warn(&pdev->dev, 75262306a36Sopenharmony_ci "failure state error detected, request disconnect\n"); 75362306a36Sopenharmony_ci return PCI_ERS_RESULT_DISCONNECT; 75462306a36Sopenharmony_ci } 75562306a36Sopenharmony_ci return PCI_ERS_RESULT_NEED_RESET; 75662306a36Sopenharmony_ci} 75762306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(cxl_error_detected, CXL); 758