From 54edbc705991b333120158d071fad4fa73c548c8 Mon Sep 17 00:00:00 2001 From: Chuck Horkin Date: Wed, 11 Dec 2024 09:55:49 -0800 Subject: [PATCH] mi_xfer: Added nvme_mi_mi_xfer API This is added to be analogous to nvme_mi_admin_xfer Signed-off-by: Chuck Horkin --- src/nvme/mi.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvme/mi.h | 30 +++++++++++++++++++++++++ test/mi.c | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/src/nvme/mi.c b/src/nvme/mi.c index e9f39a84..da76e7f1 100644 --- a/src/nvme/mi.c +++ b/src/nvme/mi.c @@ -1599,6 +1599,67 @@ static int nvme_mi_read_data(nvme_mi_ep_t ep, __u32 cdw0, return 0; } +int nvme_mi_mi_xfer(nvme_mi_ep_t ep, + struct nvme_mi_mi_req_hdr *mi_req, + size_t req_data_size, + struct nvme_mi_mi_resp_hdr *mi_resp, + size_t *resp_data_size) +{ + int rc; + struct nvme_mi_req req; + struct nvme_mi_resp resp; + + /* length/offset checks. The common _submit() API will do further + * checking on the message lengths too, so these are kept specific + * to the requirements of the Admin command set + */ + + /* NVMe-MI v1.2 imposes a limit of 4096 bytes on the dlen field */ + if (*resp_data_size > 4096) { + errno = EINVAL; + return -1; + } + + /* request and response lengths & offset must be aligned */ + if ((req_data_size & 0x3) || + (*resp_data_size & 0x3)) { + errno = EINVAL; + return -1; + } + + /* bidirectional not permitted (see DLEN definition) */ + if (req_data_size && *resp_data_size) { + errno = EINVAL; + return -1; + } + + mi_req->hdr.type = NVME_MI_MSGTYPE_NVME; + mi_req->hdr.nmp = (NVME_MI_ROR_REQ << 7) | + (NVME_MI_MT_MI << 3); + + memset(&req, 0, sizeof(req)); + req.hdr = &mi_req->hdr; + req.hdr_len = sizeof(*mi_req); + req.data = mi_req + 1; + req.data_len = req_data_size; + + nvme_mi_calc_req_mic(&req); + + memset(&resp, 0, sizeof(resp)); + resp.hdr = &mi_resp->hdr; + resp.hdr_len = sizeof(*mi_resp); + resp.data = mi_resp + 1; + resp.data_len = *resp_data_size; + + rc = nvme_mi_submit(ep, &req, &resp); + if (rc) + return rc; + + *resp_data_size = resp.data_len; + + return 0; +} + int nvme_mi_mi_read_mi_data_subsys(nvme_mi_ep_t ep, struct nvme_mi_read_nvm_ss_info *s) { diff --git a/src/nvme/mi.h b/src/nvme/mi.h index 694f3260..a21b3f2f 100644 --- a/src/nvme/mi.h +++ b/src/nvme/mi.h @@ -729,6 +729,36 @@ char *nvme_mi_endpoint_desc(nvme_mi_ep_t ep); /* MI Command API: nvme_mi_mi_ prefix */ +/** + * nvme_mi_mi_xfer() - Raw mi transfer interface. + * @ep: endpoint to send the admin command to + * @mi_req: request data + * @req_data_size: size of request data payload + * @mi_resp: buffer for response data + * @resp_data_size: size of response data buffer, updated to received size + * + * Performs an arbitrary NVMe MI command, using the provided request data, + * in @admin_req. The size of the request data *payload* is specified in + * @req_data_size - this does not include the standard header length (so a + * header-only request would have a size of 0). + * + * On success, response data is stored in @admin_resp, which has an optional + * appended payload buffer of @resp_data_size bytes. The actual payload + * transferred will be stored in @resp_data_size. These sizes do not include + * the Admin request header, so 0 represents no payload. + * + * + * See: &struct nvme_mi_mi_req_hdr and &struct nvme_mi_mi_resp_hdr. + * + * Return: The nvme command status if a response was received (see + * &enum nvme_status_field) or -1 with errno set otherwise.. + */ +int nvme_mi_mi_xfer(nvme_mi_ep_t ep, + struct nvme_mi_mi_req_hdr *mi_req, + size_t req_data_size, + struct nvme_mi_mi_resp_hdr *mi_resp, + size_t *resp_data_size); + /** * nvme_mi_mi_read_mi_data_subsys() - Perform a Read MI Data Structure command, * retrieving subsystem data. diff --git a/test/mi.c b/test/mi.c index 05eee97b..68eea6f2 100644 --- a/test/mi.c +++ b/test/mi.c @@ -629,6 +629,44 @@ static void test_admin_invalid_formats(nvme_mi_ep_t ep) assert(rc != 0); } +static void test_mi_invalid_formats(nvme_mi_ep_t ep) +{ + struct { + struct nvme_mi_mi_req_hdr hdr; + uint8_t data[4]; + } req = { 0 }; + struct nvme_mi_mi_resp_hdr resp = { 0 }; + nvme_mi_ctrl_t ctrl; + size_t len; + int rc; + + test_set_transport_callback(ep, test_admin_invalid_formats_cb, NULL); + + ctrl = nvme_mi_init_ctrl(ep, 1); + assert(ctrl); + + /* unaligned req size */ + len = 0; + + rc = nvme_mi_mi_xfer(ep, &req.hdr, 1, &resp, &len); + assert(rc != 0); + + /* unaligned resp size */ + len = 1; + rc = nvme_mi_mi_xfer(ep, &req.hdr, 0, &resp, &len); + assert(rc != 0); + + /* resp too large */ + len = 4096 + 4; + rc = nvme_mi_mi_xfer(ep, &req.hdr, 0, &resp, &len); + assert(rc != 0); + + /* req and resp payloads */ + len = 4; + rc = nvme_mi_mi_xfer(ep, &req.hdr, 4, &resp, &len); + assert(rc != 0); +} + /* test: header length too small */ static int test_resp_hdr_small_cb(struct nvme_mi_ep *ep, struct nvme_mi_req *req, @@ -2049,6 +2087,7 @@ struct test { DEFINE_TEST(endpoint_quirk_probe), DEFINE_TEST(admin_dlen_doff_req), DEFINE_TEST(admin_dlen_doff_resp), + DEFINE_TEST(mi_invalid_formats), }; static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep)