diff --git a/Documentation/ABI/testing/sysfs-bus-cxl b/Documentation/ABI/testing/sysfs-bus-cxl index 16a9b3d2e2c0..7352dbd70bc7 100644 --- a/Documentation/ABI/testing/sysfs-bus-cxl +++ b/Documentation/ABI/testing/sysfs-bus-cxl @@ -201,7 +201,8 @@ Description: capability. -What: /sys/bus/cxl/devices/{port,endpoint}X/parent_dport +What: /sys/bus/cxl/devices/portX/parent_dport +What: /sys/bus/cxl/devices/endpointX/parent_dport Date: January, 2023 KernelVersion: v6.3 Contact: linux-cxl@vger.kernel.org @@ -258,7 +259,8 @@ Description: instance id of a given decoder resource. -What: /sys/bus/cxl/devices/decoderX.Y/{start,size} +What: /sys/bus/cxl/devices/decoderX.Y/start +What: /sys/bus/cxl/devices/decoderX.Y/size Date: June, 2021 KernelVersion: v5.14 Contact: linux-cxl@vger.kernel.org @@ -297,7 +299,10 @@ Description: Each entry in the list is a dport id. -What: /sys/bus/cxl/devices/decoderX.Y/cap_{pmem,ram,type2,type3} +What: /sys/bus/cxl/devices/decoderX.Y/cap_pmem +What: /sys/bus/cxl/devices/decoderX.Y/cap_ram +What: /sys/bus/cxl/devices/decoderX.Y/cap_type2 +What: /sys/bus/cxl/devices/decoderX.Y/cap_type3 Date: June, 2021 KernelVersion: v5.14 Contact: linux-cxl@vger.kernel.org @@ -410,7 +415,8 @@ Description: interleave_granularity). -What: /sys/bus/cxl/devices/decoderX.Y/create_{pmem,ram}_region +What: /sys/bus/cxl/devices/decoderX.Y/create_pmem_region +What: /sys/bus/cxl/devices/decoderX.Y/create_ram_region Date: May, 2022, January, 2023 KernelVersion: v6.0 (pmem), v6.3 (ram) Contact: linux-cxl@vger.kernel.org @@ -500,6 +506,7 @@ Description: cache is present, the size indicates extended linear cache size plus the CXL region size. + What: /sys/bus/cxl/devices/regionZ/extended_linear_cache_size Date: October, 2025 KernelVersion: v6.19 diff --git a/Documentation/ABI/testing/sysfs-bus-nvdimm b/Documentation/ABI/testing/sysfs-bus-nvdimm index 64eb8f4c6a41..46dafd8482b9 100644 --- a/Documentation/ABI/testing/sysfs-bus-nvdimm +++ b/Documentation/ABI/testing/sysfs-bus-nvdimm @@ -48,7 +48,8 @@ What: /sys/bus/nd/devices/nmemX/cxl/id Date: November 2022 KernelVersion: 6.2 Contact: Dave Jiang -Description: (RO) Show the id (serial) of the device. This is CXL specific. +Description: (RO) Show the id (serial) of the device, formatted as an + unsigned 64-bit decimal value. This is CXL specific. What: /sys/bus/nd/devices/nmemX/cxl/provider Date: November 2022 diff --git a/Documentation/driver-api/cxl/linux/dax-driver.rst b/Documentation/driver-api/cxl/linux/dax-driver.rst index 10d953a2167b..72c85a0f8606 100644 --- a/Documentation/driver-api/cxl/linux/dax-driver.rst +++ b/Documentation/driver-api/cxl/linux/dax-driver.rst @@ -35,9 +35,9 @@ will be exposed to the kernel page allocator in the user-selected memory zone. The :code:`memmap_on_memory` setting (both global and DAX device local) -dictates where the kernell will allocate the :code:`struct folio` descriptors +dictates where the kernel will allocate the :code:`struct folio` descriptors for this memory will come from. If :code:`memmap_on_memory` is set, memory hotplug will set aside a portion of the memory block capacity to allocate folios. If unset, the memory is allocated via a normal :code:`GFP_KERNEL` -allocation - and as a result will most likely land on the local NUM node of the +allocation - and as a result will most likely land on the local NUMA node of the CPU executing the hotplug operation. diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index bc1dfa3398b4..fe10ab0e02f6 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -749,7 +749,7 @@ static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data, CXL_CPER_PROT_ERR_FIFO_DEPTH); /* Synchronize schedule_work() with cxl_cper_prot_err_work changes */ -static DEFINE_SPINLOCK(cxl_cper_prot_err_work_lock); +static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, @@ -761,7 +761,7 @@ static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, if (cxl_cper_sec_prot_err_valid(prot_err)) return; - guard(spinlock_irqsave)(&cxl_cper_prot_err_work_lock); + guard(raw_spinlock_irqsave)(&cxl_cper_prot_err_work_lock); if (!cxl_cper_prot_err_work) return; @@ -778,40 +778,48 @@ static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, #endif } -int cxl_cper_register_prot_err_work(struct work_struct *work) +void cxl_cper_register_prot_err_work(struct work_struct *work) { - if (cxl_cper_prot_err_work) - return -EINVAL; + guard(raw_spinlock_irqsave)(&cxl_cper_prot_err_work_lock); - guard(spinlock)(&cxl_cper_prot_err_work_lock); + if (WARN_ONCE(cxl_cper_prot_err_work, + "CPER-CXL kfifo consumer already registered\n")) + return; cxl_cper_prot_err_work = work; - return 0; } -EXPORT_SYMBOL_NS_GPL(cxl_cper_register_prot_err_work, "CXL"); +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_register_prot_err_work, "cxl_core"); -int cxl_cper_unregister_prot_err_work(struct work_struct *work) +void cxl_cper_unregister_prot_err_work(void) { - if (cxl_cper_prot_err_work != work) - return -EINVAL; + struct work_struct *old; - guard(spinlock)(&cxl_cper_prot_err_work_lock); - cxl_cper_prot_err_work = NULL; - return 0; + scoped_guard(raw_spinlock_irqsave, &cxl_cper_prot_err_work_lock) { + WARN_ONCE(!cxl_cper_prot_err_work, + "CPER-CXL kfifo consumer not registered on unregister\n"); + old = cxl_cper_prot_err_work; + cxl_cper_prot_err_work = NULL; + } + + if (old) + cancel_work_sync(old); + + /* Discard stale entries so they are not replayed on next module load */ + kfifo_reset(&cxl_cper_prot_err_fifo); } -EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_prot_err_work, "CXL"); +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_unregister_prot_err_work, "cxl_core"); int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd) { return kfifo_get(&cxl_cper_prot_err_fifo, wd); } -EXPORT_SYMBOL_NS_GPL(cxl_cper_prot_err_kfifo_get, "CXL"); +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_prot_err_kfifo_get, "cxl_core"); /* Room for 8 entries for each of the 4 event log queues */ #define CXL_CPER_FIFO_DEPTH 32 DEFINE_KFIFO(cxl_cper_fifo, struct cxl_cper_work_data, CXL_CPER_FIFO_DEPTH); /* Synchronize schedule_work() with cxl_cper_work changes */ -static DEFINE_SPINLOCK(cxl_cper_work_lock); +static DEFINE_RAW_SPINLOCK(cxl_cper_work_lock); struct work_struct *cxl_cper_work; static void cxl_cper_post_event(enum cxl_event_type event_type, @@ -831,7 +839,7 @@ static void cxl_cper_post_event(enum cxl_event_type event_type, return; } - guard(spinlock_irqsave)(&cxl_cper_work_lock); + guard(raw_spinlock_irqsave)(&cxl_cper_work_lock); if (!cxl_cper_work) return; @@ -849,23 +857,29 @@ static void cxl_cper_post_event(enum cxl_event_type event_type, int cxl_cper_register_work(struct work_struct *work) { - if (cxl_cper_work) + guard(raw_spinlock_irqsave)(&cxl_cper_work_lock); + if (WARN_ONCE(cxl_cper_work, + "CXL CPER kfifo consumer already registered\n")) return -EINVAL; - guard(spinlock)(&cxl_cper_work_lock); cxl_cper_work = work; return 0; } EXPORT_SYMBOL_NS_GPL(cxl_cper_register_work, "CXL"); -int cxl_cper_unregister_work(struct work_struct *work) +void cxl_cper_unregister_work(struct work_struct *work) { - if (cxl_cper_work != work) - return -EINVAL; + scoped_guard(raw_spinlock_irqsave, &cxl_cper_work_lock) { + if (WARN_ONCE(cxl_cper_work != work, + "CXL CPER kfifo consumer mismatch on unregister\n")) + return; + cxl_cper_work = NULL; + } - guard(spinlock)(&cxl_cper_work_lock); - cxl_cper_work = NULL; - return 0; + cancel_work_sync(work); + + /* Discard stale entries so they are not replayed on next module load */ + kfifo_reset(&cxl_cper_fifo); } EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_work, "CXL"); diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h index f7cebb026552..35eaf636adc9 100644 --- a/drivers/cxl/core/core.h +++ b/drivers/cxl/core/core.h @@ -185,7 +185,7 @@ static inline struct device *dport_to_host(struct cxl_dport *dport) return &port->dev; } #ifdef CONFIG_CXL_RAS -int cxl_ras_init(void); +void cxl_ras_init(void); void cxl_ras_exit(void); bool cxl_handle_ras(struct device *dev, void __iomem *ras_base); void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base); @@ -194,10 +194,7 @@ void cxl_disable_rch_root_ints(struct cxl_dport *dport); void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds); void devm_cxl_dport_ras_setup(struct cxl_dport *dport); #else -static inline int cxl_ras_init(void) -{ - return 0; -} +static inline void cxl_ras_init(void) { } static inline void cxl_ras_exit(void) { } static inline bool cxl_handle_ras(struct device *dev, void __iomem *ras_base) { diff --git a/drivers/cxl/core/features.c b/drivers/cxl/core/features.c index 85185af46b72..ba6d2a5acb74 100644 --- a/drivers/cxl/core/features.c +++ b/drivers/cxl/core/features.c @@ -225,7 +225,7 @@ size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, void *feat_out, size_t feat_out_size, u16 offset, u16 *return_code) { - size_t data_to_rd_size, size_out; + size_t data_to_rd_size; struct cxl_mbox_get_feat_in pi; struct cxl_mbox_cmd mbox_cmd; size_t data_rcvd_size = 0; @@ -237,9 +237,10 @@ size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, if (!feat_out || !feat_out_size) return 0; - size_out = min(feat_out_size, cxl_mbox->payload_size); uuid_copy(&pi.uuid, feat_uuid); pi.selection = selection; + + guard(mutex)(&cxl_mbox->feat_mutex); do { data_to_rd_size = min(feat_out_size - data_rcvd_size, cxl_mbox->payload_size); @@ -250,7 +251,7 @@ size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, .opcode = CXL_MBOX_OP_GET_FEATURE, .size_in = sizeof(pi), .payload_in = &pi, - .size_out = size_out, + .size_out = data_to_rd_size, .payload_out = feat_out + data_rcvd_size, .min_out = data_to_rd_size, }; @@ -314,6 +315,7 @@ int cxl_set_feature(struct cxl_mailbox *cxl_mbox, data_in_size = cxl_mbox->payload_size - hdr_size; } + guard(mutex)(&cxl_mbox->feat_mutex); do { int rc; @@ -471,6 +473,10 @@ static void *cxlctl_get_feature(struct cxl_features_state *cxlfs, if (!count) return ERR_PTR(-EINVAL); + if (out_size < offsetof(struct fwctl_rpc_cxl_out, payload) || + count > out_size - offsetof(struct fwctl_rpc_cxl_out, payload)) + return ERR_PTR(-EINVAL); + struct fwctl_rpc_cxl_out *rpc_out __free(kvfree) = kvzalloc(out_size, GFP_KERNEL); if (!rpc_out) @@ -516,6 +522,9 @@ static void *cxlctl_set_feature(struct cxl_features_state *cxlfs, flags = le32_to_cpu(feat_in->flags); out_size = *out_len; + if (out_size < offsetof(struct fwctl_rpc_cxl_out, payload)) + return ERR_PTR(-EINVAL); + struct fwctl_rpc_cxl_out *rpc_out __free(kvfree) = kvzalloc(out_size, GFP_KERNEL); if (!rpc_out) @@ -649,7 +658,13 @@ static void *cxlctl_fw_rpc(struct fwctl_uctx *uctx, enum fwctl_rpc_scope scope, struct cxl_memdev *cxlmd = fwctl_to_memdev(fwctl_dev); struct cxl_features_state *cxlfs = to_cxlfs(cxlmd->cxlds); const struct fwctl_rpc_cxl *rpc_in = in; - u16 opcode = rpc_in->opcode; + u16 opcode; + + if (in_len < sizeof(rpc_in->hdr) || + rpc_in->op_size > in_len - sizeof(rpc_in->hdr)) + return ERR_PTR(-EINVAL); + + opcode = rpc_in->opcode; if (!cxlctl_validate_hw_command(cxlfs, rpc_in, scope, opcode)) return ERR_PTR(-EINVAL); diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 97b1e61ad018..55828a836c01 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -11,7 +11,6 @@ #include "core.h" #include "trace.h" -#include "mce.h" static bool cxl_raw_allow_all; @@ -91,6 +90,10 @@ static struct cxl_mem_command cxl_mem_commands[CXL_MEM_COMMAND_ID_MAX] = { * * CXL_MBOX_OP_[GET_,INJECT_,CLEAR_]POISON: These commands require kernel * driver orchestration for safety. + * + * CXL_MBOX_OP_[GET_SUPPORTED_FEATURES,GET_FEATURE,SET_FEATURE]: Features are + * accessed through the fwctl ABI, which applies scope-based access control. + * The RAW path would bypass those checks, so it is not permitted here. */ static u16 cxl_disabled_raw_commands[] = { CXL_MBOX_OP_ACTIVATE_FW, @@ -102,6 +105,9 @@ static u16 cxl_disabled_raw_commands[] = { CXL_MBOX_OP_GET_POISON, CXL_MBOX_OP_INJECT_POISON, CXL_MBOX_OP_CLEAR_POISON, + CXL_MBOX_OP_GET_SUPPORTED_FEATURES, + CXL_MBOX_OP_GET_FEATURE, + CXL_MBOX_OP_SET_FEATURE, }; /* @@ -380,11 +386,7 @@ static int cxl_mbox_cmd_ctor(struct cxl_mbox_cmd *mbox_cmd, } } - /* Prepare to handle a full payload for variable sized output */ - if (out_size == CXL_VARIABLE_PAYLOAD) - mbox_cmd->size_out = cxl_mbox->payload_size; - else - mbox_cmd->size_out = out_size; + mbox_cmd->size_out = min_t(size_t, out_size, cxl_mbox->payload_size); if (mbox_cmd->size_out) { mbox_cmd->payload_out = kvzalloc(mbox_cmd->size_out, GFP_KERNEL); @@ -1406,6 +1408,11 @@ int cxl_mem_get_poison(struct cxl_memdev *cxlmd, u64 offset, u64 len, if (rc) break; + if (!le16_to_cpu(po->count)) { + dev_dbg(&cxlmd->dev, "Poison empty payload!\n"); + break; + } + for (int i = 0; i < le16_to_cpu(po->count); i++) trace_cxl_poison(cxlmd, cxlr, &po->record[i], po->flags, po->overflow_ts, @@ -1467,6 +1474,7 @@ int cxl_mailbox_init(struct cxl_mailbox *cxl_mbox, struct device *host) cxl_mbox->host = host; mutex_init(&cxl_mbox->mbox_mutex); + mutex_init(&cxl_mbox->feat_mutex); rcuwait_init(&cxl_mbox->mbox_wait); return 0; @@ -1477,7 +1485,6 @@ struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial, u16 dvsec) { struct cxl_memdev_state *mds; - int rc; mds = devm_cxl_dev_state_create(dev, CXL_DEVTYPE_CLASSMEM, serial, dvsec, struct cxl_memdev_state, cxlds, @@ -1489,12 +1496,6 @@ struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial, mutex_init(&mds->event.log_lock); - rc = devm_cxl_register_mce_notifier(dev, &mds->mce_notifier); - if (rc == -EOPNOTSUPP) - dev_warn(dev, "CXL MCE unsupported\n"); - else if (rc) - return ERR_PTR(rc); - return mds; } EXPORT_SYMBOL_NS_GPL(cxl_memdev_state_create, "CXL"); diff --git a/drivers/cxl/core/mce.c b/drivers/cxl/core/mce.c index ff8d078c6ca1..65fed913b221 100644 --- a/drivers/cxl/core/mce.c +++ b/drivers/cxl/core/mce.c @@ -4,16 +4,16 @@ #include #include #include -#include +#include +#include "core.h" #include "mce.h" static int cxl_handle_mce(struct notifier_block *nb, unsigned long val, void *data) { - struct cxl_memdev_state *mds = container_of(nb, struct cxl_memdev_state, - mce_notifier); - struct cxl_memdev *cxlmd = mds->cxlds.cxlmd; - struct cxl_port *endpoint = cxlmd->endpoint; + struct cxl_region *cxlr = container_of(nb, struct cxl_region, + mce_notifier); + struct cxl_region_params *p = &cxlr->params; struct mce *mce = data; u64 spa, spa_alias; unsigned long pfn; @@ -21,26 +21,25 @@ static int cxl_handle_mce(struct notifier_block *nb, unsigned long val, if (!mce || !mce_usable_address(mce)) return NOTIFY_DONE; - if (!endpoint) - return NOTIFY_DONE; - spa = mce->addr & MCI_ADDR_PHYSADDR; - pfn = spa >> PAGE_SHIFT; - if (!pfn_valid(pfn)) + if (!cxl_resource_contains_addr(p->res, spa)) return NOTIFY_DONE; - spa_alias = cxl_port_get_spa_cache_alias(endpoint, spa); - if (spa_alias == ~0ULL) - return NOTIFY_DONE; + if (spa >= p->res->start + p->cache_size) + spa_alias = spa - p->cache_size; + else + spa_alias = spa + p->cache_size; pfn = spa_alias >> PAGE_SHIFT; + if (!pfn_valid(pfn)) + return NOTIFY_DONE; /* * Take down the aliased memory page. The original memory page flagged * by the MCE will be taken cared of by the standard MCE handler. */ - dev_emerg(mds->cxlds.dev, "Offlining aliased SPA address0: %#llx\n", + dev_emerg(&cxlr->dev, "Offlining aliased SPA address0: %#llx\n", spa_alias); if (!memory_failure(pfn, 0)) set_mce_nospec(pfn); diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index 2e457b1ebc7d..b3419df586b9 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -988,7 +988,7 @@ static enum fw_upload_err cxl_fw_prepare(struct fw_upload *fwl, const u8 *data, if (!size) return FW_UPLOAD_ERR_INVALID_SIZE; - mds->fw.oneshot = struct_size(transfer, data, size) < + mds->fw.oneshot = struct_size(transfer, data, size) <= cxl_mbox->payload_size; if (cxl_mem_get_fw_info(mds)) diff --git a/drivers/cxl/core/pmem.c b/drivers/cxl/core/pmem.c index 68462e38a977..5a3bb7e8a1f1 100644 --- a/drivers/cxl/core/pmem.c +++ b/drivers/cxl/core/pmem.c @@ -219,12 +219,14 @@ static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_nvdimm_bridge *cxl_nvb, dev->bus = &cxl_bus_type; dev->type = &cxl_nvdimm_type; /* - * A "%llx" string is 17-bytes vs dimm_id that is max - * NVDIMM_KEY_DESC_LEN + * dev_id is the nvdimm dimm_id used for security key lookup. + * It must match id_show(), which emits the CXL serial as an + * unsigned decimal. A u64 decimal string is at most 20 digits + * plus NUL. */ - BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 17 || + BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 21 || sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN); - sprintf(cxl_nvd->dev_id, "%llx", cxlmd->cxlds->serial); + sprintf(cxl_nvd->dev_id, "%llu", cxlmd->cxlds->serial); return cxl_nvd; } diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c index cb633e19151b..625e4aa427db 100644 --- a/drivers/cxl/core/port.c +++ b/drivers/cxl/core/port.c @@ -933,11 +933,10 @@ struct cxl_port *devm_cxl_add_port(struct device *host, parent_port = parent_dport ? parent_dport->port : NULL; if (IS_ERR(port)) { - dev_dbg(uport_dev, "Failed to add%s%s%s: %ld\n", + dev_dbg(uport_dev, "Failed to add%s%s%s: %pe\n", parent_port ? " port to " : "", parent_port ? dev_name(&parent_port->dev) : "", - parent_port ? "" : " root port", - PTR_ERR(port)); + parent_port ? "" : " root port", port); } else { dev_dbg(uport_dev, "%s added%s%s%s\n", dev_name(&port->dev), @@ -1272,8 +1271,8 @@ struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port, dport = __devm_cxl_add_dport(port, dport_dev, port_id, component_reg_phys, CXL_RESOURCE_NONE); if (IS_ERR(dport)) { - dev_dbg(dport_dev, "failed to add dport to %s: %ld\n", - dev_name(&port->dev), PTR_ERR(dport)); + dev_dbg(dport_dev, "failed to add dport to %s: %pe\n", + dev_name(&port->dev), dport); } else { dev_dbg(dport_dev, "dport added to %s\n", dev_name(&port->dev)); @@ -1306,8 +1305,8 @@ struct cxl_dport *devm_cxl_add_rch_dport(struct cxl_port *port, dport = __devm_cxl_add_dport(port, dport_dev, port_id, CXL_RESOURCE_NONE, rcrb); if (IS_ERR(dport)) { - dev_dbg(dport_dev, "failed to add RCH dport to %s: %ld\n", - dev_name(&port->dev), PTR_ERR(dport)); + dev_dbg(dport_dev, "failed to add RCH dport to %s: %pe\n", + dev_name(&port->dev), dport); } else { dev_dbg(dport_dev, "RCH dport added to %s\n", dev_name(&port->dev)); @@ -1380,7 +1379,7 @@ static int match_port_by_dport(struct device *dev, const void *data) return dport != NULL; } -static struct cxl_port *__find_cxl_port(struct cxl_find_port_ctx *ctx) +static struct cxl_port *__find_cxl_port_by_dport(struct cxl_find_port_ctx *ctx) { struct device *dev; @@ -1393,8 +1392,16 @@ static struct cxl_port *__find_cxl_port(struct cxl_find_port_ctx *ctx) return NULL; } -static struct cxl_port *find_cxl_port(struct device *dport_dev, - struct cxl_dport **dport) +/** + * find_cxl_port_by_dport - find a cxl_port by one of its targets + * @dport_dev: device representing the dport target + * @dport: optional output of the 'struct cxl_dport' companion of the @dport_dev + * + * Return a 'struct cxl_port' with an elevated reference if found. Use + * __free(put_cxl_port) to release. + */ +static struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev, + struct cxl_dport **dport) { struct cxl_find_port_ctx ctx = { .dport_dev = dport_dev, @@ -1402,7 +1409,7 @@ static struct cxl_port *find_cxl_port(struct device *dport_dev, }; struct cxl_port *port; - port = __find_cxl_port(&ctx); + port = __find_cxl_port_by_dport(&ctx); return port; } @@ -1750,8 +1757,8 @@ static int add_port_attach_ep(struct cxl_memdev *cxlmd, parent_dport, uport_dev, dport_dev); if (IS_ERR(dport)) { - /* Port already exists, restart iteration */ - if (PTR_ERR(dport) == -EAGAIN) + /* Port or dport already exists, restart iteration */ + if (PTR_ERR(dport) == -EAGAIN || PTR_ERR(dport) == -EBUSY) return 0; return PTR_ERR(dport); } @@ -1896,14 +1903,14 @@ EXPORT_SYMBOL_NS_GPL(devm_cxl_enumerate_ports, "CXL"); struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev, struct cxl_dport **dport) { - return find_cxl_port(pdev->dev.parent, dport); + return find_cxl_port_by_dport(pdev->dev.parent, dport); } EXPORT_SYMBOL_NS_GPL(cxl_pci_find_port, "CXL"); struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd, struct cxl_dport **dport) { - return find_cxl_port(grandparent(&cxlmd->dev), dport); + return find_cxl_port_by_dport(grandparent(&cxlmd->dev), dport); } EXPORT_SYMBOL_NS_GPL(cxl_mem_find_port, "CXL"); @@ -2532,14 +2539,10 @@ static __init int cxl_core_init(void) if (rc) goto err_region; - rc = cxl_ras_init(); - if (rc) - goto err_ras; + cxl_ras_init(); return 0; -err_ras: - cxl_region_exit(); err_region: bus_unregister(&cxl_bus_type); err_bus: diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index 99fb00949c2f..e307361bb39e 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -129,15 +129,14 @@ static void cxl_cper_prot_err_work_fn(struct work_struct *work) } static DECLARE_WORK(cxl_cper_prot_err_work, cxl_cper_prot_err_work_fn); -int cxl_ras_init(void) +void cxl_ras_init(void) { - return cxl_cper_register_prot_err_work(&cxl_cper_prot_err_work); + cxl_cper_register_prot_err_work(&cxl_cper_prot_err_work); } void cxl_ras_exit(void) { - cxl_cper_unregister_prot_err_work(&cxl_cper_prot_err_work); - cancel_work_sync(&cxl_cper_prot_err_work); + cxl_cper_unregister_prot_err_work(); } static void cxl_dport_map_ras(struct cxl_dport *dport) diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c index 0a8b3b9b6388..e0e01aa5eba6 100644 --- a/drivers/cxl/core/ras_rch.c +++ b/drivers/cxl/core/ras_rch.c @@ -58,13 +58,28 @@ void cxl_disable_rch_root_ints(struct cxl_dport *dport) static bool cxl_rch_get_aer_info(void __iomem *aer_base, struct aer_capability_regs *aer_regs) { - int read_cnt = sizeof(struct aer_capability_regs) / sizeof(u32); + /* + * Bound the copy to the physically-defined AER registers (header + * through the 16-byte Header Log). struct aer_capability_regs is a + * software layout whose embedded struct pcie_tlp_log is larger than + * the on-wire AER capability; copying sizeof(*aer_regs) would + * over-read the RCRB-mapped MMIO block. + */ + int read_cnt = (PCI_ERR_HEADER_LOG + 16) / sizeof(u32); u32 *aer_regs_buf = (u32 *)aer_regs; int n; if (!aer_base) return false; + /* + * Zero the destination so the software-only tail fields + * (e.g. header_log.header_len) are deterministic rather than + * left as uninitialized stack, which could drive a bogus loop + * length in pcie_print_tlp_log(). + */ + memset(aer_regs, 0, sizeof(*aer_regs)); + /* Use readl() to guarantee 32-bit accesses */ for (n = 0; n < read_cnt; n++) aer_regs_buf[n] = readl(aer_base + n * sizeof(u32)); @@ -79,11 +94,11 @@ static bool cxl_rch_get_aer_info(void __iomem *aer_base, static bool cxl_rch_get_aer_severity(struct aer_capability_regs *aer_regs, int *severity) { - if (aer_regs->uncor_status & ~aer_regs->uncor_mask) { - if (aer_regs->uncor_status & PCI_ERR_ROOT_FATAL_RCV) - *severity = AER_FATAL; - else - *severity = AER_NONFATAL; + u32 uncor_status = aer_regs->uncor_status & ~aer_regs->uncor_mask; + + if (uncor_status) { + *severity = (uncor_status & aer_regs->uncor_severity) ? + AER_FATAL : AER_NONFATAL; return true; } diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c index 1e211542b6b6..27e63e6dab7c 100644 --- a/drivers/cxl/core/region.c +++ b/drivers/cxl/core/region.c @@ -15,6 +15,7 @@ #include #include #include "core.h" +#include "mce.h" /** * DOC: cxl core region @@ -682,8 +683,8 @@ static int alloc_hpa(struct cxl_region *cxlr, resource_size_t size) dev_name(&cxlr->dev)); if (IS_ERR(res)) { dev_dbg(&cxlr->dev, - "HPA allocation error (%ld) for size:%pap in %s %pr\n", - PTR_ERR(res), &size, cxlrd->res->name, cxlrd->res); + "HPA allocation error (%pe) for size:%pap in %s %pr\n", + res, &size, cxlrd->res->name, cxlrd->res); return PTR_ERR(res); } @@ -1913,15 +1914,14 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range, { struct cxl_switch_decoder *cxlsd; struct cxl_port *parent; - struct device *dev; int rc = -ENXIO; parent = parent_port_of(port); if (!parent) return rc; - dev = device_find_child(&parent->dev, range, - match_switch_decoder_by_range); + struct device *dev __free(put_device) = + device_find_child(&parent->dev, range, match_switch_decoder_by_range); if (!dev) { dev_err(port->uport_dev, "failed to find decoder mapping %#llx-%#llx\n", @@ -1938,14 +1938,11 @@ static int find_pos_and_ways(struct cxl_port *port, struct range *range, break; } } - put_device(dev); - if (rc) dev_err(port->uport_dev, "failed to find %s:%s in target list of %s\n", dev_name(&port->dev), - dev_name(port->parent_dport->dport_dev), - dev_name(&cxlsd->cxld.dev)); + dev_name(port->parent_dport->dport_dev), dev_name(dev)); return rc; } @@ -3773,9 +3770,9 @@ static struct cxl_region *construct_region(struct cxl_root_decoder *cxlrd, if (IS_ERR(cxlr)) { dev_err(cxlmd->dev.parent, - "%s:%s: %s failed assign region: %ld\n", + "%s:%s: %s failed assign region: %pe\n", dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev), - __func__, PTR_ERR(cxlr)); + __func__, cxlr); return cxlr; } @@ -3859,34 +3856,6 @@ int cxl_add_to_region(struct cxl_endpoint_decoder *cxled) } EXPORT_SYMBOL_NS_GPL(cxl_add_to_region, "CXL"); -u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, u64 spa) -{ - struct cxl_region_ref *iter; - unsigned long index; - - if (!endpoint) - return ~0ULL; - - guard(rwsem_write)(&cxl_rwsem.region); - - xa_for_each(&endpoint->regions, index, iter) { - struct cxl_region_params *p = &iter->region->params; - - if (cxl_resource_contains_addr(p->res, spa)) { - if (!p->cache_size) - return ~0ULL; - - if (spa >= p->res->start + p->cache_size) - return spa - p->cache_size; - - return spa + p->cache_size; - } - } - - return ~0ULL; -} -EXPORT_SYMBOL_NS_GPL(cxl_port_get_spa_cache_alias, "CXL"); - static int is_system_ram(struct resource *res, void *arg) { struct cxl_region *cxlr = arg; @@ -4217,6 +4186,20 @@ static int cxl_region_probe(struct device *dev) if (rc) return rc; + /* + * Regions fronted by an extended linear cache need the MCE notifier to + * offline the aliased page on a memory error. + */ + if (p->cache_size) { + rc = devm_cxl_register_mce_notifier(&cxlr->dev, + &cxlr->mce_notifier); + if (rc == -EOPNOTSUPP) + dev_warn(&cxlr->dev, + "CONFIG_CXL_MCE disabled, MCE notifier not registered\n"); + else if (rc) + return rc; + } + rc = cxl_region_setup_poison(cxlr); if (rc) return rc; diff --git a/drivers/cxl/core/trace.h b/drivers/cxl/core/trace.h index d37876096dd7..c379d60047fc 100644 --- a/drivers/cxl/core/trace.h +++ b/drivers/cxl/core/trace.h @@ -107,7 +107,7 @@ TRACE_EVENT(cxl_aer_uncorrectable_error, memcpy(__entry->header_log, hl, CXL_HEADERLOG_TRACE_SIZE_U32 * sizeof(u32)); ), - TP_printk("memdev=%s host=%s serial=%lld: status: '%s' first_error: '%s'", + TP_printk("memdev=%s host=%s serial=%llu: status: '%s' first_error: '%s'", __get_str(memdev), __get_str(host), __entry->serial, show_uc_errs(__entry->status), show_uc_errs(__entry->first_error) @@ -166,7 +166,7 @@ TRACE_EVENT(cxl_aer_correctable_error, __entry->serial = cxlmd->cxlds->serial; __entry->status = status; ), - TP_printk("memdev=%s host=%s serial=%lld: status: '%s'", + TP_printk("memdev=%s host=%s serial=%llu: status: '%s'", __get_str(memdev), __get_str(host), __entry->serial, show_ce_errs(__entry->status) ) @@ -206,7 +206,7 @@ TRACE_EVENT(cxl_overflow, __entry->last_ts = le64_to_cpu(payload->last_overflow_timestamp); ), - TP_printk("memdev=%s host=%s serial=%lld: log=%s : %u records from %llu to %llu", + TP_printk("memdev=%s host=%s serial=%llu: log=%s : %u records from %llu to %llu", __get_str(memdev), __get_str(host), __entry->serial, cxl_event_log_type_str(__entry->log), __entry->count, __entry->first_ts, __entry->last_ts) @@ -279,7 +279,7 @@ TRACE_EVENT(cxl_overflow, __entry->hdr_head_id = (hdr).head_id #define CXL_EVT_TP_printk(fmt, ...) \ - TP_printk("memdev=%s host=%s serial=%lld log=%s : time=%llu uuid=%pUb " \ + TP_printk("memdev=%s host=%s serial=%llu log=%s : time=%llu uuid=%pUb " \ "len=%d flags='%s' handle=%x related_handle=%x " \ "maint_op_class=%u maint_op_sub_class=%u " \ "ld_id=%x head_id=%x : " fmt, \ @@ -1088,7 +1088,7 @@ TRACE_EVENT(cxl_poison, } ), - TP_printk("memdev=%s host=%s serial=%lld trace_type=%s region=%s " \ + TP_printk("memdev=%s host=%s serial=%llu trace_type=%s region=%s " \ "region_uuid=%pU hpa=0x%llx hpa_alias0=0x%llx dpa=0x%llx " \ "dpa_length=0x%x source=%s flags=%s overflow_time=%llu", __get_str(memdev), diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h index c0e5308e4d1b..cab8ce39f465 100644 --- a/drivers/cxl/cxl.h +++ b/drivers/cxl/cxl.h @@ -478,6 +478,7 @@ struct cxl_region_params { * @coord: QoS access coordinates for the region * @node_notifier: notifier for setting the access coordinates to node * @adist_notifier: notifier for calculating the abstract distance of node + * @mce_notifier: notifier for MCE */ struct cxl_region { struct device dev; @@ -493,6 +494,7 @@ struct cxl_region { struct access_coordinate coord[ACCESS_COORDINATE_MAX]; struct notifier_block node_notifier; struct notifier_block adist_notifier; + struct notifier_block mce_notifier; }; struct cxl_nvdimm_bridge { @@ -503,7 +505,8 @@ struct cxl_nvdimm_bridge { struct nvdimm_bus_descriptor nd_desc; }; -#define CXL_DEV_ID_LEN 19 +/* Holds a u64 serial as a decimal string: up to 20 digits + NUL */ +#define CXL_DEV_ID_LEN 21 enum { CXL_NVD_F_INVALIDATED = 0, @@ -870,7 +873,6 @@ bool is_cxl_pmem_region(struct device *dev); struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev); int cxl_add_to_region(struct cxl_endpoint_decoder *cxled); struct cxl_dax_region *to_cxl_dax_region(struct device *dev); -u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, u64 spa); bool cxl_region_contains_resource(const struct resource *res); #else static inline bool is_cxl_pmem_region(struct device *dev) @@ -889,11 +891,6 @@ static inline struct cxl_dax_region *to_cxl_dax_region(struct device *dev) { return NULL; } -static inline u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, - u64 spa) -{ - return 0; -} static inline bool cxl_region_contains_resource(const struct resource *res) { return false; diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index ed419d0c59f2..c401e3a1af06 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -183,7 +183,7 @@ static inline struct cxl_ep *cxl_ep_load(struct cxl_port *port, C(MBUNSUPPORTED, -ENXIO, "unsupported on the mailbox it was issued on"),\ C(PAYLOADLEN, -ENXIO, "invalid payload length"), \ C(LOG, -ENXIO, "invalid or unsupported log page"), \ - C(INTERRUPTED, -ENXIO, "asynchronous event occured"), \ + C(INTERRUPTED, -ENXIO, "asynchronous event occurred"), \ C(FEATUREVERSION, -ENXIO, "unsupported feature version"), \ C(FEATURESELVALUE, -ENXIO, "unsupported feature selection value"), \ C(FEATURETRANSFERIP, -ENXIO, "feature transfer in progress"), \ @@ -431,7 +431,6 @@ static inline struct cxl_dev_state *mbox_to_cxlds(struct cxl_mailbox *cxl_mbox) * @poison: poison driver state info * @security: security driver state info * @fw: firmware upload / activation state - * @mce_notifier: MCE notifier * * See CXL 3.0 8.2.9.8.2 Capacity Configuration and Label Storage for * details on capacity parameters. @@ -451,7 +450,6 @@ struct cxl_memdev_state { struct cxl_poison_state poison; struct cxl_security_state security; struct cxl_fw_state fw; - struct notifier_block mce_notifier; }; static inline struct cxl_memdev_state * diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index bb892dbfdd6d..c7c91e8dc51d 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -692,12 +692,6 @@ static int cxl_pci_type3_init_mailbox(struct cxl_dev_state *cxlds) { int rc; - /* - * Fail the init if there's no mailbox. For a type3 this is out of spec. - */ - if (!cxlds->reg_map.device_map.mbox.valid) - return -ENODEV; - rc = cxl_mailbox_init(&cxlds->cxl_mbox, cxlds->dev); if (rc) return rc; @@ -830,10 +824,13 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) */ rc = cxl_pci_setup_regs(pdev, CXL_REGLOC_RBI_COMPONENT, &cxlds->reg_map); - if (rc) + if (rc) { + if (rc == -EPROBE_DEFER) + return rc; dev_warn(&pdev->dev, "No component registers (%d)\n", rc); - else if (!cxlds->reg_map.component_map.ras.valid) + } else if (!cxlds->reg_map.component_map.ras.valid) { dev_dbg(&pdev->dev, "RAS registers not found\n"); + } rc = cxl_pci_type3_init_mailbox(cxlds); if (rc) @@ -1084,7 +1081,6 @@ static int __init cxl_pci_driver_init(void) static void __exit cxl_pci_driver_exit(void) { cxl_cper_unregister_work(&cxl_cper_work); - cancel_work_sync(&cxl_cper_work); pci_unregister_driver(&cxl_pci_driver); } diff --git a/drivers/cxl/pmem.c b/drivers/cxl/pmem.c index 261dff7ced9f..a9f50281875d 100644 --- a/drivers/cxl/pmem.c +++ b/drivers/cxl/pmem.c @@ -52,7 +52,7 @@ static ssize_t id_show(struct device *dev, struct device_attribute *attr, char * struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm); struct cxl_dev_state *cxlds = cxl_nvd->cxlmd->cxlds; - return sysfs_emit(buf, "%lld\n", cxlds->serial); + return sysfs_emit(buf, "%llu\n", cxlds->serial); } static DEVICE_ATTR_RO(id); diff --git a/include/cxl/event.h b/include/cxl/event.h index ff97fea718d2..b5673384d930 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -287,10 +287,10 @@ struct cxl_cper_prot_err_work_data { #ifdef CONFIG_ACPI_APEI_GHES int cxl_cper_register_work(struct work_struct *work); -int cxl_cper_unregister_work(struct work_struct *work); +void cxl_cper_unregister_work(struct work_struct *work); int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd); -int cxl_cper_register_prot_err_work(struct work_struct *work); -int cxl_cper_unregister_prot_err_work(struct work_struct *work); +void cxl_cper_register_prot_err_work(struct work_struct *work); +void cxl_cper_unregister_prot_err_work(void); int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd); #else static inline int cxl_cper_register_work(struct work_struct *work) @@ -298,22 +298,13 @@ static inline int cxl_cper_register_work(struct work_struct *work) return 0; } -static inline int cxl_cper_unregister_work(struct work_struct *work) -{ - return 0; -} +static inline void cxl_cper_unregister_work(struct work_struct *work) { } static inline int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd) { return 0; } -static inline int cxl_cper_register_prot_err_work(struct work_struct *work) -{ - return 0; -} -static inline int cxl_cper_unregister_prot_err_work(struct work_struct *work) -{ - return 0; -} +static inline void cxl_cper_register_prot_err_work(struct work_struct *work) { } +static inline void cxl_cper_unregister_prot_err_work(void) { } static inline int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd) { return 0; diff --git a/include/cxl/mailbox.h b/include/cxl/mailbox.h index c4e99e2e3a9d..d008b9db07aa 100644 --- a/include/cxl/mailbox.h +++ b/include/cxl/mailbox.h @@ -50,6 +50,7 @@ struct cxl_mbox_cmd { * @payload_size: Size of space for payload * (CXL 3.1 8.2.8.4.3 Mailbox Capabilities Register) * @mbox_mutex: mutex protects device mailbox and firmware + * @feat_mutex: serializes multi-part Get/Set Feature transfers * @mbox_wait: rcuwait for mailbox * @mbox_send: @dev specific transport for transmitting mailbox commands * @feat_cap: Features capability @@ -60,6 +61,7 @@ struct cxl_mailbox { DECLARE_BITMAP(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX); size_t payload_size; struct mutex mbox_mutex; /* lock to protect mailbox context */ + struct mutex feat_mutex; struct rcuwait mbox_wait; int (*mbox_send)(struct cxl_mailbox *cxl_mbox, struct cxl_mbox_cmd *cmd); enum cxl_features_capability feat_cap; diff --git a/tools/testing/cxl/test/Kbuild b/tools/testing/cxl/test/Kbuild index c168e3c998a7..9a24ddc28488 100644 --- a/tools/testing/cxl/test/Kbuild +++ b/tools/testing/cxl/test/Kbuild @@ -5,10 +5,12 @@ obj-m += cxl_test.o obj-m += cxl_mock.o obj-m += cxl_mock_mem.o obj-m += cxl_translate.o +obj-m += cxl_mock_accel.o cxl_test-y := cxl.o cxl_test-y += hmem_test.o cxl_mock-y := mock.o cxl_mock_mem-y := mem.o +cxl_mock_accel-y := accel.o KBUILD_CFLAGS := $(filter-out -Wmissing-prototypes -Wmissing-declarations, $(KBUILD_CFLAGS)) diff --git a/tools/testing/cxl/test/accel.c b/tools/testing/cxl/test/accel.c new file mode 100644 index 000000000000..8e6f4687ca02 --- /dev/null +++ b/tools/testing/cxl/test/accel.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2026 Intel Corporation. All rights reserved. + +#include +#include +#include +#include +#include +#include + +struct mock_cxl_accel { + struct cxl_dev_state cxlds; + struct cxl_memdev *cxlmd; +}; + +static int cxl_mock_accel_probe(struct platform_device *pdev) +{ + struct mock_cxl_accel *cxl_accel; + struct device *dev = &pdev->dev; + struct cxl_dev_state *cxlds; + struct cxl_memdev *cxlmd; + struct range mock_range; + int rc; + + cxl_accel = devm_cxl_dev_state_create(&pdev->dev, CXL_DEVTYPE_DEVMEM, + pdev->id + 1, 0, + struct mock_cxl_accel, cxlds, + false); + if (!cxl_accel) + return -ENOMEM; + + cxlds = &cxl_accel->cxlds; + cxlds->media_ready = true; + rc = cxl_set_capacity(cxlds, SZ_512M); + if (rc) + return rc; + + cxlmd = devm_cxl_probe_mem(cxlds, &mock_range); + if (IS_ERR(cxlmd)) + return PTR_ERR(cxlmd); + cxl_accel->cxlmd = cxlmd; + + dev_dbg(dev, "Probed mock accelerator with range %pra\n", &mock_range); + + return 0; +} + +static const struct platform_device_id cxl_mock_accel_ids[] = { + { .name = "cxl_type2_accel" }, + { } +}; +MODULE_DEVICE_TABLE(platform, cxl_mock_accel_ids); + +static struct platform_driver cxl_mock_accel_driver = { + .probe = cxl_mock_accel_probe, + .id_table = cxl_mock_accel_ids, + .driver = { + .name = KBUILD_MODNAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +module_platform_driver(cxl_mock_accel_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("cxl_test: accelerator device mock module"); +MODULE_IMPORT_NS("CXL"); diff --git a/tools/testing/cxl/test/cxl.c b/tools/testing/cxl/test/cxl.c index ef92dd35e030..62bd92b3be45 100644 --- a/tools/testing/cxl/test/cxl.c +++ b/tools/testing/cxl/test/cxl.c @@ -17,6 +17,7 @@ static int interleave_arithmetic; static bool extended_linear_cache; static bool fail_autoassemble; +static bool type2_test; #define FAKE_QTG_ID 42 @@ -27,6 +28,7 @@ static bool fail_autoassemble; #define NR_CXL_SWITCH_PORTS 2 #define NR_CXL_PORT_DECODERS 8 #define NR_BRIDGES (NR_CXL_HOST_BRIDGES + NR_CXL_SINGLE_HOST + NR_CXL_RCH) +#define NR_CXL_TYPE2_ACCEL 1 #define MOCK_AUTO_REGION_SIZE_DEFAULT SZ_512M static int mock_auto_region_size = MOCK_AUTO_REGION_SIZE_DEFAULT; @@ -384,6 +386,19 @@ static struct { }, }; +static struct acpi_cedt_cfmws type2_cfmws0 = { + .header = { + .type = ACPI_CEDT_TYPE_CFMWS, + .length = sizeof(mock_cedt.cfmws0), + }, + .interleave_ways = 0, + .granularity = 4, + .restrictions = ACPI_CEDT_CFMWS_RESTRICT_DEVMEM | + ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, + .qtg_id = FAKE_QTG_ID, + .window_size = SZ_256M * 4, +}; + struct acpi_cedt_cfmws *mock_cfmws[] = { [0] = &mock_cedt.cfmws0.cfmws, [1] = &mock_cedt.cfmws1.cfmws, @@ -476,6 +491,11 @@ static void cfmws_elc_update(struct acpi_cedt_cfmws *window, int index) window->window_size = mock_auto_region_size * 2; } +static void update_type2_cfmws(void) +{ + memcpy(&mock_cedt.cfmws0.cfmws, &type2_cfmws0, sizeof(type2_cfmws0)); +} + static int populate_cedt(void) { struct cxl_mock_res *res; @@ -497,11 +517,15 @@ static int populate_cedt(void) chbs->length = size; } + if (type2_test) + update_type2_cfmws(); + for (i = cfmws_start; i <= cfmws_end; i++) { struct acpi_cedt_cfmws *window = mock_cfmws[i]; int align = SZ_256M; - cfmws_elc_update(window, i); + if (i == 0 && !type2_test) + cfmws_elc_update(window, i); if (window->restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE) align = max_t(int, SZ_256M, PMD_SIZE); res = alloc_mock_res(window->window_size, align); @@ -1060,74 +1084,26 @@ static int first_decoder(struct device *dev, const void *data) return 0; } -/* - * Initialize a decoder during HDM enumeration. - * - * If a saved registry entry exists: - * - enabled decoders are restored from the saved programming - * - disabled decoders are initialized in a clean disabled state - * - * If no registry entry exists the decoder follows the normal mock - * initialization path, including the special auto-region setup for - * the first endpoints under host-bridge0. - * - * Returns true if decoder state was restored from the registry. In - * that case the saved decode configuration (including target mapping) - * has already been applied and the map_targets() is skipped. - */ -static bool mock_init_hdm_decoder(struct cxl_decoder *cxld) +enum cxld_init_type { + MOCK_DECODER_INIT_DEFAULT, + MOCK_DECODER_INIT_SAVED, + MOCK_DECODER_INIT_TYPE3_AUTO, + MOCK_DECODER_INIT_TYPE2_AUTO, +}; + +static enum cxld_init_type get_decoder_init_type(struct cxl_decoder *cxld, + struct platform_device *pdev, + bool hb0, + struct cxl_test_decoder **td) { - struct acpi_cedt_cfmws *window = mock_cfmws[0]; - struct platform_device *pdev = NULL; - struct cxl_endpoint_decoder *cxled; - struct cxl_switch_decoder *cxlsd; - struct cxl_port *port, *iter; - struct cxl_test_decoder *td; - struct cxl_memdev *cxlmd; - struct cxl_dport *dport; - struct device *dev; - bool hb0 = false; - u64 base; - int i; + struct cxl_test_decoder *found_td = cxld_registry_find(cxld); - if (is_endpoint_decoder(&cxld->dev)) { - cxled = to_cxl_endpoint_decoder(&cxld->dev); - cxlmd = cxled_to_memdev(cxled); - WARN_ON(!dev_is_platform(cxlmd->dev.parent)); - pdev = to_platform_device(cxlmd->dev.parent); - - /* check is endpoint is attach to host-bridge0 */ - port = cxled_to_port(cxled); - do { - if (port->uport_dev == &cxl_host_bridge[0]->dev) { - hb0 = true; - break; - } - if (is_cxl_port(port->dev.parent)) - port = to_cxl_port(port->dev.parent); - else - port = NULL; - } while (port); - port = cxled_to_port(cxled); - } else { - port = to_cxl_port(cxld->dev.parent); + if (found_td) { + *td = found_td; + return MOCK_DECODER_INIT_SAVED; } - td = cxld_registry_find(cxld); - if (td) { - bool enabled; - - if (is_switch_decoder(&cxld->dev)) - enabled = td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE; - else - enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE; - - if (enabled) - return !cxld_registry_restore(cxld, td); - - init_disabled_mock_decoder(cxld); - return false; - } + *td = NULL; /* * The first decoder on the first 2 devices on the first switch @@ -1138,15 +1114,119 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld) * See 'cxl list -BMPu -m cxl_mem.0,cxl_mem.4' */ if (!is_endpoint_decoder(&cxld->dev) || !hb0 || pdev->id % 4 || - pdev->id > 4 || cxld->id > 0) { - default_mock_decoder(cxld); - return false; - } + pdev->id > 4 || cxld->id > 0) + return MOCK_DECODER_INIT_DEFAULT; + + return type2_test ? MOCK_DECODER_INIT_TYPE2_AUTO : + MOCK_DECODER_INIT_TYPE3_AUTO; +} + +static bool mock_decoder_handle_saved(struct cxl_decoder *cxld, struct cxl_test_decoder *td) +{ + bool enabled; + + if (is_switch_decoder(&cxld->dev)) + enabled = td->cxlsd.cxld.flags & CXL_DECODER_F_ENABLE; + else + enabled = td->cxled.cxld.flags & CXL_DECODER_F_ENABLE; + + if (enabled) + return !cxld_registry_restore(cxld, td); + + init_disabled_mock_decoder(cxld); + return false; +} + +static void mock_init_hdm_type2_cxled(struct cxl_endpoint_decoder *cxled, + struct cxl_port *port) +{ + struct acpi_cedt_cfmws *window = mock_cfmws[0]; + struct cxl_decoder *cxld = &cxled->cxld; + struct cxl_switch_decoder *cxlsd; + struct cxl_dport *dport; + struct cxl_port *root_port; + struct device *dev; + u64 base; + + base = window->base_hpa; + cxld->hpa_range = (struct range) { + .start = base, + .end = base + mock_auto_region_size - 1, + }; + + cxld->interleave_ways = 1; + eig_to_granularity(window->granularity, &cxld->interleave_granularity); + cxld->target_type = CXL_DECODER_DEVMEM; + cxld->flags = CXL_DECODER_F_ENABLE; + cxled->state = CXL_DECODER_STATE_AUTO; + port->commit_end = cxld->id; + devm_cxl_dpa_reserve(cxled, 0, + mock_auto_region_size / cxld->interleave_ways, 0); + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; + + WARN_ON_ONCE(!cxld_registry_new(cxld)); + /* + * Now that endpoint decoder is set up, walk up the hierarchy + * and setup the root port decoder targeting @cxlmd. + */ + dport = port->parent_dport; + root_port = dport->port; + dev = device_find_child(&root_port->dev, NULL, first_decoder); + /* + * Ancestor ports are guaranteed to be enumerated before + * @port, and all ports have at least one decoder. + */ + if (WARN_ON(!dev)) + return; + + cxlsd = to_cxl_switch_decoder(dev); + cxld = &cxlsd->cxld; + cxld->target_type = CXL_DECODER_DEVMEM; + cxld->flags = CXL_DECODER_F_ENABLE; + root_port->commit_end = 0; + cxld->interleave_ways = 1; + cxld->interleave_granularity = 4096; + cxld->target_map[0] = dport->port_id; + cxld->hpa_range = (struct range) { + .start = base, + .end = base + mock_auto_region_size - 1, + }; + cxld->commit = mock_decoder_commit; + cxld->reset = mock_decoder_reset; + + /* + * Only target_map[] is programmed above, mimicking + * firmware. On real hardware target[] is populated as + * dports enumerate, via update_decoder_targets(). The + * mock's dports are already bound by now, so fire that + * resolution explicitly here rather than stamping + * target[] directly. + */ + cxl_port_update_decoder_targets(root_port, dport); + + cxld_registry_update(cxld); + put_device(dev); +} + +static void mock_init_hdm_type3_cxled(struct cxl_endpoint_decoder *cxled, + struct cxl_port *port, + struct platform_device *pdev, + bool hb0) +{ + struct acpi_cedt_cfmws *window = mock_cfmws[0]; + struct cxl_decoder *cxld = &cxled->cxld; + struct cxl_switch_decoder *cxlsd; + struct cxl_dport *dport; + struct cxl_port *iter; + struct device *dev; + u64 base; + int i; /* Simulate missing cxl_mem.4 configuration */ if (hb0 && pdev->id == 4 && cxld->id == 0 && fail_autoassemble) { default_mock_decoder(cxld); - return false; + return; } base = window->base_hpa; @@ -1228,7 +1308,76 @@ static bool mock_init_hdm_decoder(struct cxl_decoder *cxld) cxld_registry_update(cxld); put_device(dev); } +} +/* + * Initialize a decoder during HDM enumeration. + * + * If a saved registry entry exists: + * - enabled decoders are restored from the saved programming + * - disabled decoders are initialized in a clean disabled state + * + * If no registry entry exists the decoder follows the normal mock + * initialization path, including the special auto-region setup for + * the first endpoints under host-bridge0. + * + * Returns true if decoder state was restored from the registry. In + * that case the saved decode configuration (including target mapping) + * has already been applied and the map_targets() is skipped. + */ +static bool mock_init_hdm_decoder(struct cxl_decoder *cxld) +{ + struct cxl_endpoint_decoder *cxled = NULL; + struct platform_device *pdev = NULL; + struct cxl_test_decoder *td; + struct cxl_memdev *cxlmd; + struct cxl_port *port; + bool hb0 = false; + + if (is_endpoint_decoder(&cxld->dev)) { + cxled = to_cxl_endpoint_decoder(&cxld->dev); + cxlmd = cxled_to_memdev(cxled); + WARN_ON(!dev_is_platform(cxlmd->dev.parent)); + pdev = to_platform_device(cxlmd->dev.parent); + + /* check is endpoint is attach to host-bridge0 */ + port = cxled_to_port(cxled); + do { + if (port->uport_dev == &cxl_host_bridge[0]->dev) { + hb0 = true; + break; + } + if (is_cxl_port(port->dev.parent)) + port = to_cxl_port(port->dev.parent); + else + port = NULL; + } while (port); + port = cxled_to_port(cxled); + } else { + port = to_cxl_port(cxld->dev.parent); + } + + switch (get_decoder_init_type(cxld, pdev, hb0, &td)) { + case MOCK_DECODER_INIT_SAVED: + if (WARN_ON(!td)) + return false; + return mock_decoder_handle_saved(cxld, td); + case MOCK_DECODER_INIT_DEFAULT: + /* + * The default path picks up all the decoders that are not + * endpoint. + */ + default_mock_decoder(cxld); + return false; + case MOCK_DECODER_INIT_TYPE3_AUTO: + mock_init_hdm_type3_cxled(cxled, port, pdev, hb0); + return false; + case MOCK_DECODER_INIT_TYPE2_AUTO: + mock_init_hdm_type2_cxled(cxled, port); + return false; + default: + return false; + } return false; } @@ -1563,8 +1712,10 @@ static __init int cxl_rch_topo_init(void) struct platform_device *pdev; pdev = platform_device_alloc("cxl_host_bridge", idx); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_bridge; + } mock_companion(adev, &pdev->dev); rc = cxl_mock_platform_device_add(pdev, &cxl_rch[i]); @@ -1618,8 +1769,10 @@ static __init int cxl_single_topo_init(void) pdev = platform_device_alloc("cxl_host_bridge", NR_CXL_HOST_BRIDGES + i); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_bridge; + } mock_companion(adev, &pdev->dev); rc = cxl_mock_platform_device_add(pdev, &cxl_hb_single[i]); @@ -1640,8 +1793,10 @@ static __init int cxl_single_topo_init(void) pdev = platform_device_alloc("cxl_root_port", NR_MULTI_ROOT + i); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_port; + } pdev->dev.parent = &bridge->dev; rc = cxl_mock_platform_device_add(pdev, &cxl_root_single[i]); @@ -1655,8 +1810,10 @@ static __init int cxl_single_topo_init(void) pdev = platform_device_alloc("cxl_switch_uport", NR_MULTI_ROOT + i); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_uport; + } pdev->dev.parent = &root_port->dev; rc = cxl_mock_platform_device_add(pdev, &cxl_swu_single[i]); @@ -1671,8 +1828,10 @@ static __init int cxl_single_topo_init(void) pdev = platform_device_alloc("cxl_switch_dport", i + NR_MEM_MULTI); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_dport; + } pdev->dev.parent = &uport->dev; rc = cxl_mock_platform_device_add(pdev, &cxl_swd_single[i]); @@ -1724,19 +1883,84 @@ static void cxl_single_topo_exit(void) } } -static void cxl_mem_exit(void) +static void cxl_type3_mem_exit(void) { + struct platform_device *pdev; int i; - for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--) + for (i = ARRAY_SIZE(cxl_rcd) - 1; i >= 0; i--) { + pdev = cxl_rcd[i]; + if (!pdev) + continue; platform_device_unregister(cxl_rcd[i]); - for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--) + } + + for (i = ARRAY_SIZE(cxl_mem_single) - 1; i >= 0; i--) { + pdev = cxl_mem_single[i]; + if (!pdev) + continue; platform_device_unregister(cxl_mem_single[i]); - for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) - platform_device_unregister(cxl_mem[i]); + } + + for (i = ARRAY_SIZE(cxl_mem) - 1; i >= 0; i--) { + pdev = cxl_mem[i]; + if (!pdev) + continue; + platform_device_unregister(pdev); + } } -static int cxl_mem_init(void) +static void cxl_type2_mem_exit(void) +{ + for (int i = NR_CXL_TYPE2_ACCEL - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_mem[i]; + + if (!pdev) + continue; + platform_device_unregister(pdev); + } +} + +static void cxl_mem_exit(void) +{ + if (type2_test) { + cxl_type2_mem_exit(); + return; + } + + cxl_type3_mem_exit(); +} + +static int cxl_type2_mem_init(void) +{ + int i, rc; + + for (i = 0; i < NR_CXL_TYPE2_ACCEL; i++) { + struct platform_device *dport = cxl_root_port[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_type2_accel", i); + if (!pdev) { + rc = -ENOMEM; + goto err_mem; + } + pdev->dev.parent = &dport->dev; + set_dev_node(&pdev->dev, i % 2); + + rc = cxl_mock_platform_device_add(pdev, &cxl_mem[i]); + if (rc) + goto err_mem; + } + + return 0; + +err_mem: + for (i = NR_CXL_TYPE2_ACCEL - 1; i >= 0; i--) + platform_device_unregister(cxl_mem[i]); + return rc; +} + +static int cxl_type3_mem_init(void) { int i, rc; @@ -1745,8 +1969,10 @@ static int cxl_mem_init(void) struct platform_device *pdev; pdev = platform_device_alloc("cxl_mem", i); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_mem; + } pdev->dev.parent = &dport->dev; set_dev_node(&pdev->dev, i % 2); @@ -1760,8 +1986,10 @@ static int cxl_mem_init(void) struct platform_device *pdev; pdev = platform_device_alloc("cxl_mem", NR_MEM_MULTI + i); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_single; + } pdev->dev.parent = &dport->dev; set_dev_node(&pdev->dev, i % 2); @@ -1776,8 +2004,10 @@ static int cxl_mem_init(void) struct platform_device *pdev; pdev = platform_device_alloc("cxl_rcd", idx); - if (!pdev) + if (!pdev) { + rc = -ENOMEM; goto err_rcd; + } pdev->dev.parent = &rch->dev; set_dev_node(&pdev->dev, i % 2); @@ -1800,6 +2030,13 @@ static int cxl_mem_init(void) return rc; } +static int cxl_mem_init(void) +{ + if (type2_test) + return cxl_type2_mem_init(); + return cxl_type3_mem_init(); +} + static ssize_t decoder_reset_preserve_registry_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1827,10 +2064,310 @@ static struct attribute *cxl_acpi_attrs[] = { }; ATTRIBUTE_GROUPS(cxl_acpi); +static bool __init have_multiple_modparms(void) +{ + int count = 0; + + if (interleave_arithmetic) + count++; + if (extended_linear_cache) + count++; + if (hmem_test) + count++; + if (type2_test) + count++; + + return count > 1; +} + +static void host_bridges_remove(void) +{ + int i; + + for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_host_bridge[i]; + + if (!pdev) + continue; + + sysfs_remove_link(&pdev->dev.kobj, "physical_node"); + platform_device_unregister(cxl_host_bridge[i]); + } +} + +static int host_bridges_populate(void) +{ + int rc = 0; + + for (int i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) { + struct acpi_device *adev = &host_bridge[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_host_bridge", i); + if (!pdev) { + rc = -ENOMEM; + goto err_bridge; + } + + mock_companion(adev, &pdev->dev); + rc = cxl_mock_platform_device_add(pdev, &cxl_host_bridge[i]); + if (rc) + goto err_bridge; + + mock_pci_bus[i].bridge = &pdev->dev; + rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj, + "physical_node"); + if (rc) + goto err_bridge; + } + + return 0; + +err_bridge: + host_bridges_remove(); + return rc; +} + +static void cxl_rootports_remove(void) +{ + for (int i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_root_port[i]; + + if (!pdev) + continue; + + platform_device_unregister(pdev); + } +} + +static int cxl_rootports_populate(void) +{ + int rc = 0; + + for (int i = 0; i < ARRAY_SIZE(cxl_root_port); i++) { + struct platform_device *bridge = + cxl_host_bridge[i % ARRAY_SIZE(cxl_host_bridge)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_root_port", i); + if (!pdev) { + rc = -ENOMEM; + goto err_port; + } + + pdev->dev.parent = &bridge->dev; + + rc = cxl_mock_platform_device_add(pdev, &cxl_root_port[i]); + if (rc) + goto err_port; + } + + return 0; + +err_port: + cxl_rootports_remove(); + return rc; +} + +static void cxl_usps_remove(void) +{ + for (int i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_switch_uport[i]; + + if (!pdev) + continue; + + platform_device_unregister(cxl_switch_uport[i]); + } +} + +static int cxl_usps_populate(void) +{ + int rc = 0; + + for (int i = 0; i < ARRAY_SIZE(cxl_switch_uport); i++) { + struct platform_device *root_port = cxl_root_port[i]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_uport", i); + if (!pdev) { + rc = -ENOMEM; + goto err_uport; + } + + pdev->dev.parent = &root_port->dev; + + rc = cxl_mock_platform_device_add(pdev, &cxl_switch_uport[i]); + if (rc) + goto err_uport; + } + + return 0; + +err_uport: + cxl_usps_remove(); + return rc; +} + +static void cxl_dsps_remove(void) +{ + for (int i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--) { + struct platform_device *pdev = cxl_switch_dport[i]; + + if (!pdev) + continue; + + platform_device_unregister(cxl_switch_dport[i]); + } +} + + +static int cxl_dsps_populate(void) +{ + int rc = 0; + + for (int i = 0; i < ARRAY_SIZE(cxl_switch_dport); i++) { + struct platform_device *uport = + cxl_switch_uport[i % ARRAY_SIZE(cxl_switch_uport)]; + struct platform_device *pdev; + + pdev = platform_device_alloc("cxl_switch_dport", i); + if (!pdev) { + rc = -ENOMEM; + goto err_dport; + } + pdev->dev.parent = &uport->dev; + + rc = cxl_mock_platform_device_add(pdev, &cxl_switch_dport[i]); + if (rc) + goto err_dport; + } + + return 0; + +err_dport: + cxl_dsps_remove(); + return rc; +} + +static void cxl_switches_remove(void) +{ + cxl_dsps_remove(); + cxl_usps_remove(); +} + +static int cxl_switches_populate(void) +{ + int rc; + + BUILD_BUG_ON(ARRAY_SIZE(cxl_switch_uport) != ARRAY_SIZE(cxl_root_port)); + rc = cxl_usps_populate(); + if (rc) + return rc; + + rc = cxl_dsps_populate(); + if (rc) { + cxl_usps_remove(); + return rc; + } + + return 0; +} + +static void cxl_type2_topo_exit(void) +{ + cxl_rootports_remove(); + host_bridges_remove(); +} + +static int cxl_type2_topo_init(void) +{ + int rc; + + rc = host_bridges_populate(); + if (rc) + return rc; + + rc = cxl_rootports_populate(); + if (rc) { + host_bridges_remove(); + return rc; + } + + return 0; +} + +static void cxl_type3_topo_exit(void) +{ + cxl_rch_topo_exit(); + cxl_single_topo_exit(); + cxl_switches_remove(); + cxl_rootports_remove(); + host_bridges_remove(); +} + +static int cxl_type3_topo_init(void) +{ + int rc; + + rc = host_bridges_populate(); + if (rc) + return rc; + + rc = cxl_rootports_populate(); + if (rc) + goto err_host_bridges; + + rc = cxl_switches_populate(); + if (rc) + goto err_root_ports; + + rc = cxl_single_topo_init(); + if (rc) + goto err_switches; + + rc = cxl_rch_topo_init(); + if (rc) + goto err_single; + + return 0; + +err_single: + cxl_single_topo_exit(); +err_switches: + cxl_switches_remove(); +err_root_ports: + cxl_rootports_remove(); +err_host_bridges: + host_bridges_remove(); + return rc; +} + +static void cxl_topo_exit(void) +{ + if (type2_test) { + cxl_type2_topo_exit(); + return; + } + + cxl_type3_topo_exit(); +} + +static int cxl_topo_init(void) +{ + if (type2_test) + return cxl_type2_topo_init(); + return cxl_type3_topo_init(); +} + static __init int cxl_test_init(void) { - int rc, i; struct range mappable; + int rc; + + /* Enforce a single module param active at a time */ + if (have_multiple_modparms()) + return -EINVAL; if (!IS_ALIGNED(mock_auto_region_size, PMD_SIZE)) { pr_err_once("mock_auto_region_size %d must be PMD-aligned\n", @@ -1872,82 +2409,15 @@ static __init int cxl_test_init(void) if (rc) goto err_populate; - for (i = 0; i < ARRAY_SIZE(cxl_host_bridge); i++) { - struct acpi_device *adev = &host_bridge[i]; - struct platform_device *pdev; - - pdev = platform_device_alloc("cxl_host_bridge", i); - if (!pdev) - goto err_bridge; - - mock_companion(adev, &pdev->dev); - rc = cxl_mock_platform_device_add(pdev, &cxl_host_bridge[i]); - if (rc) - goto err_bridge; - - mock_pci_bus[i].bridge = &pdev->dev; - rc = sysfs_create_link(&pdev->dev.kobj, &pdev->dev.kobj, - "physical_node"); - if (rc) - goto err_bridge; - } - - for (i = 0; i < ARRAY_SIZE(cxl_root_port); i++) { - struct platform_device *bridge = - cxl_host_bridge[i % ARRAY_SIZE(cxl_host_bridge)]; - struct platform_device *pdev; - - pdev = platform_device_alloc("cxl_root_port", i); - if (!pdev) - goto err_port; - pdev->dev.parent = &bridge->dev; - - rc = cxl_mock_platform_device_add(pdev, &cxl_root_port[i]); - if (rc) - goto err_port; - } - - BUILD_BUG_ON(ARRAY_SIZE(cxl_switch_uport) != ARRAY_SIZE(cxl_root_port)); - for (i = 0; i < ARRAY_SIZE(cxl_switch_uport); i++) { - struct platform_device *root_port = cxl_root_port[i]; - struct platform_device *pdev; - - pdev = platform_device_alloc("cxl_switch_uport", i); - if (!pdev) - goto err_uport; - pdev->dev.parent = &root_port->dev; - - rc = cxl_mock_platform_device_add(pdev, &cxl_switch_uport[i]); - if (rc) - goto err_uport; - } - - for (i = 0; i < ARRAY_SIZE(cxl_switch_dport); i++) { - struct platform_device *uport = - cxl_switch_uport[i % ARRAY_SIZE(cxl_switch_uport)]; - struct platform_device *pdev; - - pdev = platform_device_alloc("cxl_switch_dport", i); - if (!pdev) - goto err_dport; - pdev->dev.parent = &uport->dev; - - rc = cxl_mock_platform_device_add(pdev, &cxl_switch_dport[i]); - if (rc) - goto err_dport; - } - - rc = cxl_single_topo_init(); + rc = cxl_topo_init(); if (rc) - goto err_dport; - - rc = cxl_rch_topo_init(); - if (rc) - goto err_single; + goto err_populate; cxl_acpi = platform_device_alloc("cxl_acpi", 0); - if (!cxl_acpi) - goto err_rch; + if (!cxl_acpi) { + rc = -ENOMEM; + goto err_topo; + } mock_companion(&acpi0017_mock, &cxl_acpi->dev); acpi0017_mock.dev.bus = &platform_bus_type; @@ -1955,7 +2425,7 @@ static __init int cxl_test_init(void) rc = cxl_mock_platform_device_add(cxl_acpi, NULL); if (rc) - goto err_rch; + goto err_topo; rc = cxl_mem_init(); if (rc) @@ -1971,28 +2441,8 @@ static __init int cxl_test_init(void) cxl_mem_exit(); err_root: platform_device_unregister(cxl_acpi); -err_rch: - cxl_rch_topo_exit(); -err_single: - cxl_single_topo_exit(); -err_dport: - for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--) - platform_device_unregister(cxl_switch_dport[i]); -err_uport: - for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--) - platform_device_unregister(cxl_switch_uport[i]); -err_port: - for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) - platform_device_unregister(cxl_root_port[i]); -err_bridge: - for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) { - struct platform_device *pdev = cxl_host_bridge[i]; - - if (!pdev) - continue; - sysfs_remove_link(&pdev->dev.kobj, "physical_node"); - platform_device_unregister(cxl_host_bridge[i]); - } +err_topo: + cxl_topo_exit(); err_populate: depopulate_all_mock_resources(); err_gen_pool_add: @@ -2015,27 +2465,10 @@ static void free_decoder_registry(void) static __exit void cxl_test_exit(void) { - int i; - hmem_test_exit(); cxl_mem_exit(); platform_device_unregister(cxl_acpi); - cxl_rch_topo_exit(); - cxl_single_topo_exit(); - for (i = ARRAY_SIZE(cxl_switch_dport) - 1; i >= 0; i--) - platform_device_unregister(cxl_switch_dport[i]); - for (i = ARRAY_SIZE(cxl_switch_uport) - 1; i >= 0; i--) - platform_device_unregister(cxl_switch_uport[i]); - for (i = ARRAY_SIZE(cxl_root_port) - 1; i >= 0; i--) - platform_device_unregister(cxl_root_port[i]); - for (i = ARRAY_SIZE(cxl_host_bridge) - 1; i >= 0; i--) { - struct platform_device *pdev = cxl_host_bridge[i]; - - if (!pdev) - continue; - sysfs_remove_link(&pdev->dev.kobj, "physical_node"); - platform_device_unregister(cxl_host_bridge[i]); - } + cxl_topo_exit(); depopulate_all_mock_resources(); gen_pool_destroy(cxl_mock_pool); unregister_cxl_mock_ops(&cxl_mock_ops); @@ -2049,6 +2482,8 @@ module_param(extended_linear_cache, bool, 0444); MODULE_PARM_DESC(extended_linear_cache, "Enable extended linear cache support"); module_param(fail_autoassemble, bool, 0444); MODULE_PARM_DESC(fail_autoassemble, "Simulate missing member of an auto-region"); +module_param(type2_test, bool, 0444); +MODULE_PARM_DESC(type2_test, "Enable type 2 support testing"); module_init(cxl_test_init); module_exit(cxl_test_exit); MODULE_LICENSE("GPL v2"); diff --git a/tools/testing/cxl/test/hmem_test.c b/tools/testing/cxl/test/hmem_test.c index 3a1a089e1721..0fa00f7e16db 100644 --- a/tools/testing/cxl/test/hmem_test.c +++ b/tools/testing/cxl/test/hmem_test.c @@ -3,8 +3,9 @@ #include #include #include "../../../drivers/dax/bus.h" +#include "mock.h" -static bool hmem_test; +bool hmem_test; static void hmem_test_work(struct work_struct *work) { diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c index a7da279aa3ef..7b756000a1a6 100644 --- a/tools/testing/cxl/test/mem.c +++ b/tools/testing/cxl/test/mem.c @@ -1713,6 +1713,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) struct cxl_mockmem_data *mdata; struct cxl_mailbox *cxl_mbox; struct cxl_dpa_info range_info = { 0 }; + u64 serial; int rc; /* Increase async probe race window */ @@ -1739,7 +1740,19 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) if (rc) return rc; - mds = cxl_memdev_state_create(dev, pdev->id + 1, 0); + /* + * Mock serials have historically been derived from pdev->id and stayed + * single-digit, so they never exercised either decimal-vs-hex key + * lookup or unsigned formatting. Give one mock device a full-width + * serial with bit 63 set, matching real hardware such as Montage CXL + * devices. pdev->id 7 is unused by the auto-region topology. + */ + if (pdev->id == 7) + serial = 0x8a34567890abcdef; + else + serial = pdev->id + 1; + + mds = cxl_memdev_state_create(dev, serial, 0); if (IS_ERR(mds)) return PTR_ERR(mds); diff --git a/tools/testing/cxl/test/mock.h b/tools/testing/cxl/test/mock.h index 4f57dc80ae7d..846d7c5d6eaa 100644 --- a/tools/testing/cxl/test/mock.h +++ b/tools/testing/cxl/test/mock.h @@ -5,6 +5,8 @@ #include #include +extern bool hmem_test; + struct cxl_mock_ops { struct list_head list; bool (*is_mock_adev)(struct acpi_device *dev);