1 /* 2 * CXL Utility library for mailbox interface 3 * 4 * Copyright(C) 2020 Intel Corporation. 5 * 6 * This work is licensed under the terms of the GNU GPL, version 2. See the 7 * COPYING file in the top-level directory. 8 */ 9 10 #include "qemu/osdep.h" 11 #include "hw/cxl/cxl.h" 12 #include "hw/pci/pci.h" 13 #include "qemu/log.h" 14 #include "qemu/uuid.h" 15 16 /* 17 * How to add a new command, example. The command set FOO, with cmd BAR. 18 * 1. Add the command set and cmd to the enum. 19 * FOO = 0x7f, 20 * #define BAR 0 21 * 2. Implement the handler 22 * static ret_code cmd_foo_bar(struct cxl_cmd *cmd, 23 * CXLDeviceState *cxl_dstate, uint16_t *len) 24 * 3. Add the command to the cxl_cmd_set[][] 25 * [FOO][BAR] = { "FOO_BAR", cmd_foo_bar, x, y }, 26 * 4. Implement your handler 27 * define_mailbox_handler(FOO_BAR) { ... return CXL_MBOX_SUCCESS; } 28 * 29 * 30 * Writing the handler: 31 * The handler will provide the &struct cxl_cmd, the &CXLDeviceState, and the 32 * in/out length of the payload. The handler is responsible for consuming the 33 * payload from cmd->payload and operating upon it as necessary. It must then 34 * fill the output data into cmd->payload (overwriting what was there), 35 * setting the length, and returning a valid return code. 36 * 37 * XXX: The handler need not worry about endianess. The payload is read out of 38 * a register interface that already deals with it. 39 */ 40 41 enum { 42 EVENTS = 0x01, 43 #define GET_RECORDS 0x0 44 #define CLEAR_RECORDS 0x1 45 #define GET_INTERRUPT_POLICY 0x2 46 #define SET_INTERRUPT_POLICY 0x3 47 TIMESTAMP = 0x03, 48 #define GET 0x0 49 #define SET 0x1 50 LOGS = 0x04, 51 #define GET_SUPPORTED 0x0 52 #define GET_LOG 0x1 53 }; 54 55 /* 8.2.8.4.5.1 Command Return Codes */ 56 typedef enum { 57 CXL_MBOX_SUCCESS = 0x0, 58 CXL_MBOX_BG_STARTED = 0x1, 59 CXL_MBOX_INVALID_INPUT = 0x2, 60 CXL_MBOX_UNSUPPORTED = 0x3, 61 CXL_MBOX_INTERNAL_ERROR = 0x4, 62 CXL_MBOX_RETRY_REQUIRED = 0x5, 63 CXL_MBOX_BUSY = 0x6, 64 CXL_MBOX_MEDIA_DISABLED = 0x7, 65 CXL_MBOX_FW_XFER_IN_PROGRESS = 0x8, 66 CXL_MBOX_FW_XFER_OUT_OF_ORDER = 0x9, 67 CXL_MBOX_FW_AUTH_FAILED = 0xa, 68 CXL_MBOX_FW_INVALID_SLOT = 0xb, 69 CXL_MBOX_FW_ROLLEDBACK = 0xc, 70 CXL_MBOX_FW_REST_REQD = 0xd, 71 CXL_MBOX_INVALID_HANDLE = 0xe, 72 CXL_MBOX_INVALID_PA = 0xf, 73 CXL_MBOX_INJECT_POISON_LIMIT = 0x10, 74 CXL_MBOX_PERMANENT_MEDIA_FAILURE = 0x11, 75 CXL_MBOX_ABORTED = 0x12, 76 CXL_MBOX_INVALID_SECURITY_STATE = 0x13, 77 CXL_MBOX_INCORRECT_PASSPHRASE = 0x14, 78 CXL_MBOX_UNSUPPORTED_MAILBOX = 0x15, 79 CXL_MBOX_INVALID_PAYLOAD_LENGTH = 0x16, 80 CXL_MBOX_MAX = 0x17 81 } ret_code; 82 83 struct cxl_cmd; 84 typedef ret_code (*opcode_handler)(struct cxl_cmd *cmd, 85 CXLDeviceState *cxl_dstate, uint16_t *len); 86 struct cxl_cmd { 87 const char *name; 88 opcode_handler handler; 89 ssize_t in; 90 uint16_t effect; /* Reported in CEL */ 91 uint8_t *payload; 92 }; 93 94 #define DEFINE_MAILBOX_HANDLER_ZEROED(name, size) \ 95 uint16_t __zero##name = size; \ 96 static ret_code cmd_##name(struct cxl_cmd *cmd, \ 97 CXLDeviceState *cxl_dstate, uint16_t *len) \ 98 { \ 99 *len = __zero##name; \ 100 memset(cmd->payload, 0, *len); \ 101 return CXL_MBOX_SUCCESS; \ 102 } 103 #define DEFINE_MAILBOX_HANDLER_NOP(name) \ 104 static ret_code cmd_##name(struct cxl_cmd *cmd, \ 105 CXLDeviceState *cxl_dstate, uint16_t *len) \ 106 { \ 107 return CXL_MBOX_SUCCESS; \ 108 } 109 110 DEFINE_MAILBOX_HANDLER_ZEROED(events_get_records, 0x20); 111 DEFINE_MAILBOX_HANDLER_NOP(events_clear_records); 112 DEFINE_MAILBOX_HANDLER_ZEROED(events_get_interrupt_policy, 4); 113 DEFINE_MAILBOX_HANDLER_NOP(events_set_interrupt_policy); 114 115 /* 8.2.9.3.1 */ 116 static ret_code cmd_timestamp_get(struct cxl_cmd *cmd, 117 CXLDeviceState *cxl_dstate, 118 uint16_t *len) 119 { 120 uint64_t time, delta; 121 uint64_t final_time = 0; 122 123 if (cxl_dstate->timestamp.set) { 124 /* First find the delta from the last time the host set the time. */ 125 time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 126 delta = time - cxl_dstate->timestamp.last_set; 127 final_time = cxl_dstate->timestamp.host_set + delta; 128 } 129 130 /* Then adjust the actual time */ 131 stq_le_p(cmd->payload, final_time); 132 *len = 8; 133 134 return CXL_MBOX_SUCCESS; 135 } 136 137 /* 8.2.9.3.2 */ 138 static ret_code cmd_timestamp_set(struct cxl_cmd *cmd, 139 CXLDeviceState *cxl_dstate, 140 uint16_t *len) 141 { 142 cxl_dstate->timestamp.set = true; 143 cxl_dstate->timestamp.last_set = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 144 145 cxl_dstate->timestamp.host_set = le64_to_cpu(*(uint64_t *)cmd->payload); 146 147 *len = 0; 148 return CXL_MBOX_SUCCESS; 149 } 150 151 static QemuUUID cel_uuid; 152 153 /* 8.2.9.4.1 */ 154 static ret_code cmd_logs_get_supported(struct cxl_cmd *cmd, 155 CXLDeviceState *cxl_dstate, 156 uint16_t *len) 157 { 158 struct { 159 uint16_t entries; 160 uint8_t rsvd[6]; 161 struct { 162 QemuUUID uuid; 163 uint32_t size; 164 } log_entries[1]; 165 } QEMU_PACKED *supported_logs = (void *)cmd->payload; 166 QEMU_BUILD_BUG_ON(sizeof(*supported_logs) != 0x1c); 167 168 supported_logs->entries = 1; 169 supported_logs->log_entries[0].uuid = cel_uuid; 170 supported_logs->log_entries[0].size = 4 * cxl_dstate->cel_size; 171 172 *len = sizeof(*supported_logs); 173 return CXL_MBOX_SUCCESS; 174 } 175 176 /* 8.2.9.4.2 */ 177 static ret_code cmd_logs_get_log(struct cxl_cmd *cmd, 178 CXLDeviceState *cxl_dstate, 179 uint16_t *len) 180 { 181 struct { 182 QemuUUID uuid; 183 uint32_t offset; 184 uint32_t length; 185 } QEMU_PACKED QEMU_ALIGNED(16) *get_log = (void *)cmd->payload; 186 187 /* 188 * 8.2.9.4.2 189 * The device shall return Invalid Parameter if the Offset or Length 190 * fields attempt to access beyond the size of the log as reported by Get 191 * Supported Logs. 192 * 193 * XXX: Spec is wrong, "Invalid Parameter" isn't a thing. 194 * XXX: Spec doesn't address incorrect UUID incorrectness. 195 * 196 * The CEL buffer is large enough to fit all commands in the emulation, so 197 * the only possible failure would be if the mailbox itself isn't big 198 * enough. 199 */ 200 if (get_log->offset + get_log->length > cxl_dstate->payload_size) { 201 return CXL_MBOX_INVALID_INPUT; 202 } 203 204 if (!qemu_uuid_is_equal(&get_log->uuid, &cel_uuid)) { 205 return CXL_MBOX_UNSUPPORTED; 206 } 207 208 /* Store off everything to local variables so we can wipe out the payload */ 209 *len = get_log->length; 210 211 memmove(cmd->payload, cxl_dstate->cel_log + get_log->offset, 212 get_log->length); 213 214 return CXL_MBOX_SUCCESS; 215 } 216 217 #define IMMEDIATE_CONFIG_CHANGE (1 << 1) 218 #define IMMEDIATE_POLICY_CHANGE (1 << 3) 219 #define IMMEDIATE_LOG_CHANGE (1 << 4) 220 221 static struct cxl_cmd cxl_cmd_set[256][256] = { 222 [EVENTS][GET_RECORDS] = { "EVENTS_GET_RECORDS", 223 cmd_events_get_records, 1, 0 }, 224 [EVENTS][CLEAR_RECORDS] = { "EVENTS_CLEAR_RECORDS", 225 cmd_events_clear_records, ~0, IMMEDIATE_LOG_CHANGE }, 226 [EVENTS][GET_INTERRUPT_POLICY] = { "EVENTS_GET_INTERRUPT_POLICY", 227 cmd_events_get_interrupt_policy, 0, 0 }, 228 [EVENTS][SET_INTERRUPT_POLICY] = { "EVENTS_SET_INTERRUPT_POLICY", 229 cmd_events_set_interrupt_policy, 4, IMMEDIATE_CONFIG_CHANGE }, 230 [TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 }, 231 [TIMESTAMP][SET] = { "TIMESTAMP_SET", cmd_timestamp_set, 8, IMMEDIATE_POLICY_CHANGE }, 232 [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0, 0 }, 233 [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 }, 234 }; 235 236 void cxl_process_mailbox(CXLDeviceState *cxl_dstate) 237 { 238 uint16_t ret = CXL_MBOX_SUCCESS; 239 struct cxl_cmd *cxl_cmd; 240 uint64_t status_reg; 241 opcode_handler h; 242 uint64_t command_reg = cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD]; 243 244 uint8_t set = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND_SET); 245 uint8_t cmd = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND); 246 uint16_t len = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH); 247 cxl_cmd = &cxl_cmd_set[set][cmd]; 248 h = cxl_cmd->handler; 249 if (h) { 250 if (len == cxl_cmd->in) { 251 cxl_cmd->payload = cxl_dstate->mbox_reg_state + 252 A_CXL_DEV_CMD_PAYLOAD; 253 ret = (*h)(cxl_cmd, cxl_dstate, &len); 254 assert(len <= cxl_dstate->payload_size); 255 } else { 256 ret = CXL_MBOX_INVALID_PAYLOAD_LENGTH; 257 } 258 } else { 259 qemu_log_mask(LOG_UNIMP, "Command %04xh not implemented\n", 260 set << 8 | cmd); 261 ret = CXL_MBOX_UNSUPPORTED; 262 } 263 264 /* Set the return code */ 265 status_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_STS, ERRNO, ret); 266 267 /* Set the return length */ 268 command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND_SET, 0); 269 command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND, 0); 270 command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH, len); 271 272 cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD] = command_reg; 273 cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_STS] = status_reg; 274 275 /* Tell the host we're done */ 276 ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL, 277 DOORBELL, 0); 278 } 279 280 int cxl_initialize_mailbox(CXLDeviceState *cxl_dstate) 281 { 282 /* CXL 2.0: Table 169 Get Supported Logs Log Entry */ 283 const char *cel_uuidstr = "0da9c0b5-bf41-4b78-8f79-96b1623b3f17"; 284 285 for (int set = 0; set < 256; set++) { 286 for (int cmd = 0; cmd < 256; cmd++) { 287 if (cxl_cmd_set[set][cmd].handler) { 288 struct cxl_cmd *c = &cxl_cmd_set[set][cmd]; 289 struct cel_log *log = 290 &cxl_dstate->cel_log[cxl_dstate->cel_size]; 291 292 log->opcode = (set << 8) | cmd; 293 log->effect = c->effect; 294 cxl_dstate->cel_size++; 295 } 296 } 297 } 298 299 return qemu_uuid_parse(cel_uuidstr, &cel_uuid); 300 } 301