From: Wilfred Mallawa <wilfred.mall...@wdc.com> Adds the NVMe Admin Security Send/Receive command support with support for DMTFs SPDM. The transport binding for SPDM is defined in the DMTF DSP0286.
Signed-off-by: Wilfred Mallawa <wilfred.mall...@wdc.com> Reviewed-by: Stefan Hajnoczi <stefa...@redhat.com> Reviewed-by: Jonathan Cameron <jonathan.came...@huawei.com> --- hw/nvme/ctrl.c | 212 ++++++++++++++++++++++++++++++++++- hw/nvme/nvme.h | 5 + include/block/nvme.h | 15 +++ include/system/spdm-socket.h | 2 + 4 files changed, 233 insertions(+), 1 deletion(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index f5ee6bf260..ad52e8f569 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -282,6 +282,8 @@ static const uint32_t nvme_cse_acs_default[256] = { [NVME_ADM_CMD_FORMAT_NVM] = NVME_CMD_EFF_CSUPP | NVME_CMD_EFF_LBCC, [NVME_ADM_CMD_DIRECTIVE_RECV] = NVME_CMD_EFF_CSUPP, [NVME_ADM_CMD_DIRECTIVE_SEND] = NVME_CMD_EFF_CSUPP, + [NVME_ADM_CMD_SECURITY_SEND] = NVME_CMD_EFF_CSUPP, + [NVME_ADM_CMD_SECURITY_RECV] = NVME_CMD_EFF_CSUPP, }; static const uint32_t nvme_cse_iocs_nvm_default[256] = { @@ -7282,6 +7284,207 @@ static uint16_t nvme_dbbuf_config(NvmeCtrl *n, const NvmeRequest *req) return NVME_SUCCESS; } +static uint16_t nvme_sec_prot_spdm_send(NvmeCtrl *n, NvmeRequest *req) +{ + StorageSpdmTransportHeader hdr = {0}; + g_autofree uint8_t *sec_buf = NULL; + uint32_t transfer_len = le32_to_cpu(req->cmd.cdw11); + uint32_t transport_transfer_len = transfer_len; + uint32_t dw10 = le32_to_cpu(req->cmd.cdw10); + uint32_t recvd; + uint16_t nvme_cmd_status, ret; + uint8_t secp = extract32(dw10, 24, 8); + uint16_t spsp = extract32(dw10, 8, 16); + bool spdm_res; + + if (transport_transfer_len > UINT32_MAX - sizeof(hdr)) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + transport_transfer_len += sizeof(hdr); + if (transport_transfer_len > SPDM_SOCKET_MAX_MESSAGE_BUFFER_SIZE) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + ret = nvme_check_mdts(n, transport_transfer_len); + if (ret != NVME_SUCCESS) { + return ret; + } + + /* Generate the NVMe transport header */ + hdr.security_protocol = secp; + hdr.security_protocol_specific = cpu_to_le16(spsp); + hdr.length = cpu_to_le32(transfer_len); + + sec_buf = g_try_malloc0(transport_transfer_len); + if (!sec_buf) { + return NVME_INTERNAL_DEV_ERROR; + } + + /* Attach the transport header */ + memcpy(sec_buf, &hdr, sizeof(hdr)); + ret = nvme_h2c(n, sec_buf + sizeof(hdr), transfer_len, req); + if (ret) { + return ret; + } + + spdm_res = spdm_socket_send(n->spdm_socket, SPDM_SOCKET_STORAGE_CMD_IF_SEND, + SPDM_SOCKET_TRANSPORT_TYPE_NVME, sec_buf, + transport_transfer_len); + if (!spdm_res) { + return NVME_DATA_TRAS_ERROR | NVME_DNR; + } + + /* The responder shall ack with message status */ + recvd = spdm_socket_receive(n->spdm_socket, SPDM_SOCKET_TRANSPORT_TYPE_NVME, + &nvme_cmd_status, + SPDM_SOCKET_MAX_MSG_STATUS_LEN); + + nvme_cmd_status = be16_to_cpu(nvme_cmd_status); + + if (recvd < SPDM_SOCKET_MAX_MSG_STATUS_LEN) { + return NVME_DATA_TRAS_ERROR | NVME_DNR; + } + + return nvme_cmd_status; +} + +/* From host to controller */ +static uint16_t nvme_security_send(NvmeCtrl *n, NvmeRequest *req) +{ + uint32_t dw10 = le32_to_cpu(req->cmd.cdw10); + uint8_t secp = extract32(dw10, 24, 8); + + switch (secp) { + case NVME_SEC_PROT_DMTF_SPDM: + if (n->spdm_socket < 0) { + return NVME_INVALID_FIELD | NVME_DNR; + } + return nvme_sec_prot_spdm_send(n, req); + default: + /* Unsupported Security Protocol Type */ + return NVME_INVALID_FIELD | NVME_DNR; + } + + return NVME_INVALID_FIELD | NVME_DNR; +} + +static uint16_t nvme_sec_prot_spdm_receive(NvmeCtrl *n, NvmeRequest *req) +{ + StorageSpdmTransportHeader hdr; + g_autofree uint8_t *rsp_spdm_buf = NULL; + uint32_t dw10 = le32_to_cpu(req->cmd.cdw10); + uint32_t alloc_len = le32_to_cpu(req->cmd.cdw11); + uint32_t recvd, spdm_res; + uint16_t nvme_cmd_status, ret; + uint8_t secp = extract32(dw10, 24, 8); + uint8_t spsp = extract32(dw10, 8, 16); + if (!alloc_len) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + /* Generate the NVMe transport header */ + hdr = (StorageSpdmTransportHeader) { + .security_protocol = secp, + .security_protocol_specific = cpu_to_le16(spsp), + .length = cpu_to_le32(alloc_len), + }; + + /* Forward if_recv to the SPDM Server with SPSP0 */ + spdm_res = spdm_socket_send(n->spdm_socket, SPDM_SOCKET_STORAGE_CMD_IF_RECV, + SPDM_SOCKET_TRANSPORT_TYPE_NVME, + &hdr, sizeof(hdr)); + if (!spdm_res) { + return NVME_DATA_TRAS_ERROR | NVME_DNR; + } + + /* The responder shall ack with message status */ + recvd = spdm_socket_receive(n->spdm_socket, SPDM_SOCKET_TRANSPORT_TYPE_NVME, + &nvme_cmd_status, + SPDM_SOCKET_MAX_MSG_STATUS_LEN); + if (recvd < SPDM_SOCKET_MAX_MSG_STATUS_LEN) { + return NVME_DATA_TRAS_ERROR | NVME_DNR; + } + + nvme_cmd_status = be16_to_cpu(nvme_cmd_status); + /* An error here implies the prior if_recv from requester was spurious */ + if (nvme_cmd_status != NVME_SUCCESS) { + return nvme_cmd_status; + } + + /* Clear to start receiving data from the server */ + rsp_spdm_buf = g_try_malloc0(alloc_len); + if (!rsp_spdm_buf) { + return NVME_INTERNAL_DEV_ERROR; + } + + recvd = spdm_socket_receive(n->spdm_socket, + SPDM_SOCKET_TRANSPORT_TYPE_NVME, + rsp_spdm_buf, alloc_len); + if (!recvd) { + return NVME_DATA_TRAS_ERROR | NVME_DNR; + } + + ret = nvme_c2h(n, rsp_spdm_buf, MIN(recvd, alloc_len), req); + if (ret) { + return ret; + } + + return NVME_SUCCESS; +} + +static uint16_t nvme_get_sec_prot_info(NvmeCtrl *n, NvmeRequest *req) +{ + uint32_t alloc_len = le32_to_cpu(req->cmd.cdw11); + uint8_t resp[10] = { + /* Support Security Protol List Length */ + [6] = 0, /* MSB */ + [7] = 2, /* LSB */ + /* Support Security Protocol List */ + [8] = SFSC_SECURITY_PROT_INFO, + [9] = 0, + }; + + if (n->spdm_socket >= 0) { + resp[9] = NVME_SEC_PROT_DMTF_SPDM; + } + + if (alloc_len < 10) { + return NVME_INVALID_FIELD | NVME_DNR; + } + + return nvme_c2h(n, resp, sizeof(resp), req); +} + +/* From controller to host */ +static uint16_t nvme_security_receive(NvmeCtrl *n, NvmeRequest *req) +{ + uint32_t dw10 = le32_to_cpu(req->cmd.cdw10); + uint16_t spsp = extract32(dw10, 8, 16); + uint8_t secp = extract32(dw10, 24, 8); + + switch (secp) { + case SFSC_SECURITY_PROT_INFO: + switch (spsp) { + case 0: + /* Supported security protocol list */ + return nvme_get_sec_prot_info(n, req); + case 1: + /* Certificate data */ + /* fallthrough */ + default: + return NVME_INVALID_FIELD | NVME_DNR; + } + case NVME_SEC_PROT_DMTF_SPDM: + if (n->spdm_socket < 0) { + return NVME_INVALID_FIELD | NVME_DNR; + } + return nvme_sec_prot_spdm_receive(n, req); + default: + return NVME_INVALID_FIELD | NVME_DNR; + } +} + static uint16_t nvme_directive_send(NvmeCtrl *n, NvmeRequest *req) { return NVME_INVALID_FIELD | NVME_DNR; @@ -7389,6 +7592,10 @@ static uint16_t nvme_admin_cmd(NvmeCtrl *n, NvmeRequest *req) return nvme_directive_send(n, req); case NVME_ADM_CMD_DIRECTIVE_RECV: return nvme_directive_receive(n, req); + case NVME_ADM_CMD_SECURITY_SEND: + return nvme_security_send(n, req); + case NVME_ADM_CMD_SECURITY_RECV: + return nvme_security_receive(n, req); default: g_assert_not_reached(); } @@ -8459,6 +8666,8 @@ static void nvme_init_state(NvmeCtrl *n) sctrl->vfn = cpu_to_le16(i + 1); } + n->spdm_socket = -1; + cap->cntlid = cpu_to_le16(n->cntlid); cap->crt = NVME_CRT_VQ | NVME_CRT_VI; @@ -8824,7 +9033,8 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) id->mdts = n->params.mdts; id->ver = cpu_to_le32(NVME_SPEC_VER); - oacs = NVME_OACS_NMS | NVME_OACS_FORMAT | NVME_OACS_DIRECTIVES; + oacs = NVME_OACS_NMS | NVME_OACS_FORMAT | NVME_OACS_DIRECTIVES | + NVME_OACS_SECURITY; if (n->params.dbcs) { oacs |= NVME_OACS_DBCS; diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h index b5c9378ea4..67ed562e00 100644 --- a/hw/nvme/nvme.h +++ b/hw/nvme/nvme.h @@ -461,6 +461,8 @@ static inline const char *nvme_adm_opc_str(uint8_t opc) case NVME_ADM_CMD_DIRECTIVE_RECV: return "NVME_ADM_CMD_DIRECTIVE_RECV"; case NVME_ADM_CMD_DBBUF_CONFIG: return "NVME_ADM_CMD_DBBUF_CONFIG"; case NVME_ADM_CMD_FORMAT_NVM: return "NVME_ADM_CMD_FORMAT_NVM"; + case NVME_ADM_CMD_SECURITY_SEND: return "NVME_ADM_CMD_SECURITY_SEND"; + case NVME_ADM_CMD_SECURITY_RECV: return "NVME_ADM_CMD_SECURITY_RECV"; default: return "NVME_ADM_CMD_UNKNOWN"; } } @@ -648,6 +650,9 @@ typedef struct NvmeCtrl { } next_pri_ctrl_cap; /* These override pri_ctrl_cap after reset */ uint32_t dn; /* Disable Normal */ NvmeAtomic atomic; + + /* Socket mapping to SPDM over NVMe Security In/Out commands */ + int spdm_socket; } NvmeCtrl; typedef enum NvmeResetType { diff --git a/include/block/nvme.h b/include/block/nvme.h index 358e516e38..9fa2ecaf28 100644 --- a/include/block/nvme.h +++ b/include/block/nvme.h @@ -1779,6 +1779,21 @@ enum NvmeDirectiveOperations { NVME_DIRECTIVE_RETURN_PARAMS = 0x1, }; +typedef enum SfscSecurityProtocol { + SFSC_SECURITY_PROT_INFO = 0x00, +} SfscSecurityProtocol; + +typedef enum NvmeSecurityProtocols { + NVME_SEC_PROT_DMTF_SPDM = 0xE8, +} NvmeSecurityProtocols; + +typedef enum SpdmOperationCodes { + SPDM_STORAGE_DISCOVERY = 0x1, /* Mandatory */ + SPDM_STORAGE_PENDING_INFO = 0x2, /* Optional */ + SPDM_STORAGE_MSG = 0x5, /* Mandatory */ + SPDM_STORAGE_SEC_MSG = 0x6, /* Optional */ +} SpdmOperationCodes; + typedef struct QEMU_PACKED NvmeFdpConfsHdr { uint16_t num_confs; uint8_t version; diff --git a/include/system/spdm-socket.h b/include/system/spdm-socket.h index 80cd7021c1..f0c8b64d1b 100644 --- a/include/system/spdm-socket.h +++ b/include/system/spdm-socket.h @@ -112,7 +112,9 @@ typedef struct { #define SPDM_SOCKET_TRANSPORT_TYPE_MCTP 0x01 #define SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE 0x02 +#define SPDM_SOCKET_TRANSPORT_TYPE_NVME 0x04 #define SPDM_SOCKET_MAX_MESSAGE_BUFFER_SIZE 0x1200 +#define SPDM_SOCKET_MAX_MSG_STATUS_LEN 0x02 #endif -- 2.51.0