From: Jeremy Kerr Date: Sat, 24 Sep 2022 06:44:36 +0000 (+0800) Subject: mi: Add firmware download and commit commands X-Git-Tag: v1.2~24^2 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=ac5f00a76938cbf5c3fff05cca3dfa390c29c013;p=users%2Fsagi%2Flibnvme.git mi: Add firmware download and commit commands 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 --- diff --git a/src/libnvme-mi.map b/src/libnvme-mi.map index 37a38fa9..3a318fa2 100644 --- a/src/libnvme-mi.map +++ b/src/libnvme-mi.map @@ -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 { diff --git a/src/nvme/mi.c b/src/nvme/mi.c index 7b7ae020..8fd1a15d 100644 --- a/src/nvme/mi.c +++ b/src/nvme/mi.c @@ -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) { diff --git a/src/nvme/mi.h b/src/nvme/mi.h index 2ec3e97f..83e09a2a 100644 --- a/src/nvme/mi.h +++ b/src/nvme/mi.h @@ -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 diff --git a/test/mi.c b/test/mi.c index bee35135..b9cffa33 100644 --- 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), };