Merge branch 'for-6.18/cxl-poison-inject' into cxl-for-next

Add support to allow expert users to inject and clear poison for the CXL
subsystem by writing a System Physical Address (SPA) to a debugfs file.
This commit is contained in:
Dave Jiang 2025-08-12 16:23:07 -07:00
commit d9412f08e2
9 changed files with 406 additions and 36 deletions

View File

@ -19,6 +19,20 @@ Description:
is returned to the user. The inject_poison attribute is only
visible for devices supporting the capability.
TEST-ONLY INTERFACE: This interface is intended for testing
and validation purposes only. It is not a data repair mechanism
and should never be used on production systems or live data.
DATA LOSS RISK: For CXL persistent memory (PMEM) devices,
poison injection can result in permanent data loss. Injected
poison may render data permanently inaccessible even after
clearing, as the clear operation writes zeros and does not
recover original data.
SYSTEM STABILITY RISK: For volatile memory, poison injection
can cause kernel crashes, system instability, or unpredictable
behavior if the poisoned addresses are accessed by running code
or critical kernel structures.
What: /sys/kernel/debug/cxl/memX/clear_poison
Date: April, 2023
@ -35,6 +49,79 @@ Description:
The clear_poison attribute is only visible for devices
supporting the capability.
TEST-ONLY INTERFACE: This interface is intended for testing
and validation purposes only. It is not a data repair mechanism
and should never be used on production systems or live data.
CLEAR IS NOT DATA RECOVERY: This operation writes zeros to the
specified address range and removes the address from the poison
list. It does NOT recover or restore original data that may have
been present before poison injection. Any original data at the
cleared address is permanently lost and replaced with zeros.
CLEAR IS NOT A REPAIR MECHANISM: This interface is for testing
purposes only and should not be used as a data repair tool.
Clearing poison is fundamentally different from data recovery
or error correction.
What: /sys/kernel/debug/cxl/regionX/inject_poison
Date: August, 2025
Contact: linux-cxl@vger.kernel.org
Description:
(WO) When a Host Physical Address (HPA) is written to this
attribute, the region driver translates it to a Device
Physical Address (DPA) and identifies the corresponding
memdev. It then sends an inject poison command to that memdev
at the translated DPA. Refer to the memdev ABI entry at:
/sys/kernel/debug/cxl/memX/inject_poison for the detailed
behavior. This attribute is only visible if all memdevs
participating in the region support both inject and clear
poison commands.
TEST-ONLY INTERFACE: This interface is intended for testing
and validation purposes only. It is not a data repair mechanism
and should never be used on production systems or live data.
DATA LOSS RISK: For CXL persistent memory (PMEM) devices,
poison injection can result in permanent data loss. Injected
poison may render data permanently inaccessible even after
clearing, as the clear operation writes zeros and does not
recover original data.
SYSTEM STABILITY RISK: For volatile memory, poison injection
can cause kernel crashes, system instability, or unpredictable
behavior if the poisoned addresses are accessed by running code
or critical kernel structures.
What: /sys/kernel/debug/cxl/regionX/clear_poison
Date: August, 2025
Contact: linux-cxl@vger.kernel.org
Description:
(WO) When a Host Physical Address (HPA) is written to this
attribute, the region driver translates it to a Device
Physical Address (DPA) and identifies the corresponding
memdev. It then sends a clear poison command to that memdev
at the translated DPA. Refer to the memdev ABI entry at:
/sys/kernel/debug/cxl/memX/clear_poison for the detailed
behavior. This attribute is only visible if all memdevs
participating in the region support both inject and clear
poison commands.
TEST-ONLY INTERFACE: This interface is intended for testing
and validation purposes only. It is not a data repair mechanism
and should never be used on production systems or live data.
CLEAR IS NOT DATA RECOVERY: This operation writes zeros to the
specified address range and removes the address from the poison
list. It does NOT recover or restore original data that may have
been present before poison injection. Any original data at the
cleared address is permanently lost and replaced with zeros.
CLEAR IS NOT A REPAIR MECHANISM: This interface is for testing
purposes only and should not be used as a data repair tool.
Clearing poison is fundamentally different from data recovery
or error correction.
What: /sys/kernel/debug/cxl/einj_types
Date: January, 2024
KernelVersion: v6.9

View File

@ -173,7 +173,7 @@ Accelerator
User Flow Support
-----------------
* [0] Inject & clear poison by HPA
* [2] Inject & clear poison by region offset
Details
=======

View File

@ -20,8 +20,7 @@ static const guid_t acpi_cxl_qtg_id_guid =
GUID_INIT(0xF365F9A6, 0xA7DE, 0x4071,
0xA6, 0x6A, 0xB4, 0x0C, 0x0B, 0x4F, 0x8E, 0x52);
static u64 cxl_xor_hpa_to_spa(struct cxl_root_decoder *cxlrd, u64 hpa)
static u64 cxl_apply_xor_maps(struct cxl_root_decoder *cxlrd, u64 addr)
{
struct cxl_cxims_data *cximsd = cxlrd->platform_data;
int hbiw = cxlrd->cxlsd.nr_targets;
@ -30,19 +29,23 @@ static u64 cxl_xor_hpa_to_spa(struct cxl_root_decoder *cxlrd, u64 hpa)
/* No xormaps for host bridge interleave ways of 1 or 3 */
if (hbiw == 1 || hbiw == 3)
return hpa;
return addr;
/*
* For root decoders using xormaps (hbiw: 2,4,6,8,12,16) restore
* the position bit to its value before the xormap was applied at
* HPA->DPA translation.
* In regions using XOR interleave arithmetic the CXL HPA may not
* be the same as the SPA. This helper performs the SPA->CXL HPA
* or the CXL HPA->SPA translation. Since XOR is self-inverting,
* so is this function.
*
* For root decoders using xormaps (hbiw: 2,4,6,8,12,16) applying the
* xormaps will toggle a position bit.
*
* pos is the lowest set bit in an XORMAP
* val is the XORALLBITS(HPA & XORMAP)
* val is the XORALLBITS(addr & XORMAP)
*
* XORALLBITS: The CXL spec (3.1 Table 9-22) defines XORALLBITS
* as an operation that outputs a single bit by XORing all the
* bits in the input (hpa & xormap). Implement XORALLBITS using
* bits in the input (addr & xormap). Implement XORALLBITS using
* hweight64(). If the hamming weight is even the XOR of those
* bits results in val==0, if odd the XOR result is val==1.
*/
@ -51,11 +54,11 @@ static u64 cxl_xor_hpa_to_spa(struct cxl_root_decoder *cxlrd, u64 hpa)
if (!cximsd->xormaps[i])
continue;
pos = __ffs(cximsd->xormaps[i]);
val = (hweight64(hpa & cximsd->xormaps[i]) & 1);
hpa = (hpa & ~(1ULL << pos)) | (val << pos);
val = (hweight64(addr & cximsd->xormaps[i]) & 1);
addr = (addr & ~(1ULL << pos)) | (val << pos);
}
return hpa;
return addr;
}
struct cxl_cxims_context {
@ -472,8 +475,14 @@ static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws,
cxlrd->qos_class = cfmws->qtg_id;
if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_XOR)
cxlrd->hpa_to_spa = cxl_xor_hpa_to_spa;
if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_XOR) {
cxlrd->ops = kzalloc(sizeof(*cxlrd->ops), GFP_KERNEL);
if (!cxlrd->ops)
return -ENOMEM;
cxlrd->ops->hpa_to_spa = cxl_apply_xor_maps;
cxlrd->ops->spa_to_hpa = cxl_apply_xor_maps;
}
rc = cxl_decoder_add(cxld, target_map);
if (rc)

View File

@ -135,6 +135,10 @@ enum cxl_poison_trace_type {
CXL_POISON_TRACE_CLEAR,
};
enum poison_cmd_enabled_bits;
bool cxl_memdev_has_poison_cmd(struct cxl_memdev *cxlmd,
enum poison_cmd_enabled_bits cmd);
long cxl_pci_get_latency(struct pci_dev *pdev);
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_update_hmat_access_coordinates(int nid, struct cxl_region *cxlr,

View File

@ -200,6 +200,14 @@ static ssize_t security_erase_store(struct device *dev,
static struct device_attribute dev_attr_security_erase =
__ATTR(erase, 0200, NULL, security_erase_store);
bool cxl_memdev_has_poison_cmd(struct cxl_memdev *cxlmd,
enum poison_cmd_enabled_bits cmd)
{
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
return test_bit(cmd, mds->poison.enabled_cmds);
}
static int cxl_get_poison_by_memdev(struct cxl_memdev *cxlmd)
{
struct cxl_dev_state *cxlds = cxlmd->cxlds;
@ -276,7 +284,7 @@ static int cxl_validate_poison_dpa(struct cxl_memdev *cxlmd, u64 dpa)
return 0;
}
int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa)
int cxl_inject_poison_locked(struct cxl_memdev *cxlmd, u64 dpa)
{
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_mbox_inject_poison inject;
@ -288,13 +296,8 @@ int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa)
if (!IS_ENABLED(CONFIG_DEBUG_FS))
return 0;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
lockdep_assert_held(&cxl_rwsem.dpa);
lockdep_assert_held(&cxl_rwsem.region);
rc = cxl_validate_poison_dpa(cxlmd, dpa);
if (rc)
@ -324,9 +327,24 @@ int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa)
return 0;
}
int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa)
{
int rc;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
return cxl_inject_poison_locked(cxlmd, dpa);
}
EXPORT_SYMBOL_NS_GPL(cxl_inject_poison, "CXL");
int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa)
int cxl_clear_poison_locked(struct cxl_memdev *cxlmd, u64 dpa)
{
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_mbox_clear_poison clear;
@ -338,13 +356,8 @@ int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa)
if (!IS_ENABLED(CONFIG_DEBUG_FS))
return 0;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
lockdep_assert_held(&cxl_rwsem.dpa);
lockdep_assert_held(&cxl_rwsem.region);
rc = cxl_validate_poison_dpa(cxlmd, dpa);
if (rc)
@ -383,6 +396,21 @@ int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa)
return 0;
}
int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa)
{
int rc;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
return cxl_clear_poison_locked(cxlmd, dpa);
}
EXPORT_SYMBOL_NS_GPL(cxl_clear_poison, "CXL");
static struct attribute *cxl_memdev_attributes[] = {

View File

@ -450,6 +450,7 @@ static void cxl_root_decoder_release(struct device *dev)
if (atomic_read(&cxlrd->region_id) >= 0)
memregion_free(atomic_read(&cxlrd->region_id));
__cxl_decoder_release(&cxlrd->cxlsd.cxld);
kfree(cxlrd->ops);
kfree(cxlrd);
}

View File

@ -2,6 +2,7 @@
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/memregion.h>
#include <linux/genalloc.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/memory.h>
@ -2917,6 +2918,16 @@ static bool cxl_is_hpa_in_chunk(u64 hpa, struct cxl_region *cxlr, int pos)
return false;
}
static bool has_hpa_to_spa(struct cxl_root_decoder *cxlrd)
{
return cxlrd->ops && cxlrd->ops->hpa_to_spa;
}
static bool has_spa_to_hpa(struct cxl_root_decoder *cxlrd)
{
return cxlrd->ops && cxlrd->ops->spa_to_hpa;
}
u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
u64 dpa)
{
@ -2971,8 +2982,8 @@ u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
hpa = hpa_offset + p->res->start + p->cache_size;
/* Root decoder translation overrides typical modulo decode */
if (cxlrd->hpa_to_spa)
hpa = cxlrd->hpa_to_spa(cxlrd, hpa);
if (has_hpa_to_spa(cxlrd))
hpa = cxlrd->ops->hpa_to_spa(cxlrd, hpa);
if (!cxl_resource_contains_addr(p->res, hpa)) {
dev_dbg(&cxlr->dev,
@ -2981,12 +2992,107 @@ u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
}
/* Simple chunk check, by pos & gran, only applies to modulo decodes */
if (!cxlrd->hpa_to_spa && (!cxl_is_hpa_in_chunk(hpa, cxlr, pos)))
if (!has_hpa_to_spa(cxlrd) && (!cxl_is_hpa_in_chunk(hpa, cxlr, pos)))
return ULLONG_MAX;
return hpa;
}
struct dpa_result {
struct cxl_memdev *cxlmd;
u64 dpa;
};
static int region_offset_to_dpa_result(struct cxl_region *cxlr, u64 offset,
struct dpa_result *result)
{
struct cxl_region_params *p = &cxlr->params;
struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
struct cxl_endpoint_decoder *cxled;
u64 hpa, hpa_offset, dpa_offset;
u64 bits_upper, bits_lower;
u64 shifted, rem, temp;
u16 eig = 0;
u8 eiw = 0;
int pos;
lockdep_assert_held(&cxl_rwsem.region);
lockdep_assert_held(&cxl_rwsem.dpa);
/* Input validation ensures valid ways and gran */
granularity_to_eig(p->interleave_granularity, &eig);
ways_to_eiw(p->interleave_ways, &eiw);
/*
* If the root decoder has SPA to CXL HPA callback, use it. Otherwise
* CXL HPA is assumed to equal SPA.
*/
if (has_spa_to_hpa(cxlrd)) {
hpa = cxlrd->ops->spa_to_hpa(cxlrd, p->res->start + offset);
hpa_offset = hpa - p->res->start;
} else {
hpa_offset = offset;
}
/*
* Interleave position: CXL Spec 3.2 Section 8.2.4.20.13
* eiw < 8
* Position is in the IW bits at HPA_OFFSET[IG+8+IW-1:IG+8].
* Per spec "remove IW bits starting with bit position IG+8"
* eiw >= 8
* Position is not explicitly stored in HPA_OFFSET bits. It is
* derived from the modulo operation of the upper bits using
* the total number of interleave ways.
*/
if (eiw < 8) {
pos = (hpa_offset >> (eig + 8)) & GENMASK(eiw - 1, 0);
} else {
shifted = hpa_offset >> (eig + 8);
div64_u64_rem(shifted, p->interleave_ways, &rem);
pos = rem;
}
if (pos < 0 || pos >= p->nr_targets) {
dev_dbg(&cxlr->dev, "Invalid position %d for %d targets\n",
pos, p->nr_targets);
return -ENXIO;
}
/*
* DPA offset: CXL Spec 3.2 Section 8.2.4.20.13
* Lower bits [IG+7:0] pass through unchanged
* (eiw < 8)
* Per spec: DPAOffset[51:IG+8] = (HPAOffset[51:IG+IW+8] >> IW)
* Clear the position bits to isolate upper section, then
* reverse the left shift by eiw that occurred during DPA->HPA
* (eiw >= 8)
* Per spec: DPAOffset[51:IG+8] = HPAOffset[51:IG+IW] / 3
* Extract upper bits from the correct bit range and divide by 3
* to recover the original DPA upper bits
*/
bits_lower = hpa_offset & GENMASK_ULL(eig + 7, 0);
if (eiw < 8) {
temp = hpa_offset &= ~((u64)GENMASK(eig + eiw + 8 - 1, 0));
dpa_offset = temp >> eiw;
} else {
bits_upper = div64_u64(hpa_offset >> (eig + eiw), 3);
dpa_offset = bits_upper << (eig + 8);
}
dpa_offset |= bits_lower;
/* Look-up and return the result: a memdev and a DPA */
for (int i = 0; i < p->nr_targets; i++) {
cxled = p->targets[i];
if (cxled->pos != pos)
continue;
result->cxlmd = cxled_to_memdev(cxled);
result->dpa = cxl_dpa_resource_start(cxled) + dpa_offset;
return 0;
}
dev_err(&cxlr->dev, "No device found for position %d\n", pos);
return -ENXIO;
}
static struct lock_class_key cxl_pmem_region_key;
static int cxl_pmem_region_alloc(struct cxl_region *cxlr)
@ -3541,6 +3647,105 @@ static void shutdown_notifiers(void *_cxlr)
unregister_mt_adistance_algorithm(&cxlr->adist_notifier);
}
static void remove_debugfs(void *dentry)
{
debugfs_remove_recursive(dentry);
}
static int validate_region_offset(struct cxl_region *cxlr, u64 offset)
{
struct cxl_region_params *p = &cxlr->params;
resource_size_t region_size;
u64 hpa;
if (offset < p->cache_size) {
dev_err(&cxlr->dev,
"Offset %#llx is within extended linear cache %#llx\n",
offset, p->cache_size);
return -EINVAL;
}
region_size = resource_size(p->res);
if (offset >= region_size) {
dev_err(&cxlr->dev, "Offset %#llx exceeds region size %#llx\n",
offset, region_size);
return -EINVAL;
}
hpa = p->res->start + offset;
if (hpa < p->res->start || hpa > p->res->end) {
dev_err(&cxlr->dev, "HPA %#llx not in region %pr\n", hpa,
p->res);
return -EINVAL;
}
return 0;
}
static int cxl_region_debugfs_poison_inject(void *data, u64 offset)
{
struct dpa_result result = { .dpa = ULLONG_MAX, .cxlmd = NULL };
struct cxl_region *cxlr = data;
int rc;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
if (validate_region_offset(cxlr, offset))
return -EINVAL;
rc = region_offset_to_dpa_result(cxlr, offset, &result);
if (rc || !result.cxlmd || result.dpa == ULLONG_MAX) {
dev_dbg(&cxlr->dev,
"Failed to resolve DPA for region offset %#llx rc %d\n",
offset, rc);
return rc ? rc : -EINVAL;
}
return cxl_inject_poison_locked(result.cxlmd, result.dpa);
}
DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_inject_fops, NULL,
cxl_region_debugfs_poison_inject, "%llx\n");
static int cxl_region_debugfs_poison_clear(void *data, u64 offset)
{
struct dpa_result result = { .dpa = ULLONG_MAX, .cxlmd = NULL };
struct cxl_region *cxlr = data;
int rc;
ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &region_rwsem)))
return rc;
ACQUIRE(rwsem_read_intr, dpa_rwsem)(&cxl_rwsem.dpa);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &dpa_rwsem)))
return rc;
if (validate_region_offset(cxlr, offset))
return -EINVAL;
rc = region_offset_to_dpa_result(cxlr, offset, &result);
if (rc || !result.cxlmd || result.dpa == ULLONG_MAX) {
dev_dbg(&cxlr->dev,
"Failed to resolve DPA for region offset %#llx rc %d\n",
offset, rc);
return rc ? rc : -EINVAL;
}
return cxl_clear_poison_locked(result.cxlmd, result.dpa);
}
DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_clear_fops, NULL,
cxl_region_debugfs_poison_clear, "%llx\n");
static int cxl_region_can_probe(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
@ -3570,6 +3775,7 @@ static int cxl_region_probe(struct device *dev)
{
struct cxl_region *cxlr = to_cxl_region(dev);
struct cxl_region_params *p = &cxlr->params;
bool poison_supported = true;
int rc;
rc = cxl_region_can_probe(cxlr);
@ -3593,6 +3799,31 @@ static int cxl_region_probe(struct device *dev)
if (rc)
return rc;
/* Create poison attributes if all memdevs support the capabilities */
for (int i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
if (!cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_INJECT) ||
!cxl_memdev_has_poison_cmd(cxlmd, CXL_POISON_ENABLED_CLEAR)) {
poison_supported = false;
break;
}
}
if (poison_supported) {
struct dentry *dentry;
dentry = cxl_debugfs_create_dir(dev_name(dev));
debugfs_create_file("inject_poison", 0200, dentry, cxlr,
&cxl_poison_inject_fops);
debugfs_create_file("clear_poison", 0200, dentry, cxlr,
&cxl_poison_clear_fops);
rc = devm_add_action_or_reset(dev, remove_debugfs, dentry);
if (rc)
return rc;
}
switch (cxlr->mode) {
case CXL_PARTMODE_PMEM:
rc = devm_cxl_region_edac_register(cxlr);

View File

@ -419,27 +419,35 @@ struct cxl_switch_decoder {
};
struct cxl_root_decoder;
typedef u64 (*cxl_hpa_to_spa_fn)(struct cxl_root_decoder *cxlrd, u64 hpa);
/**
* struct cxl_rd_ops - CXL root decoder callback operations
* @hpa_to_spa: Convert host physical address to system physical address
* @spa_to_hpa: Convert system physical address to host physical address
*/
struct cxl_rd_ops {
u64 (*hpa_to_spa)(struct cxl_root_decoder *cxlrd, u64 hpa);
u64 (*spa_to_hpa)(struct cxl_root_decoder *cxlrd, u64 spa);
};
/**
* struct cxl_root_decoder - Static platform CXL address decoder
* @res: host / parent resource for region allocations
* @cache_size: extended linear cache size if exists, otherwise zero.
* @region_id: region id for next region provisioning event
* @hpa_to_spa: translate CXL host-physical-address to Platform system-physical-address
* @platform_data: platform specific configuration data
* @range_lock: sync region autodiscovery by address range
* @qos_class: QoS performance class cookie
* @ops: CXL root decoder operations
* @cxlsd: base cxl switch decoder
*/
struct cxl_root_decoder {
struct resource *res;
resource_size_t cache_size;
atomic_t region_id;
cxl_hpa_to_spa_fn hpa_to_spa;
void *platform_data;
struct mutex range_lock;
int qos_class;
struct cxl_rd_ops *ops;
struct cxl_switch_decoder cxlsd;
};

View File

@ -869,6 +869,8 @@ int cxl_mem_get_poison(struct cxl_memdev *cxlmd, u64 offset, u64 len,
int cxl_trigger_poison_list(struct cxl_memdev *cxlmd);
int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_inject_poison_locked(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_clear_poison_locked(struct cxl_memdev *cxlmd, u64 dpa);
#ifdef CONFIG_CXL_EDAC_MEM_FEATURES
int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd);