Register a CXL isolation interrupt handler as part of cxl_dport set up. Only CXL-capable PCIe Root Ports have CXL.mem isolation interrupt support. The interrupts are left masked and will be unmasked in a later commit. A CXL-capable PCIe Root Port that has CXL.mem isolation support and no interrupt support will have any isolation support enabled. If isolation were enabled without interrupts CXL.mem transactions could return poisoned data. This could cause data/system corruption if left unhandled, so the capability is left disabled in this case. CXL endpoint drivers can add an isolation handler for a device through the isolation_handler member of struct cxl_dev_state. If this handler is not present, the system will panic. If the handler opts to not panic (i.e. returns "CXL_ERR_NONE"), the endpoint driver is charged with maintaining system reliability (cleaning up CXL memory, disabling device state, etc.). Signed-off-by: Ben Cheatham <Benjamin.Cheatham@xxxxxxx> --- drivers/cxl/core/port.c | 113 ++++++++++++++++++++++++++++++++++++++-- drivers/cxl/cxl.h | 1 + drivers/cxl/cxlmem.h | 4 ++ include/cxl/isolation.h | 10 ++++ 4 files changed, 125 insertions(+), 3 deletions(-) diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index 71e954ebc5aa..a36440e85647 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -1174,15 +1174,117 @@ static void cxl_dport_unlink(void *data) sysfs_remove_link(&port->dev.kobj, link_name); } +struct isolation_intr_data { + struct cxl_dport *dport; + struct cxl_port *port; +}; + +static irqreturn_t cxl_isolation_thread(int irq, void *_data) +{ + struct isolation_intr_data *data = _data; + struct cxl_dport *dport = data->dport; + struct cxl_port *port = data->port; + enum cxl_err_results res, acc; + struct cxl_dev_state *cxlds; + struct cxl_memdev *cxlmd; + struct cxl_dport *iter; + unsigned long index; + struct cxl_ep *ep; + bool lnk_down; + u32 status; + + if (!dport || !port) + return IRQ_NONE; + + guard(device)(&port->dev); + if (!dport->regs.isolation) + goto panic; + + status = readl(dport->regs.isolation + CXL_ISOLATION_STATUS_OFFSET); + lnk_down = FIELD_GET(CXL_ISOLATION_STAT_LNK_DOWN, status); + + acc = CXL_ERR_NONE; + xa_for_each(&port->endpoints, index, ep) { + iter = ep->dport; + while (iter && (&iter->port->dev != &port->dev)) + iter = iter->port->parent_dport; + + res = CXL_ERR_PANIC; + if (iter->dport_dev == dport->dport_dev) { + cxlmd = to_cxl_memdev(ep->ep); + cxlds = cxlmd->cxlds; + + if (cxlds && cxlds->isolation_handler) + res = cxlds->isolation_handler(cxlds, lnk_down); + } + + acc = max(res, acc); + } + + if (acc == CXL_ERR_NONE) + return IRQ_HANDLED; + +panic: + panic("%s: downstream devices could not recover from CXL.mem link down\n", + dev_name(dport->dport_dev)); + return IRQ_NONE; +} + +static void cxl_dport_free_interrupts(void *data) +{ + struct cxl_isolation_info *info; + struct cxl_dport *dport = data; + struct pci_dev *pdev = to_pci_dev(dport->dport_dev); + + info = pcie_cxl_dport_get_isolation_info(pdev); + if (!info) + return; + + devm_free_irq(info->dev, info->irq, dport); +} + +static int cxl_dport_setup_interrupts(struct device *host, + struct cxl_dport *dport) +{ + struct isolation_intr_data *data; + struct cxl_isolation_info *info; + u32 cap; + int rc; + + cap = readl(dport->regs.isolation + CXL_ISOLATION_CAPABILITY_OFFSET); + if (!(cap & CXL_ISOLATION_CAP_INTR_SUPP)) + return -ENXIO; + + info = pcie_cxl_dport_get_isolation_info(to_pci_dev(dport->dport_dev)); + if (!info) + return -ENXIO; + + data = devm_kmalloc(host, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + data->port = dport->port; + data->dport = dport; + + rc = devm_request_threaded_irq(info->dev, info->irq, NULL, + cxl_isolation_thread, + IRQF_SHARED | IRQF_ONESHOT, + "cxl_isolation", data); + if (rc) + return rc; + + return devm_add_action_or_reset(host, cxl_dport_free_interrupts, dport); +} + /** * cxl_dport_enable_isolation - Enable CXL Isolation for a CXL dport. This is * an optional capability only supported by PCIe Root Ports. - * + * @host: Host device for @dport * @dport: CXL-capable PCIe Root Port * * Returns 0 if capability unsupported, or when enabled. */ -static int cxl_dport_enable_isolation(struct cxl_dport *dport) +static int cxl_dport_enable_isolation(struct device *host, + struct cxl_dport *dport) { u32 cap; int rc; @@ -1199,6 +1301,10 @@ static int cxl_dport_enable_isolation(struct cxl_dport *dport) if (!(cap & CXL_ISOLATION_CAP_MEM_ISO_SUPP)) return 0; + rc = cxl_dport_setup_interrupts(host, dport); + if (rc) + return rc == -ENXIO ? 0 : rc; + cxl_enable_isolation(dport); return 0; } @@ -1266,7 +1372,7 @@ __devm_cxl_add_dport(struct cxl_port *port, struct device *dport_dev, &component_reg_phys); if (IS_ENABLED(CONFIG_CXL_ISOLATION)) { - rc = cxl_dport_enable_isolation(dport); + rc = cxl_dport_enable_isolation(host, dport); if (rc) return ERR_PTR(rc); } @@ -1543,6 +1649,7 @@ static void reap_dport(struct cxl_port *port, struct cxl_dport *dport) { devm_release_action(&port->dev, cxl_dport_unlink, dport); devm_release_action(&port->dev, cxl_dport_remove, dport); + devm_release_action(&port->dev, cxl_dport_free_interrupts, dport); devm_kfree(&port->dev, dport); } diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h index 999ffa05b68f..9e3ca754251d 100644 --- a/drivers/cxl/cxl.h +++ b/drivers/cxl/cxl.h @@ -143,6 +143,7 @@ static inline int ways_to_eiw(unsigned int ways, u8 *eiw) #define CXL_ISOLATION_CTRL_MEM_ISO_ENABLE BIT(16) #define CXL_ISOLATION_STATUS_OFFSET 0xC #define CXL_ISOLATION_STAT_MEM_ISO BIT(8) +#define CXL_ISOLATION_STAT_LNK_DOWN BIT(9) #define CXL_ISOLATION_STAT_RP_BUSY BIT(14) #define CXL_ISOLATION_CAPABILITY_LENGTH 0x10 diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index 551b0ba2caa1..fbe64c580785 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -9,6 +9,7 @@ #include <linux/node.h> #include <cxl/event.h> #include <cxl/mailbox.h> +#include <cxl/isolation.h> #include "cxl.h" /* CXL 2.0 8.2.8.5.1.1 Memory Device Status Register */ @@ -426,6 +427,7 @@ struct cxl_dpa_partition { * @type: Generic Memory Class device or Vendor Specific Memory device * @cxl_mbox: CXL mailbox context * @cxlfs: CXL features context + * @isolation_handler: CXL isolation CXL.mem link down handler */ struct cxl_dev_state { struct device *dev; @@ -444,6 +446,8 @@ struct cxl_dev_state { #ifdef CONFIG_CXL_FEATURES struct cxl_features_state *cxlfs; #endif + enum cxl_err_results (*isolation_handler)(struct cxl_dev_state *cxlds, + bool lnk_down); }; static inline resource_size_t cxl_pmem_size(struct cxl_dev_state *cxlds) diff --git a/include/cxl/isolation.h b/include/cxl/isolation.h index 429501a655dd..3ad05ccc5e01 100644 --- a/include/cxl/isolation.h +++ b/include/cxl/isolation.h @@ -4,6 +4,16 @@ #include <linux/pci.h> +/** + * enum cxl_err_results - Possible results of an CXL isolation handler + * @CXL_ERR_NONE: Device can recover without CXL core intervention + * @CXL_ERR_PANIC: Device can't recover + */ +enum cxl_err_results { + CXL_ERR_NONE = 0, + CXL_ERR_PANIC, +}; + /** * struct cxl_isolation_info - Information for mapping CXL Isolation interrupts * @dev: PCIe portdrv service device associated with IRQ -- 2.34.1