From 386849895ddffcff22cbe0f965b579d7ec73b58e 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, providing a way for the application to implement vendor specific mi commands. Signed-off-by: Chuck Horkin --- src/libnvme-mi.map | 5 ++++ src/nvme/mi.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/nvme/mi.h | 30 +++++++++++++++++++++ test/mi.c | 45 ++++++++++++++++++++++++++++--- 4 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/libnvme-mi.map b/src/libnvme-mi.map index 11b3954d..0fb37829 100644 --- a/src/libnvme-mi.map +++ b/src/libnvme-mi.map @@ -1,3 +1,8 @@ +LIBNVME_MI_1_12 { + global: + nvme_mi_mi_xfer; +}; + LIBNVME_MI_1_11 { global: nvme_mi_control; diff --git a/src/nvme/mi.c b/src/nvme/mi.c index e9f39a84..b2622f06 100644 --- a/src/nvme/mi.c +++ b/src/nvme/mi.c @@ -1599,6 +1599,72 @@ 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; + + /* There is nothing in the spec to define this limit but going with the limits + * from the admin message types for DLEN seems like a reasonable starting point + * to check for coding errors + */ + const size_t mi_data_xfer_size_limit = 4096; + + /* 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 particular command set + */ + + if (*resp_data_size > mi_data_xfer_size_limit) { + 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 */ + 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..746ee0fb 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 MI 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 @mi_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). Note that the Management + * Request Doublewords are considered part of the header data. + * + * On success, response data is stored in @mi_resp, which has an optional + * appended payload buffer of @resp_data_size bytes. The actual payload + * size transferred will be stored in @resp_data_size. This size does not + * include the MI response 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..6126d879 100644 --- a/test/mi.c +++ b/test/mi.c @@ -566,8 +566,8 @@ static void test_admin_err_nvme_resp(nvme_mi_ep_t ep) | NVME_SC_DNR)); } -/* invalid Admin command transfers */ -static int test_admin_invalid_formats_cb(struct nvme_mi_ep *ep, +/* invalid command transfers */ +static int test_rejected_command_cb(struct nvme_mi_ep *ep, struct nvme_mi_req *req, struct nvme_mi_resp *resp, void *data) @@ -588,7 +588,7 @@ static void test_admin_invalid_formats(nvme_mi_ep_t ep) size_t len; int rc; - test_set_transport_callback(ep, test_admin_invalid_formats_cb, NULL); + test_set_transport_callback(ep, test_rejected_command_cb, NULL); ctrl = nvme_mi_init_ctrl(ep, 1); assert(ctrl); @@ -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_rejected_command_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)