nvme queue

-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEUigzqnXi3OaiR2bATeGvMW1PDekFAmkDE7gACgkQTeGvMW1P
 DekCOwgAuOQKWWW/UA1MmZ4ZHs+djf4q5UDwqGDx8tra8d32mZWRHgpJ/OBBOY2z
 CmuHqWLgooAqfx4hsrXELdNBEe7ccNE9nvsE3GjnYWxjoe51yl2Xc0RD5CZBVrN4
 RRMbBZRCewxGShyUaT31eedolWdr4zBuqkpLf9gcG8Yk7YD+xUkHUPeMXeAy+vkS
 pxW59AkXdjJZgBktOdV5uVj9gaCPgTcGaQNH2FYSnzHwdu5VyV8BKiiZE/fXS6FU
 xZvu+5p1Ro5vOdwG+iFBrbBwcGyjVOF1OfBZctyc83foyFxwzxqoqj9gy0ewuT2g
 HsupUiJgbkZ1Ut9fzaS5pHx3dd3dKw==
 =WDrH
 -----END PGP SIGNATURE-----

Merge tag 'pull-nvme-20251030' of https://gitlab.com/birkelund/qemu into staging

nvme queue

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCgAdFiEEUigzqnXi3OaiR2bATeGvMW1PDekFAmkDE7gACgkQTeGvMW1P
# DekCOwgAuOQKWWW/UA1MmZ4ZHs+djf4q5UDwqGDx8tra8d32mZWRHgpJ/OBBOY2z
# CmuHqWLgooAqfx4hsrXELdNBEe7ccNE9nvsE3GjnYWxjoe51yl2Xc0RD5CZBVrN4
# RRMbBZRCewxGShyUaT31eedolWdr4zBuqkpLf9gcG8Yk7YD+xUkHUPeMXeAy+vkS
# pxW59AkXdjJZgBktOdV5uVj9gaCPgTcGaQNH2FYSnzHwdu5VyV8BKiiZE/fXS6FU
# xZvu+5p1Ro5vOdwG+iFBrbBwcGyjVOF1OfBZctyc83foyFxwzxqoqj9gy0ewuT2g
# HsupUiJgbkZ1Ut9fzaS5pHx3dd3dKw==
# =WDrH
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 30 Oct 2025 08:28:56 AM CET
# gpg:                using RSA key 522833AA75E2DCE6A24766C04DE1AF316D4F0DE9
# gpg: Good signature from "Klaus Jensen <its@irrelevant.dk>" [unknown]
# gpg:                 aka "Klaus Jensen <k.jensen@samsung.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: DDCA 4D9C 9EF9 31CC 3468  4272 63D5 6FC5 E55D A838
#      Subkey fingerprint: 5228 33AA 75E2 DCE6 A247  66C0 4DE1 AF31 6D4F 0DE9

* tag 'pull-nvme-20251030' of https://gitlab.com/birkelund/qemu:
  hw/nvme: add atomic boundary support
  hw/nvme: enable ns atomic writes
  hw/nvme: connect SPDM over NVMe Security Send/Recv
  spdm: define SPDM transport enum types
  hw/nvme: add NVMe Admin Security SPDM support
  spdm: add spdm storage transport virtual header
  spdm-socket: add seperate send/recv functions

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2025-10-31 12:56:05 +01:00
commit c494afbb7d
8 changed files with 571 additions and 30 deletions

View file

@ -13,6 +13,9 @@
#include "qemu/osdep.h"
#include "system/spdm-socket.h"
#include "qapi/error.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/core/qdev-prop-internal.h"
static bool read_bytes(const int socket, uint8_t *buffer,
size_t number_of_bytes)
@ -184,29 +187,61 @@ int spdm_socket_connect(uint16_t port, Error **errp)
return client_socket;
}
uint32_t spdm_socket_rsp(const int socket, uint32_t transport_type,
void *req, uint32_t req_len,
void *rsp, uint32_t rsp_len)
static bool spdm_socket_command_valid(uint32_t command)
{
switch (command) {
case SPDM_SOCKET_COMMAND_NORMAL:
case SPDM_SOCKET_STORAGE_CMD_IF_SEND:
case SPDM_SOCKET_STORAGE_CMD_IF_RECV:
case SOCKET_SPDM_STORAGE_ACK_STATUS:
case SPDM_SOCKET_COMMAND_OOB_ENCAP_KEY_UPDATE:
case SPDM_SOCKET_COMMAND_CONTINUE:
case SPDM_SOCKET_COMMAND_SHUTDOWN:
case SPDM_SOCKET_COMMAND_UNKOWN:
case SPDM_SOCKET_COMMAND_TEST:
return true;
default:
return false;
}
}
uint32_t spdm_socket_receive(const int socket, uint32_t transport_type,
void *rsp, uint32_t rsp_len)
{
uint32_t command;
bool result;
result = send_platform_data(socket, transport_type,
SPDM_SOCKET_COMMAND_NORMAL,
req, req_len);
if (!result) {
return 0;
}
result = receive_platform_data(socket, transport_type, &command,
(uint8_t *)rsp, &rsp_len);
/* we may have received some data, but check if the command is valid */
if (!result || !spdm_socket_command_valid(command)) {
return 0;
}
return rsp_len;
}
bool spdm_socket_send(const int socket, uint32_t socket_cmd,
uint32_t transport_type, void *req, uint32_t req_len)
{
return send_platform_data(socket, transport_type, socket_cmd, req,
req_len);
}
uint32_t spdm_socket_rsp(const int socket, uint32_t transport_type,
void *req, uint32_t req_len,
void *rsp, uint32_t rsp_len)
{
bool result;
result = spdm_socket_send(socket, SPDM_SOCKET_COMMAND_NORMAL,
transport_type, req, req_len);
if (!result) {
return 0;
}
assert(command != 0);
return rsp_len;
return spdm_socket_receive(socket, transport_type, rsp, rsp_len);
}
void spdm_socket_close(const int socket, uint32_t transport_type)
@ -214,3 +249,23 @@ void spdm_socket_close(const int socket, uint32_t transport_type)
send_platform_data(socket, transport_type,
SPDM_SOCKET_COMMAND_SHUTDOWN, NULL, 0);
}
const QEnumLookup SpdmTransport_lookup = {
.array = (const char *const[]) {
[SPDM_SOCKET_TRANSPORT_TYPE_UNSPEC] = "unspecified",
[SPDM_SOCKET_TRANSPORT_TYPE_MCTP] = "mctp",
[SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE] = "doe",
[SPDM_SOCKET_TRANSPORT_TYPE_SCSI] = "scsi",
[SPDM_SOCKET_TRANSPORT_TYPE_NVME] = "nvme",
},
.size = SPDM_SOCKET_TRANSPORT_TYPE_MAX
};
const PropertyInfo qdev_prop_spdm_trans = {
.type = "SpdmTransportType",
.description = "Spdm Transport, doe/nvme/mctp/scsi/unspecified",
.enum_table = &SpdmTransport_lookup,
.get = qdev_propinfo_get_enum,
.set = qdev_propinfo_set_enum,
.set_default_value = qdev_propinfo_set_default_value_enum,
};

View file

@ -98,7 +98,7 @@ Then you can add this to your QEMU command line:
.. code-block:: shell
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe
At which point QEMU will try to connect to the SPDM server.
@ -113,7 +113,13 @@ of the default. So the entire QEMU command might look like this
-append "root=/dev/vda console=ttyS0" \
-net none -nographic \
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe
The ``spdm_trans`` argument defines the underlying transport type that is
emulated by QEMU. For an PCIe NVMe controller, both "doe" and "nvme" are
supported. Where, "doe" does SPDM transport over the PCIe extended capability
Data Object Exchange (DOE), and "nvme" uses the NVMe Admin Security
Send/Receive commands to implement the SPDM transport.
.. _DMTF:
https://www.dmtf.org/standards/SPDM

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] = {
@ -6703,6 +6705,35 @@ static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeRequest *req)
} else {
atomic->atomic_writes = 1;
}
for (i = 1; i <= NVME_MAX_NAMESPACES; i++) {
ns = nvme_ns(n, i);
if (ns && ns->atomic.atomic_writes) {
if (n->dn) {
ns->atomic.atomic_max_write_size =
le16_to_cpu(ns->id_ns.nawupf) + 1;
if (ns->id_ns.nabspf) {
ns->atomic.atomic_boundary =
le16_to_cpu(ns->id_ns.nabspf) + 1;
} else {
ns->atomic.atomic_boundary = 0;
}
} else {
ns->atomic.atomic_max_write_size =
le16_to_cpu(ns->id_ns.nawun) + 1;
if (ns->id_ns.nabsn) {
ns->atomic.atomic_boundary =
le16_to_cpu(ns->id_ns.nabsn) + 1;
} else {
ns->atomic.atomic_boundary = 0;
}
}
if (ns->atomic.atomic_max_write_size == 1) {
ns->atomic.atomic_writes = 0;
} else {
ns->atomic.atomic_writes = 1;
}
}
}
break;
default:
return NVME_FEAT_NOT_CHANGEABLE | NVME_DNR;
@ -7282,6 +7313,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 +7621,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();
}
@ -7412,6 +7648,36 @@ static void nvme_update_sq_tail(NvmeSQueue *sq)
trace_pci_nvme_update_sq_tail(sq->sqid, sq->tail);
}
static int nvme_atomic_boundary_check(NvmeCtrl *n, NvmeCmd *cmd,
NvmeAtomic *atomic)
{
NvmeRwCmd *rw = (NvmeRwCmd *)cmd;
if (atomic->atomic_boundary) {
uint64_t slba = le64_to_cpu(rw->slba);
uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb);
uint64_t elba = slba + nlb;
uint64_t imask;
if ((slba < atomic->atomic_nabo) || (elba < atomic->atomic_nabo)) {
return 0;
}
/* Update slba/elba based on boundary offset */
slba = slba - atomic->atomic_nabo;
elba = slba + nlb;
imask = ~(atomic->atomic_boundary - 1);
if ((slba & imask) != (elba & imask)) {
if (n->atomic.atomic_max_write_size &&
((nlb + 1) <= n->atomic.atomic_max_write_size)) {
return 1;
}
return 0;
}
}
return 1;
}
#define NVME_ATOMIC_NO_START 0
#define NVME_ATOMIC_START_ATOMIC 1
#define NVME_ATOMIC_START_NONATOMIC 2
@ -7431,6 +7697,15 @@ static int nvme_atomic_write_check(NvmeCtrl *n, NvmeCmd *cmd,
cmd_atomic_wr = false;
}
/*
* Check if a write crosses an atomic boundary.
*/
if (cmd->opcode == NVME_CMD_WRITE) {
if (!nvme_atomic_boundary_check(n, cmd, atomic)) {
cmd_atomic_wr = false;
}
}
/*
* Walk the queues to see if there are any atomic conflicts.
*/
@ -7481,6 +7756,12 @@ static int nvme_atomic_write_check(NvmeCtrl *n, NvmeCmd *cmd,
static NvmeAtomic *nvme_get_atomic(NvmeCtrl *n, NvmeCmd *cmd)
{
NvmeNamespace *ns = nvme_ns(n, cmd->nsid);
if (ns && ns->atomic.atomic_writes) {
return &ns->atomic;
}
if (n->atomic.atomic_writes) {
return &n->atomic;
}
@ -8459,6 +8740,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;
@ -8509,6 +8792,8 @@ static void nvme_init_state(NvmeCtrl *n)
} else {
atomic->atomic_writes = 1;
}
atomic->atomic_boundary = 0;
atomic->atomic_nabo = 0;
}
}
@ -8734,19 +9019,33 @@ static bool nvme_init_pci(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp)
pcie_cap_deverr_init(pci_dev);
/* DOE Initialisation */
/* SPDM Initialisation */
if (pci_dev->spdm_port) {
uint16_t doe_offset = n->params.sriov_max_vfs ?
PCI_CONFIG_SPACE_SIZE + PCI_ARI_SIZEOF
: PCI_CONFIG_SPACE_SIZE;
uint16_t doe_offset = PCI_CONFIG_SPACE_SIZE;
pcie_doe_init(pci_dev, &pci_dev->doe_spdm, doe_offset,
doe_spdm_prot, true, 0);
switch (pci_dev->spdm_trans) {
case SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE:
if (n->params.sriov_max_vfs) {
doe_offset += PCI_ARI_SIZEOF;
}
pci_dev->doe_spdm.spdm_socket = spdm_socket_connect(pci_dev->spdm_port,
errp);
pcie_doe_init(pci_dev, &pci_dev->doe_spdm, doe_offset,
doe_spdm_prot, true, 0);
if (pci_dev->doe_spdm.spdm_socket < 0) {
pci_dev->doe_spdm.spdm_socket =
spdm_socket_connect(pci_dev->spdm_port, errp);
if (pci_dev->doe_spdm.spdm_socket < 0) {
return false;
}
break;
case SPDM_SOCKET_TRANSPORT_TYPE_NVME:
n->spdm_socket = spdm_socket_connect(pci_dev->spdm_port, errp);
if (n->spdm_socket < 0) {
return false;
}
break;
default:
return false;
}
}
@ -8820,7 +9119,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;
@ -9036,9 +9336,14 @@ static void nvme_exit(PCIDevice *pci_dev)
g_free(n->cmb.buf);
}
/* Only one of the `spdm_socket`s below should have been setup */
assert(!(pci_dev->doe_spdm.spdm_socket > 0 && n->spdm_socket >= 0));
if (pci_dev->doe_spdm.spdm_socket > 0) {
spdm_socket_close(pci_dev->doe_spdm.spdm_socket,
SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE);
} else if (n->spdm_socket >= 0) {
spdm_socket_close(pci_dev->doe_spdm.spdm_socket,
SPDM_SOCKET_TRANSPORT_TYPE_NVME);
}
if (n->pmr.dev) {
@ -9093,6 +9398,8 @@ static const Property nvme_props[] = {
false),
DEFINE_PROP_UINT16("mqes", NvmeCtrl, params.mqes, 0x7ff),
DEFINE_PROP_UINT16("spdm_port", PCIDevice, spdm_port, 0),
DEFINE_PROP_SPDM_TRANS("spdm_trans", PCIDevice, spdm_trans,
SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE),
DEFINE_PROP_BOOL("ctratt.mem", NvmeCtrl, params.ctratt.mem, false),
DEFINE_PROP_BOOL("atomic.dn", NvmeCtrl, params.atomic_dn, 0),
DEFINE_PROP_UINT16("atomic.awun", NvmeCtrl, params.atomic_awun, 0),
@ -9168,7 +9475,9 @@ static void nvme_pci_write_config(PCIDevice *dev, uint32_t address,
{
uint16_t old_num_vfs = pcie_sriov_num_vfs(dev);
if (pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE)) {
/* DOE is only initialised if SPDM over DOE is used */
if (pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE) &&
dev->spdm_trans == SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE) {
pcie_doe_write_config(&dev->doe_spdm, address, val, len);
}
pci_default_write_config(dev, address, val, len);
@ -9179,7 +9488,9 @@ static void nvme_pci_write_config(PCIDevice *dev, uint32_t address,
static uint32_t nvme_pci_read_config(PCIDevice *dev, uint32_t address, int len)
{
uint32_t val;
if (dev->spdm_port && pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE)) {
if (dev->spdm_port && pcie_find_capability(dev, PCI_EXT_CAP_ID_DOE) &&
(dev->spdm_trans == SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE)) {
if (pcie_doe_read_config(&dev->doe_spdm, address, len, &val)) {
return val;
}

View file

@ -724,11 +724,79 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp)
BusState *s = qdev_get_parent_bus(dev);
NvmeCtrl *n = NVME(s->parent);
NvmeSubsystem *subsys = n->subsys;
NvmeIdCtrl *id = &n->id_ctrl;
NvmeIdNs *id_ns = &ns->id_ns;
uint32_t nsid = ns->params.nsid;
int i;
assert(subsys);
/* Set atomic write parameters */
if (ns->params.atomic_nsfeat) {
id_ns->nsfeat |= NVME_ID_NS_NSFEAT_NSABPNS;
id_ns->nawun = cpu_to_le16(ns->params.atomic_nawun);
if (!id->awupf || (id_ns->nawun && (id_ns->nawun < id->awun))) {
error_report("Invalid NAWUN: %x AWUN=%x", id_ns->nawun, id->awun);
}
id_ns->nawupf = cpu_to_le16(ns->params.atomic_nawupf);
if (!id->awupf || (id_ns->nawupf && (id_ns->nawupf < id->awupf))) {
error_report("Invalid NAWUPF: %x AWUPF=%x",
id_ns->nawupf, id->awupf);
}
if (id_ns->nawupf > id_ns->nawun) {
error_report("Invalid: NAWUN=%x NAWUPF=%x",
id_ns->nawun, id_ns->nawupf);
}
id_ns->nabsn = cpu_to_le16(ns->params.atomic_nabsn);
id_ns->nabspf = cpu_to_le16(ns->params.atomic_nabspf);
id_ns->nabo = cpu_to_le16(ns->params.atomic_nabo);
if (!id->awun || (id_ns->nabsn && ((id_ns->nabsn < id_ns->nawun) ||
(id_ns->nabsn < id->awun)))) {
error_report("Invalid NABSN: %x NAWUN=%x AWUN=%x",
id_ns->nabsn, id_ns->nawun, id->awun);
}
if (!id->awupf || (id_ns->nabspf && ((id_ns->nabspf < id_ns->nawupf) ||
(id_ns->nawupf < id->awupf)))) {
error_report("Invalid NABSPF: %x NAWUPF=%x AWUPF=%x",
id_ns->nabspf, id_ns->nawupf, id->awupf);
}
if (id_ns->nabo && ((id_ns->nabo > id_ns->nabsn) ||
(id_ns->nabo > id_ns->nabspf))) {
error_report("Invalid NABO: %x NABSN=%x NABSPF=%x",
id_ns->nabo, id_ns->nabsn, id_ns->nabspf);
}
if (id_ns->nawupf > id_ns->nawun) {
error_report("Invalid: NAWUN=%x NAWUPF=%x", id_ns->nawun,
id_ns->nawupf);
}
}
if (id_ns->nawun || id_ns->nawupf) {
NvmeAtomic *atomic = &ns->atomic;
if (n->dn) {
atomic->atomic_max_write_size = cpu_to_le16(id_ns->nawupf) + 1;
if (id_ns->nabspf) {
atomic->atomic_boundary = cpu_to_le16(id_ns->nabspf) + 1;
} else {
atomic->atomic_boundary = 0;
}
} else {
atomic->atomic_max_write_size = cpu_to_le16(id_ns->nawun) + 1;
if (id_ns->nabsn) {
atomic->atomic_boundary = cpu_to_le16(id_ns->nabsn) + 1;
} else {
atomic->atomic_boundary = 0;
}
}
if (atomic->atomic_max_write_size == 1) {
atomic->atomic_writes = 0;
} else {
atomic->atomic_writes = 1;
}
atomic->atomic_nabo = cpu_to_le16(id_ns->nabo);
}
/* reparent to subsystem bus */
if (!qdev_set_parent_bus(dev, &subsys->bus.parent_bus, errp)) {
return;
@ -804,6 +872,12 @@ static const Property nvme_ns_props[] = {
DEFINE_PROP_BOOL("eui64-default", NvmeNamespace, params.eui64_default,
false),
DEFINE_PROP_STRING("fdp.ruhs", NvmeNamespace, params.fdp.ruhs),
DEFINE_PROP_UINT16("atomic.nawun", NvmeNamespace, params.atomic_nawun, 0),
DEFINE_PROP_UINT16("atomic.nawupf", NvmeNamespace, params.atomic_nawupf, 0),
DEFINE_PROP_UINT16("atomic.nabspf", NvmeNamespace, params.atomic_nabspf, 0),
DEFINE_PROP_UINT16("atomic.nabsn", NvmeNamespace, params.atomic_nabsn, 0),
DEFINE_PROP_UINT16("atomic.nabo", NvmeNamespace, params.atomic_nabo, 0),
DEFINE_PROP_BOOL("atomic.nsfeat", NvmeNamespace, params.atomic_nsfeat, 0),
};
static void nvme_ns_class_init(ObjectClass *oc, const void *data)

View file

@ -218,10 +218,18 @@ typedef struct NvmeNamespaceParams {
struct {
char *ruhs;
} fdp;
uint16_t atomic_nawun;
uint16_t atomic_nawupf;
uint16_t atomic_nabsn;
uint16_t atomic_nabspf;
uint16_t atomic_nabo;
bool atomic_nsfeat;
} NvmeNamespaceParams;
typedef struct NvmeAtomic {
uint32_t atomic_max_write_size;
uint64_t atomic_boundary;
uint64_t atomic_nabo;
bool atomic_writes;
} NvmeAtomic;
@ -280,6 +288,12 @@ typedef struct NvmeNamespace {
/* reclaim unit handle identifiers indexed by placement handle */
uint16_t *phs;
} fdp;
uint16_t atomic_nawun;
uint16_t atomic_nawupf;
uint16_t atomic_nabsn;
uint16_t atomic_nabspf;
uint16_t atomic_nabo;
NvmeAtomic atomic;
} NvmeNamespace;
static inline uint32_t nvme_nsid(NvmeNamespace *ns)
@ -461,6 +475,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 +664,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 {

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;

View file

@ -4,6 +4,7 @@
#include "hw/pci/pci.h"
#include "hw/pci/pcie.h"
#include "hw/pci/pcie_doe.h"
#include "system/spdm-socket.h"
#define TYPE_PCI_DEVICE "pci-device"
typedef struct PCIDeviceClass PCIDeviceClass;
@ -166,6 +167,7 @@ struct PCIDevice {
/* SPDM */
uint16_t spdm_port;
SpdmTransportType spdm_trans;
/* DOE */
DOECap doe_spdm;

View file

@ -50,6 +50,35 @@ uint32_t spdm_socket_rsp(const int socket, uint32_t transport_type,
void *req, uint32_t req_len,
void *rsp, uint32_t rsp_len);
/**
* spdm_socket_rsp: Receive a message from an SPDM server
* @socket: socket returned from spdm_socket_connect()
* @transport_type: SPDM_SOCKET_TRANSPORT_TYPE_* macro
* @rsp: response buffer
* @rsp_len: response buffer length
*
* Receives a message from the SPDM server and returns the number of bytes
* received or 0 on failure. This can be used to receive a message from the SPDM
* server without sending anything first.
*/
uint32_t spdm_socket_receive(const int socket, uint32_t transport_type,
void *rsp, uint32_t rsp_len);
/**
* spdm_socket_rsp: Sends a message to an SPDM server
* @socket: socket returned from spdm_socket_connect()
* @socket_cmd: socket command type (normal/if_recv/if_send etc...)
* @transport_type: SPDM_SOCKET_TRANSPORT_TYPE_* macro
* @req: request buffer
* @req_len: request buffer length
*
* Sends platform data to a SPDM server on socket, returns true on success.
* The response from the server must then be fetched by using
* spdm_socket_receive().
*/
bool spdm_socket_send(const int socket, uint32_t socket_cmd,
uint32_t transport_type, void *req, uint32_t req_len);
/**
* spdm_socket_close: send a shutdown command to the server
* @socket: socket returned from spdm_socket_connect()
@ -59,16 +88,46 @@ uint32_t spdm_socket_rsp(const int socket, uint32_t transport_type,
*/
void spdm_socket_close(const int socket, uint32_t transport_type);
/*
* Defines the transport encoding for SPDM, this information shall be passed
* down to the SPDM server, when conforming to the SPDM over Storage standard
* as defined by DSP0286.
*/
typedef struct {
uint8_t security_protocol; /* Must be 0xE8 for SPDM Commands
as per SCSI Primary Commands 5 */
uint16_t security_protocol_specific; /* Bit[7:2] SPDM Operation
Bit[0:1] Connection ID
per DSP0286 1.0: Section 7.2 */
uint32_t length; /* Length of the SPDM Message*/
} QEMU_PACKED StorageSpdmTransportHeader;
#define SPDM_SOCKET_COMMAND_NORMAL 0x0001
#define SPDM_SOCKET_STORAGE_CMD_IF_SEND 0x0002
#define SPDM_SOCKET_STORAGE_CMD_IF_RECV 0x0003
#define SOCKET_SPDM_STORAGE_ACK_STATUS 0x0004
#define SPDM_SOCKET_COMMAND_OOB_ENCAP_KEY_UPDATE 0x8001
#define SPDM_SOCKET_COMMAND_CONTINUE 0xFFFD
#define SPDM_SOCKET_COMMAND_SHUTDOWN 0xFFFE
#define SPDM_SOCKET_COMMAND_UNKOWN 0xFFFF
#define SPDM_SOCKET_COMMAND_TEST 0xDEAD
#define SPDM_SOCKET_TRANSPORT_TYPE_MCTP 0x01
#define SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE 0x02
#define SPDM_SOCKET_MAX_MESSAGE_BUFFER_SIZE 0x1200
#define SPDM_SOCKET_MAX_MSG_STATUS_LEN 0x02
typedef enum SpdmTransportType {
SPDM_SOCKET_TRANSPORT_TYPE_UNSPEC = 0,
SPDM_SOCKET_TRANSPORT_TYPE_MCTP,
SPDM_SOCKET_TRANSPORT_TYPE_PCI_DOE,
SPDM_SOCKET_TRANSPORT_TYPE_SCSI,
SPDM_SOCKET_TRANSPORT_TYPE_NVME,
SPDM_SOCKET_TRANSPORT_TYPE_MAX
} SpdmTransportType;
extern const PropertyInfo qdev_prop_spdm_trans;
#define DEFINE_PROP_SPDM_TRANS(_name, _state, _field, _default) \
DEFINE_PROP_UNSIGNED(_name, _state, _field, _default, \
qdev_prop_spdm_trans, SpdmTransportType)
#endif