mirror of
https://github.com/torvalds/linux.git
synced 2026-05-26 08:02:27 +02:00
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:
commit
d9412f08e2
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ Accelerator
|
|||
User Flow Support
|
||||
-----------------
|
||||
|
||||
* [0] Inject & clear poison by HPA
|
||||
* [2] Inject & clear poison by region offset
|
||||
|
||||
Details
|
||||
=======
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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, ®ion_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, ®ion_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, ®ion_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, ®ion_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[] = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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, ®ion_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, ®ion_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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user