Add functions to enable programming the CXL.mem transaction timeout range, if supported. Add a sysfs attribute to the "cxl_isolation" group to allow programming the timeout from userspace. The attribute can take either the CXL spec-defined hex value for the associated timeout range (CXL 3.2 8.2.4.24.2 field 3:0) or a string with the range. The range string is formatted as the range letter in uppercase or lowercase, with an optional "2" to specify the second range in the aforementioned spec ref. For example, to program the port with a timeout of 65ms to 210ms (range B) the following strings could be specified: "b2"/"B2". Picking the first portion of range B (16ms to 55ms) would be: "b"/"B". Signed-off-by: Ben Cheatham <Benjamin.Cheatham@xxxxxxx> --- drivers/cxl/core/pci.c | 49 +++++++++++++++ drivers/pci/pcie/cxl_isolation.c | 102 +++++++++++++++++++++++++++++++ include/cxl/isolation.h | 3 + 3 files changed, 154 insertions(+) diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index dd6c602d57d3..616c337c818d 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -1259,3 +1259,52 @@ void cxl_disable_timeout(struct cxl_dport *dport) dev_dbg(dport->dport_dev, "Disabled CXL.mem transaction timeout\n"); } + +static bool timeout_range_supported(u32 cap, u32 val) +{ + u32 supported = FIELD_GET(CXL_ISOLATION_CAP_MEM_TIME_MASK, cap); + + if (!supported) + return false; + + /* CXL 3.2 8.2.4.24.1 field 3:0 */ + switch (val) { + /* Range A (default) */ + case 0x0: + case 0x1: + case 0x2: + return (supported & BIT(0)); + /* Range B */ + case 0x5: + case 0x6: + return (supported & BIT(1)); + /* Range C */ + case 0x9: + case 0xA: + return (supported & BIT(2)); + case 0xD: + case 0xE: + /* Range D */ + return (supported & BIT(3)); + default: + return false; + } +} + +int cxl_set_timeout_range(struct cxl_dport *dport, u8 val) +{ + u32 cap, ctrl; + + cap = readl(dport->regs.isolation + CXL_ISOLATION_CAPABILITY_OFFSET); + if (!(cap & CXL_ISOLATION_CAP_MEM_TIME_SUPP)) + return -ENXIO; + + if (!timeout_range_supported(cap, val)) + return -EINVAL; + + ctrl = readl(dport->regs.isolation + CXL_ISOLATION_CTRL_OFFSET); + ctrl &= FIELD_PREP(CXL_ISOLATION_CTRL_MEM_TIME_MASK, 0); + ctrl |= FIELD_PREP(CXL_ISOLATION_CTRL_MEM_TIME_MASK, val); + writel(ctrl, dport->regs.isolation + CXL_ISOLATION_CTRL_OFFSET); + return 0; +} diff --git a/drivers/pci/pcie/cxl_isolation.c b/drivers/pci/pcie/cxl_isolation.c index 9d2ad14810e8..107201b5843f 100644 --- a/drivers/pci/pcie/cxl_isolation.c +++ b/drivers/pci/pcie/cxl_isolation.c @@ -193,9 +193,111 @@ static ssize_t timeout_ctrl_show(struct device *dev, } DEVICE_ATTR_RW(timeout_ctrl); +/* CXL 3.2 8.2.4.24.2 CXL Timeout & Isolation Control Register, field 3:0 */ +const struct timeout_ranges { + char *str; + u8 val; +} ranges[] = { + { .str = "a", .val = 0x1 }, + { .str = "A", .val = 0x1 }, + { .str = "a2", .val = 0x2 }, + { .str = "A2", .val = 0x2 }, + { .str = "b", .val = 0x5 }, + { .str = "B", .val = 0x5 }, + { .str = "b2", .val = 0x6 }, + { .str = "B2", .val = 0x6 }, + { .str = "c", .val = 0x9 }, + { .str = "C", .val = 0x9 }, + { .str = "c2", .val = 0xA }, + { .str = "C2", .val = 0xA }, + { .str = "d", .val = 0xD }, + { .str = "D", .val = 0xD }, + { .str = "d2", .val = 0xE }, + { .str = "D2", .val = 0xE }, +}; + +static int timeout_range_str_to_val(const char *str, u8 *val) +{ + char val_buf[32] = { 0 }; + char *start; + + strscpy(val_buf, str, ARRAY_SIZE(val_buf) - 1); + start = strim(val_buf); + if (!start) + return -EINVAL; + + for (int i = 0; i < ARRAY_SIZE(ranges); i++) + if (strcmp(start, ranges[i].str) == 0) + return ranges[i].val; + + return -EINVAL; +} + +static ssize_t timeout_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t n) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct cxl_port *port; + u8 val; + int rc; + + rc = kstrtou8(buf, 0, &val); + if (rc && timeout_range_str_to_val(buf, &val) < 0) + return -EINVAL; + + struct cxl_dport **dport __free(kfree) = + kzalloc(sizeof(*dport), GFP_KERNEL); + if (!dport) + return -ENOMEM; + + port = cxl_find_pcie_rp(pdev, dport); + if (!port || !(*dport)) + return -ENODEV; + + if (!(*dport)->regs.isolation) + return -ENXIO; + + rc = cxl_set_timeout_range(*dport, val); + put_device(&port->dev); + return rc ? rc : n; +} + +static ssize_t timeout_range_show(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct cxl_port *port; + u32 ctrl, val; + + struct cxl_dport **dport __free(kfree) = + kzalloc(sizeof(*dport), GFP_KERNEL); + if (!dport) + return -ENOMEM; + + port = cxl_find_pcie_rp(pdev, dport); + if (!port || !(*dport)) + return -ENODEV; + + if (!(*dport)->regs.isolation) + return -ENXIO; + + ctrl = readl((*dport)->regs.isolation + CXL_ISOLATION_CTRL_OFFSET); + put_device(&port->dev); + + val = FIELD_GET(CXL_ISOLATION_CTRL_MEM_TIME_MASK, ctrl); + for (int i = 0; i < ARRAY_SIZE(ranges); i++) + if (ranges[i].val == val) + return sysfs_emit(buf, "%s\n", ranges[i].str); + + return -ENXIO; +} +DEVICE_ATTR_RW(timeout_range); + static struct attribute *isolation_attrs[] = { &dev_attr_timeout_ctrl.attr, &dev_attr_isolation_ctrl.attr, + &dev_attr_timeout_range.attr, NULL, }; diff --git a/include/cxl/isolation.h b/include/cxl/isolation.h index 0b6e4f0160a8..f2c4feb5a42b 100644 --- a/include/cxl/isolation.h +++ b/include/cxl/isolation.h @@ -30,6 +30,7 @@ void cxl_enable_isolation(struct cxl_dport *dport); int cxl_disable_isolation(struct cxl_dport *dport); void cxl_enable_timeout(struct cxl_dport *dport); void cxl_disable_timeout(struct cxl_dport *dport); +int cxl_set_timeout_range(struct cxl_dport *dport, u8 val); struct cxl_port *cxl_find_pcie_rp(struct pci_dev *pdev, struct cxl_dport **dport); @@ -39,6 +40,8 @@ static inline int cxl_disable_isolation(struct cxl_dport *dport) { return -ENXIO; } static inline void cxl_enable_timeout(struct cxl_dport *dport) {} static inline void cxl_disable_timeout(struct cxl_dport *dport) {} +static inline int cxl_set_timeout_range(struct cxl_dport *dport, u8 val) +{ return -ENXIO; } static inline struct cxl_port *cxl_find_pcie_rp(struct pci_dev *pdev, struct cxl_dport **dport); -- 2.34.1