diff options
Diffstat (limited to 'drivers/cxl/core/memdev.c')
| -rw-r--r-- | drivers/cxl/core/memdev.c | 227 | 
1 files changed, 227 insertions, 0 deletions
diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 28a05f2fe32d..057a43267290 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -6,6 +6,7 @@  #include <linux/idr.h>  #include <linux/pci.h>  #include <cxlmem.h> +#include "trace.h"  #include "core.h"  static DECLARE_RWSEM(cxl_memdev_rwsem); @@ -106,6 +107,232 @@ static ssize_t numa_node_show(struct device *dev, struct device_attribute *attr,  }  static DEVICE_ATTR_RO(numa_node); +static int cxl_get_poison_by_memdev(struct cxl_memdev *cxlmd) +{ +	struct cxl_dev_state *cxlds = cxlmd->cxlds; +	u64 offset, length; +	int rc = 0; + +	/* CXL 3.0 Spec 8.2.9.8.4.1 Separate pmem and ram poison requests */ +	if (resource_size(&cxlds->pmem_res)) { +		offset = cxlds->pmem_res.start; +		length = resource_size(&cxlds->pmem_res); +		rc = cxl_mem_get_poison(cxlmd, offset, length, NULL); +		if (rc) +			return rc; +	} +	if (resource_size(&cxlds->ram_res)) { +		offset = cxlds->ram_res.start; +		length = resource_size(&cxlds->ram_res); +		rc = cxl_mem_get_poison(cxlmd, offset, length, NULL); +		/* +		 * Invalid Physical Address is not an error for +		 * volatile addresses. Device support is optional. +		 */ +		if (rc == -EFAULT) +			rc = 0; +	} +	return rc; +} + +int cxl_trigger_poison_list(struct cxl_memdev *cxlmd) +{ +	struct cxl_port *port; +	int rc; + +	port = dev_get_drvdata(&cxlmd->dev); +	if (!port || !is_cxl_endpoint(port)) +		return -EINVAL; + +	rc = down_read_interruptible(&cxl_dpa_rwsem); +	if (rc) +		return rc; + +	if (port->commit_end == -1) { +		/* No regions mapped to this memdev */ +		rc = cxl_get_poison_by_memdev(cxlmd); +	} else { +		/* Regions mapped, collect poison by endpoint */ +		rc =  cxl_get_poison_by_endpoint(port); +	} +	up_read(&cxl_dpa_rwsem); + +	return rc; +} +EXPORT_SYMBOL_NS_GPL(cxl_trigger_poison_list, CXL); + +struct cxl_dpa_to_region_context { +	struct cxl_region *cxlr; +	u64 dpa; +}; + +static int __cxl_dpa_to_region(struct device *dev, void *arg) +{ +	struct cxl_dpa_to_region_context *ctx = arg; +	struct cxl_endpoint_decoder *cxled; +	u64 dpa = ctx->dpa; + +	if (!is_endpoint_decoder(dev)) +		return 0; + +	cxled = to_cxl_endpoint_decoder(dev); +	if (!cxled->dpa_res || !resource_size(cxled->dpa_res)) +		return 0; + +	if (dpa > cxled->dpa_res->end || dpa < cxled->dpa_res->start) +		return 0; + +	dev_dbg(dev, "dpa:0x%llx mapped in region:%s\n", dpa, +		dev_name(&cxled->cxld.region->dev)); + +	ctx->cxlr = cxled->cxld.region; + +	return 1; +} + +static struct cxl_region *cxl_dpa_to_region(struct cxl_memdev *cxlmd, u64 dpa) +{ +	struct cxl_dpa_to_region_context ctx; +	struct cxl_port *port; + +	ctx = (struct cxl_dpa_to_region_context) { +		.dpa = dpa, +	}; +	port = dev_get_drvdata(&cxlmd->dev); +	if (port && is_cxl_endpoint(port) && port->commit_end != -1) +		device_for_each_child(&port->dev, &ctx, __cxl_dpa_to_region); + +	return ctx.cxlr; +} + +static int cxl_validate_poison_dpa(struct cxl_memdev *cxlmd, u64 dpa) +{ +	struct cxl_dev_state *cxlds = cxlmd->cxlds; + +	if (!IS_ENABLED(CONFIG_DEBUG_FS)) +		return 0; + +	if (!resource_size(&cxlds->dpa_res)) { +		dev_dbg(cxlds->dev, "device has no dpa resource\n"); +		return -EINVAL; +	} +	if (dpa < cxlds->dpa_res.start || dpa > cxlds->dpa_res.end) { +		dev_dbg(cxlds->dev, "dpa:0x%llx not in resource:%pR\n", +			dpa, &cxlds->dpa_res); +		return -EINVAL; +	} +	if (!IS_ALIGNED(dpa, 64)) { +		dev_dbg(cxlds->dev, "dpa:0x%llx is not 64-byte aligned\n", dpa); +		return -EINVAL; +	} + +	return 0; +} + +int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa) +{ +	struct cxl_dev_state *cxlds = cxlmd->cxlds; +	struct cxl_mbox_inject_poison inject; +	struct cxl_poison_record record; +	struct cxl_mbox_cmd mbox_cmd; +	struct cxl_region *cxlr; +	int rc; + +	if (!IS_ENABLED(CONFIG_DEBUG_FS)) +		return 0; + +	rc = down_read_interruptible(&cxl_dpa_rwsem); +	if (rc) +		return rc; + +	rc = cxl_validate_poison_dpa(cxlmd, dpa); +	if (rc) +		goto out; + +	inject.address = cpu_to_le64(dpa); +	mbox_cmd = (struct cxl_mbox_cmd) { +		.opcode = CXL_MBOX_OP_INJECT_POISON, +		.size_in = sizeof(inject), +		.payload_in = &inject, +	}; +	rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); +	if (rc) +		goto out; + +	cxlr = cxl_dpa_to_region(cxlmd, dpa); +	if (cxlr) +		dev_warn_once(cxlds->dev, +			      "poison inject dpa:%#llx region: %s\n", dpa, +			      dev_name(&cxlr->dev)); + +	record = (struct cxl_poison_record) { +		.address = cpu_to_le64(dpa), +		.length = cpu_to_le32(1), +	}; +	trace_cxl_poison(cxlmd, cxlr, &record, 0, 0, CXL_POISON_TRACE_INJECT); +out: +	up_read(&cxl_dpa_rwsem); + +	return rc; +} +EXPORT_SYMBOL_NS_GPL(cxl_inject_poison, CXL); + +int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa) +{ +	struct cxl_dev_state *cxlds = cxlmd->cxlds; +	struct cxl_mbox_clear_poison clear; +	struct cxl_poison_record record; +	struct cxl_mbox_cmd mbox_cmd; +	struct cxl_region *cxlr; +	int rc; + +	if (!IS_ENABLED(CONFIG_DEBUG_FS)) +		return 0; + +	rc = down_read_interruptible(&cxl_dpa_rwsem); +	if (rc) +		return rc; + +	rc = cxl_validate_poison_dpa(cxlmd, dpa); +	if (rc) +		goto out; + +	/* +	 * In CXL 3.0 Spec 8.2.9.8.4.3, the Clear Poison mailbox command +	 * is defined to accept 64 bytes of write-data, along with the +	 * address to clear. This driver uses zeroes as write-data. +	 */ +	clear = (struct cxl_mbox_clear_poison) { +		.address = cpu_to_le64(dpa) +	}; + +	mbox_cmd = (struct cxl_mbox_cmd) { +		.opcode = CXL_MBOX_OP_CLEAR_POISON, +		.size_in = sizeof(clear), +		.payload_in = &clear, +	}; + +	rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); +	if (rc) +		goto out; + +	cxlr = cxl_dpa_to_region(cxlmd, dpa); +	if (cxlr) +		dev_warn_once(cxlds->dev, "poison clear dpa:%#llx region: %s\n", +			      dpa, dev_name(&cxlr->dev)); + +	record = (struct cxl_poison_record) { +		.address = cpu_to_le64(dpa), +		.length = cpu_to_le32(1), +	}; +	trace_cxl_poison(cxlmd, cxlr, &record, 0, 0, CXL_POISON_TRACE_CLEAR); +out: +	up_read(&cxl_dpa_rwsem); + +	return rc; +} +EXPORT_SYMBOL_NS_GPL(cxl_clear_poison, CXL); +  static struct attribute *cxl_memdev_attributes[] = {  	&dev_attr_serial.attr,  	&dev_attr_firmware_version.attr,  |