*/
#include "qemu/osdep.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
#include "hw/cxl/cxl.h"
#include "hw/cxl/cxl_events.h"
#include "hw/pci/pci.h"
#include "qemu/log.h"
#include "qemu/units.h"
#include "qemu/uuid.h"
+#include "sysemu/hostmem.h"
#define CXL_CAPACITY_MULTIPLIER (256 * MiB)
enum {
INFOSTAT = 0x00,
#define IS_IDENTIFY 0x1
+ #define BACKGROUND_OPERATION_STATUS 0x2
EVENTS = 0x01,
#define GET_RECORDS 0x0
#define CLEAR_RECORDS 0x1
#define GET_PARTITION_INFO 0x0
#define GET_LSA 0x2
#define SET_LSA 0x3
+ SANITIZE = 0x44,
+ #define OVERWRITE 0x0
+ #define SECURE_ERASE 0x1
+ PERSISTENT_MEM = 0x45,
+ #define GET_SECURITY_STATE 0x0
MEDIA_AND_POISON = 0x43,
#define GET_POISON_LIST 0x0
#define INJECT_POISON 0x1
PHYSICAL_SWITCH = 0x51,
#define IDENTIFY_SWITCH_DEVICE 0x0
#define GET_PHYSICAL_PORT_STATE 0x1
+ TUNNEL = 0x53,
+ #define MANAGEMENT_COMMAND 0x0
};
+/* CCI Message Format CXL r3.0 Figure 7-19 */
+typedef struct CXLCCIMessage {
+ uint8_t category;
+#define CXL_CCI_CAT_REQ 0
+#define CXL_CCI_CAT_RSP 1
+ uint8_t tag;
+ uint8_t resv1;
+ uint8_t command;
+ uint8_t command_set;
+ uint8_t pl_length[3];
+ uint16_t rc;
+ uint16_t vendor_specific;
+ uint8_t payload[];
+} QEMU_PACKED CXLCCIMessage;
+
+/* This command is only defined to an MLD FM Owned LD or an MHD */
+static CXLRetCode cmd_tunnel_management_cmd(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ PCIDevice *tunnel_target;
+ CXLCCI *target_cci;
+ struct {
+ uint8_t port_or_ld_id;
+ uint8_t target_type;
+ uint16_t size;
+ CXLCCIMessage ccimessage;
+ } QEMU_PACKED *in;
+ struct {
+ uint16_t resp_len;
+ uint8_t resv[2];
+ CXLCCIMessage ccimessage;
+ } QEMU_PACKED *out;
+ size_t pl_length, length_out;
+ bool bg_started;
+ int rc;
+
+ if (cmd->in < sizeof(*in)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ in = (void *)payload_in;
+ out = (void *)payload_out;
+
+ /* Enough room for minimum sized message - no payload */
+ if (in->size < sizeof(in->ccimessage)) {
+ return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
+ }
+ /* Length of input payload should be in->size + a wrapping tunnel header */
+ if (in->size != len_in - offsetof(typeof(*out), ccimessage)) {
+ return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
+ }
+ if (in->ccimessage.category != CXL_CCI_CAT_REQ) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ if (in->target_type != 0) {
+ qemu_log_mask(LOG_UNIMP,
+ "Tunneled Command sent to non existent FM-LD");
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ /*
+ * Target of a tunnel unfortunately depends on type of CCI readint
+ * the message.
+ * If in a switch, then it's the port number.
+ * If in an MLD it is the ld number.
+ * If in an MHD target type indicate where we are going.
+ */
+ if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_TYPE3)) {
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ if (in->port_or_ld_id != 0) {
+ /* Only pretending to have one for now! */
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ target_cci = &ct3d->ld0_cci;
+ } else if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_USP)) {
+ CXLUpstreamPort *usp = CXL_USP(cci->d);
+
+ tunnel_target = pcie_find_port_by_pn(&PCI_BRIDGE(usp)->sec_bus,
+ in->port_or_ld_id);
+ if (!tunnel_target) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ tunnel_target =
+ pci_bridge_get_sec_bus(PCI_BRIDGE(tunnel_target))->devices[0];
+ if (!tunnel_target) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ if (object_dynamic_cast(OBJECT(tunnel_target), TYPE_CXL_TYPE3)) {
+ CXLType3Dev *ct3d = CXL_TYPE3(tunnel_target);
+ /* Tunneled VDMs always land on FM Owned LD */
+ target_cci = &ct3d->vdm_fm_owned_ld_mctp_cci;
+ } else {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ } else {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ pl_length = in->ccimessage.pl_length[2] << 16 |
+ in->ccimessage.pl_length[1] << 8 | in->ccimessage.pl_length[0];
+ rc = cxl_process_cci_message(target_cci,
+ in->ccimessage.command_set,
+ in->ccimessage.command,
+ pl_length, in->ccimessage.payload,
+ &length_out, out->ccimessage.payload,
+ &bg_started);
+ /* Payload should be in place. Rest of CCI header and needs filling */
+ out->resp_len = length_out + sizeof(CXLCCIMessage);
+ st24_le_p(out->ccimessage.pl_length, length_out);
+ out->ccimessage.rc = rc;
+ out->ccimessage.category = CXL_CCI_CAT_RSP;
+ out->ccimessage.command = in->ccimessage.command;
+ out->ccimessage.command_set = in->ccimessage.command_set;
+ out->ccimessage.tag = in->ccimessage.tag;
+ *len_out = length_out + sizeof(*out);
+
+ return CXL_MBOX_SUCCESS;
+}
static CXLRetCode cmd_events_get_records(const struct cxl_cmd *cmd,
uint8_t *payload_in, size_t len_in,
out = (struct cxl_fmapi_ident_switch_dev_resp_pl *)payload_out;
*out = (struct cxl_fmapi_ident_switch_dev_resp_pl) {
.num_physical_ports = num_phys_ports + 1, /* 1 USP */
- .num_vcss = 1, /* Not yet support multiple VCS - potentialy tricky */
+ .num_vcss = 1, /* Not yet support multiple VCS - potentially tricky */
.active_vcs_bitmask[0] = 0x1,
.total_vppbs = num_phys_ports + 1,
.bound_vppbs = num_phys_ports + 1,
return CXL_MBOX_SUCCESS;
}
+/* CXL r3.0 8.2.9.1.2 */
+static CXLRetCode cmd_infostat_bg_op_sts(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ struct {
+ uint8_t status;
+ uint8_t rsvd;
+ uint16_t opcode;
+ uint16_t returncode;
+ uint16_t vendor_ext_status;
+ } QEMU_PACKED *bg_op_status;
+ QEMU_BUILD_BUG_ON(sizeof(*bg_op_status) != 8);
+
+ bg_op_status = (void *)payload_out;
+ memset(bg_op_status, 0, sizeof(*bg_op_status));
+ bg_op_status->status = cci->bg.complete_pct << 1;
+ if (cci->bg.runtime > 0) {
+ bg_op_status->status |= 1U << 0;
+ }
+ bg_op_status->opcode = cci->bg.opcode;
+ bg_op_status->returncode = cci->bg.ret_code;
+ *len_out = sizeof(*bg_op_status);
+
+ return CXL_MBOX_SUCCESS;
+}
+
/* 8.2.9.2.1 */
static CXLRetCode cmd_firmware_update_get_info(const struct cxl_cmd *cmd,
uint8_t *payload_in,
return CXL_MBOX_SUCCESS;
}
+/* Perform the actual device zeroing */
+static void __do_sanitization(CXLType3Dev *ct3d)
+{
+ MemoryRegion *mr;
+
+ if (ct3d->hostvmem) {
+ mr = host_memory_backend_get_memory(ct3d->hostvmem);
+ if (mr) {
+ void *hostmem = memory_region_get_ram_ptr(mr);
+ memset(hostmem, 0, memory_region_size(mr));
+ }
+ }
+
+ if (ct3d->hostpmem) {
+ mr = host_memory_backend_get_memory(ct3d->hostpmem);
+ if (mr) {
+ void *hostmem = memory_region_get_ram_ptr(mr);
+ memset(hostmem, 0, memory_region_size(mr));
+ }
+ }
+ if (ct3d->lsa) {
+ mr = host_memory_backend_get_memory(ct3d->lsa);
+ if (mr) {
+ void *lsa = memory_region_get_ram_ptr(mr);
+ memset(lsa, 0, memory_region_size(mr));
+ }
+ }
+}
+
+/*
+ * CXL 3.0 spec section 8.2.9.8.5.1 - Sanitize.
+ *
+ * Once the Sanitize command has started successfully, the device shall be
+ * placed in the media disabled state. If the command fails or is interrupted
+ * by a reset or power failure, it shall remain in the media disabled state
+ * until a successful Sanitize command has been completed. During this state:
+ *
+ * 1. Memory writes to the device will have no effect, and all memory reads
+ * will return random values (no user data returned, even for locations that
+ * the failed Sanitize operation didn’t sanitize yet).
+ *
+ * 2. Mailbox commands shall still be processed in the disabled state, except
+ * that commands that access Sanitized areas shall fail with the Media Disabled
+ * error code.
+ */
+static CXLRetCode cmd_sanitize_overwrite(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ uint64_t total_mem; /* in Mb */
+ int secs;
+
+ total_mem = (ct3d->cxl_dstate.vmem_size + ct3d->cxl_dstate.pmem_size) >> 20;
+ if (total_mem <= 512) {
+ secs = 4;
+ } else if (total_mem <= 1024) {
+ secs = 8;
+ } else if (total_mem <= 2 * 1024) {
+ secs = 15;
+ } else if (total_mem <= 4 * 1024) {
+ secs = 30;
+ } else if (total_mem <= 8 * 1024) {
+ secs = 60;
+ } else if (total_mem <= 16 * 1024) {
+ secs = 2 * 60;
+ } else if (total_mem <= 32 * 1024) {
+ secs = 4 * 60;
+ } else if (total_mem <= 64 * 1024) {
+ secs = 8 * 60;
+ } else if (total_mem <= 128 * 1024) {
+ secs = 15 * 60;
+ } else if (total_mem <= 256 * 1024) {
+ secs = 30 * 60;
+ } else if (total_mem <= 512 * 1024) {
+ secs = 60 * 60;
+ } else if (total_mem <= 1024 * 1024) {
+ secs = 120 * 60;
+ } else {
+ secs = 240 * 60; /* max 4 hrs */
+ }
+
+ /* EBUSY other bg cmds as of now */
+ cci->bg.runtime = secs * 1000UL;
+ *len_out = 0;
+
+ cxl_dev_disable_media(&ct3d->cxl_dstate);
+
+ if (secs > 2) {
+ /* sanitize when done */
+ return CXL_MBOX_BG_STARTED;
+ } else {
+ __do_sanitization(ct3d);
+ cxl_dev_enable_media(&ct3d->cxl_dstate);
+
+ return CXL_MBOX_SUCCESS;
+ }
+}
+
+static CXLRetCode cmd_get_security_state(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ uint32_t *state = (uint32_t *)payload_out;
+
+ *state = 0;
+ *len_out = 4;
+ return CXL_MBOX_SUCCESS;
+}
/*
* This is very inefficient, but good enough for now!
* Also the payload will always fit, so no need to handle the MORE flag and
#define IMMEDIATE_DATA_CHANGE (1 << 2)
#define IMMEDIATE_POLICY_CHANGE (1 << 3)
#define IMMEDIATE_LOG_CHANGE (1 << 4)
+#define SECURITY_STATE_CHANGE (1 << 5)
+#define BACKGROUND_OPERATION (1 << 6)
static const struct cxl_cmd cxl_cmd_set[256][256] = {
[EVENTS][GET_RECORDS] = { "EVENTS_GET_RECORDS",
[CCLS][GET_LSA] = { "CCLS_GET_LSA", cmd_ccls_get_lsa, 8, 0 },
[CCLS][SET_LSA] = { "CCLS_SET_LSA", cmd_ccls_set_lsa,
~0, IMMEDIATE_CONFIG_CHANGE | IMMEDIATE_DATA_CHANGE },
+ [SANITIZE][OVERWRITE] = { "SANITIZE_OVERWRITE", cmd_sanitize_overwrite, 0,
+ IMMEDIATE_DATA_CHANGE | SECURITY_STATE_CHANGE | BACKGROUND_OPERATION },
+ [PERSISTENT_MEM][GET_SECURITY_STATE] = { "GET_SECURITY_STATE",
+ cmd_get_security_state, 0, 0 },
[MEDIA_AND_POISON][GET_POISON_LIST] = { "MEDIA_AND_POISON_GET_POISON_LIST",
cmd_media_get_poison_list, 16, 0 },
[MEDIA_AND_POISON][INJECT_POISON] = { "MEDIA_AND_POISON_INJECT_POISON",
static const struct cxl_cmd cxl_cmd_set_sw[256][256] = {
[INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0 },
+ [INFOSTAT][BACKGROUND_OPERATION_STATUS] = { "BACKGROUND_OPERATION_STATUS",
+ cmd_infostat_bg_op_sts, 0, 0 },
[TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 },
[TIMESTAMP][SET] = { "TIMESTAMP_SET", cmd_timestamp_set, 0,
IMMEDIATE_POLICY_CHANGE },
cmd_identify_switch_device, 0, 0 },
[PHYSICAL_SWITCH][GET_PHYSICAL_PORT_STATE] = { "SWITCH_PHYSICAL_PORT_STATS",
cmd_get_physical_port_state, ~0, 0 },
+ [TUNNEL][MANAGEMENT_COMMAND] = { "TUNNEL_MANAGEMENT_COMMAND",
+ cmd_tunnel_management_cmd, ~0, 0 },
};
+/*
+ * While the command is executing in the background, the device should
+ * update the percentage complete in the Background Command Status Register
+ * at least once per second.
+ */
+
+#define CXL_MBOX_BG_UPDATE_FREQ 1000UL
+
int cxl_process_cci_message(CXLCCI *cci, uint8_t set, uint8_t cmd,
size_t len_in, uint8_t *pl_in, size_t *len_out,
uint8_t *pl_out, bool *bg_started)
{
+ int ret;
const struct cxl_cmd *cxl_cmd;
opcode_handler h;
return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
}
- return (*h)(cxl_cmd, pl_in, len_in, pl_out, len_out, cci);
+ /* Only one bg command at a time */
+ if ((cxl_cmd->effect & BACKGROUND_OPERATION) &&
+ cci->bg.runtime > 0) {
+ return CXL_MBOX_BUSY;
+ }
+
+ /* forbid any selected commands while overwriting */
+ if (sanitize_running(cci)) {
+ if (h == cmd_events_get_records ||
+ h == cmd_ccls_get_partition_info ||
+ h == cmd_ccls_set_lsa ||
+ h == cmd_ccls_get_lsa ||
+ h == cmd_logs_get_log ||
+ h == cmd_media_get_poison_list ||
+ h == cmd_media_inject_poison ||
+ h == cmd_media_clear_poison ||
+ h == cmd_sanitize_overwrite) {
+ return CXL_MBOX_MEDIA_DISABLED;
+ }
+ }
+
+ ret = (*h)(cxl_cmd, pl_in, len_in, pl_out, len_out, cci);
+ if ((cxl_cmd->effect & BACKGROUND_OPERATION) &&
+ ret == CXL_MBOX_BG_STARTED) {
+ *bg_started = true;
+ } else {
+ *bg_started = false;
+ }
+
+ /* Set bg and the return code */
+ if (*bg_started) {
+ uint64_t now;
+
+ cci->bg.opcode = (set << 8) | cmd;
+
+ cci->bg.complete_pct = 0;
+ cci->bg.ret_code = 0;
+
+ now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ cci->bg.starttime = now;
+ timer_mod(cci->bg.timer, now + CXL_MBOX_BG_UPDATE_FREQ);
+ }
+
+ return ret;
+}
+
+static void bg_timercb(void *opaque)
+{
+ CXLCCI *cci = opaque;
+ uint64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ uint64_t total_time = cci->bg.starttime + cci->bg.runtime;
+
+ assert(cci->bg.runtime > 0);
+
+ if (now >= total_time) { /* we are done */
+ uint16_t ret = CXL_MBOX_SUCCESS;
+
+ cci->bg.complete_pct = 100;
+ cci->bg.ret_code = ret;
+ if (ret == CXL_MBOX_SUCCESS) {
+ switch (cci->bg.opcode) {
+ case 0x4400: /* sanitize */
+ {
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+
+ __do_sanitization(ct3d);
+ cxl_dev_enable_media(&ct3d->cxl_dstate);
+ }
+ break;
+ case 0x4304: /* TODO: scan media */
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ }
+
+ qemu_log("Background command %04xh finished: %s\n",
+ cci->bg.opcode,
+ ret == CXL_MBOX_SUCCESS ? "success" : "aborted");
+ } else {
+ /* estimate only */
+ cci->bg.complete_pct = 100 * now / total_time;
+ timer_mod(cci->bg.timer, now + CXL_MBOX_BG_UPDATE_FREQ);
+ }
+
+ if (cci->bg.complete_pct == 100) {
+ /* TODO: generalize to switch CCI */
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate;
+ PCIDevice *pdev = PCI_DEVICE(cci->d);
+
+ cci->bg.starttime = 0;
+ /* registers are updated, allow new bg-capable cmds */
+ cci->bg.runtime = 0;
+
+ if (msix_enabled(pdev)) {
+ msix_notify(pdev, cxl_dstate->mbox_msi_n);
+ } else if (msi_enabled(pdev)) {
+ msi_notify(pdev, cxl_dstate->mbox_msi_n);
+ }
+ }
}
void cxl_init_cci(CXLCCI *cci, size_t payload_max)
}
}
}
+ cci->bg.complete_pct = 0;
+ cci->bg.starttime = 0;
+ cci->bg.runtime = 0;
+ cci->bg.timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ bg_timercb, cci);
}
void cxl_initialize_mailbox_swcci(CXLCCI *cci, DeviceState *intf,
cci->intf = d;
cxl_init_cci(cci, payload_max);
}
+
+static const struct cxl_cmd cxl_cmd_set_t3_ld[256][256] = {
+ [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0 },
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
+ 0 },
+ [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
+};
+
+void cxl_initialize_t3_ld_cci(CXLCCI *cci, DeviceState *d, DeviceState *intf,
+ size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set_t3_ld;
+ cci->d = d;
+ cci->intf = intf;
+ cxl_init_cci(cci, payload_max);
+}
+
+static const struct cxl_cmd cxl_cmd_set_t3_fm_owned_ld_mctp[256][256] = {
+ [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0},
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
+ 0 },
+ [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
+ [TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 },
+ [TUNNEL][MANAGEMENT_COMMAND] = { "TUNNEL_MANAGEMENT_COMMAND",
+ cmd_tunnel_management_cmd, ~0, 0 },
+};
+
+void cxl_initialize_t3_fm_owned_ld_mctpcci(CXLCCI *cci, DeviceState *d,
+ DeviceState *intf,
+ size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set_t3_fm_owned_ld_mctp;
+ cci->d = d;
+ cci->intf = intf;
+ cxl_init_cci(cci, payload_max);
+}