On Thu, Sep 14, 2023 at 11:53:43AM +0200, Klaus Jensen wrote: > From: Klaus Jensen <k.jen...@samsung.com> > > Add the 'nmi-i2c' device that emulates an NVMe Management Interface > controller. > > Initial support is very basic (Read NMI DS, Configuration Get). > > This is based on previously posted code by Padmakar Kalghatgi, Arun > Kumar Agasar and Saurav Kumar.
This seems fine. Acked-by: Corey Minyard <cminy...@mvista.com> One question, though. You don't have any tests. Did you test invalid packets and such? I think the logic is correct, but those are things that are good to test. Having tests in qemu would be even better. > > Reviewed-by: Jonathan Cameron <jonathan.came...@huawei.com> > Signed-off-by: Klaus Jensen <k.jen...@samsung.com> > --- > hw/nvme/Kconfig | 4 + > hw/nvme/meson.build | 1 + > hw/nvme/nmi-i2c.c | 407 > +++++++++++++++++++++++++++++++++++++++++++++++++++ > hw/nvme/trace-events | 6 + > 4 files changed, 418 insertions(+) > > diff --git a/hw/nvme/Kconfig b/hw/nvme/Kconfig > index cfa2ab0f9d5a..e1f6360c0f4b 100644 > --- a/hw/nvme/Kconfig > +++ b/hw/nvme/Kconfig > @@ -2,3 +2,7 @@ config NVME_PCI > bool > default y if PCI_DEVICES || PCIE_DEVICES > depends on PCI > + > +config NVME_NMI_I2C > + bool > + default y if I2C_MCTP > diff --git a/hw/nvme/meson.build b/hw/nvme/meson.build > index 1a6a2ca2f307..7bc85f31c149 100644 > --- a/hw/nvme/meson.build > +++ b/hw/nvme/meson.build > @@ -1 +1,2 @@ > system_ss.add(when: 'CONFIG_NVME_PCI', if_true: files('ctrl.c', 'dif.c', > 'ns.c', 'subsys.c')) > +system_ss.add(when: 'CONFIG_NVME_NMI_I2C', if_true: files('nmi-i2c.c')) > diff --git a/hw/nvme/nmi-i2c.c b/hw/nvme/nmi-i2c.c > new file mode 100644 > index 000000000000..bf4648db0457 > --- /dev/null > +++ b/hw/nvme/nmi-i2c.c > @@ -0,0 +1,407 @@ > +/* > + * SPDX-License-Identifier: GPL-2.0-or-later > + * > + * SPDX-FileCopyrightText: Copyright (c) 2023 Samsung Electronics Co., Ltd. > + * > + * SPDX-FileContributor: Padmakar Kalghatgi <p.kalgha...@samsung.com> > + * SPDX-FileContributor: Arun Kumar Agasar <arun....@samsung.com> > + * SPDX-FileContributor: Saurav Kumar <saurav...@partner.samsung.com> > + * SPDX-FileContributor: Klaus Jensen <k.jen...@samsung.com> > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/crc32c.h" > +#include "hw/registerfields.h" > +#include "hw/i2c/i2c.h" > +#include "hw/i2c/mctp.h" > +#include "net/mctp.h" > +#include "trace.h" > + > +/* NVM Express Management Interface 1.2c, Section 3.1 */ > +#define NMI_MAX_MESSAGE_LENGTH 4224 > + > +#define TYPE_NMI_I2C_DEVICE "nmi-i2c" > +OBJECT_DECLARE_SIMPLE_TYPE(NMIDevice, NMI_I2C_DEVICE) > + > +typedef struct NMIDevice { > + MCTPI2CEndpoint mctp; > + > + uint8_t buffer[NMI_MAX_MESSAGE_LENGTH]; > + uint8_t scratch[NMI_MAX_MESSAGE_LENGTH]; > + > + size_t len; > + int64_t pos; > +} NMIDevice; > + > +FIELD(NMI_MCTPD, MT, 0, 7) > +FIELD(NMI_MCTPD, IC, 7, 1) > + > +#define NMI_MCTPD_MT_NMI 0x4 > +#define NMI_MCTPD_IC_ENABLED 0x1 > + > +FIELD(NMI_NMP, ROR, 7, 1) > +FIELD(NMI_NMP, NMIMT, 3, 4) > + > +#define NMI_NMP_NMIMT_NVME_MI 0x1 > +#define NMI_NMP_NMIMT_NVME_ADMIN 0x2 > + > +typedef struct NMIMessage { > + uint8_t mctpd; > + uint8_t nmp; > + uint8_t rsvd2[2]; > + uint8_t payload[]; /* includes the Message Integrity Check */ > +} NMIMessage; > + > +typedef struct NMIRequest { > + uint8_t opc; > + uint8_t rsvd1[3]; > + uint32_t dw0; > + uint32_t dw1; > + uint32_t mic; > +} NMIRequest; > + > +FIELD(NMI_CMD_READ_NMI_DS_DW0, DTYP, 24, 8) > + > +typedef enum NMIReadDSType { > + NMI_CMD_READ_NMI_DS_SUBSYSTEM = 0x0, > + NMI_CMD_READ_NMI_DS_PORTS = 0x1, > + NMI_CMD_READ_NMI_DS_CTRL_LIST = 0x2, > + NMI_CMD_READ_NMI_DS_CTRL_INFO = 0x3, > + NMI_CMD_READ_NMI_DS_OPT_CMD_SUPPORT = 0x4, > + NMI_CMD_READ_NMI_DS_MEB_CMD_SUPPORT = 0x5, > +} NMIReadDSType; > + > +#define NMI_STATUS_INVALID_PARAMETER 0x4 > + > +static void nmi_scratch_append(NMIDevice *nmi, const void *buf, size_t count) > +{ > + assert(nmi->pos + count <= NMI_MAX_MESSAGE_LENGTH); > + > + memcpy(nmi->scratch + nmi->pos, buf, count); > + nmi->pos += count; > +} > + > +static void nmi_set_parameter_error(NMIDevice *nmi, uint8_t bit, uint16_t > byte) > +{ > + /* NVM Express Management Interface 1.2c, Figure 30 */ > + struct resp { > + uint8_t status; > + uint8_t bit; > + uint16_t byte; > + }; > + > + struct resp buf = { > + .status = NMI_STATUS_INVALID_PARAMETER, > + .bit = bit & 0x3, > + .byte = byte, > + }; > + > + nmi_scratch_append(nmi, &buf, sizeof(buf)); > +} > + > +static void nmi_set_error(NMIDevice *nmi, uint8_t status) > +{ > + const uint8_t buf[4] = {status,}; > + > + nmi_scratch_append(nmi, buf, sizeof(buf)); > +} > + > +static void nmi_handle_mi_read_nmi_ds(NMIDevice *nmi, NMIRequest *request) > +{ > + I2CSlave *i2c = I2C_SLAVE(nmi); > + > + uint32_t dw0 = le32_to_cpu(request->dw0); > + uint8_t dtyp = FIELD_EX32(dw0, NMI_CMD_READ_NMI_DS_DW0, DTYP); > + > + trace_nmi_handle_mi_read_nmi_ds(dtyp); > + > + static const uint8_t nmi_ds_subsystem[36] = { > + 0x00, /* success */ > + 0x20, 0x00, /* response data length */ > + 0x00, /* reserved */ > + 0x00, /* number of ports */ > + 0x01, /* major version */ > + 0x01, /* minor version */ > + }; > + > + /* > + * Cannot be static (or const) since we need to patch in the i2c > + * address. > + */ > + const uint8_t nmi_ds_ports[36] = { > + 0x00, /* success */ > + 0x20, 0x00, /* response data length */ > + 0x00, /* reserved */ > + 0x02, /* port type (smbus) */ > + 0x00, /* reserved */ > + 0x40, 0x00, /* maximum mctp transission unit size (64 bytes) */ > + 0x00, 0x00, 0x00, 0x00, /* management endpoint buffer size */ > + 0x00, /* vpd i2c address */ > + 0x00, /* vpd i2c frequency */ > + i2c->address, /* management endpoint i2c address */ > + 0x01, /* management endpoint i2c frequency */ > + 0x00, /* nvme basic management command NOT supported */ > + }; > + > + /** > + * Controller Information is zeroed, since there are no associated > + * controllers at this point. > + */ > + static const uint8_t nmi_ds_ctrl[36] = {}; > + > + /** > + * For the Controller List, Optionally Supported Command List and > + * Management Endpoint Buffer Supported Command List data structures. > + * > + * The Controller List data structure is defined in the NVM Express Base > + * Specification, revision 2.0b, Figure 134. > + */ > + static const uint8_t nmi_ds_empty[6] = { > + 0x00, /* success */ > + 0x02, /* response data length */ > + 0x00, 0x00, /* reserved */ > + 0x00, 0x00, /* number of entries (zero) */ > + }; > + > + switch (dtyp) { > + case NMI_CMD_READ_NMI_DS_SUBSYSTEM: > + nmi_scratch_append(nmi, &nmi_ds_subsystem, sizeof(nmi_ds_subsystem)); > + return; > + > + case NMI_CMD_READ_NMI_DS_PORTS: > + nmi_scratch_append(nmi, &nmi_ds_ports, sizeof(nmi_ds_ports)); > + return; > + > + case NMI_CMD_READ_NMI_DS_CTRL_INFO: > + nmi_scratch_append(nmi, &nmi_ds_ctrl, sizeof(nmi_ds_ctrl)); > + return; > + > + case NMI_CMD_READ_NMI_DS_CTRL_LIST: > + case NMI_CMD_READ_NMI_DS_OPT_CMD_SUPPORT: > + case NMI_CMD_READ_NMI_DS_MEB_CMD_SUPPORT: > + nmi_scratch_append(nmi, &nmi_ds_empty, sizeof(nmi_ds_empty)); > + return; > + > + default: > + nmi_set_parameter_error(nmi, offsetof(NMIRequest, dw0) + 4, 0); > + return; > + } > +} > + > +FIELD(NMI_CMD_CONFIGURATION_GET_DW0, IDENTIFIER, 0, 8) > + > +enum { > + NMI_CMD_CONFIGURATION_GET_SMBUS_FREQ = 0x1, > + NMI_CMD_CONFIGURATION_GET_HEALTH_STATUS_CHANGE = 0x2, > + NMI_CMD_CONFIGURATION_GET_MCTP_TRANSMISSION_UNIT = 0x3, > +}; > + > +static void nmi_handle_mi_config_get(NMIDevice *nmi, NMIRequest *request) > +{ > + uint32_t dw0 = le32_to_cpu(request->dw0); > + uint8_t identifier = FIELD_EX32(dw0, NMI_CMD_CONFIGURATION_GET_DW0, > + IDENTIFIER); > + static const uint8_t smbus_freq[4] = { > + 0x00, /* success */ > + 0x01, 0x00, 0x00, /* 100 kHz */ > + }; > + > + static const uint8_t mtu[4] = { > + 0x00, /* success */ > + 0x40, 0x00, /* 64 */ > + 0x00, /* reserved */ > + }; > + > + trace_nmi_handle_mi_config_get(identifier); > + > + switch (identifier) { > + case NMI_CMD_CONFIGURATION_GET_SMBUS_FREQ: > + nmi_scratch_append(nmi, &smbus_freq, sizeof(smbus_freq)); > + return; > + > + case NMI_CMD_CONFIGURATION_GET_MCTP_TRANSMISSION_UNIT: > + nmi_scratch_append(nmi, &mtu, sizeof(mtu)); > + return; > + > + default: > + nmi_set_parameter_error(nmi, 0x0, offsetof(NMIRequest, dw0)); > + return; > + } > +} > + > +enum { > + NMI_CMD_READ_NMI_DS = 0x0, > + NMI_CMD_CONFIGURATION_GET = 0x4, > +}; > + > +static void nmi_handle_mi(NMIDevice *nmi, NMIMessage *msg) > +{ > + NMIRequest *request = (NMIRequest *)msg->payload; > + > + trace_nmi_handle_mi(request->opc); > + > + switch (request->opc) { > + case NMI_CMD_READ_NMI_DS: > + nmi_handle_mi_read_nmi_ds(nmi, request); > + break; > + > + case NMI_CMD_CONFIGURATION_GET: > + nmi_handle_mi_config_get(nmi, request); > + break; > + > + default: > + nmi_set_parameter_error(nmi, 0x0, 0x0); > + fprintf(stderr, "nmi command 0x%x not handled\n", request->opc); > + > + break; > + } > +} > + > +static void nmi_reset(MCTPI2CEndpoint *mctp) > +{ > + NMIDevice *nmi = NMI_I2C_DEVICE(mctp); > + nmi->len = 0; > +} > + > +static void nmi_handle(MCTPI2CEndpoint *mctp) > +{ > + NMIDevice *nmi = NMI_I2C_DEVICE(mctp); > + NMIMessage *msg = (NMIMessage *)nmi->buffer; > + uint32_t crc; > + uint8_t nmimt; > + > + const uint8_t buf[] = { > + msg->mctpd, > + FIELD_DP8(msg->nmp, NMI_NMP, ROR, 1), > + 0x0, 0x0, > + }; > + > + if (FIELD_EX8(msg->mctpd, NMI_MCTPD, MT) != NMI_MCTPD_MT_NMI) { > + goto drop; > + } > + > + if (FIELD_EX8(msg->mctpd, NMI_MCTPD, IC) != NMI_MCTPD_IC_ENABLED) { > + goto drop; > + } > + > + nmi->pos = 0; > + nmi_scratch_append(nmi, buf, sizeof(buf)); > + > + nmimt = FIELD_EX8(msg->nmp, NMI_NMP, NMIMT); > + > + trace_nmi_handle_msg(nmimt); > + > + switch (nmimt) { > + case NMI_NMP_NMIMT_NVME_MI: > + nmi_handle_mi(nmi, msg); > + break; > + > + default: > + fprintf(stderr, "nmi message type 0x%x not handled\n", nmimt); > + > + nmi_set_error(nmi, 0x3); > + > + break; > + } > + > + crc = crc32c(0xffffffff, nmi->scratch, nmi->pos); > + nmi_scratch_append(nmi, &crc, sizeof(crc)); > + > + nmi->len = nmi->pos; > + nmi->pos = 0; > + > + i2c_mctp_schedule_send(mctp); > + > + return; > + > +drop: > + nmi_reset(mctp); > +} > + > +static size_t nmi_get_buf(MCTPI2CEndpoint *mctp, const uint8_t **buf, > + size_t maxlen, uint8_t *mctp_flags) > +{ > + NMIDevice *nmi = NMI_I2C_DEVICE(mctp); > + size_t len; > + > + len = MIN(maxlen, nmi->len - nmi->pos); > + > + if (len == 0) { > + return 0; > + } > + > + if (nmi->pos == 0) { > + *mctp_flags = FIELD_DP8(*mctp_flags, MCTP_H_FLAGS, SOM, 1); > + } > + > + *buf = nmi->scratch + nmi->pos; > + nmi->pos += len; > + > + if (nmi->pos == nmi->len) { > + *mctp_flags = FIELD_DP8(*mctp_flags, MCTP_H_FLAGS, EOM, 1); > + > + nmi->pos = nmi->len = 0; > + } > + > + return len; > +} > + > +static int nmi_put_buf(MCTPI2CEndpoint *mctp, uint8_t *buf, size_t len) > +{ > + NMIDevice *nmi = NMI_I2C_DEVICE(mctp); > + > + if (nmi->len + len > NMI_MAX_MESSAGE_LENGTH) { > + return -1; > + } > + > + memcpy(nmi->buffer + nmi->len, buf, len); > + nmi->len += len; > + > + return 0; > +} > + > +static size_t nmi_get_types(MCTPI2CEndpoint *mctp, const uint8_t **data) > +{ > + /** > + * DSP0236 1.3.0, Table 19. > + * > + * This only includes message types that are supported *in addition* to > the > + * MCTP control message type. > + */ > + static const uint8_t buf[] = { > + 0x0, /* success */ > + 0x1, /* number of message types in list (supported) */ > + NMI_MCTPD_MT_NMI, > + }; > + > + *data = buf; > + > + return sizeof(buf); > +} > + > +static void nvme_mi_class_init(ObjectClass *oc, void *data) > +{ > + MCTPI2CEndpointClass *mc = MCTP_I2C_ENDPOINT_CLASS(oc); > + > + mc->get_types = nmi_get_types; > + > + mc->get_buf = nmi_get_buf; > + mc->put_buf = nmi_put_buf; > + > + mc->handle = nmi_handle; > + mc->reset = nmi_reset; > +} > + > +static const TypeInfo nvme_mi = { > + .name = TYPE_NMI_I2C_DEVICE, > + .parent = TYPE_MCTP_I2C_ENDPOINT, > + .instance_size = sizeof(NMIDevice), > + .class_init = nvme_mi_class_init, > +}; > + > +static void register_types(void) > +{ > + type_register_static(&nvme_mi); > +} > + > +type_init(register_types); > diff --git a/hw/nvme/trace-events b/hw/nvme/trace-events > index 3a67680c6ad1..41e2c540dcf2 100644 > --- a/hw/nvme/trace-events > +++ b/hw/nvme/trace-events > @@ -216,3 +216,9 @@ pci_nvme_ub_db_wr_invalid_sq(uint32_t qid) "submission > queue doorbell write for > pci_nvme_ub_db_wr_invalid_sqtail(uint32_t qid, uint16_t new_tail) > "submission queue doorbell write value beyond queue size, sqid=%"PRIu32", > new_head=%"PRIu16", ignoring" > pci_nvme_ub_unknown_css_value(void) "unknown value in cc.css field" > pci_nvme_ub_too_many_mappings(void) "too many prp/sgl mappings" > + > +# nmi-i2c > +nmi_handle_mi_read_nmi_ds(uint8_t dtyp) "dtyp 0x%"PRIx8"" > +nmi_handle_mi_config_get(uint8_t identifier) "identifier 0x%"PRIx8"" > +nmi_handle_mi(uint8_t opc) "opc 0x%"PRIx8"" > +nmi_handle_msg(uint8_t nmint) "nmint 0x%"PRIx8"" > > -- > 2.42.0 > >