mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
commit
d304df5f32
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user