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:
commit
c494afbb7d
8 changed files with 571 additions and 30 deletions
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
335
hw/nvme/ctrl.c
335
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] = {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
74
hw/nvme/ns.c
74
hw/nvme/ns.c
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue