]> www.infradead.org Git - users/sagi/libnvme.git/commitdiff
mi: Add firmware download and commit commands
authorJeremy Kerr <jk@codeconstruct.com.au>
Sat, 24 Sep 2022 06:44:36 +0000 (14:44 +0800)
committerJeremy Kerr <jk@codeconstruct.com.au>
Mon, 26 Sep 2022 13:17:46 +0000 (21:17 +0800)
This change adds MI implementations for the Firmware Download and
Firmware Commit admin commands, as well as a couple of tests.

As usual, these are designed to match the ioctl API.

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
src/libnvme-mi.map
src/nvme/mi.c
src/nvme/mi.h
test/mi.c

index 37a38fa9fa2bc8043ef05ebaa8570869294932a5..3a318fa213989c0e0dbb675d99492a151a221781 100644 (file)
@@ -6,6 +6,8 @@ LIBNVME_MI_1_2 {
                nvme_mi_admin_ns_attach;
                nvme_mi_admin_format_nvm;
                nvme_mi_admin_sanitize_nvm;
+               nvme_mi_admin_fw_download;
+               nvme_mi_admin_fw_commit;
 };
 
 LIBNVME_MI_1_1 {
index 7b7ae020e9286946d5f5c1364fca2f9826f78d4a..8fd1a15d8c1b5bd822f5c3f67bf628cb78e61afd 100644 (file)
@@ -831,6 +831,78 @@ int nvme_mi_admin_ns_attach(nvme_mi_ctrl_t ctrl,
        return nvme_mi_admin_parse_status(&resp, args->result);
 }
 
+int nvme_mi_admin_fw_download(nvme_mi_ctrl_t ctrl,
+                             struct nvme_fw_download_args *args)
+{
+       struct nvme_mi_admin_resp_hdr resp_hdr;
+       struct nvme_mi_admin_req_hdr req_hdr;
+       struct nvme_mi_resp resp;
+       struct nvme_mi_req req;
+       int rc;
+
+       if (args->args_size < sizeof(*args))
+               return -EINVAL;
+
+       if (args->data_len & 0x3)
+               return -EINVAL;
+
+       if (args->offset & 0x3)
+               return -EINVAL;
+
+       if (!args->data_len)
+               return -EINVAL;
+
+       nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+                              nvme_admin_fw_download);
+
+       req_hdr.cdw10 = cpu_to_le32((args->data_len >> 2) - 1);
+       req_hdr.cdw11 = cpu_to_le32(args->offset >> 2);
+       req.data = args->data;
+       req.data_len = args->data_len;
+       req_hdr.dlen = cpu_to_le32(args->data_len);
+       req_hdr.flags = 0x1;
+
+       nvme_mi_calc_req_mic(&req);
+
+       nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+       rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+       if (rc)
+               return rc;
+
+       return nvme_mi_admin_parse_status(&resp, NULL);
+}
+
+int nvme_mi_admin_fw_commit(nvme_mi_ctrl_t ctrl,
+                           struct nvme_fw_commit_args *args)
+{
+       struct nvme_mi_admin_resp_hdr resp_hdr;
+       struct nvme_mi_admin_req_hdr req_hdr;
+       struct nvme_mi_resp resp;
+       struct nvme_mi_req req;
+       int rc;
+
+       if (args->args_size < sizeof(*args))
+               return -EINVAL;
+
+       nvme_mi_admin_init_req(&req, &req_hdr, ctrl->id,
+                              nvme_admin_fw_commit);
+
+       req_hdr.cdw10 = cpu_to_le32(((args->bpid & 0x1) << 31) |
+                                   ((args->action & 0x7) << 3) |
+                                   ((args->slot & 0x7) << 0));
+
+       nvme_mi_calc_req_mic(&req);
+
+       nvme_mi_admin_init_resp(&resp, &resp_hdr);
+
+       rc = nvme_mi_submit(ctrl->ep, &req, &resp);
+       if (rc)
+               return rc;
+
+       return nvme_mi_admin_parse_status(&resp, NULL);
+}
+
 int nvme_mi_admin_format_nvm(nvme_mi_ctrl_t ctrl,
                             struct nvme_format_nvm_args *args)
 {
index 2ec3e97f40036543cc0c7ee4c82165c66f292887..83e09a2a3e2373ecabba183dcac6601a8d0ae2d4 100644 (file)
@@ -2344,6 +2344,42 @@ static inline int nvme_mi_admin_ns_detach_ctrls(nvme_mi_ctrl_t ctrl, __u32 nsid,
        return nvme_mi_admin_ns_attach(ctrl, &args);
 }
 
+/**
+ * nvme_mi_admin_fw_download() - Download part or all of a firmware image to
+ * the controller
+ * @ctrl: Controller to send firmware data to
+ * @args: &struct nvme_fw_download_args argument structure
+ *
+ * The Firmware Image Download command downloads all or a portion of an image
+ * for a future update to the controller. The Firmware Image Download command
+ * downloads a new image (in whole or in part) to the controller.
+ *
+ * The image may be constructed of multiple pieces that are individually
+ * downloaded with separate Firmware Image Download commands. Each Firmware
+ * Image Download command includes a Dword Offset and Number of Dwords that
+ * specify a dword range.
+ *
+ * The new firmware image is not activated as part of the Firmware Image
+ * Download command. Use the nvme_mi_admin_fw_commit() to activate a newly
+ * downloaded image.
+ *
+ * Return: 0 on success, non-zero on failure
+ */
+int nvme_mi_admin_fw_download(nvme_mi_ctrl_t ctrl,
+                             struct nvme_fw_download_args *args);
+
+/**
+ * nvme_mi_admin_fw_commit() - Commit firmware using the specified action
+ * @ctrl: Controller to send firmware data to
+ * @args: &struct nvme_fw_download_args argument structure
+ *
+ * The Firmware Commit command modifies the firmware image or Boot Partitions.
+ *
+ * Return: 0 on success, non-zero on failure
+ */
+int nvme_mi_admin_fw_commit(nvme_mi_ctrl_t ctrl,
+                           struct nvme_fw_commit_args *args);
+
 /**
  * nvme_mi_admin_format_nvm() - Format NVMe namespace
  * @ctrl: Controller to send command to
index bee35135498e05e9bf10b5eed69987ba3eac7c47..b9cffa33876fed85598933d8170bea3fbe10c1cf 100644 (file)
--- a/test/mi.c
+++ b/test/mi.c
@@ -1396,6 +1396,167 @@ static void test_admin_ns_detach(struct nvme_mi_ep *ep)
        assert(!rc);
 }
 
+struct fw_download_info {
+       uint32_t offset;
+       uint32_t len;
+       void *data;
+};
+
+static int test_admin_fw_download_cb(struct nvme_mi_ep *ep,
+                                    struct nvme_mi_req *req,
+                                    struct nvme_mi_resp *resp,
+                                    void *data)
+{
+       struct fw_download_info *info = data;
+       __u32 len, off;
+       __u8 *rq_hdr;
+
+       rq_hdr = (__u8 *)req->hdr;
+       assert(rq_hdr[4] == nvme_admin_fw_download);
+
+       len = rq_hdr[47] << 24 | rq_hdr[46] << 16 | rq_hdr[45] << 8 | rq_hdr[44];
+       off = rq_hdr[51] << 24 | rq_hdr[50] << 16 | rq_hdr[49] << 8 | rq_hdr[48];
+
+       assert(off << 2 == info->offset);
+       assert(((len+1) << 2) == info->len);
+
+       /* ensure that the request len matches too */
+       assert(req->data_len == info->len);
+
+       assert(!memcmp(req->data, info->data, len));
+
+       test_transport_resp_calc_mic(resp);
+
+       return 0;
+}
+
+static void test_admin_fw_download(struct nvme_mi_ep *ep)
+{
+       struct nvme_fw_download_args args;
+       struct fw_download_info info;
+       unsigned char fw[4096];
+       nvme_mi_ctrl_t ctrl;
+       int rc, i;
+
+       for (i = 0; i < sizeof(fw); i++)
+               fw[i] = i % 0xff;
+
+       info.offset = 0;
+       info.len = 0;
+       info.data = fw;
+       args.data = fw;
+       args.args_size = sizeof(args);
+
+       test_set_transport_callback(ep, test_admin_fw_download_cb, &info);
+
+       ctrl = nvme_mi_init_ctrl(ep, 5);
+       assert(ctrl);
+
+       /* invalid (zero) len */
+       args.data_len = info.len = 1;
+       args.offset = info.offset = 0;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(rc);
+
+       /* invalid (unaligned) len */
+       args.data_len = info.len = 1;
+       args.offset = info.offset = 0;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(rc);
+
+       /* invalid offset */
+       args.data_len = info.len = 4;
+       args.offset = info.offset = 1;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(rc);
+
+       /* smallest len */
+       args.data_len = info.len = 4;
+       args.offset = info.offset = 0;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(!rc);
+
+       /* largest len */
+       args.data_len = info.len = 4096;
+       args.offset = info.offset = 0;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(!rc);
+
+       /* offset value */
+       args.data_len = info.len = 4096;
+       args.offset = info.offset = 4096;
+       rc = nvme_mi_admin_fw_download(ctrl, &args);
+       assert(!rc);
+}
+
+struct fw_commit_info {
+       __u8 bpid;
+       __u8 action;
+       __u8 slot;
+};
+
+static int test_admin_fw_commit_cb(struct nvme_mi_ep *ep,
+                                  struct nvme_mi_req *req,
+                                  struct nvme_mi_resp *resp,
+                                  void *data)
+{
+       struct fw_commit_info *info = data;
+       __u8 bpid, action, slot;
+       __u8 *rq_hdr;
+
+       rq_hdr = (__u8 *)req->hdr;
+       assert(rq_hdr[4] == nvme_admin_fw_commit);
+
+       bpid = (rq_hdr[47] >> 7) & 0x1;
+       slot = rq_hdr[44] & 0x7;
+       action = (rq_hdr[44] >> 3) & 0x7;
+
+       assert(!!bpid == !!info->bpid);
+       assert(slot == info->slot);
+       assert(action == info->action);
+
+       test_transport_resp_calc_mic(resp);
+
+       return 0;
+}
+
+static void test_admin_fw_commit(struct nvme_mi_ep *ep)
+{
+       struct nvme_fw_commit_args args;
+       struct fw_commit_info info;
+       nvme_mi_ctrl_t ctrl;
+       int rc;
+
+       args.args_size = sizeof(args);
+       info.bpid = args.bpid = 0;
+
+       test_set_transport_callback(ep, test_admin_fw_commit_cb, &info);
+
+       ctrl = nvme_mi_init_ctrl(ep, 5);
+       assert(ctrl);
+
+       /* all zeros */
+       info.bpid = args.bpid = 0;
+       info.slot = args.slot = 0;
+       info.action = args.action = 0;
+       rc = nvme_mi_admin_fw_commit(ctrl, &args);
+       assert(!rc);
+
+       /* all ones */
+       info.bpid = args.bpid = 1;
+       info.slot = args.slot = 0x7;
+       info.action = args.action = 0x7;
+       rc = nvme_mi_admin_fw_commit(ctrl, &args);
+       assert(!rc);
+
+       /* correct fields */
+       info.bpid = args.bpid = 1;
+       info.slot = args.slot = 2;
+       info.action = args.action = 3;
+       rc = nvme_mi_admin_fw_commit(ctrl, &args);
+       assert(!rc);
+}
+
 struct format_data {
        __u32 nsid;
        __u8 lbafu;
@@ -1573,6 +1734,8 @@ struct test {
        DEFINE_TEST(admin_ns_mgmt_delete),
        DEFINE_TEST(admin_ns_attach),
        DEFINE_TEST(admin_ns_detach),
+       DEFINE_TEST(admin_fw_download),
+       DEFINE_TEST(admin_fw_commit),
        DEFINE_TEST(admin_format_nvm),
        DEFINE_TEST(admin_sanitize_nvm),
 };