|
@@ -674,6 +674,7 @@ static bool cxl_create_dc_regions(CXLType3Dev *ct3d, Error **errp)
|
|
ct3d->dc.total_capacity += region->len;
|
|
ct3d->dc.total_capacity += region->len;
|
|
}
|
|
}
|
|
QTAILQ_INIT(&ct3d->dc.extents);
|
|
QTAILQ_INIT(&ct3d->dc.extents);
|
|
|
|
+ QTAILQ_INIT(&ct3d->dc.extents_pending);
|
|
|
|
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
@@ -681,10 +682,19 @@ static bool cxl_create_dc_regions(CXLType3Dev *ct3d, Error **errp)
|
|
static void cxl_destroy_dc_regions(CXLType3Dev *ct3d)
|
|
static void cxl_destroy_dc_regions(CXLType3Dev *ct3d)
|
|
{
|
|
{
|
|
CXLDCExtent *ent, *ent_next;
|
|
CXLDCExtent *ent, *ent_next;
|
|
|
|
+ CXLDCExtentGroup *group, *group_next;
|
|
|
|
|
|
QTAILQ_FOREACH_SAFE(ent, &ct3d->dc.extents, node, ent_next) {
|
|
QTAILQ_FOREACH_SAFE(ent, &ct3d->dc.extents, node, ent_next) {
|
|
cxl_remove_extent_from_extent_list(&ct3d->dc.extents, ent);
|
|
cxl_remove_extent_from_extent_list(&ct3d->dc.extents, ent);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ QTAILQ_FOREACH_SAFE(group, &ct3d->dc.extents_pending, node, group_next) {
|
|
|
|
+ QTAILQ_REMOVE(&ct3d->dc.extents_pending, group, node);
|
|
|
|
+ QTAILQ_FOREACH_SAFE(ent, &group->list, node, ent_next) {
|
|
|
|
+ cxl_remove_extent_from_extent_list(&group->list, ent);
|
|
|
|
+ }
|
|
|
|
+ g_free(group);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
static bool cxl_setup_memory(CXLType3Dev *ct3d, Error **errp)
|
|
static bool cxl_setup_memory(CXLType3Dev *ct3d, Error **errp)
|
|
@@ -1449,7 +1459,6 @@ static int ct3d_qmp_cxl_event_log_enc(CxlEventLog log)
|
|
return CXL_EVENT_TYPE_FAIL;
|
|
return CXL_EVENT_TYPE_FAIL;
|
|
case CXL_EVENT_LOG_FATAL:
|
|
case CXL_EVENT_LOG_FATAL:
|
|
return CXL_EVENT_TYPE_FATAL;
|
|
return CXL_EVENT_TYPE_FATAL;
|
|
-/* DCD not yet supported */
|
|
|
|
default:
|
|
default:
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
@@ -1700,6 +1709,301 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* CXL r3.1 Table 8-50: Dynamic Capacity Event Record */
|
|
|
|
+static const QemuUUID dynamic_capacity_uuid = {
|
|
|
|
+ .data = UUID(0xca95afa7, 0xf183, 0x4018, 0x8c, 0x2f,
|
|
|
|
+ 0x95, 0x26, 0x8e, 0x10, 0x1a, 0x2a),
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+typedef enum CXLDCEventType {
|
|
|
|
+ DC_EVENT_ADD_CAPACITY = 0x0,
|
|
|
|
+ DC_EVENT_RELEASE_CAPACITY = 0x1,
|
|
|
|
+ DC_EVENT_FORCED_RELEASE_CAPACITY = 0x2,
|
|
|
|
+ DC_EVENT_REGION_CONFIG_UPDATED = 0x3,
|
|
|
|
+ DC_EVENT_ADD_CAPACITY_RSP = 0x4,
|
|
|
|
+ DC_EVENT_CAPACITY_RELEASED = 0x5,
|
|
|
|
+} CXLDCEventType;
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Check whether the range [dpa, dpa + len - 1] has overlaps with extents in
|
|
|
|
+ * the list.
|
|
|
|
+ * Return value: return true if has overlaps; otherwise, return false
|
|
|
|
+ */
|
|
|
|
+static bool cxl_extents_overlaps_dpa_range(CXLDCExtentList *list,
|
|
|
|
+ uint64_t dpa, uint64_t len)
|
|
|
|
+{
|
|
|
|
+ CXLDCExtent *ent;
|
|
|
|
+ Range range1, range2;
|
|
|
|
+
|
|
|
|
+ if (!list) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ range_init_nofail(&range1, dpa, len);
|
|
|
|
+ QTAILQ_FOREACH(ent, list, node) {
|
|
|
|
+ range_init_nofail(&range2, ent->start_dpa, ent->len);
|
|
|
|
+ if (range_overlaps_range(&range1, &range2)) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Check whether the range [dpa, dpa + len - 1] is contained by extents in
|
|
|
|
+ * the list.
|
|
|
|
+ * Will check multiple extents containment once superset release is added.
|
|
|
|
+ * Return value: return true if range is contained; otherwise, return false
|
|
|
|
+ */
|
|
|
|
+bool cxl_extents_contains_dpa_range(CXLDCExtentList *list,
|
|
|
|
+ uint64_t dpa, uint64_t len)
|
|
|
|
+{
|
|
|
|
+ CXLDCExtent *ent;
|
|
|
|
+ Range range1, range2;
|
|
|
|
+
|
|
|
|
+ if (!list) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ range_init_nofail(&range1, dpa, len);
|
|
|
|
+ QTAILQ_FOREACH(ent, list, node) {
|
|
|
|
+ range_init_nofail(&range2, ent->start_dpa, ent->len);
|
|
|
|
+ if (range_contains_range(&range2, &range1)) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool cxl_extent_groups_overlaps_dpa_range(CXLDCExtentGroupList *list,
|
|
|
|
+ uint64_t dpa, uint64_t len)
|
|
|
|
+{
|
|
|
|
+ CXLDCExtentGroup *group;
|
|
|
|
+
|
|
|
|
+ if (!list) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ QTAILQ_FOREACH(group, list, node) {
|
|
|
|
+ if (cxl_extents_overlaps_dpa_range(&group->list, dpa, len)) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * The main function to process dynamic capacity event with extent list.
|
|
|
|
+ * Currently DC extents add/release requests are processed.
|
|
|
|
+ */
|
|
|
|
+static void qmp_cxl_process_dynamic_capacity_prescriptive(const char *path,
|
|
|
|
+ uint16_t hid, CXLDCEventType type, uint8_t rid,
|
|
|
|
+ CXLDynamicCapacityExtentList *records, Error **errp)
|
|
|
|
+{
|
|
|
|
+ Object *obj;
|
|
|
|
+ CXLEventDynamicCapacity dCap = {};
|
|
|
|
+ CXLEventRecordHdr *hdr = &dCap.hdr;
|
|
|
|
+ CXLType3Dev *dcd;
|
|
|
|
+ uint8_t flags = 1 << CXL_EVENT_TYPE_INFO;
|
|
|
|
+ uint32_t num_extents = 0;
|
|
|
|
+ CXLDynamicCapacityExtentList *list;
|
|
|
|
+ CXLDCExtentGroup *group = NULL;
|
|
|
|
+ g_autofree CXLDCExtentRaw *extents = NULL;
|
|
|
|
+ uint8_t enc_log = CXL_EVENT_TYPE_DYNAMIC_CAP;
|
|
|
|
+ uint64_t dpa, offset, len, block_size;
|
|
|
|
+ g_autofree unsigned long *blk_bitmap = NULL;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ obj = object_resolve_path_type(path, TYPE_CXL_TYPE3, NULL);
|
|
|
|
+ if (!obj) {
|
|
|
|
+ error_setg(errp, "Unable to resolve CXL type 3 device");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dcd = CXL_TYPE3(obj);
|
|
|
|
+ if (!dcd->dc.num_regions) {
|
|
|
|
+ error_setg(errp, "No dynamic capacity support from the device");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (rid >= dcd->dc.num_regions) {
|
|
|
|
+ error_setg(errp, "region id is too large");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ block_size = dcd->dc.regions[rid].block_size;
|
|
|
|
+ blk_bitmap = bitmap_new(dcd->dc.regions[rid].len / block_size);
|
|
|
|
+
|
|
|
|
+ /* Sanity check and count the extents */
|
|
|
|
+ list = records;
|
|
|
|
+ while (list) {
|
|
|
|
+ offset = list->value->offset;
|
|
|
|
+ len = list->value->len;
|
|
|
|
+ dpa = offset + dcd->dc.regions[rid].base;
|
|
|
|
+
|
|
|
|
+ if (len == 0) {
|
|
|
|
+ error_setg(errp, "extent with 0 length is not allowed");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (offset % block_size || len % block_size) {
|
|
|
|
+ error_setg(errp, "dpa or len is not aligned to region block size");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (offset + len > dcd->dc.regions[rid].len) {
|
|
|
|
+ error_setg(errp, "extent range is beyond the region end");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* No duplicate or overlapped extents are allowed */
|
|
|
|
+ if (test_any_bits_set(blk_bitmap, offset / block_size,
|
|
|
|
+ len / block_size)) {
|
|
|
|
+ error_setg(errp, "duplicate or overlapped extents are detected");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ bitmap_set(blk_bitmap, offset / block_size, len / block_size);
|
|
|
|
+
|
|
|
|
+ if (type == DC_EVENT_RELEASE_CAPACITY) {
|
|
|
|
+ if (cxl_extent_groups_overlaps_dpa_range(&dcd->dc.extents_pending,
|
|
|
|
+ dpa, len)) {
|
|
|
|
+ error_setg(errp,
|
|
|
|
+ "cannot release extent with pending DPA range");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (!cxl_extents_contains_dpa_range(&dcd->dc.extents, dpa, len)) {
|
|
|
|
+ error_setg(errp,
|
|
|
|
+ "cannot release extent with non-existing DPA range");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ } else if (type == DC_EVENT_ADD_CAPACITY) {
|
|
|
|
+ if (cxl_extents_overlaps_dpa_range(&dcd->dc.extents, dpa, len)) {
|
|
|
|
+ error_setg(errp,
|
|
|
|
+ "cannot add DPA already accessible to the same LD");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (cxl_extent_groups_overlaps_dpa_range(&dcd->dc.extents_pending,
|
|
|
|
+ dpa, len)) {
|
|
|
|
+ error_setg(errp,
|
|
|
|
+ "cannot add DPA again while still pending");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ list = list->next;
|
|
|
|
+ num_extents++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Create extent list for event being passed to host */
|
|
|
|
+ i = 0;
|
|
|
|
+ list = records;
|
|
|
|
+ extents = g_new0(CXLDCExtentRaw, num_extents);
|
|
|
|
+ while (list) {
|
|
|
|
+ offset = list->value->offset;
|
|
|
|
+ len = list->value->len;
|
|
|
|
+ dpa = dcd->dc.regions[rid].base + offset;
|
|
|
|
+
|
|
|
|
+ extents[i].start_dpa = dpa;
|
|
|
|
+ extents[i].len = len;
|
|
|
|
+ memset(extents[i].tag, 0, 0x10);
|
|
|
|
+ extents[i].shared_seq = 0;
|
|
|
|
+ if (type == DC_EVENT_ADD_CAPACITY) {
|
|
|
|
+ group = cxl_insert_extent_to_extent_group(group,
|
|
|
|
+ extents[i].start_dpa,
|
|
|
|
+ extents[i].len,
|
|
|
|
+ extents[i].tag,
|
|
|
|
+ extents[i].shared_seq);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ list = list->next;
|
|
|
|
+ i++;
|
|
|
|
+ }
|
|
|
|
+ if (group) {
|
|
|
|
+ cxl_extent_group_list_insert_tail(&dcd->dc.extents_pending, group);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * CXL r3.1 section 8.2.9.2.1.6: Dynamic Capacity Event Record
|
|
|
|
+ *
|
|
|
|
+ * All Dynamic Capacity event records shall set the Event Record Severity
|
|
|
|
+ * field in the Common Event Record Format to Informational Event. All
|
|
|
|
+ * Dynamic Capacity related events shall be logged in the Dynamic Capacity
|
|
|
|
+ * Event Log.
|
|
|
|
+ */
|
|
|
|
+ cxl_assign_event_header(hdr, &dynamic_capacity_uuid, flags, sizeof(dCap),
|
|
|
|
+ cxl_device_get_timestamp(&dcd->cxl_dstate));
|
|
|
|
+
|
|
|
|
+ dCap.type = type;
|
|
|
|
+ /* FIXME: for now, validity flag is cleared */
|
|
|
|
+ dCap.validity_flags = 0;
|
|
|
|
+ stw_le_p(&dCap.host_id, hid);
|
|
|
|
+ /* only valid for DC_REGION_CONFIG_UPDATED event */
|
|
|
|
+ dCap.updated_region_id = 0;
|
|
|
|
+ dCap.flags = 0;
|
|
|
|
+ for (i = 0; i < num_extents; i++) {
|
|
|
|
+ memcpy(&dCap.dynamic_capacity_extent, &extents[i],
|
|
|
|
+ sizeof(CXLDCExtentRaw));
|
|
|
|
+
|
|
|
|
+ if (i < num_extents - 1) {
|
|
|
|
+ /* Set "More" flag */
|
|
|
|
+ dCap.flags |= BIT(0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (cxl_event_insert(&dcd->cxl_dstate, enc_log,
|
|
|
|
+ (CXLEventRecordRaw *)&dCap)) {
|
|
|
|
+ cxl_event_irq_assert(dcd);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void qmp_cxl_add_dynamic_capacity(const char *path, uint16_t host_id,
|
|
|
|
+ CXLExtSelPolicy sel_policy, uint8_t region,
|
|
|
|
+ const char *tag,
|
|
|
|
+ CXLDynamicCapacityExtentList *extents,
|
|
|
|
+ Error **errp)
|
|
|
|
+{
|
|
|
|
+ switch (sel_policy) {
|
|
|
|
+ case CXL_EXT_SEL_POLICY_PRESCRIPTIVE:
|
|
|
|
+ qmp_cxl_process_dynamic_capacity_prescriptive(path, host_id,
|
|
|
|
+ DC_EVENT_ADD_CAPACITY,
|
|
|
|
+ region, extents, errp);
|
|
|
|
+ return;
|
|
|
|
+ default:
|
|
|
|
+ error_setg(errp, "Selection policy not supported");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void qmp_cxl_release_dynamic_capacity(const char *path, uint16_t host_id,
|
|
|
|
+ CXLExtRemovalPolicy removal_policy,
|
|
|
|
+ bool has_forced_removal,
|
|
|
|
+ bool forced_removal,
|
|
|
|
+ bool has_sanitize_on_release,
|
|
|
|
+ bool sanitize_on_release,
|
|
|
|
+ uint8_t region,
|
|
|
|
+ const char *tag,
|
|
|
|
+ CXLDynamicCapacityExtentList *extents,
|
|
|
|
+ Error **errp)
|
|
|
|
+{
|
|
|
|
+ CXLDCEventType type = DC_EVENT_RELEASE_CAPACITY;
|
|
|
|
+
|
|
|
|
+ if (has_forced_removal && forced_removal) {
|
|
|
|
+ /* TODO: enable forced removal in the future */
|
|
|
|
+ type = DC_EVENT_FORCED_RELEASE_CAPACITY;
|
|
|
|
+ error_setg(errp, "Forced removal not supported yet");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (removal_policy) {
|
|
|
|
+ case CXL_EXT_REMOVAL_POLICY_PRESCRIPTIVE:
|
|
|
|
+ qmp_cxl_process_dynamic_capacity_prescriptive(path, host_id, type,
|
|
|
|
+ region, extents, errp);
|
|
|
|
+ return;
|
|
|
|
+ default:
|
|
|
|
+ error_setg(errp, "Removal policy not supported");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
static void ct3_class_init(ObjectClass *oc, void *data)
|
|
static void ct3_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|