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