diff options
| author | Dave Jiang <dave.jiang@intel.com> | 2026-10-13 09:01:39 -0700 |
|---|---|---|
| committer | Dave Jiang <dave.jiang@intel.com> | 2026-10-13 09:01:39 -0700 |
| commit | d304df5f329302fff4658cd6ed8f93aec4b94d27 (patch) | |
| tree | 7733586cca27a7347a37e9542eedbed049f6d8c9 | |
| parent | 91320b84095afcaf9e4f676eef5b05346d60eb79 (diff) | |
| parent | e4478834aa09fd28abaaf774a4bcaa4bbe6804e5 (diff) | |
Merge branch 'for-7.4/cxl-dcd' into cxl-for-next
Part 1 of DCD support. Functionality disabled.
Documentation/cxl: Document DPA partition layout and ordering rules
cxl/core: Enforce partition order/simplify partition calls
cxl/mem: Configure dynamic capacity interrupts
cxl/pci: Factor out interrupt policy check
cxl/events: Split event msgnum configuration from irq setup
cxl/cdat: Gather DSMAS data for DCD partitions
cxl/mem: Read dynamic capacity configuration from the device
cxl/mbox: Flag support for Dynamic Capacity Devices (DCD)
| -rw-r--r-- | Documentation/driver-api/cxl/linux/cxl-driver.rst | 35 | ||||
| -rw-r--r-- | Documentation/driver-api/cxl/maturity-map.rst | 4 | ||||
| -rw-r--r-- | drivers/cxl/core/cdat.c | 99 | ||||
| -rw-r--r-- | drivers/cxl/core/hdm.c | 24 | ||||
| -rw-r--r-- | drivers/cxl/core/mbox.c | 283 | ||||
| -rw-r--r-- | drivers/cxl/core/memdev.c | 100 | ||||
| -rw-r--r-- | drivers/cxl/cxl.h | 14 | ||||
| -rw-r--r-- | drivers/cxl/cxlmem.h | 88 | ||||
| -rw-r--r-- | drivers/cxl/mem.c | 2 | ||||
| -rw-r--r-- | drivers/cxl/pci.c | 207 | ||||
| -rw-r--r-- | include/cxl/cxl.h | 29 | ||||
| -rw-r--r-- | tools/testing/cxl/test/mem.c | 2 |
12 files changed, 790 insertions, 97 deletions
diff --git a/Documentation/driver-api/cxl/linux/cxl-driver.rst b/Documentation/driver-api/cxl/linux/cxl-driver.rst index dd6dd17dc536..212df4fb8d63 100644 --- a/Documentation/driver-api/cxl/linux/cxl-driver.rst +++ b/Documentation/driver-api/cxl/linux/cxl-driver.rst @@ -181,6 +181,41 @@ A Memory Device is a discrete base object that is not a port. While the physical device it belongs to may also host an `endpoint`, the relationship between an `endpoint` and a `memdev` is not captured in sysfs. +DPA Partitions +~~~~~~~~~~~~~~ +A memory device presents its capacity as one flat `Device Physical Address` +(DPA) space divided into `partitions`, which Linux lays out in a fixed +order:: + + DPA 0 end + +---------------+---------------+---------------------------+ + | ram | pmem | dynamic_ram_1 | + +---------------+---------------+---------------------------+ + part[0] part[1] part[2] + +Part of that order is required by the CXL specification and part of it is a +Linux choice. + +The `ram` and `pmem` order is mandated. CXL r4.0 section 8.2.10.9.2.1 "Get +Partition Info" (4100h), Table 8-310, mandates that volatile capacity starts +at DPA 0 and pmem starts at the DPA immediately following it. + +Dynamic Capacity partitions only need to be 256MB aligned according to +CXL r4.0 section 8.2.10.9.9.1 "Get Dynamic Capacity Configuration" +(opcode 4800h), Table 8-347. So a device could leave a gap between ram/pmem +(static) capacity and its first DC partition, or between one DC partition +and the next. + +Linux chooses not to support gaps between static and dynamic capacity: the +first DC partition must begin at the DPA immediately following static +capacity -- after pmem, after ram on a device with no pmem, or at DPA 0 on +a device with no static capacity at all. + +Currently, only one dynamic partition is supported. A device may report up +to eight (CXL r4.0 Table 8-346); Linux configures the first as its +`dynamic_ram_1` partition. Gaps between DC partitions are not checked, since +only the first is used. + Port Relationships ~~~~~~~~~~~~~~~~~~ In our example described above, there are four host bridges attached to the diff --git a/Documentation/driver-api/cxl/maturity-map.rst b/Documentation/driver-api/cxl/maturity-map.rst index 282c1102dd81..fefd31899229 100644 --- a/Documentation/driver-api/cxl/maturity-map.rst +++ b/Documentation/driver-api/cxl/maturity-map.rst @@ -150,6 +150,10 @@ Memory-pooling * [1] Hotplug of LDs (via PCI hotplug) * [0] Dynamic Capacity Device (DCD) Support + * [1] DC partition enumeration / configuration (Get DC Config, CDAT DSMAS, event interrupts) + * [0] Extent add / release event handling + * [0] DC-backed DAX regions + Multi-host sharing ------------------ diff --git a/drivers/cxl/core/cdat.c b/drivers/cxl/core/cdat.c index df9261ba84e5..f5f86b69c86d 100644 --- a/drivers/cxl/core/cdat.c +++ b/drivers/cxl/core/cdat.c @@ -17,6 +17,8 @@ struct dsmas_entry { struct access_coordinate cdat_coord[ACCESS_COORDINATE_MAX]; int entries; int qos_class; + bool shareable; + bool read_only; }; static u32 cdat_normalize(u16 entry, u64 base, u8 type) @@ -73,7 +75,13 @@ static int cdat_dsmas_handler(union acpi_subtable_headers *header, void *arg, if (!dent) return -ENOMEM; + /* Stays invalid unless the QTG lookup assigns a class */ + dent->qos_class = CXL_QOS_CLASS_INVALID; dent->handle = dsmas->dsmad_handle; + /* Shareable is CDAT 1.03 and later, DSMAS Flags bit 3 */ + dent->shareable = dsmas->flags & ACPI_CDAT_DSMAS_SHAREABLE; + /* Read Only is CDAT 1.04 and later, DSMAS Flags bit 6 */ + dent->read_only = dsmas->flags & ACPI_CDAT_DSMAS_READ_ONLY; dent->dpa_range.start = le64_to_cpu((__force __le64)dsmas->dpa_base_address); dent->dpa_range.end = le64_to_cpu((__force __le64)dsmas->dpa_base_address) + le64_to_cpu((__force __le64)dsmas->dpa_length) - 1; @@ -188,9 +196,10 @@ static int cxl_cdat_endpoint_process(struct cxl_port *port, if (rc) return rc; + /* DSLBIS is optional; zero entries is not an error */ rc = cdat_table_parse(ACPI_CDAT_TYPE_DSLBIS, cdat_dslbis_handler, dsmas_xa, port->cdat.table, port->cdat.length); - return cdat_table_parse_output(rc); + return rc < 0 ? rc : 0; } static int cxl_port_perf_data_calculate(struct cxl_port *port, @@ -219,6 +228,13 @@ static int cxl_port_perf_data_calculate(struct cxl_port *port, xa_for_each(dsmas_xa, index, dent) { int qos_class; + /* No DSLBIS for this DSMAS; nothing to hand the QTG _DSM */ + if (!dent->cdat_coord[ACCESS_COORDINATE_CPU].read_bandwidth && + !dent->cdat_coord[ACCESS_COORDINATE_CPU].write_bandwidth && + !dent->cdat_coord[ACCESS_COORDINATE_CPU].read_latency && + !dent->cdat_coord[ACCESS_COORDINATE_CPU].write_latency) + continue; + cxl_coordinates_combine(dent->coord, dent->cdat_coord, ep_c); dent->entries = 1; rc = cxl_root->ops.qos_class(cxl_root, @@ -255,35 +271,90 @@ static void update_perf_entry(struct device *dev, struct dsmas_entry *dent, dent->coord[ACCESS_COORDINATE_CPU].write_latency); } -static void cxl_memdev_set_qos_class(struct cxl_dev_state *cxlds, - struct xarray *dsmas_xa) +/* + * A DCD shall describe its DC partition in a DSMAS entry (CXL r4.0 9.13.3). + * Without one the partition's attributes are unknown, so disable DCD. + */ +static void cxl_dcd_dsmas_missing(struct cxl_dev_state *cxlds, const char *why) +{ + struct cxl_memdev_state *mds; + + if (cxlds->type != CXL_DEVTYPE_CLASSMEM) + return; + + mds = to_cxl_memdev_state(cxlds); + if (!cxl_dcd_supported(mds) || + !cxl_part_size(cxlds, CXL_PARTMODE_DYNAMIC_RAM)) + return; + + dev_warn(cxlds->dev, "DC partition has no valid DSMAS entry (%s), disabling DCD\n", + why); + cxl_disable_dcd(mds); +} + +enum cxl_dsmas_apply { + CXL_DSMAS_APPLY_FLAGS, /* shareable etc.; no QTG data */ + CXL_DSMAS_APPLY_FLAGS_PERF, /* perf coordinates and qos_class */ +}; + +/* + * DSMAS flags such as shareable are properties of the CDAT entry and are + * applied regardless; perf data is applied only if the QTG lookup produced + * recommendations. + */ +static void cxl_memdev_apply_dsmas(struct cxl_dev_state *cxlds, + struct xarray *dsmas_xa, + enum cxl_dsmas_apply attrs) { struct device *dev = cxlds->dev; struct dsmas_entry *dent; + bool dc_matched = false; unsigned long index; xa_for_each(dsmas_xa, index, dent) { bool found = false; for (int i = 0; i < cxlds->nr_partitions; i++) { - struct resource *res = &cxlds->part[i].res; + struct cxl_dpa_partition *part = &cxlds->part[i]; struct range range = { - .start = res->start, - .end = res->end, + .start = part->res.start, + .end = part->res.end, }; - if (range_contains(&range, &dent->dpa_range)) { - update_perf_entry(dev, dent, - &cxlds->part[i].perf); - found = true; + if (!range_contains(&range, &dent->dpa_range)) + continue; + + found = true; + /* + * part->handle is from Get DC Config, dent->handle + * from the CDAT DSMAS entry. + */ + if (part->mode == CXL_PARTMODE_DYNAMIC_RAM && + dent->handle != part->handle) { + dev_warn(dev, + "DSMAD handle mismatch: %pra has %u, DSMAS %pra has %u\n", + &range, part->handle, + &dent->dpa_range, dent->handle); break; } + + if (attrs == CXL_DSMAS_APPLY_FLAGS_PERF) + update_perf_entry(dev, dent, &part->perf); + if (part->mode == CXL_PARTMODE_DYNAMIC_RAM) { + part->shareable = dent->shareable; + part->read_only = dent->read_only; + dc_matched = true; + } + break; } if (!found) dev_dbg(dev, "no partition for dsmas dpa: %pra\n", &dent->dpa_range); } + + if (!dc_matched) + cxl_dcd_dsmas_missing(cxlds, "no matching entry"); } static int match_cxlrd_qos_class(struct device *dev, void *data) @@ -409,22 +480,26 @@ void cxl_endpoint_parse_cdat(struct cxl_port *port) int rc; xa_init(&__dsmas_xa); - if (!port->cdat.table) + if (!port->cdat.table) { + cxl_dcd_dsmas_missing(cxlds, "no CDAT"); return; + } rc = cxl_cdat_endpoint_process(port, dsmas_xa); if (rc < 0) { dev_dbg(&port->dev, "Failed to parse CDAT: %d\n", rc); + cxl_dcd_dsmas_missing(cxlds, "CDAT parse failed"); return; } rc = cxl_port_perf_data_calculate(port, dsmas_xa); if (rc) { dev_dbg(&port->dev, "Failed to do perf coord calculations.\n"); + cxl_memdev_apply_dsmas(cxlds, dsmas_xa, CXL_DSMAS_APPLY_FLAGS); return; } - cxl_memdev_set_qos_class(cxlds, dsmas_xa); + cxl_memdev_apply_dsmas(cxlds, dsmas_xa, CXL_DSMAS_APPLY_FLAGS_PERF); cxl_qos_class_verify(cxlmd); cxl_memdev_update_perf(cxlmd); } diff --git a/drivers/cxl/core/hdm.c b/drivers/cxl/core/hdm.c index c51b2614aa39..fdcef20e01ba 100644 --- a/drivers/cxl/core/hdm.c +++ b/drivers/cxl/core/hdm.c @@ -477,6 +477,8 @@ static const char *cxl_mode_name(enum cxl_partition_mode mode) return "ram"; case CXL_PARTMODE_PMEM: return "pmem"; + case CXL_PARTMODE_DYNAMIC_RAM: + return "dynamic_ram"; default: return ""; }; @@ -486,6 +488,7 @@ static const char *cxl_mode_name(enum cxl_partition_mode mode) int cxl_dpa_setup(struct cxl_dev_state *cxlds, const struct cxl_dpa_info *info) { struct device *dev = cxlds->dev; + int dc_nth = 0; guard(rwsem_write)(&cxl_rwsem.dpa); @@ -498,14 +501,33 @@ int cxl_dpa_setup(struct cxl_dev_state *cxlds, const struct cxl_dpa_info *info) return 0; } + /* Verify partitions are in expected order. */ + for (int i = 1; i < info->nr_partitions; i++) { + if (info->part[i].mode < info->part[i - 1].mode) { + dev_err(dev, "Partition order mismatch: %d (%s) follows %d (%s)\n", + i, cxl_mode_name(info->part[i].mode), + i - 1, cxl_mode_name(info->part[i - 1].mode)); + return -EINVAL; + } + } + cxlds->dpa_res = DEFINE_RES_MEM(0, info->size); for (int i = 0; i < info->nr_partitions; i++) { const struct cxl_dpa_part_info *part = &info->part[i]; + const char *name = cxl_mode_name(part->mode); int rc; + if (part->mode == CXL_PARTMODE_DYNAMIC_RAM) { + name = devm_kasprintf(dev, GFP_KERNEL, "%s_%d", name, + ++dc_nth); + if (!name) + return -ENOMEM; + } + cxlds->part[i].perf.qos_class = CXL_QOS_CLASS_INVALID; cxlds->part[i].mode = part->mode; + cxlds->part[i].handle = part->handle; /* Require ordered + contiguous partitions */ if (i) { @@ -516,7 +538,7 @@ int cxl_dpa_setup(struct cxl_dev_state *cxlds, const struct cxl_dpa_info *info) } rc = add_dpa_res(dev, &cxlds->dpa_res, &cxlds->part[i].res, part->range.start, range_len(&part->range), - cxl_mode_name(part->mode)); + name); if (rc) return rc; cxlds->nr_partitions++; diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c index 1a2553332801..5992aa029712 100644 --- a/drivers/cxl/core/mbox.c +++ b/drivers/cxl/core/mbox.c @@ -171,6 +171,33 @@ static void cxl_set_security_cmd_enabled(struct cxl_security_state *security, } } +static bool cxl_is_dcd_command(u16 opcode) +{ +#define CXL_MBOX_OP_DCD_CMDS 0x48 + + return (opcode >> 8) == CXL_MBOX_OP_DCD_CMDS; +} + +static void cxl_set_dcd_cmd_enabled(u16 opcode, unsigned long *cmd_mask) +{ + switch (opcode) { + case CXL_MBOX_OP_GET_DC_CONFIG: + set_bit(CXL_DCD_ENABLED_GET_CONFIG, cmd_mask); + break; + case CXL_MBOX_OP_GET_DC_EXTENT_LIST: + set_bit(CXL_DCD_ENABLED_GET_EXTENT_LIST, cmd_mask); + break; + case CXL_MBOX_OP_ADD_DC_RESPONSE: + set_bit(CXL_DCD_ENABLED_ADD_RESPONSE, cmd_mask); + break; + case CXL_MBOX_OP_RELEASE_DC: + set_bit(CXL_DCD_ENABLED_RELEASE, cmd_mask); + break; + default: + break; + } +} + static bool cxl_is_poison_command(u16 opcode) { #define CXL_MBOX_OP_POISON_CMDS 0x43 @@ -759,6 +786,7 @@ static void cxl_walk_cel(struct cxl_memdev_state *mds, size_t size, u8 *cel) struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; struct cxl_cel_entry *cel_entry; const int cel_entries = size / sizeof(*cel_entry); + DECLARE_BITMAP(dcd_cmds, CXL_DCD_ENABLED_MAX) = {}; struct device *dev = mds->cxlds.dev; int i, ro_cmds = 0, wr_cmds = 0; @@ -787,11 +815,22 @@ static void cxl_walk_cel(struct cxl_memdev_state *mds, size_t size, u8 *cel) enabled++; } + if (cxl_is_dcd_command(opcode)) { + cxl_set_dcd_cmd_enabled(opcode, dcd_cmds); + enabled++; + } + dev_dbg(dev, "Opcode 0x%04x %s\n", opcode, enabled ? "enabled" : "unsupported by driver"); } set_features_cap(cxl_mbox, ro_cmds, wr_cmds); + /* + * Disabled until event handling implemented. + */ + if (bitmap_full(dcd_cmds, CXL_DCD_ENABLED_MAX)) + dev_dbg(dev, "Device supports DCD; capability disabled\n"); + mds->dcd_supported = false; } static struct cxl_mbox_get_supported_logs *cxl_get_gsl(struct cxl_memdev_state *mds) @@ -1310,6 +1349,250 @@ int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd) return -EBUSY; } +/* + * A partition unavailable for Dynamic Capacity reports zeros for decode_length, + * length, and block_size. + */ +static bool cxl_dc_partition_unavailable(struct cxl_dc_partition *dev_part) +{ + return !le64_to_cpu(dev_part->decode_length) && + !le64_to_cpu(dev_part->length) && + !le64_to_cpu(dev_part->block_size); +} + +static int cxl_dc_check(struct device *dev, struct cxl_dc_partition_info *part, + struct cxl_dc_partition *dev_part) +{ + u64 decode_len = le64_to_cpu(dev_part->decode_length); + u64 blk_size = le64_to_cpu(dev_part->block_size); + u64 len = le64_to_cpu(dev_part->length); + u32 handle = le32_to_cpu(dev_part->dsmad_handle); + u64 size; + + /* + * Not an error; leave the entry empty. A partially zeroed partition + * is rejected by the checks below. CXL r4.0 Table 8-347. + */ + if (cxl_dc_partition_unavailable(dev_part)) { + *part = (struct cxl_dc_partition_info) { }; + dev_dbg(dev, "Partition 0 unavailable for DC\n"); + return 0; + } + + /* The CDAT DSMAD handle this refers to is 8 bits */ + if (handle > U8_MAX) { + dev_warn(dev, "DSMAD handle 0x%x exceeds the 8 bit CDAT DSMAD handle\n", + handle); + return -EINVAL; + } + + /* + * Decode length is a count of 256M units, so a device reporting 2^36 + * or more wraps. + */ + if (check_mul_overflow(decode_len, (u64)CXL_CAPACITY_MULTIPLIER, &size)) { + dev_err(dev, "DC partition 0 decode length %#llx overflows\n", + decode_len); + return -EINVAL; + } + + *part = (struct cxl_dc_partition_info) { + .start = le64_to_cpu(dev_part->base), + .size = size, + .handle = handle, + }; + + /* + * Block size is a power of 2 and a multiple of 40h. is_power_of_2() + * takes an unsigned long, which truncates blk_size on 32 bit. + */ + if (blk_size == 0 || (blk_size & (blk_size - 1)) || + blk_size % CXL_DCD_BLOCK_LINE_SIZE) { + dev_err(dev, "DC partition 0 invalid block size %#llx\n", blk_size); + return -EINVAL; + } + + if (part->size == 0) { + dev_err(dev, "DC partition 0 zero decode length\n"); + return -EINVAL; + } + + if (len == 0) { + dev_err(dev, "DC partition 0 zero length\n"); + return -EINVAL; + } + + if (len > part->size) { + dev_err(dev, "DC partition 0 length %#llx exceeds decode length %#llx\n", + len, part->size); + return -EINVAL; + } + + if (!IS_ALIGNED(len, blk_size)) { + dev_err(dev, "DC partition 0 length %#llx not a multiple of block size %#llx\n", + len, blk_size); + return -EINVAL; + } + + if (!IS_ALIGNED(part->start, SZ_256M)) { + dev_err(dev, "DC partition 0 base %#llx not aligned to 256M\n", + part->start); + return -EINVAL; + } + + dev_dbg(dev, "DC partition 0 start %#llx size %#llx blk_size: %#llx\n", + part->start, part->size, blk_size); + + return 0; +} + +/* Returns the number of partitions in dc_resp or -ERRNO */ +static int cxl_get_dc_config(struct cxl_mailbox *mbox, u8 start_partition, + u8 partition_count, + struct cxl_mbox_get_dc_config_out *dc_resp, + size_t dc_resp_size) +{ + struct cxl_mbox_get_dc_config_in get_dc = (struct cxl_mbox_get_dc_config_in) { + .partition_count = partition_count, + .start_partition_index = start_partition, + }; + struct cxl_mbox_cmd mbox_cmd = (struct cxl_mbox_cmd) { + .opcode = CXL_MBOX_OP_GET_DC_CONFIG, + .payload_in = &get_dc, + .size_in = sizeof(get_dc), + .size_out = dc_resp_size, + .payload_out = dc_resp, + /* The device must return at least the fixed header */ + .min_out = sizeof(*dc_resp), + }; + size_t expected_sz; + int rc; + + rc = cxl_internal_send_cmd(mbox, &mbox_cmd); + if (rc < 0) + return rc; + + if (dc_resp->partitions_returned > partition_count) { + dev_err(mbox->host, "Device returned %u partitions, requested %u\n", + dc_resp->partitions_returned, partition_count); + return -EIO; + } + + /* The trailing extent/tag counts (CXL r4.0 Table 8-346) are not read */ + expected_sz = struct_size(dc_resp, partition, + dc_resp->partitions_returned); + + if (mbox_cmd.size_out < expected_sz) { + dev_err(mbox->host, + "Payload size %zu less than expected %zu for %u partitions\n", + mbox_cmd.size_out, expected_sz, + dc_resp->partitions_returned); + return -EIO; + } + + dev_dbg(mbox->host, "Read %d/%d DC partitions\n", + dc_resp->partitions_returned, dc_resp->avail_partition_count); + return dc_resp->partitions_returned; +} + +/** + * cxl_dev_dc_identify() - Reads the dynamic capacity information from the + * device. + * @mbox: Mailbox to query + * @dc_info: The dynamic partition information to return + * + * Read every partition the device reports, but validate only the first: + * Linux maps partition 0 and nothing else, so a defect in capacity the + * driver never touches is not a reason to refuse the device dynamic + * capacity. The remaining partitions are read but not recorded. + * + * Return: 0 if identify was executed successfully, -ERRNO on error. + * On error, @dc_info is left unchanged. + */ +int cxl_dev_dc_identify(struct cxl_mailbox *mbox, + struct cxl_dc_partition_info *dc_info) +{ + struct cxl_dc_partition_info partitions[CXL_MAX_DC_PARTITIONS] = { }; + struct device *dev = mbox->host; + u8 avail_partitions = 0; + u8 num_partitions; + u8 partition_count; + size_t dc_resp_size; + + /* + * Bound requested number of partitions by mailbox payload size. The + * 256 byte spec minimum, verified in cxl_pci_setup_mailbox(), keeps + * the subtraction below from underflowing. + */ + partition_count = min(CXL_MAX_DC_PARTITIONS, + (mbox->payload_size - + sizeof(struct cxl_mbox_get_dc_config_out) - + sizeof(struct cxl_mbox_get_dc_config_tail)) / + sizeof(struct cxl_dc_partition)); + dc_resp_size = struct_size_t(struct cxl_mbox_get_dc_config_out, + partition, partition_count) + + sizeof(struct cxl_mbox_get_dc_config_tail); + + struct cxl_mbox_get_dc_config_out *dc_resp __free(kfree) = + kmalloc(dc_resp_size, GFP_KERNEL); + if (!dc_resp) + return -ENOMEM; + + num_partitions = 0; + do { + int returned, rc; + + returned = cxl_get_dc_config(mbox, num_partitions, + partition_count, dc_resp, + dc_resp_size); + if (returned < 0) { + dev_err(dev, "Failed to get DC config: %d\n", returned); + return returned; + } + + if (returned == 0) { + dev_err(dev, + "Device reported %u partitions available but returned none at index %u\n", + dc_resp->avail_partition_count, num_partitions); + return -EIO; + } + + /* A DCD reports at least one partition (CXL r4.0 Table 8-346) */ + if (dc_resp->avail_partition_count == 0) { + dev_err(dev, "Device reported no DC partitions\n"); + return -EIO; + } + + avail_partitions = min(dc_resp->avail_partition_count, + CXL_MAX_DC_PARTITIONS); + + /* + * Only partition 0 is mapped, so it is the only one checked. + * The rest are read to complete the enumeration and dropped. + */ + if (num_partitions == 0) { + rc = cxl_dc_check(dev, &partitions[0], + &dc_resp->partition[0]); + if (rc) + return rc; + } + + num_partitions += returned; + + } while (num_partitions < avail_partitions); + + /* Linux only supports the 1st partition; nothing to do if it is unavailable */ + if (partitions[0].size == 0) + return -ENODEV; + + *dc_info = partitions[0]; + dev_dbg(dev, "Returning partition 0 %#llx size %#llx\n", + dc_info->start, dc_info->size); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_dev_dc_identify, "CXL"); + int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c index b3419df586b9..787988e92965 100644 --- a/drivers/cxl/core/memdev.c +++ b/drivers/cxl/core/memdev.c @@ -77,20 +77,12 @@ static ssize_t label_storage_size_show(struct device *dev, } static DEVICE_ATTR_RO(label_storage_size); -static resource_size_t cxl_ram_size(struct cxl_dev_state *cxlds) -{ - /* Static RAM is only expected at partition 0. */ - if (cxlds->part[0].mode != CXL_PARTMODE_RAM) - return 0; - return resource_size(&cxlds->part[0].res); -} - static ssize_t ram_size_show(struct device *dev, struct device_attribute *attr, char *buf) { struct cxl_memdev *cxlmd = to_cxl_memdev(dev); struct cxl_dev_state *cxlds = cxlmd->cxlds; - unsigned long long len = cxl_ram_size(cxlds); + unsigned long long len = cxl_part_size(cxlds, CXL_PARTMODE_RAM); return sysfs_emit(buf, "%#llx\n", len); } @@ -103,7 +95,7 @@ static ssize_t pmem_size_show(struct device *dev, struct device_attribute *attr, { struct cxl_memdev *cxlmd = to_cxl_memdev(dev); struct cxl_dev_state *cxlds = cxlmd->cxlds; - unsigned long long len = cxl_pmem_size(cxlds); + unsigned long long len = cxl_part_size(cxlds, CXL_PARTMODE_PMEM); return sysfs_emit(buf, "%#llx\n", len); } @@ -426,10 +418,11 @@ static struct attribute *cxl_memdev_attributes[] = { NULL, }; -static struct cxl_dpa_perf *to_pmem_perf(struct cxl_dev_state *cxlds) +static struct cxl_dpa_perf *part_perf(struct cxl_dev_state *cxlds, + enum cxl_partition_mode mode) { for (int i = 0; i < cxlds->nr_partitions; i++) - if (cxlds->part[i].mode == CXL_PARTMODE_PMEM) + if (cxlds->part[i].mode == mode) return &cxlds->part[i].perf; return NULL; } @@ -440,7 +433,7 @@ static ssize_t pmem_qos_class_show(struct device *dev, struct cxl_memdev *cxlmd = to_cxl_memdev(dev); struct cxl_dev_state *cxlds = cxlmd->cxlds; - return sysfs_emit(buf, "%d\n", to_pmem_perf(cxlds)->qos_class); + return sysfs_emit(buf, "%d\n", part_perf(cxlds, CXL_PARTMODE_PMEM)->qos_class); } static struct device_attribute dev_attr_pmem_qos_class = @@ -452,20 +445,13 @@ static struct attribute *cxl_memdev_pmem_attributes[] = { NULL, }; -static struct cxl_dpa_perf *to_ram_perf(struct cxl_dev_state *cxlds) -{ - if (cxlds->part[0].mode != CXL_PARTMODE_RAM) - return NULL; - return &cxlds->part[0].perf; -} - static ssize_t ram_qos_class_show(struct device *dev, struct device_attribute *attr, char *buf) { struct cxl_memdev *cxlmd = to_cxl_memdev(dev); struct cxl_dev_state *cxlds = cxlmd->cxlds; - return sysfs_emit(buf, "%d\n", to_ram_perf(cxlds)->qos_class); + return sysfs_emit(buf, "%d\n", part_perf(cxlds, CXL_PARTMODE_RAM)->qos_class); } static struct device_attribute dev_attr_ram_qos_class = @@ -501,7 +487,7 @@ static umode_t cxl_ram_visible(struct kobject *kobj, struct attribute *a, int n) { struct device *dev = kobj_to_dev(kobj); struct cxl_memdev *cxlmd = to_cxl_memdev(dev); - struct cxl_dpa_perf *perf = to_ram_perf(cxlmd->cxlds); + struct cxl_dpa_perf *perf = part_perf(cxlmd->cxlds, CXL_PARTMODE_RAM); if (a == &dev_attr_ram_qos_class.attr && (!perf || perf->qos_class == CXL_QOS_CLASS_INVALID)) @@ -520,7 +506,7 @@ static umode_t cxl_pmem_visible(struct kobject *kobj, struct attribute *a, int n { struct device *dev = kobj_to_dev(kobj); struct cxl_memdev *cxlmd = to_cxl_memdev(dev); - struct cxl_dpa_perf *perf = to_pmem_perf(cxlmd->cxlds); + struct cxl_dpa_perf *perf = part_perf(cxlmd->cxlds, CXL_PARTMODE_PMEM); if (a == &dev_attr_pmem_qos_class.attr && (!perf || perf->qos_class == CXL_QOS_CLASS_INVALID)) @@ -594,7 +580,8 @@ bool is_cxl_memdev(const struct device *dev) } EXPORT_SYMBOL_NS_GPL(is_cxl_memdev, "CXL"); -static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_partition_mode mode) +static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, + enum cxl_partition_mode mode, u8 handle) { int i = info->nr_partitions; @@ -606,6 +593,7 @@ static void add_part(struct cxl_dpa_info *info, u64 start, u64 size, enum cxl_pa .end = start + size - 1, }; info->part[i].mode = mode; + info->part[i].handle = handle; info->nr_partitions++; } @@ -623,9 +611,9 @@ int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) info->size = mds->total_bytes; if (mds->partition_align_bytes == 0) { - add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM); + add_part(info, 0, mds->volatile_only_bytes, CXL_PARTMODE_RAM, 0); add_part(info, mds->volatile_only_bytes, - mds->persistent_only_bytes, CXL_PARTMODE_PMEM); + mds->persistent_only_bytes, CXL_PARTMODE_PMEM, 0); return 0; } @@ -635,14 +623,68 @@ int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) return rc; } - add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM); + add_part(info, 0, mds->active_volatile_bytes, CXL_PARTMODE_RAM, 0); add_part(info, mds->active_volatile_bytes, mds->active_persistent_bytes, - CXL_PARTMODE_PMEM); + CXL_PARTMODE_PMEM, 0); return 0; } EXPORT_SYMBOL_NS_GPL(cxl_mem_dpa_fetch, "CXL"); +int cxl_configure_dcd(struct cxl_memdev_state *mds, struct cxl_dpa_info *info) +{ + struct cxl_dc_partition_info dc_info = { }; + struct device *dev = mds->cxlds.dev; + u64 static_end = 0; + u64 dc_end; + int rc; + + if (!mds->cxlds.media_ready) + return 0; + + rc = cxl_dev_dc_identify(&mds->cxlds.cxl_mbox, &dc_info); + if (rc) { + dev_warn(dev, + "Failed to read Dynamic Capacity config: %d\n", rc); + return rc; + } + + if (info->nr_partitions) + static_end = info->part[info->nr_partitions - 1].range.end + 1; + + if (dc_info.start < static_end) { + dev_err(dev, + "DC partition 0 base %#llx overlaps static capacity ending at %#llx\n", + dc_info.start, static_end); + return -EINVAL; + } + + /* A gap between static capacity and the DC partition is not supported */ + if (dc_info.start > static_end) { + dev_warn(dev, + "DC partition 0 base %#llx leaves a gap from static capacity ending at %#llx\n", + dc_info.start, static_end); + return -EOPNOTSUPP; + } + + if (check_add_overflow(dc_info.start, dc_info.size, &dc_end)) { + dev_err(dev, + "DC partition 0 size %#llx overflows DPA space at base %#llx\n", + dc_info.size, dc_info.start); + return -EINVAL; + } + + /* The DPA space must reach the end of the DC partition */ + if (dc_end > info->size) + info->size = dc_end; + dev_dbg(dev, "Adding dynamic ram partition; %#llx size %#llx\n", + dc_info.start, dc_info.size); + add_part(info, dc_info.start, dc_info.size, CXL_PARTMODE_DYNAMIC_RAM, + dc_info.handle); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cxl_configure_dcd, "CXL"); /** * cxl_set_capacity: initialize dpa by a driver without a mailbox. @@ -656,7 +698,7 @@ int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity) .size = capacity, }; - add_part(&range_info, 0, capacity, CXL_PARTMODE_RAM); + add_part(&range_info, 0, capacity, CXL_PARTMODE_RAM, 0); return cxl_dpa_setup(cxlds, &range_info); } EXPORT_SYMBOL_NS_GPL(cxl_set_capacity, "CXL"); diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h index d96210b8f147..baa6df2d362a 100644 --- a/drivers/cxl/cxl.h +++ b/drivers/cxl/cxl.h @@ -194,11 +194,15 @@ static inline int ways_to_eiw(unsigned int ways, u8 *eiw) #define CXLDEV_EVENT_STATUS_WARN BIT(1) #define CXLDEV_EVENT_STATUS_FAIL BIT(2) #define CXLDEV_EVENT_STATUS_FATAL BIT(3) - -#define CXLDEV_EVENT_STATUS_ALL (CXLDEV_EVENT_STATUS_INFO | \ - CXLDEV_EVENT_STATUS_WARN | \ - CXLDEV_EVENT_STATUS_FAIL | \ - CXLDEV_EVENT_STATUS_FATAL) +#define CXLDEV_EVENT_STATUS_DCD BIT(4) +/* + * Logs whose ownership follows native_cxl_error; the DCD log is always + * OS owned. + */ +#define CXLDEV_EVENT_STATUS_STANDARD_LOGS (CXLDEV_EVENT_STATUS_INFO | \ + CXLDEV_EVENT_STATUS_WARN | \ + CXLDEV_EVENT_STATUS_FAIL | \ + CXLDEV_EVENT_STATUS_FATAL) /* CXL rev 3.0 section 8.2.9.2.4; Table 8-52 */ #define CXLDEV_EVENT_INT_MODE_MASK GENMASK(1, 0) diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h index c401e3a1af06..35268d1c387c 100644 --- a/drivers/cxl/cxlmem.h +++ b/drivers/cxl/cxlmem.h @@ -140,6 +140,7 @@ struct cxl_dpa_info { struct cxl_dpa_part_info { struct range range; enum cxl_partition_mode mode; + u8 handle; } part[CXL_NR_PARTITIONS_MAX]; int nr_partitions; }; @@ -239,7 +240,9 @@ struct cxl_event_interrupt_policy { u8 warn_settings; u8 failure_settings; u8 fatal_settings; + u8 dcd_settings; } __packed; +#define CXL_EVENT_INT_POLICY_BASE_SIZE 4 /* info, warn, failure, fatal */ /** * struct cxl_event_state - Event log driver state @@ -252,6 +255,20 @@ struct cxl_event_state { struct mutex log_lock; }; +/* + * CXL r4.0 Section 8.2.10.9 - Memory Device Command Sets. See Table 8-308. + * + * The 48h Command Set (Opcodes 4800h - 4803h) defines the device-enabled DCD + * commands. + */ +enum dcd_cmd_enabled_bits { + CXL_DCD_ENABLED_GET_CONFIG, + CXL_DCD_ENABLED_GET_EXTENT_LIST, + CXL_DCD_ENABLED_ADD_RESPONSE, + CXL_DCD_ENABLED_RELEASE, + CXL_DCD_ENABLED_MAX +}; + /* Device enabled poison commands */ enum poison_cmd_enabled_bits { CXL_POISON_ENABLED_LIST, @@ -393,14 +410,13 @@ struct cxl_security_state { struct kernfs_node *sanitize_node; }; -static inline resource_size_t cxl_pmem_size(struct cxl_dev_state *cxlds) +#define CXL_MAX_DC_PARTITIONS 8 + +static inline resource_size_t cxl_part_size(struct cxl_dev_state *cxlds, + enum cxl_partition_mode mode) { - /* - * Static PMEM may be at partition index 0 when there is no static RAM - * capacity. - */ for (int i = 0; i < cxlds->nr_partitions; i++) - if (cxlds->part[i].mode == CXL_PARTMODE_PMEM) + if (cxlds->part[i].mode == mode) return resource_size(&cxlds->part[i].res); return 0; } @@ -427,6 +443,7 @@ static inline struct cxl_dev_state *mbox_to_cxlds(struct cxl_mailbox *cxl_mbox) * @partition_align_bytes: alignment size for partition-able capacity * @active_volatile_bytes: sum of hard + soft volatile * @active_persistent_bytes: sum of hard + soft persistent + * @dcd_supported: all DCD commands are supported * @event: event log driver state * @poison: poison driver state info * @security: security driver state info @@ -445,6 +462,7 @@ struct cxl_memdev_state { u64 partition_align_bytes; u64 active_volatile_bytes; u64 active_persistent_bytes; + bool dcd_supported; struct cxl_event_state event; struct cxl_poison_state poison; @@ -505,6 +523,10 @@ enum cxl_opcode { CXL_MBOX_OP_UNLOCK = 0x4503, CXL_MBOX_OP_FREEZE_SECURITY = 0x4504, CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE = 0x4505, + CXL_MBOX_OP_GET_DC_CONFIG = 0x4800, + CXL_MBOX_OP_GET_DC_EXTENT_LIST = 0x4801, + CXL_MBOX_OP_ADD_DC_RESPONSE = 0x4802, + CXL_MBOX_OP_RELEASE_DC = 0x4803, CXL_MBOX_OP_MAX = 0x10000 }; @@ -669,6 +691,39 @@ struct cxl_mbox_set_shutdown_state_in { u8 state; } __packed; +/* See CXL r4.0 Table 8-345 get dynamic capacity config Input Payload */ +struct cxl_mbox_get_dc_config_in { + u8 partition_count; + u8 start_partition_index; +} __packed; + +/* See CXL r4.0 Table 8-346 get dynamic capacity config Output Payload */ +struct cxl_mbox_get_dc_config_out { + u8 avail_partition_count; + u8 partitions_returned; + u8 rsvd[6]; + /* See CXL r4.0 Table 8-347 */ + struct cxl_dc_partition { + __le64 base; + __le64 decode_length; + __le64 length; + __le64 block_size; + __le32 dsmad_handle; + u8 flags; + u8 rsvd[3]; + } __packed partition[] __counted_by(partitions_returned); + /* Trailing extent/tag count fields unused */ +} __packed; + +/* Trailing counts; cannot be a member after the flex array above */ +struct cxl_mbox_get_dc_config_tail { + __le32 num_extents_supported; + __le32 num_extents_available; + __le32 num_tags_supported; + __le32 num_tags_available; +} __packed; +#define CXL_DCD_BLOCK_LINE_SIZE 0x40 + /* Set Timestamp CXL 3.0 Spec 8.2.9.4.2 */ struct cxl_mbox_set_timestamp_in { __le64 timestamp; @@ -792,9 +847,19 @@ enum { int cxl_internal_send_cmd(struct cxl_mailbox *cxl_mbox, struct cxl_mbox_cmd *cmd); int cxl_dev_state_identify(struct cxl_memdev_state *mds); + +struct cxl_dc_partition_info { + u64 start; + u64 size; + u8 handle; +}; + +int cxl_dev_dc_identify(struct cxl_mailbox *mbox, + struct cxl_dc_partition_info *dc_info); int cxl_await_media_ready(struct cxl_dev_state *cxlds); int cxl_enumerate_cmds(struct cxl_memdev_state *mds); int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info); +int cxl_configure_dcd(struct cxl_memdev_state *mds, struct cxl_dpa_info *info); struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial, u16 dvsec); void set_exclusive_cxl_commands(struct cxl_memdev_state *mds, @@ -808,6 +873,17 @@ void cxl_event_trace_record(struct cxl_memdev *cxlmd, const uuid_t *uuid, union cxl_event *evt); int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count); int cxl_arm_dirty_shutdown(struct cxl_memdev_state *mds); + +static inline bool cxl_dcd_supported(struct cxl_memdev_state *mds) +{ + return mds->dcd_supported; +} + +static inline void cxl_disable_dcd(struct cxl_memdev_state *mds) +{ + mds->dcd_supported = false; +} + int cxl_set_timestamp(struct cxl_memdev_state *mds); int cxl_poison_state_init(struct cxl_memdev_state *mds); int cxl_mem_get_poison(struct cxl_memdev *cxlmd, u64 offset, u64 len, diff --git a/drivers/cxl/mem.c b/drivers/cxl/mem.c index 493c4071245d..6fba230905c1 100644 --- a/drivers/cxl/mem.c +++ b/drivers/cxl/mem.c @@ -145,7 +145,7 @@ static int cxl_mem_probe(struct device *dev) return -ENXIO; } - if (cxl_pmem_size(cxlds) && IS_ENABLED(CONFIG_CXL_PMEM)) { + if (cxl_part_size(cxlds, CXL_PARTMODE_PMEM) && IS_ENABLED(CONFIG_CXL_PMEM)) { rc = devm_cxl_add_nvdimm(dev, parent_port, cxlmd); if (rc) { if (rc == -ENODEV) diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 44ae1a20bbbf..5bd1e9e1b5b0 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -510,11 +510,22 @@ static bool cxl_alloc_irq_vectors(struct pci_dev *pdev) return true; } +/* Event logs the driver drains: standard logs when native_cxl */ +static u32 cxl_event_drain_mask(struct pci_host_bridge *host_bridge) +{ + if (host_bridge->native_cxl_error) + return CXLDEV_EVENT_STATUS_STANDARD_LOGS; + return 0; +} + static irqreturn_t cxl_event_thread(int irq, void *id) { struct cxl_dev_id *dev_id = id; struct cxl_dev_state *cxlds = dev_id->cxlds; struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); + struct pci_host_bridge *host_bridge = + pci_find_host_bridge(to_pci_dev(cxlds->dev)->bus); + u32 mask = cxl_event_drain_mask(host_bridge); bool handled = false; u32 status; @@ -524,8 +535,8 @@ static irqreturn_t cxl_event_thread(int irq, void *id) * ignore the reserved upper 32 bits */ status = readl(cxlds->regs.status + CXLDEV_DEV_EVENT_STATUS_OFFSET); - /* Ignore logs unknown to the driver */ - status &= CXLDEV_EVENT_STATUS_ALL; + /* Ignore logs unknown to the driver or owned by BIOS */ + status &= mask; if (!status) break; @@ -554,84 +565,108 @@ static int cxl_event_req_irq(struct cxl_dev_state *cxlds, u8 setting) } static int cxl_event_get_int_policy(struct cxl_memdev_state *mds, - struct cxl_event_interrupt_policy *policy) + struct cxl_event_interrupt_policy *policy, + size_t *policy_size) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; struct cxl_mbox_cmd mbox_cmd = { .opcode = CXL_MBOX_OP_GET_EVT_INT_POLICY, .payload_out = policy, .size_out = sizeof(*policy), + /* CXL 2.0 firmware omits dcd_settings; accept the shorter reply */ + .min_out = CXL_EVENT_INT_POLICY_BASE_SIZE, }; int rc; rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); - if (rc < 0) + if (rc < 0) { dev_err(mds->cxlds.dev, "Failed to get event interrupt policy : %d", rc); + return rc; + } + if (policy_size) + *policy_size = mbox_cmd.size_out; return rc; } -static int cxl_event_config_msgnums(struct cxl_memdev_state *mds, - struct cxl_event_interrupt_policy *policy) +static int cxl_event_set_int_policy(struct cxl_memdev_state *mds, + struct cxl_event_interrupt_policy *policy, + size_t policy_size) { struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox; - struct cxl_mbox_cmd mbox_cmd; - int rc; - - *policy = (struct cxl_event_interrupt_policy) { - .info_settings = CXL_INT_MSI_MSIX, - .warn_settings = CXL_INT_MSI_MSIX, - .failure_settings = CXL_INT_MSI_MSIX, - .fatal_settings = CXL_INT_MSI_MSIX, - }; - - mbox_cmd = (struct cxl_mbox_cmd) { + struct cxl_mbox_cmd mbox_cmd = { .opcode = CXL_MBOX_OP_SET_EVT_INT_POLICY, .payload_in = policy, - .size_in = sizeof(*policy), + .size_in = policy_size, }; + int rc; rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); - if (rc < 0) { + if (rc < 0) dev_err(mds->cxlds.dev, "Failed to set event interrupt policy : %d", rc); - return rc; + return rc; +} + +static int cxl_event_config_msgnums(struct cxl_memdev_state *mds, + struct cxl_event_interrupt_policy *policy, + bool native_cxl, size_t policy_size) +{ + int rc; + + /* memory event policy is left if FW has control */ + if (native_cxl) { + *policy = (struct cxl_event_interrupt_policy) { + .info_settings = CXL_INT_MSI_MSIX, + .warn_settings = CXL_INT_MSI_MSIX, + .failure_settings = CXL_INT_MSI_MSIX, + .fatal_settings = CXL_INT_MSI_MSIX, + .dcd_settings = 0, + }; } + /* + * A CXL 3.0+ device can carry dcd_settings field without DCD command + * support, so size the request by the device's policy_size and only + * enable the DCD interrupt when DCD commands are supported. + */ + if (cxl_dcd_supported(mds)) + policy->dcd_settings = CXL_INT_MSI_MSIX; + + rc = cxl_event_set_int_policy(mds, policy, policy_size); + if (rc) + return rc; + /* Retrieve final interrupt settings */ - return cxl_event_get_int_policy(mds, policy); + return cxl_event_get_int_policy(mds, policy, NULL); } -static int cxl_event_irqsetup(struct cxl_memdev_state *mds) +static int cxl_event_irqsetup(struct cxl_memdev_state *mds, + struct cxl_event_interrupt_policy *policy) { struct cxl_dev_state *cxlds = &mds->cxlds; - struct cxl_event_interrupt_policy policy; int rc; - rc = cxl_event_config_msgnums(mds, &policy); - if (rc) - return rc; - - rc = cxl_event_req_irq(cxlds, policy.info_settings); + rc = cxl_event_req_irq(cxlds, policy->info_settings); if (rc) { dev_err(cxlds->dev, "Failed to get interrupt for event Info log\n"); return rc; } - rc = cxl_event_req_irq(cxlds, policy.warn_settings); + rc = cxl_event_req_irq(cxlds, policy->warn_settings); if (rc) { dev_err(cxlds->dev, "Failed to get interrupt for event Warn log\n"); return rc; } - rc = cxl_event_req_irq(cxlds, policy.failure_settings); + rc = cxl_event_req_irq(cxlds, policy->failure_settings); if (rc) { dev_err(cxlds->dev, "Failed to get interrupt for event Failure log\n"); return rc; } - rc = cxl_event_req_irq(cxlds, policy.fatal_settings); + rc = cxl_event_req_irq(cxlds, policy->fatal_settings); if (rc) { dev_err(cxlds->dev, "Failed to get interrupt for event Fatal log\n"); return rc; @@ -640,6 +675,37 @@ static int cxl_event_irqsetup(struct cxl_memdev_state *mds) return 0; } +static int cxl_irqsetup(struct cxl_memdev_state *mds, + struct cxl_event_interrupt_policy *policy, + bool native_cxl, size_t policy_size) +{ + struct cxl_dev_state *cxlds = &mds->cxlds; + int rc; + + if (native_cxl) { + rc = cxl_event_irqsetup(mds, policy); + if (rc) + return rc; + } + + if (cxl_dcd_supported(mds)) { + rc = cxl_event_req_irq(cxlds, policy->dcd_settings); + if (rc) { + dev_err(cxlds->dev, "Failed to get interrupt for DCD event log\n"); + cxl_disable_dcd(mds); + /* No handler will service the DCD vector; tell the device */ + policy->dcd_settings = CXL_INT_NONE; + rc = cxl_event_set_int_policy(mds, policy, policy_size); + if (rc) + dev_warn(cxlds->dev, + "DCD disabled but its interrupt policy could not be cleared: %d\n", + rc); + } + } + + return 0; +} + static bool cxl_event_int_is_fw(u8 setting) { u8 mode = FIELD_GET(CXLDEV_EVENT_INT_MODE_MASK, setting); @@ -647,48 +713,101 @@ static bool cxl_event_int_is_fw(u8 setting) return mode == CXL_INT_FW; } +static bool cxl_event_fw_owns_mem_logs(struct cxl_event_interrupt_policy *policy) +{ + return cxl_event_int_is_fw(policy->info_settings) || + cxl_event_int_is_fw(policy->warn_settings) || + cxl_event_int_is_fw(policy->failure_settings) || + cxl_event_int_is_fw(policy->fatal_settings); +} + static int cxl_event_config(struct pci_host_bridge *host_bridge, struct cxl_memdev_state *mds, bool irq_avail) { - struct cxl_event_interrupt_policy policy; + struct cxl_event_interrupt_policy policy = { 0 }; + bool native_cxl = host_bridge->native_cxl_error; + size_t policy_size; + u32 mask; int rc; /* * When BIOS maintains CXL error reporting control, it will process * event records. Only one agent can do so. + * + * If BIOS has control of events and DCD is not supported skip event + * configuration. */ - if (!host_bridge->native_cxl_error) + if (!native_cxl && !cxl_dcd_supported(mds)) return 0; if (!irq_avail) { dev_info(mds->cxlds.dev, "No interrupt support, disable event processing.\n"); + if (cxl_dcd_supported(mds)) { + dev_info(mds->cxlds.dev, "DCD requires interrupts, disable DCD\n"); + cxl_disable_dcd(mds); + } return 0; } - rc = cxl_event_get_int_policy(mds, &policy); + rc = cxl_event_get_int_policy(mds, &policy, &policy_size); if (rc) - return rc; + goto err; - if (cxl_event_int_is_fw(policy.info_settings) || - cxl_event_int_is_fw(policy.warn_settings) || - cxl_event_int_is_fw(policy.failure_settings) || - cxl_event_int_is_fw(policy.fatal_settings)) { + /* + * dcd_settings cannot be sent to a device whose policy is too short + * to hold it, so the device would never accept the setting + * cxl_irqsetup() arms the DCD interrupt with. + */ + if (cxl_dcd_supported(mds) && policy_size < sizeof(policy)) { + dev_warn(mds->cxlds.dev, + "DCD supported but interrupt policy is only %zu bytes\n", + policy_size); + cxl_disable_dcd(mds); + /* DCD was the only reason to be here */ + if (!native_cxl) + return 0; + } + + if (native_cxl && cxl_event_fw_owns_mem_logs(&policy)) { dev_err(mds->cxlds.dev, "FW still in control of Event Logs despite _OSC settings\n"); return -EBUSY; } + rc = cxl_event_config_msgnums(mds, &policy, native_cxl, policy_size); + if (rc) + goto err; + rc = cxl_mem_alloc_event_buf(mds); if (rc) return rc; - rc = cxl_event_irqsetup(mds); + rc = cxl_irqsetup(mds, &policy, native_cxl, policy_size); if (rc) return rc; - cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_ALL); + mask = cxl_event_drain_mask(host_bridge); + if (mask) + cxl_mem_get_event_records(mds, mask); + + dev_dbg(mds->cxlds.dev, "Event config : %s DCD %s\n", + native_cxl ? "OS" : "BIOS", + cxl_dcd_supported(mds) ? "supported" : "not supported"); return 0; + +err: + /* + * Failed to Get/Set interrupt policy for DCD while BIOS owns the + * other logs. Disable DCD and continue. + */ + if (!native_cxl) { + dev_warn(mds->cxlds.dev, + "Event interrupt policy failed (%d), disabling DCD\n", rc); + cxl_disable_dcd(mds); + return 0; + } + return rc; } static int cxl_pci_type3_init_mailbox(struct cxl_dev_state *cxlds) @@ -872,6 +991,12 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (rc) return rc; + if (cxl_dcd_supported(mds)) { + rc = cxl_configure_dcd(mds, &range_info); + if (rc) + cxl_disable_dcd(mds); + } + rc = cxl_dpa_setup(cxlds, &range_info); if (rc) return rc; diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h index 802b143de83d..1c04a7823f64 100644 --- a/include/cxl/cxl.h +++ b/include/cxl/cxl.h @@ -130,9 +130,30 @@ struct cxl_dpa_perf { int qos_class; }; +/* + * Partitions share one flat DPA space in a fixed order: + * + * DPA 0 end + * +---------------+---------------+---------------------------+ + * | ram | pmem | dynamic_ram_1 | + * +---------------+---------------+---------------------------+ + * part[0] part[1] part[2] + * + * The static ram and pmem partition order is mandated by CXL r4.0 + * 8.2.10.9.2.1 "Get Partition Info" (opcode 4100h), Table 8-310: volatile + * capacity starts at DPA 0 and persistent capacity starts at the DPA + * immediately following it. A dynamic partition has no such + * requirement -- 8.2.10.9.9.1 "Get Dynamic Capacity Configuration" + * (opcode 4800h), Table 8-347, only requires the DC Region Base to be + * 256MB aligned -- but Linux follows that precedent and requires it to + * start at the DPA immediately following static capacity: after pmem, + * after ram on a device with no pmem, or at DPA 0 on a device with no + * static capacity at all. cxl_configure_dcd() enforces it. + */ enum cxl_partition_mode { CXL_PARTMODE_RAM, CXL_PARTMODE_PMEM, + CXL_PARTMODE_DYNAMIC_RAM, }; /** @@ -140,14 +161,20 @@ enum cxl_partition_mode { * @res: shortcut to the partition in the DPA resource tree (cxlds->dpa_res) * @perf: performance attributes of the partition from CDAT * @mode: operation mode for the DPA capacity, e.g. ram, pmem, dynamic... + * @handle: DSMAS handle that represents this partition + * @shareable: Is the partition shareable (from its CDAT DSMAS entry) + * @read_only: Is the partition read-only (from its CDAT DSMAS entry) */ struct cxl_dpa_partition { struct resource res; struct cxl_dpa_perf perf; enum cxl_partition_mode mode; + u8 handle; + bool shareable; + bool read_only; }; -#define CXL_NR_PARTITIONS_MAX 2 +#define CXL_NR_PARTITIONS_MAX 3 /** * struct cxl_dev_state - The driver device state diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c index 7b756000a1a6..c740372bdaaa 100644 --- a/tools/testing/cxl/test/mem.c +++ b/tools/testing/cxl/test/mem.c @@ -1818,7 +1818,7 @@ static int cxl_mock_mem_probe(struct platform_device *pdev) if (rc) dev_dbg(dev, "No CXL FWCTL setup\n"); - cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_ALL); + cxl_mem_get_event_records(mds, CXLDEV_EVENT_STATUS_STANDARD_LOGS); cxl_mock_test_feat_init(mdata); return 0; |
