hw/nvme: add NVMe Admin Security SPDM support

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.mallawa@wdc.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Reviewed-by: Klaus Jensen <k.jensen@samsung.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Signed-off-by: Klaus Jensen <k.jensen@samsung.com>
This commit is contained in:
Wilfred Mallawa
2025-10-30 07:07:14 +01:00
committed by Klaus Jensen
parent 64a9336a41
commit e5534abeb4
4 changed files with 233 additions and 1 deletions
+211 -1
View File
@@ -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;
@@ -8820,7 +9029,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;
+5
View File
@@ -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 {
+15
View File
@@ -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;
+2
View File
@@ -114,7 +114,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