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)
This commit is contained in:
Dave Jiang 2026-10-13 09:01:39 -07:00
commit d304df5f32
12 changed files with 800 additions and 107 deletions

View File

@ -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

View File

@ -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
------------------

View File

@ -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);
}

View File

@ -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++;

View File

@ -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;

View File

@ -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");

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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) {
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_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 = {
.opcode = CXL_MBOX_OP_SET_EVT_INT_POLICY,
.payload_in = policy,
.size_in = policy_size,
};
int rc;
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
dev_err(mds->cxlds.dev,
"Failed to get event interrupt policy : %d", rc);
dev_err(mds->cxlds.dev, "Failed to set event interrupt policy : %d",
rc);
return rc;
}
static int cxl_event_config_msgnums(struct cxl_memdev_state *mds,
struct cxl_event_interrupt_policy *policy)
struct cxl_event_interrupt_policy *policy,
bool native_cxl, 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) {
.opcode = CXL_MBOX_OP_SET_EVT_INT_POLICY,
.payload_in = policy,
.size_in = sizeof(*policy),
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0) {
dev_err(mds->cxlds.dev, "Failed to set event interrupt policy : %d",
rc);
return 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,
};
}
/* Retrieve final interrupt settings */
return cxl_event_get_int_policy(mds, policy);
}
/*
* 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;
static int cxl_event_irqsetup(struct cxl_memdev_state *mds)
{
struct cxl_dev_state *cxlds = &mds->cxlds;
struct cxl_event_interrupt_policy policy;
int rc;
rc = cxl_event_config_msgnums(mds, &policy);
rc = cxl_event_set_int_policy(mds, policy, policy_size);
if (rc)
return rc;
rc = cxl_event_req_irq(cxlds, policy.info_settings);
/* Retrieve final interrupt settings */
return cxl_event_get_int_policy(mds, policy, NULL);
}
static int cxl_event_irqsetup(struct cxl_memdev_state *mds,
struct cxl_event_interrupt_policy *policy)
{
struct cxl_dev_state *cxlds = &mds->cxlds;
int rc;
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;

View File

@ -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

View File

@ -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;