CXL_FEATURE_ECS,
CXL_FEATURE_SPPR,
CXL_FEATURE_HPPR,
+ CXL_FEATURE_CACHELINE_SPARING,
+ CXL_FEATURE_ROW_SPARING,
+ CXL_FEATURE_BANK_SPARING,
+ CXL_FEATURE_RANK_SPARING,
CXL_FEATURE_MAX
};
CXLMemECSWriteAttrs feat_data[];
} QEMU_PACKED QEMU_ALIGNED(16) CXLMemECSSetFeature;
+/*
+ * CXL r3.2 section 8.2.10.7.2.3:
+ * Memory Sparing Features Discovery and Configuration
+ */
+static const QemuUUID cacheline_sparing_uuid = {
+ .data = UUID(0x96C33386, 0x91dd, 0x44c7, 0x9e, 0xcb,
+ 0xfd, 0xaf, 0x65, 0x03, 0xba, 0xc4)
+};
+
+static const QemuUUID row_sparing_uuid = {
+ .data = UUID(0x450ebf67, 0xb135, 0x4f97, 0xa4, 0x98,
+ 0xc2, 0xd5, 0x7f, 0x27, 0x9b, 0xed)
+};
+
+static const QemuUUID bank_sparing_uuid = {
+ .data = UUID(0x78b79636, 0x90ac, 0x4b64, 0xa4, 0xef,
+ 0xfa, 0xac, 0x5d, 0x18, 0xa8, 0x63)
+};
+
+static const QemuUUID rank_sparing_uuid = {
+ .data = UUID(0x34dbaff5, 0x0552, 0x4281, 0x8f, 0x76,
+ 0xda, 0x0b, 0x5e, 0x7a, 0x76, 0xa7)
+};
+
+typedef struct CXLMemSparingSetFeature {
+ CXLSetFeatureInHeader hdr;
+ CXLMemSparingWriteAttrs feat_data;
+} QEMU_PACKED QEMU_ALIGNED(16) CXLMemSparingSetFeature;
+
/* CXL r3.1 section 8.2.9.6.1: Get Supported Features (Opcode 0500h) */
static CXLRetCode cmd_features_get_supported(const struct cxl_cmd *cmd,
uint8_t *payload_in,
CXL_FEAT_ENTRY_SFE_CEL_VALID,
};
break;
+ case CXL_FEATURE_CACHELINE_SPARING:
+ /* Fill supported feature entry for Cacheline Memory Sparing */
+ get_feats_out->feat_entries[entry++] =
+ (struct CXLSupportedFeatureEntry) {
+ .uuid = cacheline_sparing_uuid,
+ .feat_index = index,
+ .get_feat_size = sizeof(CXLMemSparingReadAttrs),
+ .set_feat_size = sizeof(CXLMemSparingWriteAttrs),
+ .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE |
+ CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL,
+ .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION,
+ .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION,
+ .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE |
+ CXL_FEAT_ENTRY_SFE_CEL_VALID,
+ };
+ break;
+ case CXL_FEATURE_ROW_SPARING:
+ /* Fill supported feature entry for Row Memory Sparing */
+ get_feats_out->feat_entries[entry++] =
+ (struct CXLSupportedFeatureEntry) {
+ .uuid = row_sparing_uuid,
+ .feat_index = index,
+ .get_feat_size = sizeof(CXLMemSparingReadAttrs),
+ .set_feat_size = sizeof(CXLMemSparingWriteAttrs),
+ .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE |
+ CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL,
+ .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION,
+ .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION,
+ .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE |
+ CXL_FEAT_ENTRY_SFE_CEL_VALID,
+ };
+ break;
+ case CXL_FEATURE_BANK_SPARING:
+ /* Fill supported feature entry for Bank Memory Sparing */
+ get_feats_out->feat_entries[entry++] =
+ (struct CXLSupportedFeatureEntry) {
+ .uuid = bank_sparing_uuid,
+ .feat_index = index,
+ .get_feat_size = sizeof(CXLMemSparingReadAttrs),
+ .set_feat_size = sizeof(CXLMemSparingWriteAttrs),
+ .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE |
+ CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL,
+ .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION,
+ .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION,
+ .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE |
+ CXL_FEAT_ENTRY_SFE_CEL_VALID,
+ };
+ break;
+ case CXL_FEATURE_RANK_SPARING:
+ /* Fill supported feature entry for Rank Memory Sparing */
+ get_feats_out->feat_entries[entry++] =
+ (struct CXLSupportedFeatureEntry) {
+ .uuid = rank_sparing_uuid,
+ .feat_index = index,
+ .get_feat_size = sizeof(CXLMemSparingReadAttrs),
+ .set_feat_size = sizeof(CXLMemSparingWriteAttrs),
+ .attr_flags = CXL_FEAT_ENTRY_ATTR_FLAG_CHANGABLE |
+ CXL_FEAT_ENTRY_ATTR_FLAG_SUPPORT_DEFAULT_SEL,
+ .get_feat_version = CXL_MEMDEV_SPARING_GET_FEATURE_VERSION,
+ .set_feat_version = CXL_MEMDEV_SPARING_SET_FEATURE_VERSION,
+ .set_feat_effects = CXL_FEAT_ENTRY_SFE_IMMEDIATE_CONFIG_CHANGE |
+ CXL_FEAT_ENTRY_SFE_CEL_VALID,
+ };
+ break;
default:
__builtin_unreachable();
}
memcpy(payload_out,
(uint8_t *)&ct3d->hard_ppr_attrs + get_feature->offset,
bytes_to_copy);
+ } else if (qemu_uuid_is_equal(&get_feature->uuid,
+ &cacheline_sparing_uuid)) {
+ if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ bytes_to_copy = sizeof(CXLMemSparingReadAttrs) -
+ get_feature->offset;
+ bytes_to_copy = MIN(bytes_to_copy, get_feature->count);
+ memcpy(payload_out,
+ (uint8_t *)&ct3d->cacheline_sparing_attrs + get_feature->offset,
+ bytes_to_copy);
+ } else if (qemu_uuid_is_equal(&get_feature->uuid, &row_sparing_uuid)) {
+ if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ bytes_to_copy = sizeof(CXLMemSparingReadAttrs) -
+ get_feature->offset;
+ bytes_to_copy = MIN(bytes_to_copy, get_feature->count);
+ memcpy(payload_out,
+ (uint8_t *)&ct3d->row_sparing_attrs + get_feature->offset,
+ bytes_to_copy);
+ } else if (qemu_uuid_is_equal(&get_feature->uuid, &bank_sparing_uuid)) {
+ if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ bytes_to_copy = sizeof(CXLMemSparingReadAttrs) -
+ get_feature->offset;
+ bytes_to_copy = MIN(bytes_to_copy, get_feature->count);
+ memcpy(payload_out,
+ (uint8_t *)&ct3d->bank_sparing_attrs + get_feature->offset,
+ bytes_to_copy);
+ } else if (qemu_uuid_is_equal(&get_feature->uuid, &rank_sparing_uuid)) {
+ if (get_feature->offset >= sizeof(CXLMemSparingReadAttrs)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ bytes_to_copy = sizeof(CXLMemSparingReadAttrs) -
+ get_feature->offset;
+ bytes_to_copy = MIN(bytes_to_copy, get_feature->count);
+ memcpy(payload_out,
+ (uint8_t *)&ct3d->rank_sparing_attrs + get_feature->offset,
+ bytes_to_copy);
} else {
return CXL_MBOX_UNSUPPORTED;
}
ct3d->hard_ppr_attrs.hppr_op_mode =
ct3d->hard_ppr_wr_attrs.hppr_op_mode;
}
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &cacheline_sparing_uuid)) {
+ CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in;
+ CXLMemSparingWriteAttrs *mem_sparing_write_attrs =
+ &mem_sparing_set_feature->feat_data;
+
+ if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) {
+ return CXL_MBOX_UNSUPPORTED;
+ }
+
+ memcpy((uint8_t *)&ct3d->cacheline_sparing_wr_attrs + hdr->offset,
+ mem_sparing_write_attrs, bytes_to_copy);
+ set_feat_info->data_size += bytes_to_copy;
+
+ if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
+ data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
+ ct3d->cacheline_sparing_attrs.op_mode =
+ ct3d->cacheline_sparing_wr_attrs.op_mode;
+ }
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &row_sparing_uuid)) {
+ CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in;
+ CXLMemSparingWriteAttrs *mem_sparing_write_attrs =
+ &mem_sparing_set_feature->feat_data;
+
+ if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) {
+ return CXL_MBOX_UNSUPPORTED;
+ }
+
+ memcpy((uint8_t *)&ct3d->row_sparing_wr_attrs + hdr->offset,
+ mem_sparing_write_attrs, bytes_to_copy);
+ set_feat_info->data_size += bytes_to_copy;
+
+ if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
+ data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
+ ct3d->row_sparing_attrs.op_mode =
+ ct3d->row_sparing_wr_attrs.op_mode;
+ }
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &bank_sparing_uuid)) {
+ CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in;
+ CXLMemSparingWriteAttrs *mem_sparing_write_attrs =
+ &mem_sparing_set_feature->feat_data;
+
+ if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) {
+ return CXL_MBOX_UNSUPPORTED;
+ }
+
+ memcpy((uint8_t *)&ct3d->bank_sparing_wr_attrs + hdr->offset,
+ mem_sparing_write_attrs, bytes_to_copy);
+ set_feat_info->data_size += bytes_to_copy;
+
+ if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
+ data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
+ ct3d->bank_sparing_attrs.op_mode =
+ ct3d->bank_sparing_wr_attrs.op_mode;
+ }
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &rank_sparing_uuid)) {
+ CXLMemSparingSetFeature *mem_sparing_set_feature = (void *)payload_in;
+ CXLMemSparingWriteAttrs *mem_sparing_write_attrs =
+ &mem_sparing_set_feature->feat_data;
+
+ if (hdr->version != CXL_MEMDEV_SPARING_SET_FEATURE_VERSION) {
+ return CXL_MBOX_UNSUPPORTED;
+ }
+
+ memcpy((uint8_t *)&ct3d->rank_sparing_wr_attrs + hdr->offset,
+ mem_sparing_write_attrs, bytes_to_copy);
+ set_feat_info->data_size += bytes_to_copy;
+
+ if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
+ data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
+ ct3d->rank_sparing_attrs.op_mode =
+ ct3d->rank_sparing_wr_attrs.op_mode;
+ }
} else {
return CXL_MBOX_UNSUPPORTED;
}
memset(&ct3d->soft_ppr_wr_attrs, 0, set_feat_info->data_size);
} else if (qemu_uuid_is_equal(&hdr->uuid, &hard_ppr_uuid)) {
memset(&ct3d->hard_ppr_wr_attrs, 0, set_feat_info->data_size);
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &cacheline_sparing_uuid)) {
+ memset(&ct3d->cacheline_sparing_wr_attrs, 0,
+ set_feat_info->data_size);
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &row_sparing_uuid)) {
+ memset(&ct3d->row_sparing_wr_attrs, 0, set_feat_info->data_size);
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &bank_sparing_uuid)) {
+ memset(&ct3d->bank_sparing_wr_attrs, 0, set_feat_info->data_size);
+ } else if (qemu_uuid_is_equal(&hdr->uuid, &rank_sparing_uuid)) {
+ memset(&ct3d->rank_sparing_wr_attrs, 0, set_feat_info->data_size);
}
set_feat_info->data_transfer_flag = 0;
set_feat_info->data_saved_across_reset = false;
return CXL_MBOX_SUCCESS;
}
-static void cxl_mbox_create_mem_sparing_event_records(CXLType3Dev *ct3d,
+#define CXL_MEM_SPARING_FLAGS_QUERY_RESOURCES BIT(0)
+#define CXL_MEM_SPARING_FLAGS_HARD_SPARING BIT(1)
+#define CXL_MEM_SPARING_FLAGS_SUB_CHANNEL_VALID BIT(2)
+#define CXL_MEM_SPARING_FLAGS_NIB_MASK_VALID BIT(3)
+
+typedef struct CXLMemSparingMaintInPayload {
+ uint8_t flags;
+ uint8_t channel;
+ uint8_t rank;
+ uint8_t nibble_mask[3];
+ uint8_t bank_group;
+ uint8_t bank;
+ uint8_t row[3];
+ uint16_t column;
+ uint8_t sub_channel;
+} QEMU_PACKED CXLMemSparingMaintInPayload;
+
+static void cxl_create_mem_sparing_event_records(CXLType3Dev *ct3d,
uint8_t maint_op_class, uint8_t maint_op_sub_class,
- CXLMaintenance *ent)
+ CXLMaintenance *ent,
+ CXLMemSparingMaintInPayload *sparing_pi)
{
CXLEventSparing event_rec = {};
strncpy((char *)event_rec.component_id, (char *)ent->component_id,
sizeof(event_rec.component_id));
}
+ } else if (sparing_pi) {
+ event_rec.flags = CXL_MSER_FLAGS_QUERY_RESOURCES;
+ event_rec.result = 0;
+ event_rec.validity_flags = CXL_MSER_VALID_CHANNEL |
+ CXL_MSER_VALID_RANK |
+ CXL_MSER_VALID_NIB_MASK |
+ CXL_MSER_VALID_BANK_GROUP |
+ CXL_MSER_VALID_BANK |
+ CXL_MSER_VALID_ROW |
+ CXL_MSER_VALID_COLUMN;
+ event_rec.res_avail = 1;
+ event_rec.channel = sparing_pi->channel;
+ event_rec.rank = sparing_pi->rank;
+ if (sparing_pi->flags & CXL_MEM_SPARING_FLAGS_NIB_MASK_VALID) {
+ memcpy(event_rec.nibble_mask, sparing_pi->nibble_mask,
+ sizeof(sparing_pi->nibble_mask));
+ }
+ event_rec.bank_group = sparing_pi->bank_group;
+ event_rec.bank = sparing_pi->bank;
+ event_rec.column = sparing_pi->column;
+ memcpy(event_rec.row, sparing_pi->row, sizeof(sparing_pi->row));
+ if (sparing_pi->flags & CXL_MEM_SPARING_FLAGS_SUB_CHANNEL_VALID) {
+ event_rec.sub_channel = sparing_pi->sub_channel;
+ event_rec.validity_flags |= CXL_MSER_VALID_SUB_CHANNEL;
+ }
} else {
return;
}
}
}
+static CXLRetCode cxl_perform_mem_sparing(CXLType3Dev *ct3d, uint8_t sub_class,
+ void *maint_pi)
+{
+ switch (sub_class) {
+ case CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING:
+ qemu_log("Cacheline Memory Sparing\n");
+ return CXL_MBOX_SUCCESS;
+ case CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING:
+ qemu_log("Row Memory Sparing\n");
+ return CXL_MBOX_SUCCESS;
+ case CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING:
+ qemu_log("Bank Memory Sparing\n");
+ return CXL_MBOX_SUCCESS;
+ case CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING:
+ qemu_log("Rank Memory Sparing\n");
+ return CXL_MBOX_SUCCESS;
+ default:
+ return CXL_MBOX_INVALID_INPUT;
+ }
+}
static void cxl_perform_ppr(CXLType3Dev *ct3d, uint64_t dpa)
{
/* Produce a Memory Sparing Event Record */
if (ct3d->soft_ppr_attrs.sppr_op_mode &
CXL_MEMDEV_SPPR_OP_MODE_MEM_SPARING_EV_REC_EN) {
- cxl_mbox_create_mem_sparing_event_records(ct3d,
+ cxl_create_mem_sparing_event_records(ct3d,
CXL_MEMDEV_MAINT_CLASS_SPARING,
CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING,
- ent);
+ ent, NULL);
}
break;
}
uint64_t dpa;
uint8_t nibble_mask[3];
} QEMU_PACKED ppr;
+ CXLMemSparingMaintInPayload mem_sparing_pi;
};
} QEMU_PACKED *maint_in = (void *)payload_in;
CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
return CXL_MBOX_INVALID_INPUT;
}
break;
+ case CXL_MEMDEV_MAINT_CLASS_SPARING:
+ if (maint_in->mem_sparing_pi.flags &
+ CXL_MEM_SPARING_FLAGS_QUERY_RESOURCES) {
+ /*
+ * CXL r3.2 sect 8.2.10.7.1.4 - Memory Sparing Maintenance Operation
+ * Produce Memory Sparing Event record to report resources
+ * availability.
+ */
+ cxl_create_mem_sparing_event_records(ct3d, maint_in->class,
+ maint_in->subclass, NULL,
+ &maint_in->mem_sparing_pi);
+
+ return CXL_MBOX_SUCCESS;
+ }
+
+ switch (maint_in->subclass) {
+ case CXL_MEMDEV_MAINT_SUBCLASS_CACHELINE_SPARING:
+ case CXL_MEMDEV_MAINT_SUBCLASS_ROW_SPARING:
+ case CXL_MEMDEV_MAINT_SUBCLASS_BANK_SPARING:
+ case CXL_MEMDEV_MAINT_SUBCLASS_RANK_SPARING:
+ return cxl_perform_mem_sparing(ct3d, maint_in->subclass,
+ &maint_in->mem_sparing_pi);
+ default:
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ break;
default:
return CXL_MBOX_INVALID_INPUT;
}