From: Jeremy Kerr Date: Tue, 12 Oct 2021 03:13:58 +0000 (+0800) Subject: mi: Add MI configuration commands X-Git-Tag: v1.1-rc0~12^2~1 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=3db872eeb22374c2cf88d996f6ff27cf17d38ffb;p=users%2Fsagi%2Flibnvme.git mi: Add MI configuration commands This change adds an API to send MI Get / Set Configuration commands to an endpoint, allowing control of endpoint SMBus frequenct, MCTP MTU and clearing the subsystem health status bits. We do this with a new couple of functions: int nvme_mi_mi_config_get(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1, __u32 *nmresp) int nvme_mi_mi_config_set(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1) The dw0, dw1 and nmresp formats depend on the type of configuration accessed, which can be a little opaque. Se we add a bunch of helpers too, to get/set the three configuration params: nvme_mi_mi_config_get_smbus_freq(...); nvme_mi_mi_config_set_smbus_freq(...); nvme_mi_mi_config_get_mctp_mtu(...); nvme_mi_mi_config_set_mctp_mtu(...); nvme_mi_mi_config_set_health_status_change(...); [there's no getter for the latter, as this just clears polled bits] Signed-off-by: Jeremy Kerr --- diff --git a/src/libnvme-mi.map b/src/libnvme-mi.map index 2ad3478c..33447e90 100644 --- a/src/libnvme-mi.map +++ b/src/libnvme-mi.map @@ -5,6 +5,8 @@ LIBNVME_MI_1_1 { nvme_mi_init_ctrl; nvme_mi_close_ctrl; nvme_mi_close; + nvme_mi_mi_config_get; + nvme_mi_mi_config_set; nvme_mi_mi_read_mi_data_subsys; nvme_mi_mi_read_mi_data_port; nvme_mi_mi_read_mi_data_ctrl_list; diff --git a/src/nvme/mi.c b/src/nvme/mi.c index cbed80ba..b804f22b 100644 --- a/src/nvme/mi.c +++ b/src/nvme/mi.c @@ -732,6 +732,77 @@ int nvme_mi_mi_subsystem_health_status_poll(nvme_mi_ep_t ep, bool clear, return 0; } +int nvme_mi_mi_config_get(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1, + __u32 *nmresp) +{ + struct nvme_mi_mi_resp_hdr resp_hdr; + struct nvme_mi_mi_req_hdr req_hdr; + struct nvme_mi_resp resp; + struct nvme_mi_req req; + int rc; + + memset(&req_hdr, 0, sizeof(req_hdr)); + req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME; + req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3); + req_hdr.opcode = nvme_mi_mi_opcode_configuration_get; + req_hdr.cdw0 = cpu_to_le32(dw0); + req_hdr.cdw1 = cpu_to_le32(dw1); + + memset(&req, 0, sizeof(req)); + req.hdr = &req_hdr.hdr; + req.hdr_len = sizeof(req_hdr); + + memset(&resp, 0, sizeof(resp)); + resp.hdr = &resp_hdr.hdr; + resp.hdr_len = sizeof(resp_hdr); + + rc = nvme_mi_submit(ep, &req, &resp); + if (rc) + return rc; + + if (resp_hdr.status) + return resp_hdr.status; + + *nmresp = resp_hdr.nmresp[0] | + resp_hdr.nmresp[1] << 8 | + resp_hdr.nmresp[2] << 16; + + return 0; +} + +int nvme_mi_mi_config_set(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1) +{ + struct nvme_mi_mi_resp_hdr resp_hdr; + struct nvme_mi_mi_req_hdr req_hdr; + struct nvme_mi_resp resp; + struct nvme_mi_req req; + int rc; + + memset(&req_hdr, 0, sizeof(req_hdr)); + req_hdr.hdr.type = NVME_MI_MSGTYPE_NVME; + req_hdr.hdr.nmp = (NVME_MI_ROR_REQ << 7) | (NVME_MI_MT_MI << 3); + req_hdr.opcode = nvme_mi_mi_opcode_configuration_set; + req_hdr.cdw0 = cpu_to_le32(dw0); + req_hdr.cdw1 = cpu_to_le32(dw1); + + memset(&req, 0, sizeof(req)); + req.hdr = &req_hdr.hdr; + req.hdr_len = sizeof(req_hdr); + + memset(&resp, 0, sizeof(resp)); + resp.hdr = &resp_hdr.hdr; + resp.hdr_len = sizeof(resp_hdr); + + rc = nvme_mi_submit(ep, &req, &resp); + if (rc) + return rc; + + if (resp_hdr.status) + return resp_hdr.status; + + return 0; +} + void nvme_mi_close(nvme_mi_ep_t ep) { struct nvme_mi_ctrl *ctrl, *tmp; diff --git a/src/nvme/mi.h b/src/nvme/mi.h index 9e71e863..17444ddb 100644 --- a/src/nvme/mi.h +++ b/src/nvme/mi.h @@ -164,10 +164,14 @@ struct nvme_mi_msg_resp { * enum nvme_mi_mi_opcode - Operation code for supported NVMe-MI commands. * @nvme_mi_mi_opcode_mi_data_read: Read NVMe-MI Data Structure * @nvme_mi_mi_opcode_subsys_health_status_poll: Subsystem Health Status Poll + * @nvme_mi_mi_opcode_configuration_set: MI Configuration Set + * @nvme_mi_mi_opcode_configuration_get: MI Configuration Get */ enum nvme_mi_mi_opcode { nvme_mi_mi_opcode_mi_data_read = 0x00, nvme_mi_mi_opcode_subsys_health_status_poll = 0x01, + nvme_mi_mi_opcode_configuration_set = 0x03, + nvme_mi_mi_opcode_configuration_get = 0x04, }; /** @@ -643,6 +647,171 @@ int nvme_mi_mi_read_mi_data_ctrl(nvme_mi_ep_t ep, __u16 ctrl_id, int nvme_mi_mi_subsystem_health_status_poll(nvme_mi_ep_t ep, bool clear, struct nvme_mi_nvm_ss_health_status *nshds); +/** + * nvme_mi_mi_config_get - query a configuration parameter + * @ep: endpoint for MI communication + * @dw0: management doubleword 0, containing configuration identifier, plus + * config-specific fields + * @dw1: management doubleword 0, config-specific. + * @nmresp: set to queried configuration data in NMRESP field of response. + * + * Performs a MI Configuration Get command, with the configuration identifier + * as the LSB of @dw0. Other @dw0 and @dw1 data is configuration-identifier + * specific. + * + * On a sucessful Configuration Get, the @nmresp pointer will be populated with + * the bytes from the 3-byte NMRESP field, converted to native endian. + * + * See &enum nvme_mi_config_id for identifiers. + * + * Return: 0 on success, non-zero on failure. + */ +int nvme_mi_mi_config_get(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1, + __u32 *nmresp); + +/** + * nvme_mi_mi_config_set - set a configuration parameter + * @ep: endpoint for MI communication + * @dw0: management doubleword 0, containing configuration identifier, plus + * config-specific fields + * @dw1: management doubleword 0, config-specific. + * + * Performs a MI Configuration Set command, with the command as the LSB of + * @dw0. Other @dw0 and @dw1 data is configuration-identifier specific. + * + * See &enum nvme_mi_config_id for identifiers. + * + * Return: 0 on success, non-zero on failure. + */ +int nvme_mi_mi_config_set(nvme_mi_ep_t ep, __u32 dw0, __u32 dw1); + +/** + * nvme_mi_mi_config_get_smbus_freq - get configuraton: SMBus port frequency + * @ep: endpoint for MI communication + * @port: port ID to query + * @freq: output value for current frequency configuration + * + * Performs a MI Configuration Get, to query the current SMBus frequency of + * the port specified in @port. On success, populates @freq with the port + * frequency + * + * Return: 0 on success, non-zero on failure. + */ +static inline int nvme_mi_mi_config_get_smbus_freq(nvme_mi_ep_t ep, __u8 port, + enum nvme_mi_config_smbus_freq *freq) +{ + __u32 tmp, dw0; + int rc; + + dw0 = port << 24 | NVME_MI_CONFIG_SMBUS_FREQ; + + rc = nvme_mi_mi_config_get(ep, dw0, 0, &tmp); + if (!rc) + *freq = tmp & 0x3; + return rc; +} + +/** + * nvme_mi_mi_config_set_smbus_freq - set configuraton: SMBus port frequency + * @ep: endpoint for MI communication + * @port: port ID to set + * @freq: new frequency configuration + * + * Performs a MI Configuration Set, to update the current SMBus frequency of + * the port specified in @port. + * + * See &struct nvme_mi_read_port_info for the maximum supported SMBus frequency + * for the port. + * + * Return: 0 on success, non-zero on failure. + */ +static inline int nvme_mi_mi_config_set_smbus_freq(nvme_mi_ep_t ep, __u8 port, + enum nvme_mi_config_smbus_freq freq) +{ + __u32 dw0 = port << 24 | + (freq & 0x3) << 8 | + NVME_MI_CONFIG_SMBUS_FREQ; + + return nvme_mi_mi_config_set(ep, dw0, 0); +} + +/** + * nvme_mi_mi_config_set_health_status_change - clear CCS bits in health status + * @ep: endpoint for MI communication + * @mask: bitmask to clear + * + * Performs a MI Configuration Set, to update the current health status poll + * values of the Composite Controller Status bits. Bits set in @mask will + * be cleared from future health status poll data, and may be re-triggered by + * a future health change event. + * + * See &nvme_mi_mi_subsystem_health_status_poll(), &enum nvme_mi_ccs for + * values in @mask. + * + * Return: 0 on success, non-zero on failure. + */ +static inline int nvme_mi_mi_config_set_health_status_change(nvme_mi_ep_t ep, + __u32 mask) +{ + return nvme_mi_mi_config_set(ep, NVME_MI_CONFIG_HEALTH_STATUS_CHANGE, + mask); +} + +/** + * nvme_mi_mi_config_get_mctp_mtu - get configuraton: MCTP MTU + * @ep: endpoint for MI communication + * @port: port ID to query + * @mtu: output value for current MCTP MTU configuration + * + * Performs a MI Configuration Get, to query the current MCTP Maximum + * Transmission Unit size (MTU) of the port specified in @port. On success, + * populates @mtu with the MTU. + * + * The default reset value is 64, corresponding to the MCTP baseline MTU. + * + * Some controllers may also use this as the maximum receive unit size, and + * may not accept MCTP messages larger than the configured MTU. + * + * Return: 0 on success, non-zero on failure. + */ +static inline int nvme_mi_mi_config_get_mctp_mtu(nvme_mi_ep_t ep, __u8 port, + __u16 *mtu) +{ + __u32 tmp, dw0; + int rc; + + dw0 = port << 24 | NVME_MI_CONFIG_MCTP_MTU; + + rc = nvme_mi_mi_config_get(ep, dw0, 0, &tmp); + if (!rc) + *mtu = tmp & 0xffff; + return rc; +} + +/** + * nvme_mi_mi_config_set_mctp_mtu - set configuraton: MCTP MTU + * @ep: endpoint for MI communication + * @port: port ID to set + * @mtu: new MTU configuration + * + * Performs a MI Configuration Set, to update the current MCTP MTU value for + * the port specified in @port. + * + * Some controllers may also use this as the maximum receive unit size, and + * may not accept MCTP messages larger than the configured MTU. When setting + * this value, you will likely need to change the MTU of the local MCTP + * interface(s) to match. + * + * Return: 0 on success, non-zero on failure. + */ +static inline int nvme_mi_mi_config_set_mctp_mtu(nvme_mi_ep_t ep, __u8 port, + __u16 mtu) +{ + __u32 dw0 = port << 24 | NVME_MI_CONFIG_MCTP_MTU; + + return nvme_mi_mi_config_set(ep, dw0, mtu); +} + /* Admin channel functions */ /** diff --git a/test/mi.c b/test/mi.c index 2cc7dde2..d269060f 100644 --- a/test/mi.c +++ b/test/mi.c @@ -643,6 +643,110 @@ static void test_resp_csi(nvme_mi_ep_t ep) assert(rc != 0); } +/* test: config get MTU request & response layout, ensure we're handling + * endianness in the 3-byte NMRESP field correctly */ +static int test_mi_config_get_mtu_cb(struct nvme_mi_ep *ep, + struct nvme_mi_req *req, + struct nvme_mi_resp *resp, + void *data) +{ + struct nvme_mi_mi_resp_hdr *mi_resp; + uint8_t *buf; + + assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr)); + assert(req->data_len == 0); + + /* validate req as raw bytes */ + buf = (void *)req->hdr; + assert(buf[4] == nvme_mi_mi_opcode_configuration_get); + /* dword 0: port and config id */ + assert(buf[11] == 0x5); + assert(buf[8] == NVME_MI_CONFIG_MCTP_MTU); + + /* set MTU in response */ + mi_resp = (void *)resp->hdr; + mi_resp->nmresp[1] = 0x12; + mi_resp->nmresp[0] = 0x34; + resp->hdr_len = sizeof(*mi_resp); + resp->data_len = 0; + + test_transport_resp_calc_mic(resp); + return 0; +} + +static void test_mi_config_get_mtu(nvme_mi_ep_t ep) +{ + uint16_t mtu; + int rc; + + test_set_transport_callback(ep, test_mi_config_get_mtu_cb, NULL); + + rc = nvme_mi_mi_config_get_mctp_mtu(ep, 5, &mtu); + assert(rc == 0); + assert(mtu == 0x1234); +} + +/* test: config set SMBus freq, both valid and invalid */ +static int test_mi_config_set_freq_cb(struct nvme_mi_ep *ep, + struct nvme_mi_req *req, + struct nvme_mi_resp *resp, + void *data) +{ + struct nvme_mi_mi_resp_hdr *mi_resp; + uint8_t *buf; + + assert(req->hdr_len == sizeof(struct nvme_mi_mi_req_hdr)); + assert(req->data_len == 0); + + /* validate req as raw bytes */ + buf = (void *)req->hdr; + assert(buf[4] == nvme_mi_mi_opcode_configuration_set); + /* dword 0: port and config id */ + assert(buf[11] == 0x5); + assert(buf[8] == NVME_MI_CONFIG_SMBUS_FREQ); + + mi_resp = (void *)resp->hdr; + resp->hdr_len = sizeof(*mi_resp); + resp->data_len = 0; + + /* accept 100 & 400, reject others */ + switch (buf[9]) { + case NVME_MI_CONFIG_SMBUS_FREQ_100kHz: + case NVME_MI_CONFIG_SMBUS_FREQ_400kHz: + mi_resp->status = 0; + break; + case NVME_MI_CONFIG_SMBUS_FREQ_1MHz: + default: + mi_resp->status = 0x4; + break; + } + + test_transport_resp_calc_mic(resp); + return 0; +} + +static void test_mi_config_set_freq(nvme_mi_ep_t ep) +{ + int rc; + + test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL); + + rc = nvme_mi_mi_config_set_smbus_freq(ep, 5, + NVME_MI_CONFIG_SMBUS_FREQ_100kHz); + assert(rc == 0); +} + +static void test_mi_config_set_freq_invalid(nvme_mi_ep_t ep) +{ + int rc; + + test_set_transport_callback(ep, test_mi_config_set_freq_cb, NULL); + + rc = nvme_mi_mi_config_set_smbus_freq(ep, 5, + NVME_MI_CONFIG_SMBUS_FREQ_1MHz); + assert(rc == 4); +} + #define DEFINE_TEST(name) { #name, test_ ## name } struct test { const char *name; @@ -662,6 +766,9 @@ struct test { DEFINE_TEST(resp_hdr_small), DEFINE_TEST(resp_invalid_type), DEFINE_TEST(resp_csi), + DEFINE_TEST(mi_config_get_mtu), + DEFINE_TEST(mi_config_set_freq), + DEFINE_TEST(mi_config_set_freq_invalid), }; static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep)