From 15eea0d56d53015481132d3c57a27e2b52d14df5 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Thu, 11 Dec 2025 23:54:47 -0800 Subject: [PATCH 01/10] lib: Use PATH_MAX for path buffers ... instead of hardcoding values, avoiding potential truncation issues. Signed-off-by: Davidlohr Bueso --- src/cxlmi/cxlmi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cxlmi/cxlmi.c b/src/cxlmi/cxlmi.c index 6891378..0cdbd49 100644 --- a/src/cxlmi/cxlmi.c +++ b/src/cxlmi/cxlmi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -1532,7 +1533,7 @@ CXLMI_EXPORT struct cxlmi_endpoint *cxlmi_open(struct cxlmi_ctx *ctx, { struct cxlmi_endpoint *ep, *tmp; int errno_save; - char filename[40]; + char filename[PATH_MAX]; /* ensure no duplicates */ cxlmi_for_each_endpoint(ctx, tmp) { @@ -1593,7 +1594,7 @@ CXLMI_EXPORT int cxlmi_scan(struct cxlmi_ctx *ctx) while ((entry = readdir(dir)) != NULL) { struct cxlmi_endpoint *ep; struct stat statbuf; - char fullpath[512]; + char fullpath[PATH_MAX]; /* Skip . and .. */ if (entry->d_name[0] == '.') From 3b0a2c0c10d4af3ee809b44c86b990f78590987c Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Fri, 12 Dec 2025 00:03:46 -0800 Subject: [PATCH 02/10] lib/log: Remove unused global variable Removed the unused static struct cxlmi_ctx *ctx; declaration and its dead-code reference (if (!c) c = ctx;) from log.c. Since ctx was never assigned a value, the fallback was ineffective. Signed-off-by: Davidlohr Bueso --- src/cxlmi/log.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cxlmi/log.c b/src/cxlmi/log.c index 043e1fe..9b6f7cf 100644 --- a/src/cxlmi/log.c +++ b/src/cxlmi/log.c @@ -22,8 +22,6 @@ #define LOG_CLOCK CLOCK_MONOTONIC #endif -static struct cxlmi_ctx *ctx; - void __attribute__((format(printf, 4, 5))) __cxlmi_msg(struct cxlmi_ctx *c, int lvl, const char *func, const char *format, ...) @@ -46,8 +44,6 @@ __cxlmi_msg(struct cxlmi_ctx *c, int lvl, _cleanup_free_ char *message = NULL; int idx = 0; - if (!c) - c = ctx; if (c) fp = c->fp; From e73151d2514f964cd24bfbe544e6ef173e637814 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Fri, 12 Dec 2025 19:36:57 -0800 Subject: [PATCH 03/10] lib: Get Log and Sanitize fixlets 1. In cxlmi_cmd_get_log() in->length already specifies the number of bytes to read from the log, not the number of struct cxlmi_cmd_get_log_rsp entries. Multiplying by sizeof(*rsp_pl) (which is 4 bytes) may cause a buffer overflow. According to the CXL spec, the Get Log command's length field specifies the number of bytes to transfer, and the response is raw log data of that length. The fix is to simply copy in->length bytes. 2. Fix conflicting names for Sanitize operations. Signed-off-by: Davidlohr Bueso --- src/cxlmi/commands.c | 4 ++-- src/cxlmi/private.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cxlmi/commands.c b/src/cxlmi/commands.c index 12b08fb..6380faa 100644 --- a/src/cxlmi/commands.c +++ b/src/cxlmi/commands.c @@ -632,7 +632,7 @@ CXLMI_EXPORT int cxlmi_cmd_get_log(struct cxlmi_endpoint *ep, return rc; rsp_pl = (void *)rsp->payload; - memcpy(ret, rsp_pl, in->length * sizeof(*rsp_pl)); + memcpy(ret, rsp_pl, in->length); return rc; } @@ -1520,7 +1520,7 @@ CXLMI_EXPORT int cxlmi_cmd_memdev_sanitize(struct cxlmi_endpoint *ep, { struct cxlmi_cci_msg req, rsp; - arm_cci_request(ep, &req, 0, SANITIZE, SANITIZE); + arm_cci_request(ep, &req, 0, SANITIZE, SANITIZE_OP); return send_cmd_cci(ep, ti, &req, sizeof(req), &rsp, sizeof(rsp), sizeof(rsp)); diff --git a/src/cxlmi/private.h b/src/cxlmi/private.h index ea6705b..818451b 100644 --- a/src/cxlmi/private.h +++ b/src/cxlmi/private.h @@ -233,7 +233,7 @@ enum { #define SCAN_MEDIA 0x4 #define GET_SCAN_MEDIA_RESULTS 0x5 SANITIZE = 0x44, - #define SANITIZE 0x0 + #define SANITIZE_OP 0x0 #define SECURE_ERASE 0x1 #define MEDIA_OPERATIONS 0x2 PERSISTENT_MEM = 0x45, From 49b94c91e6a685c492a58b51a8e74d966d3e0435 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Sat, 13 Dec 2025 17:45:52 -0800 Subject: [PATCH 04/10] lib/commands: fix CCI payload length for variable-length requests The arm_cci_request() function sets the pl_length field in the CCI message header, which must contain only the payload size (data in the payload[] flexible array), not including the CCI message header itself. Several commands were passing incorrect payload sizes: 1. Commands passing req_sz instead of payload size: - get_feature: was passing req_sz (header + payload) instead of just sizeof(*req_pl) - set_feature: same issue, now correctly includes feature_data_sz 2. Commands not accounting for variable-length data: - clear_event_records: now includes handles array size - transfer_fw: now includes firmware data size - set_lsa: now includes LSA data size - media_operations_sanitize: now includes DPA range list size - add_dc_response: now includes extent list size - release_dc: now includes extent list size - send_ld_cxlio_mem_request: now includes data length - set_ld_allocations: now includes allocation list size - set_qos_allocated_bw: now includes QoS fraction array size - set_qos_bw_limit: now includes QoS limit fraction array size ... and some other fixes: 3. Logic error: - vendor_specific: condition was inverted (in ? 0 : in_size should be in ? in_size : 0) 4. Export cxlmi_cmd_fmapi_get_head_info() which was not in the main header file. 5. Add the extra data size parameters for transfer fw, set lsa and security send. Signed-off-by: Davidlohr Bueso --- src/cxlmi/commands.c | 121 +++++++++++++++++++++++++------------------ src/libcxlmi.h | 12 +++-- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/cxlmi/commands.c b/src/cxlmi/commands.c index 6380faa..0afd8c3 100644 --- a/src/cxlmi/commands.c +++ b/src/cxlmi/commands.c @@ -14,6 +14,12 @@ #define CXL_CAPACITY_MULTIPLIER (256 * 1024 * 1024) +/* Max poison records to retrieve - based on typical mailbox sizes */ +#define MAX_POISON_RECORDS 256 + +/* Max scan media error records - same as poison records */ +#define MAX_SCAN_MEDIA_RECORDS 256 + CXLMI_EXPORT int cxlmi_cmd_identify(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, struct cxlmi_cmd_identify_rsp *ret) @@ -242,7 +248,7 @@ CXLMI_EXPORT int cxlmi_cmd_clear_event_records(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), EVENTS, CLEAR_RECORDS); + arm_cci_request(ep, req, sizeof(*req_pl) + handles_sz, EVENTS, CLEAR_RECORDS); req_pl = (struct cxlmi_cmd_clear_event_records_req *)req->payload; req_pl->event_log = in->event_log; @@ -443,22 +449,23 @@ CXLMI_EXPORT int cxlmi_cmd_get_fw_info(struct cxlmi_endpoint *ep, ret->slots_supported = rsp_pl->slots_supported; ret->slot_info = rsp_pl->slot_info; ret->caps = rsp_pl->caps; - pstrcpy(ret->fw_rev1, sizeof(rsp_pl->fw_rev1), rsp_pl->fw_rev1); - pstrcpy(ret->fw_rev2, sizeof(rsp_pl->fw_rev2), rsp_pl->fw_rev2); - pstrcpy(ret->fw_rev3, sizeof(rsp_pl->fw_rev3), rsp_pl->fw_rev3); - pstrcpy(ret->fw_rev4, sizeof(rsp_pl->fw_rev4), rsp_pl->fw_rev4); + memcpy(ret->fw_rev1, rsp_pl->fw_rev1, sizeof(rsp_pl->fw_rev1)); + memcpy(ret->fw_rev2, rsp_pl->fw_rev2, sizeof(rsp_pl->fw_rev2)); + memcpy(ret->fw_rev3, rsp_pl->fw_rev3, sizeof(rsp_pl->fw_rev3)); + memcpy(ret->fw_rev4, rsp_pl->fw_rev4, sizeof(rsp_pl->fw_rev4)); return rc; } CXLMI_EXPORT int cxlmi_cmd_transfer_fw(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_transfer_fw_req *in) + struct cxlmi_cmd_transfer_fw_req *in, + size_t data_sz) { struct cxlmi_cmd_transfer_fw_req *req_pl; _cleanup_free_ struct cxlmi_cci_msg *req = NULL; struct cxlmi_cci_msg rsp; - ssize_t req_sz, data_sz = struct_size(in, data, 0); + ssize_t req_sz; int rc = -1; req_sz = sizeof(*req_pl) + data_sz + sizeof(*req); @@ -466,7 +473,7 @@ CXLMI_EXPORT int cxlmi_cmd_transfer_fw(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), FIRMWARE_UPDATE, TRANSFER); + arm_cci_request(ep, req, sizeof(*req_pl) + data_sz, FIRMWARE_UPDATE, TRANSFER); req_pl = (struct cxlmi_cmd_transfer_fw_req *)req->payload; req_pl->action = in->action; @@ -913,7 +920,7 @@ CXLMI_EXPORT int cxlmi_cmd_get_feature(struct cxlmi_endpoint *ep, req_pl->offset = cpu_to_le16(in->offset); req_pl->count = cpu_to_le16(in->count); req_pl->selection = in->selection; - arm_cci_request(ep, req, req_sz, FEATURES, GET_FEATURE); + arm_cci_request(ep, req, sizeof(*req_pl), FEATURES, GET_FEATURE); rsp_sz_min = sizeof(*rsp); rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl); @@ -950,7 +957,7 @@ CXLMI_EXPORT int cxlmi_cmd_set_feature(struct cxlmi_endpoint *ep, req_pl->offset = cpu_to_le16(in->offset); req_pl->version = in->version; memcpy(req_pl->feature_data, in->feature_data, feature_data_sz); - arm_cci_request(ep, req, req_sz, FEATURES, SET_FEATURE); + arm_cci_request(ep, req, sizeof(*req_pl) + feature_data_sz, FEATURES, SET_FEATURE); rsp_sz = sizeof(*rsp); rsp = calloc(1, rsp_sz); @@ -1113,22 +1120,25 @@ CXLMI_EXPORT int cxlmi_cmd_memdev_get_lsa(struct cxlmi_endpoint *ep, CXLMI_EXPORT int cxlmi_cmd_memdev_set_lsa(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_lsa_req *in) + struct cxlmi_cmd_memdev_set_lsa_req *in, + size_t data_sz) { struct cxlmi_cmd_memdev_set_lsa_req *req_pl; _cleanup_free_ struct cxlmi_cci_msg *req = NULL; struct cxlmi_cci_msg rsp; - size_t req_sz, data_sz = struct_size(in, data, 0); + size_t req_sz, req_pl_sz; - req_sz = sizeof(*req) + data_sz + sizeof(*in); + req_pl_sz = sizeof(*req_pl) + data_sz; + req_sz = sizeof(*req) + req_pl_sz; req = calloc(1, req_sz); if (!req) return -1; - arm_cci_request(ep, req, sizeof(*in), CCLS, SET_LSA); + arm_cci_request(ep, req, req_pl_sz, CCLS, SET_LSA); req_pl = (struct cxlmi_cmd_memdev_set_lsa_req *)req->payload; req_pl->offset = cpu_to_le32(in->offset); + memcpy(req_pl->data, in->data, data_sz); return send_cmd_cci(ep, ti, req, req_sz, &rsp, sizeof(rsp), sizeof(rsp)); @@ -1331,7 +1341,8 @@ cxlmi_cmd_get_poison_list(struct cxlmi_endpoint *ep, req_pl->get_poison_list_phy_addr = cpu_to_le64(in->get_poison_list_phy_addr); req_pl->get_poison_list_phy_addr_len = cpu_to_le64(in->get_poison_list_phy_addr_len); - rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl); + rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl) + + (MAX_POISON_RECORDS * sizeof(rsp_pl->records[0])); rsp = calloc(1, rsp_sz); if (!rsp) return -1; @@ -1348,7 +1359,7 @@ cxlmi_cmd_get_poison_list(struct cxlmi_endpoint *ep, le64_to_cpu(rsp_pl->overflow_timestamp); ret->more_err_media_record_cnt = le16_to_cpu(rsp_pl->more_err_media_record_cnt); - for (i = 0; i < rsp_pl->more_err_media_record_cnt; i++) { + for (i = 0; i < ret->more_err_media_record_cnt; i++) { ret->records[i].media_err_addr = le64_to_cpu(rsp_pl->records[i].media_err_addr); ret->records[i].media_err_len = @@ -1486,7 +1497,8 @@ CXLMI_EXPORT int cxlmi_cmd_get_scan_media_results(struct cxlmi_endpoint *ep, ssize_t rsp_sz; arm_cci_request(ep, &req, 0, MEDIA_AND_POISON, GET_SCAN_MEDIA_RESULTS); - rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl); + rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl) + + (MAX_SCAN_MEDIA_RECORDS * sizeof(rsp_pl->record[0])); rsp = calloc(1, rsp_sz); if (!rsp) @@ -1566,12 +1578,13 @@ int cxlmi_cmd_memdev_media_operations_discovery(struct cxlmi_endpoint *ep, req_pl->discovery_osa.start_index = cpu_to_le16(in->discovery_osa.start_index); req_pl->discovery_osa.num_ops = cpu_to_le16(in->discovery_osa.num_ops); - rsp_sz = sizeof(*rsp_pl) + sizeof(*rsp); + rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl) + + in->discovery_osa.num_ops * sizeof(*rsp_pl->entry); rsp = calloc(1, rsp_sz); if (!rsp) return -1; - rc = send_cmd_cci(ep, ti, req, req_sz, rsp, rsp_sz, rsp_sz); + rc = send_cmd_cci(ep, ti, req, req_sz, rsp, rsp_sz, sizeof(*rsp) + sizeof(*rsp_pl)); if (rc) return rc; @@ -1598,15 +1611,17 @@ int cxlmi_cmd_memdev_media_operations_sanitize(struct cxlmi_endpoint *ep, struct cxlmi_cmd_memdev_media_operations_sanitize_req *req_pl; _cleanup_free_ struct cxlmi_cci_msg *req = NULL; struct cxlmi_cci_msg rsp; - size_t req_sz; + size_t req_sz, req_pl_sz; int i; - req_sz = sizeof(*req) + sizeof(*in); + req_pl_sz = sizeof(*req_pl) + + in->dpa_range_count * sizeof(*in->dpa_range_list); + req_sz = sizeof(*req) + req_pl_sz; req = calloc(1, req_sz); if (!req) return -1; - arm_cci_request(ep, req, sizeof(*in), SANITIZE, MEDIA_OPERATIONS); + arm_cci_request(ep, req, req_pl_sz, SANITIZE, MEDIA_OPERATIONS); req_pl = (struct cxlmi_cmd_memdev_media_operations_sanitize_req *)req->payload; req_pl->media_operation_class = in->media_operation_class; @@ -1774,33 +1789,29 @@ cxlmi_cmd_memdev_passphrase_secure_erase(struct cxlmi_endpoint *ep, CXLMI_EXPORT int cxlmi_cmd_memdev_security_send(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_security_send_req *in) + struct cxlmi_cmd_memdev_security_send_req *in, + size_t data_sz) { - struct cxlmi_cmd_memdev_security_send_req *req_pl; - _cleanup_free_ struct cxlmi_cci_msg *req = NULL; - struct cxlmi_cci_msg rsp; - size_t req_sz; - size_t in_data_size; - size_t full_in_payload_size; - - in_data_size = struct_size(in, data, 0); - - full_in_payload_size = sizeof(*in) + in_data_size; + struct cxlmi_cmd_memdev_security_send_req *req_pl; + _cleanup_free_ struct cxlmi_cci_msg *req = NULL; + struct cxlmi_cci_msg rsp; + size_t req_sz, req_pl_sz; - req_sz = sizeof(*req) + full_in_payload_size; - req = calloc(1, req_sz); - if (!req) - return -1; + req_pl_sz = sizeof(*req_pl) + data_sz; + req_sz = sizeof(*req) + req_pl_sz; + req = calloc(1, req_sz); + if (!req) + return -1; - arm_cci_request(ep, req, full_in_payload_size, SECURITY, SECURITY_SEND); + arm_cci_request(ep, req, req_pl_sz, SECURITY, SECURITY_SEND); - req_pl = (struct cxlmi_cmd_memdev_security_send_req *)req->payload; + req_pl = (struct cxlmi_cmd_memdev_security_send_req *)req->payload; - req_pl->security_protocol = in->security_protocol; - req_pl->sp_specific = cpu_to_le16(in->sp_specific); - memcpy(req_pl->data, in->data, in_data_size); + req_pl->security_protocol = in->security_protocol; + req_pl->sp_specific = cpu_to_le16(in->sp_specific); + memcpy(req_pl->data, in->data, data_sz); - return send_cmd_cci(ep, ti, req, req_sz, &rsp, sizeof(rsp), sizeof(rsp)); + return send_cmd_cci(ep, ti, req, req_sz, &rsp, sizeof(rsp), sizeof(rsp)); } /* @@ -2034,7 +2045,9 @@ CXLMI_EXPORT int cxlmi_cmd_memdev_add_dc_response(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), DCD_CONFIG, ADD_DYN_CAP_RSP); + arm_cci_request(ep, req, sizeof(*req_pl) + + in->updated_extent_list_size * sizeof(in->extents[0]), + DCD_CONFIG, ADD_DYN_CAP_RSP); req_pl = (struct cxlmi_cmd_memdev_add_dc_response_req *)req->payload; req_pl->updated_extent_list_size = cpu_to_le32(in->updated_extent_list_size); req_pl->flags = in->flags; @@ -2063,7 +2076,9 @@ CXLMI_EXPORT int cxlmi_cmd_memdev_release_dc(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), DCD_CONFIG, RELEASE_DYN_CAP); + arm_cci_request(ep, req, sizeof(*req_pl) + + in->updated_extent_list_size * sizeof(in->extents[0]), + DCD_CONFIG, RELEASE_DYN_CAP); req_pl = (struct cxlmi_cmd_memdev_release_dc_req *)req->payload; req_pl->updated_extent_list_size = cpu_to_le32(in->updated_extent_list_size); req_pl->flags = in->flags; @@ -2517,7 +2532,7 @@ int cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), MLD_PORT, SEND_LD_CXLIO_MEM_REQ); + arm_cci_request(ep, req, sizeof(*req_pl) + datalen, MLD_PORT, SEND_LD_CXLIO_MEM_REQ); req_pl = (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req *)req->payload; req_pl->port_id = in->port_id; @@ -2601,7 +2616,8 @@ CXLMI_EXPORT int cxlmi_cmd_fmapi_get_ld_allocations(struct cxlmi_endpoint *ep, req_pl->start_ld_id = in->start_ld_id; req_pl->ld_allocation_list_limit = in->ld_allocation_list_limit; - rsp_sz = sizeof(*rsp_pl) + sizeof(*rsp); + rsp_sz = sizeof(*rsp) + sizeof(*rsp_pl) + + in->ld_allocation_list_limit * sizeof(*rsp_pl->ld_allocation_list); rsp = calloc(1, rsp_sz); if (!rsp) return -1; @@ -2645,7 +2661,8 @@ CXLMI_EXPORT int cxlmi_cmd_fmapi_set_ld_allocations(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), + arm_cci_request(ep, req, sizeof(*req_pl) + + in->number_ld * sizeof(*in->ld_allocation_list), MLD_COMPONENTS, SET_LD_ALLOCATIONS); req_pl = (struct cxlmi_cmd_fmapi_set_ld_allocations_req *)req->payload; @@ -2867,7 +2884,8 @@ CXLMI_EXPORT int cxlmi_cmd_fmapi_set_qos_allocated_bw(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), + arm_cci_request(ep, req, sizeof(*req_pl) + + in->number_ld * sizeof(*in->qos_allocation_fraction), MLD_COMPONENTS, SET_QOS_ALLOCATED_BW); req_pl = (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *)req->payload; @@ -2962,7 +2980,8 @@ CXLMI_EXPORT int cxlmi_cmd_fmapi_set_qos_bw_limit(struct cxlmi_endpoint *ep, if (!req) return -1; - arm_cci_request(ep, req, sizeof(*req_pl), + arm_cci_request(ep, req, sizeof(*req_pl) + + in->number_ld * sizeof(*in->qos_limit_fraction), MLD_COMPONENTS, SET_QOS_BW_LIMIT); req_pl = (struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *)req->payload; @@ -3553,7 +3572,7 @@ CXLMI_EXPORT int cxlmi_cmd_vendor_specific(struct cxlmi_endpoint *ep, req = calloc(1, req_sz); if (!req) return -1; - arm_cci_request(ep, req, in ? 0 : in_size, opcode >> 8, opcode & 0xFF); + arm_cci_request(ep, req, in ? in_size : 0, opcode >> 8, opcode & 0xFF); if (in) memcpy(req->payload, in, in_size); diff --git a/src/libcxlmi.h b/src/libcxlmi.h index 51b97ec..b212c95 100644 --- a/src/libcxlmi.h +++ b/src/libcxlmi.h @@ -452,7 +452,7 @@ int cxlmi_cmd_get_fw_info(struct cxlmi_endpoint *ep, struct cxlmi_cmd_get_fw_info_rsp *out); int cxlmi_cmd_transfer_fw(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_transfer_fw_req *in); + struct cxlmi_cmd_transfer_fw_req *in, size_t data_sz); int cxlmi_cmd_activate_fw(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, struct cxlmi_cmd_activate_fw_req *in); @@ -522,7 +522,8 @@ int cxlmi_cmd_memdev_get_lsa(struct cxlmi_endpoint *ep, void *ret); int cxlmi_cmd_memdev_set_lsa(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_lsa_req *in); + struct cxlmi_cmd_memdev_set_lsa_req *in, + size_t data_sz); int cxlmi_cmd_memdev_get_health_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, @@ -575,7 +576,8 @@ int cxlmi_cmd_memdev_media_operations_sanitize(struct cxlmi_endpoint *ep, int cxlmi_cmd_memdev_security_send(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_security_send_req *in); + struct cxlmi_cmd_memdev_security_send_req *in, + size_t data_sz); int cxlmi_cmd_memdev_security_receive(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, struct cxlmi_cmd_memdev_security_receive_req *in, @@ -713,6 +715,10 @@ int cxlmi_cmd_fmapi_get_multiheaded_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, struct cxlmi_cmd_fmapi_get_multiheaded_info_req *in, struct cxlmi_cmd_fmapi_get_multiheaded_info_rsp *ret); +int cxlmi_cmd_fmapi_get_head_info(struct cxlmi_endpoint *ep, + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_get_head_info_req *in, + struct cxlmi_cmd_fmapi_get_head_info_rsp *ret); int cxlmi_cmd_fmapi_get_dcd_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, From acefd919c6b89afe17f4c420745ffa1048a6490f Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Sat, 13 Dec 2025 18:08:32 -0800 Subject: [PATCH 05/10] docs: fix miscelaneous inconsistencies throughout command sets Generic-Component-Commands.md 1. Fixed syntax error (extra semicolon) in cxlmi_cmd_get_event_records signature 2. Fixed wrong function name for Get FW Info - was cxlmi_cmd_request_bg_op_abort, now cxlmi_cmd_get_fw_info 3. Fixed num_supported_feature_entries type from uint32_t to uint16_t in cxlmi_cmd_get_supported_features_rsp 4. Fixed feature_data from pointer to array in cxlmi_cmd_get_feature_rsp Memory-Device-Commands.md 1. Added _rsp suffix to all response struct names 2. Added _req suffix to all request struct names 3. Fixed Get LSA - was labeled "Return payload" but is actually "Input payload", added missing void *ret parameter 4. Fixed Set LSA - added missing size_t data_sz parameter 5. Fixed Set SLD QoS Control - removed return payload (function doesn't have one) 6. Fixed Security Send - added missing size_t data_sz parameter 7. Fixed Release Dynamic Capacity - wrong struct name cxlmi_cmd_memdev_release_dyn_cap changed to cxlmi_cmd_memdev_release_dc_req FM-API.md 1. Added _rsp suffix to all response struct names 2. Added _req suffix to all request struct names 3. Fixed Get DCD Info - changed supported_block_sizes[8] array to individual region_X_supported_blk_sz_mask fields 4. Fixed DC List Tags - wrong field names start_ind/max_tags changed to start_idx/tags_count Signed-off-by: Davidlohr Bueso --- docs/FM-API.md | 77 ++++++++------- docs/Generic-Component-Commands.md | 19 ++-- docs/Memory-Device-Commands.md | 146 +++++++++++++++-------------- 3 files changed, 128 insertions(+), 114 deletions(-) diff --git a/docs/FM-API.md b/docs/FM-API.md index 7742bee..50b7a88 100644 --- a/docs/FM-API.md +++ b/docs/FM-API.md @@ -55,7 +55,7 @@ command set, as per the latest specification. Return payload: ```C -struct cxlmi_cmd_fmapi_identify_sw_device { +struct cxlmi_cmd_fmapi_identify_sw_device_rsp { uint8_t ingress_port_id; uint8_t rsv1; uint8_t num_physical_ports; @@ -72,8 +72,8 @@ Command name: ```C int cxlmi_cmd_fmapi_identify_sw_device(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_identify_sw_device *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_identify_sw_device_rsp *ret); ``` ## Get Physical Port State (5101h) @@ -129,7 +129,7 @@ int cxlmi_cmd_fmapi_get_phys_port_state(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_fmapi_phys_port_control { +struct cxlmi_cmd_fmapi_phys_port_control_req { uint8_t ppb_id; uint8_t port_opcode; }; @@ -139,8 +139,8 @@ Command name: ```C int cxlmi_cmd_fmapi_phys_port_control(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_phys_port_control *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_phys_port_control_req *in); ``` ## Send PPB CXL.io Configuration Request (5103) @@ -177,7 +177,7 @@ int cxlmi_cmd_fmapi_send_ppb_cxlio_config_request(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_fmapi_get_domain_validation_sv_state { +struct cxlmi_cmd_fmapi_get_domain_validation_sv_state_rsp { uint8_t secret_value_state; }; ``` @@ -186,8 +186,8 @@ Command name: ```C int cxlmi_cmd_fmapi_get_domain_validation_sv_state(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_get_domain_validation_sv_state *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_get_domain_validation_sv_state_rsp *ret); ``` ## Set Domain Validation SV (5105h) @@ -195,7 +195,7 @@ int cxlmi_cmd_fmapi_get_domain_validation_sv_state(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_fmapi_set_domain_validation_sv { +struct cxlmi_cmd_fmapi_set_domain_validation_sv_req { uint8_t secret_value_uuid[0x10]; }; ``` @@ -204,8 +204,8 @@ Command name: ```C int cxlmi_cmd_fmapi_set_domain_validation_sv(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_set_domain_validation_sv *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_set_domain_validation_sv_req *in); ``` ## Get VCS Domain Validation SV State (5106h) @@ -269,7 +269,7 @@ int cxlmi_cmd_fmapi_get_domain_validation_sv(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_fmapi_bind_vppb { +struct cxlmi_cmd_fmapi_bind_vppb_req { uint8_t vcs_id; uint8_t vppb_id; uint8_t port_id; @@ -282,8 +282,8 @@ Command name: ```C int cxlmi_cmd_fmapi_bind_vppb(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_bind_vppb *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_bind_vppb_req *in); ``` ## Unbind vPPB (5202) @@ -291,7 +291,7 @@ int cxlmi_cmd_fmapi_bind_vppb(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_fmapi_unbind_vppb { +struct cxlmi_cmd_fmapi_unbind_vppb_req { uint8_t vcs_id; uint8_t vppb_id; uint8_t option; @@ -302,8 +302,8 @@ Command name: ```C int cxlmi_cmd_fmapi_unbind_vppb(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_unbind_vppb *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_unbind_vppb_req *in); ``` # MLD Port (53h) @@ -427,7 +427,7 @@ int cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_fmapi_get_ld_info { +struct cxlmi_cmd_fmapi_get_ld_info_rsp { uint64_t memory_size; uint16_t ld_count; uint8_t qos_telemetry_capability; @@ -439,7 +439,7 @@ Command name: ```C int cxlmi_cmd_fmapi_get_ld_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_get_ld_info *ret); + struct cxlmi_cmd_fmapi_get_ld_info_rsp *ret); ``` ## Get LD Allocations (5401h) @@ -517,7 +517,7 @@ int cxlmi_cmd_fmapi_set_ld_allocations(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_fmapi_get_qos_control { +struct cxlmi_cmd_fmapi_get_qos_control_rsp { uint8_t qos_telemetry_control; uint8_t egress_moderate_percentage; uint8_t egress_severe_percentage; @@ -531,8 +531,8 @@ Command name: ```C int cxlmi_cmd_fmapi_get_qos_control(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_get_qos_control *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_get_qos_control_rsp *ret); ``` ## Set QoS Control (5404h) @@ -577,7 +577,7 @@ int cxlmi_cmd_fmapi_set_qos_control(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_fmapi_get_qos_status { +struct cxlmi_cmd_fmapi_get_qos_status_rsp { uint8_t backpressure_avg_percentage; }; ``` @@ -586,8 +586,8 @@ Command name: ```C int cxlmi_cmd_fmapi_get_qos_status(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_get_qos_status *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_fmapi_get_qos_status_rsp *ret); ``` ## Get QoS Allocated BW (5406h) @@ -797,7 +797,7 @@ int cxlmi_cmd_fmapi_get_head_info(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_fmapi_get_dcd_info { +struct cxlmi_cmd_fmapi_get_dcd_info_rsp { uint8_t num_hosts; uint8_t num_supported_dc_regions; uint8_t rsvd1[0x2]; @@ -807,7 +807,14 @@ struct cxlmi_cmd_fmapi_get_dcd_info { uint8_t sanitize_on_release_config_mask; uint8_t rsvd3; uint64_t total_dynamic_capacity; - uint64_t supported_block_sizes[8]; + uint64_t region_0_supported_blk_sz_mask; + uint64_t region_1_supported_blk_sz_mask; + uint64_t region_2_supported_blk_sz_mask; + uint64_t region_3_supported_blk_sz_mask; + uint64_t region_4_supported_blk_sz_mask; + uint64_t region_5_supported_blk_sz_mask; + uint64_t region_6_supported_blk_sz_mask; + uint64_t region_7_supported_blk_sz_mask; }; ``` @@ -816,7 +823,7 @@ Command name: ```C int cxlmi_cmd_fmapi_get_dcd_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_get_dcd_info *ret); + struct cxlmi_cmd_fmapi_get_dcd_info_rsp *ret); ``` ## Get Host DC Region Config (5601h) @@ -868,7 +875,7 @@ int cxlmi_cmd_fmapi_get_dc_reg_config(struct cxlmi_endpoint *ep, ## Set Host DC Region Config (5602h) Input Payload: ```C -struct cxlmi_cmd_fmapi_set_dc_region_config { +struct cxlmi_cmd_fmapi_set_dc_region_config_req { uint8_t region_id; uint8_t rsvd[3]; uint64_t block_sz; @@ -881,7 +888,7 @@ Command name: ```C int cxlmi_cmd_fmapi_set_dc_region_config(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_fmapi_set_dc_region_config *in); + struct cxlmi_cmd_fmapi_set_dc_region_config_req *in); ``` ## Get DC Region Extent Lists (5603h) @@ -1018,8 +1025,8 @@ Input Payload: ```C struct cxlmi_cmd_fmapi_dc_list_tags_req { - uint32_t start_ind; - uint32_t max_tags; + uint32_t start_idx; + uint32_t tags_count; }; ``` @@ -1035,8 +1042,8 @@ struct cxlmi_cmd_fmapi_dc_list_tags_rsp { uint8_t tag[0x10]; uint8_t flags; uint8_t rsvd[3]; - uint8_t ref_bitmap[32]; - uint8_t pending_ref_bitmap[32]; + uint8_t ref_bitmap[0x20]; + uint8_t pending_ref_bitmap[0x20]; } tags_list[]; }; ``` diff --git a/docs/Generic-Component-Commands.md b/docs/Generic-Component-Commands.md index 84375d4..9822056 100644 --- a/docs/Generic-Component-Commands.md +++ b/docs/Generic-Component-Commands.md @@ -150,7 +150,9 @@ struct cxlmi_event_record { uint64_t timestamp; uint8_t maint_op_class; uint8_t maint_op_subclass; - uint8_t reserved[0xe]; + uint16_t ld_id; + uint8_t head_id; + uint8_t reserved[0xb]; uint8_t data[0x50]; }; ``` @@ -197,7 +199,7 @@ Command name: ```C int cxlmi_cmd_get_event_records(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_get_event_records_req *in,; + struct cxlmi_cmd_get_event_records_req *in, struct cxlmi_cmd_get_event_records_rsp *ret); ``` @@ -344,7 +346,9 @@ struct cxlmi_cmd_get_fw_info_rsp { Command Name: ```C -int cxlmi_cmd_request_bg_op_abort(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti); +int cxlmi_cmd_get_fw_info(struct cxlmi_endpoint *ep, + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_get_fw_info_rsp *ret); ``` ## Transfer FW (0201h) @@ -367,7 +371,8 @@ Command name: ```C int cxlmi_cmd_transfer_fw(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_transfer_fw_req *in); + struct cxlmi_cmd_transfer_fw_req *in, + size_t data_sz); ``` ## Activate FW (0202h) @@ -603,7 +608,7 @@ Return payload: ```C struct cxlmi_cmd_get_supported_features_rsp { - uint32_t num_supported_feature_entries; + uint16_t num_supported_feature_entries; uint16_t device_supported_features; uint8_t rsvd[4]; struct { @@ -646,7 +651,7 @@ Return payload: ```C struct cxlmi_cmd_get_feature_rsp { - uint8_t *feature_data; + uint8_t feature_data[CXL_MAILBOX_MAX_PAYLOAD_SIZE]; }; ``` @@ -670,7 +675,7 @@ struct cxlmi_cmd_set_feature_req { uint16_t offset; uint8_t version; uint8_t rsvd[9]; - uint8_t feature_data[MAX_FEATURE_SIZE]; + uint8_t feature_data[]; }; ``` diff --git a/docs/Memory-Device-Commands.md b/docs/Memory-Device-Commands.md index b72ae72..5dd1424 100644 --- a/docs/Memory-Device-Commands.md +++ b/docs/Memory-Device-Commands.md @@ -58,7 +58,7 @@ command set, as per the latest specification. Return payload: ```C -struct cxlmi_cmd_memdev_identify { +struct cxlmi_cmd_memdev_identify_rsp { char fw_revision[0x10]; uint64_t total_capacity; uint64_t volatile_capacity; @@ -81,8 +81,8 @@ Command name: ```C int cxlmi_cmd_memdev_identify(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_identify *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_identify_rsp *ret); ``` # Capacity Configuration and Label Storage (41h) @@ -92,7 +92,7 @@ int cxlmi_cmd_memdev_identify(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_partition_info { +struct cxlmi_cmd_memdev_get_partition_info_rsp { uint64_t active_vmem; uint64_t active_pmem; uint64_t next_vmem; @@ -105,7 +105,7 @@ Command name: ```C int cxlmi_cmd_memdev_get_partition_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_partition_info *ret); + struct cxlmi_cmd_memdev_get_partition_info_rsp *ret); ``` ## Set Partition Info (4101h) @@ -113,7 +113,7 @@ int cxlmi_cmd_memdev_get_partition_info(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_set_partition_info { +struct cxlmi_cmd_memdev_set_partition_info_req { uint64_t volatile_capacity; uint8_t flags; }; @@ -124,16 +124,16 @@ Command name: ```C int cxlmi_cmd_memdev_set_partition_info(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_partition_info *in); + struct cxlmi_cmd_memdev_set_partition_info_req *in); ``` ## Get LSA (4102h) -Return payload: +Input payload: ```C -struct cxlmi_cmd_memdev_get_lsa { +struct cxlmi_cmd_memdev_get_lsa_req { uint32_t offset; uint32_t length; }; @@ -143,8 +143,9 @@ Command name: ```C int cxlmi_cmd_memdev_get_lsa(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_lsa *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_get_lsa_req *in, + void *ret); ``` ## Set LSA (4103h) @@ -152,7 +153,7 @@ int cxlmi_cmd_memdev_get_lsa(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_set_lsa { +struct cxlmi_cmd_memdev_set_lsa_req { uint32_t offset; uint32_t rsvd; uint8_t data[]; @@ -163,8 +164,9 @@ Command name: ```C int cxlmi_cmd_memdev_set_lsa(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_lsa *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_set_lsa_req *in, + size_t data_sz); ``` # Health Info and Alerts (42h) @@ -174,7 +176,7 @@ int cxlmi_cmd_memdev_set_lsa(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_health_info { +struct cxlmi_cmd_memdev_get_health_info_rsp { uint8_t health_status; uint8_t media_status; uint8_t additional_status; @@ -190,8 +192,8 @@ Command name: ```C int cxlmi_cmd_memdev_get_health_info(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_health_info *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_get_health_info_rsp *ret); ``` ## Get Alert Configuration (4201h) @@ -199,7 +201,7 @@ int cxlmi_cmd_memdev_get_health_info(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_alert_config { +struct cxlmi_cmd_memdev_get_alert_config_rsp { uint8_t valid_alerts; uint8_t programmable_alerts; uint8_t life_used_critical_alert_threshold; @@ -217,8 +219,8 @@ Command name: ```C int cxlmi_cmd_memdev_get_alert_config(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_alert_config *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_get_alert_config_rsp *ret); ``` ## Set Alert Configuration (4202h) @@ -226,7 +228,7 @@ int cxlmi_cmd_memdev_get_alert_config(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_set_alert_config { +struct cxlmi_cmd_memdev_set_alert_config_req { uint8_t valid_alert_actions; uint8_t enable_alert_actions; uint8_t life_used_programmable_warning_threshold; @@ -242,8 +244,8 @@ Command name: ```C int cxlmi_cmd_memdev_set_alert_config(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_alert_config *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_set_alert_config_req *in); ``` ## Get Shutdown State (4203h) @@ -251,7 +253,7 @@ int cxlmi_cmd_memdev_set_alert_config(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_shutdown_state { +struct cxlmi_cmd_memdev_get_shutdown_state_rsp { uint8_t state; }; ``` @@ -260,8 +262,8 @@ Command name: ```C int cxlmi_cmd_memdev_get_shutdown_state(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_shutdown_state *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_get_shutdown_state_rsp *ret); ``` ## Set Shutdown State (4204h) @@ -269,7 +271,7 @@ int cxlmi_cmd_memdev_get_shutdown_state(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_set_shutdown_state { +struct cxlmi_cmd_memdev_set_shutdown_state_req { uint8_t state; }; ``` @@ -278,8 +280,8 @@ Command name: ```C int cxlmi_cmd_memdev_set_shutdown_state(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_shutdown_state *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_set_shutdown_state_req *in); ``` # Media and Poison Management (43h) @@ -328,7 +330,7 @@ int cxlmi_cmd_get_poison_list(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_inject_poison { +struct cxlmi_cmd_memdev_inject_poison_req { uint64_t inject_poison_phy_addr; }; ``` @@ -338,7 +340,7 @@ Command name: ```C int cxlmi_cmd_memdev_inject_poison(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_inject_poison *in); + struct cxlmi_cmd_memdev_inject_poison_req *in); ``` ## Clear Poison (4302h) @@ -346,7 +348,7 @@ int cxlmi_cmd_memdev_inject_poison(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_clear_poison { +struct cxlmi_cmd_memdev_clear_poison_req { uint64_t clear_poison_phy_addr; uint8_t clear_poison_write_data[64]; }; @@ -356,8 +358,8 @@ Command name: ```C int cxlmi_cmd_memdev_clear_poison(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_clear_poison *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_clear_poison_req *in); ``` ## Get Scan Media Capabilities (4303h) @@ -393,11 +395,11 @@ int cxlmi_cmd_get_scan_media_capabilities(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_scan_media { +struct cxlmi_cmd_scan_media_req { uint64_t scan_media_physaddr; uint64_t scan_media_physaddr_length; uint8_t scan_media_flags; -} __attribute__((packed)); +}; ``` Command name: @@ -405,7 +407,7 @@ Command name: ```C int cxlmi_cmd_scan_media(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_scan_media *in); + struct cxlmi_cmd_scan_media_req *in); ``` ## Get Scan Media Results (4305h) @@ -419,7 +421,7 @@ struct cxlmi_media_error_record { uint8_t rsvd[4]; }; -struct cxlmi_cmd_get_scan_media_results { +struct cxlmi_cmd_get_scan_media_results_rsp { uint64_t scan_media_restart_physaddr; uint64_t scan_media_restart_physaddr_length; uint8_t scan_media_flags; @@ -434,8 +436,8 @@ Command name: ```C int cxlmi_cmd_get_scan_media_results(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_get_scan_media_results *ret); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_get_scan_media_results_rsp *ret); ``` @@ -515,7 +517,7 @@ struct cxlmi_cmd_memdev_media_ops_dpa_range_list_entry { uint64_t length; }; -struct cxlmi_cmd_memdev_media_operations_sanitize { +struct cxlmi_cmd_memdev_media_operations_sanitize_req { uint8_t media_operation_class; uint8_t media_operation_subclass; uint8_t rsvd[2]; @@ -529,7 +531,7 @@ Sanitize Command name: ```C int cxlmi_cmd_memdev_media_operations_sanitize(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_media_operations_sanitize *in); + struct cxlmi_cmd_memdev_media_operations_sanitize_req *in); ``` # Persistent Memory Data-at-rest Security (45h) @@ -539,7 +541,7 @@ int cxlmi_cmd_memdev_media_operations_sanitize(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_security_state { +struct cxlmi_cmd_memdev_get_security_state_rsp { uint32_t security_state; }; ``` @@ -549,7 +551,7 @@ Command name: ```C int cxlmi_cmd_memdev_get_security_state(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_security_state *ret); + struct cxlmi_cmd_memdev_get_security_state_rsp *ret); ``` ## Set Passphrase (4501h) @@ -557,7 +559,7 @@ int cxlmi_cmd_memdev_get_security_state(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_set_passphrase { +struct cxlmi_cmd_memdev_set_passphrase_req { uint8_t passphrase_type; uint8_t rsvd[0x1F]; uint8_t current_passphrase[0x20]; @@ -569,8 +571,8 @@ Command name: ```C int cxlmi_cmd_memdev_set_passphrase(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_passphrase *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_set_passphrase_req *in); ``` ## Disable Passphrase (4502h) @@ -578,7 +580,7 @@ int cxlmi_cmd_memdev_set_passphrase(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_disable_passphrase { +struct cxlmi_cmd_memdev_disable_passphrase_req { uint8_t passphrase_type; uint8_t rsvd[0x1F]; uint8_t passphrase[0x20]; @@ -589,8 +591,8 @@ Command name: ```C int cxlmi_cmd_memdev_disable_passphrase(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_disable_passphrase *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_disable_passphrase_req *in); ``` ## Unlock (4503h) @@ -598,7 +600,7 @@ int cxlmi_cmd_memdev_disable_passphrase(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_unlock { +struct cxlmi_cmd_memdev_unlock_req { uint8_t current_passphrase[0x20]; }; ``` @@ -608,7 +610,7 @@ Command name: ```C int cxlmi_cmd_memdev_unlock(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_unlock *in); + struct cxlmi_cmd_memdev_unlock_req *in); ``` ## Freeze Security State (4504h) @@ -627,7 +629,7 @@ int cxlmi_cmd_memdev_freeze_security_state(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_passphrase_secure_erase { +struct cxlmi_cmd_memdev_passphrase_secure_erase_req { uint8_t passphrase_type; uint8_t rsvd[0x1F]; uint8_t passphrase[0x20]; @@ -639,7 +641,7 @@ Command name: ```C int cxlmi_cmd_memdev_passphrase_secure_erase(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_passphrase_secure_erase *in); + struct cxlmi_cmd_memdev_passphrase_secure_erase_req *in); ``` # Security Passthrough (46h) @@ -652,7 +654,7 @@ a maximum of 256 maximum protocols to be retrieved for each receive. Input payload: ```C -struct cxlmi_cmd_memdev_security_send { +struct cxlmi_cmd_memdev_security_send_req { uint8_t security_protocol; uint16_t sp_specific; uint8_t rsvd[0x5]; @@ -664,8 +666,9 @@ Command name: ```C int cxlmi_cmd_memdev_security_send(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_security_send *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_security_send_req *in, + size_t data_sz); ``` ## Security Receive (4601h) @@ -673,7 +676,7 @@ int cxlmi_cmd_memdev_security_send(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_security_receive { +struct cxlmi_cmd_memdev_security_receive_req { uint8_t security_protocol; uint16_t sp_specific; uint8_t rsvd[0x5]; @@ -698,7 +701,7 @@ int cxlmi_cmd_memdev_security_receive(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_sld_qos_control { +struct cxlmi_cmd_memdev_get_sld_qos_control_rsp { uint8_t qos_telemetry_control; uint8_t egress_moderate_percentage; uint8_t egress_severe_percentage; @@ -711,15 +714,15 @@ Command name: ```C int cxlmi_cmd_memdev_get_sld_qos_control(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_sld_qos_control *ret); + struct cxlmi_cmd_memdev_get_sld_qos_control_rsp *ret); ``` ## Set SLD QoS Control (4701h) -Input/Return payloads: +Input payload: ```C -struct cxlmi_cmd_memdev_get_sld_qos_control { +struct cxlmi_cmd_memdev_set_sld_qos_control_req { uint8_t qos_telemetry_control; uint8_t egress_moderate_percentage; uint8_t egress_severe_percentage; @@ -732,8 +735,7 @@ Command name: ```C int cxlmi_cmd_memdev_set_sld_qos_control(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_set_sld_qos_control *in, - struct cxlmi_cmd_memdev_set_sld_qos_control *ret); + struct cxlmi_cmd_memdev_set_sld_qos_control_req *in); ``` ## Get SLD QoS Status (4702h) @@ -741,7 +743,7 @@ int cxlmi_cmd_memdev_set_sld_qos_control(struct cxlmi_endpoint *ep, Return payload: ```C -struct cxlmi_cmd_memdev_get_sld_qos_status { +struct cxlmi_cmd_memdev_get_sld_qos_status_rsp { uint8_t backpressure_avg_percentage; }; ``` @@ -751,7 +753,7 @@ Command name: ```C int cxlmi_cmd_memdev_get_sld_qos_status(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_get_sld_qos_status *ret); + struct cxlmi_cmd_memdev_get_sld_qos_status_rsp *ret); ``` # Dynamic Capacity (48h) @@ -846,7 +848,7 @@ int cxlmi_cmd_memdev_get_dc_extent_list(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_add_dc_response { +struct cxlmi_cmd_memdev_add_dc_response_req { uint32_t updated_extent_list_size; uint8_t flags; uint8_t rsvd1[3]; @@ -862,8 +864,8 @@ Command name: ```C int cxlmi_cmd_memdev_add_dc_response(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_add_dc_response *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_add_dc_response_req *in); ``` ## Release Dynamic Capacity (4803h) @@ -871,7 +873,7 @@ int cxlmi_cmd_memdev_add_dc_response(struct cxlmi_endpoint *ep, Input payload: ```C -struct cxlmi_cmd_memdev_release_dc { +struct cxlmi_cmd_memdev_release_dc_req { uint32_t updated_extent_list_size; uint8_t flags; uint8_t rsvd1[3]; @@ -887,6 +889,6 @@ Command name: ```C int cxlmi_cmd_memdev_release_dc(struct cxlmi_endpoint *ep, - struct cxlmi_tunnel_info *ti, - struct cxlmi_cmd_memdev_release_dyn_cap *in); + struct cxlmi_tunnel_info *ti, + struct cxlmi_cmd_memdev_release_dc_req *in); ``` From ee32dddc5778feaa0af8efc88c91f6c9e0f21752 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Sun, 14 Dec 2025 04:09:39 -0800 Subject: [PATCH 06/10] tests: add mock transport testing infrastructure Add a comprehensive mock transport layer and test suite for unit testing the libcxlmi command API without requiring real CXL hardware. The mock transport allows tests to: - Set expected responses for specific command set/opcode combinations - Queue multiple responses for sequential command testing - Verify request payloads sent by the library - Test error code handling and retry scenarios - Validate endianness conversions Test coverage includes 325 tests across: - All 76 CXL-MI commands (generic, events, firmware, timestamps, logs, features, memory device, health/alerts, media/poison, sanitize, security, SLD QoS, DCD config, and FM-API commands) - Error code handling for all defined CXL return codes - Request payload verification (correct encoding and field values) - Response payload verification (correct decoding and field extraction) - Endianness verification for multi-byte fields - Edge cases for variable-length responses (empty arrays, multiple entries) - Boundary value tests (max counts, zero values, overflow conditions) Also adds GitHub CI workflow with: - Build matrix (GCC/Clang on x86-64 and ARM64) - AddressSanitizer and UndefinedBehaviorSanitizer - Valgrind memory checking - Code coverage with Codecov integration - Clang static analyzer Generated-by: Claude AI Signed-off-by: Davidlohr Bueso --- .github/workflows/abi.yml | 62 + .github/workflows/analysis.yml | 108 + .github/workflows/build.yml | 138 + .github/workflows/spelling.yml | 26 + .github/workflows/test.yml | 69 + README.md | 60 +- docs/Testing.md | 425 ++ src/cxlmi/cxlmi.c | 31 +- src/cxlmi/mock.c | 306 + src/cxlmi/mock.h | 28 + src/cxlmi/test.h | 94 + src/meson.build | 3 +- tests/meson.build | 9 + tests/mock-tests.c | 10122 +++++++++++++++++++++++++++++++ 14 files changed, 11470 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/abi.yml create mode 100644 .github/workflows/analysis.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/spelling.yml create mode 100644 .github/workflows/test.yml create mode 100644 docs/Testing.md create mode 100644 src/cxlmi/mock.c create mode 100644 src/cxlmi/mock.h create mode 100644 src/cxlmi/test.h create mode 100644 tests/mock-tests.c diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml new file mode 100644 index 0000000..3ed3f04 --- /dev/null +++ b/.github/workflows/abi.yml @@ -0,0 +1,62 @@ +name: ABI + +permissions: + contents: read + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + abi-check: + name: ABI Compatibility + runs-on: ubuntu-24.04 + + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson libdbus-1-dev abigail-tools + + - name: Checkout base branch (PR) + if: github.event_name == 'pull_request' + uses: actions/checkout@v6 + with: + ref: ${{ github.base_ref }} + path: base + + - name: Checkout base commit (push) + if: github.event_name == 'push' + uses: actions/checkout@v6 + with: + ref: ${{ github.event.before }} + path: base + + - name: Build base + run: | + cd base + meson setup build -Dlibdbus=enabled + meson compile -C build + + - name: Checkout current + uses: actions/checkout@v6 + with: + path: current + + - name: Build current + run: | + cd current + meson setup build -Dlibdbus=enabled + meson compile -C build + + - name: Check ABI compatibility + run: | + abidiff --headers-dir1 base/src --headers-dir2 current/src \ + base/build/src/libcxlmi.so current/build/src/libcxlmi.so \ + --no-added-syms \ + --drop-private-types || { + echo "::warning::ABI changes detected. Please review if this is intentional." + exit 0 + } diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml new file mode 100644 index 0000000..3ed85d3 --- /dev/null +++ b/.github/workflows/analysis.yml @@ -0,0 +1,108 @@ +name: Analysis + +permissions: + contents: read + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + coverage: + name: Code Coverage + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson lcov gcovr libdbus-1-dev + + - name: Configure with coverage + run: meson setup build -Db_coverage=true -Dlibdbus=enabled + + - name: Build + run: meson compile -C build + + - name: Run tests + run: meson test -C build + + - name: Generate coverage report + run: | + gcovr --xml --output build/meson-logs/coverage.xml \ + --root . \ + --filter 'src/' \ + --exclude 'tests/' \ + --exclude 'examples/' \ + build + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: build/meson-logs/coverage.xml + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + static-analysis: + name: Static Analysis + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson clang clang-tools libdbus-1-dev + + - name: Run Clang Static Analyzer + run: | + # Exclude meson-private to avoid false positives from meson's test files + scan-build --status-bugs --exclude build/meson-private \ + meson setup build -Dlibdbus=enabled + scan-build --status-bugs --exclude build/meson-private \ + -o scan-results meson compile -C build 2>&1 || { + echo "=== Static Analysis Errors Found ===" + echo "" + for report in scan-results/*/report-*.html; do + if [ -f "$report" ]; then + echo "--- $report ---" + # Extract bug info from HTML report + grep -E "BUGDESC|BUGTYPE|BUGFILE|BUGLINE" "$report" | \ + sed 's///g' + echo "" + fi + done + exit 1 + } + + clang-tidy: + name: Clang-Tidy + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson clang clang-tidy libdbus-1-dev + + - name: Configure + run: meson setup build -Dlibdbus=enabled + env: + CC: clang + + - name: Run clang-tidy + run: | + # Generate compile_commands.json is automatic with meson + # Run clang-analyzer checks (security and correctness) + find src -name '*.c' -exec clang-tidy --quiet {} \ + -p build \ + --checks='-*,clang-analyzer-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling' \ + --warnings-as-errors='*' \; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c6cebbe --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,138 @@ +name: Build + +permissions: + contents: read + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + name: Build (${{ matrix.compiler }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # x86-64 + - os: ubuntu-24.04 + compiler: gcc + cc: gcc + - os: ubuntu-24.04 + compiler: clang + cc: clang + - os: ubuntu-22.04 + compiler: gcc + cc: gcc + # ARM64 + - os: ubuntu-24.04-arm + compiler: gcc + cc: gcc + - os: ubuntu-24.04-arm + compiler: clang + cc: clang + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson libdbus-1-dev + + - name: Configure + run: meson setup build -Dlibdbus=enabled --werror + env: + CC: ${{ matrix.cc }} + + - name: Build + run: meson compile -C build + + - name: Run mock tests + run: meson test -C build + + build-options: + name: Options (${{ matrix.name }}, ${{ matrix.arch }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # x86-64 configurations + - name: minimal + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=disabled + - name: dbus-openbmc + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=enabled -Dmctpd=openbmc + - name: dbus-codeconstruct + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=enabled -Dmctpd=codeconstruct + - name: cxl2.0-compat + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=enabled -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+dbus-openbmc + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=enabled -Dmctpd=openbmc -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+dbus-codeconstruct + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=enabled -Dmctpd=codeconstruct -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+minimal + arch: x86-64 + os: ubuntu-24.04 + configure: -Dlibdbus=disabled -Dcxl2_0_mode=enabled + # ARM64 configurations + - name: minimal + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=disabled + - name: dbus-openbmc + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=enabled -Dmctpd=openbmc + - name: dbus-codeconstruct + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=enabled -Dmctpd=codeconstruct + - name: cxl2.0-compat + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=enabled -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+dbus-openbmc + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=enabled -Dmctpd=openbmc -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+dbus-codeconstruct + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=enabled -Dmctpd=codeconstruct -Dcxl2_0_mode=enabled + - name: cxl2.0-compat+minimal + arch: arm64 + os: ubuntu-24.04-arm + configure: -Dlibdbus=disabled -Dcxl2_0_mode=enabled + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson libdbus-1-dev + + - name: Configure + run: meson setup build ${{ matrix.configure }} --werror + + - name: Build + run: meson compile -C build + + - name: Run mock tests + run: meson test -C build diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 0000000..d12fb1a --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,26 @@ +name: Spelling + +permissions: + contents: read + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + codespell: + name: Codespell + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install codespell + run: | + sudo apt-get update + sudo apt-get install -y codespell + + - name: Run codespell + run: codespell src/ tests/ examples/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a9f9a12 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,69 @@ +name: Test + +permissions: + contents: read + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + sanitizers: + name: Sanitizers (${{ matrix.sanitizer }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - sanitizer: address + name: AddressSanitizer + - sanitizer: undefined + name: UndefinedBehaviorSanitizer + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson clang libdbus-1-dev + + - name: Configure with ${{ matrix.name }} + run: meson setup build -Db_sanitize=${{ matrix.sanitizer }} -Db_lundef=false -Dlibdbus=enabled + env: + CC: clang + + - name: Build + run: meson compile -C build + + - name: Run mock tests + run: ./build/tests/mock-tests + env: + ASAN_OPTIONS: abort_on_error=1:halt_on_error=1 + UBSAN_OPTIONS: abort_on_error=1:halt_on_error=1:print_stacktrace=1 + + valgrind: + name: Valgrind + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y meson valgrind libdbus-1-dev + + - name: Configure + run: meson setup build -Dlibdbus=enabled + + - name: Build + run: meson compile -C build + + - name: Run mock tests under Valgrind + run: | + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + --track-origins=yes --errors-for-leak-kinds=definite,possible \ + ./build/tests/mock-tests diff --git a/README.md b/README.md index 036e0cc..aa8c491 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,12 @@ CXL Management Interface library (libcxlmi). -[](https://scan.coverity.com/projects/computexpresslink-libcxlmi) +[![Build](https://github.com/computexpresslink/libcxlmi/actions/workflows/build.yml/badge.svg)](https://github.com/computexpresslink/libcxlmi/actions/workflows/build.yml) +[![Test](https://github.com/computexpresslink/libcxlmi/actions/workflows/test.yml/badge.svg)](https://github.com/computexpresslink/libcxlmi/actions/workflows/test.yml) +[![Analysis](https://github.com/computexpresslink/libcxlmi/actions/workflows/analysis.yml/badge.svg)](https://github.com/computexpresslink/libcxlmi/actions/workflows/analysis.yml) +[![ABI](https://github.com/computexpresslink/libcxlmi/actions/workflows/abi.yml/badge.svg)](https://github.com/computexpresslink/libcxlmi/actions/workflows/abi.yml) +[![Spelling](https://github.com/computexpresslink/libcxlmi/actions/workflows/spelling.yml/badge.svg)](https://github.com/computexpresslink/libcxlmi/actions/workflows/spelling.yml) +[![Coverity](https://scan.coverity.com/projects/31615/badge.svg)](https://scan.coverity.com/projects/computexpresslink-libcxlmi) CXL Management Interface utility library provides type definitions for CXL specification structures, enumerations and helper functions to @@ -401,7 +406,6 @@ Note that when using the sanitize feature, the library `libasan.so` must be avai It's also possible to enable the undefined behavior sanitizer with `-Db_sanitize=undefined`. To enable both, use `-Db_sanitize=address,undefined`. - 2. Then compile it: ``` meson compile -C build @@ -415,6 +419,58 @@ meson install -C build rm -rf build ``` +Testing +======= + +The library includes a mock transport layer for unit testing without hardware. +To run the tests: + +``` +meson setup build +meson test -C build +``` + +The mock transport intercepts commands at the transport layer, exercising the +complete command encoding/decoding path including endianness conversion. This +enables testing of all CCI commands without CXL hardware. + +Include `` for the mock API: + +```C +#include +#include + +struct cxlmi_ctx *ctx = cxlmi_new_ctx(stderr, LOG_ERR); +struct cxlmi_endpoint *ep = cxlmi_open_mock(ctx); + +/* Configure a mock response */ +struct cxlmi_cmd_identify_rsp rsp = { .vendor_id = 0x1234 }; +cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + +/* Send the command - it will receive the configured response */ +struct cxlmi_cmd_identify_rsp ret; +int rc = cxlmi_cmd_identify(ep, NULL, &ret); + +cxlmi_close(ep); +cxlmi_free_ctx(ctx); +``` + +Code Coverage +------------- +To generate code coverage reports (requires `lcov` or `gcovr`): + +```bash +meson setup build-coverage -Db_coverage=true +meson compile -C build-coverage +meson test -C build-coverage +ninja -C build-coverage coverage-html +``` + +The HTML report will be at `build-coverage/meson-logs/coveragereport/index.html`. + +For detailed documentation on the mock testing infrastructure, coverage +reporting, and fuzz testing, see [docs/Testing.md](docs/Testing.md). + Linking ======= diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..a0064ba --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,425 @@ +# Mock Device Testing Infrastructure + +libcxlmi includes a mock transport layer that enables comprehensive testing without +requiring physical CXL hardware. This document explains the architecture, benefits, +limitations, and usage of the mock testing infrastructure. + +## Overview + +The mock transport simulates a CXL device endpoint, allowing test code to: + +- Send CXL-MI commands through the library's normal API +- Configure expected responses with specific return codes and payloads +- Verify that commands are properly formatted before transmission +- Test error handling paths that are difficult to trigger with real hardware + +This enables developers to validate library behavior, catch regressions, and verify +protocol correctness without access to CXL devices. + +## Architecture + +### How It Works + +The mock transport intercepts commands at the transport layer, where the library +would normally send data over MCTP or through a mailbox interface: + +``` +┌─────────────────┐ +│ Test Code │ +└────────┬────────┘ + │ cxlmi_cmd_*() + ▼ +┌─────────────────┐ +│ libcxlmi API │ +│ (commands.c) │ +└────────┬────────┘ + │ send_cmd_cci() + ▼ +┌─────────────────┐ +│ Mock Transport │ ← Intercepts here instead of real hardware +│ (mock.c) │ +└─────────────────┘ +``` + +When a command is sent to a mock endpoint: + +1. The library builds the CCI message (command set, opcode, payload) exactly as + it would for real hardware +2. The mock transport records the command for later verification +3. It looks up a pre-configured response matching the command set and opcode +4. It returns that response (or "unsupported" if none was configured) +5. The library processes the response and returns to the caller + +This means the entire command encoding/decoding path is exercised, just as it +would be with a real device. + +### Key Components + +**`src/cxlmi/mock.c`** - Mock transport implementation: +- `cxlmi_open_mock()` - Creates a mock endpoint +- `cxlmi_mock_set_response()` - Queues a response for a specific command +- `cxlmi_mock_get_last_command()` - Retrieves the last command sent (for verification) +- `cxlmi_mock_get_stats()` - Returns command/response counts +- `send_mock_cmd()` - Internal function that handles mock command processing + +**`src/cxlmi/test.h`** - Public test API header (separate from `libcxlmi.h` to +keep the main API clean) + +**`tests/mock-tests.c`** - Comprehensive test suite using the mock infrastructure + +## Benefits + +### Hardware Independence + +The primary benefit is the ability to test without CXL hardware. This enables: + +- CI/CD pipelines that run on standard servers +- Development on laptops and workstations +- Testing of commands for devices that don't exist yet + +### Complete Path Coverage + +Unlike unit tests that mock at the function level, the mock transport exercises +the complete command path: + +- Struct serialization to wire format +- Endianness conversion (host byte order ↔ little-endian wire format) +- Payload size calculations +- Response parsing and field extraction + +### Error Path Testing + +Real hardware rarely returns errors, making error handling difficult to test. +The mock transport can simulate any return code: + +```c +/* Test handling of background operation status */ +cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_BACKGROUND, NULL, 0); +rc = cxlmi_cmd_identify(ep, NULL, &id); +assert(rc == CXLMI_RET_BACKGROUND); +``` + +### Protocol Verification + +Tests can verify that commands are encoded correctly by inspecting the raw +payload sent to the mock transport: + +```c +/* Verify 64-bit field is encoded as little-endian */ +cxlmi_mock_get_last_command(ep, &cmd_set, &cmd, payload, &payload_size); +assert(payload[0] == 0x78); /* Low byte of 0x12345678 */ +assert(payload[3] == 0x12); /* High byte */ +``` + +### Regression Testing + +With 299 tests covering various commands, return codes, and edge cases, the +test suite catches regressions when modifying the library. + +## Limitations + +### No Device Logic + +The mock transport does not simulate device behavior. It simply returns +pre-configured responses. This means: + +- It cannot validate that a request makes sense for the device state +- It cannot simulate stateful operations (e.g., firmware update progress) +- It cannot test timing-dependent behavior + +### Response Configuration Required + +Every command needs a response configured before it's sent. Unconfigured +commands return `CXLMI_RET_UNSUPPORTED`. This is intentional—it forces tests +to be explicit about expected behavior. + +### Single Response Per Command + +Responses are consumed in FIFO order per command. For commands that might be +called multiple times, you must queue multiple responses: + +```c +/* Queue 3 responses for retry testing */ +cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_BUSY, NULL, 0); +cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_BUSY, NULL, 0); +cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); +``` + +### No MCTP/Transport Layer Testing + +The mock transport bypasses the actual transport layer (MCTP, mailbox). To test +transport-level behavior, you need real hardware or a separate transport mock. + +## Usage + +### Basic Test Pattern + +```c +#include +#include + +void test_identify(void) +{ + struct cxlmi_ctx *ctx; + struct cxlmi_endpoint *ep; + struct cxlmi_cmd_identify_rsp wire_rsp = {0}; + struct cxlmi_cmd_identify_rsp result; + int rc; + + /* Create context and mock endpoint */ + ctx = cxlmi_new_ctx(stderr, LOG_ERR); + ep = cxlmi_open_mock(ctx); + + /* Configure the response the mock should return */ + wire_rsp.vendor_id = cpu_to_le16(0x1234); + wire_rsp.device_id = cpu_to_le16(0x5678); + cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + + /* Call the library function under test */ + rc = cxlmi_cmd_identify(ep, NULL, &result); + + /* Verify results */ + assert(rc == CXLMI_RET_SUCCESS); + assert(result.vendor_id == 0x1234); + assert(result.device_id == 0x5678); + + /* Cleanup */ + cxlmi_close(ep); + cxlmi_free_ctx(ctx); +} +``` + +### Testing Request Encoding + +To verify that a request is encoded correctly on the wire: + +```c +void test_request_encoding(void) +{ + struct cxlmi_cmd_set_timestamp_req req = {0}; + uint8_t cmd_set, cmd; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + + /* ... setup mock endpoint ... */ + + req.timestamp = 0x0102030405060708ULL; + + cxlmi_mock_set_response(ep, 0x03, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + cxlmi_cmd_set_timestamp(ep, NULL, &req); + + /* Retrieve and verify the raw command payload */ + cxlmi_mock_get_last_command(ep, &cmd_set, &cmd, payload, &payload_size); + + assert(cmd_set == 0x03); /* TIMESTAMP command set */ + assert(cmd == 0x01); /* SET_TIMESTAMP opcode */ + + /* Verify little-endian encoding */ + assert(payload[0] == 0x08); /* LSB */ + assert(payload[7] == 0x01); /* MSB */ +} +``` + +### Testing Error Handling + +```c +void test_error_handling(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + /* ... setup mock endpoint ... */ + + /* Configure mock to return an error */ + cxlmi_mock_set_response(ep, 0x00, 0x01, CXLMI_RET_INVALID_INPUT, NULL, 0); + + rc = cxlmi_cmd_identify(ep, NULL, &id); + + assert(rc == CXLMI_RET_INVALID_INPUT); +} +``` + +## Test Categories + +The test suite (`tests/mock-tests.c`) covers several categories: + +| Category | Description | +|----------|-------------| +| Mock Infrastructure | Tests for the mock transport itself | +| Generic Component Commands | Identify, background operation status | +| Events Commands | Get/clear event records | +| Firmware Update Commands | Get FW info, transfer, activate | +| Timestamp Commands | Get/set timestamp | +| Logs Commands | Get log, supported logs, CEL | +| Features Commands | Get/set features | +| Memory Device Commands | Sanitize, secure erase | +| Health Info/Alerts Commands | Health info, alert config | +| Media and Poison Commands | Poison list, inject/clear poison | +| DCD Config Commands | Dynamic capacity configuration | +| FM-API Commands | Fabric Manager API commands | +| Error Code Handling | All CXL return codes | +| Request Payload Verification | Wire format encoding | +| Response Payload Verification | Wire format decoding | +| Endianness Verification | Multi-byte field byte order | + +## Running Tests + +Build and run the test suite: + +```bash +meson setup build +meson compile -C build +./build/tests/mock-tests +``` + +Example output: + +``` +Mock Infrastructure: + test_mock_create_close [PASS] + test_mock_no_response_returns_unsupported [PASS] + test_mock_stats_tracking [PASS] + ... + +========================================================== +Results: 299 passed, 0 failed, 299 total +========================================================== +``` + +## Code Coverage + +The project supports code coverage reporting using gcov/lcov. This helps identify +untested code paths and measure test effectiveness. + +### Prerequisites + +Install the coverage tools: + +```bash +# Debian/Ubuntu +apt install lcov + +# Fedora/RHEL +dnf install lcov + +# Or use gcovr instead +pip install gcovr +``` + +### Generating Coverage Reports + +1. **Configure a coverage build:** + +```bash +meson setup build-coverage -Db_coverage=true +``` + +2. **Build and run tests:** + +```bash +meson compile -C build-coverage +meson test -C build-coverage +``` + +3. **Generate reports:** + +```bash +# HTML report (recommended for detailed analysis) +ninja -C build-coverage coverage-html +# Opens: build-coverage/meson-logs/coveragereport/index.html + +# Text summary +ninja -C build-coverage coverage-text + +# XML report (for CI integration) +ninja -C build-coverage coverage-xml + +# Sonarqube format +ninja -C build-coverage coverage-sonarqube +``` + +### Understanding Coverage Output + +The coverage report shows three metrics: + +- **Line coverage**: Percentage of executable lines that were run +- **Function coverage**: Percentage of functions that were called +- **Branch coverage**: Percentage of conditional branches taken + +Example output: +``` +Summary coverage rate: + lines......: 77.9% (7681 of 9863 lines) + functions..: 82.2% (416 of 506 functions) + branches...: 47.9% (2138 of 4459 branches) +``` + +### HTML Report Navigation + +The HTML report provides: + +- **Directory view**: Coverage breakdown by directory +- **File view**: Line-by-line coverage highlighting + - Green: Executed lines + - Red: Unexecuted lines + - Yellow: Partially covered branches +- **Summary statistics**: Overall and per-file metrics + +### CI Integration + +For continuous integration, use the text or XML output: + +```bash +# Get a quick pass/fail based on coverage threshold +ninja -C build-coverage coverage-text 2>&1 | grep "lines" + +# Generate Cobertura XML for CI tools (Jenkins, GitLab, etc.) +ninja -C build-coverage coverage-xml +# Output: build-coverage/meson-logs/coverage.xml +``` + +### Improving Coverage + +When coverage reports show gaps: + +1. **Identify uncovered functions**: Look for red functions in the HTML report +2. **Add targeted tests**: Create tests that exercise the uncovered paths +3. **Consider error paths**: Many gaps are in error handling code +4. **Check branch coverage**: Ensure both true/false paths are tested + +Example of adding a test for an uncovered error path: + +```c +static int test_error_resource_exhausted(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, + CXLMI_RET_RESOURCES_EXHAUSTED, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_RESOURCES_EXHAUSTED, "wrong error code"); + return 0; +} +``` + +## Adding New Tests + +When adding support for a new command, add corresponding tests: + +1. **Basic functionality test** - Configure a valid response and verify the + library returns correct values + +2. **Request encoding test** - Verify multi-byte fields are encoded as + little-endian on the wire + +3. **Response decoding test** - Provide a little-endian wire response and + verify fields are converted to host byte order + +4. **Error handling test** - Verify the command handles error return codes + +See existing tests in `tests/mock-tests.c` for examples. diff --git a/src/cxlmi/cxlmi.c b/src/cxlmi/cxlmi.c index 0cdbd49..3452044 100644 --- a/src/cxlmi/cxlmi.c +++ b/src/cxlmi/cxlmi.c @@ -38,6 +38,7 @@ #include #include "private.h" +#include "mock.h" #if !defined(AF_MCTP) #define AF_MCTP 45 @@ -295,7 +296,9 @@ static void mctp_close(struct cxlmi_endpoint *ep) CXLMI_EXPORT void cxlmi_close(struct cxlmi_endpoint *ep) { - if (ep->transport_data) { + if (cxlmi_is_mock_endpoint(ep)) { + mock_close(ep); + } else if (ep->transport_data) { mctp_close(ep); free(ep->transport_data); } else { @@ -385,8 +388,13 @@ static int send_mctp_direct(struct cxlmi_endpoint *ep, bool fmapi, memset(rsp_msg, 0, rsp_msg_sz); - len = sendto(sd, req_msg, req_msg_sz, 0, - (struct sockaddr *)&addr, sizeof(addr)); + if (sendto(sd, req_msg, req_msg_sz, 0, + (struct sockaddr *)&addr, sizeof(addr)) < 0) { + errno_save = errno; + cxlmi_msg(ep->ctx, LOG_ERR, "Failed to send on MCTP socket"); + errno = errno_save; + return -1; + } pollfds[0].fd = sd; pollfds[0].events = POLLIN; @@ -1079,7 +1087,10 @@ int send_cmd_cci(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, if (cxlmi_ep_has_quirk(ep, CXLMI_QUIRK_MIN_INTER_COMMAND_TIME)) cxlmi_insert_delay(ep); - if (ep->transport_data) { + if (cxlmi_is_mock_endpoint(ep)) { + rc = send_mock_cmd(ep, req_msg, req_msg_sz, + rsp_msg, rsp_msg_sz, rsp_msg_sz_min); + } else if (ep->transport_data) { if (!ti || ti->level == 0) rc = send_mctp_direct(ep, fmapi_cmd, req_msg, req_msg_sz, rsp_msg, rsp_msg_sz, rsp_msg_sz_min); @@ -1489,8 +1500,6 @@ int cxlmi_scan_mctp(struct cxlmi_ctx *ctx) /* objects container */ dbus_message_iter_recurse(&args, &objs); - rc = 0; - do { DBusMessageIter ent; int opened; @@ -1687,7 +1696,7 @@ CXLMI_EXPORT struct cxlmi_endpoint *cxlmi_next_endpoint(struct cxlmi_ctx *ctx, void arm_cci_request(struct cxlmi_endpoint *ep, struct cxlmi_cci_msg *req, size_t req_pl_sz, uint8_t cmdset, uint8_t cmd) { - if (ep->transport_data) { + if (ep->transport_data && !cxlmi_is_mock_endpoint(ep)) { struct cxlmi_transport_mctp *mctp = ep->transport_data; *req = (struct cxlmi_cci_msg) { @@ -1704,11 +1713,17 @@ void arm_cci_request(struct cxlmi_endpoint *ep, struct cxlmi_cci_msg *req, req->pl_length[2] = (req_pl_sz >> 16) & 0xff; } } else { - /* while CCIs arent sent directly over ioctl, add general info */ + /* ioctl and mock: add general info */ *req = (struct cxlmi_cci_msg) { .command = cmd, .command_set = cmdset, .vendor_ext_status = 0xabcd, }; + + if (req_pl_sz) { + req->pl_length[0] = req_pl_sz & 0xff; + req->pl_length[1] = (req_pl_sz >> 8) & 0xff; + req->pl_length[2] = (req_pl_sz >> 16) & 0xff; + } } } diff --git a/src/cxlmi/mock.c b/src/cxlmi/mock.c new file mode 100644 index 0000000..fd06f36 --- /dev/null +++ b/src/cxlmi/mock.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libcxlmi. + * + * Mock transport layer for testing without hardware. + */ + +#include +#include +#include + +#include + +#include + +#include "private.h" +#include "mock.h" + +/* Magic value to identify mock transport */ +#define CXLMI_MOCK_TRANSPORT_MAGIC 0x4D4F434B /* "MOCK" */ + +struct cxlmi_mock_response { + struct list_node entry; + uint8_t command_set; + uint8_t command; + uint16_t return_code; + void *payload; + size_t payload_size; +}; + +struct cxlmi_transport_mock { + uint32_t magic; + struct list_head responses; + /* Stats for testing */ + unsigned int commands_sent; + unsigned int responses_returned; + /* Last command received (for verification) */ + uint8_t last_command_set; + uint8_t last_command; + void *last_payload; + size_t last_payload_size; +}; + +bool cxlmi_is_mock_endpoint(struct cxlmi_endpoint *ep) +{ + struct cxlmi_transport_mock *mock; + + if (!ep || !ep->transport_data) + return false; + + mock = ep->transport_data; + return mock->magic == CXLMI_MOCK_TRANSPORT_MAGIC; +} + +CXLMI_EXPORT struct cxlmi_endpoint *cxlmi_open_mock(struct cxlmi_ctx *ctx) +{ + struct cxlmi_endpoint *ep; + struct cxlmi_transport_mock *mock; + + if (!ctx) { + errno = EINVAL; + return NULL; + } + + ep = calloc(1, sizeof(*ep)); + if (!ep) + return NULL; + + mock = calloc(1, sizeof(*mock)); + if (!mock) { + free(ep); + return NULL; + } + + mock->magic = CXLMI_MOCK_TRANSPORT_MAGIC; + list_head_init(&mock->responses); + + list_node_init(&ep->entry); + ep->ctx = ctx; + ep->transport_data = mock; + ep->timeout_ms = 5000; + ep->has_fmapi = true; /* Mock supports everything */ + ep->fd = -1; + + list_add(&ctx->endpoints, &ep->entry); + + return ep; +} + +CXLMI_EXPORT int cxlmi_mock_set_response(struct cxlmi_endpoint *ep, + uint8_t command_set, + uint8_t command, + uint16_t return_code, + void *payload, + size_t payload_size) +{ + struct cxlmi_transport_mock *mock; + struct cxlmi_mock_response *resp; + + if (!ep || !cxlmi_is_mock_endpoint(ep)) { + errno = EINVAL; + return -1; + } + + mock = ep->transport_data; + + resp = calloc(1, sizeof(*resp)); + if (!resp) + return -1; + + resp->command_set = command_set; + resp->command = command; + resp->return_code = return_code; + + if (payload && payload_size > 0) { + resp->payload = malloc(payload_size); + if (!resp->payload) { + free(resp); + return -1; + } + memcpy(resp->payload, payload, payload_size); + resp->payload_size = payload_size; + } + + list_add_tail(&mock->responses, &resp->entry); + + return 0; +} + +CXLMI_EXPORT void cxlmi_mock_clear_responses(struct cxlmi_endpoint *ep) +{ + struct cxlmi_transport_mock *mock; + struct cxlmi_mock_response *resp, *next; + + if (!ep || !cxlmi_is_mock_endpoint(ep)) + return; + + mock = ep->transport_data; + + list_for_each_safe(&mock->responses, resp, next, entry) { + list_del(&resp->entry); + free(resp->payload); + free(resp); + } +} + +CXLMI_EXPORT int cxlmi_mock_get_stats(struct cxlmi_endpoint *ep, + unsigned int *commands_sent, + unsigned int *responses_returned) +{ + struct cxlmi_transport_mock *mock; + + if (!ep || !cxlmi_is_mock_endpoint(ep)) { + errno = EINVAL; + return -1; + } + + mock = ep->transport_data; + + if (commands_sent) + *commands_sent = mock->commands_sent; + if (responses_returned) + *responses_returned = mock->responses_returned; + + return 0; +} + +CXLMI_EXPORT int cxlmi_mock_get_last_command(struct cxlmi_endpoint *ep, + uint8_t *command_set, + uint8_t *command, + void *payload, + size_t *payload_size) +{ + struct cxlmi_transport_mock *mock; + + if (!ep || !cxlmi_is_mock_endpoint(ep)) { + errno = EINVAL; + return -1; + } + + mock = ep->transport_data; + + if (command_set) + *command_set = mock->last_command_set; + if (command) + *command = mock->last_command; + if (payload_size) { + if (payload && mock->last_payload) { + size_t copy_size = *payload_size < mock->last_payload_size ? + *payload_size : mock->last_payload_size; + memcpy(payload, mock->last_payload, copy_size); + } + *payload_size = mock->last_payload_size; + } + + return 0; +} + +/* Internal: find and consume a matching response */ +static struct cxlmi_mock_response * +mock_find_response(struct cxlmi_transport_mock *mock, + uint8_t command_set, uint8_t command) +{ + struct cxlmi_mock_response *resp; + + list_for_each(&mock->responses, resp, entry) { + if (resp->command_set == command_set && + resp->command == command) { + list_del(&resp->entry); + return resp; + } + } + + return NULL; +} + +/* Internal: called by send_cmd_cci for mock transport */ +int send_mock_cmd(struct cxlmi_endpoint *ep, + struct cxlmi_cci_msg *req_msg, size_t req_msg_sz, + struct cxlmi_cci_msg *rsp_msg, size_t rsp_msg_sz, + size_t rsp_msg_sz_min) +{ + struct cxlmi_transport_mock *mock = ep->transport_data; + struct cxlmi_mock_response *resp; + size_t req_pl_size; + + mock->commands_sent++; + + /* Store last command for verification */ + mock->last_command_set = req_msg->command_set; + mock->last_command = req_msg->command; + + req_pl_size = req_msg->pl_length[0] | + (req_msg->pl_length[1] << 8) | + ((req_msg->pl_length[2] & 0xf) << 16); + + free(mock->last_payload); + mock->last_payload = NULL; + mock->last_payload_size = 0; + + if (req_pl_size > 0) { + mock->last_payload = malloc(req_pl_size); + if (mock->last_payload) { + memcpy(mock->last_payload, req_msg->payload, req_pl_size); + mock->last_payload_size = req_pl_size; + } + } + + /* Find matching response */ + resp = mock_find_response(mock, req_msg->command_set, req_msg->command); + if (!resp) { + /* No response configured - return unsupported */ + cxlmi_msg(ep->ctx, LOG_DEBUG, + "mock: no response for cmd %02x:%02x\n", + req_msg->command_set, req_msg->command); + if (rsp_msg && rsp_msg_sz > 0) { + memset(rsp_msg, 0, rsp_msg_sz); + rsp_msg->command_set = req_msg->command_set; + rsp_msg->command = req_msg->command; + rsp_msg->return_code = CXLMI_RET_UNSUPPORTED; + } + return CXLMI_RET_UNSUPPORTED; + } + + mock->responses_returned++; + + /* Build response message */ + if (rsp_msg && rsp_msg_sz > 0) { + memset(rsp_msg, 0, rsp_msg_sz); + rsp_msg->category = 1; /* Response */ + rsp_msg->tag = req_msg->tag; + rsp_msg->command = req_msg->command; + rsp_msg->command_set = req_msg->command_set; + rsp_msg->return_code = resp->return_code; + + if (resp->payload && resp->payload_size > 0) { + size_t copy_size = resp->payload_size; + if (copy_size > rsp_msg_sz - sizeof(*rsp_msg)) + copy_size = rsp_msg_sz - sizeof(*rsp_msg); + + memcpy(rsp_msg->payload, resp->payload, copy_size); + rsp_msg->pl_length[0] = copy_size & 0xff; + rsp_msg->pl_length[1] = (copy_size >> 8) & 0xff; + rsp_msg->pl_length[2] = (copy_size >> 16) & 0xf; + } + } + + int rc = resp->return_code; + free(resp->payload); + free(resp); + + return rc; +} + +void mock_close(struct cxlmi_endpoint *ep) +{ + struct cxlmi_transport_mock *mock = ep->transport_data; + + if (!mock) + return; + + cxlmi_mock_clear_responses(ep); + free(mock->last_payload); + free(mock); + ep->transport_data = NULL; +} diff --git a/src/cxlmi/mock.h b/src/cxlmi/mock.h new file mode 100644 index 0000000..ea0b826 --- /dev/null +++ b/src/cxlmi/mock.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libcxlmi. + * + * Internal mock transport declarations. + */ +#ifndef _LIBCXLMI_MOCK_H +#define _LIBCXLMI_MOCK_H + +#include +#include + +struct cxlmi_endpoint; +struct cxlmi_cci_msg; + +/* Check if endpoint is a mock endpoint */ +bool cxlmi_is_mock_endpoint(struct cxlmi_endpoint *ep); + +/* Internal send function for mock transport */ +int send_mock_cmd(struct cxlmi_endpoint *ep, + struct cxlmi_cci_msg *req_msg, size_t req_msg_sz, + struct cxlmi_cci_msg *rsp_msg, size_t rsp_msg_sz, + size_t rsp_msg_sz_min); + +/* Internal close function for mock transport */ +void mock_close(struct cxlmi_endpoint *ep); + +#endif /* _LIBCXLMI_MOCK_H */ diff --git a/src/cxlmi/test.h b/src/cxlmi/test.h new file mode 100644 index 0000000..3d0c286 --- /dev/null +++ b/src/cxlmi/test.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libcxlmi. + * + * Mock transport API for testing without hardware. + * This header is separate from libcxlmi.h to keep the public API clean. + */ +#ifndef _LIBCXLMI_TEST_H +#define _LIBCXLMI_TEST_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct cxlmi_ctx; +struct cxlmi_endpoint; + +/** + * cxlmi_open_mock() - Create a mock endpoint for testing. + * @ctx: library context object to create under + * + * Creates a mock endpoint that doesn't require hardware. Use + * cxlmi_mock_set_response() to configure expected responses. + * + * Return: New mock endpoint object, or NULL on failure. + * + * See &cxlmi_mock_set_response, &cxlmi_close + */ +struct cxlmi_endpoint *cxlmi_open_mock(struct cxlmi_ctx *ctx); + +/** + * cxlmi_mock_set_response() - Configure a response for the mock endpoint. + * @ep: Mock endpoint object + * @command_set: Command set (opcode high byte) + * @command: Command (opcode low byte) + * @return_code: CXL return code to return + * @payload: Response payload data (copied, may be NULL) + * @payload_size: Size of payload in bytes + * + * Queues a response to be returned when a matching command is sent. + * Responses are consumed in FIFO order per command. + * + * Return: 0 on success, -1 on failure. + */ +int cxlmi_mock_set_response(struct cxlmi_endpoint *ep, + uint8_t command_set, + uint8_t command, + uint16_t return_code, + void *payload, + size_t payload_size); + +/** + * cxlmi_mock_clear_responses() - Clear all queued mock responses. + * @ep: Mock endpoint object + */ +void cxlmi_mock_clear_responses(struct cxlmi_endpoint *ep); + +/** + * cxlmi_mock_get_stats() - Get mock endpoint statistics. + * @ep: Mock endpoint object + * @commands_sent: Output for number of commands sent (may be NULL) + * @responses_returned: Output for number of responses returned (may be NULL) + * + * Return: 0 on success, -1 on failure. + */ +int cxlmi_mock_get_stats(struct cxlmi_endpoint *ep, + unsigned int *commands_sent, + unsigned int *responses_returned); + +/** + * cxlmi_mock_get_last_command() - Get the last command sent to mock endpoint. + * @ep: Mock endpoint object + * @command_set: Output for command set (may be NULL) + * @command: Output for command (may be NULL) + * @payload: Buffer for payload copy (may be NULL) + * @payload_size: In/out size of payload buffer. On input, the buffer size. + * On output, the actual payload size (0 if no payload). + * + * Return: 0 on success, -1 on failure. + */ +int cxlmi_mock_get_last_command(struct cxlmi_endpoint *ep, + uint8_t *command_set, + uint8_t *command, + void *payload, + size_t *payload_size); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBCXLMI_TEST_H */ diff --git a/src/meson.build b/src/meson.build index ba0c712..e1b2dc6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,7 +7,8 @@ deps = [ libdbus_dep ] sources = [ 'cxlmi/log.c', 'cxlmi/commands.c', - 'cxlmi/cxlmi.c' + 'cxlmi/cxlmi.c', + 'cxlmi/mock.c' ] cxlmi = library('cxlmi', # defaults to shared lib diff --git a/tests/meson.build b/tests/meson.build index 707af54..82f0865 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -8,3 +8,12 @@ executable( dependencies: libcxlmi_dep, include_directories: [inc] ) + +mock_tests = executable( + 'mock-tests', + ['mock-tests.c'], + dependencies: libcxlmi_dep, + include_directories: [inc] +) + +test('mock-tests', mock_tests) diff --git a/tests/mock-tests.c b/tests/mock-tests.c new file mode 100644 index 0000000..45a5ae3 --- /dev/null +++ b/tests/mock-tests.c @@ -0,0 +1,10122 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libcxlmi. + * + * Comprehensive unit tests using the mock transport layer. + */ +#include +#include +#include + +#include + +#include +#include + +static int tests_run = 0; +static int tests_passed = 0; +static int tests_failed = 0; +static const char *current_suite = NULL; + +#define RUN_TEST(test_func) do { \ + tests_run++; \ + printf(" %-60s ", #test_func); \ + fflush(stdout); \ + if (test_func() == 0) { \ + printf("[PASS]\n"); \ + tests_passed++; \ + } else { \ + printf("[FAIL]\n"); \ + tests_failed++; \ + } \ +} while (0) + +#define TEST_SUITE(name) do { \ + current_suite = name; \ + printf("\n%s:\n", name); \ +} while (0) + +#define ASSERT_EQ(actual, expected, msg) do { \ + if ((actual) != (expected)) { \ + fprintf(stderr, "\n ASSERT FAILED: %s (got %d, expected %d)\n", \ + msg, (int)(actual), (int)(expected)); \ + return 1; \ + } \ +} while (0) + +#define ASSERT_TRUE(cond, msg) do { \ + if (!(cond)) { \ + fprintf(stderr, "\n ASSERT FAILED: %s\n", msg); \ + return 1; \ + } \ +} while (0) + +/* Helper: create mock context and endpoint */ +static struct cxlmi_ctx *test_ctx; +static struct cxlmi_endpoint *test_ep; + +/* Helper: set multiple identical responses for retry testing */ +static int mock_set_response_n(struct cxlmi_endpoint *ep, + uint8_t command_set, uint8_t command, + uint16_t return_code, + void *payload, size_t payload_size, + unsigned int count) +{ + unsigned int i; + int rc; + + for (i = 0; i < count; i++) { + rc = cxlmi_mock_set_response(ep, command_set, command, + return_code, payload, payload_size); + if (rc) + return rc; + } + return 0; +} + +static int setup(void) +{ + test_ctx = cxlmi_new_ctx(stderr, LOG_ERR); + if (!test_ctx) + return -1; + test_ep = cxlmi_open_mock(test_ctx); + if (!test_ep) { + cxlmi_free_ctx(test_ctx); + return -1; + } + return 0; +} + +static void teardown(void) +{ + if (test_ep) + cxlmi_close(test_ep); + if (test_ctx) + cxlmi_free_ctx(test_ctx); + test_ep = NULL; + test_ctx = NULL; +} + +/* ============================================================ + * Mock Infrastructure Tests + * ============================================================ */ + +static int test_mock_create_close(void) +{ + struct cxlmi_ctx *ctx; + struct cxlmi_endpoint *ep; + + ctx = cxlmi_new_ctx(stderr, LOG_ERR); + ASSERT_TRUE(ctx != NULL, "failed to create context"); + + ep = cxlmi_open_mock(ctx); + ASSERT_TRUE(ep != NULL, "failed to create mock endpoint"); + + cxlmi_close(ep); + cxlmi_free_ctx(ctx); + return 0; +} + +static int test_mock_no_response_returns_unsupported(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_UNSUPPORTED, "expected UNSUPPORTED"); + return 0; +} + +static int test_mock_stats_tracking(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + unsigned int sent, returned; + + ASSERT_EQ(setup(), 0, "setup failed"); + + cxlmi_mock_get_stats(test_ep, &sent, &returned); + ASSERT_EQ(sent, 0, "initial sent should be 0"); + + cxlmi_cmd_identify(test_ep, NULL, &id); + cxlmi_mock_get_stats(test_ep, &sent, &returned); + ASSERT_EQ(sent, 1, "sent should be 1"); + + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + cxlmi_cmd_identify(test_ep, NULL, &id); + cxlmi_mock_get_stats(test_ep, &sent, &returned); + ASSERT_EQ(sent, 2, "sent should be 2"); + ASSERT_EQ(returned, 1, "returned should be 1"); + + teardown(); + return 0; +} + +static int test_mock_clear_responses(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + cxlmi_mock_clear_responses(test_ep); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + + teardown(); + ASSERT_EQ(rc, CXLMI_RET_UNSUPPORTED, "expected UNSUPPORTED after clear"); + return 0; +} + +static int test_mock_payload_size_zero_when_no_payload(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + uint8_t payload[16]; + size_t payload_size; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* identify has no request payload */ + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + payload_size = sizeof(payload); + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + + teardown(); + ASSERT_EQ(payload_size, 0, "payload_size should be 0 for no-payload commands"); + return 0; +} + +static int test_mock_response_sequence(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* Queue: BUSY, BUSY, SUCCESS - simulates retry scenario */ + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_BUSY, &rsp, sizeof(rsp)); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_BUSY, &rsp, sizeof(rsp)); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_BUSY, "first call should return BUSY"); + + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_BUSY, "second call should return BUSY"); + + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "third call should return SUCCESS"); + + /* Fourth call should return UNSUPPORTED (no more responses) */ + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_UNSUPPORTED, "fourth call should return UNSUPPORTED"); + + teardown(); + return 0; +} + +static int test_mock_response_sequence_helper(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + unsigned int sent, returned; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* Use helper to queue 3 identical responses */ + rc = mock_set_response_n(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp), 3); + ASSERT_EQ(rc, 0, "failed to queue responses"); + + cxlmi_cmd_identify(test_ep, NULL, &id); + cxlmi_cmd_identify(test_ep, NULL, &id); + cxlmi_cmd_identify(test_ep, NULL, &id); + + cxlmi_mock_get_stats(test_ep, &sent, &returned); + ASSERT_EQ(sent, 3, "sent should be 3"); + ASSERT_EQ(returned, 3, "returned should be 3"); + + /* Fourth call exhausts responses */ + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_UNSUPPORTED, "should be UNSUPPORTED after exhausting responses"); + + teardown(); + return 0; +} + +/* ============================================================ + * Generic Component Commands (Info/Status - 0x00) + * ============================================================ */ + +static int test_cmd_identify(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, ret; + int rc; + + rsp.vendor_id = 0x1234; + rsp.device_id = 0x5678; + rsp.serial_num = 0xDEADBEEFCAFEBABE; + rsp.component_type = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.vendor_id, 0x1234, "vendor_id mismatch"); + ASSERT_EQ(ret.device_id, 0x5678, "device_id mismatch"); + ASSERT_EQ(ret.component_type, 0x03, "component_type mismatch"); + return 0; +} + +static int test_cmd_bg_op_status(void) +{ + struct cxlmi_cmd_bg_op_status_rsp rsp = {0}, ret; + int rc; + + rsp.status = 0x01; + rsp.opcode = 0x4304; + rsp.returncode = 0x0000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x02, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_bg_op_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.status, 0x01, "status mismatch"); + return 0; +} + +static int test_cmd_get_response_msg_limit(void) +{ + struct cxlmi_cmd_get_response_msg_limit_rsp rsp = {0}, ret; + int rc; + + rsp.limit = 10; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_response_msg_limit(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.limit, 10, "limit mismatch"); + return 0; +} + +static int test_cmd_set_response_msg_limit(void) +{ + struct cxlmi_cmd_set_response_msg_limit_req req = {0}; + struct cxlmi_cmd_set_response_msg_limit_rsp rsp = {0}, ret; + int rc; + + req.limit = 8; + rsp.limit = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x04, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_set_response_msg_limit(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.limit, 8, "limit mismatch"); + return 0; +} + +static int test_cmd_request_bg_op_abort(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_request_bg_op_abort(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Events Commands (0x01) + * ============================================================ */ + +static int test_cmd_get_event_records(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp rsp = {0}, ret = {0}; + int rc; + + req.event_log = 0; /* Info log */ + rsp.flags = 0; + rsp.record_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.record_count, 0, "record_count mismatch"); + return 0; +} + +static int test_cmd_get_event_interrupt_policy(void) +{ + struct cxlmi_cmd_get_event_interrupt_policy_rsp rsp = {0}, ret; + int rc; + + rsp.informational_settings = 0x01; + rsp.warning_settings = 0x02; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x02, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_interrupt_policy(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.informational_settings, 0x01, "info settings mismatch"); + return 0; +} + +static int test_cmd_set_event_interrupt_policy(void) +{ + struct cxlmi_cmd_set_event_interrupt_policy_req req = {0}; + int rc; + + req.informational_settings = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_event_interrupt_policy(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Firmware Update Commands (0x02) + * ============================================================ */ + +static int test_cmd_get_fw_info(void) +{ + struct cxlmi_cmd_get_fw_info_rsp rsp = {0}, ret; + int rc; + + rsp.slots_supported = 2; + rsp.slot_info = 0x01; + memcpy(rsp.fw_rev1, "1.0.0", 5); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_fw_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.slots_supported, 2, "slots_supported mismatch"); + return 0; +} + +static int test_cmd_activate_fw(void) +{ + struct cxlmi_cmd_activate_fw_req req = {0}; + int rc; + + req.action = 0x01; + req.slot = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Timestamp Commands (0x03) + * ============================================================ */ + +static int test_cmd_get_timestamp(void) +{ + struct cxlmi_cmd_get_timestamp_rsp rsp = {0}, ret; + int rc; + + rsp.timestamp = 0x123456789ABCDEF0ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_timestamp(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.timestamp == 0x123456789ABCDEF0ULL, "timestamp mismatch"); + return 0; +} + +static int test_cmd_set_timestamp(void) +{ + struct cxlmi_cmd_set_timestamp_req req = {0}; + int rc; + + req.timestamp = 0x123456789ABCDEF0ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_timestamp(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Logs Commands (0x04) + * ============================================================ */ + +static int test_cmd_get_supported_logs(void) +{ + struct cxlmi_cmd_get_supported_logs_rsp *rsp, *ret; + size_t struct_sz; + int rc; + + /* Allocate space for header + 2 entries (flexible array member) */ + struct_sz = sizeof(*rsp) + 2 * sizeof(rsp->entries[0]); + rsp = calloc(1, struct_sz); + ret = calloc(1, struct_sz); + if (!rsp || !ret) { + free(rsp); + free(ret); + return 1; + } + + rsp->num_supported_log_entries = 2; + /* Fill in some test UUIDs */ + memset(rsp->entries[0].uuid, 0xAA, sizeof(rsp->entries[0].uuid)); + rsp->entries[0].log_size = 0x100; + memset(rsp->entries[1].uuid, 0xBB, sizeof(rsp->entries[1].uuid)); + rsp->entries[1].log_size = 0x200; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, rsp, struct_sz); + rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 2, "num entries mismatch"); + ASSERT_EQ(ret->entries[0].log_size, 0x100, "entry 0 log_size mismatch"); + ASSERT_EQ(ret->entries[1].log_size, 0x200, "entry 1 log_size mismatch"); + + free(rsp); + free(ret); + return 0; +} + +static int test_cmd_get_log_capabilities(void) +{ + struct cxlmi_cmd_get_log_capabilities_req req = {0}; + struct cxlmi_cmd_get_log_capabilities_rsp rsp = {0}, ret; + int rc; + + rsp.parameter_flags = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x02, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_log_capabilities(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.parameter_flags, 0x01, "flags mismatch"); + return 0; +} + +static int test_cmd_clear_log(void) +{ + struct cxlmi_cmd_clear_log_req req = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_clear_log(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_populate_log(void) +{ + struct cxlmi_cmd_populate_log_req req = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_populate_log(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Memory Device Identify (0x40) + * ============================================================ */ + +static int test_cmd_memdev_identify(void) +{ + struct cxlmi_cmd_memdev_identify_rsp rsp = {0}, ret; + int rc; + + rsp.total_capacity = 0x1000; + rsp.volatile_capacity = 0x800; + rsp.persistent_capacity = 0x800; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x40, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.total_capacity == 0x1000, "total_capacity mismatch"); + return 0; +} + +/* ============================================================ + * CCLS Commands (0x41) + * ============================================================ */ + +static int test_cmd_memdev_get_partition_info(void) +{ + struct cxlmi_cmd_memdev_get_partition_info_rsp rsp = {0}, ret; + int rc; + + rsp.active_vmem = 0x100; + rsp.active_pmem = 0x200; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_partition_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.active_vmem == 0x100, "active_vmem mismatch"); + return 0; +} + +static int test_cmd_memdev_set_partition_info(void) +{ + struct cxlmi_cmd_memdev_set_partition_info_req req = {0}; + int rc; + + req.volatile_capacity = 0x100; + req.flags = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_partition_info(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Health Info/Alerts Commands (0x42) + * ============================================================ */ + +static int test_cmd_memdev_get_health_info(void) +{ + struct cxlmi_cmd_memdev_get_health_info_rsp rsp = {0}, ret; + int rc; + + rsp.health_status = 0x01; + rsp.media_status = 0x00; + rsp.life_used = 10; + rsp.device_temperature = 350; /* 35.0 C */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_health_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.health_status, 0x01, "health_status mismatch"); + ASSERT_EQ(ret.life_used, 10, "life_used mismatch"); + return 0; +} + +static int test_cmd_memdev_get_alert_config(void) +{ + struct cxlmi_cmd_memdev_get_alert_config_rsp rsp = {0}, ret; + int rc; + + rsp.valid_alerts = 0xFF; + rsp.life_used_critical_alert_threshold = 90; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_alert_config(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.valid_alerts, 0xFF, "valid_alerts mismatch"); + return 0; +} + +static int test_cmd_memdev_set_alert_config(void) +{ + struct cxlmi_cmd_memdev_set_alert_config_req req = {0}; + int rc; + + req.valid_alert_actions = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_alert_config(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_get_shutdown_state(void) +{ + struct cxlmi_cmd_memdev_get_shutdown_state_rsp rsp = {0}, ret; + int rc; + + rsp.state = 0x00; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_shutdown_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.state, 0x00, "state mismatch"); + return 0; +} + +static int test_cmd_memdev_set_shutdown_state(void) +{ + struct cxlmi_cmd_memdev_set_shutdown_state_req req = {0}; + int rc; + + req.state = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_shutdown_state(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Media and Poison Commands (0x43) + * ============================================================ */ + +static int test_cmd_memdev_inject_poison(void) +{ + struct cxlmi_cmd_memdev_inject_poison_req req = {0}; + int rc; + + req.inject_poison_phy_addr = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_inject_poison(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_clear_poison(void) +{ + struct cxlmi_cmd_memdev_clear_poison_req req = {0}; + int rc; + + req.clear_poison_phy_addr = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_clear_poison(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_get_scan_media_capabilities(void) +{ + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp rsp = {0}, ret; + int rc; + + req.get_scan_media_capabilities_start_physaddr = 0x0; + req.get_scan_media_capabilities_physaddr_length = 0x10000; + rsp.estimated_scan_media_time = 1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_scan_media_capabilities(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.estimated_scan_media_time, 1000, "time mismatch"); + return 0; +} + +static int test_cmd_scan_media(void) +{ + struct cxlmi_cmd_scan_media_req req = {0}; + int rc; + + req.scan_media_physaddr = 0x0; + req.scan_media_physaddr_length = 0x10000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x04, CXLMI_RET_BACKGROUND, NULL, 0); + rc = cxlmi_cmd_scan_media(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_BACKGROUND, "expected background op"); + return 0; +} + +/* ============================================================ + * Sanitize Commands (0x44) + * ============================================================ */ + +static int test_cmd_memdev_sanitize(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x00, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_sanitize(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_secure_erase(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_secure_erase(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Security Commands (0x45) + * ============================================================ */ + +static int test_cmd_memdev_get_security_state(void) +{ + struct cxlmi_cmd_memdev_get_security_state_rsp rsp = {0}, ret; + int rc; + + rsp.security_state = 0x00; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_security_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.security_state, 0x00, "security_state mismatch"); + return 0; +} + +static int test_cmd_memdev_freeze_security_state(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_freeze_security_state(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * SLD QoS Commands (0x47) + * ============================================================ */ + +static int test_cmd_memdev_get_sld_qos_control(void) +{ + struct cxlmi_cmd_memdev_get_sld_qos_control_rsp rsp = {0}, ret; + int rc; + + rsp.qos_telemetry_control = 0x01; + rsp.egress_moderate_percentage = 50; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_sld_qos_control(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x01, "qos_telemetry_control mismatch"); + return 0; +} + +static int test_cmd_memdev_set_sld_qos_control(void) +{ + struct cxlmi_cmd_memdev_set_sld_qos_control_req req = {0}; + int rc; + + req.qos_telemetry_control = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_sld_qos_control(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_get_sld_qos_status(void) +{ + struct cxlmi_cmd_memdev_get_sld_qos_status_rsp rsp = {0}, ret; + int rc; + + rsp.backpressure_avg_percentage = 25; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x02, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_sld_qos_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.backpressure_avg_percentage, 25, "percentage mismatch"); + return 0; +} + +/* ============================================================ + * DCD Config Commands (0x48) + * ============================================================ */ + +static int test_cmd_memdev_get_dc_config(void) +{ + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp rsp = {0}, ret; + int rc; + + req.region_cnt = 8; + req.start_region_id = 0; + rsp.num_regions = 2; + rsp.regions_returned = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_regions, 2, "num_regions mismatch"); + return 0; +} + +/* ============================================================ + * FM-API Physical Switch Commands (0x51) + * ============================================================ */ + +static int test_cmd_fmapi_identify_sw_device(void) +{ + struct cxlmi_cmd_fmapi_identify_sw_device_rsp rsp = {0}, ret; + int rc; + + rsp.ingress_port_id = 0; + rsp.num_physical_ports = 8; + rsp.num_vcs = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_identify_sw_device(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_physical_ports, 8, "num_physical_ports mismatch"); + return 0; +} + +static int test_cmd_fmapi_phys_port_control(void) +{ + struct cxlmi_cmd_fmapi_phys_port_control_req req = {0}; + int rc; + + req.ppb_id = 0; + req.port_opcode = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_phys_port_control(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * FM-API Virtual Switch Commands (0x52) + * ============================================================ */ + +static int test_cmd_fmapi_bind_vppb(void) +{ + struct cxlmi_cmd_fmapi_bind_vppb_req req = {0}; + int rc; + + req.vcs_id = 0; + req.vppb_id = 0; + req.port_id = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x52, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_bind_vppb(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_unbind_vppb(void) +{ + struct cxlmi_cmd_fmapi_unbind_vppb_req req = {0}; + int rc; + + req.vcs_id = 0; + req.vppb_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x52, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_unbind_vppb(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * FM-API MLD Components Commands (0x54) + * ============================================================ */ + +static int test_cmd_fmapi_get_ld_info(void) +{ + struct cxlmi_cmd_fmapi_get_ld_info_rsp rsp = {0}, ret; + int rc; + + rsp.memory_size = 0x100000000ULL; + rsp.ld_count = 16; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_ld_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.ld_count, 16, "ld_count mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_qos_control(void) +{ + struct cxlmi_cmd_fmapi_get_qos_control_rsp rsp = {0}, ret; + int rc; + + rsp.qos_telemetry_control = 0x01; + rsp.egress_moderate_percentage = 50; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_control(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x01, "qos_telemetry_control mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_qos_status(void) +{ + struct cxlmi_cmd_fmapi_get_qos_status_rsp rsp = {0}, ret; + int rc; + + rsp.backpressure_avg_percentage = 10; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x05, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.backpressure_avg_percentage, 10, "percentage mismatch"); + return 0; +} + +/* ============================================================ + * FM-API DCD Management Commands (0x56) + * ============================================================ */ + +static int test_cmd_fmapi_get_dcd_info(void) +{ + struct cxlmi_cmd_fmapi_get_dcd_info_rsp rsp = {0}, ret; + int rc; + + rsp.num_hosts = 4; + rsp.num_supported_dc_regions = 8; + rsp.total_dynamic_capacity = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dcd_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_hosts, 4, "num_hosts mismatch"); + return 0; +} + +/* ============================================================ + * Additional Logs Commands (0x04) + * ============================================================ */ + +static int test_cmd_get_log(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + uint8_t ret_buf[64] = {0}; + uint8_t rsp_data[32]; + int rc; + + /* Fill response with pattern */ + for (int i = 0; i < 32; i++) + rsp_data[i] = i; + + memset(req.uuid, 0xAA, sizeof(req.uuid)); + req.offset = 0; + req.length = 32; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + rsp_data, sizeof(rsp_data)); + rc = cxlmi_cmd_get_log(test_ep, NULL, &req, ret_buf); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret_buf[0], 0, "ret_buf[0] mismatch"); + ASSERT_EQ(ret_buf[31], 31, "ret_buf[31] mismatch"); + return 0; +} + +static int test_cmd_get_log_cel(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + struct cxlmi_cmd_get_log_cel_rsp rsp = {0}; + struct cxlmi_cmd_get_log_cel_rsp *ret; + int rc; + + rsp.opcode = 0x0001; /* Identify */ + rsp.command_effect = 0x0000; + + req.offset = 0; + req.length = sizeof(rsp); + + ret = calloc(1, sizeof(*ret) + req.length); + ASSERT_TRUE(ret != NULL, "allocation failed"); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + free(ret); + return 0; +} + +static int test_cmd_get_supported_logs_sublist(void) +{ + struct cxlmi_cmd_get_supported_logs_sublist_req req = {0}; + struct cxlmi_cmd_get_supported_logs_sublist_rsp rsp = {0}; + /* Allocate storage for entries - lib will copy entries into ret */ + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_logs_sublist_rsp) + + 8 * sizeof(struct cxlmi_supported_log_entry)]; + struct cxlmi_cmd_get_supported_logs_sublist_rsp *ret = + (struct cxlmi_cmd_get_supported_logs_sublist_rsp *)ret_buf; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + req.max_supported_log_entries = 8; + req.start_log_entry_index = 0; + + rsp.num_supported_log_entries = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_supported_logs_sublist(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 2, "num entries mismatch"); + return 0; +} + +/* ============================================================ + * Features Commands (0x05) + * ============================================================ */ + +static int test_cmd_get_supported_features(void) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + /* Response buffer with storage for 3 entries (each 48 bytes) */ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_get_supported_features_rsp) + 3 * 48]; + struct cxlmi_cmd_get_supported_features_rsp *rsp = + (struct cxlmi_cmd_get_supported_features_rsp *)rsp_buf; + /* Return buffer with storage for entries */ + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_features_rsp) + 3 * 48]; + struct cxlmi_cmd_get_supported_features_rsp *ret = + (struct cxlmi_cmd_get_supported_features_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.count = 3 * 48; + req.starting_feature_index = 0; + + rsp->num_supported_feature_entries = 3; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, + &rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_feature_entries, 3, "num features mismatch"); + return 0; +} + +static int test_cmd_get_feature(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}, ret; + int rc; + + memset(req.feature_id, 0xBB, sizeof(req.feature_id)); + req.offset = 0; + req.count = 32; + req.selection = 0; /* Current value */ + + rsp.feature_data[0] = 0x12; + rsp.feature_data[1] = 0x34; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.feature_data[0], 0x12, "feature data mismatch"); + return 0; +} + +static int test_cmd_set_feature(void) +{ + struct cxlmi_cmd_set_feature_req req = {0}; + int rc; + + memset(req.feature_id, 0xCC, sizeof(req.feature_id)); + req.set_feature_flags = 0x01; + req.offset = 0; + req.version = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_feature(test_ep, NULL, &req, 0); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional CCLS Commands (0x41) + * ============================================================ */ + +static int test_cmd_memdev_get_lsa(void) +{ + struct cxlmi_cmd_memdev_get_lsa_req req = {0}; + uint8_t ret_buf[64] = {0}; + uint8_t rsp_data[32]; + int rc; + + for (int i = 0; i < 32; i++) + rsp_data[i] = 0xA0 + i; + + req.offset = 0; + req.length = 32; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x02, CXLMI_RET_SUCCESS, + rsp_data, sizeof(rsp_data)); + rc = cxlmi_cmd_memdev_get_lsa(test_ep, NULL, &req, ret_buf); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret_buf[0], 0xA0, "lsa data mismatch"); + return 0; +} + +static int test_cmd_memdev_set_lsa(void) +{ + /* req struct has flexible array member for data */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_set_lsa_req) + 32]; + struct cxlmi_cmd_memdev_set_lsa_req *req = + (struct cxlmi_cmd_memdev_set_lsa_req *)req_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->offset = 0; + memset(req->data, 0xAA, 32); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_lsa(test_ep, NULL, req, 32); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional Media/Poison Commands (0x43) + * ============================================================ */ + +static int test_cmd_get_poison_list(void) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp rsp = {0}, ret; + int rc; + + req.get_poison_list_phy_addr = 0x0; + req.get_poison_list_phy_addr_len = 0x100000; + + rsp.poison_list_flags = 0x01; + rsp.overflow_timestamp = 0x123456789ABCDEF0ULL; + rsp.more_err_media_record_cnt = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.poison_list_flags, 0x01, "flags mismatch"); + return 0; +} + +static int test_cmd_get_scan_media_results(void) +{ + struct cxlmi_cmd_get_scan_media_results_rsp rsp = {0}, ret; + int rc; + + rsp.scan_media_restart_physaddr = 0x1000; + rsp.scan_media_restart_physaddr_length = 0x500; + rsp.scan_media_flags = 0x01; + rsp.media_error_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_scan_media_results(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.scan_media_flags, 0x01, "flags mismatch"); + return 0; +} + +/* ============================================================ + * Additional DCD Commands (0x48) + * ============================================================ */ + +static int test_cmd_memdev_get_dc_extent_list(void) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp rsp = {0}, ret; + int rc; + + req.extent_cnt = 8; + req.start_extent_idx = 0; + + /* Return 0 extents to avoid accessing flexible array member */ + rsp.num_extents_returned = 0; + rsp.total_num_extents = 5; + rsp.generation_num = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.total_num_extents, 5, "total extents mismatch"); + return 0; +} + +static int test_cmd_memdev_add_dc_response(void) +{ + struct cxlmi_cmd_memdev_add_dc_response_req req = {0}; + int rc; + + req.updated_extent_list_size = 0; + req.flags = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_add_dc_response(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_release_dc(void) +{ + struct cxlmi_cmd_memdev_release_dc_req req = {0}; + int rc; + + req.updated_extent_list_size = 0; + req.flags = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_release_dc(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional FM-API Physical Switch Commands (0x51) + * ============================================================ */ + +static int test_cmd_fmapi_get_phys_port_state(void) +{ + /* Request struct has flexible array for ports */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_req) + 1]; + struct cxlmi_cmd_fmapi_get_phys_port_state_req *req = + (struct cxlmi_cmd_fmapi_get_phys_port_state_req *)req_buf; + /* Response struct has flexible array for port_info_list */ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + 64]; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + 64]; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *ret = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->num_ports = 1; + req->ports[0] = 0; + + rsp->num_ports = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_phys_port_state(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_ports, 1, "num_ports mismatch"); + return 0; +} + +/* ============================================================ + * Additional FM-API MLD Components Commands (0x54) + * ============================================================ */ + +static int test_cmd_fmapi_get_ld_allocations(void) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + /* Response has flexible array for ld_allocation_list */ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 4 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 4 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.start_ld_id = 0; + req.ld_allocation_list_limit = 4; + + rsp->number_ld = 2; + rsp->ld_allocation_list_len = 2; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_ld_allocations(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld mismatch"); + return 0; +} + +static int test_cmd_fmapi_set_qos_control(void) +{ + struct cxlmi_cmd_fmapi_set_qos_control_req req = {0}; + struct cxlmi_cmd_fmapi_set_qos_control_rsp rsp = {0}, ret; + int rc; + + req.qos_telemetry_control = 0x01; + req.egress_moderate_percentage = 50; + req.egress_severe_percentage = 80; + + rsp.qos_telemetry_control = 0x01; + rsp.egress_moderate_percentage = 50; + rsp.egress_severe_percentage = 80; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_set_qos_control(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x01, "telemetry control mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_qos_allocated_bw(void) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req req = {0}; + /* Response has flexible array for qos_allocation_fraction */ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.number_ld = 2; + req.start_ld_id = 0; + + rsp->number_ld = 2; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x06, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld mismatch"); + return 0; +} + +static int test_cmd_fmapi_set_qos_allocated_bw(void) +{ + /* Both req and rsp have flexible arrays for qos_allocation_fraction */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *req = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 1; + req->start_ld_id = 0; + + rsp->number_ld = 1; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x07, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_allocated_bw(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 1, "number_ld mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_qos_bw_limit(void) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req req = {0}; + /* Response has flexible array for qos_bw_limit_fraction */ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.number_ld = 2; + req.start_ld_id = 0; + + rsp->number_ld = 2; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x08, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld mismatch"); + return 0; +} + +static int test_cmd_fmapi_set_qos_bw_limit(void) +{ + /* Both req and rsp have flexible arrays for qos_bw_limit_fraction */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_req) + 4]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *req = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 1; + req->start_ld_id = 0; + + rsp->number_ld = 1; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x09, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_bw_limit(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 1, "number_ld mismatch"); + return 0; +} + +/* ============================================================ + * FM-API Multi-Headed Commands (0x55) + * ============================================================ */ + +static int test_cmd_fmapi_get_multiheaded_info(void) +{ + struct cxlmi_cmd_fmapi_get_multiheaded_info_req req = {0}; + struct cxlmi_cmd_fmapi_get_multiheaded_info_rsp rsp = {0}, ret; + int rc; + + req.start_ld_id = 0; + req.ld_map_list_limit = 4; + + rsp.num_lds = 2; + rsp.num_heads = 2; + rsp.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_multiheaded_info(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_heads, 2, "num_heads mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_head_info(void) +{ + struct cxlmi_cmd_fmapi_get_head_info_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_head_info_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_get_head_info_blkfmt)]; + struct cxlmi_cmd_fmapi_get_head_info_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_head_info_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_head_info_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_get_head_info_blkfmt)]; + struct cxlmi_cmd_fmapi_get_head_info_rsp *ret = + (struct cxlmi_cmd_fmapi_get_head_info_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.start_head = 0; + req.num_heads = 2; + + rsp->num_heads = 2; + rsp->head_info_list[0].port_num = 1; + rsp->head_info_list[0].ltssm_state = 0x10; + rsp->head_info_list[1].port_num = 2; + rsp->head_info_list[1].ltssm_state = 0x10; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_head_info(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_heads, 2, "num_heads mismatch"); + ASSERT_EQ(ret->head_info_list[0].port_num, 1, "head[0].port_num mismatch"); + ASSERT_EQ(ret->head_info_list[1].port_num, 2, "head[1].port_num mismatch"); + return 0; +} + +/* ============================================================ + * FM-API DCD Management Commands (0x56) + * ============================================================ */ + +static int test_cmd_fmapi_get_dc_reg_config(void) +{ + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp rsp = {0}, ret; + int rc; + + req.host_id = 0; + req.region_cnt = 4; + req.start_region_id = 0; + + rsp.num_regions = 2; + rsp.regions_returned = 2; + rsp.num_extents_available = 10; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dc_reg_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_regions, 2, "num_regions mismatch"); + return 0; +} + +static int test_cmd_fmapi_set_dc_region_config(void) +{ + struct cxlmi_cmd_fmapi_set_dc_region_config_req req = {0}; + int rc; + + req.region_id = 0; + req.block_sz = 0x10000; + req.sanitize_on_release = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_set_dc_region_config(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_get_dc_region_ext_list(void) +{ + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp rsp = {0}, ret; + int rc; + + req.host_id = 0; + req.extent_count = 8; + req.start_ext_index = 0; + + /* Return 0 extents to avoid accessing flexible array member */ + rsp.extents_returned = 0; + rsp.total_extents = 5; + rsp.list_generation_num = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.total_extents, 5, "total extents mismatch"); + return 0; +} + +static int test_cmd_fmapi_initiate_dc_add(void) +{ + struct cxlmi_cmd_fmapi_initiate_dc_add_req req = {0}; + int rc; + + req.host_id = 0; + req.selection_policy = 0; + req.region_num = 0; + req.length = 0x10000; + req.ext_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_initiate_dc_add(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_initiate_dc_release(void) +{ + struct cxlmi_cmd_fmapi_initiate_dc_release_req req = {0}; + int rc; + + req.host_id = 0; + req.flags = 0; + req.ext_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_initiate_dc_release(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional Events Commands (0x01) + * ============================================================ */ + +static int test_cmd_clear_event_records(void) +{ + struct cxlmi_cmd_clear_event_records_req req = {0}; + int rc; + + req.event_log = 0x01; /* Informational log */ + req.clear_flags = 0x01; /* Clear all */ + req.nr_recs = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_clear_event_records(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_get_mctp_event_interrupt_policy(void) +{ + struct cxlmi_cmd_get_mctp_event_interrupt_policy_rsp rsp = {0}, ret; + int rc; + + rsp.event_interrupt_settings = 0x1234; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_mctp_event_interrupt_policy(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.event_interrupt_settings, 0x1234, "settings mismatch"); + return 0; +} + +static int test_cmd_set_mctp_event_interrupt_policy(void) +{ + struct cxlmi_cmd_set_mctp_event_interrupt_policy_req req = {0}; + int rc; + + req.event_interrupt_settings = 0x5678; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_mctp_event_interrupt_policy(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_event_notification(void) +{ + struct cxlmi_cmd_event_notification_req req = {0}; + int rc; + + req.event = 0x0001; /* Event notification */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x06, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_event_notification(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional Firmware Commands (0x02) + * ============================================================ */ + +static int test_cmd_transfer_fw(void) +{ + /* transfer_fw req has flexible array for data */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_transfer_fw_req) + 64]; + struct cxlmi_cmd_transfer_fw_req *req = + (struct cxlmi_cmd_transfer_fw_req *)req_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->action = 0x01; /* Full transfer */ + req->slot = 0x01; + req->offset = 0; + memset(req->data, 0xAA, 64); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_transfer_fw(test_ep, NULL, req, 64); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional Security Commands (0x45) + * ============================================================ */ + +static int test_cmd_memdev_set_passphrase(void) +{ + struct cxlmi_cmd_memdev_set_passphrase_req req = {0}; + int rc; + + req.passphrase_type = 0x01; /* User passphrase */ + memset(req.current_passphrase, 0x11, sizeof(req.current_passphrase)); + memset(req.new_passphrase, 0x22, sizeof(req.new_passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_passphrase(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_disable_passphrase(void) +{ + struct cxlmi_cmd_memdev_disable_passphrase_req req = {0}; + int rc; + + req.passphrase_type = 0x01; + memset(req.passphrase, 0x33, sizeof(req.passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_disable_passphrase(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_unlock(void) +{ + struct cxlmi_cmd_memdev_unlock_req req = {0}; + int rc; + + memset(req.current_passphrase, 0x44, sizeof(req.current_passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_unlock(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_passphrase_secure_erase(void) +{ + struct cxlmi_cmd_memdev_passphrase_secure_erase_req req = {0}; + int rc; + + req.passphrase_type = 0x01; + memset(req.passphrase, 0x55, sizeof(req.passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_passphrase_secure_erase(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_memdev_security_send(void) +{ + /* req struct has flexible array member for data */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_security_send_req) + 32]; + struct cxlmi_cmd_memdev_security_send_req *req = + (struct cxlmi_cmd_memdev_security_send_req *)req_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->security_protocol = 0x01; + req->sp_specific = 0x1234; + memset(req->data, 0xAA, 32); + + ASSERT_EQ(setup(), 0, "setup failed"); + /* Security command set 0x46, Security Send opcode 0x00 */ + cxlmi_mock_set_response(test_ep, 0x46, 0x00, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_security_send(test_ep, NULL, req, 32); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional Media Operations Commands (0x44) + * ============================================================ */ + +static int test_cmd_memdev_media_operations_discovery(void) +{ + struct cxlmi_cmd_memdev_media_operations_discovery_req req = {0}; + struct cxlmi_cmd_memdev_media_operations_discovery_rsp rsp = {0}, ret; + int rc; + + req.media_operation_class = 0x00; + req.media_operation_subclass = 0x00; + req.dpa_range_count = 0; + req.discovery_osa.start_index = 0; + req.discovery_osa.num_ops = 4; + + rsp.dpa_range_granularity = 0x1000; + rsp.total_supported_ops = 2; + rsp.num_supported_ops = 0; /* Return 0 to avoid flexible array */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_media_operations_discovery(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.total_supported_ops, 2, "total_supported_ops mismatch"); + return 0; +} + +static int test_cmd_memdev_media_operations_sanitize(void) +{ + struct cxlmi_cmd_memdev_media_operations_sanitize_req req = {0}; + int rc; + + req.media_operation_class = 0x00; + req.media_operation_subclass = 0x00; + req.dpa_range_count = 0; /* No DPA ranges */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_media_operations_sanitize(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional FM-API Physical Switch Commands (0x51) + * ============================================================ */ + +static int test_cmd_fmapi_send_ppb_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_rsp rsp = {0}, ret; + int rc; + + req.ppb_id = 0x01; + req.transaction_data = 0x12345678; + + rsp.return_data = 0xABCDEF01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ppb_cxlio_config_request(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.return_data, 0xABCDEF01, "return_data mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_domain_validation_sv_state(void) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_state_rsp rsp = {0}, ret; + int rc; + + rsp.secret_value_state = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_domain_validation_sv_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_state, 0x01, "state mismatch"); + return 0; +} + +static int test_cmd_fmapi_set_domain_validation_sv(void) +{ + struct cxlmi_cmd_fmapi_set_domain_validation_sv_req req = {0}; + int rc; + + memset(req.secret_value_uuid, 0xAA, sizeof(req.secret_value_uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_set_domain_validation_sv(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_get_vcs_domain_validation_sv_state(void) +{ + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_req req = {0}; + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_rsp rsp = {0}, ret; + int rc; + + req.vcs_id = 0x01; + rsp.secret_value_state = 0x02; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x06, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_state, 0x02, "state mismatch"); + return 0; +} + +static int test_cmd_fmapi_get_domain_validation_sv(void) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_req req = {0}; + struct cxlmi_cmd_fmapi_get_domain_validation_sv_rsp rsp = {0}, ret; + int rc; + + req.vcs_id = 0x01; + memset(rsp.secret_value_uuid, 0xBB, sizeof(rsp.secret_value_uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x07, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_domain_validation_sv(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_uuid[0], 0xBB, "uuid mismatch"); + return 0; +} + +/* ============================================================ + * Additional FM-API MLD Port Commands (0x53) + * ============================================================ */ + +static int test_cmd_fmapi_send_ld_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_rsp rsp = {0}, ret; + int rc; + + req.ppb_id = 0x01; + req.ld_id = 0x0002; + req.transaction_data = 0x12345678; + + rsp.return_data = 0xABCDEF01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_config_request(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.return_data, 0xABCDEF01, "return_data mismatch"); + return 0; +} + +static int test_cmd_fmapi_send_ld_cxlio_mem_request(void) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp rsp = {0}, ret; + int rc; + + req.port_id = 0x01; + req.ld_id = 0x0002; + req.transaction_len = 0; + req.transaction_addr = 0x1000; + + rsp.return_size = 0; /* No return data to avoid flexible array */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional FM-API MLD Components Commands (0x54) + * ============================================================ */ + +static int test_cmd_fmapi_set_ld_allocations(void) +{ + struct cxlmi_cmd_fmapi_set_ld_allocations_req req = {0}; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp rsp = {0}, ret; + int rc; + + req.number_ld = 0; /* No LD allocations to avoid flexible array */ + req.start_ld_id = 0; + + rsp.number_ld = 0; + rsp.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_set_ld_allocations(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* ============================================================ + * Additional FM-API DCD Commands (0x56) + * ============================================================ */ + +static int test_cmd_fmapi_dc_add_reference(void) +{ + struct cxlmi_cmd_fmapi_dc_add_ref_req req = {0}; + int rc; + + memset(req.tag, 0xAA, sizeof(req.tag)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x06, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_dc_add_reference(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_dc_remove_reference(void) +{ + struct cxlmi_cmd_fmapi_dc_remove_ref_req req = {0}; + int rc; + + memset(req.tag, 0xBB, sizeof(req.tag)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x07, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_dc_remove_reference(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_fmapi_dc_list_tags(void) +{ + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp rsp = {0}, ret; + int rc; + + req.start_idx = 0; + req.tags_count = 4; + + rsp.generation_num = 1; + rsp.total_num_tags = 5; + rsp.num_tags_returned = 0; /* Avoid flexible array */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x08, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_dc_list_tags(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.total_num_tags, 5, "total_num_tags mismatch"); + return 0; +} + +static int test_cmd_memdev_security_receive(void) +{ + struct cxlmi_cmd_memdev_security_receive_req req = {0}; + uint8_t ret[264]; /* 6 rsvd + 2 len + 256 protos */ + int rc; + + /* Use security protocol 0x00 (Security protocol information) */ + req.security_protocol = 0x00; + req.sp_specific = 0x0000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x46, 0x01, CXLMI_RET_SUCCESS, + ret, sizeof(ret)); + rc = cxlmi_cmd_memdev_security_receive(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +static int test_cmd_vendor_specific(void) +{ + uint8_t in_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + uint8_t out_data[16] = {0}; + uint8_t expected[16] = {0xAA, 0xBB, 0xCC, 0xDD}; + int rc; + + /* Vendor-specific opcode must be >= 0xC000 */ + uint16_t opcode = 0xC001; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, opcode >> 8, opcode & 0xFF, + CXLMI_RET_SUCCESS, expected, sizeof(expected)); + rc = cxlmi_cmd_vendor_specific(test_ep, NULL, opcode, + in_data, sizeof(in_data), + out_data, sizeof(out_data)); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(out_data[0], 0xAA, "out_data[0] mismatch"); + ASSERT_EQ(out_data[1], 0xBB, "out_data[1] mismatch"); + return 0; +} + +/* ============================================================ + * Error Code Tests + * ============================================================ */ + +static int test_error_code_busy(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_BUSY, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_BUSY, "expected BUSY"); + return 0; +} + +static int test_error_code_invalid_input(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_INPUT, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_INPUT, "expected INPUT error"); + return 0; +} + +static int test_error_code_internal(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_INTERNAL, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_INTERNAL, "expected INTERNAL error"); + return 0; +} + +static int test_error_code_retry(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_RETRY, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_RETRY, "expected RETRY error"); + return 0; +} + +static int test_error_code_media_disabled(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_MEDIADISABLED, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_MEDIADISABLED, "expected MEDIADISABLED error"); + return 0; +} + +static int test_error_code_abort(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_ABORT, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_ABORT, "expected ABORT error"); + return 0; +} + +static int test_error_code_security(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SECURITY, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SECURITY, "expected SECURITY error"); + return 0; +} + +static int test_error_code_passphrase(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_PASSPHRASE, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_PASSPHRASE, "expected PASSPHRASE error"); + return 0; +} + +static int test_error_code_mailbox_unsupported(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_MBUNSUPPORTED, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_MBUNSUPPORTED, "expected MBUNSUPPORTED error"); + return 0; +} + +static int test_error_code_payload_length(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_PAYLOADLEN, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_PAYLOADLEN, "expected PAYLOADLEN error"); + return 0; +} + +static int test_error_code_log(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_LOG, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_LOG, "expected LOG error"); + return 0; +} + +static int test_error_code_interrupted(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_INTERRUPTED, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_INTERRUPTED, "expected INTERRUPTED error"); + return 0; +} + +/* Firmware-specific error codes */ +static int test_error_code_fw_in_progress(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 1 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWINPROGRESS, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWINPROGRESS, "expected FWINPROGRESS error"); + return 0; +} + +static int test_error_code_fw_out_of_order(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 1 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWOOO, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWOOO, "expected FWOOO error"); + return 0; +} + +static int test_error_code_fw_auth(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 1 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWAUTH, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWAUTH, "expected FWAUTH error"); + return 0; +} + +static int test_error_code_fw_slot(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 99 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWSLOT, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWSLOT, "expected FWSLOT error"); + return 0; +} + +static int test_error_code_fw_rollback(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 1 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWROLLBACK, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWROLLBACK, "expected FWROLLBACK error"); + return 0; +} + +static int test_error_code_fw_reset(void) +{ + struct cxlmi_cmd_activate_fw_req req = { .action = 1, .slot = 1 }; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_FWRESET, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FWRESET, "expected FWRESET error"); + return 0; +} + +/* Memory/media error codes */ +static int test_error_code_invalid_handle(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_HANDLE, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_HANDLE, "expected HANDLE error"); + return 0; +} + +static int test_error_code_physical_address(void) +{ + struct cxlmi_cmd_memdev_inject_poison_req req = {0}; + int rc; + + req.inject_poison_phy_addr = 0xFFFFFFFFFFFFFFFFULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* MEDIA_AND_POISON=0x43, INJECT_POISON=0x01 */ + cxlmi_mock_set_response(test_ep, 0x43, 0x01, CXLMI_RET_PADDR, NULL, 0); + rc = cxlmi_cmd_memdev_inject_poison(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_PADDR, "expected PADDR error"); + return 0; +} + +static int test_error_code_poison_limit(void) +{ + struct cxlmi_cmd_memdev_inject_poison_req req = {0}; + int rc; + + req.inject_poison_phy_addr = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* MEDIA_AND_POISON=0x43, INJECT_POISON=0x01 */ + cxlmi_mock_set_response(test_ep, 0x43, 0x01, CXLMI_RET_POISONLMT, NULL, 0); + rc = cxlmi_cmd_memdev_inject_poison(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_POISONLMT, "expected POISONLMT error"); + return 0; +} + +static int test_error_code_media_failure(void) +{ + struct cxlmi_cmd_memdev_clear_poison_req req = {0}; + int rc; + + req.clear_poison_phy_addr = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* MEDIA_AND_POISON=0x43, CLEAR_POISON=0x02 */ + cxlmi_mock_set_response(test_ep, 0x43, 0x02, CXLMI_RET_MEDIAFAILURE, NULL, 0); + rc = cxlmi_cmd_memdev_clear_poison(test_ep, NULL, &req); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_MEDIAFAILURE, "expected MEDIAFAILURE error"); + return 0; +} + +/* Feature-specific error codes */ +static int test_error_code_feature_version(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* FEATURES=0x05, GET_FEATURE=0x01 */ + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_FEATUREVERSION, NULL, 0); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &rsp); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FEATUREVERSION, "expected FEATUREVERSION error"); + return 0; +} + +static int test_error_code_feature_selection(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* FEATURES=0x05, GET_FEATURE=0x01 */ + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_FEATURESELVALUE, NULL, 0); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &rsp); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FEATURESELVALUE, "expected FEATURESELVALUE error"); + return 0; +} + +static int test_error_code_feature_transfer_in_progress(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* FEATURES=0x05, GET_FEATURE=0x01 */ + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_FEATURETRANSFERIP, NULL, 0); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &rsp); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FEATURETRANSFERIP, "expected FEATURETRANSFERIP error"); + return 0; +} + +static int test_error_code_feature_transfer_out_of_order(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* FEATURES=0x05, GET_FEATURE=0x01 */ + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_FEATURETRANSFEROOO, NULL, 0); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &rsp); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_FEATURETRANSFEROOO, "expected FEATURETRANSFEROOO error"); + return 0; +} + +/* Resource and DCD error codes */ +static int test_error_code_resource_exhausted(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_RESOURCEEXHAUSTED, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_RESOURCEEXHAUSTED, "expected RESOURCEEXHAUSTED error"); + return 0; +} + +static int test_error_code_extent_list(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_EXTLIST, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_EXTLIST, "expected EXTLIST error"); + return 0; +} + +static int test_error_code_transfer_out_of_order(void) +{ + struct cxlmi_cmd_identify_rsp id; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_TRANSFEROOO, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_TRANSFEROOO, "expected TRANSFEROOO error"); + return 0; +} + +static int test_error_code_no_bg_abort(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x05, CXLMI_RET_NO_BGABORT, NULL, 0); + rc = cxlmi_cmd_request_bg_op_abort(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_NO_BGABORT, "expected NO_BGABORT error"); + return 0; +} + +static int test_error_code_background(void) +{ + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x00, CXLMI_RET_BACKGROUND, NULL, 0); + rc = cxlmi_cmd_memdev_sanitize(test_ep, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_BACKGROUND, "expected BACKGROUND (success, bg started)"); + return 0; +} + +/* Error sequence tests - simulating real-world scenarios */ +static int test_error_retry_then_success(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + int rc; + + rsp.vendor_id = 0x1234; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* First attempt returns RETRY */ + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_RETRY, NULL, 0); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_RETRY, "first call should return RETRY"); + + /* Second attempt succeeds */ + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "second call should succeed"); + ASSERT_EQ(id.vendor_id, 0x1234, "vendor_id should match"); + + teardown(); + return 0; +} + +static int test_error_busy_multiple_retries(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + unsigned int sent, returned; + int rc; + + rsp.vendor_id = 0xABCD; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* Queue: BUSY, BUSY, BUSY, SUCCESS */ + mock_set_response_n(test_ep, 0x00, 0x01, CXLMI_RET_BUSY, NULL, 0, 3); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + + /* Simulate polling loop */ + do { + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + } while (rc == CXLMI_RET_BUSY); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "should eventually succeed"); + ASSERT_EQ(id.vendor_id, 0xABCD, "vendor_id should match"); + + cxlmi_mock_get_stats(test_ep, &sent, &returned); + ASSERT_EQ(sent, 4, "should have sent 4 commands"); + ASSERT_EQ(returned, 4, "should have returned 4 responses"); + + teardown(); + return 0; +} + +static int test_error_with_partial_response(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, id; + int rc; + + /* Set only some fields - simulates partial/corrupt response */ + rsp.vendor_id = 0x5678; + /* Other fields remain zero */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &id); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command should succeed"); + ASSERT_EQ(id.vendor_id, 0x5678, "vendor_id should match"); + ASSERT_EQ(id.device_id, 0, "unset fields should be zero"); + return 0; +} + +static int test_error_different_commands_different_errors(void) +{ + struct cxlmi_cmd_identify_rsp id; + struct cxlmi_cmd_get_timestamp_rsp ts; + int rc1, rc2; + + ASSERT_EQ(setup(), 0, "setup failed"); + + /* Queue different errors for different commands */ + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_BUSY, NULL, 0); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_INTERNAL, NULL, 0); + + rc1 = cxlmi_cmd_identify(test_ep, NULL, &id); + rc2 = cxlmi_cmd_get_timestamp(test_ep, NULL, &ts); + + teardown(); + + ASSERT_EQ(rc1, CXLMI_RET_BUSY, "identify should return BUSY"); + ASSERT_EQ(rc2, CXLMI_RET_INTERNAL, "get_timestamp should return INTERNAL"); + return 0; +} + +/* ============================================================ + * Edge Case Tests - Variable-Length Responses + * ============================================================ */ + +/* Test get_supported_logs with zero entries */ +static int test_edge_get_supported_logs_empty(void) +{ + struct cxlmi_cmd_get_supported_logs_rsp rsp = {0}, ret; + int rc; + + rsp.num_supported_log_entries = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_supported_log_entries, 0, "should be 0 entries"); + return 0; +} + +/* Test get_supported_logs with multiple entries */ +static int test_edge_get_supported_logs_multiple(void) +{ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_get_supported_logs_rsp) + + 3 * sizeof(struct cxlmi_supported_log_entry)] = {0}; + struct cxlmi_cmd_get_supported_logs_rsp *rsp = + (struct cxlmi_cmd_get_supported_logs_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_logs_rsp) + + 3 * sizeof(struct cxlmi_supported_log_entry)] = {0}; + struct cxlmi_cmd_get_supported_logs_rsp *ret = + (struct cxlmi_cmd_get_supported_logs_rsp *)ret_buf; + int rc; + + rsp->num_supported_log_entries = cpu_to_le16(3); + /* Entry 0 */ + rsp->entries[0].log_size = cpu_to_le32(0x1000); + /* Entry 1 */ + rsp->entries[1].log_size = cpu_to_le32(0x2000); + /* Entry 2 */ + rsp->entries[2].log_size = cpu_to_le32(0x3000); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 3, "should be 3 entries"); + return 0; +} + +/* Test get_event_records with zero records */ +static int test_edge_get_event_records_empty(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp rsp = {0}, ret = {0}; + int rc; + + req.event_log = 0; + rsp.flags = 0; + rsp.overflow_err_count = 0; + rsp.first_overflow_timestamp = 0; + rsp.last_overflow_timestamp = 0; + rsp.record_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.record_count, 0, "should be 0 records"); + return 0; +} + +/* Test get_poison_list with zero records */ +static int test_edge_get_poison_list_empty(void) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp rsp = {0}, ret; + int rc; + + req.get_poison_list_phy_addr = 0; + req.get_poison_list_phy_addr_len = 0x100000; + + rsp.poison_list_flags = 0; + rsp.overflow_timestamp = 0; + rsp.more_err_media_record_cnt = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.more_err_media_record_cnt, 0, "should be 0 records"); + return 0; +} + +/* Test get_poison_list with multiple records */ +static int test_edge_get_poison_list_multiple(void) +{ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)] = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp *rsp = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)] = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp *ret = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)ret_buf; + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + int rc; + + req.get_poison_list_phy_addr = 0; + req.get_poison_list_phy_addr_len = 0x100000; + + rsp->poison_list_flags = 0; + rsp->more_err_media_record_cnt = cpu_to_le16(2); + /* Record 0 */ + rsp->records[0].media_err_addr = cpu_to_le64(0x1000); + rsp->records[0].media_err_len = cpu_to_le32(64); + /* Record 1 */ + rsp->records[1].media_err_addr = cpu_to_le64(0x2000); + rsp->records[1].media_err_len = cpu_to_le32(128); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->more_err_media_record_cnt, 2, "should be 2 records"); + return 0; +} + +/* Test get_dc_extent_list with zero extents */ +static int test_edge_get_dc_extent_list_empty(void) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp rsp = {0}, ret; + int rc; + + req.extent_cnt = 8; + req.start_extent_idx = 0; + + rsp.num_extents_returned = 0; + rsp.total_num_extents = 0; + rsp.generation_num = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_extents_returned, 0, "should be 0 extents returned"); + ASSERT_EQ(ret.total_num_extents, 0, "should be 0 total extents"); + return 0; +} + +/* Test fmapi_get_ld_allocations with zero allocations */ +static int test_edge_fmapi_get_ld_allocations_empty(void) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp rsp = {0}, ret; + int rc; + + req.start_ld_id = 0; + req.ld_allocation_list_limit = 8; + + rsp.number_ld = 0; + rsp.memory_granularity = 0; + rsp.start_ld_id = 0; + rsp.ld_allocation_list_len = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_ld_allocations(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.ld_allocation_list_len, 0, "should be 0 allocations"); + return 0; +} + +/* Test fmapi_get_ld_allocations with multiple allocations */ +static int test_edge_fmapi_get_ld_allocations_multiple(void) +{ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 3 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)] = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 3 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)] = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)ret_buf; + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + int rc; + + req.start_ld_id = 0; + req.ld_allocation_list_limit = 8; + + rsp->number_ld = 3; + rsp->memory_granularity = 8; + rsp->start_ld_id = 0; + rsp->ld_allocation_list_len = 3; + /* Allocation entries */ + rsp->ld_allocation_list[0].range_1_allocation_mult = cpu_to_le64(0x1000); + rsp->ld_allocation_list[1].range_1_allocation_mult = cpu_to_le64(0x2000); + rsp->ld_allocation_list[2].range_1_allocation_mult = cpu_to_le64(0x3000); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_ld_allocations(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->ld_allocation_list_len, 3, "should be 3 allocations"); + return 0; +} + +/* Test fmapi_get_phys_port_state with zero ports */ +static int test_edge_fmapi_get_phys_port_state_empty(void) +{ + struct cxlmi_cmd_fmapi_get_phys_port_state_req req = {0}; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp rsp = {0}, ret; + int rc; + + req.num_ports = 0; + + rsp.num_ports = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_phys_port_state(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_ports, 0, "should be 0 ports"); + return 0; +} + +/* Test fmapi_get_qos_allocated_bw with zero LDs */ +static int test_edge_fmapi_get_qos_allocated_bw_empty(void) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req req = {0}; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp rsp = {0}, ret; + int rc; + + req.number_ld = 0; + req.start_ld_id = 0; + + rsp.number_ld = 0; + rsp.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x06, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.number_ld, 0, "should be 0 LDs"); + return 0; +} + +/* Test fmapi_get_qos_bw_limit with zero LDs */ +static int test_edge_fmapi_get_qos_bw_limit_empty(void) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req req = {0}; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp rsp = {0}, ret; + int rc; + + req.number_ld = 0; + req.start_ld_id = 0; + + rsp.number_ld = 0; + rsp.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x08, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.number_ld, 0, "should be 0 LDs"); + return 0; +} + +/* Test get_supported_features with zero features */ +static int test_edge_get_supported_features_empty(void) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + struct cxlmi_cmd_get_supported_features_rsp rsp = {0}, ret; + int rc; + + req.count = 8; + req.starting_feature_index = 0; + + rsp.num_supported_feature_entries = 0; + rsp.device_supported_features = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_supported_feature_entries, 0, "should be 0 features"); + return 0; +} + +/* Test get_log_cel with zero entries */ +static int test_edge_get_log_cel_empty(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + struct cxlmi_cmd_get_log_cel_rsp rsp = {0}, ret; + int rc; + + req.offset = 0; + req.length = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + &rsp, 0); + rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + return 0; +} + +/* Test get_scan_media_results with zero errors */ +static int test_edge_get_scan_media_results_empty(void) +{ + struct cxlmi_cmd_get_scan_media_results_rsp rsp = {0}, ret; + int rc; + + rsp.scan_media_restart_physaddr = 0; + rsp.scan_media_restart_physaddr_length = 0; + rsp.scan_media_flags = 0; + rsp.media_error_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_scan_media_results(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.media_error_count, 0, "should be 0 errors"); + return 0; +} + +/* ============================================================ + * Edge Case Tests - Boundary Values + * ============================================================ */ + +/* Test identify with maximum values */ +static int test_edge_identify_max_values(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, ret; + int rc; + + rsp.vendor_id = cpu_to_le16(0xFFFF); + rsp.device_id = cpu_to_le16(0xFFFF); + rsp.subsys_vendor_id = cpu_to_le16(0xFFFF); + rsp.subsys_id = cpu_to_le16(0xFFFF); + rsp.serial_num = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.max_msg_size = 0xFF; + rsp.component_type = 0xFF; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.vendor_id, 0xFFFF, "vendor_id mismatch"); + ASSERT_EQ(ret.device_id, 0xFFFF, "device_id mismatch"); + ASSERT_TRUE(ret.serial_num == 0xFFFFFFFFFFFFFFFFULL, "serial_num mismatch"); + return 0; +} + +/* Test timestamp with max value */ +static int test_edge_timestamp_max_value(void) +{ + struct cxlmi_cmd_get_timestamp_rsp rsp = {0}, ret; + int rc; + + rsp.timestamp = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_timestamp(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.timestamp == 0xFFFFFFFFFFFFFFFFULL, "timestamp mismatch"); + return 0; +} + +/* Test timestamp with zero value */ +static int test_edge_timestamp_zero_value(void) +{ + struct cxlmi_cmd_get_timestamp_rsp rsp = {0}, ret; + int rc; + + rsp.timestamp = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_timestamp(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.timestamp == 0, "timestamp should be 0"); + return 0; +} + +/* Test health info with critical thresholds */ +static int test_edge_health_info_critical(void) +{ + struct cxlmi_cmd_memdev_get_health_info_rsp rsp = {0}, ret; + int rc; + + rsp.health_status = 0xFF; /* All flags set */ + rsp.media_status = 0xFF; + rsp.additional_status = 0xFF; + rsp.life_used = 100; /* 100% life used */ + rsp.device_temperature = cpu_to_le16(0x7FFF); /* Max positive temp */ + rsp.dirty_shutdown_count = cpu_to_le32(0xFFFFFFFF); + rsp.corrected_volatile_error_count = cpu_to_le32(0xFFFFFFFF); + rsp.corrected_persistent_error_count = cpu_to_le32(0xFFFFFFFF); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_health_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.health_status, 0xFF, "health_status mismatch"); + ASSERT_EQ(ret.life_used, 100, "life_used mismatch"); + return 0; +} + +/* Test memdev identify with max capacity */ +static int test_edge_memdev_identify_max_capacity(void) +{ + struct cxlmi_cmd_memdev_identify_rsp rsp = {0}, ret; + int rc; + + rsp.total_capacity = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.volatile_capacity = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.persistent_capacity = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.partition_align = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.lsa_size = cpu_to_le32(0xFFFFFFFF); + /* poison_list_max_mer is a 3-byte field */ + rsp.poison_list_max_mer[0] = 0xFF; + rsp.poison_list_max_mer[1] = 0xFF; + rsp.poison_list_max_mer[2] = 0xFF; + rsp.inject_poison_limit = cpu_to_le16(0xFFFF); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x40, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.total_capacity == 0xFFFFFFFFFFFFFFFFULL, + "total_capacity mismatch"); + return 0; +} + +/* Test DC config with max regions */ +static int test_edge_dc_config_max_regions(void) +{ + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp rsp = {0}, ret; + int rc; + + req.region_cnt = 8; + req.start_region_id = 0; + + rsp.num_regions = 8; + rsp.regions_returned = 8; + rsp.num_extents_supported = cpu_to_le32(0xFFFFFFFF); + rsp.num_extents_available = cpu_to_le32(0xFFFFFFFF); + rsp.num_tags_supported = cpu_to_le32(0xFFFFFFFF); + rsp.num_tags_available = cpu_to_le32(0xFFFFFFFF); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_regions, 8, "num_regions mismatch"); + ASSERT_EQ(ret.num_extents_supported, 0xFFFFFFFF, "num_extents mismatch"); + return 0; +} + +/* Test QoS status with max values */ +static int test_edge_qos_status_max_values(void) +{ + struct cxlmi_cmd_fmapi_get_qos_status_rsp rsp = {0}, ret; + int rc; + + rsp.backpressure_avg_percentage = 0xFF; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.backpressure_avg_percentage, 0xFF, "backpressure mismatch"); + return 0; +} + +/* Test FW info with all slots used */ +static int test_edge_fw_info_all_slots(void) +{ + struct cxlmi_cmd_get_fw_info_rsp rsp = {0}, ret; + int rc; + + rsp.slots_supported = 4; + rsp.slot_info = 0x04; /* Slot 4 active */ + rsp.caps = 0xFF; + memset(rsp.fw_rev1, 'A', sizeof(rsp.fw_rev1)); + memset(rsp.fw_rev2, 'B', sizeof(rsp.fw_rev2)); + memset(rsp.fw_rev3, 'C', sizeof(rsp.fw_rev3)); + memset(rsp.fw_rev4, 'D', sizeof(rsp.fw_rev4)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_fw_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.slots_supported, 4, "slots_supported mismatch"); + return 0; +} + +/* Test bg_op_status with max values */ +static int test_edge_bg_op_status_max_values(void) +{ + struct cxlmi_cmd_bg_op_status_rsp rsp = {0}, ret; + int rc; + + rsp.status = 0xFF; + rsp.opcode = cpu_to_le16(0xFFFF); + rsp.returncode = cpu_to_le16(0xFFFF); + rsp.vendor_ext_status = cpu_to_le16(0xFFFF); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_bg_op_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.status, 0xFF, "status mismatch"); + ASSERT_EQ(ret.opcode, 0xFFFF, "opcode mismatch"); + return 0; +} + +/* Test event records with overflow flags */ +static int test_edge_event_records_overflow(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp rsp = {0}, ret = {0}; + int rc; + + req.event_log = 0; + + rsp.flags = 0x03; /* More records + overflow */ + rsp.overflow_err_count = cpu_to_le16(0xFFFF); + rsp.first_overflow_timestamp = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.last_overflow_timestamp = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.record_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.flags, 0x03, "flags mismatch"); + ASSERT_EQ(ret.overflow_err_count, 0xFFFF, "overflow_err_count mismatch"); + return 0; +} + +/* Test poison list with overflow */ +static int test_edge_poison_list_overflow(void) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp rsp = {0}, ret; + int rc; + + req.get_poison_list_phy_addr = 0; + req.get_poison_list_phy_addr_len = 0x100000; + + rsp.poison_list_flags = 0x03; /* More data + overflow */ + rsp.overflow_timestamp = cpu_to_le64(0xFFFFFFFFFFFFFFFFULL); + rsp.more_err_media_record_cnt = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.poison_list_flags, 0x03, "flags mismatch"); + return 0; +} + +/* Test response message limit at boundaries */ +static int test_edge_response_msg_limit_boundaries(void) +{ + struct cxlmi_cmd_get_response_msg_limit_rsp rsp = {0}, ret; + int rc; + + /* Test minimum limit (256 bytes = 0x08) */ + rsp.limit = 0x08; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_response_msg_limit(test_ep, NULL, &ret); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.limit, 0x08, "min limit mismatch"); + + /* Test maximum limit */ + rsp.limit = 0xFF; + cxlmi_mock_set_response(test_ep, 0x00, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_response_msg_limit(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.limit, 0xFF, "max limit mismatch"); + return 0; +} + +/* ============================================================ + * Request Payload Verification Tests + * ============================================================ */ + +/* --- Generic Component Commands (0x00) --- */ + +static int test_payload_set_response_msg_limit(void) +{ + struct cxlmi_cmd_set_response_msg_limit_req req = {0}; + struct cxlmi_cmd_set_response_msg_limit_rsp ret; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.limit = 0x0A; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x04, CXLMI_RET_SUCCESS, &req, sizeof(req)); + rc = cxlmi_cmd_set_response_msg_limit(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 1, "payload size should be 1 byte"); + ASSERT_EQ(payload[0], 0x0A, "limit mismatch"); + + return 0; +} + +/* --- Events Commands (0x01) --- */ + +static int test_payload_get_event_records(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.event_log = 0x02; /* Warning log */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 1, "payload size should be 1 byte"); + ASSERT_EQ(payload[0], 0x02, "event_log mismatch"); + + return 0; +} + +static int test_payload_set_event_interrupt_policy(void) +{ + struct cxlmi_cmd_set_event_interrupt_policy_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.informational_settings = 0x01; + req.warning_settings = 0x02; + req.failure_settings = 0x03; + req.fatal_settings = 0x04; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_event_interrupt_policy(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload[0], 0x01, "informational_settings mismatch"); + ASSERT_EQ(payload[1], 0x02, "warning_settings mismatch"); + ASSERT_EQ(payload[2], 0x03, "failure_settings mismatch"); + ASSERT_EQ(payload[3], 0x04, "fatal_settings mismatch"); + + return 0; +} + +/* --- Firmware Commands (0x02) --- */ + +static int test_payload_set_timestamp(void) +{ + struct cxlmi_cmd_set_timestamp_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t sent_timestamp; + int rc; + + req.timestamp = 0x123456789ABCDEF0ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_timestamp(test_ep, NULL, &req); + + /* Verify the payload that was sent */ + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size should be 8 bytes"); + + /* Verify little-endian encoding */ + sent_timestamp = le64_to_cpu(*(leint64_t *)payload); + ASSERT_TRUE(sent_timestamp == 0x123456789ABCDEF0ULL, "timestamp not encoded correctly"); + + return 0; +} + +static int test_payload_activate_fw(void) +{ + struct cxlmi_cmd_activate_fw_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.action = 0x02; + req.slot = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_activate_fw(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size should be 2 bytes"); + ASSERT_EQ(payload[0], 0x02, "action byte mismatch"); + ASSERT_EQ(payload[1], 0x03, "slot byte mismatch"); + + return 0; +} + +static int test_payload_set_partition_info(void) +{ + struct cxlmi_cmd_memdev_set_partition_info_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t sent_capacity; + int rc; + + req.volatile_capacity = 0x0000000100000000ULL; /* 256MB units */ + req.flags = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_partition_info(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 9, "payload size should be 9 bytes"); + + /* Verify little-endian encoding of 64-bit capacity */ + sent_capacity = le64_to_cpu(*(leint64_t *)payload); + ASSERT_TRUE(sent_capacity == 0x0000000100000000ULL, "capacity not encoded correctly"); + ASSERT_EQ(payload[8], 0x01, "flags byte mismatch"); + + return 0; +} + +static int test_payload_inject_poison(void) +{ + struct cxlmi_cmd_memdev_inject_poison_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t sent_addr; + int rc; + + req.inject_poison_phy_addr = 0xDEADBEEF00001000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_inject_poison(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size should be 8 bytes"); + + sent_addr = le64_to_cpu(*(leint64_t *)payload); + ASSERT_TRUE(sent_addr == 0xDEADBEEF00001000ULL, "address not encoded correctly"); + + return 0; +} + +static int test_payload_command_opcode(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, ret; + uint8_t cmd_set, cmd; + int rc; + + rsp.vendor_id = 0x1234; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + + /* Verify the command set and opcode that were sent */ + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, NULL, NULL); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x00, "command set should be 0x00 (INFOSTAT)"); + ASSERT_EQ(cmd, 0x01, "command should be 0x01 (IDENTIFY)"); + + return 0; +} + +static int test_payload_fmapi_bind_vppb(void) +{ + struct cxlmi_cmd_fmapi_bind_vppb_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint8_t cmd_set, cmd; + int rc; + + req.vcs_id = 0x02; + req.vppb_id = 0x05; + req.port_id = 0x07; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x52, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_bind_vppb(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x52, "command set should be 0x52 (FMAPI VS)"); + ASSERT_EQ(cmd, 0x01, "command should be 0x01 (BIND_VPPB)"); + ASSERT_EQ(payload[0], 0x02, "vcs_id mismatch"); + ASSERT_EQ(payload[1], 0x05, "vppb_id mismatch"); + ASSERT_EQ(payload[2], 0x07, "port_id mismatch"); + + return 0; +} + +/* --- Logs Commands (0x04) --- */ + +static int test_payload_get_log(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + uint8_t ret_buf[512]; + uint8_t rsp_data[256] = {0}; /* Mock response payload */ + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint32_t offset, length; + int rc; + + /* CEL UUID */ + memset(req.uuid, 0xAA, sizeof(req.uuid)); + req.offset = 0x00001000; + req.length = 256; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + rsp_data, sizeof(rsp_data)); + rc = cxlmi_cmd_get_log(test_ep, NULL, &req, ret_buf); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 24, "payload size should be 24 bytes"); + ASSERT_EQ(payload[0], 0xAA, "uuid[0] mismatch"); + + offset = le32_to_cpu(*(leint32_t *)&payload[16]); + length = le32_to_cpu(*(leint32_t *)&payload[20]); + ASSERT_EQ(offset, 0x00001000, "offset mismatch"); + ASSERT_EQ(length, 256, "length mismatch"); + + return 0; +} + +static int test_payload_clear_log(void) +{ + struct cxlmi_cmd_clear_log_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req.uuid, 0xBB, sizeof(req.uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_clear_log(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size should be 16 bytes"); + ASSERT_EQ(payload[0], 0xBB, "uuid[0] mismatch"); + ASSERT_EQ(payload[15], 0xBB, "uuid[15] mismatch"); + + return 0; +} + +/* --- Memory Device Commands (0x40-0x48) --- */ + +static int test_payload_get_poison_list(void) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t addr, len; + int rc; + + req.get_poison_list_phy_addr = 0x0000100000000000ULL; + req.get_poison_list_phy_addr_len = 0x0000000010000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size should be 16 bytes"); + + addr = le64_to_cpu(*(leint64_t *)&payload[0]); + len = le64_to_cpu(*(leint64_t *)&payload[8]); + ASSERT_TRUE(addr == 0x0000100000000000ULL, "phy_addr mismatch"); + ASSERT_TRUE(len == 0x0000000010000000ULL, "phy_addr_len mismatch"); + + return 0; +} + +static int test_payload_clear_poison(void) +{ + struct cxlmi_cmd_memdev_clear_poison_req req = {0}; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint64_t addr; + int rc; + + req.clear_poison_phy_addr = 0xDEADBEEF12345678ULL; + memset(req.clear_poison_write_data, 0x55, 64); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_clear_poison(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 72, "payload size should be 72 bytes"); + + addr = le64_to_cpu(*(leint64_t *)&payload[0]); + ASSERT_TRUE(addr == 0xDEADBEEF12345678ULL, "phy_addr mismatch"); + ASSERT_EQ(payload[8], 0x55, "write_data[0] mismatch"); + ASSERT_EQ(payload[71], 0x55, "write_data[63] mismatch"); + + return 0; +} + +static int test_payload_scan_media(void) +{ + struct cxlmi_cmd_scan_media_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t addr, len; + int rc; + + req.scan_media_physaddr = 0x0000200000000000ULL; + req.scan_media_physaddr_length = 0x0000000020000000ULL; + req.scan_media_flags = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_scan_media(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 17, "payload size should be 17 bytes"); + + addr = le64_to_cpu(*(leint64_t *)&payload[0]); + len = le64_to_cpu(*(leint64_t *)&payload[8]); + ASSERT_TRUE(addr == 0x0000200000000000ULL, "physaddr mismatch"); + ASSERT_TRUE(len == 0x0000000020000000ULL, "physaddr_length mismatch"); + ASSERT_EQ(payload[16], 0x01, "flags mismatch"); + + return 0; +} + +static int test_payload_set_alert_config(void) +{ + struct cxlmi_cmd_memdev_set_alert_config_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint16_t temp; + int rc; + + req.valid_alert_actions = 0xFF; + req.enable_alert_actions = 0x0F; + req.life_used_programmable_warning_threshold = 80; + req.device_over_temperature_programmable_warning_threshold = 850; /* 85.0 C */ + req.device_under_temperature_programmable_warning_threshold = 50; /* 5.0 C */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_alert_config(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload[0], 0xFF, "valid_alert_actions mismatch"); + ASSERT_EQ(payload[1], 0x0F, "enable_alert_actions mismatch"); + ASSERT_EQ(payload[2], 80, "life_used threshold mismatch"); + + temp = le16_to_cpu(*(leint16_t *)&payload[4]); + ASSERT_EQ(temp, 850, "over_temp threshold mismatch"); + + return 0; +} + +static int test_payload_set_shutdown_state(void) +{ + struct cxlmi_cmd_memdev_set_shutdown_state_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.state = 0x01; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_shutdown_state(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 1, "payload size should be 1 byte"); + ASSERT_EQ(payload[0], 0x01, "state mismatch"); + + return 0; +} + +static int test_payload_get_dc_config(void) +{ + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.region_cnt = 8; + req.start_region_id = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x00, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_memdev_get_dc_config(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size should be 2 bytes"); + ASSERT_EQ(payload[0], 8, "region_cnt mismatch"); + ASSERT_EQ(payload[1], 2, "start_region_id mismatch"); + + return 0; +} + +static int test_payload_set_sld_qos_control(void) +{ + struct cxlmi_cmd_memdev_set_sld_qos_control_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.qos_telemetry_control = 0x01; + req.egress_moderate_percentage = 50; + req.egress_severe_percentage = 80; + req.backpressure_sample_interval = 100; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_sld_qos_control(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 4, "payload size should be 4 bytes"); + ASSERT_EQ(payload[0], 0x01, "qos_telemetry_control mismatch"); + ASSERT_EQ(payload[1], 50, "egress_moderate_percentage mismatch"); + ASSERT_EQ(payload[2], 80, "egress_severe_percentage mismatch"); + ASSERT_EQ(payload[3], 100, "backpressure_sample_interval mismatch"); + + return 0; +} + +/* --- FM-API Commands (0x51-0x56) --- */ + +static int test_payload_fmapi_phys_port_control(void) +{ + struct cxlmi_cmd_fmapi_phys_port_control_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint8_t cmd_set, cmd; + int rc; + + req.ppb_id = 0x03; + req.port_opcode = 0x02; /* Disable port */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_phys_port_control(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x51, "command set should be 0x51"); + ASSERT_EQ(cmd, 0x02, "command should be 0x02"); + ASSERT_EQ(payload_size, 2, "payload size should be 2 bytes"); + ASSERT_EQ(payload[0], 0x03, "ppb_id mismatch"); + ASSERT_EQ(payload[1], 0x02, "port_opcode mismatch"); + + return 0; +} + +static int test_payload_fmapi_unbind_vppb(void) +{ + struct cxlmi_cmd_fmapi_unbind_vppb_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint8_t cmd_set, cmd; + int rc; + + req.vcs_id = 0x01; + req.vppb_id = 0x04; + req.option = 0x01; /* Wait for clean unbind */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x52, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_unbind_vppb(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x52, "command set should be 0x52"); + ASSERT_EQ(cmd, 0x02, "command should be 0x02"); + ASSERT_EQ(payload[0], 0x01, "vcs_id mismatch"); + ASSERT_EQ(payload[1], 0x04, "vppb_id mismatch"); + ASSERT_EQ(payload[2], 0x01, "option mismatch"); + + return 0; +} + +static int test_payload_clear_event_records(void) +{ + /* Use a buffer to hold the request with flexible array member */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_clear_event_records_req) + + 2 * sizeof(uint16_t)]; + struct cxlmi_cmd_clear_event_records_req *req = + (struct cxlmi_cmd_clear_event_records_req *)req_buf; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->event_log = 0x03; /* Failure log */ + req->clear_flags = 0x01; + req->nr_recs = 2; + req->handles[0] = 0x1234; + req->handles[1] = 0x5678; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_clear_event_records(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload[0], 0x03, "event_log mismatch"); + ASSERT_EQ(payload[1], 0x01, "clear_flags mismatch"); + ASSERT_EQ(payload[2], 2, "nr_recs mismatch"); + + return 0; +} + +static int test_payload_get_log_capabilities(void) +{ + struct cxlmi_cmd_get_log_capabilities_req req = {0}; + struct cxlmi_cmd_get_log_capabilities_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* Set UUID to specific pattern */ + memset(req.uuid, 0xCC, sizeof(req.uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x02, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_log_capabilities(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size should be 16 bytes"); + ASSERT_EQ(payload[0], 0xCC, "uuid[0] mismatch"); + ASSERT_EQ(payload[15], 0xCC, "uuid[15] mismatch"); + + return 0; +} + +static int test_payload_populate_log(void) +{ + struct cxlmi_cmd_populate_log_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* Set UUID to specific pattern */ + memset(req.uuid, 0xDD, sizeof(req.uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_populate_log(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size should be 16 bytes"); + ASSERT_EQ(payload[0], 0xDD, "uuid[0] mismatch"); + ASSERT_EQ(payload[15], 0xDD, "uuid[15] mismatch"); + + return 0; +} + +static int test_payload_get_supported_logs_sublist(void) +{ + struct cxlmi_cmd_get_supported_logs_sublist_req req = {0}; + struct cxlmi_cmd_get_supported_logs_sublist_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.max_supported_log_entries = 16; + req.start_log_entry_index = 4; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x05, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_supported_logs_sublist(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size should be 2 bytes"); + ASSERT_EQ(payload[0], 16, "max_supported_log_entries mismatch"); + ASSERT_EQ(payload[1], 4, "start_log_entry_index mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_ld_allocations(void) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.start_ld_id = 4; + req.ld_allocation_list_limit = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x01, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_fmapi_get_ld_allocations(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size should be 2 bytes"); + ASSERT_EQ(payload[0], 4, "start_ld_id mismatch"); + ASSERT_EQ(payload[1], 8, "ld_allocation_list_limit mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_qos_control(void) +{ + struct cxlmi_cmd_fmapi_set_qos_control_req req = {0}; + struct cxlmi_cmd_fmapi_set_qos_control_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint16_t recmpbasis; + int rc; + + req.qos_telemetry_control = 0x03; + req.egress_moderate_percentage = 45; + req.egress_severe_percentage = 75; + req.backpressure_sample_interval = 200; + req.recmpbasis = 300; + req.completion_collection_interval = 50; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x04, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_fmapi_set_qos_control(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 7, "payload size should be 7 bytes"); + ASSERT_EQ(payload[0], 0x03, "qos_telemetry_control mismatch"); + ASSERT_EQ(payload[1], 45, "egress_moderate_percentage mismatch"); + ASSERT_EQ(payload[2], 75, "egress_severe_percentage mismatch"); + ASSERT_EQ(payload[3], 200, "backpressure_sample_interval mismatch"); + + recmpbasis = le16_to_cpu(*(leint16_t *)&payload[4]); + ASSERT_EQ(recmpbasis, 300, "recmpbasis mismatch"); + ASSERT_EQ(payload[6], 50, "completion_collection_interval mismatch"); + + return 0; +} + +static int test_payload_memdev_get_lsa(void) +{ + struct cxlmi_cmd_memdev_get_lsa_req req = {0}; + uint8_t ret_buf[64] = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint32_t offset, length; + int rc; + + req.offset = 0x00001234; + req.length = 0x00000040; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x02, CXLMI_RET_SUCCESS, ret_buf, 64); + rc = cxlmi_cmd_memdev_get_lsa(test_ep, NULL, &req, ret_buf); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size should be 8 bytes"); + + offset = le32_to_cpu(*(leint32_t *)&payload[0]); + length = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(offset, 0x00001234, "offset mismatch"); + ASSERT_EQ(length, 0x00000040, "length mismatch"); + + return 0; +} + +static int test_payload_get_scan_media_capabilities(void) +{ + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint64_t addr, len; + int rc; + + req.get_scan_media_capabilities_start_physaddr = 0x0000300000000000ULL; + req.get_scan_media_capabilities_physaddr_length = 0x0000000040000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x03, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_scan_media_capabilities(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size should be 16 bytes"); + + addr = le64_to_cpu(*(leint64_t *)&payload[0]); + len = le64_to_cpu(*(leint64_t *)&payload[8]); + ASSERT_TRUE(addr == 0x0000300000000000ULL, "start_physaddr mismatch"); + ASSERT_TRUE(len == 0x0000000040000000ULL, "physaddr_length mismatch"); + + return 0; +} + +static int test_payload_transfer_fw(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_transfer_fw_req) + 16]; + struct cxlmi_cmd_transfer_fw_req *req = + (struct cxlmi_cmd_transfer_fw_req *)req_buf; + uint8_t payload[256]; + size_t payload_size = sizeof(payload); + uint32_t offset; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->action = 0x02; + req->slot = 0x01; + req->offset = 0x00001000; + memset(req->data, 0x55, 16); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_transfer_fw(test_ep, NULL, req, 16); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (0x80 bytes) + data (16 bytes) */ + ASSERT_EQ(payload_size, 0x80 + 16, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x02, "action mismatch"); + ASSERT_EQ(payload[1], 0x01, "slot mismatch"); + offset = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(offset, 0x00001000, "offset mismatch"); + /* Check data starts at offset 0x80 */ + ASSERT_EQ(payload[0x80], 0x55, "data[0] mismatch"); + ASSERT_EQ(payload[0x80 + 15], 0x55, "data[15] mismatch"); + + return 0; +} + +static int test_payload_memdev_set_lsa(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_set_lsa_req) + 32]; + struct cxlmi_cmd_memdev_set_lsa_req *req = + (struct cxlmi_cmd_memdev_set_lsa_req *)req_buf; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint32_t offset; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->offset = 0x00000100; + memset(req->data, 0xAA, 32); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_lsa(test_ep, NULL, req, 32); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (8 bytes: offset + rsvd) + data (32 bytes) */ + ASSERT_EQ(payload_size, 8 + 32, "payload size mismatch"); + offset = le32_to_cpu(*(leint32_t *)&payload[0]); + ASSERT_EQ(offset, 0x00000100, "offset mismatch"); + /* Check data starts at offset 8 */ + ASSERT_EQ(payload[8], 0xAA, "data[0] mismatch"); + ASSERT_EQ(payload[8 + 31], 0xAA, "data[31] mismatch"); + + return 0; +} + +static int test_payload_memdev_add_dc_response(void) +{ + /* Extent size is 24 bytes (8 + 8 + 8) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_add_dc_response_req) + 2 * 24]; + struct cxlmi_cmd_memdev_add_dc_response_req *req = + (struct cxlmi_cmd_memdev_add_dc_response_req *)req_buf; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint32_t extent_count; + uint64_t dpa, len; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->updated_extent_list_size = 2; + req->flags = 0x01; + req->extents[0].start_dpa = 0x0000100000000000ULL; + req->extents[0].len = 0x0000000010000000ULL; + req->extents[1].start_dpa = 0x0000200000000000ULL; + req->extents[1].len = 0x0000000020000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_add_dc_response(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (8 bytes) + 2 extents (24 bytes each) */ + ASSERT_EQ(payload_size, 8 + 2 * 24, "payload size mismatch"); + + extent_count = le32_to_cpu(*(leint32_t *)&payload[0]); + ASSERT_EQ(extent_count, 2, "extent count mismatch"); + ASSERT_EQ(payload[4], 0x01, "flags mismatch"); + + /* Check first extent at offset 8 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[8]); + len = le64_to_cpu(*(leint64_t *)&payload[16]); + ASSERT_TRUE(dpa == 0x0000100000000000ULL, "extent[0] dpa mismatch"); + ASSERT_TRUE(len == 0x0000000010000000ULL, "extent[0] len mismatch"); + + /* Check second extent at offset 8 + 24 = 32 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[32]); + len = le64_to_cpu(*(leint64_t *)&payload[40]); + ASSERT_TRUE(dpa == 0x0000200000000000ULL, "extent[1] dpa mismatch"); + ASSERT_TRUE(len == 0x0000000020000000ULL, "extent[1] len mismatch"); + + return 0; +} + +static int test_payload_memdev_release_dc(void) +{ + /* Extent size is 24 bytes (8 + 8 + 8) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_release_dc_req) + 24]; + struct cxlmi_cmd_memdev_release_dc_req *req = + (struct cxlmi_cmd_memdev_release_dc_req *)req_buf; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint32_t extent_count; + uint64_t dpa, len; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->updated_extent_list_size = 1; + req->flags = 0x02; + req->extents[0].start_dpa = 0x0000300000000000ULL; + req->extents[0].len = 0x0000000030000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_release_dc(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (8 bytes) + 1 extent (24 bytes) */ + ASSERT_EQ(payload_size, 8 + 24, "payload size mismatch"); + + extent_count = le32_to_cpu(*(leint32_t *)&payload[0]); + ASSERT_EQ(extent_count, 1, "extent count mismatch"); + ASSERT_EQ(payload[4], 0x02, "flags mismatch"); + + dpa = le64_to_cpu(*(leint64_t *)&payload[8]); + len = le64_to_cpu(*(leint64_t *)&payload[16]); + ASSERT_TRUE(dpa == 0x0000300000000000ULL, "extent[0] dpa mismatch"); + ASSERT_TRUE(len == 0x0000000030000000ULL, "extent[0] len mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_ld_allocations(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_req) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_req *req = + (struct cxlmi_cmd_fmapi_set_ld_allocations_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)ret_buf; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint64_t r1, r2; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 2; + req->start_ld_id = 0; + req->ld_allocation_list[0].range_1_allocation_mult = 0x100; + req->ld_allocation_list[0].range_2_allocation_mult = 0x200; + req->ld_allocation_list[1].range_1_allocation_mult = 0x300; + req->ld_allocation_list[1].range_2_allocation_mult = 0x400; + + rsp->number_ld = 2; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x02, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_ld_allocations(test_ep, NULL, req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (4 bytes) + 2 allocations (16 bytes each) */ + ASSERT_EQ(payload_size, 4 + 2 * 16, "payload size mismatch"); + + ASSERT_EQ(payload[0], 2, "number_ld mismatch"); + ASSERT_EQ(payload[1], 0, "start_ld_id mismatch"); + + /* Check first allocation at offset 4 */ + r1 = le64_to_cpu(*(leint64_t *)&payload[4]); + r2 = le64_to_cpu(*(leint64_t *)&payload[12]); + ASSERT_EQ(r1, 0x100, "alloc[0] range_1 mismatch"); + ASSERT_EQ(r2, 0x200, "alloc[0] range_2 mismatch"); + + /* Check second allocation at offset 4 + 16 = 20 */ + r1 = le64_to_cpu(*(leint64_t *)&payload[20]); + r2 = le64_to_cpu(*(leint64_t *)&payload[28]); + ASSERT_EQ(r1, 0x300, "alloc[1] range_1 mismatch"); + ASSERT_EQ(r2, 0x400, "alloc[1] range_2 mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_qos_allocated_bw(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *req = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)ret_buf; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 4; + req->start_ld_id = 0; + req->qos_allocation_fraction[0] = 0x10; + req->qos_allocation_fraction[1] = 0x20; + req->qos_allocation_fraction[2] = 0x30; + req->qos_allocation_fraction[3] = 0x40; + + rsp->number_ld = 4; + rsp->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x07, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_allocated_bw(test_ep, NULL, req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (2 bytes) + 4 fractions (1 byte each) */ + ASSERT_EQ(payload_size, 2 + 4, "payload size mismatch"); + + ASSERT_EQ(payload[0], 4, "number_ld mismatch"); + ASSERT_EQ(payload[1], 0, "start_ld_id mismatch"); + ASSERT_EQ(payload[2], 0x10, "fraction[0] mismatch"); + ASSERT_EQ(payload[3], 0x20, "fraction[1] mismatch"); + ASSERT_EQ(payload[4], 0x30, "fraction[2] mismatch"); + ASSERT_EQ(payload[5], 0x40, "fraction[3] mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_qos_bw_limit(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_req) + 3]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *req = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 3]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 3]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)ret_buf; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 3; + req->start_ld_id = 1; + req->qos_limit_fraction[0] = 0xAA; + req->qos_limit_fraction[1] = 0xBB; + req->qos_limit_fraction[2] = 0xCC; + + rsp->number_ld = 3; + rsp->start_ld_id = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x09, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_bw_limit(test_ep, NULL, req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (2 bytes) + 3 fractions (1 byte each) */ + ASSERT_EQ(payload_size, 2 + 3, "payload size mismatch"); + + ASSERT_EQ(payload[0], 3, "number_ld mismatch"); + ASSERT_EQ(payload[1], 1, "start_ld_id mismatch"); + ASSERT_EQ(payload[2], 0xAA, "fraction[0] mismatch"); + ASSERT_EQ(payload[3], 0xBB, "fraction[1] mismatch"); + ASSERT_EQ(payload[4], 0xCC, "fraction[2] mismatch"); + + return 0; +} + +static int test_payload_security_send(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_security_send_req) + 16]; + struct cxlmi_cmd_memdev_security_send_req *req = + (struct cxlmi_cmd_memdev_security_send_req *)req_buf; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint16_t sp_specific; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->security_protocol = 0xEC; + req->sp_specific = 0x1234; + memset(req->data, 0x77, 16); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x46, 0x00, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_security_send(test_ep, NULL, req, 16); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (8 bytes) + data (16 bytes) */ + ASSERT_EQ(payload_size, 8 + 16, "payload size mismatch"); + + ASSERT_EQ(payload[0], 0xEC, "security_protocol mismatch"); + sp_specific = le16_to_cpu(*(leint16_t *)&payload[1]); + ASSERT_EQ(sp_specific, 0x1234, "sp_specific mismatch"); + /* Data starts at offset 8 */ + ASSERT_EQ(payload[8], 0x77, "data[0] mismatch"); + ASSERT_EQ(payload[8 + 15], 0x77, "data[15] mismatch"); + + return 0; +} + +static int test_payload_media_operations_sanitize(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_memdev_media_operations_sanitize_req) + + 2 * sizeof(struct cxlmi_cmd_memdev_media_ops_dpa_range_list_entry)]; + struct cxlmi_cmd_memdev_media_operations_sanitize_req *req = + (struct cxlmi_cmd_memdev_media_operations_sanitize_req *)req_buf; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint32_t count; + uint64_t dpa, len; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->media_operation_class = 0x00; + req->media_operation_subclass = 0x00; + req->dpa_range_count = 2; + req->dpa_range_list[0].starting_dpa = 0x0000400000000000ULL; + req->dpa_range_list[0].length = 0x0000000010000000ULL; + req->dpa_range_list[1].starting_dpa = 0x0000500000000000ULL; + req->dpa_range_list[1].length = 0x0000000020000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_media_operations_sanitize(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (8 bytes) + 2 ranges (16 bytes each) */ + ASSERT_EQ(payload_size, 8 + 2 * 16, "payload size mismatch"); + + ASSERT_EQ(payload[0], 0x00, "media_operation_class mismatch"); + ASSERT_EQ(payload[1], 0x00, "media_operation_subclass mismatch"); + count = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(count, 2, "dpa_range_count mismatch"); + + /* First range at offset 8 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[8]); + len = le64_to_cpu(*(leint64_t *)&payload[16]); + ASSERT_TRUE(dpa == 0x0000400000000000ULL, "range[0] dpa mismatch"); + ASSERT_TRUE(len == 0x0000000010000000ULL, "range[0] len mismatch"); + + /* Second range at offset 8 + 16 = 24 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[24]); + len = le64_to_cpu(*(leint64_t *)&payload[32]); + ASSERT_TRUE(dpa == 0x0000500000000000ULL, "range[1] dpa mismatch"); + ASSERT_TRUE(len == 0x0000000020000000ULL, "range[1] len mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_phys_port_state(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_req) + 3]; + struct cxlmi_cmd_fmapi_get_phys_port_state_req *req = + (struct cxlmi_cmd_fmapi_get_phys_port_state_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + + 3 * sizeof(struct cxlmi_cmd_fmapi_port_state_info_block)]; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + + 3 * sizeof(struct cxlmi_cmd_fmapi_port_state_info_block)]; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *ret = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)ret_buf; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->num_ports = 3; + req->ports[0] = 0x01; + req->ports[1] = 0x02; + req->ports[2] = 0x05; + + rsp->num_ports = 3; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_phys_port_state(test_ep, NULL, req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (1 byte) + 3 port IDs (1 byte each) */ + ASSERT_EQ(payload_size, 1 + 3, "payload size mismatch"); + + ASSERT_EQ(payload[0], 3, "num_ports mismatch"); + ASSERT_EQ(payload[1], 0x01, "port[0] mismatch"); + ASSERT_EQ(payload[2], 0x02, "port[1] mismatch"); + ASSERT_EQ(payload[3], 0x05, "port[2] mismatch"); + + return 0; +} + +static int test_payload_set_feature(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_set_feature_req) + 8]; + struct cxlmi_cmd_set_feature_req *req = + (struct cxlmi_cmd_set_feature_req *)req_buf; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint32_t flags; + uint16_t offset; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + /* Set a test UUID */ + memset(req->feature_id, 0xAB, sizeof(req->feature_id)); + req->set_feature_flags = 0x00000003; + req->offset = 0x0100; + req->version = 0x01; + memset(req->feature_data, 0x55, 8); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_feature(test_ep, NULL, req, 8); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* feature_id(16) + flags(4) + offset(2) + version(1) + rsvd(9) + feature_data(8) = 40 */ + ASSERT_EQ(payload_size, 32 + 8, "payload size mismatch"); + ASSERT_EQ(payload[0], 0xAB, "feature_id[0] mismatch"); + flags = le32_to_cpu(*(leint32_t *)&payload[16]); + ASSERT_EQ(flags, 0x00000003, "flags mismatch"); + offset = le16_to_cpu(*(leint16_t *)&payload[20]); + ASSERT_EQ(offset, 0x0100, "offset mismatch"); + ASSERT_EQ(payload[22], 0x01, "version mismatch"); + /* feature_data starts at offset 32 (after 16+4+2+1+9 = 32 bytes) */ + ASSERT_EQ(payload[32], 0x55, "feature_data[0] mismatch"); + + return 0; +} + +static int test_payload_get_feature(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp ret = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + uint16_t offset, count; + int rc; + + memset(req.feature_id, 0xCD, sizeof(req.feature_id)); + req.offset = 0x0200; + req.count = 0x0040; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 21, "payload size mismatch"); + ASSERT_EQ(payload[0], 0xCD, "feature_id[0] mismatch"); + offset = le16_to_cpu(*(leint16_t *)&payload[16]); + ASSERT_EQ(offset, 0x0200, "offset mismatch"); + count = le16_to_cpu(*(leint16_t *)&payload[18]); + ASSERT_EQ(count, 0x0040, "count mismatch"); + + return 0; +} + +static int test_payload_memdev_set_passphrase(void) +{ + struct cxlmi_cmd_memdev_set_passphrase_req req = {0}; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + int rc; + + req.passphrase_type = 0x01; + memset(req.current_passphrase, 0x11, sizeof(req.current_passphrase)); + memset(req.new_passphrase, 0x22, sizeof(req.new_passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_passphrase(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* passphrase_type(1) + rsvd(31) + current(32) + new(32) = 96 bytes */ + ASSERT_EQ(payload_size, 96, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x01, "passphrase_type mismatch"); + /* current_passphrase at offset 32 (after type + rsvd) */ + ASSERT_EQ(payload[32], 0x11, "current_passphrase[0] mismatch"); + ASSERT_EQ(payload[63], 0x11, "current_passphrase[31] mismatch"); + /* new_passphrase at offset 64 */ + ASSERT_EQ(payload[64], 0x22, "new_passphrase[0] mismatch"); + ASSERT_EQ(payload[95], 0x22, "new_passphrase[31] mismatch"); + + return 0; +} + +static int test_payload_memdev_disable_passphrase(void) +{ + struct cxlmi_cmd_memdev_disable_passphrase_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.passphrase_type = 0x01; + memset(req.passphrase, 0x33, sizeof(req.passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_disable_passphrase(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* passphrase_type(1) + rsvd(31) + passphrase(32) = 64 bytes */ + ASSERT_EQ(payload_size, 64, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x01, "passphrase_type mismatch"); + /* passphrase at offset 32 (after type + rsvd) */ + ASSERT_EQ(payload[32], 0x33, "passphrase[0] mismatch"); + ASSERT_EQ(payload[63], 0x33, "passphrase[31] mismatch"); + + return 0; +} + +static int test_payload_memdev_unlock(void) +{ + struct cxlmi_cmd_memdev_unlock_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req.current_passphrase, 0x44, sizeof(req.current_passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x03, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_unlock(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* current_passphrase(32) = 32 bytes */ + ASSERT_EQ(payload_size, 32, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x44, "current_passphrase[0] mismatch"); + ASSERT_EQ(payload[31], 0x44, "current_passphrase[31] mismatch"); + + return 0; +} + +static int test_payload_memdev_passphrase_secure_erase(void) +{ + struct cxlmi_cmd_memdev_passphrase_secure_erase_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + req.passphrase_type = 0x01; + memset(req.passphrase, 0x55, sizeof(req.passphrase)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_passphrase_secure_erase(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* passphrase_type(1) + rsvd(31) + passphrase(32) = 64 bytes */ + ASSERT_EQ(payload_size, 64, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x01, "passphrase_type mismatch"); + /* passphrase at offset 32 (after type + rsvd) */ + ASSERT_EQ(payload[32], 0x55, "passphrase[0] mismatch"); + ASSERT_EQ(payload[63], 0x55, "passphrase[31] mismatch"); + + return 0; +} + +static int test_payload_memdev_get_dc_extent_list(void) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp rsp = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + uint32_t cnt, start; + int rc; + + /* Note: library clamps extent_cnt > 8 to 8, so use a value <= 8 */ + req.extent_cnt = 6; + req.start_extent_idx = 5; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, &rsp); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + cnt = le32_to_cpu(*(leint32_t *)&payload[0]); + start = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(cnt, 6, "extent_cnt mismatch"); + ASSERT_EQ(start, 5, "start_extent_idx mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_qos_allocated_bw(void) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)ret_buf; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.number_ld = 4; + req.start_ld_id = 2; + + rsp->number_ld = 4; + rsp->start_ld_id = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x06, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(test_ep, NULL, &req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + ASSERT_EQ(payload[0], 4, "number_ld mismatch"); + ASSERT_EQ(payload[1], 2, "start_ld_id mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_qos_bw_limit(void) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 3]; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 3]; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)ret_buf; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.number_ld = 3; + req.start_ld_id = 1; + + rsp->number_ld = 3; + rsp->start_ld_id = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x08, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(test_ep, NULL, &req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + ASSERT_EQ(payload[0], 3, "number_ld mismatch"); + ASSERT_EQ(payload[1], 1, "start_ld_id mismatch"); + + return 0; +} + +static int test_payload_fmapi_initiate_dc_add(void) +{ + /* Extent size is 40 bytes (8 + 8 + 0x10 + 2 + 6) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_initiate_dc_add_req) + 40]; + struct cxlmi_cmd_fmapi_initiate_dc_add_req *req = + (struct cxlmi_cmd_fmapi_initiate_dc_add_req *)req_buf; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint16_t host_id; + uint64_t length, dpa, len; + uint32_t ext_count; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->host_id = 0x1234; + req->selection_policy = 0x01; + req->region_num = 0x02; + req->length = 0x0000000100000000ULL; + memset(req->tag, 0xEE, sizeof(req->tag)); + req->ext_count = 1; + req->extents[0].start_dpa = 0x0000600000000000ULL; + req->extents[0].len = 0x0000000040000000ULL; + memset(req->extents[0].tag, 0xFF, sizeof(req->extents[0].tag)); + req->extents[0].shared_seq = 0xABCD; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_initiate_dc_add(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (32 bytes) + 1 extent (40 bytes) */ + ASSERT_EQ(payload_size, 32 + 40, "payload size mismatch"); + + host_id = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(host_id, 0x1234, "host_id mismatch"); + ASSERT_EQ(payload[2], 0x01, "selection_policy mismatch"); + ASSERT_EQ(payload[3], 0x02, "region_num mismatch"); + length = le64_to_cpu(*(leint64_t *)&payload[4]); + ASSERT_TRUE(length == 0x0000000100000000ULL, "length mismatch"); + ASSERT_EQ(payload[12], 0xEE, "tag[0] mismatch"); + ext_count = le32_to_cpu(*(leint32_t *)&payload[28]); + ASSERT_EQ(ext_count, 1, "ext_count mismatch"); + + /* Check extent at offset 32 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[32]); + len = le64_to_cpu(*(leint64_t *)&payload[40]); + ASSERT_TRUE(dpa == 0x0000600000000000ULL, "extent dpa mismatch"); + ASSERT_TRUE(len == 0x0000000040000000ULL, "extent len mismatch"); + + return 0; +} + +static int test_payload_fmapi_initiate_dc_release(void) +{ + /* Extent size is 40 bytes (8 + 8 + 0x10 + 2 + 6) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_initiate_dc_release_req) + 40]; + struct cxlmi_cmd_fmapi_initiate_dc_release_req *req = + (struct cxlmi_cmd_fmapi_initiate_dc_release_req *)req_buf; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + uint16_t host_id; + uint64_t length, dpa, len; + uint32_t ext_count; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + req->host_id = 0x5678; + req->flags = 0x03; + req->length = 0x0000000200000000ULL; + memset(req->tag, 0xDD, sizeof(req->tag)); + req->ext_count = 1; + req->extents[0].start_dpa = 0x0000700000000000ULL; + req->extents[0].len = 0x0000000080000000ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_initiate_dc_release(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (32 bytes) + 1 extent (40 bytes) */ + ASSERT_EQ(payload_size, 32 + 40, "payload size mismatch"); + + host_id = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(host_id, 0x5678, "host_id mismatch"); + ASSERT_EQ(payload[2], 0x03, "flags mismatch"); + length = le64_to_cpu(*(leint64_t *)&payload[4]); + ASSERT_TRUE(length == 0x0000000200000000ULL, "length mismatch"); + ASSERT_EQ(payload[12], 0xDD, "tag[0] mismatch"); + ext_count = le32_to_cpu(*(leint32_t *)&payload[28]); + ASSERT_EQ(ext_count, 1, "ext_count mismatch"); + + /* Check extent at offset 32 */ + dpa = le64_to_cpu(*(leint64_t *)&payload[32]); + len = le64_to_cpu(*(leint64_t *)&payload[40]); + ASSERT_TRUE(dpa == 0x0000700000000000ULL, "extent dpa mismatch"); + ASSERT_TRUE(len == 0x0000000080000000ULL, "extent len mismatch"); + + return 0; +} + +static int test_payload_fmapi_send_ld_cxlio_mem_request(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req) + 8]; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req *req = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req *)req_buf; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp) + 8]; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *rsp = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp) + 8]; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *ret = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *)ret_buf; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + uint16_t ld_id, trans_len, trans_addr; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->port_id = 0x03; + req->ld_id = 0x0102; + req->transaction_len = 8; + req->transaction_addr = 0x1000; + memset(req->transaction_data, 0x99, 8); + + rsp->return_size = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x02, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(test_ep, NULL, req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Header (9 bytes) + data (8 bytes) */ + ASSERT_EQ(payload_size, 9 + 8, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x03, "port_id mismatch"); + ld_id = le16_to_cpu(*(leint16_t *)&payload[3]); + ASSERT_EQ(ld_id, 0x0102, "ld_id mismatch"); + trans_len = le16_to_cpu(*(leint16_t *)&payload[5]); + ASSERT_EQ(trans_len, 8, "transaction_len mismatch"); + trans_addr = le16_to_cpu(*(leint16_t *)&payload[7]); + ASSERT_EQ(trans_addr, 0x1000, "transaction_addr mismatch"); + ASSERT_EQ(payload[9], 0x99, "data[0] mismatch"); + + return 0; +} + +static int test_payload_event_notification(void) +{ + struct cxlmi_cmd_event_notification_req req = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t event; + int rc; + + req.event = 0x0003; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x06, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_event_notification(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + event = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(event, 0x0003, "event mismatch"); + + return 0; +} + +static int test_payload_set_mctp_event_interrupt_policy(void) +{ + struct cxlmi_cmd_set_mctp_event_interrupt_policy_req req = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t settings; + int rc; + + req.event_interrupt_settings = 0x1234; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_mctp_event_interrupt_policy(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + settings = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(settings, 0x1234, "event_interrupt_settings mismatch"); + + return 0; +} + +static int test_payload_get_log_cel(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + struct cxlmi_cmd_get_log_cel_rsp ret[2] = {0}; + uint8_t rsp_buf[8] = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + uint32_t offset, length; + int rc; + + memset(req.uuid, 0x00, sizeof(req.uuid)); + req.offset = 0; + req.length = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 24, "payload size mismatch"); + offset = le32_to_cpu(*(leint32_t *)&payload[16]); + length = le32_to_cpu(*(leint32_t *)&payload[20]); + ASSERT_EQ(offset, 0, "offset mismatch"); + ASSERT_EQ(length, 8, "length mismatch"); + + return 0; +} + +static int test_payload_get_supported_features(void) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + /* Response includes header + variable entries (use raw bytes for simplicity) */ + uint8_t rsp_buf[64] = {0}; + struct cxlmi_cmd_get_supported_features_rsp *ret; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint32_t count; + uint16_t start_idx; + int rc; + + ret = (struct cxlmi_cmd_get_supported_features_rsp *)rsp_buf; + req.count = 4; + req.starting_feature_index = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + count = le32_to_cpu(*(leint32_t *)&payload[0]); + start_idx = le16_to_cpu(*(leint16_t *)&payload[4]); + ASSERT_EQ(count, 4, "count mismatch"); + ASSERT_EQ(start_idx, 0, "starting_feature_index mismatch"); + + return 0; +} + +static int test_payload_memdev_media_operations_discovery(void) +{ + struct cxlmi_cmd_memdev_media_operations_discovery_req req = {0}; + struct cxlmi_cmd_memdev_media_operations_discovery_rsp ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint32_t dpa_range_count; + uint16_t start_index, num_ops; + int rc; + + req.media_operation_class = 0x01; + req.media_operation_subclass = 0x02; + req.dpa_range_count = 0; + req.discovery_osa.start_index = 0; + req.discovery_osa.num_ops = 4; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x02, CXLMI_RET_SUCCESS, &ret, sizeof(ret)); + rc = cxlmi_cmd_memdev_media_operations_discovery(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 12, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x01, "media_operation_class mismatch"); + ASSERT_EQ(payload[1], 0x02, "media_operation_subclass mismatch"); + dpa_range_count = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(dpa_range_count, 0, "dpa_range_count mismatch"); + start_index = le16_to_cpu(*(leint16_t *)&payload[8]); + num_ops = le16_to_cpu(*(leint16_t *)&payload[10]); + ASSERT_EQ(start_index, 0, "start_index mismatch"); + ASSERT_EQ(num_ops, 4, "num_ops mismatch"); + + return 0; +} + +static int test_payload_memdev_security_receive(void) +{ + struct cxlmi_cmd_memdev_security_receive_req req = {0}; + /* Response size for protocol 0x00 is 6 + 2 + 256 = 264 bytes */ + uint8_t rsp_buf[264] = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t sp_specific; + int rc; + + req.security_protocol = 0x00; /* Use supported protocol */ + req.sp_specific = 0x0001; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x46, 0x01, CXLMI_RET_SUCCESS, rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_memdev_security_receive(test_ep, NULL, &req, rsp_buf); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x00, "security_protocol mismatch"); + sp_specific = le16_to_cpu(*(leint16_t *)&payload[1]); + ASSERT_EQ(sp_specific, 0x0001, "sp_specific mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_multiheaded_info(void) +{ + struct cxlmi_cmd_fmapi_get_multiheaded_info_req req = {0}; + struct cxlmi_cmd_fmapi_get_multiheaded_info_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + req.start_ld_id = 2; + req.ld_map_list_limit = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x00, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_multiheaded_info(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + ASSERT_EQ(payload[0], 2, "start_ld_id mismatch"); + ASSERT_EQ(payload[1], 8, "ld_map_list_limit mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_head_info(void) +{ + struct cxlmi_cmd_fmapi_get_head_info_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_head_info_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_get_head_info_blkfmt)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_head_info_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_get_head_info_blkfmt)]; + struct cxlmi_cmd_fmapi_get_head_info_rsp *ret = + (struct cxlmi_cmd_fmapi_get_head_info_rsp *)ret_buf; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req.start_head = 1; + req.num_heads = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x01, CXLMI_RET_SUCCESS, rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_head_info(test_ep, NULL, &req, ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 2, "payload size mismatch"); + ASSERT_EQ(payload[0], 1, "start_head mismatch"); + ASSERT_EQ(payload[1], 2, "num_heads mismatch"); + + return 0; +} + +static int test_payload_fmapi_send_ppb_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint32_t trans_data; + int rc; + + req.ppb_id = 0x05; + req.transaction_data = 0x12345678; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ppb_cxlio_config_request(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x05, "ppb_id mismatch"); + trans_data = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(trans_data, 0x12345678, "transaction_data mismatch"); + + return 0; +} + +static int test_payload_fmapi_send_ld_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t ld_id; + uint32_t trans_data; + int rc; + + req.ppb_id = 0x03; + req.ld_id = 0x0102; + req.transaction_data = 0xAABBCCDD; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_config_request(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 12, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x03, "ppb_id mismatch"); + ld_id = le16_to_cpu(*(leint16_t *)&payload[4]); + ASSERT_EQ(ld_id, 0x0102, "ld_id mismatch"); + trans_data = le32_to_cpu(*(leint32_t *)&payload[8]); + ASSERT_EQ(trans_data, 0xAABBCCDD, "transaction_data mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_domain_validation_sv(void) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_req req = {0}; + struct cxlmi_cmd_fmapi_get_domain_validation_sv_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + req.vcs_id = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x07, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_domain_validation_sv(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 1, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x03, "vcs_id mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_domain_validation_sv(void) +{ + struct cxlmi_cmd_fmapi_set_domain_validation_sv_req req = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req.secret_value_uuid, 0xAB, sizeof(req.secret_value_uuid)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x05, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_set_domain_validation_sv(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size mismatch"); + ASSERT_EQ(payload[0], 0xAB, "secret_value_uuid[0] mismatch"); + ASSERT_EQ(payload[15], 0xAB, "secret_value_uuid[15] mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_vcs_domain_validation_sv_state(void) +{ + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_req req = {0}; + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + req.vcs_id = 0x05; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x06, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 1, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x05, "vcs_id mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_dc_reg_config(void) +{ + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp ret = {0}; + uint8_t rsp_buf[100] = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t host_id; + int rc; + + req.host_id = 0x0102; + req.region_cnt = 4; + req.start_region_id = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x01, CXLMI_RET_SUCCESS, rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_dc_reg_config(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 4, "payload size mismatch"); + host_id = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(host_id, 0x0102, "host_id mismatch"); + ASSERT_EQ(payload[2], 4, "region_cnt mismatch"); + ASSERT_EQ(payload[3], 1, "start_region_id mismatch"); + + return 0; +} + +static int test_payload_fmapi_set_dc_region_config(void) +{ + struct cxlmi_cmd_fmapi_set_dc_region_config_req req = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + uint64_t block_sz; + int rc; + + req.region_id = 2; + req.block_sz = 0x100000; + req.sanitize_on_release = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_set_dc_region_config(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size mismatch"); + ASSERT_EQ(payload[0], 2, "region_id mismatch"); + block_sz = le64_to_cpu(*(leint64_t *)&payload[4]); + ASSERT_EQ(block_sz, 0x100000, "block_sz mismatch"); + ASSERT_EQ(payload[12], 1, "sanitize_on_release mismatch"); + + return 0; +} + +static int test_payload_fmapi_get_dc_region_ext_list(void) +{ + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint16_t host_id; + uint32_t extent_count, start_ext_index; + int rc; + + req.host_id = 0x0203; + req.extent_count = 8; + req.start_ext_index = 4; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x03, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 12, "payload size mismatch"); + host_id = le16_to_cpu(*(leint16_t *)&payload[0]); + ASSERT_EQ(host_id, 0x0203, "host_id mismatch"); + extent_count = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(extent_count, 8, "extent_count mismatch"); + start_ext_index = le32_to_cpu(*(leint32_t *)&payload[8]); + ASSERT_EQ(start_ext_index, 4, "start_ext_index mismatch"); + + return 0; +} + +static int test_payload_fmapi_dc_add_reference(void) +{ + struct cxlmi_cmd_fmapi_dc_add_ref_req req = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req.tag, 0xCD, sizeof(req.tag)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x06, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_dc_add_reference(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size mismatch"); + ASSERT_EQ(payload[0], 0xCD, "tag[0] mismatch"); + ASSERT_EQ(payload[15], 0xCD, "tag[15] mismatch"); + + return 0; +} + +static int test_payload_fmapi_dc_remove_reference(void) +{ + struct cxlmi_cmd_fmapi_dc_remove_ref_req req = {0}; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req.tag, 0xEF, sizeof(req.tag)); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x07, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_fmapi_dc_remove_reference(test_ep, NULL, &req); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 16, "payload size mismatch"); + ASSERT_EQ(payload[0], 0xEF, "tag[0] mismatch"); + ASSERT_EQ(payload[15], 0xEF, "tag[15] mismatch"); + + return 0; +} + +static int test_payload_fmapi_dc_list_tags(void) +{ + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp rsp = {0}, ret = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + uint32_t start_idx, tags_count; + int rc; + + req.start_idx = 5; + req.tags_count = 10; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x08, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_dc_list_tags(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + start_idx = le32_to_cpu(*(leint32_t *)&payload[0]); + tags_count = le32_to_cpu(*(leint32_t *)&payload[4]); + ASSERT_EQ(start_idx, 5, "start_idx mismatch"); + ASSERT_EQ(tags_count, 10, "tags_count mismatch"); + + return 0; +} + +static int test_payload_vendor_specific(void) +{ + uint8_t cmd_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + uint8_t rsp_data[8] = {0}; + uint8_t payload[16]; + size_t payload_size = sizeof(payload); + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0xC0, 0x00, CXLMI_RET_SUCCESS, rsp_data, sizeof(rsp_data)); + rc = cxlmi_cmd_vendor_specific(test_ep, NULL, 0xC000, cmd_data, sizeof(cmd_data), + rsp_data, sizeof(rsp_data)); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(payload_size, 8, "payload size mismatch"); + ASSERT_EQ(payload[0], 0x11, "cmd_data[0] mismatch"); + ASSERT_EQ(payload[7], 0x88, "cmd_data[7] mismatch"); + + return 0; +} + +/* ============================================================ + * Response Payload Verification Tests + * These tests verify that the library correctly decodes + * little-endian response data from the device. + * ============================================================ */ + +static int test_response_identify(void) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}, ret = {0}; + int rc; + + /* Set up response in little-endian wire format */ + rsp.vendor_id = cpu_to_le16(0x1234); + rsp.device_id = cpu_to_le16(0x5678); + rsp.subsys_vendor_id = cpu_to_le16(0xABCD); + rsp.subsys_id = cpu_to_le16(0xEF01); + rsp.serial_num = cpu_to_le64(0x123456789ABCDEF0ULL); + rsp.max_msg_size = 9; /* 512 bytes */ + rsp.component_type = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.vendor_id, 0x1234, "vendor_id mismatch"); + ASSERT_EQ(ret.device_id, 0x5678, "device_id mismatch"); + ASSERT_EQ(ret.subsys_vendor_id, 0xABCD, "subsys_vendor_id mismatch"); + ASSERT_EQ(ret.subsys_id, 0xEF01, "subsys_id mismatch"); + ASSERT_EQ(ret.serial_num, 0x123456789ABCDEF0ULL, "serial_num mismatch"); + ASSERT_EQ(ret.max_msg_size, 9, "max_msg_size mismatch"); + ASSERT_EQ(ret.component_type, 0x03, "component_type mismatch"); + + return 0; +} + +static int test_response_bg_op_status(void) +{ + struct cxlmi_cmd_bg_op_status_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.status = 0x02; /* In progress */ + rsp.opcode = cpu_to_le16(0x4400); /* Sanitize */ + rsp.returncode = cpu_to_le16(0x0000); + rsp.vendor_ext_status = cpu_to_le16(0x1234); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_bg_op_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.status, 0x02, "status mismatch"); + ASSERT_EQ(ret.opcode, 0x4400, "opcode mismatch"); + ASSERT_EQ(ret.vendor_ext_status, 0x1234, "vendor_ext_status mismatch"); + + return 0; +} + +static int test_response_get_timestamp(void) +{ + struct cxlmi_cmd_get_timestamp_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.timestamp = cpu_to_le64(0xFEDCBA9876543210ULL); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_timestamp(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.timestamp, 0xFEDCBA9876543210ULL, "timestamp mismatch"); + + return 0; +} + +static int test_response_memdev_identify(void) +{ + struct cxlmi_cmd_memdev_identify_rsp rsp = {0}, ret = {0}; + int rc; + + memcpy(rsp.fw_revision, "TestFW-1.2.3 ", 16); + rsp.total_capacity = cpu_to_le64(0x100000000ULL); /* 4GB in 256MB units */ + rsp.volatile_capacity = cpu_to_le64(0x80000000ULL); + rsp.persistent_capacity = cpu_to_le64(0x80000000ULL); + rsp.partition_align = cpu_to_le64(0x10000000ULL); + rsp.info_event_log_size = cpu_to_le16(1024); + rsp.warning_event_log_size = cpu_to_le16(512); + rsp.failure_event_log_size = cpu_to_le16(256); + rsp.fatal_event_log_size = cpu_to_le16(128); + rsp.lsa_size = cpu_to_le32(65536); + rsp.inject_poison_limit = cpu_to_le16(256); + rsp.poison_caps = 0x07; + rsp.qos_telemetry_caps = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x40, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.total_capacity, 0x100000000ULL, "total_capacity mismatch"); + ASSERT_EQ(ret.volatile_capacity, 0x80000000ULL, "volatile_capacity mismatch"); + ASSERT_EQ(ret.persistent_capacity, 0x80000000ULL, "persistent_capacity mismatch"); + ASSERT_EQ(ret.partition_align, 0x10000000ULL, "partition_align mismatch"); + ASSERT_EQ(ret.info_event_log_size, 1024, "info_event_log_size mismatch"); + ASSERT_EQ(ret.warning_event_log_size, 512, "warning_event_log_size mismatch"); + ASSERT_EQ(ret.lsa_size, 65536, "lsa_size mismatch"); + ASSERT_EQ(ret.inject_poison_limit, 256, "inject_poison_limit mismatch"); + + return 0; +} + +static int test_response_memdev_get_partition_info(void) +{ + struct cxlmi_cmd_memdev_get_partition_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.active_vmem = cpu_to_le64(0x200000000ULL); + rsp.active_pmem = cpu_to_le64(0x300000000ULL); + rsp.next_vmem = cpu_to_le64(0x180000000ULL); + rsp.next_pmem = cpu_to_le64(0x380000000ULL); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_partition_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.active_vmem, 0x200000000ULL, "active_vmem mismatch"); + ASSERT_EQ(ret.active_pmem, 0x300000000ULL, "active_pmem mismatch"); + ASSERT_EQ(ret.next_vmem, 0x180000000ULL, "next_vmem mismatch"); + ASSERT_EQ(ret.next_pmem, 0x380000000ULL, "next_pmem mismatch"); + + return 0; +} + +static int test_response_memdev_get_health_info(void) +{ + struct cxlmi_cmd_memdev_get_health_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.health_status = 0x01; + rsp.media_status = 0x02; + rsp.additional_status = 0x03; + rsp.life_used = 25; /* 25% */ + rsp.device_temperature = cpu_to_le16(325); /* 32.5C */ + rsp.dirty_shutdown_count = cpu_to_le32(5); + rsp.corrected_volatile_error_count = cpu_to_le32(100); + rsp.corrected_persistent_error_count = cpu_to_le32(50); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_health_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.health_status, 0x01, "health_status mismatch"); + ASSERT_EQ(ret.media_status, 0x02, "media_status mismatch"); + ASSERT_EQ(ret.life_used, 25, "life_used mismatch"); + ASSERT_EQ(ret.device_temperature, 325, "device_temperature mismatch"); + ASSERT_EQ(ret.dirty_shutdown_count, 5, "dirty_shutdown_count mismatch"); + ASSERT_EQ(ret.corrected_volatile_error_count, 100, "corrected_volatile_error_count mismatch"); + ASSERT_EQ(ret.corrected_persistent_error_count, 50, "corrected_persistent_error_count mismatch"); + + return 0; +} + +static int test_response_memdev_get_alert_config(void) +{ + struct cxlmi_cmd_memdev_get_alert_config_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.valid_alerts = 0xFF; + rsp.programmable_alerts = 0x0F; + rsp.life_used_critical_alert_threshold = 90; + rsp.life_used_programmable_warning_threshold = 80; + rsp.device_over_temperature_critical_alert_threshold = cpu_to_le16(850); + rsp.device_under_temperature_critical_alert_threshold = cpu_to_le16(100); + rsp.device_over_temperature_programmable_warning_threshold = cpu_to_le16(750); + rsp.device_under_temperature_programmable_warning_threshold = cpu_to_le16(150); + rsp.corrected_volatile_mem_error_programmable_warning_threshold = cpu_to_le16(1000); + rsp.corrected_persistent_mem_error_programmable_warning_threshold = cpu_to_le16(500); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_alert_config(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.valid_alerts, 0xFF, "valid_alerts mismatch"); + ASSERT_EQ(ret.life_used_critical_alert_threshold, 90, "life_used_critical mismatch"); + ASSERT_EQ(ret.device_over_temperature_critical_alert_threshold, 850, "over_temp_critical mismatch"); + ASSERT_EQ(ret.device_over_temperature_programmable_warning_threshold, 750, "over_temp_warning mismatch"); + ASSERT_EQ(ret.corrected_volatile_mem_error_programmable_warning_threshold, 1000, "volatile_error_warning mismatch"); + + return 0; +} + +static int test_response_fmapi_identify_sw_device(void) +{ + struct cxlmi_cmd_fmapi_identify_sw_device_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.ingress_port_id = 0x05; + rsp.num_physical_ports = 16; + rsp.num_vcs = 4; + rsp.active_port_bitmask[0] = 0xFF; + rsp.active_port_bitmask[1] = 0x0F; + rsp.active_vcs_bitmask[0] = 0x0F; + rsp.num_total_vppb = cpu_to_le16(64); + rsp.num_active_vppb = cpu_to_le16(32); + rsp.num_hdm_decoder_per_usp = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_identify_sw_device(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.ingress_port_id, 0x05, "ingress_port_id mismatch"); + ASSERT_EQ(ret.num_physical_ports, 16, "num_physical_ports mismatch"); + ASSERT_EQ(ret.num_vcs, 4, "num_vcs mismatch"); + ASSERT_EQ(ret.num_total_vppb, 64, "num_total_vppb mismatch"); + ASSERT_EQ(ret.num_active_vppb, 32, "num_active_vppb mismatch"); + ASSERT_EQ(ret.num_hdm_decoder_per_usp, 8, "num_hdm_decoder_per_usp mismatch"); + + return 0; +} + +static int test_response_fmapi_get_ld_info(void) +{ + struct cxlmi_cmd_fmapi_get_ld_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.memory_size = cpu_to_le64(0x400000000ULL); /* 16GB */ + rsp.ld_count = cpu_to_le16(8); + rsp.qos_telemetry_capability = 0x07; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_ld_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.memory_size, 0x400000000ULL, "memory_size mismatch"); + ASSERT_EQ(ret.ld_count, 8, "ld_count mismatch"); + ASSERT_EQ(ret.qos_telemetry_capability, 0x07, "qos_telemetry_capability mismatch"); + + return 0; +} + +static int test_response_fmapi_get_qos_status(void) +{ + struct cxlmi_cmd_fmapi_get_qos_status_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.backpressure_avg_percentage = 45; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.backpressure_avg_percentage, 45, "backpressure mismatch"); + + return 0; +} + +static int test_response_get_fw_info(void) +{ + struct cxlmi_cmd_get_fw_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.slots_supported = 4; + rsp.slot_info = 0x12; /* Active slot 2, staged slot 1 */ + rsp.caps = 0x03; + memcpy(rsp.fw_rev1, "FW-Slot1-v1.0 ", 16); + memcpy(rsp.fw_rev2, "FW-Slot2-v2.0 ", 16); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x02, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_fw_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.slots_supported, 4, "slots_supported mismatch"); + ASSERT_EQ(ret.slot_info, 0x12, "slot_info mismatch"); + ASSERT_EQ(ret.caps, 0x03, "caps mismatch"); + ASSERT_EQ(memcmp(ret.fw_rev1, "FW-Slot1-v1.0 ", 16), 0, "fw_rev1 mismatch"); + + return 0; +} + +static int test_response_memdev_get_poison_list(void) +{ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)]; + struct cxlmi_cmd_memdev_get_poison_list_rsp *rsp = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)]; + struct cxlmi_cmd_memdev_get_poison_list_rsp *ret = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)ret_buf; + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->poison_list_flags = 0x01; + rsp->overflow_timestamp = cpu_to_le64(0x123456789ABCDEF0ULL); + rsp->more_err_media_record_cnt = cpu_to_le16(2); + rsp->records[0].media_err_addr = cpu_to_le64(0x1000); + rsp->records[0].media_err_len = cpu_to_le32(0x100); + rsp->records[1].media_err_addr = cpu_to_le64(0x2000); + rsp->records[1].media_err_len = cpu_to_le32(0x200); + + req.get_poison_list_phy_addr = 0; + req.get_poison_list_phy_addr_len = 0x10000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->poison_list_flags, 0x01, "poison_list_flags mismatch"); + ASSERT_EQ(ret->overflow_timestamp, 0x123456789ABCDEF0ULL, "overflow_timestamp mismatch"); + ASSERT_EQ(ret->more_err_media_record_cnt, 2, "more_err_media_record_cnt mismatch"); + ASSERT_EQ(ret->records[0].media_err_addr, 0x1000, "record[0].media_err_addr mismatch"); + ASSERT_EQ(ret->records[0].media_err_len, 0x100, "record[0].media_err_len mismatch"); + ASSERT_EQ(ret->records[1].media_err_addr, 0x2000, "record[1].media_err_addr mismatch"); + + return 0; +} + +static int test_response_get_event_records(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.flags = 0x03; + rsp.overflow_err_count = cpu_to_le16(5); + rsp.first_overflow_timestamp = cpu_to_le64(0x1111222233334444ULL); + rsp.last_overflow_timestamp = cpu_to_le64(0x5555666677778888ULL); + rsp.record_count = cpu_to_le16(0); /* No records to avoid flexible array */ + + req.event_log = 0x00; /* Information log */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.flags, 0x03, "flags mismatch"); + ASSERT_EQ(ret.overflow_err_count, 5, "overflow_err_count mismatch"); + ASSERT_EQ(ret.first_overflow_timestamp, 0x1111222233334444ULL, "first_overflow_timestamp mismatch"); + ASSERT_EQ(ret.last_overflow_timestamp, 0x5555666677778888ULL, "last_overflow_timestamp mismatch"); + ASSERT_EQ(ret.record_count, 0, "record_count mismatch"); + + return 0; +} + +static int test_response_get_supported_logs(void) +{ + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_get_supported_logs_rsp) + + 3 * sizeof(struct cxlmi_supported_log_entry)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_logs_rsp) + + 3 * sizeof(struct cxlmi_supported_log_entry)]; + struct cxlmi_cmd_get_supported_logs_rsp *rsp = + (struct cxlmi_cmd_get_supported_logs_rsp *)rsp_buf; + struct cxlmi_cmd_get_supported_logs_rsp *ret = + (struct cxlmi_cmd_get_supported_logs_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->num_supported_log_entries = cpu_to_le16(3); + /* Set some test data in entries */ + memset(rsp->entries[0].uuid, 0xAA, sizeof(rsp->entries[0].uuid)); + rsp->entries[0].log_size = cpu_to_le32(0x1000); + memset(rsp->entries[1].uuid, 0xBB, sizeof(rsp->entries[1].uuid)); + rsp->entries[1].log_size = cpu_to_le32(0x2000); + memset(rsp->entries[2].uuid, 0xCC, sizeof(rsp->entries[2].uuid)); + rsp->entries[2].log_size = cpu_to_le32(0x3000); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 3, "num_supported_log_entries mismatch"); + ASSERT_EQ(ret->entries[0].uuid[0], 0xAA, "entry[0].uuid wrong"); + ASSERT_EQ(ret->entries[0].log_size, 0x1000, "entry[0].log_size wrong"); + ASSERT_EQ(ret->entries[1].uuid[0], 0xBB, "entry[1].uuid wrong"); + ASSERT_EQ(ret->entries[1].log_size, 0x2000, "entry[1].log_size wrong"); + ASSERT_EQ(ret->entries[2].uuid[0], 0xCC, "entry[2].uuid wrong"); + ASSERT_EQ(ret->entries[2].log_size, 0x3000, "entry[2].log_size wrong"); + + return 0; +} + +static int test_response_get_response_msg_limit(void) +{ + struct cxlmi_cmd_get_response_msg_limit_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.limit = 10; /* 1KB */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_response_msg_limit(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.limit, 10, "limit mismatch"); + + return 0; +} + +static int test_response_memdev_get_shutdown_state(void) +{ + struct cxlmi_cmd_memdev_get_shutdown_state_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.state = 0x01; /* Clean shutdown */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_shutdown_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.state, 0x01, "state mismatch"); + + return 0; +} + +static int test_response_memdev_get_security_state(void) +{ + struct cxlmi_cmd_memdev_get_security_state_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.security_state = cpu_to_le32(0x0000001F); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x45, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_security_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.security_state, 0x0000001F, "security_state mismatch"); + + return 0; +} + +static int test_response_memdev_get_sld_qos_control(void) +{ + struct cxlmi_cmd_memdev_get_sld_qos_control_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.qos_telemetry_control = 0x03; + rsp.egress_moderate_percentage = 50; + rsp.egress_severe_percentage = 80; + rsp.backpressure_sample_interval = 100; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_sld_qos_control(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x03, "qos_telemetry_control mismatch"); + ASSERT_EQ(ret.egress_moderate_percentage, 50, "egress_moderate_percentage mismatch"); + ASSERT_EQ(ret.egress_severe_percentage, 80, "egress_severe_percentage mismatch"); + ASSERT_EQ(ret.backpressure_sample_interval, 100, "backpressure_sample_interval mismatch"); + + return 0; +} + +static int test_response_memdev_get_sld_qos_status(void) +{ + struct cxlmi_cmd_memdev_get_sld_qos_status_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.backpressure_avg_percentage = 42; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x47, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_sld_qos_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.backpressure_avg_percentage, 42, "backpressure_avg_percentage mismatch"); + + return 0; +} + +static int test_response_fmapi_get_qos_control(void) +{ + struct cxlmi_cmd_fmapi_get_qos_control_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.qos_telemetry_control = 0x07; + rsp.egress_moderate_percentage = 40; + rsp.egress_severe_percentage = 70; + rsp.backpressure_sample_interval = 50; + rsp.recmpbasis = cpu_to_le16(1000); + rsp.completion_collection_interval = 5; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_qos_control(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x07, "qos_telemetry_control mismatch"); + ASSERT_EQ(ret.egress_moderate_percentage, 40, "egress_moderate_percentage mismatch"); + ASSERT_EQ(ret.recmpbasis, 1000, "recmpbasis mismatch"); + ASSERT_EQ(ret.completion_collection_interval, 5, "completion_collection_interval mismatch"); + + return 0; +} + +static int test_response_get_scan_media_capabilities(void) +{ + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.estimated_scan_media_time = cpu_to_le32(3600); + + req.get_scan_media_capabilities_start_physaddr = 0x1000; + req.get_scan_media_capabilities_physaddr_length = 0x10000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_scan_media_capabilities(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.estimated_scan_media_time, 3600, "estimated_scan_media_time mismatch"); + + return 0; +} + +static int test_response_get_scan_media_results(void) +{ + struct cxlmi_cmd_get_scan_media_results_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.scan_media_restart_physaddr = cpu_to_le64(0x2000); + rsp.scan_media_restart_physaddr_length = cpu_to_le64(0x8000); + rsp.scan_media_flags = 0x01; + rsp.media_error_count = cpu_to_le16(0); /* No records to avoid flexible array */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x05, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_scan_media_results(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.scan_media_restart_physaddr, 0x2000, "scan_media_restart_physaddr mismatch"); + ASSERT_EQ(ret.scan_media_restart_physaddr_length, 0x8000, "scan_media_restart_physaddr_length mismatch"); + ASSERT_EQ(ret.scan_media_flags, 0x01, "scan_media_flags mismatch"); + + return 0; +} + +static int test_response_memdev_get_dc_config(void) +{ + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp ret = {0}; + /* + * The wire format has the extents/tags fields immediately after the + * returned region_configs, not at a fixed offset. Build a buffer with + * 2 regions followed by the 4 uint32 fields. + */ + uint8_t rsp_buf[8 + 2*40 + 16] = {0}; /* hdr + 2 regions + 4 u32s */ + uint8_t *p = rsp_buf; + int rc; + + /* Header: num_regions, regions_returned, rsvd[6] */ + p[0] = 2; /* num_regions */ + p[1] = 2; /* regions_returned */ + p += 8; /* skip rsvd */ + + /* Region config 0 */ + *(leint64_t *)p = cpu_to_le64(0x100000000ULL); p += 8; /* base */ + *(leint64_t *)p = cpu_to_le64(0x80000000ULL); p += 8; /* decode_len */ + *(leint64_t *)p = cpu_to_le64(0x80000000ULL); p += 8; /* region_len */ + *(leint64_t *)p = cpu_to_le64(0x40); p += 8; /* block_size */ + *(leint32_t *)p = cpu_to_le32(0); p += 4; /* dsmadhandle */ + *p++ = 0; p += 3; /* flags + rsvd */ + + /* Region config 1 */ + *(leint64_t *)p = cpu_to_le64(0x200000000ULL); p += 8; /* base */ + *(leint64_t *)p = cpu_to_le64(0x40000000ULL); p += 8; /* decode_len */ + *(leint64_t *)p = cpu_to_le64(0x40000000ULL); p += 8; /* region_len */ + *(leint64_t *)p = cpu_to_le64(0x80); p += 8; /* block_size */ + *(leint32_t *)p = cpu_to_le32(0); p += 4; /* dsmadhandle */ + *p++ = 0; p += 3; /* flags + rsvd */ + + /* Extents/tags fields immediately after 2 regions */ + *(leint32_t *)p = cpu_to_le32(1024); p += 4; /* num_extents_supported */ + *(leint32_t *)p = cpu_to_le32(512); p += 4; /* num_extents_available */ + *(leint32_t *)p = cpu_to_le32(256); p += 4; /* num_tags_supported */ + *(leint32_t *)p = cpu_to_le32(128); /* num_tags_available */ + + req.region_cnt = 8; + req.start_region_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x00, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_memdev_get_dc_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_regions, 2, "num_regions mismatch"); + ASSERT_EQ(ret.regions_returned, 2, "regions_returned mismatch"); + ASSERT_EQ(ret.region_configs[0].base, 0x100000000ULL, "region_configs[0].base mismatch"); + ASSERT_EQ(ret.num_extents_supported, 1024, "num_extents_supported mismatch"); + ASSERT_EQ(ret.num_extents_available, 512, "num_extents_available mismatch"); + ASSERT_EQ(ret.num_tags_supported, 256, "num_tags_supported mismatch"); + ASSERT_EQ(ret.num_tags_available, 128, "num_tags_available mismatch"); + + return 0; +} + +static int test_response_fmapi_get_ld_allocations(void) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 2; + rsp->memory_granularity = 8; + rsp->start_ld_id = 0; + rsp->ld_allocation_list_len = 2; + rsp->ld_allocation_list[0].range_1_allocation_mult = cpu_to_le64(0x100); + rsp->ld_allocation_list[0].range_2_allocation_mult = cpu_to_le64(0x200); + rsp->ld_allocation_list[1].range_1_allocation_mult = cpu_to_le64(0x150); + rsp->ld_allocation_list[1].range_2_allocation_mult = cpu_to_le64(0x250); + + req.start_ld_id = 0; + req.ld_allocation_list_limit = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_ld_allocations(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld mismatch"); + ASSERT_EQ(ret->memory_granularity, 8, "memory_granularity mismatch"); + ASSERT_EQ(ret->ld_allocation_list[0].range_1_allocation_mult, 0x100, "entries[0].range1 mismatch"); + ASSERT_EQ(ret->ld_allocation_list[1].range_1_allocation_mult, 0x150, "entries[1].range1 mismatch"); + + return 0; +} + +static int test_response_fmapi_get_qos_allocated_bw(void) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 4; + rsp->start_ld_id = 0; + rsp->qos_allocation_fraction[0] = 64; + rsp->qos_allocation_fraction[1] = 64; + rsp->qos_allocation_fraction[2] = 64; + rsp->qos_allocation_fraction[3] = 64; + + req.number_ld = 4; + req.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x06, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 4, "number_ld mismatch"); + ASSERT_EQ(ret->qos_allocation_fraction[0], 64, "fractions[0] mismatch"); + ASSERT_EQ(ret->qos_allocation_fraction[3], 64, "fractions[3] mismatch"); + + return 0; +} + +static int test_response_fmapi_get_qos_bw_limit(void) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 4]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 4; + rsp->start_ld_id = 0; + rsp->qos_limit_fraction[0] = 128; + rsp->qos_limit_fraction[1] = 128; + rsp->qos_limit_fraction[2] = 255; + rsp->qos_limit_fraction[3] = 255; + + req.number_ld = 4; + req.start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x08, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 4, "number_ld mismatch"); + ASSERT_EQ(ret->qos_limit_fraction[0], 128, "limits[0] mismatch"); + ASSERT_EQ(ret->qos_limit_fraction[2], 255, "limits[2] mismatch"); + + return 0; +} + +static int test_response_get_event_interrupt_policy(void) +{ + struct cxlmi_cmd_get_event_interrupt_policy_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.informational_settings = 0x01; + rsp.warning_settings = 0x02; + rsp.failure_settings = 0x04; + rsp.fatal_settings = 0x08; +#ifndef SUPPORT_CXL_2_0 + rsp.dcd_settings = 0x10; +#endif + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_event_interrupt_policy(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.informational_settings, 0x01, "informational_settings mismatch"); + ASSERT_EQ(ret.warning_settings, 0x02, "warning_settings mismatch"); + ASSERT_EQ(ret.failure_settings, 0x04, "failure_settings mismatch"); + ASSERT_EQ(ret.fatal_settings, 0x08, "fatal_settings mismatch"); + + return 0; +} + +static int test_response_get_mctp_event_interrupt_policy(void) +{ + struct cxlmi_cmd_get_mctp_event_interrupt_policy_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.event_interrupt_settings = cpu_to_le16(0x1234); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_mctp_event_interrupt_policy(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.event_interrupt_settings, 0x1234, "event_interrupt_settings mismatch"); + + return 0; +} + +static int test_response_get_log_capabilities(void) +{ + struct cxlmi_cmd_get_log_capabilities_req req = {0}; + struct cxlmi_cmd_get_log_capabilities_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.parameter_flags = cpu_to_le32(0x00000007); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x02, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_log_capabilities(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.parameter_flags, 0x00000007, "parameter_flags mismatch"); + + return 0; +} + +static int test_response_get_supported_logs_sublist(void) +{ + struct cxlmi_cmd_get_supported_logs_sublist_req req = {0}; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_get_supported_logs_sublist_rsp) + + 2 * sizeof(struct cxlmi_supported_log_entry)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_logs_sublist_rsp) + + 2 * sizeof(struct cxlmi_supported_log_entry)]; + struct cxlmi_cmd_get_supported_logs_sublist_rsp *rsp = + (struct cxlmi_cmd_get_supported_logs_sublist_rsp *)rsp_buf; + struct cxlmi_cmd_get_supported_logs_sublist_rsp *ret = + (struct cxlmi_cmd_get_supported_logs_sublist_rsp *)ret_buf; + int rc; + + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->num_supported_log_entries = 2; + rsp->total_num_supported_log_entries = cpu_to_le16(10); + rsp->start_log_entry_index = 2; + /* Set UUIDs for the entries */ + memset(rsp->entries[0].uuid, 0xAA, sizeof(rsp->entries[0].uuid)); + rsp->entries[0].log_size = cpu_to_le32(0x1000); + memset(rsp->entries[1].uuid, 0xBB, sizeof(rsp->entries[1].uuid)); + rsp->entries[1].log_size = cpu_to_le32(0x2000); + + req.max_supported_log_entries = 5; + req.start_log_entry_index = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x05, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_supported_logs_sublist(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 2, "num_supported_log_entries mismatch"); + ASSERT_EQ(ret->total_num_supported_log_entries, 10, "total_num_supported_log_entries mismatch"); + ASSERT_EQ(ret->start_log_entry_index, 2, "start_log_entry_index mismatch"); + ASSERT_EQ(ret->entries[0].uuid[0], 0xAA, "entries[0].uuid[0] mismatch"); + ASSERT_EQ(ret->entries[0].log_size, 0x1000, "entries[0].log_size mismatch"); + ASSERT_EQ(ret->entries[1].uuid[0], 0xBB, "entries[1].uuid[0] mismatch"); + ASSERT_EQ(ret->entries[1].log_size, 0x2000, "entries[1].log_size mismatch"); + + return 0; +} + +static int test_response_get_supported_features(void) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + /* Entry struct is 48 bytes: 16 (feature_id) + 2+2+2+4+1+1+2+18 */ + struct { + uint16_t num_supported_feature_entries; + uint16_t device_supported_features; + uint8_t rsvd[4]; + struct { + uint8_t feature_id[0x10]; + uint16_t feature_index; + uint16_t get_feature_size; + uint16_t set_feature_size; + uint32_t attribute_flags; + uint8_t get_feature_version; + uint8_t set_feature_version; + uint16_t set_feature_effects; + uint8_t rsvd[18]; + } __attribute__((packed)) entries[2]; + } __attribute__((packed)) rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_features_rsp) + 96]; + struct cxlmi_cmd_get_supported_features_rsp *ret = + (struct cxlmi_cmd_get_supported_features_rsp *)ret_buf; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp.num_supported_feature_entries = cpu_to_le16(2); + rsp.device_supported_features = cpu_to_le16(8); + memset(rsp.entries[0].feature_id, 0xAA, sizeof(rsp.entries[0].feature_id)); + rsp.entries[0].feature_index = cpu_to_le16(0); + rsp.entries[0].get_feature_size = cpu_to_le16(64); + memset(rsp.entries[1].feature_id, 0xBB, sizeof(rsp.entries[1].feature_id)); + rsp.entries[1].feature_index = cpu_to_le16(1); + rsp.entries[1].get_feature_size = cpu_to_le16(128); + + req.count = sizeof(rsp); + req.starting_feature_index = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_feature_entries, 2, "num_supported_feature_entries mismatch"); + ASSERT_EQ(ret->device_supported_features, 8, "device_supported_features mismatch"); + ASSERT_EQ(ret->supported_feature_entries[0].feature_id[0], 0xAA, "entries[0].feature_id mismatch"); + ASSERT_EQ(ret->supported_feature_entries[0].get_feature_size, 64, "entries[0].get_feature_size mismatch"); + ASSERT_EQ(ret->supported_feature_entries[1].feature_id[0], 0xBB, "entries[1].feature_id mismatch"); + + return 0; +} + +static int test_response_get_feature(void) +{ + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp rsp = {0}, ret = {0}; + int rc; + + /* Set some feature data */ + rsp.feature_data[0] = 0xAA; + rsp.feature_data[1] = 0xBB; + rsp.feature_data[2] = 0xCC; + + req.count = 64; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_get_feature(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.feature_data[0], 0xAA, "feature_data[0] mismatch"); + ASSERT_EQ(ret.feature_data[1], 0xBB, "feature_data[1] mismatch"); + ASSERT_EQ(ret.feature_data[2], 0xCC, "feature_data[2] mismatch"); + + return 0; +} + +static int test_response_memdev_get_dc_extent_list(void) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.num_extents_returned = cpu_to_le32(0); + rsp.total_num_extents = cpu_to_le32(10); + rsp.generation_num = cpu_to_le32(5); + + req.extent_cnt = 8; + req.start_extent_idx = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_extents_returned, 0, "num_extents_returned mismatch"); + ASSERT_EQ(ret.total_num_extents, 10, "total_num_extents mismatch"); + ASSERT_EQ(ret.generation_num, 5, "generation_num mismatch"); + + return 0; +} + +static int test_response_fmapi_get_phys_port_state(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_req) + 2]; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_port_state_info_block)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_phys_port_state_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_port_state_info_block)]; + struct cxlmi_cmd_fmapi_get_phys_port_state_req *req = + (struct cxlmi_cmd_fmapi_get_phys_port_state_req *)req_buf; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *rsp = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *ret = + (struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->num_ports = 2; + rsp->ports[0].port_id = 1; + rsp->ports[0].config_state = 0x04; + rsp->ports[0].current_link_speed = 5; + rsp->ports[1].port_id = 2; + rsp->ports[1].config_state = 0x04; + rsp->ports[1].current_link_speed = 4; + + req->num_ports = 2; + req->ports[0] = 1; + req->ports[1] = 2; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_phys_port_state(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_ports, 2, "num_ports mismatch"); + ASSERT_EQ(ret->ports[0].port_id, 1, "ports[0].port_id mismatch"); + ASSERT_EQ(ret->ports[0].current_link_speed, 5, "ports[0].current_link_speed mismatch"); + ASSERT_EQ(ret->ports[1].port_id, 2, "ports[1].port_id mismatch"); + + return 0; +} + +static int test_response_fmapi_get_dcd_info(void) +{ + struct cxlmi_cmd_fmapi_get_dcd_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.num_hosts = 4; + rsp.num_supported_dc_regions = 8; + rsp.capacity_selection_policies = cpu_to_le16(0x0007); + rsp.capacity_removal_policies = cpu_to_le16(0x0003); + /* Library multiplies by 256MB, so set raw value as 4 -> 4*256MB = 1GB */ + rsp.total_dynamic_capacity = cpu_to_le64(4); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dcd_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_hosts, 4, "num_hosts mismatch"); + ASSERT_EQ(ret.num_supported_dc_regions, 8, "num_supported_dc_regions mismatch"); + ASSERT_EQ(ret.capacity_selection_policies, 0x0007, "capacity_selection_policies mismatch"); + /* 4 * 256MB = 1GB = 0x40000000 */ + ASSERT_TRUE(ret.total_dynamic_capacity == 0x40000000ULL, "total_dynamic_capacity mismatch"); + + return 0; +} + +static int test_response_fmapi_get_multiheaded_info(void) +{ + struct cxlmi_cmd_fmapi_get_multiheaded_info_req req = {0}; + struct cxlmi_cmd_fmapi_get_multiheaded_info_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.num_lds = 4; + rsp.num_heads = 2; + rsp.start_ld_id = 0; + rsp.ld_map_len = 0; + + req.start_ld_id = 0; + req.ld_map_list_limit = 4; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x00, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_multiheaded_info(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_lds, 4, "num_lds mismatch"); + ASSERT_EQ(ret.num_heads, 2, "num_heads mismatch"); + + return 0; +} + +static int test_response_fmapi_get_head_info(void) +{ + struct cxlmi_cmd_fmapi_get_head_info_req req = {0}; + /* + * Wire format: 4-byte header + 9 bytes per head entry. + * Request 2 heads. + */ + uint8_t rsp_buf[4 + 2 * 9] = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_head_info_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_get_head_info_blkfmt)]; + struct cxlmi_cmd_fmapi_get_head_info_rsp *ret = + (struct cxlmi_cmd_fmapi_get_head_info_rsp *)ret_buf; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + req.start_head = 0; + req.num_heads = 2; + + /* Header: num_heads(1) + rsvd(3) */ + rsp_buf[0] = 2; /* num_heads */ + + /* Head 0 at offset 4 */ + rsp_buf[4] = 1; /* port_num */ + rsp_buf[5] = 0x10; /* field_1 (max link width) */ + rsp_buf[6] = 0x08; /* field_2 (negotiated link width) */ + rsp_buf[7] = 0x1F; /* field_3 (supported link speed vector) */ + rsp_buf[8] = 0x04; /* field_4 (max link speed) */ + rsp_buf[9] = 0x03; /* field_5 (current link speed) */ + rsp_buf[10] = 0x10; /* ltssm_state */ + rsp_buf[11] = 0; /* first_negotiated_lane_num */ + rsp_buf[12] = 0x01; /* link_state_flags */ + + /* Head 1 at offset 13 */ + rsp_buf[13] = 2; /* port_num */ + rsp_buf[14] = 0x10; /* field_1 */ + rsp_buf[15] = 0x08; /* field_2 */ + rsp_buf[16] = 0x1F; /* field_3 */ + rsp_buf[17] = 0x04; /* field_4 */ + rsp_buf[18] = 0x03; /* field_5 */ + rsp_buf[19] = 0x10; /* ltssm_state */ + rsp_buf[20] = 0; /* first_negotiated_lane_num */ + rsp_buf[21] = 0x01; /* link_state_flags */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x55, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_head_info(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_heads, 2, "num_heads mismatch"); + ASSERT_EQ(ret->head_info_list[0].port_num, 1, "head[0].port_num mismatch"); + ASSERT_EQ(ret->head_info_list[0].ltssm_state, 0x10, "head[0].ltssm_state mismatch"); + ASSERT_EQ(ret->head_info_list[0].link_state_flags, 0x01, "head[0].link_state_flags mismatch"); + ASSERT_EQ(ret->head_info_list[1].port_num, 2, "head[1].port_num mismatch"); + ASSERT_EQ(ret->head_info_list[1].field_5, 0x03, "head[1].field_5 mismatch"); + + return 0; +} + +/* Helper to write little-endian values to potentially unaligned buffer */ +static void write_le16(uint8_t *p, uint16_t v) +{ + leint16_t le = cpu_to_le16(v); + memcpy(p, &le, sizeof(le)); +} + +static void write_le32(uint8_t *p, uint32_t v) +{ + leint32_t le = cpu_to_le32(v); + memcpy(p, &le, sizeof(le)); +} + +static void write_le64(uint8_t *p, uint64_t v) +{ + leint64_t le = cpu_to_le64(v); + memcpy(p, &le, sizeof(le)); +} + +static int test_response_fmapi_get_dc_reg_config(void) +{ + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp ret = {0}; + /* + * The wire format has the extents/tags fields immediately after the + * returned region_configs, not at a fixed offset. Build a buffer with + * 2 regions followed by the 4 uint32 fields. + * Region config: base(8) + decode_len(8) + region_len(8) + block_size(8) + + * flags(1) + rsvd(3) + sanitize(1) + rsvd2(3) = 40 bytes + */ + uint8_t rsp_buf[4 + 2*40 + 16] = {0}; /* hdr + 2 regions + 4 u32s */ + uint8_t *p = rsp_buf; + int rc; + + /* Header: host_id(2), num_regions(1), regions_returned(1) */ + write_le16(p, 1); p += 2; /* host_id */ + *p++ = 2; /* num_regions */ + *p++ = 2; /* regions_returned */ + + /* Region config 0 */ + write_le64(p, 0x100000000ULL); p += 8; /* base */ + write_le64(p, 0x80000000ULL); p += 8; /* decode_len */ + write_le64(p, 0x80000000ULL); p += 8; /* region_len */ + write_le64(p, 0x40); p += 8; /* block_size */ + *p++ = 0; p += 3; /* flags + rsvd */ + *p++ = 0; p += 3; /* sanitize + rsvd2 */ + + /* Region config 1 */ + write_le64(p, 0x200000000ULL); p += 8; /* base */ + write_le64(p, 0x40000000ULL); p += 8; /* decode_len */ + write_le64(p, 0x40000000ULL); p += 8; /* region_len */ + write_le64(p, 0x80); p += 8; /* block_size */ + *p++ = 0; p += 3; /* flags + rsvd */ + *p++ = 0; p += 3; /* sanitize + rsvd2 */ + + /* Extents/tags fields immediately after 2 regions */ + write_le32(p, 512); p += 4; /* num_extents_supported */ + write_le32(p, 256); p += 4; /* num_extents_available */ + write_le32(p, 128); p += 4; /* num_tags_supported */ + write_le32(p, 64); /* num_tags_available */ + + req.host_id = 1; + req.region_cnt = 8; + req.start_region_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_get_dc_reg_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.host_id, 1, "host_id mismatch"); + ASSERT_EQ(ret.num_regions, 2, "num_regions mismatch"); + ASSERT_EQ(ret.num_extents_supported, 512, "num_extents_supported mismatch"); + ASSERT_EQ(ret.num_extents_available, 256, "num_extents_available mismatch"); + + return 0; +} + +static int test_response_fmapi_get_dc_region_ext_list(void) +{ + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.host_id = cpu_to_le16(1); + rsp.start_ext_index = cpu_to_le32(0); + rsp.extents_returned = cpu_to_le32(0); + rsp.total_extents = cpu_to_le32(10); + rsp.list_generation_num = cpu_to_le32(5); + + req.host_id = 1; + req.extent_count = 8; + req.start_ext_index = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.host_id, 1, "host_id mismatch"); + ASSERT_EQ(ret.total_extents, 10, "total_extents mismatch"); + ASSERT_EQ(ret.list_generation_num, 5, "list_generation_num mismatch"); + + return 0; +} + +static int test_response_fmapi_dc_list_tags(void) +{ + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.generation_num = cpu_to_le32(10); + rsp.total_num_tags = cpu_to_le32(5); + rsp.num_tags_returned = cpu_to_le32(0); + rsp.validity_bitmap = 0x1F; + + req.start_idx = 0; + req.tags_count = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x08, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_dc_list_tags(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.generation_num, 10, "generation_num mismatch"); + ASSERT_EQ(ret.total_num_tags, 5, "total_num_tags mismatch"); + ASSERT_EQ(ret.validity_bitmap, 0x1F, "validity_bitmap mismatch"); + + return 0; +} + +static int test_response_memdev_media_operations_discovery(void) +{ + struct cxlmi_cmd_memdev_media_operations_discovery_req req = {0}; + /* + * Wire format buffer: header (12 bytes) + 2 entries (2 bytes each). + * The flexible array member entry[] starts at offset 12. + */ + uint8_t rsp_buf[12 + 4] = {0}; + uint8_t *p = rsp_buf; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_memdev_media_operations_discovery_rsp) + + 2 * sizeof(struct cxlmi_cmd_memdev_media_ops_supported_list_entry)]; + struct cxlmi_cmd_memdev_media_operations_discovery_rsp *ret = + (struct cxlmi_cmd_memdev_media_operations_discovery_rsp *)ret_buf; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Request 2 entries */ + req.discovery_osa.num_ops = 2; + + /* Header */ + *(leint64_t *)p = cpu_to_le64(0x10000); p += 8; /* dpa_range_granularity */ + *(leint16_t *)p = cpu_to_le16(4); p += 2; /* total_supported_ops */ + *(leint16_t *)p = cpu_to_le16(2); p += 2; /* num_supported_ops */ + + /* Entries immediately after header */ + *p++ = 0x01; /* entry[0].media_op_class */ + *p++ = 0x02; /* entry[0].media_op_subclass */ + *p++ = 0x03; /* entry[1].media_op_class */ + *p++ = 0x04; /* entry[1].media_op_subclass */ + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x44, 0x02, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_memdev_media_operations_discovery(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->dpa_range_granularity, 0x10000, "dpa_range_granularity mismatch"); + ASSERT_EQ(ret->total_supported_ops, 4, "total_supported_ops mismatch"); + ASSERT_EQ(ret->num_supported_ops, 2, "num_supported_ops mismatch"); + ASSERT_EQ(ret->entry[0].media_op_class, 0x01, "entry[0].media_op_class mismatch"); + ASSERT_EQ(ret->entry[1].media_op_subclass, 0x04, "entry[1].media_op_subclass mismatch"); + + return 0; +} + +static int test_response_fmapi_get_domain_validation_sv_state(void) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_state_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.secret_value_state = 0x02; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_domain_validation_sv_state(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_state, 0x02, "secret_value_state mismatch"); + + return 0; +} + +static int test_response_fmapi_get_vcs_domain_validation_sv_state(void) +{ + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_req req = {0}; + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.secret_value_state = 0x01; + req.vcs_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x06, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_state, 0x01, "secret_value_state mismatch"); + + return 0; +} + +static int test_response_fmapi_get_domain_validation_sv(void) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_req req = {0}; + struct cxlmi_cmd_fmapi_get_domain_validation_sv_rsp rsp = {0}, ret = {0}; + int rc; + + memset(rsp.secret_value_uuid, 0xAB, sizeof(rsp.secret_value_uuid)); + req.vcs_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x07, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_get_domain_validation_sv(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.secret_value_uuid[0], 0xAB, "secret_value_uuid[0] mismatch"); + ASSERT_EQ(ret.secret_value_uuid[15], 0xAB, "secret_value_uuid[15] mismatch"); + + return 0; +} + +static int test_response_fmapi_send_ppb_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.return_data = cpu_to_le32(0x12345678); + req.ppb_id = 1; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x51, 0x03, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ppb_cxlio_config_request(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.return_data, 0x12345678, "return_data mismatch"); + + return 0; +} + +static int test_response_fmapi_send_ld_cxlio_config_request(void) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.return_data = cpu_to_le32(0xDEADBEEF); + req.ppb_id = 1; + req.ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x01, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_config_request(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.return_data, 0xDEADBEEF, "return_data mismatch"); + + return 0; +} + +static int test_response_fmapi_send_ld_cxlio_mem_request(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req) + 8]; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp) + 8]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp) + 8]; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req *req = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req *)req_buf; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *rsp = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *ret = + (struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->return_size = cpu_to_le16(8); + memset(rsp->return_data, 0x55, 8); + + req->port_id = 1; + req->ld_id = 0; + req->transaction_len = 8; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x53, 0x02, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->return_size, 8, "return_size mismatch"); + ASSERT_EQ(ret->return_data[0], 0x55, "return_data[0] mismatch"); + + return 0; +} + +static int test_response_fmapi_set_ld_allocations(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_req) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_req *req = + (struct cxlmi_cmd_fmapi_set_ld_allocations_req *)req_buf; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 2; + rsp->start_ld_id = 0; + rsp->ld_allocation_list[0].range_1_allocation_mult = cpu_to_le64(0x100); + + req->number_ld = 2; + req->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x02, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_ld_allocations(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld mismatch"); + ASSERT_EQ(ret->ld_allocation_list[0].range_1_allocation_mult, 0x100, "entries[0].range_1 mismatch"); + + return 0; +} + +static int test_response_fmapi_set_qos_control(void) +{ + struct cxlmi_cmd_fmapi_set_qos_control_req req = {0}; + struct cxlmi_cmd_fmapi_set_qos_control_rsp rsp = {0}, ret = {0}; + int rc; + + rsp.qos_telemetry_control = 0x03; + rsp.egress_moderate_percentage = 45; + rsp.egress_severe_percentage = 75; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x04, CXLMI_RET_SUCCESS, + &rsp, sizeof(rsp)); + rc = cxlmi_cmd_fmapi_set_qos_control(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.qos_telemetry_control, 0x03, "qos_telemetry_control mismatch"); + ASSERT_EQ(ret.egress_moderate_percentage, 45, "egress_moderate_percentage mismatch"); + ASSERT_EQ(ret.egress_severe_percentage, 75, "egress_severe_percentage mismatch"); + + return 0; +} + +static int test_response_fmapi_set_qos_allocated_bw(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req) + 4]; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *req = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *)req_buf; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 4; + rsp->start_ld_id = 0; + rsp->qos_allocation_fraction[0] = 64; + rsp->qos_allocation_fraction[1] = 64; + + req->number_ld = 4; + req->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x07, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_allocated_bw(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 4, "number_ld mismatch"); + ASSERT_EQ(ret->qos_allocation_fraction[0], 64, "fractions[0] mismatch"); + + return 0; +} + +static int test_response_fmapi_set_qos_bw_limit(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_req) + 4]; + uint8_t rsp_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 4]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp) + 4]; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *req = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *)req_buf; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *rsp = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)rsp_buf; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *ret = + (struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *)ret_buf; + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(rsp_buf, 0, sizeof(rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + rsp->number_ld = 4; + rsp->start_ld_id = 0; + rsp->qos_limit_fraction[0] = 128; + rsp->qos_limit_fraction[1] = 255; + + req->number_ld = 4; + req->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x09, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_fmapi_set_qos_bw_limit(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 4, "number_ld mismatch"); + ASSERT_EQ(ret->qos_limit_fraction[0], 128, "limits[0] mismatch"); + ASSERT_EQ(ret->qos_limit_fraction[1], 255, "limits[1] mismatch"); + + return 0; +} + +static int test_response_get_log_cel(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + /* + * Wire format: array of 4-byte entries (opcode le16, command_effect le16). + * Request 2 entries. + */ + uint8_t rsp_buf[8] = {0}; + struct cxlmi_cmd_get_log_cel_rsp ret[2] = {0}; + int rc; + + /* Entry 0: opcode=0x0001 (Identify), command_effect=0x0010 */ + rsp_buf[0] = 0x01; rsp_buf[1] = 0x00; /* opcode LE */ + rsp_buf[2] = 0x10; rsp_buf[3] = 0x00; /* command_effect LE */ + /* Entry 1: opcode=0x0401 (Get Log), command_effect=0x0020 */ + rsp_buf[4] = 0x01; rsp_buf[5] = 0x04; /* opcode LE */ + rsp_buf[6] = 0x20; rsp_buf[7] = 0x00; /* command_effect LE */ + + req.length = sizeof(rsp_buf); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret[0].opcode, 0x0001, "entry[0].opcode mismatch"); + ASSERT_EQ(ret[0].command_effect, 0x0010, "entry[0].command_effect mismatch"); + ASSERT_EQ(ret[1].opcode, 0x0401, "entry[1].opcode mismatch"); + ASSERT_EQ(ret[1].command_effect, 0x0020, "entry[1].command_effect mismatch"); + + return 0; +} + +/* ============================================================ + * Endianness Verification Tests + * + * These tests use byte patterns where byte order matters (high byte != low byte). + * Values like 0xAABB will become 0xBBAA if endianness is wrong. + * ============================================================ */ + +/* + * Test 16-bit response field endianness using identify command. + * Wire format uses little-endian; library converts to host. + */ +static int test_endian_identify_16bit(void) +{ + /* + * Build wire-format response with explicit LE byte order. + * vendor_id: 0xBEEF -> wire bytes: EF BE + * device_id: 0xCAFE -> wire bytes: FE CA + * subsys_vendor_id: 0xDEAD -> wire bytes: AD DE + * subsys_id: 0xF00D -> wire bytes: 0D F0 + */ + uint8_t wire_rsp[18] = { + 0xEF, 0xBE, /* vendor_id LE */ + 0xFE, 0xCA, /* device_id LE */ + 0xAD, 0xDE, /* subsys_vendor_id LE */ + 0x0D, 0xF0, /* subsys_id LE */ + 0x78, 0x56, 0x34, 0x12, /* serial_num LE (low 32) */ + 0xF0, 0xDE, 0xBC, 0x9A, /* serial_num LE (high 32) */ + 0x09, /* max_msg_size */ + 0x03, /* component_type */ + }; + struct cxlmi_cmd_identify_rsp ret = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.vendor_id, 0xBEEF, "vendor_id endianness wrong"); + ASSERT_EQ(ret.device_id, 0xCAFE, "device_id endianness wrong"); + ASSERT_EQ(ret.subsys_vendor_id, 0xDEAD, "subsys_vendor_id endianness wrong"); + ASSERT_EQ(ret.subsys_id, 0xF00D, "subsys_id endianness wrong"); + + return 0; +} + +/* + * Test 64-bit response field endianness using identify command. + * serial_num should be 0x9ABCDEF012345678 (host order). + */ +static int test_endian_identify_64bit(void) +{ + uint8_t wire_rsp[18] = { + 0x00, 0x00, /* vendor_id */ + 0x00, 0x00, /* device_id */ + 0x00, 0x00, /* subsys_vendor_id */ + 0x00, 0x00, /* subsys_id */ + /* serial_num: 0x9ABCDEF012345678 in LE wire format */ + 0x78, 0x56, 0x34, 0x12, /* low 32 bits */ + 0xF0, 0xDE, 0xBC, 0x9A, /* high 32 bits */ + 0x00, /* max_msg_size */ + 0x00, /* component_type */ + }; + struct cxlmi_cmd_identify_rsp ret = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x01, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.serial_num == 0x9ABCDEF012345678ULL, + "serial_num endianness wrong"); + + return 0; +} + +/* + * Test 64-bit request field endianness using set_timestamp command. + * Verify the wire format has correct LE byte order. + */ +static int test_endian_set_timestamp_request(void) +{ + struct cxlmi_cmd_set_timestamp_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* Host value: 0xFEDCBA9876543210 */ + req.timestamp = 0xFEDCBA9876543210ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_set_timestamp(test_ep, NULL, &req); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(payload_size, 8, "payload size wrong"); + /* Verify LE wire format: LSB first */ + ASSERT_EQ(payload[0], 0x10, "timestamp byte 0 wrong"); + ASSERT_EQ(payload[1], 0x32, "timestamp byte 1 wrong"); + ASSERT_EQ(payload[2], 0x54, "timestamp byte 2 wrong"); + ASSERT_EQ(payload[3], 0x76, "timestamp byte 3 wrong"); + ASSERT_EQ(payload[4], 0x98, "timestamp byte 4 wrong"); + ASSERT_EQ(payload[5], 0xBA, "timestamp byte 5 wrong"); + ASSERT_EQ(payload[6], 0xDC, "timestamp byte 6 wrong"); + ASSERT_EQ(payload[7], 0xFE, "timestamp byte 7 wrong"); + + return 0; +} + +/* + * Test 64-bit response field endianness using get_timestamp command. + */ +static int test_endian_get_timestamp_response(void) +{ + /* Wire format: 0x0102030405060708 in LE */ + uint8_t wire_rsp[8] = { + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 + }; + struct cxlmi_cmd_get_timestamp_rsp ret = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x03, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_timestamp(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret.timestamp == 0x0102030405060708ULL, + "timestamp endianness wrong"); + + return 0; +} + +/* + * Test 32-bit request field endianness using get_log command. + */ +static int test_endian_get_log_request(void) +{ + struct cxlmi_cmd_get_log_req req = {0}; + uint8_t rsp_buf[32] = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* + * Use values with distinct bytes to detect swapping. + * Keep length small (32) since library allocates that many bytes. + * offset: 0xAABBCCDD (verifies 32-bit LE encoding) + * length: 32 (0x00000020) - small but functional + */ + req.offset = 0xAABBCCDD; + req.length = 32; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + rsp_buf, sizeof(rsp_buf)); + rc = cxlmi_cmd_get_log(test_ep, NULL, &req, rsp_buf); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + /* Request payload: uuid[16] + offset[4] + length[4] */ + ASSERT_TRUE(payload_size >= 24, "payload too small"); + /* offset at byte 16, LE format: 0xAABBCCDD -> DD CC BB AA */ + ASSERT_EQ(payload[16], 0xDD, "offset byte 0 wrong"); + ASSERT_EQ(payload[17], 0xCC, "offset byte 1 wrong"); + ASSERT_EQ(payload[18], 0xBB, "offset byte 2 wrong"); + ASSERT_EQ(payload[19], 0xAA, "offset byte 3 wrong"); + /* length at byte 20, LE format: 32 = 0x00000020 -> 20 00 00 00 */ + ASSERT_EQ(payload[20], 0x20, "length byte 0 wrong"); + ASSERT_EQ(payload[21], 0x00, "length byte 1 wrong"); + ASSERT_EQ(payload[22], 0x00, "length byte 2 wrong"); + ASSERT_EQ(payload[23], 0x00, "length byte 3 wrong"); + + return 0; +} + +/* + * Test memdev_identify 64-bit capacity fields. + * Library does le64_to_cpu conversion (no multiplier applied). + */ +static int test_endian_memdev_identify_capacity(void) +{ + /* + * Build wire response for memdev_identify. + * Struct layout: + * char fw_revision[16] - offset 0 + * uint64_t total_capacity - offset 16 + * uint64_t volatile_capacity - offset 24 + */ + uint8_t wire_rsp[80] = {0}; + struct cxlmi_cmd_memdev_identify_rsp ret = {0}; + int rc; + + /* total_capacity at offset 16: 0x123456789ABCDEF0 in LE */ + wire_rsp[16] = 0xF0; wire_rsp[17] = 0xDE; + wire_rsp[18] = 0xBC; wire_rsp[19] = 0x9A; + wire_rsp[20] = 0x78; wire_rsp[21] = 0x56; + wire_rsp[22] = 0x34; wire_rsp[23] = 0x12; + + /* volatile_capacity at offset 24: 0xFEDCBA9876543210 in LE */ + wire_rsp[24] = 0x10; wire_rsp[25] = 0x32; + wire_rsp[26] = 0x54; wire_rsp[27] = 0x76; + wire_rsp[28] = 0x98; wire_rsp[29] = 0xBA; + wire_rsp[30] = 0xDC; wire_rsp[31] = 0xFE; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x40, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_identify(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Library does le64_to_cpu, no multiplier */ + ASSERT_TRUE(ret.total_capacity == 0x123456789ABCDEF0ULL, + "total_capacity endianness/multiplier wrong"); + ASSERT_TRUE(ret.volatile_capacity == 0xFEDCBA9876543210ULL, + "volatile_capacity endianness/multiplier wrong"); + + return 0; +} + +/* + * Test bg_op_status 16-bit fields. + */ +static int test_endian_bg_op_status(void) +{ + /* Wire format for bg_op_status response */ + uint8_t wire_rsp[8] = { + 0x01, /* status */ + 0x00, /* rsvd */ + 0xCD, 0xAB, /* opcode: 0xABCD in LE */ + 0x34, 0x12, /* returncode: 0x1234 in LE */ + 0x78, 0x56, /* vendor_ext_status: 0x5678 in LE */ + }; + struct cxlmi_cmd_bg_op_status_rsp ret = {0}; + int rc; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x00, 0x02, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_bg_op_status(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.status, 0x01, "status mismatch"); + ASSERT_EQ(ret.opcode, 0xABCD, "opcode endianness wrong"); + ASSERT_EQ(ret.returncode, 0x1234, "returncode endianness wrong"); + ASSERT_EQ(ret.vendor_ext_status, 0x5678, "vendor_ext_status endianness wrong"); + + return 0; +} + +/* + * Test event_records response with overflow timestamps (64-bit). + */ +static int test_endian_event_records_timestamps(void) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + /* + * Wire format: flags, rsvd, overflow_err_count, timestamps, record_count + * Minimal header without records. + */ + uint8_t wire_rsp[32] = {0}; + struct cxlmi_cmd_get_event_records_rsp ret = {0}; + int rc; + + /* overflow_err_count at offset 2: 0xBEEF in LE */ + wire_rsp[2] = 0xEF; wire_rsp[3] = 0xBE; + + /* first_overflow_timestamp at offset 4: 0x0102030405060708 in LE */ + wire_rsp[4] = 0x08; wire_rsp[5] = 0x07; + wire_rsp[6] = 0x06; wire_rsp[7] = 0x05; + wire_rsp[8] = 0x04; wire_rsp[9] = 0x03; + wire_rsp[10] = 0x02; wire_rsp[11] = 0x01; + + /* last_overflow_timestamp at offset 12: 0xFEDCBA9876543210 in LE */ + wire_rsp[12] = 0x10; wire_rsp[13] = 0x32; + wire_rsp[14] = 0x54; wire_rsp[15] = 0x76; + wire_rsp[16] = 0x98; wire_rsp[17] = 0xBA; + wire_rsp[18] = 0xDC; wire_rsp[19] = 0xFE; + + /* record_count at offset 20: 0 (no records) */ + wire_rsp[20] = 0x00; wire_rsp[21] = 0x00; + + req.event_log = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.overflow_err_count, 0xBEEF, "overflow_err_count endianness wrong"); + ASSERT_TRUE(ret.first_overflow_timestamp == 0x0102030405060708ULL, + "first_overflow_timestamp endianness wrong"); + ASSERT_TRUE(ret.last_overflow_timestamp == 0xFEDCBA9876543210ULL, + "last_overflow_timestamp endianness wrong"); + + return 0; +} + +/* + * Test inject_poison request (64-bit address field). + */ +static int test_endian_inject_poison_request(void) +{ + struct cxlmi_cmd_memdev_inject_poison_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* Physical address with distinct bytes */ + req.inject_poison_phy_addr = 0xFEDCBA9876543210ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x01, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_inject_poison(test_ep, NULL, &req); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + ASSERT_EQ(payload_size, 8, "payload size wrong"); + /* Verify LE wire format */ + ASSERT_EQ(payload[0], 0x10, "addr byte 0 wrong"); + ASSERT_EQ(payload[1], 0x32, "addr byte 1 wrong"); + ASSERT_EQ(payload[2], 0x54, "addr byte 2 wrong"); + ASSERT_EQ(payload[3], 0x76, "addr byte 3 wrong"); + ASSERT_EQ(payload[4], 0x98, "addr byte 4 wrong"); + ASSERT_EQ(payload[5], 0xBA, "addr byte 5 wrong"); + ASSERT_EQ(payload[6], 0xDC, "addr byte 6 wrong"); + ASSERT_EQ(payload[7], 0xFE, "addr byte 7 wrong"); + + return 0; +} + +/* + * Test get_supported_logs response (32-bit log_size fields). + */ +static int test_endian_supported_logs_size(void) +{ + /* + * Wire format: num_supported_log_entries (le16), rsvd[6], entries[] + * Each entry: uuid[16], log_size (le32) + */ + uint8_t wire_rsp[8 + 20] = {0}; /* header + 1 entry */ + struct cxlmi_cmd_get_supported_logs_rsp *ret; + size_t ret_sz; + int rc; + + /* num_supported_log_entries: 1 */ + wire_rsp[0] = 0x01; wire_rsp[1] = 0x00; + /* entry 0: uuid (all zeros) + log_size: 0xAABBCCDD in LE */ + wire_rsp[8 + 16] = 0xDD; + wire_rsp[8 + 17] = 0xCC; + wire_rsp[8 + 18] = 0xBB; + wire_rsp[8 + 19] = 0xAA; + + ret_sz = sizeof(*ret) + sizeof(ret->entries[0]); + ret = calloc(1, ret_sz); + ASSERT_TRUE(ret != NULL, "alloc failed"); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_log_entries, 1, "num entries wrong"); + ASSERT_EQ(ret->entries[0].log_size, 0xAABBCCDD, "log_size endianness wrong"); + + free(ret); + return 0; +} + +/* + * Test round-trip: host -> wire -> host for 16/32/64 bit values. + * Uses set_feature request and verifies wire format. + */ +static int test_endian_set_feature_request(void) +{ + struct cxlmi_cmd_set_feature_req req = {0}; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + /* set_feature_flags: 0xAABBCCDD (32-bit) */ + req.set_feature_flags = 0xAABBCCDD; + /* offset: 0x1234 (16-bit) */ + req.offset = 0x1234; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + /* set_feature takes feature_data_sz as 4th param */ + rc = cxlmi_cmd_set_feature(test_ep, NULL, &req, 0); + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + cxlmi_mock_get_last_command(test_ep, NULL, NULL, payload, &payload_size); + teardown(); + + /* + * set_feature request layout: + * uuid[16] + set_feature_flags[4] + offset[2] + ... + */ + ASSERT_TRUE(payload_size >= 22, "payload too small"); + + /* set_feature_flags at offset 16, LE format */ + ASSERT_EQ(payload[16], 0xDD, "flags byte 0 wrong"); + ASSERT_EQ(payload[17], 0xCC, "flags byte 1 wrong"); + ASSERT_EQ(payload[18], 0xBB, "flags byte 2 wrong"); + ASSERT_EQ(payload[19], 0xAA, "flags byte 3 wrong"); + + /* offset at offset 20, LE format */ + ASSERT_EQ(payload[20], 0x34, "offset byte 0 wrong"); + ASSERT_EQ(payload[21], 0x12, "offset byte 1 wrong"); + + return 0; +} + +/* + * Test get_partition_info response (64-bit fields). + */ +static int test_endian_partition_info(void) +{ + /* + * Wire format: active_vmem[8], active_pmem[8], next_vmem[8], next_pmem[8] + * Library does le64_to_cpu conversion (no multiplier applied). + */ + uint8_t wire_rsp[32] = {0}; + struct cxlmi_cmd_memdev_get_partition_info_rsp ret = {0}; + int rc; + + /* active_vmem: 0x123456789ABCDEF0 in LE */ + wire_rsp[0] = 0xF0; wire_rsp[1] = 0xDE; + wire_rsp[2] = 0xBC; wire_rsp[3] = 0x9A; + wire_rsp[4] = 0x78; wire_rsp[5] = 0x56; + wire_rsp[6] = 0x34; wire_rsp[7] = 0x12; + + /* active_pmem: 0xFEDCBA9876543210 in LE */ + wire_rsp[8] = 0x10; wire_rsp[9] = 0x32; + wire_rsp[10] = 0x54; wire_rsp[11] = 0x76; + wire_rsp[12] = 0x98; wire_rsp[13] = 0xBA; + wire_rsp[14] = 0xDC; wire_rsp[15] = 0xFE; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x41, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_partition_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + /* Library does le64_to_cpu, no multiplier */ + ASSERT_TRUE(ret.active_vmem == 0x123456789ABCDEF0ULL, + "active_vmem endianness/multiplier wrong"); + ASSERT_TRUE(ret.active_pmem == 0xFEDCBA9876543210ULL, + "active_pmem endianness/multiplier wrong"); + + return 0; +} + +/* + * Test health_info response with mixed field sizes. + */ +static int test_endian_health_info(void) +{ + /* + * health_info has various field sizes: + * - health_status: 1 byte + * - media_status: 1 byte + * - additional_status: 1 byte + * - life_used: 1 byte + * - device_temperature: 2 bytes (offset 4) + * - dirty_shutdown_count: 4 bytes (offset 6) + * - corrected_volatile_error_count: 4 bytes (offset 10) + * - corrected_persistent_error_count: 4 bytes (offset 14) + */ + uint8_t wire_rsp[18] = {0}; + struct cxlmi_cmd_memdev_get_health_info_rsp ret = {0}; + int rc; + + wire_rsp[0] = 0x01; /* health_status */ + wire_rsp[1] = 0x02; /* media_status */ + wire_rsp[2] = 0x03; /* additional_status */ + wire_rsp[3] = 0x50; /* life_used */ + + /* device_temperature: 0xBEEF in LE */ + wire_rsp[4] = 0xEF; wire_rsp[5] = 0xBE; + + /* dirty_shutdown_count: 0xCAFEBABE in LE */ + wire_rsp[6] = 0xBE; wire_rsp[7] = 0xBA; + wire_rsp[8] = 0xFE; wire_rsp[9] = 0xCA; + + /* corrected_volatile_error_count: 0xDEADBEEF in LE */ + wire_rsp[10] = 0xEF; wire_rsp[11] = 0xBE; + wire_rsp[12] = 0xAD; wire_rsp[13] = 0xDE; + + /* corrected_persistent_error_count: 0x12345678 in LE */ + wire_rsp[14] = 0x78; wire_rsp[15] = 0x56; + wire_rsp[16] = 0x34; wire_rsp[17] = 0x12; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_health_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.health_status, 0x01, "health_status wrong"); + ASSERT_EQ(ret.device_temperature, 0xBEEF, "device_temperature endianness wrong"); + ASSERT_EQ(ret.dirty_shutdown_count, 0xCAFEBABE, "dirty_shutdown_count endianness wrong"); + ASSERT_EQ(ret.corrected_volatile_error_count, 0xDEADBEEF, "corrected_volatile_error_count endianness wrong"); + ASSERT_EQ(ret.corrected_persistent_error_count, 0x12345678, "corrected_persistent_error_count endianness wrong"); + + return 0; +} + +/* + * Test get_alert_config response (16-bit temperature thresholds). + * Struct layout: + * uint8_t valid_alerts - offset 0 + * uint8_t programmable_alerts - offset 1 + * uint8_t life_used_critical_alert_threshold - offset 2 + * uint8_t life_used_programmable_warning_threshold - offset 3 + * uint16_t device_over_temperature_critical_alert_threshold - offset 4 + * uint16_t device_under_temperature_critical_alert_threshold - offset 6 + * uint16_t device_over_temperature_programmable_warning_threshold - offset 8 + * uint16_t device_under_temperature_programmable_warning_threshold - offset 10 + * uint16_t corrected_volatile_mem_error_programmable_warning_threshold - offset 12 + * uint16_t corrected_persistent_mem_error_programmable_warning_threshold - offset 14 + */ +static int test_endian_get_alert_config(void) +{ + uint8_t wire_rsp[16] = {0}; + struct cxlmi_cmd_memdev_get_alert_config_rsp ret = {0}; + int rc; + + wire_rsp[0] = 0xFF; /* valid_alerts */ + wire_rsp[1] = 0x0F; /* programmable_alerts */ + wire_rsp[2] = 0x50; /* life_used_critical_alert_threshold */ + wire_rsp[3] = 0x40; /* life_used_programmable_warning_threshold */ + + /* device_over_temperature_critical_alert_threshold: 0xBEEF in LE */ + wire_rsp[4] = 0xEF; wire_rsp[5] = 0xBE; + + /* device_under_temperature_critical_alert_threshold: 0xCAFE in LE */ + wire_rsp[6] = 0xFE; wire_rsp[7] = 0xCA; + + /* device_over_temperature_programmable_warning_threshold: 0xDEAD in LE */ + wire_rsp[8] = 0xAD; wire_rsp[9] = 0xDE; + + /* device_under_temperature_programmable_warning_threshold: 0xF00D in LE */ + wire_rsp[10] = 0x0D; wire_rsp[11] = 0xF0; + + /* corrected_volatile_mem_error_programmable_warning_threshold: 0x1234 in LE */ + wire_rsp[12] = 0x34; wire_rsp[13] = 0x12; + + /* corrected_persistent_mem_error_programmable_warning_threshold: 0x5678 in LE */ + wire_rsp[14] = 0x78; wire_rsp[15] = 0x56; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x01, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_alert_config(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.valid_alerts, 0xFF, "valid_alerts wrong"); + ASSERT_EQ(ret.programmable_alerts, 0x0F, "programmable_alerts wrong"); + ASSERT_EQ(ret.device_over_temperature_critical_alert_threshold, 0xBEEF, + "device_over_temperature_critical endianness wrong"); + ASSERT_EQ(ret.device_under_temperature_critical_alert_threshold, 0xCAFE, + "device_under_temperature_critical endianness wrong"); + ASSERT_EQ(ret.device_over_temperature_programmable_warning_threshold, 0xDEAD, + "device_over_temperature_programmable endianness wrong"); + ASSERT_EQ(ret.device_under_temperature_programmable_warning_threshold, 0xF00D, + "device_under_temperature_programmable endianness wrong"); + ASSERT_EQ(ret.corrected_volatile_mem_error_programmable_warning_threshold, 0x1234, + "corrected_volatile_mem_error endianness wrong"); + ASSERT_EQ(ret.corrected_persistent_mem_error_programmable_warning_threshold, 0x5678, + "corrected_persistent_mem_error endianness wrong"); + + return 0; +} + +/* + * Test set_alert_config request encoding (16-bit temperature thresholds). + */ +static int test_endian_set_alert_config_request(void) +{ + struct cxlmi_cmd_memdev_set_alert_config_req req = {0}; + uint8_t cmd_payload[256]; + size_t cmd_payload_size = sizeof(cmd_payload); + uint8_t cmd_set, cmd; + int rc; + + req.valid_alert_actions = 0xFF; + req.enable_alert_actions = 0x0F; + req.life_used_programmable_warning_threshold = 0x40; + req.device_over_temperature_programmable_warning_threshold = 0xBEEF; + req.device_under_temperature_programmable_warning_threshold = 0xCAFE; + req.corrected_volatile_mem_error_programmable_warning_threshold = 0xDEAD; + req.corrected_persistent_mem_error_programmable_warning_threshold = 0xF00D; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x42, 0x02, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_memdev_set_alert_config(test_ep, NULL, &req); + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, cmd_payload, &cmd_payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x42, "wrong command set"); + ASSERT_EQ(cmd, 0x02, "wrong command"); + + /* + * Wire layout: + * valid_alert_actions: 1 byte (offset 0) + * enable_alert_actions: 1 byte (offset 1) + * life_used_programmable_warning_threshold: 1 byte (offset 2) + * rsvd1: 1 byte (offset 3) + * device_over_temperature_programmable_warning_threshold: 2 bytes LE (offset 4) + * device_under_temperature_programmable_warning_threshold: 2 bytes LE (offset 6) + * corrected_volatile_mem_error_programmable_warning_threshold: 2 bytes LE (offset 8) + * corrected_persistent_mem_error_programmable_warning_threshold: 2 bytes LE (offset 10) + */ + ASSERT_EQ(cmd_payload[0], 0xFF, "valid_alert_actions wrong"); + ASSERT_EQ(cmd_payload[1], 0x0F, "enable_alert_actions wrong"); + ASSERT_EQ(cmd_payload[2], 0x40, "life_used threshold wrong"); + + /* device_over_temperature: 0xBEEF -> EF BE */ + ASSERT_EQ(cmd_payload[4], 0xEF, "device_over_temp low byte wrong"); + ASSERT_EQ(cmd_payload[5], 0xBE, "device_over_temp high byte wrong"); + + /* device_under_temperature: 0xCAFE -> FE CA */ + ASSERT_EQ(cmd_payload[6], 0xFE, "device_under_temp low byte wrong"); + ASSERT_EQ(cmd_payload[7], 0xCA, "device_under_temp high byte wrong"); + + /* corrected_volatile: 0xDEAD -> AD DE */ + ASSERT_EQ(cmd_payload[8], 0xAD, "corrected_volatile low byte wrong"); + ASSERT_EQ(cmd_payload[9], 0xDE, "corrected_volatile high byte wrong"); + + /* corrected_persistent: 0xF00D -> 0D F0 */ + ASSERT_EQ(cmd_payload[10], 0x0D, "corrected_persistent low byte wrong"); + ASSERT_EQ(cmd_payload[11], 0xF0, "corrected_persistent high byte wrong"); + + return 0; +} + +/* + * Test fmapi_get_dcd_info response (16-bit policies, 64-bit capacity and masks). + * Struct layout: + * uint8_t num_hosts - offset 0 + * uint8_t num_supported_dc_regions - offset 1 + * uint8_t rsvd1[2] - offset 2 + * uint16_t capacity_selection_policies - offset 4 + * uint8_t rsvd2[2] - offset 6 + * uint16_t capacity_removal_policies - offset 8 + * uint8_t sanitize_on_release_config_mask - offset 10 + * uint8_t rsvd3 - offset 11 + * uint64_t total_dynamic_capacity - offset 12 (multiplied by 256MB) + * uint64_t region_0_supported_blk_sz_mask - offset 20 + * ... (regions 1-7 follow) + */ +static int test_endian_fmapi_get_dcd_info(void) +{ + uint8_t wire_rsp[84] = {0}; + struct cxlmi_cmd_fmapi_get_dcd_info_rsp ret = {0}; + int rc; + + wire_rsp[0] = 0x04; /* num_hosts */ + wire_rsp[1] = 0x02; /* num_supported_dc_regions */ + + /* capacity_selection_policies: 0xBEEF in LE at offset 4 */ + wire_rsp[4] = 0xEF; wire_rsp[5] = 0xBE; + + /* capacity_removal_policies: 0xCAFE in LE at offset 8 */ + wire_rsp[8] = 0xFE; wire_rsp[9] = 0xCA; + + wire_rsp[10] = 0x0F; /* sanitize_on_release_config_mask */ + + /* + * total_dynamic_capacity: 0x12345678 in LE at offset 12 + * Library multiplies by CXL_CAPACITY_MULTIPLIER (256MB = 0x10000000) + */ + wire_rsp[12] = 0x78; wire_rsp[13] = 0x56; + wire_rsp[14] = 0x34; wire_rsp[15] = 0x12; + wire_rsp[16] = 0x00; wire_rsp[17] = 0x00; + wire_rsp[18] = 0x00; wire_rsp[19] = 0x00; + + /* region_0_supported_blk_sz_mask: 0xFEDCBA9876543210 in LE at offset 20 */ + wire_rsp[20] = 0x10; wire_rsp[21] = 0x32; + wire_rsp[22] = 0x54; wire_rsp[23] = 0x76; + wire_rsp[24] = 0x98; wire_rsp[25] = 0xBA; + wire_rsp[26] = 0xDC; wire_rsp[27] = 0xFE; + + /* region_1_supported_blk_sz_mask: 0x123456789ABCDEF0 in LE at offset 28 */ + wire_rsp[28] = 0xF0; wire_rsp[29] = 0xDE; + wire_rsp[30] = 0xBC; wire_rsp[31] = 0x9A; + wire_rsp[32] = 0x78; wire_rsp[33] = 0x56; + wire_rsp[34] = 0x34; wire_rsp[35] = 0x12; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_fmapi_get_dcd_info(test_ep, NULL, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_hosts, 0x04, "num_hosts wrong"); + ASSERT_EQ(ret.num_supported_dc_regions, 0x02, "num_supported_dc_regions wrong"); + ASSERT_EQ(ret.capacity_selection_policies, 0xBEEF, + "capacity_selection_policies endianness wrong"); + ASSERT_EQ(ret.capacity_removal_policies, 0xCAFE, + "capacity_removal_policies endianness wrong"); + ASSERT_EQ(ret.sanitize_on_release_config_mask, 0x0F, + "sanitize_on_release_config_mask wrong"); + + /* total_dynamic_capacity: 0x12345678 * 256MB = 0x1234567800000000 */ + ASSERT_TRUE(ret.total_dynamic_capacity == (0x12345678ULL * 256ULL * 1024ULL * 1024ULL), + "total_dynamic_capacity endianness/multiplier wrong"); + + ASSERT_TRUE(ret.region_0_supported_blk_sz_mask == 0xFEDCBA9876543210ULL, + "region_0_supported_blk_sz_mask endianness wrong"); + ASSERT_TRUE(ret.region_1_supported_blk_sz_mask == 0x123456789ABCDEF0ULL, + "region_1_supported_blk_sz_mask endianness wrong"); + + return 0; +} + +/* + * Test get_poison_list request encoding (64-bit addresses). + */ +static int test_endian_get_poison_list_request(void) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp ret = {0}; + uint8_t cmd_payload[256]; + size_t cmd_payload_size = sizeof(cmd_payload); + uint8_t cmd_set, cmd; + int rc; + + req.get_poison_list_phy_addr = 0x123456789ABCDEF0ULL; + req.get_poison_list_phy_addr_len = 0xFEDCBA9876543210ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* Provide minimal response */ + uint8_t wire_rsp[32] = {0}; + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, &ret); + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, cmd_payload, &cmd_payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x43, "wrong command set"); + ASSERT_EQ(cmd, 0x00, "wrong command"); + + /* get_poison_list_phy_addr: 0x123456789ABCDEF0 -> F0 DE BC 9A 78 56 34 12 */ + ASSERT_EQ(cmd_payload[0], 0xF0, "phy_addr byte 0 wrong"); + ASSERT_EQ(cmd_payload[1], 0xDE, "phy_addr byte 1 wrong"); + ASSERT_EQ(cmd_payload[2], 0xBC, "phy_addr byte 2 wrong"); + ASSERT_EQ(cmd_payload[3], 0x9A, "phy_addr byte 3 wrong"); + ASSERT_EQ(cmd_payload[4], 0x78, "phy_addr byte 4 wrong"); + ASSERT_EQ(cmd_payload[5], 0x56, "phy_addr byte 5 wrong"); + ASSERT_EQ(cmd_payload[6], 0x34, "phy_addr byte 6 wrong"); + ASSERT_EQ(cmd_payload[7], 0x12, "phy_addr byte 7 wrong"); + + /* get_poison_list_phy_addr_len: 0xFEDCBA9876543210 -> 10 32 54 76 98 BA DC FE */ + ASSERT_EQ(cmd_payload[8], 0x10, "phy_addr_len byte 0 wrong"); + ASSERT_EQ(cmd_payload[9], 0x32, "phy_addr_len byte 1 wrong"); + ASSERT_EQ(cmd_payload[10], 0x54, "phy_addr_len byte 2 wrong"); + ASSERT_EQ(cmd_payload[11], 0x76, "phy_addr_len byte 3 wrong"); + ASSERT_EQ(cmd_payload[12], 0x98, "phy_addr_len byte 4 wrong"); + ASSERT_EQ(cmd_payload[13], 0xBA, "phy_addr_len byte 5 wrong"); + ASSERT_EQ(cmd_payload[14], 0xDC, "phy_addr_len byte 6 wrong"); + ASSERT_EQ(cmd_payload[15], 0xFE, "phy_addr_len byte 7 wrong"); + + return 0; +} + +/* + * Test get_poison_list response (64-bit timestamp, 16-bit count, record fields). + * Uses compound struct with explicit LE encoding to test endianness conversion. + */ +static int test_endian_get_poison_list_response(void) +{ + uint8_t wire_rsp_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_memdev_get_poison_list_rsp) + + 2 * sizeof(struct cxlmi_memdev_media_err_record)]; + struct cxlmi_cmd_memdev_get_poison_list_rsp *wire_rsp = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)wire_rsp_buf; + struct cxlmi_cmd_memdev_get_poison_list_rsp *ret = + (struct cxlmi_cmd_memdev_get_poison_list_rsp *)ret_buf; + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + int rc; + + memset(wire_rsp_buf, 0, sizeof(wire_rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Encode wire data in LE - library should decode correctly */ + wire_rsp->poison_list_flags = 0x03; + wire_rsp->overflow_timestamp = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp->more_err_media_record_cnt = cpu_to_le16(2); + + /* Records with distinct byte patterns */ + wire_rsp->records[0].media_err_addr = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp->records[0].media_err_len = cpu_to_le32(0xCAFEBABE); + wire_rsp->records[1].media_err_addr = cpu_to_le64(0x1122334455667788ULL); + wire_rsp->records[1].media_err_len = cpu_to_le32(0xDEADBEEF); + + req.get_poison_list_phy_addr = 0x1000; + req.get_poison_list_phy_addr_len = 0x10000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x00, CXLMI_RET_SUCCESS, + wire_rsp_buf, sizeof(wire_rsp_buf)); + rc = cxlmi_cmd_get_poison_list(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->poison_list_flags, 0x03, "poison_list_flags wrong"); + ASSERT_TRUE(ret->overflow_timestamp == 0x123456789ABCDEF0ULL, + "overflow_timestamp endianness wrong"); + ASSERT_EQ(ret->more_err_media_record_cnt, 2, "more_err_media_record_cnt wrong"); + + ASSERT_TRUE(ret->records[0].media_err_addr == 0xFEDCBA9876543210ULL, + "record[0].media_err_addr endianness wrong"); + ASSERT_EQ(ret->records[0].media_err_len, 0xCAFEBABE, + "record[0].media_err_len endianness wrong"); + + ASSERT_TRUE(ret->records[1].media_err_addr == 0x1122334455667788ULL, + "record[1].media_err_addr endianness wrong"); + ASSERT_EQ(ret->records[1].media_err_len, 0xDEADBEEF, + "record[1].media_err_len endianness wrong"); + + return 0; +} + +/* + * Test scan_media request encoding (64-bit addresses). + * Note: scan_media_flags is 8-bit in struct but library writes 16-bit LE. + */ +static int test_endian_scan_media_request(void) +{ + struct cxlmi_cmd_scan_media_req req = {0}; + uint8_t cmd_payload[256]; + size_t cmd_payload_size = sizeof(cmd_payload); + uint8_t cmd_set, cmd; + int rc; + + req.scan_media_physaddr = 0x123456789ABCDEF0ULL; + req.scan_media_physaddr_length = 0xFEDCBA9876543210ULL; + req.scan_media_flags = 0x03; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x04, CXLMI_RET_SUCCESS, NULL, 0); + rc = cxlmi_cmd_scan_media(test_ep, NULL, &req); + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, cmd_payload, &cmd_payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x43, "wrong command set"); + ASSERT_EQ(cmd, 0x04, "wrong command"); + + /* scan_media_physaddr: 0x123456789ABCDEF0 -> F0 DE BC 9A 78 56 34 12 */ + ASSERT_EQ(cmd_payload[0], 0xF0, "physaddr byte 0 wrong"); + ASSERT_EQ(cmd_payload[1], 0xDE, "physaddr byte 1 wrong"); + ASSERT_EQ(cmd_payload[2], 0xBC, "physaddr byte 2 wrong"); + ASSERT_EQ(cmd_payload[3], 0x9A, "physaddr byte 3 wrong"); + ASSERT_EQ(cmd_payload[4], 0x78, "physaddr byte 4 wrong"); + ASSERT_EQ(cmd_payload[5], 0x56, "physaddr byte 5 wrong"); + ASSERT_EQ(cmd_payload[6], 0x34, "physaddr byte 6 wrong"); + ASSERT_EQ(cmd_payload[7], 0x12, "physaddr byte 7 wrong"); + + /* scan_media_physaddr_length: 0xFEDCBA9876543210 -> 10 32 54 76 98 BA DC FE */ + ASSERT_EQ(cmd_payload[8], 0x10, "physaddr_length byte 0 wrong"); + ASSERT_EQ(cmd_payload[9], 0x32, "physaddr_length byte 1 wrong"); + ASSERT_EQ(cmd_payload[10], 0x54, "physaddr_length byte 2 wrong"); + ASSERT_EQ(cmd_payload[11], 0x76, "physaddr_length byte 3 wrong"); + ASSERT_EQ(cmd_payload[12], 0x98, "physaddr_length byte 4 wrong"); + ASSERT_EQ(cmd_payload[13], 0xBA, "physaddr_length byte 5 wrong"); + ASSERT_EQ(cmd_payload[14], 0xDC, "physaddr_length byte 6 wrong"); + ASSERT_EQ(cmd_payload[15], 0xFE, "physaddr_length byte 7 wrong"); + + /* scan_media_flags: low byte should be 0x03 (library writes 16-bit LE) */ + ASSERT_EQ(cmd_payload[16], 0x03, "flags low byte wrong"); + + return 0; +} + +/* + * Test get_scan_media_results response (64-bit addresses, 16-bit count, records). + * Uses compound struct with explicit LE encoding to test endianness conversion. + */ +static int test_endian_get_scan_media_results(void) +{ + uint8_t wire_rsp_buf[sizeof(struct cxlmi_cmd_get_scan_media_results_rsp) + + 2 * sizeof(struct cxlmi_media_error_record)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_scan_media_results_rsp) + + 2 * sizeof(struct cxlmi_media_error_record)]; + struct cxlmi_cmd_get_scan_media_results_rsp *wire_rsp = + (struct cxlmi_cmd_get_scan_media_results_rsp *)wire_rsp_buf; + struct cxlmi_cmd_get_scan_media_results_rsp *ret = + (struct cxlmi_cmd_get_scan_media_results_rsp *)ret_buf; + int rc; + + memset(wire_rsp_buf, 0, sizeof(wire_rsp_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Encode wire data in LE - library should decode correctly */ + wire_rsp->scan_media_restart_physaddr = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp->scan_media_restart_physaddr_length = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp->scan_media_flags = 0x05; + wire_rsp->media_error_count = cpu_to_le16(2); + + /* Records with distinct byte patterns */ + wire_rsp->record[0].media_error_address = cpu_to_le64(0xAABBCCDDEEFF0011ULL); + wire_rsp->record[0].media_error_length = cpu_to_le32(0x12345678); + wire_rsp->record[1].media_error_address = cpu_to_le64(0x1122334455667788ULL); + wire_rsp->record[1].media_error_length = cpu_to_le32(0xABCDEF01); + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x05, CXLMI_RET_SUCCESS, + wire_rsp_buf, sizeof(wire_rsp_buf)); + rc = cxlmi_cmd_get_scan_media_results(test_ep, NULL, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_TRUE(ret->scan_media_restart_physaddr == 0x123456789ABCDEF0ULL, + "scan_media_restart_physaddr endianness wrong"); + ASSERT_TRUE(ret->scan_media_restart_physaddr_length == 0xFEDCBA9876543210ULL, + "scan_media_restart_physaddr_length endianness wrong"); + ASSERT_EQ(ret->scan_media_flags, 0x05, "scan_media_flags wrong"); + ASSERT_EQ(ret->media_error_count, 2, "media_error_count wrong"); + + ASSERT_TRUE(ret->record[0].media_error_address == 0xAABBCCDDEEFF0011ULL, + "record[0].media_error_address endianness wrong"); + ASSERT_EQ(ret->record[0].media_error_length, 0x12345678, + "record[0].media_error_length endianness wrong"); + + ASSERT_TRUE(ret->record[1].media_error_address == 0x1122334455667788ULL, + "record[1].media_error_address endianness wrong"); + ASSERT_EQ(ret->record[1].media_error_length, 0xABCDEF01, + "record[1].media_error_length endianness wrong"); + + return 0; +} + +/* + * Test get_supported_features request encoding (32-bit count, 16-bit index). + */ +static int test_endian_get_supported_features_request(void) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + struct cxlmi_cmd_get_supported_features_rsp ret = {0}; + uint8_t cmd_payload[256]; + size_t cmd_payload_size = sizeof(cmd_payload); + uint8_t cmd_set, cmd; + int rc; + + req.count = 0x12345678; + req.starting_feature_index = 0xBEEF; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* Provide minimal response */ + uint8_t wire_rsp[8] = {0}; + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, &ret); + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, cmd_payload, &cmd_payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x05, "wrong command set"); + ASSERT_EQ(cmd, 0x00, "wrong command"); + + /* count: 0x12345678 -> 78 56 34 12 (32-bit LE at offset 0) */ + ASSERT_EQ(cmd_payload[0], 0x78, "count byte 0 wrong"); + ASSERT_EQ(cmd_payload[1], 0x56, "count byte 1 wrong"); + ASSERT_EQ(cmd_payload[2], 0x34, "count byte 2 wrong"); + ASSERT_EQ(cmd_payload[3], 0x12, "count byte 3 wrong"); + + /* starting_feature_index: 0xBEEF -> EF BE (16-bit LE at offset 4) */ + ASSERT_EQ(cmd_payload[4], 0xEF, "starting_feature_index byte 0 wrong"); + ASSERT_EQ(cmd_payload[5], 0xBE, "starting_feature_index byte 1 wrong"); + + return 0; +} + +/* + * Test get_supported_features response (16/32-bit header, per-entry fields). + * Uses compound struct with explicit LE encoding. + */ +static int test_endian_get_supported_features_response(void) +{ + struct wire_entry { + uint8_t feature_id[0x10]; + uint16_t feature_index; + uint16_t get_feature_size; + uint16_t set_feature_size; + uint32_t attribute_flags; + uint8_t get_feature_version; + uint8_t set_feature_version; + uint16_t set_feature_effects; + uint8_t rsvd[18]; + } __attribute__((packed)); + struct { + uint16_t num_supported_feature_entries; + uint16_t device_supported_features; + uint8_t rsvd[4]; + struct wire_entry entries[2]; + } __attribute__((packed)) wire_rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_supported_features_rsp) + + 2 * sizeof(struct wire_entry)]; + struct cxlmi_cmd_get_supported_features_rsp *ret = + (struct cxlmi_cmd_get_supported_features_rsp *)ret_buf; + struct cxlmi_cmd_get_supported_features_req req = {0}; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Header fields */ + wire_rsp.num_supported_feature_entries = cpu_to_le16(2); + wire_rsp.device_supported_features = cpu_to_le16(0xCAFE); + + /* Entry 0 with distinct byte patterns */ + wire_rsp.entries[0].feature_index = cpu_to_le16(0xBEEF); + wire_rsp.entries[0].get_feature_size = cpu_to_le16(0xDEAD); + wire_rsp.entries[0].set_feature_size = cpu_to_le16(0xF00D); + wire_rsp.entries[0].attribute_flags = cpu_to_le32(0x12345678); + wire_rsp.entries[0].set_feature_effects = cpu_to_le16(0xABCD); + + /* Entry 1 */ + wire_rsp.entries[1].feature_index = cpu_to_le16(0x1234); + wire_rsp.entries[1].get_feature_size = cpu_to_le16(0x5678); + wire_rsp.entries[1].set_feature_size = cpu_to_le16(0x9ABC); + wire_rsp.entries[1].attribute_flags = cpu_to_le32(0xFEDCBA98); + wire_rsp.entries[1].set_feature_effects = cpu_to_le16(0xEF01); + + req.count = sizeof(wire_rsp.entries); + req.starting_feature_index = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x05, 0x00, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_supported_features(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_supported_feature_entries, 2, "num_entries wrong"); + ASSERT_EQ(ret->device_supported_features, 0xCAFE, "device_supported_features endianness wrong"); + + /* Entry 0 */ + ASSERT_EQ(ret->supported_feature_entries[0].feature_index, 0xBEEF, + "entry[0].feature_index endianness wrong"); + ASSERT_EQ(ret->supported_feature_entries[0].get_feature_size, 0xDEAD, + "entry[0].get_feature_size endianness wrong"); + ASSERT_EQ(ret->supported_feature_entries[0].set_feature_size, 0xF00D, + "entry[0].set_feature_size endianness wrong"); + ASSERT_EQ(ret->supported_feature_entries[0].attribute_flags, 0x12345678, + "entry[0].attribute_flags endianness wrong"); + ASSERT_EQ(ret->supported_feature_entries[0].set_feature_effects, 0xABCD, + "entry[0].set_feature_effects endianness wrong"); + + /* Entry 1 */ + ASSERT_EQ(ret->supported_feature_entries[1].feature_index, 0x1234, + "entry[1].feature_index endianness wrong"); + ASSERT_EQ(ret->supported_feature_entries[1].attribute_flags, 0xFEDCBA98, + "entry[1].attribute_flags endianness wrong"); + + return 0; +} + +/* + * Test memdev_get_dc_config response (64-bit region fields, 32-bit counts). + * Struct has region_configs[8] array with base, decode_len, region_len, block_size. + * decode_len is multiplied by CXL_CAPACITY_MULTIPLIER (256MB). + */ +static int test_endian_memdev_get_dc_config(void) +{ + /* + * The response has variable layout: header + N regions + trailing fields. + * Trailing fields immediately follow the returned regions (not at fixed offset). + * Header: 8 bytes (num_regions, regions_returned, rsvd[6]) + * Per-region: 40 bytes (base, decode_len, region_len, block_size, dsmadhandle, flags, rsvd[3]) + * Trailing: 16 bytes (num_extents_supported/available, num_tags_supported/available) + */ + struct __attribute__((packed)) { + uint8_t num_regions; + uint8_t regions_returned; + uint8_t rsvd1[6]; + struct __attribute__((packed)) { + uint64_t base; + uint64_t decode_len; + uint64_t region_len; + uint64_t block_size; + uint32_t dsmadhandle; + uint8_t flags; + uint8_t rsvd2[3]; + } region_configs[2]; /* Only 2 regions */ + uint32_t num_extents_supported; + uint32_t num_extents_available; + uint32_t num_tags_supported; + uint32_t num_tags_available; + } wire_rsp = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp ret = {0}; + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + int rc; + + wire_rsp.num_regions = 2; + wire_rsp.regions_returned = 2; + + /* Region 0 */ + wire_rsp.region_configs[0].base = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.region_configs[0].decode_len = cpu_to_le64(0x00000010); /* 16 * 256MB = 4GB */ + wire_rsp.region_configs[0].region_len = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp.region_configs[0].block_size = cpu_to_le64(0xAABBCCDDEEFF0011ULL); + wire_rsp.region_configs[0].dsmadhandle = cpu_to_le32(0xDEADBEEF); + + /* Region 1 */ + wire_rsp.region_configs[1].base = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.region_configs[1].decode_len = cpu_to_le64(0x00000020); /* 32 * 256MB = 8GB */ + wire_rsp.region_configs[1].region_len = cpu_to_le64(0x8877665544332211ULL); + wire_rsp.region_configs[1].block_size = cpu_to_le64(0x0102030405060708ULL); + wire_rsp.region_configs[1].dsmadhandle = cpu_to_le32(0xCAFEBABE); + + /* Trailing 32-bit fields immediately after region[1] */ + wire_rsp.num_extents_supported = cpu_to_le32(0x11223344); + wire_rsp.num_extents_available = cpu_to_le32(0x55667788); + wire_rsp.num_tags_supported = cpu_to_le32(0x99AABBCC); + wire_rsp.num_tags_available = cpu_to_le32(0xDDEEFF00); + + req.region_cnt = 2; + req.start_region_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x00, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_dc_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.num_regions, 2, "num_regions wrong"); + ASSERT_EQ(ret.regions_returned, 2, "regions_returned wrong"); + + /* Region 0 */ + ASSERT_TRUE(ret.region_configs[0].base == 0x123456789ABCDEF0ULL, + "region[0].base endianness wrong"); + /* decode_len is multiplied by 256MB */ + ASSERT_TRUE(ret.region_configs[0].decode_len == (0x10ULL * 256ULL * 1024ULL * 1024ULL), + "region[0].decode_len endianness/multiplier wrong"); + ASSERT_TRUE(ret.region_configs[0].region_len == 0xFEDCBA9876543210ULL, + "region[0].region_len endianness wrong"); + ASSERT_TRUE(ret.region_configs[0].block_size == 0xAABBCCDDEEFF0011ULL, + "region[0].block_size endianness wrong"); + ASSERT_EQ(ret.region_configs[0].dsmadhandle, 0xDEADBEEF, + "region[0].dsmadhandle endianness wrong"); + + /* Region 1 */ + ASSERT_TRUE(ret.region_configs[1].base == 0x1122334455667788ULL, + "region[1].base endianness wrong"); + ASSERT_TRUE(ret.region_configs[1].decode_len == (0x20ULL * 256ULL * 1024ULL * 1024ULL), + "region[1].decode_len endianness/multiplier wrong"); + ASSERT_EQ(ret.region_configs[1].dsmadhandle, 0xCAFEBABE, + "region[1].dsmadhandle endianness wrong"); + + /* Trailing 32-bit fields */ + ASSERT_EQ(ret.num_extents_supported, 0x11223344, + "num_extents_supported endianness wrong"); + ASSERT_EQ(ret.num_extents_available, 0x55667788, + "num_extents_available endianness wrong"); + ASSERT_EQ(ret.num_tags_supported, 0x99AABBCC, + "num_tags_supported endianness wrong"); + ASSERT_EQ(ret.num_tags_available, 0xDDEEFF00, + "num_tags_available endianness wrong"); + + return 0; +} + +/* + * Test memdev_get_dc_extent_list request encoding (32-bit fields). + */ +static int test_endian_get_dc_extent_list_request(void) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp ret = {0}; + uint8_t cmd_payload[256]; + size_t cmd_payload_size = sizeof(cmd_payload); + uint8_t cmd_set, cmd; + int rc; + + req.extent_cnt = 0x12345678; + req.start_extent_idx = 0xDEADBEEF; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* Provide minimal response */ + uint8_t wire_rsp[16] = {0}; + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, + wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, &ret); + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, cmd_payload, &cmd_payload_size); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(cmd_set, 0x48, "wrong command set"); + ASSERT_EQ(cmd, 0x01, "wrong command"); + + /* + * Note: Library clamps extent_cnt to 8 if > 8 or 0, so we check + * that at least start_extent_idx is encoded correctly. + * start_extent_idx: 0xDEADBEEF -> EF BE AD DE (32-bit LE at offset 4) + */ + ASSERT_EQ(cmd_payload[4], 0xEF, "start_extent_idx byte 0 wrong"); + ASSERT_EQ(cmd_payload[5], 0xBE, "start_extent_idx byte 1 wrong"); + ASSERT_EQ(cmd_payload[6], 0xAD, "start_extent_idx byte 2 wrong"); + ASSERT_EQ(cmd_payload[7], 0xDE, "start_extent_idx byte 3 wrong"); + + return 0; +} + +/* + * Test memdev_get_dc_extent_list response (32-bit header, 64-bit/16-bit extent fields). + */ +static int test_endian_get_dc_extent_list_response(void) +{ + struct wire_extent { + uint64_t start_dpa; + uint64_t len; + uint8_t tag[0x10]; + uint16_t shared_seq; + uint8_t rsvd[0x6]; + } __attribute__((packed)); + struct { + uint32_t num_extents_returned; + uint32_t total_num_extents; + uint32_t generation_num; + uint8_t rsvd[4]; + struct wire_extent extents[2]; + } __attribute__((packed)) wire_rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_memdev_get_dc_extent_list_rsp) + + 2 * sizeof(struct wire_extent)]; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp *ret = + (struct cxlmi_cmd_memdev_get_dc_extent_list_rsp *)ret_buf; + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Header fields */ + wire_rsp.num_extents_returned = cpu_to_le32(2); + wire_rsp.total_num_extents = cpu_to_le32(0xDEADBEEF); + wire_rsp.generation_num = cpu_to_le32(0xCAFEBABE); + + /* Extent 0 */ + wire_rsp.extents[0].start_dpa = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.extents[0].len = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp.extents[0].shared_seq = cpu_to_le16(0xBEEF); + + /* Extent 1 */ + wire_rsp.extents[1].start_dpa = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.extents[1].len = cpu_to_le64(0x8877665544332211ULL); + wire_rsp.extents[1].shared_seq = cpu_to_le16(0xF00D); + + req.extent_cnt = 2; + req.start_extent_idx = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x48, 0x01, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_memdev_get_dc_extent_list(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->num_extents_returned, 2, "num_extents_returned wrong"); + ASSERT_EQ(ret->total_num_extents, 0xDEADBEEF, + "total_num_extents endianness wrong"); + ASSERT_EQ(ret->generation_num, 0xCAFEBABE, + "generation_num endianness wrong"); + + /* Extent 0 */ + ASSERT_TRUE(ret->extents[0].start_dpa == 0x123456789ABCDEF0ULL, + "extent[0].start_dpa endianness wrong"); + ASSERT_TRUE(ret->extents[0].len == 0xFEDCBA9876543210ULL, + "extent[0].len endianness wrong"); + ASSERT_EQ(ret->extents[0].shared_seq, 0xBEEF, + "extent[0].shared_seq endianness wrong"); + + /* Extent 1 */ + ASSERT_TRUE(ret->extents[1].start_dpa == 0x1122334455667788ULL, + "extent[1].start_dpa endianness wrong"); + ASSERT_TRUE(ret->extents[1].len == 0x8877665544332211ULL, + "extent[1].len endianness wrong"); + ASSERT_EQ(ret->extents[1].shared_seq, 0xF00D, + "extent[1].shared_seq endianness wrong"); + + return 0; +} + +/* + * Test get_event_records response (16/64-bit header and per-record fields). + * Header has overflow timestamps (64-bit) and counts (16-bit). + * Each record has handle, related_handle, ld_id (16-bit) and timestamp (64-bit). + */ +static int test_endian_get_event_records(void) +{ + /* + * Variable-length response: header + N records. + * Need to construct wire format with proper endianness. + */ + struct __attribute__((packed)) { + uint8_t flags; + uint8_t reserved1; + uint16_t overflow_err_count; + uint64_t first_overflow_timestamp; + uint64_t last_overflow_timestamp; + uint16_t record_count; + uint8_t reserved2[0xa]; + struct cxlmi_event_record records[2]; + } wire_rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_get_event_records_rsp) + + 2 * sizeof(struct cxlmi_event_record)]; + struct cxlmi_cmd_get_event_records_rsp *ret = + (struct cxlmi_cmd_get_event_records_rsp *)ret_buf; + struct cxlmi_cmd_get_event_records_req req = {0}; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Header fields */ + wire_rsp.flags = 0x01; + wire_rsp.overflow_err_count = cpu_to_le16(0xABCD); + wire_rsp.first_overflow_timestamp = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.last_overflow_timestamp = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp.record_count = cpu_to_le16(2); + + /* Record 0 */ + wire_rsp.records[0].length = 0x80; + wire_rsp.records[0].handle = cpu_to_le16(0x1234); + wire_rsp.records[0].related_handle = cpu_to_le16(0x5678); + wire_rsp.records[0].timestamp = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.records[0].ld_id = cpu_to_le16(0xBEEF); + + /* Record 1 */ + wire_rsp.records[1].length = 0x80; + wire_rsp.records[1].handle = cpu_to_le16(0xDEAD); + wire_rsp.records[1].related_handle = cpu_to_le16(0xBEEF); + wire_rsp.records[1].timestamp = cpu_to_le64(0x8877665544332211ULL); + wire_rsp.records[1].ld_id = cpu_to_le16(0xCAFE); + + req.event_log = 0; /* Info log */ + + ASSERT_EQ(setup(), 0, "setup failed"); + /* EVENTS=0x01, GET_RECORDS=0x00 */ + cxlmi_mock_set_response(test_ep, 0x01, 0x00, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_event_records(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + /* Header field checks */ + ASSERT_EQ(ret->flags, 0x01, "flags wrong"); + ASSERT_EQ(ret->overflow_err_count, 0xABCD, + "overflow_err_count endianness wrong"); + ASSERT_TRUE(ret->first_overflow_timestamp == 0x123456789ABCDEF0ULL, + "first_overflow_timestamp endianness wrong"); + ASSERT_TRUE(ret->last_overflow_timestamp == 0xFEDCBA9876543210ULL, + "last_overflow_timestamp endianness wrong"); + ASSERT_EQ(ret->record_count, 2, "record_count endianness wrong"); + + /* Record 0 checks */ + ASSERT_EQ(ret->records[0].handle, 0x1234, + "record[0].handle endianness wrong"); + ASSERT_EQ(ret->records[0].related_handle, 0x5678, + "record[0].related_handle endianness wrong"); + ASSERT_TRUE(ret->records[0].timestamp == 0x1122334455667788ULL, + "record[0].timestamp endianness wrong"); + ASSERT_EQ(ret->records[0].ld_id, 0xBEEF, + "record[0].ld_id endianness wrong"); + + /* Record 1 checks */ + ASSERT_EQ(ret->records[1].handle, 0xDEAD, + "record[1].handle endianness wrong"); + ASSERT_EQ(ret->records[1].related_handle, 0xBEEF, + "record[1].related_handle endianness wrong"); + ASSERT_TRUE(ret->records[1].timestamp == 0x8877665544332211ULL, + "record[1].timestamp endianness wrong"); + ASSERT_EQ(ret->records[1].ld_id, 0xCAFE, + "record[1].ld_id endianness wrong"); + + return 0; +} + +/* + * Test fmapi_get_dc_region_ext_list request encoding (16/32-bit fields). + */ +static int test_endian_fmapi_get_dc_region_ext_list_request(void) +{ + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp ret = {0}; + uint8_t cmd_set, cmd; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + req.host_id = 0x1234; + /* Use small extent_count - cmd allocates extent_count * sizeof(extent) bytes */ + req.extent_count = 0x0201; /* 513 - small enough to allocate, distinct bytes */ + req.start_ext_index = 0x04030201; /* Distinct bytes for endianness check */ + + ASSERT_EQ(setup(), 0, "setup failed"); + /* DCD_MANAGEMENT=0x56, GET_DC_REGION_EXTENT_LIST=0x03 */ + cxlmi_mock_set_response(test_ep, 0x56, 0x03, CXLMI_RET_SUCCESS, + NULL, 0); + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(test_ep, NULL, &req, &ret); + (void)rc; /* May fail due to minimal response, we only care about request encoding */ + + /* Check what was sent */ + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x56, "wrong command set"); + ASSERT_EQ(cmd, 0x03, "wrong command"); + + /* Check host_id encoding (16-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0x34, "host_id low byte wrong"); + ASSERT_EQ(payload[1], 0x12, "host_id high byte wrong"); + + /* Check extent_count encoding (32-bit LE at offset 4) */ + ASSERT_EQ(payload[4], 0x01, "extent_count byte 0 wrong"); + ASSERT_EQ(payload[5], 0x02, "extent_count byte 1 wrong"); + ASSERT_EQ(payload[6], 0x00, "extent_count byte 2 wrong"); + ASSERT_EQ(payload[7], 0x00, "extent_count byte 3 wrong"); + + /* Check start_ext_index encoding (32-bit LE at offset 8) */ + ASSERT_EQ(payload[8], 0x01, "start_ext_index byte 0 wrong"); + ASSERT_EQ(payload[9], 0x02, "start_ext_index byte 1 wrong"); + ASSERT_EQ(payload[10], 0x03, "start_ext_index byte 2 wrong"); + ASSERT_EQ(payload[11], 0x04, "start_ext_index byte 3 wrong"); + + return 0; +} + +/* + * Test fmapi_get_dc_region_ext_list response (16/32/64-bit fields with extents). + */ +static int test_endian_fmapi_get_dc_region_ext_list_response(void) +{ + struct wire_extent { + uint64_t start_dpa; + uint64_t len; + uint8_t tag[0x10]; + uint16_t shared_seq; + uint8_t rsvd[6]; + } __attribute__((packed)); + struct __attribute__((packed)) { + uint16_t host_id; + uint8_t rsvd1[2]; + uint32_t start_ext_index; + uint32_t extents_returned; + uint32_t total_extents; + uint32_t list_generation_num; + uint8_t rsvd2[4]; + struct wire_extent extents[2]; + } wire_rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp) + + 2 * sizeof(struct wire_extent)]; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp *ret = + (struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp *)ret_buf; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + + /* Header fields */ + wire_rsp.host_id = cpu_to_le16(0xABCD); + wire_rsp.start_ext_index = cpu_to_le32(0x11223344); + wire_rsp.extents_returned = cpu_to_le32(2); + wire_rsp.total_extents = cpu_to_le32(0xDEADBEEF); + wire_rsp.list_generation_num = cpu_to_le32(0xCAFEBABE); + + /* Extent 0 */ + wire_rsp.extents[0].start_dpa = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.extents[0].len = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp.extents[0].shared_seq = cpu_to_le16(0xBEEF); + + /* Extent 1 */ + wire_rsp.extents[1].start_dpa = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.extents[1].len = cpu_to_le64(0x8877665544332211ULL); + wire_rsp.extents[1].shared_seq = cpu_to_le16(0xF00D); + + req.host_id = 0; + req.extent_count = 2; + req.start_ext_index = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x03, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + /* Header checks */ + ASSERT_EQ(ret->host_id, 0xABCD, "host_id endianness wrong"); + ASSERT_EQ(ret->start_ext_index, 0x11223344, + "start_ext_index endianness wrong"); + ASSERT_EQ(ret->extents_returned, 2, "extents_returned wrong"); + ASSERT_EQ(ret->total_extents, 0xDEADBEEF, + "total_extents endianness wrong"); + ASSERT_EQ(ret->list_generation_num, 0xCAFEBABE, + "list_generation_num endianness wrong"); + + /* Extent 0 checks */ + ASSERT_TRUE(ret->extents[0].start_dpa == 0x123456789ABCDEF0ULL, + "extent[0].start_dpa endianness wrong"); + ASSERT_TRUE(ret->extents[0].len == 0xFEDCBA9876543210ULL, + "extent[0].len endianness wrong"); + ASSERT_EQ(ret->extents[0].shared_seq, 0xBEEF, + "extent[0].shared_seq endianness wrong"); + + /* Extent 1 checks */ + ASSERT_TRUE(ret->extents[1].start_dpa == 0x1122334455667788ULL, + "extent[1].start_dpa endianness wrong"); + ASSERT_TRUE(ret->extents[1].len == 0x8877665544332211ULL, + "extent[1].len endianness wrong"); + ASSERT_EQ(ret->extents[1].shared_seq, 0xF00D, + "extent[1].shared_seq endianness wrong"); + + return 0; +} + +/* + * Test fmapi_get_dc_reg_config request encoding (16-bit host_id). + */ +static int test_endian_fmapi_get_dc_reg_config_request(void) +{ + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp ret = {0}; + uint8_t cmd_set, cmd; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + + req.host_id = 0xABCD; + req.region_cnt = 2; + req.start_region_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* DCD_MANAGEMENT=0x56, GET_HOST_DC_REGION_CONFIG=0x01 */ + cxlmi_mock_set_response(test_ep, 0x56, 0x01, CXLMI_RET_SUCCESS, + NULL, 0); + cxlmi_cmd_fmapi_get_dc_reg_config(test_ep, NULL, &req, &ret); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x56, "wrong command set"); + ASSERT_EQ(cmd, 0x01, "wrong command"); + + /* Check host_id encoding (16-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0xCD, "host_id low byte wrong"); + ASSERT_EQ(payload[1], 0xAB, "host_id high byte wrong"); + + return 0; +} + +/* + * Test fmapi_get_dc_reg_config response (16-bit host_id, 64-bit region fields, 32-bit trailing). + * Similar to memdev_get_dc_config but with host_id field. + */ +static int test_endian_fmapi_get_dc_reg_config_response(void) +{ + /* + * Variable-length response like memdev_get_dc_config. + * Trailing fields follow returned regions. + */ + struct __attribute__((packed)) { + uint16_t host_id; + uint8_t num_regions; + uint8_t regions_returned; + struct __attribute__((packed)) { + uint64_t base; + uint64_t decode_len; + uint64_t region_len; + uint64_t block_size; + uint8_t flags; + uint8_t rsvd[3]; + uint8_t sanitize_on_release; + uint8_t rsvd2[3]; + } region_configs[2]; + uint32_t num_extents_supported; + uint32_t num_extents_available; + uint32_t num_tags_supported; + uint32_t num_tags_available; + } wire_rsp = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp ret = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + int rc; + + wire_rsp.host_id = cpu_to_le16(0xABCD); + wire_rsp.num_regions = 2; + wire_rsp.regions_returned = 2; + + /* Region 0 */ + wire_rsp.region_configs[0].base = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.region_configs[0].decode_len = cpu_to_le64(0x10); /* 16 * 256MB */ + wire_rsp.region_configs[0].region_len = cpu_to_le64(0xFEDCBA9876543210ULL); + wire_rsp.region_configs[0].block_size = cpu_to_le64(0xAABBCCDDEEFF0011ULL); + + /* Region 1 */ + wire_rsp.region_configs[1].base = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.region_configs[1].decode_len = cpu_to_le64(0x20); /* 32 * 256MB */ + wire_rsp.region_configs[1].region_len = cpu_to_le64(0x8877665544332211ULL); + wire_rsp.region_configs[1].block_size = cpu_to_le64(0x0102030405060708ULL); + + /* Trailing fields */ + wire_rsp.num_extents_supported = cpu_to_le32(0x11223344); + wire_rsp.num_extents_available = cpu_to_le32(0x55667788); + wire_rsp.num_tags_supported = cpu_to_le32(0x99AABBCC); + wire_rsp.num_tags_available = cpu_to_le32(0xDDEEFF00); + + req.host_id = 0; + req.region_cnt = 2; + req.start_region_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x01, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_fmapi_get_dc_reg_config(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + /* Header checks */ + ASSERT_EQ(ret.host_id, 0xABCD, "host_id endianness wrong"); + ASSERT_EQ(ret.num_regions, 2, "num_regions wrong"); + ASSERT_EQ(ret.regions_returned, 2, "regions_returned wrong"); + + /* Region 0 */ + ASSERT_TRUE(ret.region_configs[0].base == 0x123456789ABCDEF0ULL, + "region[0].base endianness wrong"); + ASSERT_TRUE(ret.region_configs[0].decode_len == (0x10ULL * 256ULL * 1024ULL * 1024ULL), + "region[0].decode_len endianness/multiplier wrong"); + ASSERT_TRUE(ret.region_configs[0].region_len == 0xFEDCBA9876543210ULL, + "region[0].region_len endianness wrong"); + ASSERT_TRUE(ret.region_configs[0].block_size == 0xAABBCCDDEEFF0011ULL, + "region[0].block_size endianness wrong"); + + /* Region 1 */ + ASSERT_TRUE(ret.region_configs[1].base == 0x1122334455667788ULL, + "region[1].base endianness wrong"); + ASSERT_TRUE(ret.region_configs[1].decode_len == (0x20ULL * 256ULL * 1024ULL * 1024ULL), + "region[1].decode_len endianness/multiplier wrong"); + + /* Trailing fields */ + ASSERT_EQ(ret.num_extents_supported, 0x11223344, + "num_extents_supported endianness wrong"); + ASSERT_EQ(ret.num_extents_available, 0x55667788, + "num_extents_available endianness wrong"); + ASSERT_EQ(ret.num_tags_supported, 0x99AABBCC, + "num_tags_supported endianness wrong"); + ASSERT_EQ(ret.num_tags_available, 0xDDEEFF00, + "num_tags_available endianness wrong"); + + return 0; +} + +/* + * Test fmapi_initiate_dc_add request encoding (16/32/64-bit fields with extents). + */ +static int test_endian_fmapi_initiate_dc_add_request(void) +{ + /* Extent size is 40 bytes (8 + 8 + 0x10 + 2 + 6) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_initiate_dc_add_req) + 40]; + struct cxlmi_cmd_fmapi_initiate_dc_add_req *req = + (struct cxlmi_cmd_fmapi_initiate_dc_add_req *)req_buf; + uint8_t cmd_set, cmd; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + + memset(req_buf, 0, sizeof(req_buf)); + req->host_id = 0xABCD; + req->selection_policy = 0x01; + req->region_num = 0x02; + req->length = 0x123456789ABCDEF0ULL; + req->ext_count = 1; + + req->extents[0].start_dpa = 0xFEDCBA9876543210ULL; + req->extents[0].len = 0x1122334455667788ULL; + req->extents[0].shared_seq = 0xBEEF; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* DCD_MANAGEMENT=0x56, INITIATE_DC_ADD=0x04 */ + cxlmi_mock_set_response(test_ep, 0x56, 0x04, CXLMI_RET_SUCCESS, + NULL, 0); + cxlmi_cmd_fmapi_initiate_dc_add(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x56, "wrong command set"); + ASSERT_EQ(cmd, 0x04, "wrong command"); + + /* Check host_id encoding (16-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0xCD, "host_id low byte wrong"); + ASSERT_EQ(payload[1], 0xAB, "host_id high byte wrong"); + + /* Check length encoding (64-bit LE at offset 4) */ + ASSERT_EQ(payload[4], 0xF0, "length byte 0 wrong"); + ASSERT_EQ(payload[5], 0xDE, "length byte 1 wrong"); + ASSERT_EQ(payload[6], 0xBC, "length byte 2 wrong"); + ASSERT_EQ(payload[7], 0x9A, "length byte 3 wrong"); + ASSERT_EQ(payload[8], 0x78, "length byte 4 wrong"); + ASSERT_EQ(payload[9], 0x56, "length byte 5 wrong"); + ASSERT_EQ(payload[10], 0x34, "length byte 6 wrong"); + ASSERT_EQ(payload[11], 0x12, "length byte 7 wrong"); + + /* Check ext_count encoding (32-bit LE at offset 28: after 4+8+16=28) */ + ASSERT_EQ(payload[28], 0x01, "ext_count byte 0 wrong"); + ASSERT_EQ(payload[29], 0x00, "ext_count byte 1 wrong"); + ASSERT_EQ(payload[30], 0x00, "ext_count byte 2 wrong"); + ASSERT_EQ(payload[31], 0x00, "ext_count byte 3 wrong"); + + /* Check extent[0].start_dpa (64-bit LE at offset 32) */ + ASSERT_EQ(payload[32], 0x10, "extent[0].start_dpa byte 0 wrong"); + ASSERT_EQ(payload[33], 0x32, "extent[0].start_dpa byte 1 wrong"); + ASSERT_EQ(payload[34], 0x54, "extent[0].start_dpa byte 2 wrong"); + ASSERT_EQ(payload[35], 0x76, "extent[0].start_dpa byte 3 wrong"); + ASSERT_EQ(payload[36], 0x98, "extent[0].start_dpa byte 4 wrong"); + ASSERT_EQ(payload[37], 0xBA, "extent[0].start_dpa byte 5 wrong"); + ASSERT_EQ(payload[38], 0xDC, "extent[0].start_dpa byte 6 wrong"); + ASSERT_EQ(payload[39], 0xFE, "extent[0].start_dpa byte 7 wrong"); + + /* Check extent[0].len (64-bit LE at offset 40) */ + ASSERT_EQ(payload[40], 0x88, "extent[0].len byte 0 wrong"); + ASSERT_EQ(payload[41], 0x77, "extent[0].len byte 1 wrong"); + ASSERT_EQ(payload[42], 0x66, "extent[0].len byte 2 wrong"); + ASSERT_EQ(payload[43], 0x55, "extent[0].len byte 3 wrong"); + ASSERT_EQ(payload[44], 0x44, "extent[0].len byte 4 wrong"); + ASSERT_EQ(payload[45], 0x33, "extent[0].len byte 5 wrong"); + ASSERT_EQ(payload[46], 0x22, "extent[0].len byte 6 wrong"); + ASSERT_EQ(payload[47], 0x11, "extent[0].len byte 7 wrong"); + + /* Check extent[0].shared_seq (16-bit LE at offset 64: 40+8+16=64) */ + ASSERT_EQ(payload[64], 0xEF, "extent[0].shared_seq low byte wrong"); + ASSERT_EQ(payload[65], 0xBE, "extent[0].shared_seq high byte wrong"); + + return 0; +} + +/* + * Test fmapi_initiate_dc_release request encoding (16/32/64-bit fields with extents). + */ +static int test_endian_fmapi_initiate_dc_release_request(void) +{ + /* Extent size is 40 bytes (8 + 8 + 0x10 + 2 + 6) */ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_initiate_dc_release_req) + 40]; + struct cxlmi_cmd_fmapi_initiate_dc_release_req *req = + (struct cxlmi_cmd_fmapi_initiate_dc_release_req *)req_buf; + uint8_t cmd_set, cmd; + uint8_t payload[128]; + size_t payload_size = sizeof(payload); + + memset(req_buf, 0, sizeof(req_buf)); + req->host_id = 0xDEAD; + req->flags = 0x01; + req->length = 0xCAFEBABE12345678ULL; + req->ext_count = 1; + + req->extents[0].start_dpa = 0xABCDEF0123456789ULL; + req->extents[0].len = 0x9876543210FEDCBAULL; + req->extents[0].shared_seq = 0xF00D; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* DCD_MANAGEMENT=0x56, INITIATE_DC_RELEASE=0x05 */ + cxlmi_mock_set_response(test_ep, 0x56, 0x05, CXLMI_RET_SUCCESS, + NULL, 0); + cxlmi_cmd_fmapi_initiate_dc_release(test_ep, NULL, req); + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x56, "wrong command set"); + ASSERT_EQ(cmd, 0x05, "wrong command"); + + /* Check host_id encoding (16-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0xAD, "host_id low byte wrong"); + ASSERT_EQ(payload[1], 0xDE, "host_id high byte wrong"); + + /* Check length encoding (64-bit LE at offset 4) */ + ASSERT_EQ(payload[4], 0x78, "length byte 0 wrong"); + ASSERT_EQ(payload[5], 0x56, "length byte 1 wrong"); + ASSERT_EQ(payload[6], 0x34, "length byte 2 wrong"); + ASSERT_EQ(payload[7], 0x12, "length byte 3 wrong"); + ASSERT_EQ(payload[8], 0xBE, "length byte 4 wrong"); + ASSERT_EQ(payload[9], 0xBA, "length byte 5 wrong"); + ASSERT_EQ(payload[10], 0xFE, "length byte 6 wrong"); + ASSERT_EQ(payload[11], 0xCA, "length byte 7 wrong"); + + /* Check ext_count encoding (32-bit LE at offset 28) */ + ASSERT_EQ(payload[28], 0x01, "ext_count byte 0 wrong"); + ASSERT_EQ(payload[29], 0x00, "ext_count byte 1 wrong"); + + /* Check extent[0].start_dpa (64-bit LE at offset 32) */ + ASSERT_EQ(payload[32], 0x89, "extent[0].start_dpa byte 0 wrong"); + ASSERT_EQ(payload[33], 0x67, "extent[0].start_dpa byte 1 wrong"); + ASSERT_EQ(payload[34], 0x45, "extent[0].start_dpa byte 2 wrong"); + ASSERT_EQ(payload[35], 0x23, "extent[0].start_dpa byte 3 wrong"); + ASSERT_EQ(payload[36], 0x01, "extent[0].start_dpa byte 4 wrong"); + ASSERT_EQ(payload[37], 0xEF, "extent[0].start_dpa byte 5 wrong"); + ASSERT_EQ(payload[38], 0xCD, "extent[0].start_dpa byte 6 wrong"); + ASSERT_EQ(payload[39], 0xAB, "extent[0].start_dpa byte 7 wrong"); + + /* Check extent[0].shared_seq (16-bit LE at offset 64) */ + ASSERT_EQ(payload[64], 0x0D, "extent[0].shared_seq low byte wrong"); + ASSERT_EQ(payload[65], 0xF0, "extent[0].shared_seq high byte wrong"); + + return 0; +} + +/* + * Test get_log_cel response (16-bit opcode and command_effect per entry). + */ +static int test_endian_get_log_cel(void) +{ + struct __attribute__((packed)) { + uint16_t opcode; + uint16_t command_effect; + } wire_rsp[3] = {0}; + struct cxlmi_cmd_get_log_cel_rsp ret[3] = {0}; + struct cxlmi_cmd_get_log_req req = {0}; + int rc; + + /* Entry 0 */ + wire_rsp[0].opcode = cpu_to_le16(0x1234); + wire_rsp[0].command_effect = cpu_to_le16(0x5678); + + /* Entry 1 */ + wire_rsp[1].opcode = cpu_to_le16(0xABCD); + wire_rsp[1].command_effect = cpu_to_le16(0xEF01); + + /* Entry 2 */ + wire_rsp[2].opcode = cpu_to_le16(0xDEAD); + wire_rsp[2].command_effect = cpu_to_le16(0xBEEF); + + /* CEL log UUID */ + req.uuid[0] = 0xda; + req.uuid[1] = 0x9c; + req.uuid[2] = 0x6b; + req.uuid[3] = 0x0b; + req.offset = 0; + req.length = sizeof(wire_rsp); /* 12 bytes = 3 entries */ + + ASSERT_EQ(setup(), 0, "setup failed"); + /* LOGS=0x04, GET_LOG=0x01 */ + cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + + /* Entry 0 */ + ASSERT_EQ(ret[0].opcode, 0x1234, "entry[0].opcode endianness wrong"); + ASSERT_EQ(ret[0].command_effect, 0x5678, + "entry[0].command_effect endianness wrong"); + + /* Entry 1 */ + ASSERT_EQ(ret[1].opcode, 0xABCD, "entry[1].opcode endianness wrong"); + ASSERT_EQ(ret[1].command_effect, 0xEF01, + "entry[1].command_effect endianness wrong"); + + /* Entry 2 */ + ASSERT_EQ(ret[2].opcode, 0xDEAD, "entry[2].opcode endianness wrong"); + ASSERT_EQ(ret[2].command_effect, 0xBEEF, + "entry[2].command_effect endianness wrong"); + + return 0; +} + +/* + * Test fmapi_dc_list_tags request encoding (32-bit fields). + */ +static int test_endian_fmapi_dc_list_tags_request(void) +{ + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp ret = {0}; + uint8_t cmd_set, cmd; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + req.start_idx = 0x11223344; + req.tags_count = 0x01020304; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* DCD_MANAGEMENT=0x56, DC_LIST_TAGS=0x08 */ + cxlmi_mock_set_response(test_ep, 0x56, 0x08, CXLMI_RET_SUCCESS, + NULL, 0); + rc = cxlmi_cmd_fmapi_dc_list_tags(test_ep, NULL, &req, &ret); + (void)rc; + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x56, "wrong command set"); + ASSERT_EQ(cmd, 0x08, "wrong command"); + + /* Check start_idx encoding (32-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0x44, "start_idx byte 0 wrong"); + ASSERT_EQ(payload[1], 0x33, "start_idx byte 1 wrong"); + ASSERT_EQ(payload[2], 0x22, "start_idx byte 2 wrong"); + ASSERT_EQ(payload[3], 0x11, "start_idx byte 3 wrong"); + + /* Check tags_count encoding (32-bit LE at offset 4) */ + ASSERT_EQ(payload[4], 0x04, "tags_count byte 0 wrong"); + ASSERT_EQ(payload[5], 0x03, "tags_count byte 1 wrong"); + ASSERT_EQ(payload[6], 0x02, "tags_count byte 2 wrong"); + ASSERT_EQ(payload[7], 0x01, "tags_count byte 3 wrong"); + + return 0; +} + +/* + * Test fmapi_dc_list_tags response (32-bit header fields). + */ +static int test_endian_fmapi_dc_list_tags_response(void) +{ + struct __attribute__((packed)) { + uint32_t generation_num; + uint32_t total_num_tags; + uint32_t num_tags_returned; + uint8_t validity_bitmap; + uint8_t rsvd[3]; + } wire_rsp = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp ret = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + int rc; + + wire_rsp.generation_num = cpu_to_le32(0x11223344); + wire_rsp.total_num_tags = cpu_to_le32(0x55667788); + wire_rsp.num_tags_returned = cpu_to_le32(0); /* No tags to avoid flex array */ + wire_rsp.validity_bitmap = 0xAB; + + req.start_idx = 0; + req.tags_count = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x56, 0x08, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_fmapi_dc_list_tags(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.generation_num, 0x11223344, + "generation_num endianness wrong"); + ASSERT_EQ(ret.total_num_tags, 0x55667788, + "total_num_tags endianness wrong"); + ASSERT_EQ(ret.num_tags_returned, 0, "num_tags_returned wrong"); + ASSERT_EQ(ret.validity_bitmap, 0xAB, "validity_bitmap wrong"); + + return 0; +} + +/* + * Test get_scan_media_capabilities request encoding (64-bit fields). + */ +static int test_endian_get_scan_media_capabilities_request(void) +{ + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp ret = {0}; + uint8_t cmd_set, cmd; + uint8_t payload[32]; + size_t payload_size = sizeof(payload); + int rc; + + req.get_scan_media_capabilities_start_physaddr = 0x123456789ABCDEF0ULL; + req.get_scan_media_capabilities_physaddr_length = 0xFEDCBA9876543210ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* MEDIA_AND_POISON=0x43, GET_SCAN_MEDIA_CAPABILITIES=0x03 */ + cxlmi_mock_set_response(test_ep, 0x43, 0x03, CXLMI_RET_SUCCESS, + NULL, 0); + rc = cxlmi_cmd_get_scan_media_capabilities(test_ep, NULL, &req, &ret); + (void)rc; + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x43, "wrong command set"); + ASSERT_EQ(cmd, 0x03, "wrong command"); + + /* Check start_physaddr encoding (64-bit LE at offset 0) */ + ASSERT_EQ(payload[0], 0xF0, "start_physaddr byte 0 wrong"); + ASSERT_EQ(payload[1], 0xDE, "start_physaddr byte 1 wrong"); + ASSERT_EQ(payload[2], 0xBC, "start_physaddr byte 2 wrong"); + ASSERT_EQ(payload[3], 0x9A, "start_physaddr byte 3 wrong"); + ASSERT_EQ(payload[4], 0x78, "start_physaddr byte 4 wrong"); + ASSERT_EQ(payload[5], 0x56, "start_physaddr byte 5 wrong"); + ASSERT_EQ(payload[6], 0x34, "start_physaddr byte 6 wrong"); + ASSERT_EQ(payload[7], 0x12, "start_physaddr byte 7 wrong"); + + /* Check physaddr_length encoding (64-bit LE at offset 8) */ + ASSERT_EQ(payload[8], 0x10, "physaddr_length byte 0 wrong"); + ASSERT_EQ(payload[9], 0x32, "physaddr_length byte 1 wrong"); + ASSERT_EQ(payload[10], 0x54, "physaddr_length byte 2 wrong"); + ASSERT_EQ(payload[11], 0x76, "physaddr_length byte 3 wrong"); + ASSERT_EQ(payload[12], 0x98, "physaddr_length byte 4 wrong"); + ASSERT_EQ(payload[13], 0xBA, "physaddr_length byte 5 wrong"); + ASSERT_EQ(payload[14], 0xDC, "physaddr_length byte 6 wrong"); + ASSERT_EQ(payload[15], 0xFE, "physaddr_length byte 7 wrong"); + + return 0; +} + +/* + * Test get_scan_media_capabilities response (32-bit field). + */ +static int test_endian_get_scan_media_capabilities_response(void) +{ + struct cxlmi_cmd_get_scan_media_capabilities_rsp wire_rsp = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp ret = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + int rc; + + wire_rsp.estimated_scan_media_time = cpu_to_le32(0xDEADBEEF); + + req.get_scan_media_capabilities_start_physaddr = 0; + req.get_scan_media_capabilities_physaddr_length = 0x1000; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x43, 0x03, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_get_scan_media_capabilities(test_ep, NULL, &req, &ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret.estimated_scan_media_time, 0xDEADBEEF, + "estimated_scan_media_time endianness wrong"); + + return 0; +} + +/* + * Test fmapi_set_ld_allocations request encoding (64-bit allocation fields). + */ +static int test_endian_fmapi_set_ld_allocations_request(void) +{ + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_req) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_req *req = + (struct cxlmi_cmd_fmapi_set_ld_allocations_req *)req_buf; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)ret_buf; + uint8_t cmd_set, cmd; + uint8_t payload[64]; + size_t payload_size = sizeof(payload); + int rc; + + memset(req_buf, 0, sizeof(req_buf)); + memset(ret_buf, 0, sizeof(ret_buf)); + + req->number_ld = 2; + req->start_ld_id = 0; + + req->ld_allocation_list[0].range_1_allocation_mult = 0x123456789ABCDEF0ULL; + req->ld_allocation_list[0].range_2_allocation_mult = 0xFEDCBA9876543210ULL; + + req->ld_allocation_list[1].range_1_allocation_mult = 0x1122334455667788ULL; + req->ld_allocation_list[1].range_2_allocation_mult = 0x8877665544332211ULL; + + ASSERT_EQ(setup(), 0, "setup failed"); + /* MLD_COMPONENTS=0x54, SET_LD_ALLOCATIONS=0x02 */ + cxlmi_mock_set_response(test_ep, 0x54, 0x02, CXLMI_RET_SUCCESS, + NULL, 0); + rc = cxlmi_cmd_fmapi_set_ld_allocations(test_ep, NULL, req, ret); + (void)rc; + + cxlmi_mock_get_last_command(test_ep, &cmd_set, &cmd, payload, &payload_size); + teardown(); + + ASSERT_EQ(cmd_set, 0x54, "wrong command set"); + ASSERT_EQ(cmd, 0x02, "wrong command"); + + /* Header: number_ld at 0, start_ld_id at 1, rsvd[2] at 2-3 */ + ASSERT_EQ(payload[0], 2, "number_ld wrong"); + ASSERT_EQ(payload[1], 0, "start_ld_id wrong"); + + /* LD[0].range_1_allocation_mult (64-bit LE at offset 4) */ + ASSERT_EQ(payload[4], 0xF0, "ld[0].range_1 byte 0 wrong"); + ASSERT_EQ(payload[5], 0xDE, "ld[0].range_1 byte 1 wrong"); + ASSERT_EQ(payload[6], 0xBC, "ld[0].range_1 byte 2 wrong"); + ASSERT_EQ(payload[7], 0x9A, "ld[0].range_1 byte 3 wrong"); + ASSERT_EQ(payload[8], 0x78, "ld[0].range_1 byte 4 wrong"); + ASSERT_EQ(payload[9], 0x56, "ld[0].range_1 byte 5 wrong"); + ASSERT_EQ(payload[10], 0x34, "ld[0].range_1 byte 6 wrong"); + ASSERT_EQ(payload[11], 0x12, "ld[0].range_1 byte 7 wrong"); + + /* LD[0].range_2_allocation_mult (64-bit LE at offset 12) */ + ASSERT_EQ(payload[12], 0x10, "ld[0].range_2 byte 0 wrong"); + ASSERT_EQ(payload[13], 0x32, "ld[0].range_2 byte 1 wrong"); + ASSERT_EQ(payload[14], 0x54, "ld[0].range_2 byte 2 wrong"); + ASSERT_EQ(payload[15], 0x76, "ld[0].range_2 byte 3 wrong"); + ASSERT_EQ(payload[16], 0x98, "ld[0].range_2 byte 4 wrong"); + ASSERT_EQ(payload[17], 0xBA, "ld[0].range_2 byte 5 wrong"); + ASSERT_EQ(payload[18], 0xDC, "ld[0].range_2 byte 6 wrong"); + ASSERT_EQ(payload[19], 0xFE, "ld[0].range_2 byte 7 wrong"); + + /* LD[1].range_1_allocation_mult (64-bit LE at offset 20) */ + ASSERT_EQ(payload[20], 0x88, "ld[1].range_1 byte 0 wrong"); + ASSERT_EQ(payload[21], 0x77, "ld[1].range_1 byte 1 wrong"); + ASSERT_EQ(payload[22], 0x66, "ld[1].range_1 byte 2 wrong"); + ASSERT_EQ(payload[23], 0x55, "ld[1].range_1 byte 3 wrong"); + ASSERT_EQ(payload[24], 0x44, "ld[1].range_1 byte 4 wrong"); + ASSERT_EQ(payload[25], 0x33, "ld[1].range_1 byte 5 wrong"); + ASSERT_EQ(payload[26], 0x22, "ld[1].range_1 byte 6 wrong"); + ASSERT_EQ(payload[27], 0x11, "ld[1].range_1 byte 7 wrong"); + + return 0; +} + +/* + * Test fmapi_set_ld_allocations response (64-bit allocation fields). + */ +static int test_endian_fmapi_set_ld_allocations_response(void) +{ + struct __attribute__((packed)) { + uint8_t number_ld; + uint8_t start_ld_id; + uint8_t rsvd[2]; + struct { + uint64_t range_1_allocation_mult; + uint64_t range_2_allocation_mult; + } ld_list[2]; + } wire_rsp = {0}; + uint8_t ret_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_rsp) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + uint8_t req_buf[sizeof(struct cxlmi_cmd_fmapi_set_ld_allocations_req) + + 2 * sizeof(struct cxlmi_cmd_fmapi_ld_allocations_list)]; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *ret = + (struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *)ret_buf; + struct cxlmi_cmd_fmapi_set_ld_allocations_req *req = + (struct cxlmi_cmd_fmapi_set_ld_allocations_req *)req_buf; + int rc; + + memset(ret_buf, 0, sizeof(ret_buf)); + memset(req_buf, 0, sizeof(req_buf)); + + wire_rsp.number_ld = 2; + wire_rsp.start_ld_id = 0; + + wire_rsp.ld_list[0].range_1_allocation_mult = cpu_to_le64(0x123456789ABCDEF0ULL); + wire_rsp.ld_list[0].range_2_allocation_mult = cpu_to_le64(0xFEDCBA9876543210ULL); + + wire_rsp.ld_list[1].range_1_allocation_mult = cpu_to_le64(0x1122334455667788ULL); + wire_rsp.ld_list[1].range_2_allocation_mult = cpu_to_le64(0x8877665544332211ULL); + + req->number_ld = 2; + req->start_ld_id = 0; + + ASSERT_EQ(setup(), 0, "setup failed"); + cxlmi_mock_set_response(test_ep, 0x54, 0x02, CXLMI_RET_SUCCESS, + &wire_rsp, sizeof(wire_rsp)); + rc = cxlmi_cmd_fmapi_set_ld_allocations(test_ep, NULL, req, ret); + teardown(); + + ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + ASSERT_EQ(ret->number_ld, 2, "number_ld wrong"); + ASSERT_EQ(ret->start_ld_id, 0, "start_ld_id wrong"); + + /* LD[0] */ + ASSERT_TRUE(ret->ld_allocation_list[0].range_1_allocation_mult == 0x123456789ABCDEF0ULL, + "ld[0].range_1 endianness wrong"); + ASSERT_TRUE(ret->ld_allocation_list[0].range_2_allocation_mult == 0xFEDCBA9876543210ULL, + "ld[0].range_2 endianness wrong"); + + /* LD[1] */ + ASSERT_TRUE(ret->ld_allocation_list[1].range_1_allocation_mult == 0x1122334455667788ULL, + "ld[1].range_1 endianness wrong"); + ASSERT_TRUE(ret->ld_allocation_list[1].range_2_allocation_mult == 0x8877665544332211ULL, + "ld[1].range_2 endianness wrong"); + + return 0; +} + +/* ============================================================ + * Main + * ============================================================ */ + +int main(void) +{ + printf("==========================================================\n"); + printf("libcxlmi Mock Transport Unit Tests\n"); + printf("==========================================================\n"); + + TEST_SUITE("Mock Infrastructure"); + RUN_TEST(test_mock_create_close); + RUN_TEST(test_mock_no_response_returns_unsupported); + RUN_TEST(test_mock_stats_tracking); + RUN_TEST(test_mock_clear_responses); + RUN_TEST(test_mock_payload_size_zero_when_no_payload); + RUN_TEST(test_mock_response_sequence); + RUN_TEST(test_mock_response_sequence_helper); + + TEST_SUITE("Generic Component Commands (Info/Status)"); + RUN_TEST(test_cmd_identify); + RUN_TEST(test_cmd_bg_op_status); + RUN_TEST(test_cmd_get_response_msg_limit); + RUN_TEST(test_cmd_set_response_msg_limit); + RUN_TEST(test_cmd_request_bg_op_abort); + + TEST_SUITE("Events Commands"); + RUN_TEST(test_cmd_get_event_records); + RUN_TEST(test_cmd_clear_event_records); + RUN_TEST(test_cmd_get_event_interrupt_policy); + RUN_TEST(test_cmd_set_event_interrupt_policy); + RUN_TEST(test_cmd_get_mctp_event_interrupt_policy); + RUN_TEST(test_cmd_set_mctp_event_interrupt_policy); + RUN_TEST(test_cmd_event_notification); + + TEST_SUITE("Firmware Update Commands"); + RUN_TEST(test_cmd_get_fw_info); + RUN_TEST(test_cmd_transfer_fw); + RUN_TEST(test_cmd_activate_fw); + + TEST_SUITE("Timestamp Commands"); + RUN_TEST(test_cmd_get_timestamp); + RUN_TEST(test_cmd_set_timestamp); + + TEST_SUITE("Logs Commands"); + RUN_TEST(test_cmd_get_supported_logs); + RUN_TEST(test_cmd_get_log); + RUN_TEST(test_cmd_get_log_cel); + RUN_TEST(test_cmd_get_log_capabilities); + RUN_TEST(test_cmd_clear_log); + RUN_TEST(test_cmd_populate_log); + RUN_TEST(test_cmd_get_supported_logs_sublist); + + TEST_SUITE("Features Commands"); + RUN_TEST(test_cmd_get_supported_features); + RUN_TEST(test_cmd_get_feature); + RUN_TEST(test_cmd_set_feature); + + TEST_SUITE("Memory Device Commands"); + RUN_TEST(test_cmd_memdev_identify); + RUN_TEST(test_cmd_memdev_get_partition_info); + RUN_TEST(test_cmd_memdev_set_partition_info); + RUN_TEST(test_cmd_memdev_get_lsa); + RUN_TEST(test_cmd_memdev_set_lsa); + + TEST_SUITE("Health Info/Alerts Commands"); + RUN_TEST(test_cmd_memdev_get_health_info); + RUN_TEST(test_cmd_memdev_get_alert_config); + RUN_TEST(test_cmd_memdev_set_alert_config); + RUN_TEST(test_cmd_memdev_get_shutdown_state); + RUN_TEST(test_cmd_memdev_set_shutdown_state); + + TEST_SUITE("Media and Poison Commands"); + RUN_TEST(test_cmd_get_poison_list); + RUN_TEST(test_cmd_memdev_inject_poison); + RUN_TEST(test_cmd_memdev_clear_poison); + RUN_TEST(test_cmd_get_scan_media_capabilities); + RUN_TEST(test_cmd_scan_media); + RUN_TEST(test_cmd_get_scan_media_results); + + TEST_SUITE("Sanitize Commands"); + RUN_TEST(test_cmd_memdev_sanitize); + RUN_TEST(test_cmd_memdev_secure_erase); + RUN_TEST(test_cmd_memdev_media_operations_discovery); + RUN_TEST(test_cmd_memdev_media_operations_sanitize); + + TEST_SUITE("Security Commands"); + RUN_TEST(test_cmd_memdev_get_security_state); + RUN_TEST(test_cmd_memdev_set_passphrase); + RUN_TEST(test_cmd_memdev_disable_passphrase); + RUN_TEST(test_cmd_memdev_unlock); + RUN_TEST(test_cmd_memdev_freeze_security_state); + RUN_TEST(test_cmd_memdev_passphrase_secure_erase); + RUN_TEST(test_cmd_memdev_security_send); + + TEST_SUITE("SLD QoS Commands"); + RUN_TEST(test_cmd_memdev_get_sld_qos_control); + RUN_TEST(test_cmd_memdev_set_sld_qos_control); + RUN_TEST(test_cmd_memdev_get_sld_qos_status); + + TEST_SUITE("DCD Config Commands"); + RUN_TEST(test_cmd_memdev_get_dc_config); + RUN_TEST(test_cmd_memdev_get_dc_extent_list); + RUN_TEST(test_cmd_memdev_add_dc_response); + RUN_TEST(test_cmd_memdev_release_dc); + + TEST_SUITE("FM-API Physical Switch Commands"); + RUN_TEST(test_cmd_fmapi_identify_sw_device); + RUN_TEST(test_cmd_fmapi_get_phys_port_state); + RUN_TEST(test_cmd_fmapi_phys_port_control); + RUN_TEST(test_cmd_fmapi_send_ppb_cxlio_config_request); + RUN_TEST(test_cmd_fmapi_get_domain_validation_sv_state); + RUN_TEST(test_cmd_fmapi_set_domain_validation_sv); + RUN_TEST(test_cmd_fmapi_get_vcs_domain_validation_sv_state); + RUN_TEST(test_cmd_fmapi_get_domain_validation_sv); + + TEST_SUITE("FM-API Virtual Switch Commands"); + RUN_TEST(test_cmd_fmapi_bind_vppb); + RUN_TEST(test_cmd_fmapi_unbind_vppb); + + TEST_SUITE("FM-API MLD Port Commands"); + RUN_TEST(test_cmd_fmapi_send_ld_cxlio_config_request); + RUN_TEST(test_cmd_fmapi_send_ld_cxlio_mem_request); + + TEST_SUITE("FM-API MLD Components Commands"); + RUN_TEST(test_cmd_fmapi_get_ld_info); + RUN_TEST(test_cmd_fmapi_get_ld_allocations); + RUN_TEST(test_cmd_fmapi_set_ld_allocations); + RUN_TEST(test_cmd_fmapi_get_qos_control); + RUN_TEST(test_cmd_fmapi_set_qos_control); + RUN_TEST(test_cmd_fmapi_get_qos_status); + RUN_TEST(test_cmd_fmapi_get_qos_allocated_bw); + RUN_TEST(test_cmd_fmapi_set_qos_allocated_bw); + RUN_TEST(test_cmd_fmapi_get_qos_bw_limit); + RUN_TEST(test_cmd_fmapi_set_qos_bw_limit); + + TEST_SUITE("FM-API Multi-Headed Commands"); + RUN_TEST(test_cmd_fmapi_get_multiheaded_info); + RUN_TEST(test_cmd_fmapi_get_head_info); + + TEST_SUITE("FM-API DCD Management Commands"); + RUN_TEST(test_cmd_fmapi_get_dcd_info); + RUN_TEST(test_cmd_fmapi_get_dc_reg_config); + RUN_TEST(test_cmd_fmapi_set_dc_region_config); + RUN_TEST(test_cmd_fmapi_get_dc_region_ext_list); + RUN_TEST(test_cmd_fmapi_initiate_dc_add); + RUN_TEST(test_cmd_fmapi_initiate_dc_release); + RUN_TEST(test_cmd_fmapi_dc_add_reference); + RUN_TEST(test_cmd_fmapi_dc_remove_reference); + RUN_TEST(test_cmd_fmapi_dc_list_tags); + RUN_TEST(test_cmd_memdev_security_receive); + RUN_TEST(test_cmd_vendor_specific); + + TEST_SUITE("Error Code Handling"); + RUN_TEST(test_error_code_busy); + RUN_TEST(test_error_code_invalid_input); + RUN_TEST(test_error_code_internal); + RUN_TEST(test_error_code_retry); + RUN_TEST(test_error_code_media_disabled); + RUN_TEST(test_error_code_abort); + RUN_TEST(test_error_code_security); + RUN_TEST(test_error_code_passphrase); + RUN_TEST(test_error_code_mailbox_unsupported); + RUN_TEST(test_error_code_payload_length); + RUN_TEST(test_error_code_log); + RUN_TEST(test_error_code_interrupted); + RUN_TEST(test_error_code_fw_in_progress); + RUN_TEST(test_error_code_fw_out_of_order); + RUN_TEST(test_error_code_fw_auth); + RUN_TEST(test_error_code_fw_slot); + RUN_TEST(test_error_code_fw_rollback); + RUN_TEST(test_error_code_fw_reset); + RUN_TEST(test_error_code_invalid_handle); + RUN_TEST(test_error_code_physical_address); + RUN_TEST(test_error_code_poison_limit); + RUN_TEST(test_error_code_media_failure); + RUN_TEST(test_error_code_feature_version); + RUN_TEST(test_error_code_feature_selection); + RUN_TEST(test_error_code_feature_transfer_in_progress); + RUN_TEST(test_error_code_feature_transfer_out_of_order); + RUN_TEST(test_error_code_resource_exhausted); + RUN_TEST(test_error_code_extent_list); + RUN_TEST(test_error_code_transfer_out_of_order); + RUN_TEST(test_error_code_no_bg_abort); + RUN_TEST(test_error_code_background); + + TEST_SUITE("Error Sequence Handling"); + RUN_TEST(test_error_retry_then_success); + RUN_TEST(test_error_busy_multiple_retries); + RUN_TEST(test_error_with_partial_response); + RUN_TEST(test_error_different_commands_different_errors); + + TEST_SUITE("Edge Cases - Variable-Length Responses"); + RUN_TEST(test_edge_get_supported_logs_empty); + RUN_TEST(test_edge_get_supported_logs_multiple); + RUN_TEST(test_edge_get_event_records_empty); + RUN_TEST(test_edge_get_poison_list_empty); + RUN_TEST(test_edge_get_poison_list_multiple); + RUN_TEST(test_edge_get_dc_extent_list_empty); + RUN_TEST(test_edge_fmapi_get_ld_allocations_empty); + RUN_TEST(test_edge_fmapi_get_ld_allocations_multiple); + RUN_TEST(test_edge_fmapi_get_phys_port_state_empty); + RUN_TEST(test_edge_fmapi_get_qos_allocated_bw_empty); + RUN_TEST(test_edge_fmapi_get_qos_bw_limit_empty); + RUN_TEST(test_edge_get_supported_features_empty); + RUN_TEST(test_edge_get_log_cel_empty); + RUN_TEST(test_edge_get_scan_media_results_empty); + + TEST_SUITE("Edge Cases - Boundary Values"); + RUN_TEST(test_edge_identify_max_values); + RUN_TEST(test_edge_timestamp_max_value); + RUN_TEST(test_edge_timestamp_zero_value); + RUN_TEST(test_edge_health_info_critical); + RUN_TEST(test_edge_memdev_identify_max_capacity); + RUN_TEST(test_edge_dc_config_max_regions); + RUN_TEST(test_edge_qos_status_max_values); + RUN_TEST(test_edge_fw_info_all_slots); + RUN_TEST(test_edge_bg_op_status_max_values); + RUN_TEST(test_edge_event_records_overflow); + RUN_TEST(test_edge_poison_list_overflow); + RUN_TEST(test_edge_response_msg_limit_boundaries); + + TEST_SUITE("Request Payload Verification"); + RUN_TEST(test_payload_set_response_msg_limit); + RUN_TEST(test_payload_get_event_records); + RUN_TEST(test_payload_set_event_interrupt_policy); + RUN_TEST(test_payload_clear_event_records); + RUN_TEST(test_payload_set_timestamp); + RUN_TEST(test_payload_activate_fw); + RUN_TEST(test_payload_get_log); + RUN_TEST(test_payload_clear_log); + RUN_TEST(test_payload_get_log_capabilities); + RUN_TEST(test_payload_populate_log); + RUN_TEST(test_payload_get_supported_logs_sublist); + RUN_TEST(test_payload_set_partition_info); + RUN_TEST(test_payload_memdev_get_lsa); + RUN_TEST(test_payload_get_poison_list); + RUN_TEST(test_payload_inject_poison); + RUN_TEST(test_payload_clear_poison); + RUN_TEST(test_payload_get_scan_media_capabilities); + RUN_TEST(test_payload_scan_media); + RUN_TEST(test_payload_set_alert_config); + RUN_TEST(test_payload_set_shutdown_state); + RUN_TEST(test_payload_get_dc_config); + RUN_TEST(test_payload_set_sld_qos_control); + RUN_TEST(test_payload_command_opcode); + RUN_TEST(test_payload_fmapi_phys_port_control); + RUN_TEST(test_payload_fmapi_bind_vppb); + RUN_TEST(test_payload_fmapi_unbind_vppb); + RUN_TEST(test_payload_fmapi_get_ld_allocations); + RUN_TEST(test_payload_fmapi_set_qos_control); + RUN_TEST(test_payload_transfer_fw); + RUN_TEST(test_payload_memdev_set_lsa); + RUN_TEST(test_payload_memdev_add_dc_response); + RUN_TEST(test_payload_memdev_release_dc); + RUN_TEST(test_payload_fmapi_set_ld_allocations); + RUN_TEST(test_payload_fmapi_set_qos_allocated_bw); + RUN_TEST(test_payload_fmapi_set_qos_bw_limit); + RUN_TEST(test_payload_security_send); + RUN_TEST(test_payload_media_operations_sanitize); + RUN_TEST(test_payload_fmapi_get_phys_port_state); + RUN_TEST(test_payload_set_feature); + RUN_TEST(test_payload_get_feature); + RUN_TEST(test_payload_memdev_set_passphrase); + RUN_TEST(test_payload_memdev_disable_passphrase); + RUN_TEST(test_payload_memdev_unlock); + RUN_TEST(test_payload_memdev_passphrase_secure_erase); + RUN_TEST(test_payload_memdev_get_dc_extent_list); + RUN_TEST(test_payload_fmapi_get_qos_allocated_bw); + RUN_TEST(test_payload_fmapi_get_qos_bw_limit); + RUN_TEST(test_payload_fmapi_initiate_dc_add); + RUN_TEST(test_payload_fmapi_initiate_dc_release); + RUN_TEST(test_payload_fmapi_send_ld_cxlio_mem_request); + RUN_TEST(test_payload_event_notification); + RUN_TEST(test_payload_set_mctp_event_interrupt_policy); + RUN_TEST(test_payload_get_log_cel); + RUN_TEST(test_payload_get_supported_features); + RUN_TEST(test_payload_memdev_media_operations_discovery); + RUN_TEST(test_payload_memdev_security_receive); + RUN_TEST(test_payload_fmapi_get_multiheaded_info); + RUN_TEST(test_payload_fmapi_get_head_info); + RUN_TEST(test_payload_fmapi_send_ppb_cxlio_config_request); + RUN_TEST(test_payload_fmapi_send_ld_cxlio_config_request); + RUN_TEST(test_payload_fmapi_get_domain_validation_sv); + RUN_TEST(test_payload_fmapi_set_domain_validation_sv); + RUN_TEST(test_payload_fmapi_get_vcs_domain_validation_sv_state); + RUN_TEST(test_payload_fmapi_get_dc_reg_config); + RUN_TEST(test_payload_fmapi_set_dc_region_config); + RUN_TEST(test_payload_fmapi_get_dc_region_ext_list); + RUN_TEST(test_payload_fmapi_dc_add_reference); + RUN_TEST(test_payload_fmapi_dc_remove_reference); + RUN_TEST(test_payload_fmapi_dc_list_tags); + RUN_TEST(test_payload_vendor_specific); + + TEST_SUITE("Response Payload Verification"); + RUN_TEST(test_response_identify); + RUN_TEST(test_response_bg_op_status); + RUN_TEST(test_response_get_timestamp); + RUN_TEST(test_response_memdev_identify); + RUN_TEST(test_response_memdev_get_partition_info); + RUN_TEST(test_response_memdev_get_health_info); + RUN_TEST(test_response_memdev_get_alert_config); + RUN_TEST(test_response_fmapi_identify_sw_device); + RUN_TEST(test_response_fmapi_get_ld_info); + RUN_TEST(test_response_fmapi_get_qos_status); + RUN_TEST(test_response_get_fw_info); + RUN_TEST(test_response_memdev_get_poison_list); + RUN_TEST(test_response_get_event_records); + RUN_TEST(test_response_get_supported_logs); + RUN_TEST(test_response_get_response_msg_limit); + RUN_TEST(test_response_memdev_get_shutdown_state); + RUN_TEST(test_response_memdev_get_security_state); + RUN_TEST(test_response_memdev_get_sld_qos_control); + RUN_TEST(test_response_memdev_get_sld_qos_status); + RUN_TEST(test_response_fmapi_get_qos_control); + RUN_TEST(test_response_get_scan_media_capabilities); + RUN_TEST(test_response_get_scan_media_results); + RUN_TEST(test_response_memdev_get_dc_config); + RUN_TEST(test_response_fmapi_get_ld_allocations); + RUN_TEST(test_response_fmapi_get_qos_allocated_bw); + RUN_TEST(test_response_fmapi_get_qos_bw_limit); + RUN_TEST(test_response_get_event_interrupt_policy); + RUN_TEST(test_response_get_mctp_event_interrupt_policy); + RUN_TEST(test_response_get_log_capabilities); + RUN_TEST(test_response_get_supported_logs_sublist); + RUN_TEST(test_response_get_supported_features); + RUN_TEST(test_response_get_feature); + RUN_TEST(test_response_memdev_get_dc_extent_list); + RUN_TEST(test_response_fmapi_get_phys_port_state); + RUN_TEST(test_response_fmapi_get_dcd_info); + RUN_TEST(test_response_fmapi_get_multiheaded_info); + RUN_TEST(test_response_fmapi_get_head_info); + RUN_TEST(test_response_fmapi_get_dc_reg_config); + RUN_TEST(test_response_fmapi_get_dc_region_ext_list); + RUN_TEST(test_response_fmapi_dc_list_tags); + RUN_TEST(test_response_memdev_media_operations_discovery); + RUN_TEST(test_response_fmapi_get_domain_validation_sv_state); + RUN_TEST(test_response_fmapi_get_vcs_domain_validation_sv_state); + RUN_TEST(test_response_fmapi_get_domain_validation_sv); + RUN_TEST(test_response_fmapi_send_ppb_cxlio_config_request); + RUN_TEST(test_response_fmapi_send_ld_cxlio_config_request); + RUN_TEST(test_response_fmapi_send_ld_cxlio_mem_request); + RUN_TEST(test_response_fmapi_set_ld_allocations); + RUN_TEST(test_response_fmapi_set_qos_control); + RUN_TEST(test_response_fmapi_set_qos_allocated_bw); + RUN_TEST(test_response_fmapi_set_qos_bw_limit); + RUN_TEST(test_response_get_log_cel); + + TEST_SUITE("Endianness Verification"); + RUN_TEST(test_endian_identify_16bit); + RUN_TEST(test_endian_identify_64bit); + RUN_TEST(test_endian_set_timestamp_request); + RUN_TEST(test_endian_get_timestamp_response); + RUN_TEST(test_endian_get_log_request); + RUN_TEST(test_endian_memdev_identify_capacity); + RUN_TEST(test_endian_bg_op_status); + RUN_TEST(test_endian_event_records_timestamps); + RUN_TEST(test_endian_inject_poison_request); + RUN_TEST(test_endian_supported_logs_size); + RUN_TEST(test_endian_set_feature_request); + RUN_TEST(test_endian_partition_info); + RUN_TEST(test_endian_health_info); + RUN_TEST(test_endian_get_alert_config); + RUN_TEST(test_endian_set_alert_config_request); + RUN_TEST(test_endian_fmapi_get_dcd_info); + RUN_TEST(test_endian_get_poison_list_request); + RUN_TEST(test_endian_get_poison_list_response); + RUN_TEST(test_endian_scan_media_request); + RUN_TEST(test_endian_get_scan_media_results); + RUN_TEST(test_endian_get_supported_features_request); + RUN_TEST(test_endian_get_supported_features_response); + RUN_TEST(test_endian_memdev_get_dc_config); + RUN_TEST(test_endian_get_dc_extent_list_request); + RUN_TEST(test_endian_get_dc_extent_list_response); + RUN_TEST(test_endian_get_event_records); + RUN_TEST(test_endian_fmapi_get_dc_region_ext_list_request); + RUN_TEST(test_endian_fmapi_get_dc_region_ext_list_response); + RUN_TEST(test_endian_fmapi_get_dc_reg_config_request); + RUN_TEST(test_endian_fmapi_get_dc_reg_config_response); + RUN_TEST(test_endian_fmapi_initiate_dc_add_request); + RUN_TEST(test_endian_fmapi_initiate_dc_release_request); + RUN_TEST(test_endian_get_log_cel); + RUN_TEST(test_endian_fmapi_dc_list_tags_request); + RUN_TEST(test_endian_fmapi_dc_list_tags_response); + RUN_TEST(test_endian_get_scan_media_capabilities_request); + RUN_TEST(test_endian_get_scan_media_capabilities_response); + RUN_TEST(test_endian_fmapi_set_ld_allocations_request); + RUN_TEST(test_endian_fmapi_set_ld_allocations_response); + + printf("\n==========================================================\n"); + printf("Results: %d passed, %d failed, %d total\n", + tests_passed, tests_failed, tests_run); + printf("==========================================================\n"); + + return tests_failed > 0 ? 1 : 0; +} From b89364d066e93d3c3b0a5897f78b2bdd785e4647 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Sun, 14 Dec 2025 17:06:02 -0800 Subject: [PATCH 07/10] lib: fix typos Fix typos throughout the code base found by codespell CI. Signed-off-by: Davidlohr Bueso --- examples/cxl-mctp.c | 4 ++-- examples/fmapi-mctp.c | 4 ++-- src/cxlmi/cxlmi.c | 12 ++++++------ src/libcxlmi.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/cxl-mctp.c b/examples/cxl-mctp.c index 44dde48..57d54bd 100644 --- a/examples/cxl-mctp.c +++ b/examples/cxl-mctp.c @@ -180,7 +180,7 @@ static int show_device_info(struct cxlmi_endpoint *ep) if (rc) return rc; - printf("Fimware info:\n"); + printf("Firmware info:\n"); printf("\tslots supported: %d\n", fw_info.slots_supported); printf("\trevision: %s\n", fw_info.fw_rev1); @@ -1033,7 +1033,7 @@ int main(int argc, char **argv) goto exit_free_ctx; } } else { - fprintf(stderr, "must provide MCTP endpoint nid:eid touple\n"); + fprintf(stderr, "must provide MCTP endpoint nid:eid tuple\n"); goto exit_free_ctx; } diff --git a/examples/fmapi-mctp.c b/examples/fmapi-mctp.c index 2e88f6a..2bff2af 100644 --- a/examples/fmapi-mctp.c +++ b/examples/fmapi-mctp.c @@ -20,7 +20,7 @@ static char *DAX_DEVICE_CMDS[] = { }; /* - * Prompts user for the number of extents they want to add/relase with a max. + * Prompts user for the number of extents they want to add/release with a max. * of 10. Prompts for each start/end DPA in MiB, keeping track of each one in * ext_list. * Returns the number of extents in ext_list. If user specifies invalid @@ -453,7 +453,7 @@ int main(int argc, char **argv) goto exit_free_ctx; } } else { - fprintf(stderr, "must provide MCTP endpoint nid:eid touple\n"); + fprintf(stderr, "must provide MCTP endpoint nid:eid tuple\n"); rc = -1; goto exit_free_ctx; } diff --git a/src/cxlmi/cxlmi.c b/src/cxlmi/cxlmi.c index 3452044..8e3a173 100644 --- a/src/cxlmi/cxlmi.c +++ b/src/cxlmi/cxlmi.c @@ -721,7 +721,7 @@ static int send_mctp_tunnel2(struct cxlmi_endpoint *ep, if (inner_t_rsp->length != len) { cxlmi_msg(ep->ctx, LOG_ERR, - "Tunnel lenght not consistent with received length\n"); + "Tunnel length not consistent with received length\n"); rc = -1; goto free_outer_rsp; } @@ -870,7 +870,7 @@ send_ioctl_tunnel1(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, rc = sanity_check_mctp_rsp(ep, t_req->message, t_rsp->message, len, len_max == len_min, len_min); if (rc) { - cxlmi_msg(ep->ctx, LOG_ERR, "Inner tunnel repsonse failed\n"); + cxlmi_msg(ep->ctx, LOG_ERR, "Inner tunnel response failed\n"); goto free_tunnel_rsp; } @@ -891,7 +891,7 @@ send_ioctl_tunnel1(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, /* * 2 level tunnel - so there are two tunnel_command_req, tunnel_comamnd_rsp - * burried in an ioctl message + * buried in an ioctl message */ static int send_ioctl_tunnel2(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, @@ -980,7 +980,7 @@ send_ioctl_tunnel2(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, } len = cmd.out.size; - /* Check overal message size */ + /* Check overall message size */ if (len < len_min) { cxlmi_msg(ep->ctx, LOG_ERR, "IOCTL output too small %d < %ld\n", len, len_min); @@ -1031,7 +1031,7 @@ send_ioctl_tunnel2(struct cxlmi_endpoint *ep, struct cxlmi_tunnel_info *ti, rc = sanity_check_mctp_rsp(ep, inner_t_req->message, inner_t_rsp->message, len, len_max == len_min, len_min); if (rc) { - cxlmi_msg(ep->ctx, LOG_ERR, "Inner tunnel repsonse failed\n"); + cxlmi_msg(ep->ctx, LOG_ERR, "Inner tunnel response failed\n"); goto free_tunnel_rsp; } extract_rsp_msg_from_tunnel(inner_rsp, rsp_msg, rsp_msg_sz); @@ -1663,7 +1663,7 @@ static const char *const cxlmi_cmd_retcode_tbl[] = { [CXLMI_RET_MBUNSUPPORTED] = "unsupported on the mailbox it was issued on", [CXLMI_RET_PAYLOADLEN] = "invalid payload length", [CXLMI_RET_LOG] = "invalid or unsupported log page", - [CXLMI_RET_INTERRUPTED] = "asynchronous event occured", + [CXLMI_RET_INTERRUPTED] = "asynchronous event occurred", [CXLMI_RET_FEATUREVERSION] = "unsupported feature version", [CXLMI_RET_FEATURESELVALUE] = "unsupported feature selection value", [CXLMI_RET_FEATURETRANSFERIP] = "feature transfer in progress", diff --git a/src/libcxlmi.h b/src/libcxlmi.h index b212c95..35b09eb 100644 --- a/src/libcxlmi.h +++ b/src/libcxlmi.h @@ -389,7 +389,7 @@ struct cxlmi_tunnel_info { * Tunneling Commands to an LD in an MLD through a CXL Switch. * * @name: tunnel variable name - * @port: switch downstream port number (outter tunnel) + * @port: switch downstream port number (outer tunnel) * @ld: Logical Device (LD) id within an MLD (inner tunnel) */ #define DEFINE_CXLMI_TUNNEL_SWITCH_MLD(name, port, ld) \ From a1a5cd980470a5d77cc44295c780718858b575ba Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Mon, 29 Dec 2025 16:21:02 -0800 Subject: [PATCH 08/10] lib: allow flexible length tunnel response check The response length varies based on command success/failure. Signed-off-by: Davidlohr Bueso --- src/cxlmi/cxlmi.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/cxlmi/cxlmi.c b/src/cxlmi/cxlmi.c index 8e3a173..aaba8eb 100644 --- a/src/cxlmi/cxlmi.c +++ b/src/cxlmi/cxlmi.c @@ -562,8 +562,14 @@ static int send_mctp_tunnel1(struct cxlmi_endpoint *ep, len = recvfrom(mctp->fmapi_sd, t_rsp_msg, t_rsp_msg_sz, 0, (struct sockaddr *)&addrrx, &addrlen); + /* + * For the outer tunnel response, don't use fixed-length check. + * The inner response length varies depending on whether the tunneled + * command succeeded or failed (error responses have no payload). + * Only check minimum length for the outer CCI + tunnel header. + */ rc = sanity_check_mctp_rsp(ep, t_req_msg, t_rsp_msg, len, - len_min == len_max, len_min); + false, sizeof(*t_rsp_msg) + sizeof(*t_rsp)); if (rc) goto free_rsp; @@ -676,14 +682,14 @@ static int send_mctp_tunnel2(struct cxlmi_endpoint *ep, len = recvfrom(mctp->fmapi_sd, outer_rsp, outer_rsp_sz, 0, (struct sockaddr *)&addrrx, &addrlen); - if (len < len_min) { - cxlmi_msg(ep->ctx, LOG_ERR, "Not enough data in reply\n"); - rc = -1 ; - goto free_outer_rsp; - } + /* + * For the outer tunnel response, don't use fixed-length check. + * The inner response length varies depending on whether the tunneled + * command succeeded or failed (error responses have no payload). + */ rc = sanity_check_mctp_rsp(ep, outer_req, outer_rsp, len, - len_min == len_max, len_min); + false, sizeof(*outer_rsp) + sizeof(*outer_t_rsp)); if (rc) goto free_outer_rsp; @@ -701,9 +707,13 @@ static int send_mctp_tunnel2(struct cxlmi_endpoint *ep, goto free_outer_rsp; } + /* + * For the inner (first-level) tunnel, also don't use fixed-length check. + * The innermost response length varies based on command success/failure. + */ rc = sanity_check_mctp_rsp(ep, outer_t_req->message, outer_t_rsp->message, len, - len_min == len_max, len_min); + false, sizeof(*inner_rsp) + sizeof(*inner_t_rsp)); if (rc) goto free_outer_rsp; From f5dfb61a48d09a786640493934c1e2404b2860e9 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Mon, 29 Dec 2025 16:40:53 -0800 Subject: [PATCH 09/10] examples: silence clang static analyzer runs Miscellaneous fixlets to silence the tool in the CI. ... also for mock device tests. Signed-off-by: Davidlohr Bueso --- examples/cxl-ioctl.c | 20 ++++----- examples/cxl-mctp.c | 13 +++--- examples/examples.h | 13 +++--- examples/fmapi-mctp.c | 11 ++++- tests/mock-tests.c | 94 ++++++++++++++++++++++++++++++++----------- 5 files changed, 104 insertions(+), 47 deletions(-) diff --git a/examples/cxl-ioctl.c b/examples/cxl-ioctl.c index 444e06d..1f26a36 100644 --- a/examples/cxl-ioctl.c +++ b/examples/cxl-ioctl.c @@ -500,25 +500,25 @@ int main(int argc, char **argv) printf("ep '%s'\n", argv[1]); /* yes, only 1 endpoint, but might add more */ - rc = show_some_info_from_all_devices(ctx); + (void)show_some_info_from_all_devices(ctx); - rc = play_with_device_timestamp(ep); + (void)play_with_device_timestamp(ep); - rc = play_with_poison_mgmt(ep); + (void)play_with_poison_mgmt(ep); - rc = get_device_logs(ep); + (void)get_device_logs(ep); - rc = get_log_capabilities(ep); + (void)get_log_capabilities(ep); - rc = clear_log(ep); + (void)clear_log(ep); - rc = populate_log(ep); + (void)populate_log(ep); - rc = get_alert_config(ep); + (void)get_alert_config(ep); - rc = set_alert_config(ep); + (void)set_alert_config(ep); - rc = toggle_abort(ep); + (void)toggle_abort(ep); rc = play_with_scan_media(ep); diff --git a/examples/cxl-mctp.c b/examples/cxl-mctp.c index 57d54bd..145a5d0 100644 --- a/examples/cxl-mctp.c +++ b/examples/cxl-mctp.c @@ -759,6 +759,7 @@ static int test_fmapi_initiate_dc_add_and_release(struct cxlmi_endpoint *ep) printf("Show Extents --\n"); if (print_ext_list(ep, 0, 2, 0)) { rc = -1; + goto cleanup; } rls_req = calloc(1, sizeof(*rls_req) + 2 * sizeof(rls_req->extents[0])); @@ -835,7 +836,7 @@ static int test_fmapi_initiate_dc_add_and_release(struct cxlmi_endpoint *ep) rls_req->extents[0].start_dpa = 0; rls_req->extents[0].len = 128 * MiB; - rc = cxlmi_cmd_fmapi_initiate_dc_release(ep, NULL, rls_req); + (void)cxlmi_cmd_fmapi_initiate_dc_release(ep, NULL, rls_req); /* Extent list should be empty */ while ((curr_num_extents = get_num_dc_extents(ep)) == 1); @@ -1038,15 +1039,15 @@ int main(int argc, char **argv) } cxlmi_for_each_endpoint_safe(ctx, ep, tmp) { - rc = show_device_info(ep); + (void)show_device_info(ep); - rc = play_with_device_timestamp(ep); + (void)play_with_device_timestamp(ep); - rc = get_device_logs(ep); + (void)get_device_logs(ep); - rc = play_with_poison_mgmt(ep); + (void)play_with_poison_mgmt(ep); - rc = toggle_abort(ep); + (void)toggle_abort(ep); if (ep_supports_op(ep, 0x4800)) play_with_dcd(ep); diff --git a/examples/examples.h b/examples/examples.h index 045e8cd..771b69d 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -135,17 +135,20 @@ static inline bool ep_supports_op(struct cxlmi_endpoint *ep, uint16_t opcode) return op_support; rc = cxlmi_cmd_get_supported_logs(ep, NULL, gsl); - if (rc) + if (rc) { + free(gsl); return op_support; + } rc = parse_supported_logs(gsl, &cel_size); - if (rc) + if (rc) { + free(gsl); return op_support; - else { - /* we know there is a CEL */ - rc = support_opcode(ep, cel_size, opcode, &op_support); } + /* we know there is a CEL */ + (void)support_opcode(ep, cel_size, opcode, &op_support); + free(gsl); return op_support; } diff --git a/examples/fmapi-mctp.c b/examples/fmapi-mctp.c index 2bff2af..3cf67eb 100644 --- a/examples/fmapi-mctp.c +++ b/examples/fmapi-mctp.c @@ -367,7 +367,7 @@ static bool input_is_yes(char* buf, int *rc) static int create_dax_device(void) { char buf[MAX_CHARS] = {0}; char** argv = NULL; - int i, j, rc, argc; + int i, j, rc = 0, argc; printf("Create DAX Device for this region? [y/n] "); if (input_is_yes(buf, &rc)) { @@ -412,14 +412,21 @@ int main(int argc, char **argv) { struct cxlmi_ctx *ctx; struct cxlmi_endpoint *ep, *tmp; - extent *ext_list = calloc(MAX_EXTENTS, sizeof(extent)); + extent *ext_list; char buf[MAX_CHARS]; uint8_t cmd; int rc = 0, num_extents; + ext_list = calloc(MAX_EXTENTS, sizeof(extent)); + if (!ext_list) { + fprintf(stderr, "cannot allocate extent list\n"); + return EXIT_FAILURE; + } + ctx = cxlmi_new_ctx(stdout, DEFAULT_LOGLEVEL); if (!ctx) { fprintf(stderr, "cannot create new context object\n"); + free(ext_list); return EXIT_FAILURE; } diff --git a/tests/mock-tests.c b/tests/mock-tests.c index 45a5ae3..236e93a 100644 --- a/tests/mock-tests.c +++ b/tests/mock-tests.c @@ -487,17 +487,14 @@ static int test_cmd_get_supported_logs(void) { struct cxlmi_cmd_get_supported_logs_rsp *rsp, *ret; size_t struct_sz; - int rc; + int rc, result = 1; /* Allocate space for header + 2 entries (flexible array member) */ struct_sz = sizeof(*rsp) + 2 * sizeof(rsp->entries[0]); rsp = calloc(1, struct_sz); ret = calloc(1, struct_sz); - if (!rsp || !ret) { - free(rsp); - free(ret); - return 1; - } + if (!rsp || !ret) + goto cleanup; rsp->num_supported_log_entries = 2; /* Fill in some test UUIDs */ @@ -506,19 +503,37 @@ static int test_cmd_get_supported_logs(void) memset(rsp->entries[1].uuid, 0xBB, sizeof(rsp->entries[1].uuid)); rsp->entries[1].log_size = 0x200; - ASSERT_EQ(setup(), 0, "setup failed"); + if (setup() != 0) { + fprintf(stderr, "\n ASSERT FAILED: setup failed\n"); + goto cleanup; + } cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, rsp, struct_sz); rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); teardown(); - ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); - ASSERT_EQ(ret->num_supported_log_entries, 2, "num entries mismatch"); - ASSERT_EQ(ret->entries[0].log_size, 0x100, "entry 0 log_size mismatch"); - ASSERT_EQ(ret->entries[1].log_size, 0x200, "entry 1 log_size mismatch"); + if (rc != CXLMI_RET_SUCCESS) { + fprintf(stderr, "\n ASSERT FAILED: command failed (got %d, expected %d)\n", + rc, CXLMI_RET_SUCCESS); + goto cleanup; + } + if (ret->num_supported_log_entries != 2) { + fprintf(stderr, "\n ASSERT FAILED: num entries mismatch\n"); + goto cleanup; + } + if (ret->entries[0].log_size != 0x100) { + fprintf(stderr, "\n ASSERT FAILED: entry 0 log_size mismatch\n"); + goto cleanup; + } + if (ret->entries[1].log_size != 0x200) { + fprintf(stderr, "\n ASSERT FAILED: entry 1 log_size mismatch\n"); + goto cleanup; + } + result = 0; +cleanup: free(rsp); free(ret); - return 0; + return result; } static int test_cmd_get_log_capabilities(void) @@ -1133,7 +1148,7 @@ static int test_cmd_get_log_cel(void) struct cxlmi_cmd_get_log_req req = {0}; struct cxlmi_cmd_get_log_cel_rsp rsp = {0}; struct cxlmi_cmd_get_log_cel_rsp *ret; - int rc; + int rc, result = 1; rsp.opcode = 0x0001; /* Identify */ rsp.command_effect = 0x0000; @@ -1142,17 +1157,30 @@ static int test_cmd_get_log_cel(void) req.length = sizeof(rsp); ret = calloc(1, sizeof(*ret) + req.length); - ASSERT_TRUE(ret != NULL, "allocation failed"); + if (!ret) { + fprintf(stderr, "\n ASSERT FAILED: allocation failed\n"); + return 1; + } - ASSERT_EQ(setup(), 0, "setup failed"); + if (setup() != 0) { + fprintf(stderr, "\n ASSERT FAILED: setup failed\n"); + goto cleanup; + } cxlmi_mock_set_response(test_ep, 0x04, 0x01, CXLMI_RET_SUCCESS, &rsp, sizeof(rsp)); rc = cxlmi_cmd_get_log_cel(test_ep, NULL, &req, ret); teardown(); - ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); + if (rc != CXLMI_RET_SUCCESS) { + fprintf(stderr, "\n ASSERT FAILED: command failed (got %d, expected %d)\n", + rc, CXLMI_RET_SUCCESS); + goto cleanup; + } + + result = 0; +cleanup: free(ret); - return 0; + return result; } static int test_cmd_get_supported_logs_sublist(void) @@ -7884,7 +7912,7 @@ static int test_endian_supported_logs_size(void) uint8_t wire_rsp[8 + 20] = {0}; /* header + 1 entry */ struct cxlmi_cmd_get_supported_logs_rsp *ret; size_t ret_sz; - int rc; + int rc, result = 1; /* num_supported_log_entries: 1 */ wire_rsp[0] = 0x01; wire_rsp[1] = 0x00; @@ -7896,20 +7924,38 @@ static int test_endian_supported_logs_size(void) ret_sz = sizeof(*ret) + sizeof(ret->entries[0]); ret = calloc(1, ret_sz); - ASSERT_TRUE(ret != NULL, "alloc failed"); + if (!ret) { + fprintf(stderr, "\n ASSERT FAILED: alloc failed\n"); + return 1; + } - ASSERT_EQ(setup(), 0, "setup failed"); + if (setup() != 0) { + fprintf(stderr, "\n ASSERT FAILED: setup failed\n"); + goto cleanup; + } cxlmi_mock_set_response(test_ep, 0x04, 0x00, CXLMI_RET_SUCCESS, wire_rsp, sizeof(wire_rsp)); rc = cxlmi_cmd_get_supported_logs(test_ep, NULL, ret); teardown(); - ASSERT_EQ(rc, CXLMI_RET_SUCCESS, "command failed"); - ASSERT_EQ(ret->num_supported_log_entries, 1, "num entries wrong"); - ASSERT_EQ(ret->entries[0].log_size, 0xAABBCCDD, "log_size endianness wrong"); + if (rc != CXLMI_RET_SUCCESS) { + fprintf(stderr, "\n ASSERT FAILED: command failed (got %d, expected %d)\n", + rc, CXLMI_RET_SUCCESS); + goto cleanup; + } + if (ret->num_supported_log_entries != 1) { + fprintf(stderr, "\n ASSERT FAILED: num entries wrong\n"); + goto cleanup; + } + if (ret->entries[0].log_size != 0xAABBCCDD) { + fprintf(stderr, "\n ASSERT FAILED: log_size endianness wrong\n"); + goto cleanup; + } + result = 0; +cleanup: free(ret); - return 0; + return result; } /* From 07c49a0fa3921601a3d63806c887644a3a2490c5 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Tue, 30 Dec 2025 10:46:08 -0800 Subject: [PATCH 10/10] tests: add hardware unit tests and QEMU test infrastructure Add unit tests for CXL command sets that can run against real hardware or QEMU-emulated devices: - cxl-test-generic: Tests 27 generic CXL commands (identify, events, firmware, logs, features, timestamp) - cxl-test-memdev: Tests 35 memory device commands (health, LSA, poison, sanitize, DCD, security) - cxl-test-fmapi: Tests 34 FM-API commands (physical/virtual switch, MLD port, multi-headed device, DCD) All tests support both ioctl (/dev/cxl/memX) and MCTP transports. Common test infrastructure is shared via tests/test-common.h. Add QEMU test scripts for automated testing: - scripts/qemu-cxl-test.sh: Builds libcxlmi and runs tests in QEMU with emulated CXL Type3 devices (volatile + persistent) - scripts/qemu-mctp-test.sh: Builds libcxlmi and runs tests in QEMU with emulated CXL Type3 device under a switch (FM-owned LD). - scripts/qemu-init.c: Minimal init that discovers CXL devices and runs the test suite Update docs/Testing.md with comprehensive documentation covering bare metal testing, QEMU testing, and both transport options. Generated-by: Claude AI Signed-off-by: Davidlohr Bueso --- docs/Testing.md | 750 ++++++++++++++++++- scripts/qemu-cxl-test.sh | 163 ++++ scripts/qemu-init.c | 649 ++++++++++++++++ scripts/qemu-mctp-test.sh | 333 +++++++++ tests/cxl-test-fmapi.c | 1495 +++++++++++++++++++++++++++++++++++++ tests/cxl-test-generic.c | 1125 ++++++++++++++++++++++++++++ tests/cxl-test-memdev.c | 1268 +++++++++++++++++++++++++++++++ tests/meson.build | 21 + tests/test-common.h | 354 +++++++++ 9 files changed, 6155 insertions(+), 3 deletions(-) create mode 100755 scripts/qemu-cxl-test.sh create mode 100644 scripts/qemu-init.c create mode 100755 scripts/qemu-mctp-test.sh create mode 100644 tests/cxl-test-fmapi.c create mode 100644 tests/cxl-test-generic.c create mode 100644 tests/cxl-test-memdev.c create mode 100644 tests/test-common.h diff --git a/docs/Testing.md b/docs/Testing.md index a0064ba..a1aa2ce 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,10 +1,754 @@ -# Mock Device Testing Infrastructure +# Testing Infrastructure + +libcxlmi provides a comprehensive testing infrastructure that supports multiple +testing approaches: + +- **Mock Transport Tests** - Unit tests using a simulated device (no hardware required) +- **Hardware Unit Tests** - Command set tests against real CXL devices or QEMU emulation +- **QEMU Unit Tests (ioctl)** - Automated testing using QEMU CXL Type3 device emulation via kernel ioctl +- **QEMU Unit Tests (MCTP)** - Automated testing using QEMU CXL switch topology with MCTP over I2C transport + +This document covers all testing approaches and how to use them effectively. + +--- + +## Table of Contents + +1. [Hardware Unit Tests](#hardware-unit-tests) + - [Overview](#overview) + - [Test Programs](#test-programs) + - [Transport Options](#transport-options) + - [Running on Bare Metal](#running-on-bare-metal) + - [Running with QEMU (ioctl)](#running-with-qemu) + - [Running with QEMU (MCTP)](#running-with-qemu-mctp) +2. [Mock Device Testing](#mock-device-testing-infrastructure) +3. [Code Coverage](#code-coverage) +4. [Adding New Tests](#adding-new-tests) + +--- + +## Hardware Unit Tests + +### Overview + +The hardware unit tests exercise CXL commands against real devices (or QEMU-emulated +devices). These tests verify that the library correctly communicates with actual +CXL hardware using the proper command encoding, transport handling, and response +parsing. + +Three test programs cover the major CXL command sets: + +| Test Program | Command Set | CXL Spec Reference | +|--------------|-------------|-------------------| +| `cxl-test-generic` | Generic Component Commands | CXL r3.1 Section 8.2.9.1 | +| `cxl-test-memdev` | Memory Device Commands | CXL r3.1 Section 8.2.9.9 | +| `cxl-test-fmapi` | FM-API Commands | CXL r3.1 Section 7.6 | + +### Test Programs + +#### Generic Command Set Tests (`cxl-test-generic`) + +Tests 27 generic CXL commands including: + +- **Device Info**: identify, background operation status, response message limit +- **Events**: get/clear event records, event interrupt policy, event notification +- **Firmware**: get FW info, transfer FW, activate FW +- **Logs**: get supported logs, get log, CEL, log capabilities, clear/populate log +- **Features**: get supported features, get/set feature +- **Timestamp**: get/set timestamp + +#### Memory Device Command Set Tests (`cxl-test-memdev`) + +Tests 35 memory device commands including: + +- **Identity/Capacity**: identify, partition info +- **Label Storage Area**: get/set LSA +- **Health**: health info, alert config, shutdown state +- **Media**: poison list, inject/clear poison, scan media +- **Sanitize**: sanitize, secure erase +- **Dynamic Capacity**: DCD config, extent list, add/release DC +- **Security**: get security state, passphrase operations, freeze security + +#### FM-API Command Set Tests (`cxl-test-fmapi`) + +Tests 34 Fabric Manager API commands including: + +- **Physical Switch**: identify switch, physical port state/control +- **Virtual Switch**: get/bind/unbind vPPB +- **MLD Port**: tunnel management command, MLD port operations +- **Multi-headed Device**: get MHD info +- **Dynamic Capacity**: DCD info, region config, DC operations + +### Transport Options + +All test programs support two transport mechanisms: + +#### Tunneling Options + +All test programs support tunneling options for sending commands through CXL +switches to downstream devices or to specific Logical Devices (LDs) within +Multi-Logical Devices (MLDs): + +| Option | Description | Tunnel Type | +|--------|-------------|-------------| +| (none) | Direct command to target device | No tunneling | +| `-p ` | Tunnel through switch to FM-owned LD | Level 1 (`DEFINE_CXLMI_TUNNEL_SWITCH`) | +| `-l ` | Tunnel to specific LD in MLD | Level 1 (`DEFINE_CXLMI_TUNNEL_MLD`) | +| `-p -l ` | Tunnel through switch to LD in MLD | Level 2 (`DEFINE_CXLMI_TUNNEL_SWITCH_MLD`) | +| `-m` | Tunnel to MHD LD Pool CCI | Level 1 (`DEFINE_CXLMI_TUNNEL_MHD`) | + +**Examples:** + +```bash +# Direct command to switch (no tunneling) +./build/tests/cxl-test-fmapi mctp:11,8 + +# Tunnel through switch port 0 to downstream device (FM-owned LD) +./build/tests/cxl-test-generic mctp:11,8 -p 0 + +# Tunnel to LD 1 in an MLD +./build/tests/cxl-test-memdev mctp:11,9 -l 1 + +# Two-level tunnel: through switch port 0 to LD 1 in downstream MLD +./build/tests/cxl-test-memdev mctp:11,8 -p 0 -l 1 + +# Tunnel to MHD LD Pool CCI +./build/tests/cxl-test-fmapi mctp:11,8 -m +``` + +**Notes:** +- When no tunneling options are specified, commands are sent directly to the target +- The tunneling level is automatically determined by the combination of options +- FM-API switch commands (like `identify_sw_device`) should typically run without + tunneling against the switch CCI directly +- MLD component commands require tunneling to reach the MLD device + +#### ioctl Transport (Kernel Interface) + +Uses the Linux kernel's CXL driver interface (`/dev/cxl/memX`). This is the +standard method for communicating with CXL devices on Linux. + +```bash +# Run against a memory device +./build/tests/cxl-test-generic mem0 +./build/tests/cxl-test-memdev mem0 +``` + +**Requirements:** +- Linux kernel with CXL support (`CONFIG_CXL_BUS`, `CONFIG_CXL_PCI`, `CONFIG_CXL_MEM`) +- Raw commands enabled (`CONFIG_CXL_MEM_RAW_COMMANDS=y`) +- Read/write access to `/dev/cxl/memX` + +#### MCTP Transport (Management Component Transport Protocol) + +Uses MCTP over PCIe VDM or SMBus for out-of-band management. This enables +communication with CXL devices without kernel driver involvement. + +```bash +# Run against an MCTP endpoint +./build/tests/cxl-test-generic mctp:1,0x1a +./build/tests/cxl-test-memdev mctp:1,0x50 +``` + +**Target format:** `mctp:,` + +**Requirements:** +- MCTP infrastructure (mctp kernel module or user-space daemon) +- Network connectivity to the CXL device's management controller + +### Running on Bare Metal + +For testing against physical CXL hardware: + +1. **Build the test programs:** + +```bash +meson setup build +meson compile -C build +``` + +2. **Identify your CXL devices:** + +```bash +ls /dev/cxl/ +# Output: mem0 mem1 ... +``` + +3. **Run tests against a device:** + +```bash +# Generic command set +sudo ./build/tests/cxl-test-generic mem0 + +# Memory device command set +sudo ./build/tests/cxl-test-memdev mem0 + +# FM-API (requires FM-capable device or switch) +sudo ./build/tests/cxl-test-fmapi mem0 +``` + +4. **Interpret results:** + +``` +======================================== + libcxlmi Generic Command Set Tests +======================================== +Target: mem0 + [PASS] identify + [PASS] bg_op_status + [SKIP] get_response_msg_limit: not supported + [PASS] get_event_records + ... + +======================================== + Results: 15 passed, 0 failed, 12 skipped +======================================== +``` + +- **PASS**: Command succeeded and returned valid data +- **FAIL**: Command failed unexpectedly +- **SKIP**: Command not supported by device (expected for many optional commands) + +### Running with QEMU + +QEMU provides CXL Type3 device emulation, enabling testing without physical +hardware. This is ideal for development, CI/CD pipelines, and testing edge cases. + +The test configuration includes two CXL Type3 devices with different characteristics: +- **Volatile memory device** (`mem0`) - 256 MB RAM-backed, no LSA, serial 0xDEADBEEF +- **Persistent memory device** (`mem1`) - 512 MB file-backed, 4 KB LSA, serial 0xCAFEBABE + +Tests are run against both devices automatically, allowing verification of behavior +differences between volatile and persistent memory types. + +#### Prerequisites + +1. **QEMU with CXL support** (version 8.0+ or built from source): + +```bash +# Check QEMU version +qemu-system-x86_64 --version + +# Verify CXL device support +qemu-system-x86_64 -device help | grep cxl +``` + +2. **Linux kernel with CXL support:** + +Required kernel config options: +``` +CONFIG_CXL_BUS=y +CONFIG_CXL_PCI=y +CONFIG_CXL_ACPI=y +CONFIG_CXL_MEM=y +CONFIG_CXL_MEM_RAW_COMMANDS=y +``` + +#### Automated QEMU Testing + +The `scripts/qemu-cxl-test.sh` script automates the entire test process: + +```bash +# Basic usage +./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage + +# With custom QEMU binary +QEMU_BIN=~/qemu/build/qemu-system-x86_64 \ + ./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage + +# With custom timeout (default: 60 seconds) +./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage --timeout 120 + +# With verbose output (shows detailed command responses) +./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage --verbose + +# With AddressSanitizer enabled (detects memory errors) +./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage --sanitize + +# Combined options +./scripts/qemu-cxl-test.sh --kernel /path/to/bzImage -v -s +``` + +**Script Options:** + +| Option | Short | Description | +|--------|-------|-------------| +| `--kernel ` | `-k` | Path to kernel bzImage (required) | +| `--qemu ` | `-q` | Path to QEMU binary | +| `--timeout ` | `-t` | Test timeout in seconds (default: 60) | +| `--verbose` | `-v` | Enable verbose output showing command response details | +| `--sanitize` | `-s` | Build with AddressSanitizer to detect memory errors | +| `--help` | `-h` | Show help message | + +**What the script does:** + +1. Creates a minimal initramfs with the test binaries +2. Launches QEMU with a CXL Type3 device configuration +3. Boots the kernel and runs the test suite +4. Reports results and exits with appropriate status + +**Example output:** + +``` +[*] Building libcxlmi... +[*] Creating initramfs... +[*] Starting QEMU... + +======================================== + libcxlmi Generic Command Set Tests +======================================== +Target: mem0 + [PASS] identify + [PASS] bg_op_status + ... + +======================================== + Results: 12 passed, 0 failed, 15 skipped +======================================== + +[*] === TESTS PASSED === +``` + +**Example verbose output (`-v`):** + +``` + [PASS] identify + vendor_id: 0x1b36 + device_id: 0x0001 + serial_number: 0xdeadbeef + total_capacity: 256 MB + volatile_capacity: 256 MB + persistent_capacity: 0 MB + [PASS] bg_op_status + status: 0x00 + opcode: 0x0000 + percent_complete: 0 +``` + +#### AddressSanitizer Testing + +The `--sanitize` option builds the test binaries with AddressSanitizer (ASan), +which detects memory errors at runtime: + +- Buffer overflows (stack and heap) +- Use-after-free errors +- Double-free errors +- Memory leaks (disabled in QEMU tests due to minimal init environment) + +When ASan detects an error, it prints a detailed report with stack traces: + +``` +==123==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x... +READ of size 4 at 0x... thread T0 + #0 0x... in function_name file.c:123 + #1 0x... in caller_function file.c:456 + ... +``` + +This is invaluable for catching memory corruption bugs that might otherwise +go unnoticed or cause intermittent failures. + +#### Manual QEMU Testing + +For more control, you can run QEMU manually: + +```bash +# Create backing files for CXL devices +truncate -s 4K /tmp/cxl-lsa.raw # LSA for persistent device only +truncate -s 512M /tmp/cxl-pmem1.raw # Persistent memory backing + +# Launch QEMU with two CXL Type3 devices (volatile + persistent) +qemu-system-x86_64 \ + -machine q35,accel=kvm,cxl=on \ + -m 2G -smp 2 -cpu host \ + -kernel /path/to/bzImage \ + -append "console=ttyS0 panic=-1" \ + -initrd /path/to/initramfs.cpio.gz \ + -object memory-backend-ram,id=cxl-vmem1,size=256M \ + -object memory-backend-file,id=cxl-pmem1,share=on,mem-path=/tmp/cxl-pmem1.raw,size=512M \ + -object memory-backend-file,id=cxl-lsa,share=on,mem-path=/tmp/cxl-lsa.raw,size=4K \ + -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \ + -device cxl-rp,port=0,bus=cxl.1,id=root_port0,chassis=0,slot=2 \ + -device cxl-rp,port=1,bus=cxl.1,id=root_port1,chassis=0,slot=3 \ + -device cxl-type3,bus=root_port0,volatile-memdev=cxl-vmem1,id=cxl-vmem0,sn=0xDEADBEEF \ + -device cxl-type3,bus=root_port1,persistent-memdev=cxl-pmem1,lsa=cxl-lsa,id=cxl-pmem0,sn=0xCAFEBABE \ + -M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G \ + -nographic -no-reboot +``` + +**QEMU Device Configuration:** + +| Device | Type | Size | Serial Number | LSA | +|--------|------|------|---------------|-----| +| cxl-vmem0 (mem0) | Volatile | 256 MB | 0xDEADBEEF | None | +| cxl-pmem0 (mem1) | Persistent | 512 MB | 0xCAFEBABE | 4 KB | + +Then run tests inside the VM against both devices: + +```bash +# Test volatile memory device +./cxl-test-generic mem0 +./cxl-test-memdev mem0 + +# Test persistent memory device +./cxl-test-generic mem1 +./cxl-test-memdev mem1 +``` + +#### QEMU Device Capabilities + +The QEMU CXL Type3 emulation supports many but not all CXL commands. Commands +that report as "not supported" are typically: + +- Security commands (passphrase, secure erase) +- Advanced media operations (poison injection, scan media) +- Dynamic capacity commands (DCD) +- FM-API commands (unless using switch emulation) + +This is expected behavior—the tests gracefully skip unsupported commands. + +### Running with QEMU (MCTP) + +QEMU also supports MCTP over I2C for out-of-band CXL management testing. This +enables testing FM-API commands against a CXL switch topology with tunneling +support, which is not possible with the ioctl-based approach. + +The MCTP test configuration includes: +- **CXL Switch** with FM-API CCI (upstream port with switch mailbox) +- **Type3 Device 1** - 256 MB, connected to switch downstream port 0 +- **Type3 Device 2** - 512 MB, connected to switch downstream port 3 +- **MCTP over I2C** using the aspeed-i2c controller and i2c_mctp_cxl devices + +#### Prerequisites + +1. **QEMU with CXL and MCTP support** (built from source with aspeed-i2c and + i2c_mctp_cxl device support): + +```bash +# Verify CXL and MCTP device support +qemu-system-x86_64 -device help | grep -E "cxl|mctp" +``` + +2. **Linux kernel with CXL and MCTP support:** + +Required kernel config options: +``` +# CXL support +CONFIG_CXL_BUS=y +CONFIG_CXL_PCI=y +CONFIG_CXL_ACPI=y +CONFIG_CXL_MEM=y +CONFIG_CXL_MEM_RAW_COMMANDS=y + +# MCTP support +CONFIG_MCTP=y +CONFIG_MCTP_TRANSPORT_I2C=y + +# Aspeed I2C controller (for QEMU emulation) +CONFIG_I2C_ASPEED=y +``` + +3. **MCTP tools** (mctpd and mctp utilities from [CodeConstruct](https://github.com/CodeConstruct/mctp)): + +```bash +# Build and install mctp tools +git clone https://github.com/CodeConstruct/mctp.git +cd mctp +meson setup build +meson compile -C build +sudo meson install -C build +``` + +4. **D-Bus** (required for mctpd endpoint assignment): + +```bash +# Debian/Ubuntu +apt install dbus +``` + +#### Automated QEMU MCTP Testing + +The `scripts/qemu-mctp-test.sh` script automates MCTP-based testing: + +```bash +# Basic usage +./scripts/qemu-mctp-test.sh --kernel /path/to/bzImage + +# With custom QEMU binary +QEMU_BIN=~/qemu/build/qemu-system-x86_64 \ + ./scripts/qemu-mctp-test.sh --kernel /path/to/bzImage + +# With verbose output +./scripts/qemu-mctp-test.sh --kernel /path/to/bzImage -v + +# With custom timeout (default: 120 seconds) +./scripts/qemu-mctp-test.sh --kernel /path/to/bzImage --timeout 180 +``` + +**Script Options:** + +| Option | Short | Description | +|--------|-------|-------------| +| `--kernel ` | `-k` | Path to kernel bzImage (required) | +| `--qemu ` | `-q` | Path to QEMU binary | +| `--timeout ` | `-t` | Test timeout in seconds (default: 120) | +| `--verbose` | `-v` | Enable verbose output | +| `--sanitize` | `-s` | Build with AddressSanitizer | +| `--help` | `-h` | Show help message | + +**What the script does:** + +1. Builds libcxlmi with MCTP test support +2. Creates an initramfs with test binaries, mctp tools, mctpd, and dbus +3. Launches QEMU with a CXL switch topology and MCTP I2C devices +4. Inside the VM: + - Waits for the aspeed-i2c MCTP device to appear + - Starts dbus-daemon and mctpd + - Assigns MCTP endpoints to the CXL devices via mctpd + - Runs tests against the MCTP endpoints +5. Reports results and exits with appropriate status + +**Example output:** + +``` +[*] Building libcxlmi... +[*] Creating initramfs... +[*] Found mctp tool +[*] Found mctpd +[*] Starting QEMU with CXL switch topology and MCTP I2C... + +--- generic: mctp:11,8 --- + [PASS] identify + [PASS] get_response_msg_limit + [PASS] get_supported_logs + ... +[PASS] generic: mctp:11,8 + +--- fmapi: mctp:11,8 --- + [PASS] identify_sw_device + [PASS] get_phys_port_state + [SKIP] get_ld_info: not supported + ... + Auto-detected tunnel port: 0 (device type 5) + Auto-detected MLD with 3 LDs +[Tunneling Tests] + [PASS] tunnel_switch_identify + [via port 0] vendor_id: 0x8086 + [via port 0] device_id: 0x0d93 + [PASS] tunnel_mld_identify + [via LD 0] vendor_id: 0x8086 + [PASS] tunnel_switch_mld_identify + [via port 0, LD 0] vendor_id: 0x8086 +[PASS] fmapi: mctp:11,8 + +Tunneling: switch port 0 -> FM-owned LD (level 1) +--- generic: mctp:11,8 --- + [PASS] identify + ... +[PASS] generic: mctp:11,8 (tunneled port 0) + +--- memdev: mctp:11,9 --- + [PASS] identify_memory_device + [PASS] get_health_info + ... +[PASS] memdev: mctp:11,9 + +TEST_RESULT=PASS +[*] === TESTS PASSED === +``` + +The FM-API test includes auto-detection of tunnel targets. When a CXL switch is +detected, the test queries `identify_sw_device` and `get_phys_port_state` to find +downstream ports with connected devices, then automatically runs tunneling tests +through those ports. + +#### MCTP Test Topology + +The QEMU topology creates a CXL switch with downstream Type3 devices accessible +via MCTP over I2C: + +``` + ┌─────────────────────────────────────┐ + │ Aspeed I2C Bus │ + └──────┬──────────┬──────────┬────────┘ + │ │ │ + I2C 0x4 I2C 0x5 I2C 0x6 + │ │ │ + ┌──────┴──────────┴──────────┴────────┐ + │ i2c_mctp_cxl devices │ + └──────┬──────────┬──────────┬────────┘ + │ │ │ + EID 8 EID 9 EID 10 + │ │ │ + ┌──────┴────┐ ┌───┴───┐ ┌───┴───┐ + │ CXL │ │ Type3 │ │ Type3 │ + │ Switch │ │ Dev 1 │ │ Dev 2 │ + │ (FM-API) │ │ 256MB │ │ 512MB │ + └─────┬─────┘ └───────┘ └───────┘ + │ + ┌───────────┼───────────┐ + │ │ │ + Port 0 Port 1 Port 2 + │ │ │ + ┌───┴───┐ (empty) ┌───┴───┐ + │ Type3 │ │ Type3 │ + │ Dev 1 │ │ Dev 2 │ + └───────┘ └───────┘ +``` + +**MCTP Configuration:** + +| Device | I2C Address | MCTP EID | Description | +|--------|-------------|----------|-------------| +| Switch | 0x4 | 8 | CXL Switch with FM-API CCI | +| Type3 #1 | 0x5 | 9 | 256 MB memory device on port 0 | +| Type3 #2 | 0x6 | 10 | 512 MB memory device on port 2 | +| Local | - | 50 | Host MCTP endpoint | + +**MCTP Network:** ID 11 + +#### Tests Executed + +The MCTP test run exercises: + +1. **Switch CCI (EID 8) - Direct** + - Generic command set tests (no tunneling) + - FM-API command set tests (no tunneling) - runs `identify_sw_device`, `get_phys_port_state` + - Auto-detection of downstream devices for tunneling tests + - Tunneling tests through auto-detected ports + +2. **Tunneling through Switch to Downstream Devices** + - Generic commands tunneled through switch port (`-p `) + - Memory device commands tunneled through switch port + - The FM-API test auto-detects downstream ports and runs tunneling tests + +3. **Type3 Devices (EID 9, 10) - Direct MCTP** + - Generic command set tests + - Memory device command set tests + +**Tunneling Test Coverage:** + +The FM-API test program includes built-in tunneling tests that exercise: +- `tunnel_switch_identify` - Level 1 tunnel through switch to downstream device +- `tunnel_mld_identify` - Level 1 tunnel to LD in MLD +- `tunnel_switch_mld_identify` - Level 2 tunnel through switch to LD in MLD + +These tests use auto-detected tunnel targets when available, or can be manually +configured with `-p` and `-l` options. + +#### Troubleshooting + +**Test times out waiting for MCTP I2C device:** + +- Ensure your kernel has `CONFIG_I2C_ASPEED=y` and `CONFIG_MCTP_TRANSPORT_I2C=y` +- The aspeed-i2c driver must support ACPI enumeration (requires recent kernel) +- Increase timeout with `--timeout 180` if boot is slow + +**mctpd fails to start:** + +- Ensure dbus is installed and dbus-daemon is available +- Check that mctpd is built and installed correctly + +**Endpoint assignment fails:** + +- mctpd uses D-Bus to manage endpoints; ensure dbus-daemon is running +- The script automatically handles D-Bus setup inside the VM + +**All commands show "Failed to send on MCTP socket":** + +- Check that MCTP endpoints were assigned correctly +- Verify `mctp route` shows routes to EIDs 8, 9, 10 +- Ensure mctpd is running and has discovered the endpoints + +### Test Infrastructure + +All test programs share common infrastructure from `tests/test-common.h`: + +```c +#include "test-common.h" + +TEST_DECLARE_COUNTERS; + +static void test_example(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_identify(ep, NULL, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("identify", "not supported"); + return; + } + if (rc) { + TEST_FAIL("identify", rc_str(rc)); + return; + } + TEST_PASS("identify"); +} + +int main(int argc, char **argv) +{ + TEST_MAIN_START("Example Tests", usage); + test_example(ep); + TEST_MAIN_END(); +} +``` + +**Common helpers:** + +| Function/Macro | Purpose | +|----------------|---------| +| `TEST_PASS(name)` | Record a passing test | +| `TEST_FAIL(name, reason)` | Record a failing test | +| `TEST_SKIP(name, reason)` | Record a skipped test | +| `is_unsupported(rc)` | Check if command is unsupported | +| `rc_str(rc)` | Convert return code to string | +| `wait_for_bg_done(ep, timeout)` | Wait for background operation | +| `open_endpoint(ctx, target)` | Open ioctl or MCTP endpoint | + +### Background Operation Handling + +Some CXL commands initiate background operations. The test infrastructure +handles these appropriately: + +- `CXLMI_RET_BACKGROUND` - Operation started successfully (treated as PASS) +- `CXLMI_RET_BUSY` - Device busy with existing operation (wait and retry) + +```c +static void test_populate_log(struct cxlmi_endpoint *ep) +{ + int retries = 3; + int rc; + + do { + rc = cxlmi_cmd_populate_log(ep, NULL, &req); + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) { + usleep(500000); + } + retries--; + continue; + } + break; + } while (retries > 0); + + if (rc == CXLMI_RET_BACKGROUND || rc == 0) { + TEST_PASS("populate_log"); + return; + } + // ... handle other cases +} +``` + +--- + +## Mock Device Testing Infrastructure libcxlmi includes a mock transport layer that enables comprehensive testing without -requiring physical CXL hardware. This document explains the architecture, benefits, +requiring physical CXL hardware. This section explains the architecture, benefits, limitations, and usage of the mock testing infrastructure. -## Overview +### Mock Overview The mock transport simulates a CXL device endpoint, allowing test code to: diff --git a/scripts/qemu-cxl-test.sh b/scripts/qemu-cxl-test.sh new file mode 100755 index 0000000..e043a1f --- /dev/null +++ b/scripts/qemu-cxl-test.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# QEMU CXL Unit Test Script +# +# Builds libcxlmi and a minimal initramfs, then runs unit tests +# against QEMU-emulated CXL Type3 devices using ioctl transport. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +QEMU_BIN="${QEMU_BIN:-qemu-system-x86_64}" +TIMEOUT=60 +VERBOSE="" +SANITIZE="" +WORKDIR="" + +usage() { + cat < [options] + +Required: + -k, --kernel Path to kernel bzImage (with CXL support) + +Options: + -q, --qemu Path to QEMU binary (default: qemu-system-x86_64) + -t, --timeout Test timeout in seconds (default: 60) + -v, --verbose Pass verbose flag to unit tests + -s, --sanitize Build with AddressSanitizer (ASan) + -h, --help Show this help + +Environment: + QEMU_BIN Alternative way to specify QEMU path + +Example: + $0 --kernel /boot/vmlinuz-\$(uname -r) + QEMU_BIN=~/qemu/build/qemu-system-x86_64 $0 --kernel bzImage +EOF + exit "${1:-1}" +} + +cleanup() { + [ -n "$WORKDIR" ] && [ -d "$WORKDIR" ] && rm -rf "$WORKDIR" +} +trap cleanup EXIT + +log() { echo "[*] $*"; } +die() { echo "[ERROR] $*" >&2; exit 1; } + +check_requirements() { + command -v "$QEMU_BIN" &>/dev/null || die "QEMU not found: $QEMU_BIN" + command -v meson &>/dev/null || die "meson not found" + [ -f "$KERNEL" ] || die "Kernel not found: $KERNEL" + "$QEMU_BIN" -device help 2>&1 | grep -q "cxl-type3" || die "QEMU lacks CXL support" +} + +build_libcxlmi() { + log "Building libcxlmi..." + local meson_opts="--default-library=static --prefer-static -Dlibdbus=disabled -Dtests=true" + [ -n "$SANITIZE" ] && meson_opts="$meson_opts -Db_sanitize=address" + meson setup "$WORKDIR/build" "$PROJECT_DIR" $meson_opts \ + > "$WORKDIR/meson.log" 2>&1 || { cat "$WORKDIR/meson.log"; die "Meson setup failed"; } + meson compile -C "$WORKDIR/build" \ + >> "$WORKDIR/meson.log" 2>&1 || { cat "$WORKDIR/meson.log"; die "Build failed"; } +} + +copy_binary_with_libs() { + local binary="$1" destdir="$2" destbin="$3" + cp "$binary" "$destdir/$destbin" + chmod +x "$destdir/$destbin" + ldd "$binary" 2>/dev/null | grep -oP '/[^ ]+' | while read -r lib; do + [ -f "$lib" ] || continue + mkdir -p "$destdir$(dirname "$lib")" + [ -f "$destdir$lib" ] || cp "$lib" "$destdir$lib" + done +} + +create_initramfs() { + log "Creating initramfs..." + local initramfs_dir="$WORKDIR/initramfs" + mkdir -p "$initramfs_dir"/{bin,dev,proc,sys,tmp,lib,lib64} + + # Compile and copy init + gcc -O2 -Wall "$SCRIPT_DIR/qemu-init.c" -o "$WORKDIR/init" || die "Failed to compile init" + copy_binary_with_libs "$WORKDIR/init" "$initramfs_dir" "init" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-generic" "$initramfs_dir" "bin/cxl-test-generic" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-memdev" "$initramfs_dir" "bin/cxl-test-memdev" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-fmapi" "$initramfs_dir" "bin/cxl-test-fmapi" + + # Minimal device nodes + mknod -m 622 "$initramfs_dir/dev/console" c 5 1 2>/dev/null || true + mknod -m 666 "$initramfs_dir/dev/null" c 1 3 2>/dev/null || true + + (cd "$initramfs_dir" && find . -print0 | cpio --null -o -H newc 2>/dev/null) | \ + gzip -9 > "$WORKDIR/initramfs.cpio.gz" +} + +run_qemu() { + log "Starting QEMU..." + dd if=/dev/zero of="$WORKDIR/cxl-lsa.raw" bs=4k count=1 status=none + dd if=/dev/zero of="$WORKDIR/cxl-pmem1.raw" bs=1M count=512 status=none + + local serial_log="$WORKDIR/serial.log" + local append_opts="console=ttyS0 panic=-1 rdinit=/init" + [ -n "$VERBOSE" ] && append_opts="$append_opts cxlmi_test.verbose=1" + [ -n "$SANITIZE" ] && append_opts="$append_opts cxlmi_test.sanitize=1" + + set +e + set -o pipefail + timeout --foreground "$TIMEOUT" stdbuf -oL "$QEMU_BIN" \ + -machine q35,accel=kvm,cxl=on -m 2G -smp 2 -cpu host \ + -nographic -no-reboot -monitor none \ + -kernel "$KERNEL" \ + -initrd "$WORKDIR/initramfs.cpio.gz" \ + -append "$append_opts" \ + -object memory-backend-ram,id=cxl-vmem1,size=256M \ + -object "memory-backend-file,id=cxl-pmem1,share=on,mem-path=$WORKDIR/cxl-pmem1.raw,size=512M" \ + -object "memory-backend-file,id=cxl-lsa,share=on,mem-path=$WORKDIR/cxl-lsa.raw,size=4k" \ + -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \ + -device cxl-rp,port=0,bus=cxl.1,id=root_port0,chassis=0,slot=2 \ + -device cxl-rp,port=1,bus=cxl.1,id=root_port1,chassis=0,slot=3 \ + -device cxl-type3,bus=root_port0,volatile-memdev=cxl-vmem1,id=cxl-vmem0,sn=0xDEADBEEF \ + -device cxl-type3,bus=root_port1,persistent-memdev=cxl-pmem1,lsa=cxl-lsa,id=cxl-pmem0,sn=0xCAFEBABE \ + -M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G \ + -serial stdio 2>&1 | stdbuf -oL tee "$serial_log" + local rc=${PIPESTATUS[0]} + set +o pipefail + set -e + + [ $rc -eq 124 ] && log "QEMU timed out after ${TIMEOUT}s" + echo "" + + if grep -q "TEST_RESULT=PASS" "$serial_log" 2>/dev/null; then + log "=== TESTS PASSED ==="; return 0 + elif grep -q "TEST_RESULT=FAIL" "$serial_log" 2>/dev/null; then + log "=== TESTS FAILED ==="; return 1 + else + log "=== TESTS INCONCLUSIVE ==="; return 1 + fi +} + +# Parse arguments +KERNEL="" +while [ $# -gt 0 ]; do + case "$1" in + -k|--kernel) KERNEL="$2"; shift 2 ;; + -q|--qemu) QEMU_BIN="$2"; shift 2 ;; + -t|--timeout) TIMEOUT="$2"; shift 2 ;; + -v|--verbose) VERBOSE="1"; shift ;; + -s|--sanitize) SANITIZE="1"; shift ;; + -h|--help) usage 0 ;; + *) die "Unknown option: $1" ;; + esac +done +[ -z "$KERNEL" ] && usage + +WORKDIR=$(mktemp -d -t libcxlmi-qemu-test.XXXXXX) +check_requirements +build_libcxlmi +create_initramfs +run_qemu diff --git a/scripts/qemu-init.c b/scripts/qemu-init.c new file mode 100644 index 0000000..c6e6a92 --- /dev/null +++ b/scripts/qemu-init.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Minimal init for QEMU CXL unit testing + * + * Supports two modes: + * - ioctl mode (default): Tests via /dev/cxl/memX devices + * - MCTP mode (MCTP_TEST defined): Tests via MCTP over I2C + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CXL_DEV_PATH "/dev/cxl" +#define TEST_GENERIC "/bin/cxl-test-generic" +#define TEST_MEMDEV "/bin/cxl-test-memdev" +#define TEST_FMAPI "/bin/cxl-test-fmapi" +#define MAX_WAIT_SECS 30 +#define CMDLINE_PATH "/proc/cmdline" + +static int verbose = 0; +static int sanitize = 0; + +#ifdef MCTP_TEST +static int mctp_mode = 0; +static int mctp_net = 11; +static int mctp_local_eid = 50; +/* + * MCTP endpoint configuration matching QEMU topology: + * EID 8: CXL Switch (upstream port us0) + * EID 9: Type3 SLD on switch port 0 (cxl-pmem1) + * EID 10: Type3 SLD on switch port 2 (cxl-pmem2) + * Note: Switch port 1 has virtio-rng (not a CXL device) + */ +static int switch_eid = 8; +static int type3_1_eid = 9; /* on switch port 0 */ +static int type3_2_eid = 10; /* on switch port 2 */ +static int switch_i2c_addr = 0x4; +static int type3_1_i2c_addr = 0x5; +static int type3_2_i2c_addr = 0x6; +#endif + +static int mount_filesystems(void) +{ + if (mount("proc", "/proc", "proc", 0, NULL) < 0 || + mount("sysfs", "/sys", "sysfs", 0, NULL) < 0 || + mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) < 0 || + mount("tmpfs", "/tmp", "tmpfs", 0, NULL) < 0) { + perror("mount"); + return -1; + } + return 0; +} + +#ifdef MCTP_TEST +static int parse_int_param(const char *buf, const char *param, int min, int max) +{ + const char *p = strstr(buf, param); + char *endptr; + long val; + + if (!p) + return -1; + + p += strlen(param); + errno = 0; + val = strtol(p, &endptr, 10); + + /* Check for conversion errors or out-of-range values */ + if (errno != 0 || endptr == p || val < min || val > max) + return -1; + + return (int)val; +} +#endif + +/* SIGCHLD handler to reap zombie child processes */ +static void sigchld_handler(int sig __attribute__((unused))) +{ + int saved_errno = errno; + + while (waitpid(-1, NULL, WNOHANG) > 0) + ; + + errno = saved_errno; +} + +static void parse_cmdline(void) +{ + FILE *f; + char buf[1024]; +#ifdef MCTP_TEST + int val; +#endif + + f = fopen(CMDLINE_PATH, "r"); + if (!f) + return; + + if (fgets(buf, sizeof(buf), f)) { + if (strstr(buf, "cxlmi_test.verbose=1")) + verbose = 1; + if (strstr(buf, "cxlmi_test.sanitize=1")) + sanitize = 1; +#ifdef MCTP_TEST + if (strstr(buf, "cxlmi_test.mctp=1")) + mctp_mode = 1; + /* MCTP network ID: 1-255 */ + if ((val = parse_int_param(buf, "cxlmi_test.mctp_net=", 1, 255)) > 0) + mctp_net = val; + /* MCTP EIDs: 1-254 (0=null, 255=broadcast) */ + if ((val = parse_int_param(buf, "cxlmi_test.switch_eid=", 1, 254)) > 0) + switch_eid = val; + if ((val = parse_int_param(buf, "cxlmi_test.type3_1_eid=", 1, 254)) > 0) + type3_1_eid = val; + if ((val = parse_int_param(buf, "cxlmi_test.type3_2_eid=", 1, 254)) > 0) + type3_2_eid = val; + /* I2C 7-bit addresses: 0x01-0x7F */ + if ((val = parse_int_param(buf, "cxlmi_test.switch_i2c_addr=", 0x01, 0x7F)) > 0) + switch_i2c_addr = val; + if ((val = parse_int_param(buf, "cxlmi_test.type3_1_i2c_addr=", 0x01, 0x7F)) > 0) + type3_1_i2c_addr = val; + if ((val = parse_int_param(buf, "cxlmi_test.type3_2_i2c_addr=", 0x01, 0x7F)) > 0) + type3_2_i2c_addr = val; +#endif + } + fclose(f); +} + +static int wait_for_cxl_devices(void) +{ + DIR *dir; + struct dirent *entry; + + for (int i = 0; i < MAX_WAIT_SECS * 10; i++) { + dir = opendir(CXL_DEV_PATH); + if (dir) { + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "mem", 3) == 0) { + closedir(dir); + return 0; + } + } + closedir(dir); + } + usleep(100000); + } + return -1; +} + +static int run_test_impl(const char *binary, const char *name, const char *target, + int tunnel_port) +{ + pid_t pid; + int status; + char port_str[16]; + + printf("--- %s: %s ---\n", name, target); + + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid == 0) { + if (sanitize) + setenv("ASAN_OPTIONS", "detect_leaks=1:abort_on_error=1", 1); + + if (tunnel_port >= 0) { + snprintf(port_str, sizeof(port_str), "%d", tunnel_port); + if (verbose) + execl(binary, binary, "-v", "-p", port_str, target, NULL); + else + execl(binary, binary, "-p", port_str, target, NULL); + } else { + if (verbose) + execl(binary, binary, "-v", target, NULL); + else + execl(binary, binary, target, NULL); + } + _exit(127); + } + + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) { + int code = WEXITSTATUS(status); + const char *result; + + switch (code) { + case 0: + result = "PASS"; + break; + case 1: + result = "FAIL"; + break; + case 2: + result = "SKIP"; + break; + case 127: + printf("[FAIL] %s: %s (binary not found)\n\n", name, target); + return 1; /* count as failure */ + default: + result = "FAIL"; + code = 1; + break; + } + printf("[%s] %s: %s\n\n", result, name, target); + return code; + } + + printf("[FAIL] %s: %s (crashed)\n\n", name, target); + return 1; /* count as failure */ +} + +static void count_result(int rc, int *pass, int *fail, int *skip) +{ + switch (rc) { + case 0: + (*pass)++; + break; + case 2: + (*skip)++; + break; + default: + (*fail)++; + break; + } +} + +#ifdef MCTP_TEST +static int run_cmd_quiet(const char *cmd) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) + return -1; + + if (pid == 0) { + execl("/bin/sh", "sh", "-c", cmd, NULL); + _exit(127); + } + + waitpid(pid, &status, 0); + return WIFEXITED(status) ? WEXITSTATUS(status) : -1; +} + +static int run_cmd(const char *cmd) +{ + if (verbose) + printf("Running: %s\n", cmd); + + return run_cmd_quiet(cmd); +} + +static int wait_for_mctp_i2c(void) +{ + struct stat st; + + printf("Waiting for MCTP I2C device...\n"); + + /* Wait for mctp-i2c-controller to create the mctp device */ + for (int i = 0; i < MAX_WAIT_SECS * 10; i++) { + /* Check for any mctpi2c device in /sys/bus/mctp/devices/ */ + DIR *dir = opendir("/sys/bus/mctp/devices"); + if (dir) { + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "mctpi2c", 7) == 0) { + closedir(dir); + printf("Found MCTP device: %s\n", entry->d_name); + /* Give it a moment to fully initialize */ + usleep(500000); + return 0; + } + } + closedir(dir); + } + + /* Also check /sys/class/net for mctp network interfaces */ + if (stat("/sys/class/net/mctpi2c0", &st) == 0) { + printf("Found MCTP network interface: mctpi2c0\n"); + usleep(500000); + return 0; + } + + usleep(100000); + } + + printf("Timeout waiting for MCTP I2C device\n"); + return -1; +} + +static int assign_mctp_endpoint(int i2c_addr, int expected_eid) +{ + char cmd[512]; + int rc; + + /* + * Use busctl to call mctpd's AssignEndpoint method. + * This tells mctpd to assign an EID to the device at the given I2C address. + * + * In mctpd v2, AssignEndpoint is on the interface object: + * Service: au.com.codeconstruct.MCTP1 + * Object: /au/com/codeconstruct/mctp1/interfaces/mctpi2c0 + * Interface: au.com.codeconstruct.MCTP.BusOwner1 + * Method: AssignEndpoint + * Signature: ay (byte array for physical address) + * + * For I2C, the physical address is a single byte (7-bit I2C address). + */ + snprintf(cmd, sizeof(cmd), + "busctl call au.com.codeconstruct.MCTP1 " + "/au/com/codeconstruct/mctp1/interfaces/mctpi2c0 " + "au.com.codeconstruct.MCTP.BusOwner1 " + "AssignEndpoint ay 1 0x%x", + i2c_addr); + + rc = run_cmd(cmd); + if (rc != 0) { + printf("Warning: Failed to assign EID to I2C addr 0x%x\n", i2c_addr); + return rc; + } + + printf("Assigned EID to device at I2C addr 0x%x (expected EID %d)\n", + i2c_addr, expected_eid); + return 0; +} + +static int setup_mctp_network(void) +{ + char cmd[512]; + int rc; + + printf("Setting up MCTP network...\n"); + + /* Bring up the MCTP I2C link */ + rc = run_cmd("mctp link set mctpi2c0 up 2>/dev/null || " + "ip link set mctpi2c0 up 2>/dev/null"); + if (rc != 0) + printf("Warning: Failed to bring up mctpi2c0\n"); + + /* Set local EID */ + snprintf(cmd, sizeof(cmd), "mctp addr add %d dev mctpi2c0", mctp_local_eid); + rc = run_cmd(cmd); + if (rc != 0) + printf("Warning: Failed to set local EID\n"); + + /* Set network ID */ + snprintf(cmd, sizeof(cmd), "mctp link set mctpi2c0 net %d", mctp_net); + rc = run_cmd(cmd); + if (rc != 0) + printf("Warning: Failed to set MCTP network ID\n"); + + /* Create run directories for dbus and mctpd */ + mkdir("/run", 0755); + mkdir("/run/dbus", 0755); + mkdir("/var", 0755); + mkdir("/var/run", 0755); + mkdir("/var/lib", 0755); + mkdir("/var/lib/dbus", 0755); + + /* Create machine-id for dbus (required) */ + FILE *mid = fopen("/var/lib/dbus/machine-id", "w"); + if (mid) { + fprintf(mid, "00000000000000000000000000000001\n"); + fclose(mid); + } + /* Also create /etc/machine-id as some dbus versions check there */ + mkdir("/etc", 0755); + mid = fopen("/etc/machine-id", "w"); + if (mid) { + fprintf(mid, "00000000000000000000000000000001\n"); + fclose(mid); + } + + /* Show MCTP link configuration */ + if (verbose) { + printf("\nMCTP Link Configuration:\n"); + run_cmd("mctp link 2>/dev/null || ip -d link show mctpi2c0 2>/dev/null"); + run_cmd("mctp addr 2>/dev/null"); + } + + /* Start dbus-daemon if available (required for mctpd) */ + if (access("/bin/dbus-daemon", X_OK) == 0) { + printf("Starting dbus-daemon...\n"); + rc = run_cmd("dbus-daemon --system --fork --print-pid"); + if (rc != 0) { + printf("Warning: Failed to start dbus-daemon (rc=%d)\n", rc); + /* Try with more verbose output to see what's wrong */ + run_cmd("dbus-daemon --system --fork --print-address 2>&1 || true"); + } + usleep(500000); + } + + /* Try to start mctpd if available */ + if (access("/bin/mctpd", X_OK) == 0) { + printf("Starting mctpd...\n"); + /* Run mctpd in background */ + pid_t pid = fork(); + if (pid < 0) { + printf("Warning: Failed to fork for mctpd\n"); + } else if (pid == 0) { + /* mctpd needs to run as a daemon */ + setsid(); + execl("/bin/mctpd", "mctpd", "-v", NULL); + _exit(127); + } else { + /* Give mctpd time to start and register on D-Bus */ + sleep(3); + + if (verbose) { + printf("Checking D-Bus services...\n"); + run_cmd("busctl list 2>&1 | head -20 || true"); + } + + /* Assign endpoints using busctl */ + printf("Assigning MCTP endpoints...\n"); + assign_mctp_endpoint(switch_i2c_addr, switch_eid); + usleep(500000); + assign_mctp_endpoint(type3_1_i2c_addr, type3_1_eid); + usleep(500000); + assign_mctp_endpoint(type3_2_i2c_addr, type3_2_eid); + } + } else { + printf("Warning: mctpd not found, skipping endpoint assignment\n"); + printf("Tests will use static EID configuration\n"); + } + + /* Show final MCTP configuration */ + if (verbose) { + printf("\nFinal MCTP Configuration:\n"); + run_cmd("mctp link 2>/dev/null"); + run_cmd("mctp addr 2>/dev/null"); + run_cmd("mctp route 2>/dev/null"); + } + + return 0; +} + +static int run_mctp_test(const char *binary, const char *name, int net, int eid, + int tunnel_port) +{ + char target[64]; + + snprintf(target, sizeof(target), "mctp:%d,%d", net, eid); + return run_test_impl(binary, name, target, tunnel_port); +} + +static void run_mctp_tests(int *pass, int *fail, int *skip) +{ + *pass = *fail = *skip = 0; + + printf("\n=== Testing Switch CCI (EID %d) ===\n\n", switch_eid); + + /* Run generic tests against switch */ + count_result(run_mctp_test(TEST_GENERIC, "generic", mctp_net, switch_eid, -1), + pass, fail, skip); + + /* + * Run FM-API tests against switch directly (no tunneling). + * FM-API commands (identify_sw_device, get_phys_port_state, etc.) + * are sent directly to the switch CCI. The test has an auto-detection + * section at the end that will discover downstream ports for tunneling. + */ + count_result(run_mctp_test(TEST_FMAPI, "fmapi", mctp_net, switch_eid, -1), + pass, fail, skip); + + /* + * Test tunneling generic/memdev commands through switch. + * These tests send commands to the switch (EID 8) which tunnels them + * to the Type3 device on the specified downstream port. + * + * Port 0 -> cxl-pmem1 (same device as EID 9) + * Port 2 -> cxl-pmem2 (same device as EID 10) + * Port 1 has virtio-rng (not a CXL device, skip) + */ + printf("\n=== Testing Tunneled Commands via Switch Port 0 (-> cxl-pmem1) ===\n\n"); + + count_result(run_mctp_test(TEST_GENERIC, "generic-tunnel-p0", mctp_net, switch_eid, 0), + pass, fail, skip); + count_result(run_mctp_test(TEST_MEMDEV, "memdev-tunnel-p0", mctp_net, switch_eid, 0), + pass, fail, skip); + + printf("\n=== Testing Tunneled Commands via Switch Port 2 (-> cxl-pmem2) ===\n\n"); + + count_result(run_mctp_test(TEST_GENERIC, "generic-tunnel-p2", mctp_net, switch_eid, 2), + pass, fail, skip); + count_result(run_mctp_test(TEST_MEMDEV, "memdev-tunnel-p2", mctp_net, switch_eid, 2), + pass, fail, skip); + + /* + * Test direct access to Type3 devices via their own MCTP endpoints. + * No tunneling - commands go directly to the device's MCTP CCI. + * These access the same physical devices as the tunneled tests above, + * but via their dedicated MCTP endpoints instead of through the switch. + */ + printf("\n=== Testing Type3 Device 1 Direct (EID %d, cxl-pmem1) ===\n\n", type3_1_eid); + + count_result(run_mctp_test(TEST_GENERIC, "generic", mctp_net, type3_1_eid, -1), + pass, fail, skip); + count_result(run_mctp_test(TEST_MEMDEV, "memdev", mctp_net, type3_1_eid, -1), + pass, fail, skip); + + printf("\n=== Testing Type3 Device 2 Direct (EID %d, cxl-pmem2) ===\n\n", type3_2_eid); + + count_result(run_mctp_test(TEST_GENERIC, "generic", mctp_net, type3_2_eid, -1), + pass, fail, skip); + count_result(run_mctp_test(TEST_MEMDEV, "memdev", mctp_net, type3_2_eid, -1), + pass, fail, skip); +} +#endif /* MCTP_TEST */ + +static int run_test(const char *binary, const char *name, const char *device) +{ + return run_test_impl(binary, name, device, -1); +} + +static void run_all_tests(int *pass, int *fail, int *skip) +{ + DIR *dir; + struct dirent *entry; + + *pass = *fail = *skip = 0; + + dir = opendir(CXL_DEV_PATH); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "mem", 3) == 0) { + /* Run generic command tests */ + count_result(run_test(TEST_GENERIC, "generic", entry->d_name), + pass, fail, skip); + + /* Run memdev command tests */ + count_result(run_test(TEST_MEMDEV, "memdev", entry->d_name), + pass, fail, skip); + + /* Run FM-API command tests */ + count_result(run_test(TEST_FMAPI, "fmapi", entry->d_name), + pass, fail, skip); + } + } + closedir(dir); +} + +int main(void) +{ + struct sigaction sa; + sigset_t mask; + int pass = 0, fail = 0, skip = 0; + + printf("\n========================================\n"); +#ifdef MCTP_TEST + printf(" libcxlmi QEMU CXL Unit Tests (MCTP)\n"); +#else + printf(" libcxlmi QEMU CXL Unit Tests\n"); +#endif + printf("========================================\n\n"); + + if (mount_filesystems() < 0) { + printf("TEST_RESULT=FAIL\n"); + goto out; + } + + /* Install SIGCHLD handler to reap zombie processes (we are init/PID 1) */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, NULL); + + /* + * Block SIGCHLD to prevent the handler from racing with waitpid(). + * We use explicit waitpid() for all test children, so we don't need + * the handler running concurrently. The handler will still reap any + * zombies when we exit (the signal will be delivered on unblock). + */ + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, NULL); + + parse_cmdline(); + if (verbose) + printf("Verbose mode enabled\n"); + if (sanitize) + printf("AddressSanitizer enabled\n"); +#ifdef MCTP_TEST + if (mctp_mode) + printf("MCTP transport mode enabled\n"); +#endif + if (verbose || sanitize) + printf("\n"); + +#ifdef MCTP_TEST + if (mctp_mode) { + /* MCTP mode: wait for MCTP I2C device and setup network */ + if (wait_for_mctp_i2c() < 0) { + printf("[FAIL] No MCTP I2C device found\n"); + printf("TEST_RESULT=FAIL\n"); + goto out; + } + + if (setup_mctp_network() < 0) { + printf("[FAIL] Failed to setup MCTP network\n"); + printf("TEST_RESULT=FAIL\n"); + goto out; + } + + run_mctp_tests(&pass, &fail, &skip); + } else +#endif + { + /* Standard ioctl mode */ + if (wait_for_cxl_devices() < 0) { + printf("[FAIL] No CXL devices found\n"); + printf("TEST_RESULT=FAIL\n"); + goto out; + } + + run_all_tests(&pass, &fail, &skip); + } + + printf("========================================\n"); + printf(" Results: %d passed, %d failed, %d skipped\n", pass, fail, skip); + printf("========================================\n\n"); + printf("TEST_RESULT=%s\n", fail == 0 ? "PASS" : "FAIL"); + +out: + /* Unblock SIGCHLD to let the handler reap any remaining zombies */ + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + sync(); + reboot(LINUX_REBOOT_CMD_POWER_OFF); + return 0; +} diff --git a/scripts/qemu-mctp-test.sh b/scripts/qemu-mctp-test.sh new file mode 100755 index 0000000..be7fe23 --- /dev/null +++ b/scripts/qemu-mctp-test.sh @@ -0,0 +1,333 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# QEMU CXL MCTP Unit Test Script +# +# Builds libcxlmi and a minimal initramfs, then runs unit tests +# against QEMU-emulated CXL devices using MCTP over I2C transport. +# +# This script creates a CXL switch topology with: +# - CXL switch with mailbox CCI exposed via i2c_mctp_cxl +# - Type3 SLD devices exposed via i2c_mctp_cxl +# - aspeed-i2c controller for MCTP I2C transport +# +# Based on cxl-fmapi-tests configuration: +# https://gitlab.com/jic23/cxl-fmapi-tests +# +# Requirements: +# - QEMU with CXL switch and i2c_mctp_cxl device support +# - Kernel with MCTP I2C transport and aspeed-i2c support +# (may require patches for ACPI-based aspeed-i2c) +# - mctp-tools (mctp, mctpd) for MCTP network configuration +# +# Note: The aspeed-i2c driver may need modifications: +# - Comment out clock configuration +# - Make reset optional +# See: https://gitlab.com/jic23/cxl-fmapi-tests + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +QEMU_BIN="${QEMU_BIN:-qemu-system-x86_64}" +TIMEOUT=120 +VERBOSE="" +SANITIZE="" +WORKDIR="" + +# MCTP configuration +# Note: EIDs are assigned dynamically by mctpd/busctl +# These are the expected EIDs after AssignEndpoint calls +MCTP_NET=11 +MCTP_LOCAL_EID=50 +SWITCH_I2C_ADDR=0x4 # I2C address for switch MCTP CCI +SWITCH_EID=8 # EID assigned to switch +TYPE3_1_I2C_ADDR=0x5 # I2C address for first Type3 device +TYPE3_1_EID=9 # EID assigned to first Type3 +TYPE3_2_I2C_ADDR=0x6 # I2C address for second Type3 device +TYPE3_2_EID=10 # EID assigned to second Type3 + +usage() { + cat < [options] + +Required: + -k, --kernel Path to kernel bzImage (with CXL and MCTP support) + +Options: + -q, --qemu Path to QEMU binary (default: qemu-system-x86_64) + -t, --timeout Test timeout in seconds (default: 120) + -v, --verbose Pass verbose flag to unit tests + -s, --sanitize Build with AddressSanitizer (ASan) + -h, --help Show this help + +Environment: + QEMU_BIN Alternative way to specify QEMU path + +Kernel Requirements: + CONFIG_MCTP=y + CONFIG_MCTP_TRANSPORT_I2C=y + CONFIG_I2C_ASPEED=y (may require patches for ACPI support) + +QEMU Requirements: + - CXL switch support (cxl-upstream, cxl-downstream) + - i2c_mctp_cxl device support + - aspeed-i2c controller emulation + +Example: + $0 --kernel /boot/vmlinuz-\$(uname -r) + QEMU_BIN=~/qemu/build/qemu-system-x86_64 $0 --kernel bzImage +EOF + exit "${1:-1}" +} + +cleanup() { + [ -n "$WORKDIR" ] && [ -d "$WORKDIR" ] && rm -rf "$WORKDIR" +} +trap cleanup EXIT + +log() { echo "[*] $*"; } +die() { echo "[ERROR] $*" >&2; exit 1; } + +check_requirements() { + command -v "$QEMU_BIN" &>/dev/null || die "QEMU not found: $QEMU_BIN" + command -v meson &>/dev/null || die "meson not found" + [ -f "$KERNEL" ] || die "Kernel not found: $KERNEL" + "$QEMU_BIN" -device help 2>&1 | grep -q "cxl-type3" || die "QEMU lacks CXL support" + "$QEMU_BIN" -device help 2>&1 | grep -q "cxl-upstream" || die "QEMU lacks CXL switch support" + "$QEMU_BIN" -device help 2>&1 | grep -q "i2c_mctp_cxl" || die "QEMU lacks i2c_mctp_cxl device (required for MCTP testing)" +} + +build_libcxlmi() { + log "Building libcxlmi..." + local meson_opts="--default-library=static --prefer-static -Dlibdbus=disabled -Dtests=true" + [ -n "$SANITIZE" ] && meson_opts="$meson_opts -Db_sanitize=address" + meson setup "$WORKDIR/build" "$PROJECT_DIR" $meson_opts \ + > "$WORKDIR/meson.log" 2>&1 || { cat "$WORKDIR/meson.log"; die "Meson setup failed"; } + meson compile -C "$WORKDIR/build" \ + >> "$WORKDIR/meson.log" 2>&1 || { cat "$WORKDIR/meson.log"; die "Build failed"; } +} + +copy_binary_with_libs() { + local binary="$1" destdir="$2" destbin="$3" + cp "$binary" "$destdir/$destbin" + chmod +x "$destdir/$destbin" + ldd "$binary" 2>/dev/null | grep -oP '/[^ ]+' | while read -r lib; do + [ -f "$lib" ] || continue + mkdir -p "$destdir$(dirname "$lib")" + [ -f "$destdir$lib" ] || cp "$lib" "$destdir$lib" + done +} + +create_initramfs() { + log "Creating initramfs..." + local initramfs_dir="$WORKDIR/initramfs" + mkdir -p "$initramfs_dir"/{bin,dev,proc,sys,lib,lib64,etc,run,var/run,tmp} + + # Compile and copy init + gcc -O2 -Wall -DMCTP_TEST "$SCRIPT_DIR/qemu-init.c" -o "$WORKDIR/init" || die "Failed to compile init" + copy_binary_with_libs "$WORKDIR/init" "$initramfs_dir" "init" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-generic" "$initramfs_dir" "bin/cxl-test-generic" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-memdev" "$initramfs_dir" "bin/cxl-test-memdev" + copy_binary_with_libs "$WORKDIR/build/tests/cxl-test-fmapi" "$initramfs_dir" "bin/cxl-test-fmapi" + + # Copy mctp tools and dbus if available + # Check standard paths and /usr/local/sbin for mctpd + for tool in mctp mctpd busctl dbus-daemon sh dd; do + local tool_path="" + tool_path=$(command -v "$tool" 2>/dev/null) || true + if [ -z "$tool_path" ]; then + # Tool not in PATH, check common locations + for p in /usr/local/sbin/$tool /usr/sbin/$tool /usr/local/bin/$tool; do + if [ -x "$p" ]; then + tool_path="$p" + break + fi + done + fi + [ -z "$tool_path" ] && continue + copy_binary_with_libs "$tool_path" "$initramfs_dir" "bin/$tool" + done + + # Check if mctp tools were found + if [ ! -f "$initramfs_dir/bin/mctp" ]; then + log "Warning: mctp tool not found, MCTP tests may fail" + else + log "Found mctp tool" + fi + if [ ! -f "$initramfs_dir/bin/mctpd" ]; then + log "Warning: mctpd not found, MCTP endpoint assignment may fail" + else + log "Found mctpd" + fi + if [ ! -f "$initramfs_dir/bin/dbus-daemon" ]; then + log "Warning: dbus-daemon not found, mctpd may not work" + fi + + # Copy dbus configuration if dbus-daemon is present + if [ -f "$initramfs_dir/bin/dbus-daemon" ]; then + mkdir -p "$initramfs_dir/etc/dbus-1" + mkdir -p "$initramfs_dir/usr/share/dbus-1/system-services" + mkdir -p "$initramfs_dir/usr/share/dbus-1/system.d" + + # Create minimal passwd/group files for dbus + cat > "$initramfs_dir/etc/passwd" <<'PASSWD' +root:x:0:0:root:/root:/bin/sh +messagebus:x:106:110::/nonexistent:/usr/sbin/nologin +PASSWD + cat > "$initramfs_dir/etc/group" <<'GROUP' +root:x:0: +messagebus:x:110: +GROUP + + # Create a minimal dbus system config that works in initramfs + cat > "$initramfs_dir/usr/share/dbus-1/system.conf" <<'DBUSCONF' + + + system + + /run/dbus/pid + unix:path=/run/dbus/system_bus_socket + EXTERNAL + + + + + + + + + + + + + +DBUSCONF + fi + + # Minimal device nodes + mknod -m 622 "$initramfs_dir/dev/console" c 5 1 2>/dev/null || true + mknod -m 666 "$initramfs_dir/dev/null" c 1 3 2>/dev/null || true + + # Create MCTP configuration file for init + cat > "$initramfs_dir/etc/mctp.conf" </dev/null) | \ + gzip -9 > "$WORKDIR/initramfs.cpio.gz" +} + +run_qemu() { + log "Starting QEMU with CXL switch topology and MCTP I2C..." + + # Create sparse memory backing files + truncate -s 1M "$WORKDIR/cxl-lsa1.raw" + truncate -s 1M "$WORKDIR/cxl-lsa2.raw" + truncate -s 256M "$WORKDIR/cxl-mem1.raw" + truncate -s 512M "$WORKDIR/cxl-mem2.raw" + + local serial_log="$WORKDIR/serial.log" + local append_opts="console=ttyS0 panic=-1 rdinit=/init cxlmi_test.mctp=1" + [ -n "$VERBOSE" ] && append_opts="$append_opts cxlmi_test.verbose=1" + [ -n "$SANITIZE" ] && append_opts="$append_opts cxlmi_test.sanitize=1" + + # Add MCTP endpoint information for tests + append_opts="$append_opts cxlmi_test.mctp_net=$MCTP_NET" + append_opts="$append_opts cxlmi_test.switch_eid=$SWITCH_EID" + append_opts="$append_opts cxlmi_test.type3_1_eid=$TYPE3_1_EID" + append_opts="$append_opts cxlmi_test.type3_2_eid=$TYPE3_2_EID" + append_opts="$append_opts cxlmi_test.switch_i2c_addr=$SWITCH_I2C_ADDR" + append_opts="$append_opts cxlmi_test.type3_1_i2c_addr=$TYPE3_1_I2C_ADDR" + append_opts="$append_opts cxlmi_test.type3_2_i2c_addr=$TYPE3_2_I2C_ADDR" + + set +e + set -o pipefail + + # QEMU topology: + # - pxb-cxl (CXL host bridge) + # - cxl-rp (root port) + # - cxl-upstream (switch upstream port) + # - cxl-downstream port 0 -> cxl-type3 (cxl-pmem1, Type3 SLD) + # - cxl-downstream port 1 -> virtio-rng (placeholder, not CXL) + # - cxl-downstream port 2 -> cxl-type3 (cxl-pmem2, Type3 SLD) + # - aspeed.i2c.bus.0 (I2C bus for MCTP transport) + # - i2c_mctp_cxl at address 0x4 -> us0 (switch, EID 8) + # - i2c_mctp_cxl at address 0x5 -> cxl-pmem1 (Type3 device 1, EID 9) + # - i2c_mctp_cxl at address 0x6 -> cxl-pmem2 (Type3 device 2, EID 10) + # + # Note: This topology requires QEMU with CXL switch and i2c_mctp_cxl support. + # The aspeed-i2c controller provides MCTP I2C transport. + # Kernel needs CONFIG_I2C_ASPEED with ACPI support patches. + + timeout --foreground "$TIMEOUT" stdbuf -oL "$QEMU_BIN" \ + -machine q35,accel=kvm,cxl=on -m 2G -smp 4 -cpu host \ + -nographic -no-reboot -monitor none \ + -kernel "$KERNEL" \ + -initrd "$WORKDIR/initramfs.cpio.gz" \ + -append "$append_opts" \ + -object memory-backend-file,id=cxl-mem1,mem-path="$WORKDIR/cxl-mem1.raw",size=256M \ + -object memory-backend-file,id=cxl-mem2,mem-path="$WORKDIR/cxl-mem2.raw",size=512M \ + -object memory-backend-file,id=cxl-lsa1,mem-path="$WORKDIR/cxl-lsa1.raw",size=1M \ + -object memory-backend-file,id=cxl-lsa2,mem-path="$WORKDIR/cxl-lsa2.raw",size=1M \ + -device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1,hdm_for_passthrough=true \ + -device cxl-rp,port=0,bus=cxl.1,id=cxl_rp_port0,chassis=0,slot=2 \ + -device cxl-upstream,port=2,sn=1234,bus=cxl_rp_port0,id=us0,addr=0.0,multifunction=on, \ + -device cxl-switch-mailbox-cci,bus=cxl_rp_port0,addr=0.1,target=us0 \ + -device cxl-downstream,port=0,bus=us0,id=swport0,chassis=0,slot=4 \ + -device cxl-downstream,port=1,bus=us0,id=swport1,chassis=0,slot=5 \ + -device cxl-downstream,port=2,bus=us0,id=swport2,chassis=0,slot=6 \ + -device cxl-type3,bus=swport0,memdev=cxl-mem1,id=cxl-pmem1,lsa=cxl-lsa1,sn=3 \ + -device cxl-type3,bus=swport2,memdev=cxl-mem2,id=cxl-pmem2,lsa=cxl-lsa2,sn=4 \ + -device virtio-rng-pci,bus=swport1 \ + -machine cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,cxl-fmw.0.interleave-granularity=1k \ + -device i2c_mctp_cxl,bus=aspeed.i2c.bus.0,address=$SWITCH_I2C_ADDR,target=us0 \ + -device i2c_mctp_cxl,bus=aspeed.i2c.bus.0,address=$TYPE3_1_I2C_ADDR,target=cxl-pmem1 \ + -device i2c_mctp_cxl,bus=aspeed.i2c.bus.0,address=$TYPE3_2_I2C_ADDR,target=cxl-pmem2 \ + -serial stdio 2>&1 | stdbuf -oL tee "$serial_log" + local rc=${PIPESTATUS[0]} + set +o pipefail + set -e + + [ $rc -eq 124 ] && log "QEMU timed out after ${TIMEOUT}s" + echo "" + + if grep -q "TEST_RESULT=PASS" "$serial_log" 2>/dev/null; then + log "=== TESTS PASSED ==="; return 0 + elif grep -q "TEST_RESULT=FAIL" "$serial_log" 2>/dev/null; then + log "=== TESTS FAILED ==="; return 1 + else + log "=== TESTS INCONCLUSIVE ==="; return 1 + fi +} + +# Parse arguments +KERNEL="" +while [ $# -gt 0 ]; do + case "$1" in + -k|--kernel) KERNEL="$2"; shift 2 ;; + -q|--qemu) QEMU_BIN="$2"; shift 2 ;; + -t|--timeout) TIMEOUT="$2"; shift 2 ;; + -v|--verbose) VERBOSE="1"; shift ;; + -s|--sanitize) SANITIZE="1"; shift ;; + -h|--help) usage 0 ;; + *) die "Unknown option: $1" ;; + esac +done +[ -z "$KERNEL" ] && usage + +WORKDIR=$(mktemp -d -t libcxlmi-mctp-test.XXXXXX) +check_requirements +build_libcxlmi +create_initramfs +run_qemu diff --git a/tests/cxl-test-fmapi.c b/tests/cxl-test-fmapi.c new file mode 100644 index 0000000..37b8c7c --- /dev/null +++ b/tests/cxl-test-fmapi.c @@ -0,0 +1,1495 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * CXL FM-API Command Set unit tests for libcxlmi + * + * Tests FM-API commands (CXL r3.1 Section 8.2.9.9) against a CXL FM device. + * Supports tunneling through a CXL switch with -p option. + */ + +#include "test-common.h" + +TEST_DECLARE_COUNTERS; +TEST_DECLARE_TUNNEL_CONFIG; + +/* + * Physical Switch Commands + */ + +static void test_identify_sw_device(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_identify_sw_device_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_identify_sw_device(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("identify_sw_device", "not supported"); + return; + } + if (rc) { + TEST_FAIL("identify_sw_device", rc_str(rc)); + return; + } + + TEST_PASS("identify_sw_device"); + if (verbose) { + printf(" ingress_port_id: %u\n", rsp.ingress_port_id); + printf(" num_physical_ports: %u\n", rsp.num_physical_ports); + printf(" num_vcs: %u\n", rsp.num_vcs); + printf(" num_total_vppb: %u\n", rsp.num_total_vppb); + printf(" num_active_vppb: %u\n", rsp.num_active_vppb); + printf(" num_hdm_decoder_per_usp: %u\n", rsp.num_hdm_decoder_per_usp); + } +} + +static void test_get_phys_port_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_identify_sw_device_rsp id = {0}; + struct cxlmi_cmd_fmapi_get_phys_port_state_req *req; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *rsp; + int rc, num_active_ports, port_idx; + + /* First get number of ports from identify */ + rc = cxlmi_cmd_fmapi_identify_sw_device(ep, get_tunnel_info(), &id); + if (is_unsupported(rc)) { + TEST_SKIP("get_phys_port_state", "identify not supported"); + return; + } + + /* Count active ports from bitmask */ + num_active_ports = 0; + for (int i = 0; i < 256; i++) { + if (id.active_port_bitmask[i / 8] & (1 << (i % 8))) + num_active_ports++; + } + + if (num_active_ports == 0) { + TEST_SKIP("get_phys_port_state", "no active ports"); + return; + } + + req = calloc(1, sizeof(*req) + num_active_ports); + rsp = calloc(1, sizeof(*rsp) + num_active_ports * sizeof(rsp->ports[0])); + if (!req || !rsp) { + TEST_FAIL("get_phys_port_state", "allocation failed"); + free(req); + free(rsp); + return; + } + + /* Request only active ports based on bitmask */ + req->num_ports = num_active_ports; + port_idx = 0; + for (int i = 0; i < 256 && port_idx < num_active_ports; i++) { + if (id.active_port_bitmask[i / 8] & (1 << (i % 8))) + req->ports[port_idx++] = i; + } + + rc = cxlmi_cmd_fmapi_get_phys_port_state(ep, get_tunnel_info(), req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_phys_port_state", "not supported"); + free(req); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_phys_port_state", rc_str(rc)); + free(req); + free(rsp); + return; + } + + TEST_PASS("get_phys_port_state"); + if (verbose) { + printf(" num_ports: %u\n", rsp->num_ports); + for (int i = 0; i < rsp->num_ports; i++) { + printf(" [%d] port_id: %u, state: %u, type: %u\n", + i, rsp->ports[i].port_id, + rsp->ports[i].config_state, + rsp->ports[i].conn_dev_type); + printf(" link: width=%u/%u, speed=%u/%u, ltssm=0x%02x\n", + rsp->ports[i].negotiated_link_width, + rsp->ports[i].max_link_width, + rsp->ports[i].current_link_speed, + rsp->ports[i].max_link_speed, + rsp->ports[i].ltssm_state); + } + } + free(req); + free(rsp); +} + +static void test_phys_port_control(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_phys_port_control_req req = {0}; + int rc; + + /* Try a no-op control operation on port 0 */ + req.ppb_id = 0; + req.port_opcode = 0; /* No operation / query */ + + rc = cxlmi_cmd_fmapi_phys_port_control(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("phys_port_control", "not supported"); + return; + } + if (rc) { + TEST_FAIL("phys_port_control", rc_str(rc)); + return; + } + + TEST_PASS("phys_port_control"); +} + +static void test_send_ppb_cxlio_config_request(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ppb_cxlio_config_request_rsp rsp = {0}; + int rc; + + /* Read config space offset 0 (vendor ID) */ + req.ppb_id = 0; + /* field_1[0x3] contains destination_type/opcode/address_offset packed */ + req.transaction_data = 0; + + rc = cxlmi_cmd_fmapi_send_ppb_cxlio_config_request(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("send_ppb_cxlio_config_request", "not supported"); + return; + } + if (rc) { + TEST_FAIL("send_ppb_cxlio_config_request", rc_str(rc)); + return; + } + + TEST_PASS("send_ppb_cxlio_config_request"); + if (verbose) + printf(" return_data: 0x%08x\n", rsp.return_data); +} + +/* + * Domain Validation Commands + */ + +static void test_get_domain_validation_sv_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_state_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_domain_validation_sv_state(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_domain_validation_sv_state", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_domain_validation_sv_state", rc_str(rc)); + return; + } + + TEST_PASS("get_domain_validation_sv_state"); + if (verbose) + printf(" secret_value_state: 0x%02x\n", + rsp.secret_value_state); +} + +static void test_set_domain_validation_sv(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_set_domain_validation_sv_req req = {0}; + int rc; + + /* Set a test secret value UUID (safe operation - zeros) */ + memset(req.secret_value_uuid, 0, sizeof(req.secret_value_uuid)); + + rc = cxlmi_cmd_fmapi_set_domain_validation_sv(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("set_domain_validation_sv", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_domain_validation_sv", rc_str(rc)); + return; + } + + TEST_PASS("set_domain_validation_sv"); +} + +static void test_get_vcs_domain_validation_sv_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_req req = {0}; + struct cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state_rsp rsp = {0}; + int rc; + + req.vcs_id = 0; + + rc = cxlmi_cmd_fmapi_get_vcs_domain_validation_sv_state(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_vcs_domain_validation_sv_state", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_vcs_domain_validation_sv_state", rc_str(rc)); + return; + } + + TEST_PASS("get_vcs_domain_validation_sv_state"); + if (verbose) + printf(" secret_value_state: 0x%02x\n", + rsp.secret_value_state); +} + +static void test_get_domain_validation_sv(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_domain_validation_sv_req req = {0}; + struct cxlmi_cmd_fmapi_get_domain_validation_sv_rsp rsp = {0}; + int rc; + + req.vcs_id = 0; + + rc = cxlmi_cmd_fmapi_get_domain_validation_sv(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_domain_validation_sv", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_domain_validation_sv", rc_str(rc)); + return; + } + + TEST_PASS("get_domain_validation_sv"); + if (verbose) { + printf(" secret_value_uuid: " + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x\n", + rsp.secret_value_uuid[0], rsp.secret_value_uuid[1], + rsp.secret_value_uuid[2], rsp.secret_value_uuid[3], + rsp.secret_value_uuid[4], rsp.secret_value_uuid[5], + rsp.secret_value_uuid[6], rsp.secret_value_uuid[7], + rsp.secret_value_uuid[8], rsp.secret_value_uuid[9], + rsp.secret_value_uuid[10], rsp.secret_value_uuid[11], + rsp.secret_value_uuid[12], rsp.secret_value_uuid[13], + rsp.secret_value_uuid[14], rsp.secret_value_uuid[15]); + } +} + +/* + * Virtual Switch Commands + */ + +static void test_bind_vppb(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_bind_vppb_req req = {0}; + int rc; + + req.vcs_id = 0; + req.vppb_id = 0; + req.port_id = 0; + req.ld_id = 0; + + rc = cxlmi_cmd_fmapi_bind_vppb(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("bind_vppb", "not supported"); + return; + } + if (rc) { + TEST_FAIL("bind_vppb", rc_str(rc)); + return; + } + + TEST_PASS("bind_vppb"); +} + +static void test_unbind_vppb(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_unbind_vppb_req req = {0}; + int rc; + + req.vcs_id = 0; + req.vppb_id = 0; + req.option = 0; /* Wait for clean unbind */ + + rc = cxlmi_cmd_fmapi_unbind_vppb(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("unbind_vppb", "not supported"); + return; + } + if (rc) { + TEST_FAIL("unbind_vppb", rc_str(rc)); + return; + } + + TEST_PASS("unbind_vppb"); +} + +/* + * MLD Port Commands + */ + +static void test_send_ld_cxlio_config_request(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_config_request_rsp rsp = {0}; + int rc; + + req.ppb_id = 0; + req.ld_id = 0; + /* field_1[0x3] and transaction_data contain packed config info */ + req.transaction_data = 0; + + rc = cxlmi_cmd_fmapi_send_ld_cxlio_config_request(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("send_ld_cxlio_config_request", "not supported"); + return; + } + if (rc) { + TEST_FAIL("send_ld_cxlio_config_request", rc_str(rc)); + return; + } + + TEST_PASS("send_ld_cxlio_config_request"); + if (verbose) + printf(" return_data: 0x%08x\n", rsp.return_data); +} + +static void test_send_ld_cxlio_mem_request(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_req req = {0}; + struct cxlmi_cmd_fmapi_send_ld_cxlio_mem_request_rsp rsp = {0}; + int rc; + + req.port_id = 0; + req.ld_id = 0; + req.transaction_len = 0; + req.transaction_addr = 0; + + rc = cxlmi_cmd_fmapi_send_ld_cxlio_mem_request(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("send_ld_cxlio_mem_request", "not supported"); + return; + } + if (rc) { + TEST_FAIL("send_ld_cxlio_mem_request", rc_str(rc)); + return; + } + + TEST_PASS("send_ld_cxlio_mem_request"); + if (verbose) + printf(" return_size: %u\n", rsp.return_size); +} + +/* + * MLD Component Commands + */ + +static void test_get_ld_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_ld_info_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_ld_info(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_ld_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_ld_info", rc_str(rc)); + return; + } + + TEST_PASS("get_ld_info"); + if (verbose) { + printf(" memory_size: %lu MB\n", + (unsigned long)(rsp.memory_size * 256)); + printf(" ld_count: %u\n", rsp.ld_count); + } +} + +static void test_get_ld_allocations(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req req = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->ld_allocation_list[0])); + if (!rsp) { + TEST_FAIL("get_ld_allocations", "allocation failed"); + return; + } + + req.start_ld_id = 0; + req.ld_allocation_list_limit = 16; + + rc = cxlmi_cmd_fmapi_get_ld_allocations(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_ld_allocations", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_ld_allocations", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_ld_allocations"); + if (verbose) { + printf(" number_ld: %u\n", rsp->number_ld); + printf(" memory_granularity: %u\n", rsp->memory_granularity); + printf(" ld_allocation_list_len: %u\n", + rsp->ld_allocation_list_len); + } + free(rsp); +} + +static void test_set_ld_allocations(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_ld_allocations_req get_req = {0}; + struct cxlmi_cmd_fmapi_get_ld_allocations_rsp *get_rsp; + struct cxlmi_cmd_fmapi_set_ld_allocations_req *set_req; + struct cxlmi_cmd_fmapi_set_ld_allocations_rsp *set_rsp; + int rc; + + get_rsp = calloc(1, sizeof(*get_rsp) + 16 * sizeof(get_rsp->ld_allocation_list[0])); + if (!get_rsp) { + TEST_FAIL("set_ld_allocations", "allocation failed"); + return; + } + + get_req.start_ld_id = 0; + get_req.ld_allocation_list_limit = 16; + + rc = cxlmi_cmd_fmapi_get_ld_allocations(ep, get_tunnel_info(), &get_req, get_rsp); + if (is_unsupported(rc) || get_rsp->number_ld == 0) { + TEST_SKIP("set_ld_allocations", "get not supported or no LDs"); + free(get_rsp); + return; + } + + /* Set same allocations (safe operation) */ + set_req = calloc(1, sizeof(*set_req) + get_rsp->number_ld * sizeof(set_req->ld_allocation_list[0])); + set_rsp = calloc(1, sizeof(*set_rsp) + get_rsp->number_ld * sizeof(set_rsp->ld_allocation_list[0])); + if (!set_req || !set_rsp) { + TEST_FAIL("set_ld_allocations", "allocation failed"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + set_req->number_ld = get_rsp->number_ld; + set_req->start_ld_id = 0; + memcpy(set_req->ld_allocation_list, get_rsp->ld_allocation_list, + get_rsp->number_ld * sizeof(get_rsp->ld_allocation_list[0])); + + rc = cxlmi_cmd_fmapi_set_ld_allocations(ep, get_tunnel_info(), set_req, set_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_ld_allocations", "not supported"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + if (rc) { + TEST_FAIL("set_ld_allocations", rc_str(rc)); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + TEST_PASS("set_ld_allocations"); + free(get_rsp); + free(set_req); + free(set_rsp); +} + +/* + * QoS Commands + */ + +static void test_get_qos_control(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_control_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_qos_control(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_qos_control", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_qos_control", rc_str(rc)); + return; + } + + TEST_PASS("get_qos_control"); + if (verbose) { + printf(" qos_telemetry_control: 0x%02x\n", + rsp.qos_telemetry_control); + printf(" egress_moderate_percentage: %u%%\n", + rsp.egress_moderate_percentage); + printf(" egress_severe_percentage: %u%%\n", + rsp.egress_severe_percentage); + printf(" backpressure_sample_interval: %u\n", + rsp.backpressure_sample_interval); + } +} + +static void test_set_qos_control(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_control_rsp get_rsp = {0}; + struct cxlmi_cmd_fmapi_set_qos_control_req req = {0}; + struct cxlmi_cmd_fmapi_set_qos_control_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_qos_control(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_control", "get not supported"); + return; + } + + /* Set same values (safe operation) */ + req.qos_telemetry_control = get_rsp.qos_telemetry_control; + req.egress_moderate_percentage = get_rsp.egress_moderate_percentage; + req.egress_severe_percentage = get_rsp.egress_severe_percentage; + req.backpressure_sample_interval = get_rsp.backpressure_sample_interval; + req.completion_collection_interval = get_rsp.completion_collection_interval; + + rc = cxlmi_cmd_fmapi_set_qos_control(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_control", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_qos_control", rc_str(rc)); + return; + } + + TEST_PASS("set_qos_control"); + if (verbose) { + printf(" qos_telemetry_control: 0x%02x\n", + rsp.qos_telemetry_control); + printf(" egress_moderate_percentage: %u\n", + rsp.egress_moderate_percentage); + printf(" egress_severe_percentage: %u\n", + rsp.egress_severe_percentage); + printf(" backpressure_sample_interval: %u\n", + rsp.backpressure_sample_interval); + printf(" completion_collection_interval: %u\n", + rsp.completion_collection_interval); + } +} + +static void test_get_qos_status(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_status_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_qos_status(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_qos_status", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_qos_status", rc_str(rc)); + return; + } + + TEST_PASS("get_qos_status"); + if (verbose) + printf(" backpressure_avg_percentage: %u%%\n", + rsp.backpressure_avg_percentage); +} + +static void test_get_qos_allocated_bw(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req req = {0}; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->qos_allocation_fraction[0])); + if (!rsp) { + TEST_FAIL("get_qos_allocated_bw", "allocation failed"); + return; + } + + req.number_ld = 16; + req.start_ld_id = 0; + + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_qos_allocated_bw", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_qos_allocated_bw", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_qos_allocated_bw"); + if (verbose) { + printf(" number_ld: %u\n", rsp->number_ld); + printf(" start_ld_id: %u\n", rsp->start_ld_id); + } + free(rsp); +} + +static void test_set_qos_allocated_bw(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_req get_req = {0}; + struct cxlmi_cmd_fmapi_get_qos_allocated_bw_rsp *get_rsp; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_req *set_req; + struct cxlmi_cmd_fmapi_set_qos_allocated_bw_rsp *set_rsp; + int rc; + + get_rsp = calloc(1, sizeof(*get_rsp) + 16 * sizeof(get_rsp->qos_allocation_fraction[0])); + if (!get_rsp) { + TEST_FAIL("set_qos_allocated_bw", "allocation failed"); + return; + } + + get_req.number_ld = 16; + get_req.start_ld_id = 0; + + rc = cxlmi_cmd_fmapi_get_qos_allocated_bw(ep, get_tunnel_info(), &get_req, get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_allocated_bw", "get not supported"); + free(get_rsp); + return; + } + + set_req = calloc(1, sizeof(*set_req) + get_rsp->number_ld * sizeof(set_req->qos_allocation_fraction[0])); + set_rsp = calloc(1, sizeof(*set_rsp) + get_rsp->number_ld * sizeof(set_rsp->qos_allocation_fraction[0])); + if (!set_req || !set_rsp) { + TEST_FAIL("set_qos_allocated_bw", "allocation failed"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + set_req->number_ld = get_rsp->number_ld; + set_req->start_ld_id = 0; + memcpy(set_req->qos_allocation_fraction, get_rsp->qos_allocation_fraction, + get_rsp->number_ld * sizeof(get_rsp->qos_allocation_fraction[0])); + + rc = cxlmi_cmd_fmapi_set_qos_allocated_bw(ep, get_tunnel_info(), set_req, set_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_allocated_bw", "not supported"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + if (rc) { + TEST_FAIL("set_qos_allocated_bw", rc_str(rc)); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + TEST_PASS("set_qos_allocated_bw"); + if (verbose) { + printf(" number_ld: %u\n", set_rsp->number_ld); + printf(" start_ld_id: %u\n", set_rsp->start_ld_id); + } + free(get_rsp); + free(set_req); + free(set_rsp); +} + +static void test_get_qos_bw_limit(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req req = {0}; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->qos_limit_fraction[0])); + if (!rsp) { + TEST_FAIL("get_qos_bw_limit", "allocation failed"); + return; + } + + req.number_ld = 16; + req.start_ld_id = 0; + + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_qos_bw_limit", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_qos_bw_limit", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_qos_bw_limit"); + if (verbose) { + printf(" number_ld: %u\n", rsp->number_ld); + printf(" start_ld_id: %u\n", rsp->start_ld_id); + } + free(rsp); +} + +static void test_set_qos_bw_limit(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_qos_bw_limit_req get_req = {0}; + struct cxlmi_cmd_fmapi_get_qos_bw_limit_rsp *get_rsp; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_req *set_req; + struct cxlmi_cmd_fmapi_set_qos_bw_limit_rsp *set_rsp; + int rc; + + get_rsp = calloc(1, sizeof(*get_rsp) + 16 * sizeof(get_rsp->qos_limit_fraction[0])); + if (!get_rsp) { + TEST_FAIL("set_qos_bw_limit", "allocation failed"); + return; + } + + get_req.number_ld = 16; + get_req.start_ld_id = 0; + + rc = cxlmi_cmd_fmapi_get_qos_bw_limit(ep, get_tunnel_info(), &get_req, get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_bw_limit", "get not supported"); + free(get_rsp); + return; + } + + set_req = calloc(1, sizeof(*set_req) + get_rsp->number_ld * sizeof(set_req->qos_limit_fraction[0])); + set_rsp = calloc(1, sizeof(*set_rsp) + get_rsp->number_ld * sizeof(set_rsp->qos_limit_fraction[0])); + if (!set_req || !set_rsp) { + TEST_FAIL("set_qos_bw_limit", "allocation failed"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + set_req->number_ld = get_rsp->number_ld; + set_req->start_ld_id = 0; + memcpy(set_req->qos_limit_fraction, get_rsp->qos_limit_fraction, + get_rsp->number_ld * sizeof(get_rsp->qos_limit_fraction[0])); + + rc = cxlmi_cmd_fmapi_set_qos_bw_limit(ep, get_tunnel_info(), set_req, set_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_qos_bw_limit", "not supported"); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + if (rc) { + TEST_FAIL("set_qos_bw_limit", rc_str(rc)); + free(get_rsp); + free(set_req); + free(set_rsp); + return; + } + + TEST_PASS("set_qos_bw_limit"); + if (verbose) { + printf(" number_ld: %u\n", set_rsp->number_ld); + printf(" start_ld_id: %u\n", set_rsp->start_ld_id); + } + free(get_rsp); + free(set_req); + free(set_rsp); +} + +/* + * Multi-Headed Device Commands + */ + +static void test_get_multiheaded_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_multiheaded_info_req req = {0}; + struct cxlmi_cmd_fmapi_get_multiheaded_info_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16); /* Allow for ld_map array */ + if (!rsp) { + TEST_FAIL("get_multiheaded_info", "allocation failed"); + return; + } + + req.start_ld_id = 0; + req.ld_map_list_limit = 8; + + rc = cxlmi_cmd_fmapi_get_multiheaded_info(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_multiheaded_info", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_multiheaded_info", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_multiheaded_info"); + if (verbose) { + printf(" num_heads: %u\n", rsp->num_heads); + printf(" num_lds: %u\n", rsp->num_lds); + } + free(rsp); +} + +static void test_get_head_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_head_info_req req = {0}; + struct cxlmi_cmd_fmapi_get_head_info_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 8 * sizeof(rsp->head_info_list[0])); + if (!rsp) { + TEST_FAIL("get_head_info", "allocation failed"); + return; + } + + req.start_head = 0; + req.num_heads = 8; + + rc = cxlmi_cmd_fmapi_get_head_info(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_head_info", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_head_info", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_head_info"); + if (verbose) + printf(" num_heads: %u\n", rsp->num_heads); + free(rsp); +} + +/* + * Dynamic Capacity Commands + */ + +static void test_get_dcd_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_dcd_info_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_fmapi_get_dcd_info(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_dcd_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_dcd_info", rc_str(rc)); + return; + } + + TEST_PASS("get_dcd_info"); + if (verbose) { + printf(" num_hosts: %u\n", rsp.num_hosts); + printf(" num_supported_dc_regions: %u\n", + rsp.num_supported_dc_regions); + printf(" total_dynamic_capacity: %lu MB\n", + (unsigned long)(rsp.total_dynamic_capacity * 256)); + } +} + +static void test_get_dc_reg_config(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_host_dc_region_config_req req = {0}; + struct cxlmi_cmd_fmapi_get_host_dc_region_config_rsp rsp = {0}; + int rc; + + req.host_id = 0; + req.region_cnt = 8; + req.start_region_id = 0; + + rc = cxlmi_cmd_fmapi_get_dc_reg_config(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_dc_reg_config", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_dc_reg_config", rc_str(rc)); + return; + } + + TEST_PASS("get_dc_reg_config"); + if (verbose) { + printf(" host_id: %u\n", rsp.host_id); + printf(" num_regions: %u\n", rsp.num_regions); + printf(" regions_returned: %u\n", rsp.regions_returned); + } +} + +static void test_set_dc_region_config(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_set_dc_region_config_req req = {0}; + int rc; + + req.region_id = 0; + req.block_sz = 0; /* Use default block size */ + req.sanitize_on_release = 0; + + rc = cxlmi_cmd_fmapi_set_dc_region_config(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("set_dc_region_config", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_dc_region_config", rc_str(rc)); + return; + } + + TEST_PASS("set_dc_region_config"); +} + +static void test_get_dc_region_ext_list(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_req req = {0}; + struct cxlmi_cmd_fmapi_get_dc_region_ext_list_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 8 * sizeof(rsp->extents[0])); + if (!rsp) { + TEST_FAIL("get_dc_region_ext_list", "allocation failed"); + return; + } + + req.host_id = 0; + req.extent_count = 8; + req.start_ext_index = 0; + + rc = cxlmi_cmd_fmapi_get_dc_region_ext_list(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_dc_region_ext_list", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_dc_region_ext_list", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_dc_region_ext_list"); + if (verbose) { + printf(" host_id: %u\n", rsp->host_id); + printf(" extents_returned: %u\n", rsp->extents_returned); + printf(" total_extents: %u\n", rsp->total_extents); + } + free(rsp); +} + +static void test_initiate_dc_add(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_initiate_dc_add_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("initiate_dc_add", "allocation failed"); + return; + } + + req->host_id = 0; + req->selection_policy = 0; + req->region_num = 0; + req->length = 0; + req->ext_count = 0; + + rc = cxlmi_cmd_fmapi_initiate_dc_add(ep, get_tunnel_info(), req); + if (is_unsupported(rc)) { + TEST_SKIP("initiate_dc_add", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("initiate_dc_add", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("initiate_dc_add"); + free(req); +} + +static void test_initiate_dc_release(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_initiate_dc_release_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("initiate_dc_release", "allocation failed"); + return; + } + + req->host_id = 0; + req->flags = 0; + req->length = 0; + req->ext_count = 0; + + rc = cxlmi_cmd_fmapi_initiate_dc_release(ep, get_tunnel_info(), req); + if (is_unsupported(rc)) { + TEST_SKIP("initiate_dc_release", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("initiate_dc_release", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("initiate_dc_release"); + free(req); +} + +static void test_dc_add_reference(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_dc_add_ref_req req = {0}; + int rc; + + /* tag[0x10] is the only field - zeroed by default */ + memset(req.tag, 0, sizeof(req.tag)); + + rc = cxlmi_cmd_fmapi_dc_add_reference(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("dc_add_reference", "not supported"); + return; + } + if (rc) { + TEST_FAIL("dc_add_reference", rc_str(rc)); + return; + } + + TEST_PASS("dc_add_reference"); +} + +static void test_dc_remove_reference(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_dc_remove_ref_req req = {0}; + int rc; + + /* tag[0x10] is the only field - zeroed by default */ + memset(req.tag, 0, sizeof(req.tag)); + + rc = cxlmi_cmd_fmapi_dc_remove_reference(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("dc_remove_reference", "not supported"); + return; + } + if (rc) { + TEST_FAIL("dc_remove_reference", rc_str(rc)); + return; + } + + TEST_PASS("dc_remove_reference"); +} + +static void test_dc_list_tags(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_dc_list_tags_req req = {0}; + struct cxlmi_cmd_fmapi_dc_list_tags_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->tags_list[0])); + if (!rsp) { + TEST_FAIL("dc_list_tags", "allocation failed"); + return; + } + + req.start_idx = 0; + req.tags_count = 16; + + rc = cxlmi_cmd_fmapi_dc_list_tags(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("dc_list_tags", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("dc_list_tags", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("dc_list_tags"); + if (verbose) { + printf(" generation_num: %u\n", rsp->generation_num); + printf(" total_num_tags: %u\n", rsp->total_num_tags); + printf(" num_tags_returned: %u\n", rsp->num_tags_returned); + } + free(rsp); +} + +/* + * Tunneling Tests - Exercise commands through switch/MLD tunneling + * + * These tests verify that commands can be properly tunneled: + * - Through a CXL Switch to downstream devices (DEFINE_CXLMI_TUNNEL_SWITCH) + * - To an LD within an MLD (DEFINE_CXLMI_TUNNEL_MLD) + * - Two-level: through switch to an LD in an MLD (DEFINE_CXLMI_TUNNEL_SWITCH_MLD) + */ + +static void test_tunnel_switch_identify(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}; + DEFINE_CXLMI_TUNNEL_SWITCH(ti, tunnel_port); + int rc; + + if (tunnel_port < 0) { + TEST_SKIP("tunnel_switch_identify", "no tunnel port configured"); + return; + } + + rc = cxlmi_cmd_identify(ep, &ti, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("tunnel_switch_identify", "tunneling not supported"); + return; + } + if (rc) { + TEST_FAIL("tunnel_switch_identify", rc_str(rc)); + return; + } + + TEST_PASS("tunnel_switch_identify"); + if (verbose) { + printf(" [via port %d] vendor_id: 0x%04x\n", + tunnel_port, rsp.vendor_id); + printf(" [via port %d] device_id: 0x%04x\n", + tunnel_port, rsp.device_id); + printf(" [via port %d] component_type: %u\n", + tunnel_port, rsp.component_type); + } +} + +static void test_tunnel_switch_get_health_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_health_info_rsp rsp = {0}; + DEFINE_CXLMI_TUNNEL_SWITCH(ti, tunnel_port); + int rc; + + if (tunnel_port < 0) { + TEST_SKIP("tunnel_switch_get_health_info", "no tunnel port configured"); + return; + } + + rc = cxlmi_cmd_memdev_get_health_info(ep, &ti, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("tunnel_switch_get_health_info", "not supported via tunnel"); + return; + } + if (rc) { + TEST_FAIL("tunnel_switch_get_health_info", rc_str(rc)); + return; + } + + TEST_PASS("tunnel_switch_get_health_info"); + if (verbose) { + printf(" [via port %d] health_status: 0x%02x\n", + tunnel_port, rsp.health_status); + printf(" [via port %d] device_temperature: %d C\n", + tunnel_port, rsp.device_temperature); + } +} + +static void test_tunnel_mld_identify(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}; + DEFINE_CXLMI_TUNNEL_MLD(ti, tunnel_ld); + int rc; + + if (tunnel_ld < 0) { + TEST_SKIP("tunnel_mld_identify", "no tunnel LD configured"); + return; + } + + rc = cxlmi_cmd_identify(ep, &ti, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("tunnel_mld_identify", "MLD tunneling not supported"); + return; + } + if (rc) { + TEST_FAIL("tunnel_mld_identify", rc_str(rc)); + return; + } + + TEST_PASS("tunnel_mld_identify"); + if (verbose) { + printf(" [via LD %d] vendor_id: 0x%04x\n", + tunnel_ld, rsp.vendor_id); + printf(" [via LD %d] device_id: 0x%04x\n", + tunnel_ld, rsp.device_id); + } +} + +static void test_tunnel_switch_mld_identify(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}; + DEFINE_CXLMI_TUNNEL_SWITCH_MLD(ti, tunnel_port, tunnel_ld); + int rc; + + if (tunnel_port < 0 || tunnel_ld < 0) { + TEST_SKIP("tunnel_switch_mld_identify", "tunnel port/LD not configured"); + return; + } + + rc = cxlmi_cmd_identify(ep, &ti, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("tunnel_switch_mld_identify", "two-level tunneling not supported"); + return; + } + if (rc) { + TEST_FAIL("tunnel_switch_mld_identify", rc_str(rc)); + return; + } + + TEST_PASS("tunnel_switch_mld_identify"); + if (verbose) { + printf(" [via port %d, LD %d] vendor_id: 0x%04x\n", + tunnel_port, tunnel_ld, rsp.vendor_id); + printf(" [via port %d, LD %d] device_id: 0x%04x\n", + tunnel_port, tunnel_ld, rsp.device_id); + } +} + +static void run_tunnel_tests(struct cxlmi_endpoint *ep) +{ + printf("\n[Tunneling Tests]\n"); + + /* Test tunneling through switch to downstream device */ + test_tunnel_switch_identify(ep); + test_tunnel_switch_get_health_info(ep); + + /* Test tunneling to LD within MLD */ + test_tunnel_mld_identify(ep); + + /* Test two-level tunneling: switch -> MLD -> LD */ + test_tunnel_switch_mld_identify(ep); +} + +/* + * Auto-detect tunnel targets by querying the switch + * + * This function queries the switch to find: + * - A DSP port with a connected device (for switch tunnel tests) + * - An MLD with multiple LDs (for MLD tunnel tests) + */ +static void auto_detect_tunnel_targets(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_fmapi_identify_sw_device_rsp id = {0}; + struct cxlmi_cmd_fmapi_get_phys_port_state_req *req; + struct cxlmi_cmd_fmapi_get_phys_port_state_rsp *rsp; + int rc, num_active_ports, port_idx; + + /* Skip auto-detection if already configured */ + if (tunnel_port >= 0) + return; + + rc = cxlmi_cmd_fmapi_identify_sw_device(ep, get_tunnel_info(), &id); + if (rc) + return; + + /* Count active ports from bitmask */ + num_active_ports = 0; + for (int i = 0; i < 256; i++) { + if (id.active_port_bitmask[i / 8] & (1 << (i % 8))) + num_active_ports++; + } + + if (num_active_ports == 0) + return; + + req = calloc(1, sizeof(*req) + num_active_ports); + rsp = calloc(1, sizeof(*rsp) + num_active_ports * sizeof(rsp->ports[0])); + if (!req || !rsp) { + free(req); + free(rsp); + return; + } + + /* Request only active ports based on bitmask */ + req->num_ports = num_active_ports; + port_idx = 0; + for (int i = 0; i < 256 && port_idx < num_active_ports; i++) { + if (id.active_port_bitmask[i / 8] & (1 << (i % 8))) + req->ports[port_idx++] = i; + } + + rc = cxlmi_cmd_fmapi_get_phys_port_state(ep, get_tunnel_info(), req, rsp); + if (rc) { + free(req); + free(rsp); + return; + } + + /* Find first DSP with a CXL device attached */ + for (int i = 0; i < rsp->num_ports; i++) { + /* config_state == 3 means DSP (Downstream Port) */ + if (rsp->ports[i].config_state == 3 && + rsp->ports[i].conn_dev_type >= 2) { /* CXL Type 1/2/3 */ + tunnel_port = rsp->ports[i].port_id; + if (verbose) + printf(" Auto-detected tunnel port: %d (device type %u)\n", + tunnel_port, rsp->ports[i].conn_dev_type); + + /* If it's an MLD (Type 5), also set tunnel_ld */ + if (rsp->ports[i].conn_dev_type == 5 && + rsp->ports[i].supported_ld_count > 1) { + tunnel_ld = 0; /* Start with LD 0 */ + if (verbose) + printf(" Auto-detected MLD with %u LDs\n", + rsp->ports[i].supported_ld_count); + } + break; + } + } + + free(req); + free(rsp); +} + +static void run_tests(struct cxlmi_endpoint *ep) +{ + printf("\n[FM-API Command Set]\n"); + + /* Physical Switch Commands */ + test_identify_sw_device(ep); + test_get_phys_port_state(ep); + test_phys_port_control(ep); + test_send_ppb_cxlio_config_request(ep); + + /* Domain Validation Commands */ + test_get_domain_validation_sv_state(ep); + test_set_domain_validation_sv(ep); + test_get_vcs_domain_validation_sv_state(ep); + test_get_domain_validation_sv(ep); + + /* Virtual Switch Commands */ + test_bind_vppb(ep); + test_unbind_vppb(ep); + + /* MLD Port Commands */ + test_send_ld_cxlio_config_request(ep); + test_send_ld_cxlio_mem_request(ep); + + /* MLD Component Commands */ + test_get_ld_info(ep); + test_get_ld_allocations(ep); + test_set_ld_allocations(ep); + + /* QoS Commands */ + test_get_qos_control(ep); + test_set_qos_control(ep); + test_get_qos_status(ep); + test_get_qos_allocated_bw(ep); + test_set_qos_allocated_bw(ep); + test_get_qos_bw_limit(ep); + test_set_qos_bw_limit(ep); + + /* Multi-Headed Device Commands */ + test_get_multiheaded_info(ep); + test_get_head_info(ep); + + /* Dynamic Capacity Commands */ + test_get_dcd_info(ep); + test_get_dc_reg_config(ep); + test_set_dc_region_config(ep); + test_get_dc_region_ext_list(ep); + test_initiate_dc_add(ep); + test_initiate_dc_release(ep); + test_dc_add_reference(ep); + test_dc_remove_reference(ep); + test_dc_list_tags(ep); + + /* Auto-detect tunnel targets and run tunneling tests */ + auto_detect_tunnel_targets(ep); + run_tunnel_tests(ep); +} + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options] \n", progname); + print_target_usage(); + print_tunnel_usage(); + fprintf(stderr, "\nGeneral Options:\n"); + fprintf(stderr, " -v, --verbose Show detailed response data\n"); + fprintf(stderr, " -h, --help Show this help\n"); + fprintf(stderr, "\nNotes:\n"); + fprintf(stderr, " If no tunneling options specified, auto-detection is attempted.\n"); +} + +int main(int argc, char **argv) +{ + struct cxlmi_ctx *ctx; + struct cxlmi_endpoint *ep; + const char *target = NULL; + int rc = 1; + int argidx; + + for (argidx = 1; argidx < argc; argidx++) { + int consumed; + + if (strcmp(argv[argidx], "-h") == 0 || + strcmp(argv[argidx], "--help") == 0) { + usage(argv[0]); + return 0; + } + if (strcmp(argv[argidx], "-v") == 0 || + strcmp(argv[argidx], "--verbose") == 0) { + verbose = true; + continue; + } + consumed = parse_tunnel_arg(argc, argv, argidx); + if (consumed) { + argidx += consumed - 1; + continue; + } + if (argv[argidx][0] == '-') { + fprintf(stderr, "Unknown option: %s\n", argv[argidx]); + usage(argv[0]); + return 1; + } + target = argv[argidx]; + } + + if (!target) { + usage(argv[0]); + return 1; + } + + printf("========================================\n"); + printf(" libcxlmi FM-API Command Set Tests\n"); + printf("========================================\n"); + printf("Target: %s\n", target); + print_tunnel_config(); + + ctx = cxlmi_new_ctx(stdout, LOG_WARNING); + if (!ctx) { + fprintf(stderr, "Failed to create context\n"); + return 1; + } + + ep = open_endpoint(ctx, target); + if (!ep) { + fprintf(stderr, "Failed to open endpoint: %s\n", target); + cxlmi_free_ctx(ctx); + return 1; + } + + run_tests(ep); + + TEST_PRINT_RESULTS(); + rc = TEST_EXIT_CODE(); + cxlmi_close(ep); + cxlmi_free_ctx(ctx); + return rc; +} diff --git a/tests/cxl-test-generic.c b/tests/cxl-test-generic.c new file mode 100644 index 0000000..9078617 --- /dev/null +++ b/tests/cxl-test-generic.c @@ -0,0 +1,1125 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * CXL Generic Command Set unit tests for libcxlmi + * + * Tests generic commands (CXL r3.1 Section 8.2.9.1) against a CXL device. + * Supports tunneling through a CXL switch with -p option. + */ + +#include "test-common.h" + +TEST_DECLARE_COUNTERS; +TEST_DECLARE_TUNNEL_CONFIG; + +/* + * Device Info and Status Commands + */ + +static void test_identify(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_identify_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_identify(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("identify", "not supported"); + return; + } + if (rc) { + TEST_FAIL("identify", rc_str(rc)); + return; + } + + TEST_PASS("identify"); + if (verbose) { + printf(" vendor_id: 0x%04x\n", rsp.vendor_id); + printf(" device_id: 0x%04x\n", rsp.device_id); + printf(" subsys_vendor_id: 0x%04x\n", rsp.subsys_vendor_id); + printf(" subsys_id: 0x%04x\n", rsp.subsys_id); + printf(" serial_num: 0x%016lx\n", (unsigned long)rsp.serial_num); + printf(" component_type: %u\n", rsp.component_type); + } +} + +static void test_bg_op_status(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_bg_op_status_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_bg_op_status(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("bg_op_status", "not supported"); + return; + } + if (rc) { + TEST_FAIL("bg_op_status", rc_str(rc)); + return; + } + + TEST_PASS("bg_op_status"); + if (verbose) { + printf(" status: %u\n", rsp.status); + printf(" opcode: 0x%04x\n", rsp.opcode); + printf(" returncode: %u\n", rsp.returncode); + printf(" vendor_ext_status: 0x%04x\n", rsp.vendor_ext_status); + } +} + +static void test_get_response_msg_limit(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_response_msg_limit_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_response_msg_limit(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_response_msg_limit", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_response_msg_limit", rc_str(rc)); + return; + } + + TEST_PASS("get_response_msg_limit"); + if (verbose) + printf(" limit: %u bytes\n", rsp.limit); +} + +static void test_set_response_msg_limit(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_response_msg_limit_rsp get_rsp = {0}; + struct cxlmi_cmd_set_response_msg_limit_req set_req = {0}; + struct cxlmi_cmd_set_response_msg_limit_rsp set_rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_response_msg_limit(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_response_msg_limit", "get not supported"); + return; + } + + set_req.limit = get_rsp.limit; + rc = cxlmi_cmd_set_response_msg_limit(ep, get_tunnel_info(), &set_req, &set_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_response_msg_limit", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_response_msg_limit", rc_str(rc)); + return; + } + + TEST_PASS("set_response_msg_limit"); +} + +static void test_request_bg_op_abort(struct cxlmi_endpoint *ep) +{ + int rc; + + rc = cxlmi_cmd_request_bg_op_abort(ep, get_tunnel_info()); + if (is_unsupported(rc)) { + TEST_SKIP("request_bg_op_abort", "not supported"); + return; + } + if (rc && rc != CXLMI_RET_NO_BGABORT) { + TEST_FAIL("request_bg_op_abort", rc_str(rc)); + return; + } + + TEST_PASS("request_bg_op_abort"); +} + +/* + * Timestamp Commands + */ + +static void test_get_timestamp(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_timestamp_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_timestamp(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_timestamp", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_timestamp", rc_str(rc)); + return; + } + + TEST_PASS("get_timestamp"); + if (verbose) + printf(" timestamp: %lu\n", (unsigned long)rsp.timestamp); +} + +static void test_set_timestamp(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_timestamp_rsp get_rsp = {0}; + struct cxlmi_cmd_set_timestamp_req set_req = {0}; + int rc; + + rc = cxlmi_cmd_get_timestamp(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_timestamp", "not supported"); + return; + } + + set_req.timestamp = get_rsp.timestamp; + rc = cxlmi_cmd_set_timestamp(ep, get_tunnel_info(), &set_req); + if (is_unsupported(rc)) { + TEST_SKIP("set_timestamp", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_timestamp", rc_str(rc)); + return; + } + + TEST_PASS("set_timestamp"); +} + +/* + * Event Commands + */ + +static void test_get_event_records(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_event_records_req req = {0}; + struct cxlmi_cmd_get_event_records_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->records[0])); + if (!rsp) { + TEST_FAIL("get_event_records", "allocation failed"); + return; + } + + req.event_log = 0; + rc = cxlmi_cmd_get_event_records(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_event_records", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_event_records", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_event_records"); + if (verbose) { + printf(" flags: 0x%02x\n", rsp->flags); + printf(" overflow_err_count: %u\n", rsp->overflow_err_count); + printf(" record_count: %u\n", rsp->record_count); + } + free(rsp); +} + +static void test_clear_event_records(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_clear_event_records_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("clear_event_records", "allocation failed"); + return; + } + + req->event_log = 0; + req->clear_flags = 1; /* Clear all */ + req->nr_recs = 0; + + rc = cxlmi_cmd_clear_event_records(ep, get_tunnel_info(), req); + if (is_unsupported(rc)) { + TEST_SKIP("clear_event_records", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("clear_event_records", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("clear_event_records"); + free(req); +} + +static void test_get_event_interrupt_policy(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_event_interrupt_policy_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_event_interrupt_policy(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_event_interrupt_policy", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_event_interrupt_policy", rc_str(rc)); + return; + } + + TEST_PASS("get_event_interrupt_policy"); + if (verbose) { + printf(" informational_settings: 0x%02x\n", + rsp.informational_settings); + printf(" warning_settings: 0x%02x\n", + rsp.warning_settings); + printf(" failure_settings: 0x%02x\n", + rsp.failure_settings); + printf(" fatal_settings: 0x%02x\n", + rsp.fatal_settings); +#ifndef SUPPORT_CXL_2_0 + printf(" dcd_settings: 0x%02x\n", + rsp.dcd_settings); +#endif + } +} + +static void test_set_event_interrupt_policy(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_event_interrupt_policy_rsp get_rsp = {0}; + struct cxlmi_cmd_set_event_interrupt_policy_req set_req = {0}; + int rc; + + rc = cxlmi_cmd_get_event_interrupt_policy(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_event_interrupt_policy", "get not supported"); + return; + } + + set_req.informational_settings = get_rsp.informational_settings; + set_req.warning_settings = get_rsp.warning_settings; + set_req.failure_settings = get_rsp.failure_settings; + set_req.fatal_settings = get_rsp.fatal_settings; +#ifndef SUPPORT_CXL_2_0 + set_req.dcd_settings = get_rsp.dcd_settings; +#endif + + rc = cxlmi_cmd_set_event_interrupt_policy(ep, get_tunnel_info(), &set_req); + if (is_unsupported(rc)) { + TEST_SKIP("set_event_interrupt_policy", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_event_interrupt_policy", rc_str(rc)); + return; + } + + TEST_PASS("set_event_interrupt_policy"); +} + +/* + * Firmware Commands + */ + +static void test_get_fw_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_fw_info_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_fw_info(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_fw_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_fw_info", rc_str(rc)); + return; + } + + TEST_PASS("get_fw_info"); + if (verbose) { + printf(" slots_supported: %u\n", rsp.slots_supported); + printf(" slot_info: 0x%02x (active=%u, staged=%u)\n", + rsp.slot_info, rsp.slot_info & 0x7, (rsp.slot_info >> 3) & 0x7); + printf(" caps: 0x%02x\n", rsp.caps); + if (rsp.slots_supported >= 1) + printf(" slot1_fw_rev: %.16s\n", rsp.fw_rev1); + if (rsp.slots_supported >= 2) + printf(" slot2_fw_rev: %.16s\n", rsp.fw_rev2); + if (rsp.slots_supported >= 3) + printf(" slot3_fw_rev: %.16s\n", rsp.fw_rev3); + if (rsp.slots_supported >= 4) + printf(" slot4_fw_rev: %.16s\n", rsp.fw_rev4); + } +} + +static void test_transfer_fw(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_fw_info_rsp fw_info = {0}; + struct cxlmi_cmd_transfer_fw_req *req; + uint8_t slot; + int rc; + + /* Get FW info to find a valid slot */ + rc = cxlmi_cmd_get_fw_info(ep, get_tunnel_info(), &fw_info); + if (is_unsupported(rc)) { + TEST_SKIP("transfer_fw", "get_fw_info not supported"); + return; + } + + /* Use the next available slot, or slot 1 if only one slot */ + if (fw_info.slots_supported > 1) + slot = ((fw_info.slot_info & 0x7) % fw_info.slots_supported) + 1; + else + slot = 1; + + req = calloc(1, sizeof(*req) + 128); + if (!req) { + TEST_FAIL("transfer_fw", "allocation failed"); + return; + } + + req->action = 0; /* Full transfer */ + req->slot = slot; + req->offset = 0; + + rc = cxlmi_cmd_transfer_fw(ep, get_tunnel_info(), req, 128); + if (is_unsupported(rc)) { + TEST_SKIP("transfer_fw", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("transfer_fw", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("transfer_fw"); + free(req); +} + +static void test_activate_fw(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_fw_info_rsp fw_info = {0}; + struct cxlmi_cmd_activate_fw_req req = {0}; + int rc; + + rc = cxlmi_cmd_get_fw_info(ep, get_tunnel_info(), &fw_info); + if (is_unsupported(rc)) { + TEST_SKIP("activate_fw", "get_fw_info not supported"); + return; + } + + /* Activate the currently active slot (no-op) */ + req.action = 0; /* Online activation */ + req.slot = (fw_info.slot_info & 0x7); + + rc = cxlmi_cmd_activate_fw(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("activate_fw", "not supported"); + return; + } + if (rc) { + TEST_FAIL("activate_fw", rc_str(rc)); + return; + } + + TEST_PASS("activate_fw"); +} + +/* + * Log Commands + */ + +static void test_get_supported_logs(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_logs_rsp *rsp; + int rc, i; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->entries[0])); + if (!rsp) { + TEST_FAIL("get_supported_logs", "allocation failed"); + return; + } + + rc = cxlmi_cmd_get_supported_logs(ep, get_tunnel_info(), rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_supported_logs", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_supported_logs", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_supported_logs"); + if (verbose) { + printf(" num_supported_log_entries: %u\n", + rsp->num_supported_log_entries); + for (i = 0; i < rsp->num_supported_log_entries; i++) { + printf(" [%d] uuid: ", i); + for (int j = 0; j < 16; j++) + printf("%02x", rsp->entries[i].uuid[j]); + printf(", size: %u\n", rsp->entries[i].log_size); + } + } + free(rsp); +} + +static void test_get_supported_logs_sublist(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_logs_sublist_req req = {0}; + struct cxlmi_cmd_get_supported_logs_sublist_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->entries[0])); + if (!rsp) { + TEST_FAIL("get_supported_logs_sublist", "allocation failed"); + return; + } + + req.max_supported_log_entries = 16; + req.start_log_entry_index = 0; + + rc = cxlmi_cmd_get_supported_logs_sublist(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_supported_logs_sublist", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_supported_logs_sublist", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_supported_logs_sublist"); + if (verbose) { + printf(" num_supported_log_entries: %u\n", + rsp->num_supported_log_entries); + printf(" total_num_supported_log_entries: %u\n", + rsp->total_num_supported_log_entries); + } + free(rsp); +} + +static void test_get_log_cel(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_logs_rsp *gsl; + struct cxlmi_cmd_get_log_req req = {0}; + struct cxlmi_cmd_get_log_cel_rsp *cel; + size_t cel_size = 0; + int rc, i; + + gsl = calloc(1, sizeof(*gsl) + 16 * sizeof(gsl->entries[0])); + if (!gsl) { + TEST_FAIL("get_log_cel", "allocation failed"); + return; + } + + rc = cxlmi_cmd_get_supported_logs(ep, get_tunnel_info(), gsl); + if (rc) { + TEST_SKIP("get_log_cel", "failed to get supported logs"); + free(gsl); + return; + } + + for (i = 0; i < gsl->num_supported_log_entries; i++) { + if (memcmp(gsl->entries[i].uuid, CEL_UUID, 16) == 0) { + cel_size = gsl->entries[i].log_size; + break; + } + } + free(gsl); + + if (cel_size == 0) { + TEST_SKIP("get_log_cel", "CEL not available"); + return; + } + + cel = calloc(1, sizeof(*cel) + cel_size); + if (!cel) { + TEST_FAIL("get_log_cel", "allocation failed"); + return; + } + + memcpy(req.uuid, CEL_UUID, 16); + req.offset = 0; + req.length = cel_size; + + rc = cxlmi_cmd_get_log_cel(ep, get_tunnel_info(), &req, cel); + if (rc) { + TEST_FAIL("get_log_cel", rc_str(rc)); + free(cel); + return; + } + + TEST_PASS("get_log_cel"); + if (verbose) { + int num_cmds = cel_size / sizeof(*cel); + printf(" cel_entries: %d\n", num_cmds); + for (i = 0; i < num_cmds; i++) { + printf(" [%3d] opcode: 0x%04x, effect: 0x%04x\n", + i, cel[i].opcode, cel[i].command_effect); + } + } + free(cel); +} + +static void test_get_log_capabilities(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_log_capabilities_req req = {0}; + struct cxlmi_cmd_get_log_capabilities_rsp rsp = {0}; + int rc; + + memcpy(req.uuid, CEL_UUID, 16); + + rc = cxlmi_cmd_get_log_capabilities(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_log_capabilities", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_log_capabilities", rc_str(rc)); + return; + } + + TEST_PASS("get_log_capabilities"); + if (verbose) + printf(" parameter_flags: 0x%08x\n", rsp.parameter_flags); +} + +static void test_clear_log(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_clear_log_req req = {0}; + int rc; + + memcpy(req.uuid, VENDOR_DEBUG_UUID, 16); + + rc = cxlmi_cmd_clear_log(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("clear_log", "not supported"); + return; + } + if (rc == CXLMI_RET_LOG) { + TEST_SKIP("clear_log", "log not available"); + return; + } + if (rc) { + TEST_FAIL("clear_log", rc_str(rc)); + return; + } + + TEST_PASS("clear_log"); +} + +static void test_populate_log(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_populate_log_req req = {0}; + int retries = 3; + int rc; + + memcpy(req.uuid, VENDOR_DEBUG_UUID, 16); + + do { + rc = cxlmi_cmd_populate_log(ep, get_tunnel_info(), &req); + + /* If device is busy with another bg op, wait and retry */ + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) { + /* If bg_op_status not supported, just sleep */ + usleep(500000); + } + retries--; + continue; + } + break; + } while (retries > 0); + + if (is_unsupported(rc)) { + TEST_SKIP("populate_log", "not supported"); + return; + } + if (rc == CXLMI_RET_LOG) { + TEST_SKIP("populate_log", "log not available"); + return; + } + if (rc == CXLMI_RET_BUSY) { + TEST_SKIP("populate_log", "device busy timeout"); + return; + } + if (is_bg_success(rc)) { + TEST_PASS("populate_log"); + return; + } + TEST_FAIL("populate_log", rc_str(rc)); +} + +/* + * Feature Commands + */ + +static void test_get_supported_features(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_features_req req = {0}; + struct cxlmi_cmd_get_supported_features_rsp *rsp; + int rc, i; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->supported_feature_entries[0])); + if (!rsp) { + TEST_FAIL("get_supported_features", "allocation failed"); + return; + } + + req.count = 16; + req.starting_feature_index = 0; + + rc = cxlmi_cmd_get_supported_features(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_supported_features", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_supported_features", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_supported_features"); + if (verbose) { + printf(" num_supported_feature_entries: %u\n", + rsp->num_supported_feature_entries); + printf(" device_supported_features: 0x%04x\n", + rsp->device_supported_features); + for (i = 0; i < rsp->num_supported_feature_entries; i++) { + printf(" [%d] uuid: ", i); + for (int j = 0; j < 16; j++) + printf("%02x", rsp->supported_feature_entries[i].feature_id[j]); + printf("\n get_size: %u, set_size: %u, flags: 0x%08x\n", + rsp->supported_feature_entries[i].get_feature_size, + rsp->supported_feature_entries[i].set_feature_size, + rsp->supported_feature_entries[i].attribute_flags); + } + } + free(rsp); +} + +static void test_get_feature(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_features_req sf_req = {0}; + struct cxlmi_cmd_get_supported_features_rsp *sf_rsp; + struct cxlmi_cmd_get_feature_req req = {0}; + struct cxlmi_cmd_get_feature_rsp *rsp; + int rc; + + sf_rsp = calloc(1, sizeof(*sf_rsp) + 16 * sizeof(sf_rsp->supported_feature_entries[0])); + if (!sf_rsp) { + TEST_FAIL("get_feature", "allocation failed"); + return; + } + + sf_req.count = 16; + sf_req.starting_feature_index = 0; + + rc = cxlmi_cmd_get_supported_features(ep, get_tunnel_info(), &sf_req, sf_rsp); + if (is_unsupported(rc) || sf_rsp->num_supported_feature_entries == 0) { + TEST_SKIP("get_feature", "no features supported"); + free(sf_rsp); + return; + } + + rsp = calloc(1, sizeof(*rsp) + sf_rsp->supported_feature_entries[0].get_feature_size); + if (!rsp) { + TEST_FAIL("get_feature", "allocation failed"); + free(sf_rsp); + return; + } + + memcpy(req.feature_id, sf_rsp->supported_feature_entries[0].feature_id, 16); + req.offset = 0; + req.count = sf_rsp->supported_feature_entries[0].get_feature_size; + req.selection = 0; + + rc = cxlmi_cmd_get_feature(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_feature", "not supported"); + free(rsp); + free(sf_rsp); + return; + } + if (rc) { + TEST_FAIL("get_feature", rc_str(rc)); + free(rsp); + free(sf_rsp); + return; + } + + TEST_PASS("get_feature"); + if (verbose) { + printf(" feature_uuid: " + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x\n", + req.feature_id[0], req.feature_id[1], + req.feature_id[2], req.feature_id[3], + req.feature_id[4], req.feature_id[5], + req.feature_id[6], req.feature_id[7], + req.feature_id[8], req.feature_id[9], + req.feature_id[10], req.feature_id[11], + req.feature_id[12], req.feature_id[13], + req.feature_id[14], req.feature_id[15]); + printf(" data_size: %u bytes\n", req.count); + } + free(rsp); + free(sf_rsp); +} + +static void test_set_feature(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_features_req sf_req = {0}; + struct cxlmi_cmd_get_supported_features_rsp *sf_rsp; + struct cxlmi_cmd_get_feature_req get_req = {0}; + struct cxlmi_cmd_get_feature_rsp *get_rsp; + struct cxlmi_cmd_set_feature_req *set_req; + size_t feature_size; + int rc, i; + + sf_rsp = calloc(1, sizeof(*sf_rsp) + 16 * sizeof(sf_rsp->supported_feature_entries[0])); + if (!sf_rsp) { + TEST_FAIL("set_feature", "allocation failed"); + return; + } + + sf_req.count = 16; + sf_req.starting_feature_index = 0; + + rc = cxlmi_cmd_get_supported_features(ep, get_tunnel_info(), &sf_req, sf_rsp); + if (is_unsupported(rc) || sf_rsp->num_supported_feature_entries == 0) { + TEST_SKIP("set_feature", "no features supported"); + free(sf_rsp); + return; + } + + /* Find a writable feature */ + for (i = 0; i < sf_rsp->num_supported_feature_entries; i++) { + if (sf_rsp->supported_feature_entries[i].set_feature_size > 0) + break; + } + if (i >= sf_rsp->num_supported_feature_entries) { + TEST_SKIP("set_feature", "no writable features"); + free(sf_rsp); + return; + } + + feature_size = sf_rsp->supported_feature_entries[i].get_feature_size; + + get_rsp = calloc(1, sizeof(*get_rsp) + feature_size); + if (!get_rsp) { + TEST_FAIL("set_feature", "allocation failed"); + free(sf_rsp); + return; + } + + memcpy(get_req.feature_id, sf_rsp->supported_feature_entries[i].feature_id, 16); + get_req.offset = 0; + get_req.count = feature_size; + get_req.selection = 0; + + rc = cxlmi_cmd_get_feature(ep, get_tunnel_info(), &get_req, get_rsp); + if (rc) { + TEST_SKIP("set_feature", "failed to get current value"); + free(get_rsp); + free(sf_rsp); + return; + } + + set_req = calloc(1, sizeof(*set_req) + feature_size); + if (!set_req) { + TEST_FAIL("set_feature", "allocation failed"); + free(get_rsp); + free(sf_rsp); + return; + } + + memcpy(set_req->feature_id, sf_rsp->supported_feature_entries[i].feature_id, 16); + set_req->set_feature_flags = 0; + set_req->offset = 0; + set_req->version = sf_rsp->supported_feature_entries[i].set_feature_version; + memcpy(set_req->feature_data, get_rsp->feature_data, feature_size); + + rc = cxlmi_cmd_set_feature(ep, get_tunnel_info(), set_req, feature_size); + if (is_unsupported(rc)) { + TEST_SKIP("set_feature", "not supported"); + free(set_req); + free(get_rsp); + free(sf_rsp); + return; + } + if (rc) { + TEST_FAIL("set_feature", rc_str(rc)); + free(set_req); + free(get_rsp); + free(sf_rsp); + return; + } + + TEST_PASS("set_feature"); + free(set_req); + free(get_rsp); + free(sf_rsp); +} + +static void test_get_mctp_event_interrupt_policy(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_mctp_event_interrupt_policy_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_get_mctp_event_interrupt_policy(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_mctp_event_interrupt_policy", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_mctp_event_interrupt_policy", rc_str(rc)); + return; + } + + TEST_PASS("get_mctp_event_interrupt_policy"); + if (verbose) + printf(" event_interrupt_settings: 0x%04x\n", + rsp.event_interrupt_settings); +} + +static void test_set_mctp_event_interrupt_policy(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_mctp_event_interrupt_policy_rsp get_rsp = {0}; + struct cxlmi_cmd_set_mctp_event_interrupt_policy_req set_req = {0}; + int rc; + + rc = cxlmi_cmd_get_mctp_event_interrupt_policy(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_mctp_event_interrupt_policy", "get not supported"); + return; + } + + /* Set same policy (safe operation) */ + set_req.event_interrupt_settings = get_rsp.event_interrupt_settings; + + rc = cxlmi_cmd_set_mctp_event_interrupt_policy(ep, get_tunnel_info(), &set_req); + if (is_unsupported(rc)) { + TEST_SKIP("set_mctp_event_interrupt_policy", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_mctp_event_interrupt_policy", rc_str(rc)); + return; + } + + TEST_PASS("set_mctp_event_interrupt_policy"); +} + +static void test_event_notification(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_event_notification_req req = {0}; + int rc; + + /* Send a no-op event notification */ + req.event = 0; + + rc = cxlmi_cmd_event_notification(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("event_notification", "not supported"); + return; + } + if (rc) { + TEST_FAIL("event_notification", rc_str(rc)); + return; + } + + TEST_PASS("event_notification"); +} + +static void test_get_log(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_supported_logs_rsp *gsl; + struct cxlmi_cmd_get_log_req req = {0}; + uint8_t *log_data; + int rc; + + gsl = calloc(1, sizeof(*gsl) + 16 * sizeof(gsl->entries[0])); + if (!gsl) { + TEST_FAIL("get_log", "allocation failed"); + return; + } + + rc = cxlmi_cmd_get_supported_logs(ep, get_tunnel_info(), gsl); + if (is_unsupported(rc) || gsl->num_supported_log_entries == 0) { + TEST_SKIP("get_log", "no logs available"); + free(gsl); + return; + } + + /* Try to read first available log */ + log_data = calloc(1, gsl->entries[0].log_size); + if (!log_data) { + TEST_FAIL("get_log", "allocation failed"); + free(gsl); + return; + } + + memcpy(req.uuid, gsl->entries[0].uuid, 16); + req.offset = 0; + req.length = gsl->entries[0].log_size; + + rc = cxlmi_cmd_get_log(ep, get_tunnel_info(), &req, log_data); + if (is_unsupported(rc)) { + TEST_SKIP("get_log", "not supported"); + free(log_data); + free(gsl); + return; + } + if (rc) { + TEST_FAIL("get_log", rc_str(rc)); + free(log_data); + free(gsl); + return; + } + + TEST_PASS("get_log"); + if (verbose) { + printf(" log_uuid: " + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x\n", + req.uuid[0], req.uuid[1], req.uuid[2], req.uuid[3], + req.uuid[4], req.uuid[5], req.uuid[6], req.uuid[7], + req.uuid[8], req.uuid[9], req.uuid[10], req.uuid[11], + req.uuid[12], req.uuid[13], req.uuid[14], req.uuid[15]); + printf(" log_size: %u bytes\n", req.length); + } + free(log_data); + free(gsl); +} + +static void run_tests(struct cxlmi_endpoint *ep) +{ + printf("\n[Generic Command Set]\n"); + + /* Device Info and Status */ + test_identify(ep); + test_bg_op_status(ep); + test_get_response_msg_limit(ep); + test_set_response_msg_limit(ep); + test_request_bg_op_abort(ep); + + /* Timestamp */ + test_get_timestamp(ep); + test_set_timestamp(ep); + + /* Events */ + test_get_event_records(ep); + test_clear_event_records(ep); + test_get_event_interrupt_policy(ep); + test_set_event_interrupt_policy(ep); + test_get_mctp_event_interrupt_policy(ep); + test_set_mctp_event_interrupt_policy(ep); + test_event_notification(ep); + + /* Firmware */ + test_get_fw_info(ep); + test_transfer_fw(ep); + test_activate_fw(ep); + + /* Logs */ + test_get_supported_logs(ep); + test_get_supported_logs_sublist(ep); + test_get_log(ep); + test_get_log_cel(ep); + test_get_log_capabilities(ep); + test_clear_log(ep); + test_populate_log(ep); + + /* Features */ + test_get_supported_features(ep); + test_get_feature(ep); + test_set_feature(ep); +} + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options] \n", progname); + print_target_usage(); + print_tunnel_usage(); + fprintf(stderr, "\nGeneral Options:\n"); + fprintf(stderr, " -v, --verbose Show detailed response data\n"); + fprintf(stderr, " -h, --help Show this help\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " %s mctp:50 Direct to device at MCTP addr 50\n", progname); + fprintf(stderr, " %s -p 2 mctp:50 Via switch port 2 to Type3 device\n", progname); + fprintf(stderr, " %s -p 2 -l 0 mctp:50 Via switch port 2 to MLD LD0 (level 2)\n", progname); +} + +int main(int argc, char **argv) +{ + struct cxlmi_ctx *ctx; + struct cxlmi_endpoint *ep; + const char *target = NULL; + int rc = 1; + int argidx; + + for (argidx = 1; argidx < argc; argidx++) { + int consumed; + + if (strcmp(argv[argidx], "-h") == 0 || + strcmp(argv[argidx], "--help") == 0) { + usage(argv[0]); + return 0; + } + if (strcmp(argv[argidx], "-v") == 0 || + strcmp(argv[argidx], "--verbose") == 0) { + verbose = true; + continue; + } + consumed = parse_tunnel_arg(argc, argv, argidx); + if (consumed) { + argidx += consumed - 1; + continue; + } + if (argv[argidx][0] == '-') { + fprintf(stderr, "Unknown option: %s\n", argv[argidx]); + usage(argv[0]); + return 1; + } + target = argv[argidx]; + } + + if (!target) { + usage(argv[0]); + return 1; + } + + printf("========================================\n"); + printf(" libcxlmi Generic Command Set Tests\n"); + printf("========================================\n"); + printf("Target: %s\n", target); + print_tunnel_config(); + + ctx = cxlmi_new_ctx(stdout, LOG_WARNING); + if (!ctx) { + fprintf(stderr, "Failed to create context\n"); + return 1; + } + + ep = open_endpoint(ctx, target); + if (!ep) { + fprintf(stderr, "Failed to open endpoint: %s\n", target); + cxlmi_free_ctx(ctx); + return 1; + } + + run_tests(ep); + + TEST_PRINT_RESULTS(); + rc = TEST_EXIT_CODE(); + cxlmi_close(ep); + cxlmi_free_ctx(ctx); + return rc; +} diff --git a/tests/cxl-test-memdev.c b/tests/cxl-test-memdev.c new file mode 100644 index 0000000..c84f9e1 --- /dev/null +++ b/tests/cxl-test-memdev.c @@ -0,0 +1,1268 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * CXL Memory Device Command Set unit tests for libcxlmi + * + * Tests memdev commands (CXL r3.1 Section 8.2.9.9) against a CXL device. + * Supports tunneling through a CXL switch with -p option. + */ + +#include "test-common.h" + +TEST_DECLARE_COUNTERS; +TEST_DECLARE_TUNNEL_CONFIG; + +/* + * Identify and Capacity Commands + */ + +static void test_identify(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_identify_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_identify(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("identify", "not supported"); + return; + } + if (rc) { + TEST_FAIL("identify", rc_str(rc)); + return; + } + + /* Basic sanity checks */ + if (rsp.total_capacity == 0) { + TEST_FAIL("identify", "zero total capacity"); + return; + } + + TEST_PASS("identify"); + if (verbose) { + printf(" total_capacity: %lu MB\n", + (unsigned long)(rsp.total_capacity * 256)); + printf(" volatile_capacity: %lu MB\n", + (unsigned long)(rsp.volatile_capacity * 256)); + printf(" persistent_capacity: %lu MB\n", + (unsigned long)(rsp.persistent_capacity * 256)); + printf(" lsa_size: %u bytes\n", rsp.lsa_size); + printf(" inject_poison_limit: %u\n", rsp.inject_poison_limit); + } +} + +static void test_get_partition_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_partition_info_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_partition_info(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_partition_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_partition_info", rc_str(rc)); + return; + } + + TEST_PASS("get_partition_info"); + if (verbose) { + printf(" active_vmem: %lu MB\n", + (unsigned long)(rsp.active_vmem * 256)); + printf(" active_pmem: %lu MB\n", + (unsigned long)(rsp.active_pmem * 256)); + printf(" next_vmem: %lu MB\n", + (unsigned long)(rsp.next_vmem * 256)); + printf(" next_pmem: %lu MB\n", + (unsigned long)(rsp.next_pmem * 256)); + } +} + +/* + * Label Storage Area Commands + */ + +static void test_get_lsa(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_identify_rsp id = {0}; + struct cxlmi_cmd_memdev_get_lsa_req req = {0}; + uint8_t *lsa_data; + size_t lsa_size; + int rc; + + /* First get LSA size from identify */ + rc = cxlmi_cmd_memdev_identify(ep, get_tunnel_info(), &id); + if (rc) { + TEST_SKIP("get_lsa", "failed to get LSA size"); + return; + } + + lsa_size = id.lsa_size; + if (lsa_size == 0) { + TEST_SKIP("get_lsa", "no LSA available"); + return; + } + + /* Read first 256 bytes or full LSA if smaller */ + if (lsa_size > 256) + lsa_size = 256; + + lsa_data = calloc(1, lsa_size); + if (!lsa_data) { + TEST_FAIL("get_lsa", "allocation failed"); + return; + } + + req.offset = 0; + req.length = lsa_size; + + rc = cxlmi_cmd_memdev_get_lsa(ep, get_tunnel_info(), &req, lsa_data); + if (is_unsupported(rc)) { + TEST_SKIP("get_lsa", "not supported"); + free(lsa_data); + return; + } + if (rc) { + TEST_FAIL("get_lsa", rc_str(rc)); + free(lsa_data); + return; + } + + TEST_PASS("get_lsa"); + if (verbose) { + printf(" total_lsa_size: %u\n", id.lsa_size); + printf(" read_offset: %u\n", req.offset); + printf(" read_length: %zu\n", lsa_size); + } + free(lsa_data); +} + +/* + * Health and Alert Commands + */ + +static void test_get_health_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_health_info_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_health_info(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_health_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_health_info", rc_str(rc)); + return; + } + + TEST_PASS("get_health_info"); + if (verbose) { + printf(" health_status: 0x%02x\n", rsp.health_status); + printf(" media_status: 0x%02x\n", rsp.media_status); + printf(" additional_status: 0x%02x\n", rsp.additional_status); + printf(" life_used: %u%%\n", rsp.life_used); + printf(" device_temperature: %d C\n", (int16_t)rsp.device_temperature); + printf(" dirty_shutdown_count: %u\n", rsp.dirty_shutdown_count); + printf(" corrected_volatile_error_count: %u\n", + rsp.corrected_volatile_error_count); + printf(" corrected_persistent_error_count: %u\n", + rsp.corrected_persistent_error_count); + } +} + +static void test_get_alert_config(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_alert_config_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_alert_config(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_alert_config", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_alert_config", rc_str(rc)); + return; + } + + TEST_PASS("get_alert_config"); + if (verbose) { + printf(" valid_alerts: 0x%02x\n", rsp.valid_alerts); + printf(" programmable_alerts: 0x%02x\n", rsp.programmable_alerts); + printf(" life_used_critical_threshold: %u%%\n", + rsp.life_used_critical_alert_threshold); + printf(" life_used_warning_threshold: %u%%\n", + rsp.life_used_programmable_warning_threshold); + printf(" over_temp_critical_threshold: %u\n", + rsp.device_over_temperature_critical_alert_threshold); + printf(" under_temp_critical_threshold: %u\n", + rsp.device_under_temperature_critical_alert_threshold); + } +} + +static void test_set_alert_config(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_alert_config_rsp get_rsp = {0}; + struct cxlmi_cmd_memdev_set_alert_config_req set_req = {0}; + int rc; + + /* Get current config */ + rc = cxlmi_cmd_memdev_get_alert_config(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_alert_config", "get not supported"); + return; + } + + /* Set same warning thresholds (safe operation) */ + set_req.life_used_programmable_warning_threshold = + get_rsp.life_used_programmable_warning_threshold; + set_req.device_over_temperature_programmable_warning_threshold = + get_rsp.device_over_temperature_programmable_warning_threshold; + set_req.device_under_temperature_programmable_warning_threshold = + get_rsp.device_under_temperature_programmable_warning_threshold; + set_req.corrected_volatile_mem_error_programmable_warning_threshold = + get_rsp.corrected_volatile_mem_error_programmable_warning_threshold; + set_req.corrected_persistent_mem_error_programmable_warning_threshold = + get_rsp.corrected_persistent_mem_error_programmable_warning_threshold; + + rc = cxlmi_cmd_memdev_set_alert_config(ep, get_tunnel_info(), &set_req); + if (is_unsupported(rc)) { + TEST_SKIP("set_alert_config", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_alert_config", rc_str(rc)); + return; + } + + TEST_PASS("set_alert_config"); +} + +static void test_get_shutdown_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_shutdown_state_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_shutdown_state(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_shutdown_state", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_shutdown_state", rc_str(rc)); + return; + } + + TEST_PASS("get_shutdown_state"); + if (verbose) + printf(" state: 0x%02x\n", rsp.state); +} + +/* + * Poison Commands + */ + +static void test_get_poison_list(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_poison_list_req req = {0}; + struct cxlmi_cmd_memdev_get_poison_list_rsp rsp = {0}; + int rc; + + req.get_poison_list_phy_addr = 0; + req.get_poison_list_phy_addr_len = 0x10000; /* 64KB */ + + rc = cxlmi_cmd_get_poison_list(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_poison_list", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_poison_list", rc_str(rc)); + return; + } + + TEST_PASS("get_poison_list"); + if (verbose) { + printf(" poison_list_flags: 0x%02x\n", + rsp.poison_list_flags); + printf(" more_err_media_record_cnt: %u\n", + rsp.more_err_media_record_cnt); + } +} + +static void test_inject_clear_poison(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_identify_rsp id = {0}; + struct cxlmi_cmd_memdev_inject_poison_req inject = {0}; + struct cxlmi_cmd_memdev_clear_poison_req clear = {0}; + int rc; + + /* Check if poison injection is supported */ + rc = cxlmi_cmd_memdev_identify(ep, get_tunnel_info(), &id); + if (rc) { + TEST_SKIP("inject_poison", "failed to check caps"); + return; + } + + if (id.inject_poison_limit == 0) { + TEST_SKIP("inject_poison", "poison injection not supported"); + return; + } + + /* Inject poison at a test address */ + inject.inject_poison_phy_addr = 0x1000; + rc = cxlmi_cmd_memdev_inject_poison(ep, get_tunnel_info(), &inject); + if (is_unsupported(rc)) { + TEST_SKIP("inject_poison", "not supported"); + return; + } + if (rc) { + TEST_FAIL("inject_poison", rc_str(rc)); + return; + } + + /* Clear the poison */ + clear.clear_poison_phy_addr = 0x1000; + memset(clear.clear_poison_write_data, 0, 64); + + rc = cxlmi_cmd_memdev_clear_poison(ep, get_tunnel_info(), &clear); + if (rc) { + TEST_FAIL("clear_poison", rc_str(rc)); + return; + } + + TEST_PASS("inject_poison"); + TEST_PASS("clear_poison"); +} + +/* + * Media Scan Commands + */ + +static void test_get_scan_media_caps(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_scan_media_capabilities_req req = {0}; + struct cxlmi_cmd_get_scan_media_capabilities_rsp rsp = {0}; + int rc; + + req.get_scan_media_capabilities_start_physaddr = 0; + req.get_scan_media_capabilities_physaddr_length = 0x10000; + + rc = cxlmi_cmd_get_scan_media_capabilities(ep, get_tunnel_info(), &req, &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_scan_media_caps", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_scan_media_caps", rc_str(rc)); + return; + } + + TEST_PASS("get_scan_media_caps"); + if (verbose) + printf(" estimated_scan_media_time: %u ms\n", + rsp.estimated_scan_media_time); +} + +/* + * QoS Commands + */ + +static void test_get_sld_qos_control(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_sld_qos_control_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_sld_qos_control(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_sld_qos_control", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_sld_qos_control", rc_str(rc)); + return; + } + + TEST_PASS("get_sld_qos_control"); + if (verbose) { + printf(" qos_telemetry_control: 0x%02x\n", + rsp.qos_telemetry_control); + printf(" egress_moderate_percentage: %u%%\n", + rsp.egress_moderate_percentage); + printf(" egress_severe_percentage: %u%%\n", + rsp.egress_severe_percentage); + printf(" backpressure_sample_interval: %u\n", + rsp.backpressure_sample_interval); + } +} + +static void test_set_sld_qos_control(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_sld_qos_control_rsp get_rsp = {0}; + struct cxlmi_cmd_memdev_set_sld_qos_control_req set_req = {0}; + int rc; + + /* Get current config */ + rc = cxlmi_cmd_memdev_get_sld_qos_control(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_sld_qos_control", "get not supported"); + return; + } + + /* Set same config (safe operation) */ + set_req.qos_telemetry_control = get_rsp.qos_telemetry_control; + set_req.egress_moderate_percentage = get_rsp.egress_moderate_percentage; + set_req.egress_severe_percentage = get_rsp.egress_severe_percentage; + set_req.backpressure_sample_interval = get_rsp.backpressure_sample_interval; + + rc = cxlmi_cmd_memdev_set_sld_qos_control(ep, get_tunnel_info(), &set_req); + if (is_unsupported(rc)) { + TEST_SKIP("set_sld_qos_control", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_sld_qos_control", rc_str(rc)); + return; + } + + TEST_PASS("set_sld_qos_control"); +} + +static void test_get_sld_qos_status(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_sld_qos_status_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_sld_qos_status(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_sld_qos_status", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_sld_qos_status", rc_str(rc)); + return; + } + + TEST_PASS("get_sld_qos_status"); + if (verbose) + printf(" backpressure_avg_percentage: %u%%\n", + rsp.backpressure_avg_percentage); +} + +/* + * Security Commands + */ + +static void test_get_security_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_security_state_rsp rsp = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_security_state(ep, get_tunnel_info(), &rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_security_state", "not supported"); + return; + } + if (rc) { + TEST_FAIL("get_security_state", rc_str(rc)); + return; + } + + TEST_PASS("get_security_state"); + if (verbose) + printf(" security_state: 0x%08x\n", rsp.security_state); +} + +/* + * Dynamic Capacity Commands + */ + +static void test_get_dc_config(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_dc_config_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_config_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp)); + if (!rsp) { + TEST_FAIL("get_dc_config", "allocation failed"); + return; + } + + req.region_cnt = 8; + req.start_region_id = 0; + + rc = cxlmi_cmd_memdev_get_dc_config(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_dc_config", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_dc_config", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_dc_config"); + if (verbose) { + printf(" num_regions: %u\n", rsp->num_regions); + printf(" regions_returned: %u\n", rsp->regions_returned); + printf(" num_extents_supported: %u\n", rsp->num_extents_supported); + printf(" num_extents_available: %u\n", rsp->num_extents_available); + printf(" num_tags_supported: %u\n", rsp->num_tags_supported); + printf(" num_tags_available: %u\n", rsp->num_tags_available); + } + free(rsp); +} + +static void test_get_dc_extent_list(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_dc_extent_list_req req = {0}; + struct cxlmi_cmd_memdev_get_dc_extent_list_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 8 * sizeof(rsp->extents[0])); + if (!rsp) { + TEST_FAIL("get_dc_extent_list", "allocation failed"); + return; + } + + req.extent_cnt = 8; + req.start_extent_idx = 0; + + rc = cxlmi_cmd_memdev_get_dc_extent_list(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_dc_extent_list", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_dc_extent_list", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_dc_extent_list"); + if (verbose) { + printf(" num_extents_returned: %u\n", + rsp->num_extents_returned); + printf(" total_num_extents: %u\n", + rsp->total_num_extents); + printf(" generation_num: %u\n", + rsp->generation_num); + } + free(rsp); +} + +/* + * Destructive Commands + */ + +static void test_set_partition_info(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_partition_info_rsp get_rsp = {0}; + struct cxlmi_cmd_memdev_set_partition_info_req req = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_partition_info(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_partition_info", "get not supported"); + return; + } + + /* Set same volatile capacity (safe operation) */ + req.volatile_capacity = get_rsp.active_vmem; + req.flags = 0; + + rc = cxlmi_cmd_memdev_set_partition_info(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("set_partition_info", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_partition_info", rc_str(rc)); + return; + } + + TEST_PASS("set_partition_info"); +} + +static void test_set_lsa(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_identify_rsp id = {0}; + struct cxlmi_cmd_memdev_get_lsa_req get_req = {0}; + struct cxlmi_cmd_memdev_set_lsa_req *set_req; + uint8_t *lsa_data; + size_t lsa_size; + int rc; + + /* First get LSA size from identify */ + rc = cxlmi_cmd_memdev_identify(ep, get_tunnel_info(), &id); + if (rc) { + TEST_SKIP("set_lsa", "failed to get LSA size"); + return; + } + + lsa_size = id.lsa_size; + if (lsa_size == 0) { + TEST_SKIP("set_lsa", "no LSA available"); + return; + } + + /* Read first 256 bytes or full LSA if smaller */ + if (lsa_size > 256) + lsa_size = 256; + + lsa_data = calloc(1, lsa_size); + if (!lsa_data) { + TEST_FAIL("set_lsa", "allocation failed"); + return; + } + + get_req.offset = 0; + get_req.length = lsa_size; + + rc = cxlmi_cmd_memdev_get_lsa(ep, get_tunnel_info(), &get_req, lsa_data); + if (is_unsupported(rc)) { + TEST_SKIP("set_lsa", "get not supported"); + free(lsa_data); + return; + } + if (rc) { + TEST_SKIP("set_lsa", "failed to read LSA"); + free(lsa_data); + return; + } + + /* Write back the same data (safe operation) */ + set_req = calloc(1, sizeof(*set_req) + lsa_size); + if (!set_req) { + TEST_FAIL("set_lsa", "allocation failed"); + free(lsa_data); + return; + } + + set_req->offset = 0; + memcpy(set_req->data, lsa_data, lsa_size); + + rc = cxlmi_cmd_memdev_set_lsa(ep, get_tunnel_info(), set_req, lsa_size); + if (is_unsupported(rc)) { + TEST_SKIP("set_lsa", "not supported"); + free(set_req); + free(lsa_data); + return; + } + if (rc) { + TEST_FAIL("set_lsa", rc_str(rc)); + free(set_req); + free(lsa_data); + return; + } + + TEST_PASS("set_lsa"); + free(set_req); + free(lsa_data); +} + +static void test_set_shutdown_state(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_get_shutdown_state_rsp get_rsp = {0}; + struct cxlmi_cmd_memdev_set_shutdown_state_req req = {0}; + int rc; + + rc = cxlmi_cmd_memdev_get_shutdown_state(ep, get_tunnel_info(), &get_rsp); + if (is_unsupported(rc)) { + TEST_SKIP("set_shutdown_state", "get not supported"); + return; + } + + /* Set same state (safe operation) */ + req.state = get_rsp.state; + + rc = cxlmi_cmd_memdev_set_shutdown_state(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("set_shutdown_state", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_shutdown_state", rc_str(rc)); + return; + } + + TEST_PASS("set_shutdown_state"); +} + +static void test_scan_media(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_scan_media_req req = {0}; + int retries = 3; + int rc; + + req.scan_media_physaddr = 0; + req.scan_media_physaddr_length = 0x10000; /* 64KB */ + req.scan_media_flags = 0; + + do { + rc = cxlmi_cmd_scan_media(ep, get_tunnel_info(), &req); + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) + usleep(500000); + retries--; + continue; + } + break; + } while (retries > 0); + + if (is_unsupported(rc)) { + TEST_SKIP("scan_media", "not supported"); + return; + } + if (rc == CXLMI_RET_BUSY) { + TEST_SKIP("scan_media", "device busy timeout"); + return; + } + if (is_bg_success(rc)) { + TEST_PASS("scan_media"); + return; + } + TEST_FAIL("scan_media", rc_str(rc)); +} + +static void test_get_scan_media_results(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_get_scan_media_results_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->record[0])); + if (!rsp) { + TEST_FAIL("get_scan_media_results", "allocation failed"); + return; + } + + rc = cxlmi_cmd_get_scan_media_results(ep, get_tunnel_info(), rsp); + if (is_unsupported(rc)) { + TEST_SKIP("get_scan_media_results", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("get_scan_media_results", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("get_scan_media_results"); + if (verbose) { + printf(" scan_media_flags: 0x%02x\n", + rsp->scan_media_flags); + printf(" media_error_count: %u\n", + rsp->media_error_count); + } + free(rsp); +} + +static void test_sanitize(struct cxlmi_endpoint *ep) +{ + int retries = 3; + int rc; + + do { + rc = cxlmi_cmd_memdev_sanitize(ep, get_tunnel_info()); + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) + usleep(500000); + retries--; + continue; + } + break; + } while (retries > 0); + + if (is_unsupported(rc)) { + TEST_SKIP("sanitize", "not supported"); + return; + } + if (rc == CXLMI_RET_BUSY) { + TEST_SKIP("sanitize", "device busy timeout"); + return; + } + if (is_bg_success(rc)) { + TEST_PASS("sanitize"); + return; + } + TEST_FAIL("sanitize", rc_str(rc)); +} + +static void test_secure_erase(struct cxlmi_endpoint *ep) +{ + int retries = 3; + int rc; + + do { + rc = cxlmi_cmd_memdev_secure_erase(ep, get_tunnel_info()); + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) + usleep(500000); + retries--; + continue; + } + break; + } while (retries > 0); + + if (is_unsupported(rc)) { + TEST_SKIP("secure_erase", "not supported"); + return; + } + if (rc == CXLMI_RET_BUSY) { + TEST_SKIP("secure_erase", "device busy timeout"); + return; + } + if (is_bg_success(rc)) { + TEST_PASS("secure_erase"); + return; + } + TEST_FAIL("secure_erase", rc_str(rc)); +} + +static void test_media_operations_discovery(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_media_operations_discovery_req req = {0}; + struct cxlmi_cmd_memdev_media_operations_discovery_rsp *rsp; + int rc; + + rsp = calloc(1, sizeof(*rsp) + 16 * sizeof(rsp->entry[0])); + if (!rsp) { + TEST_FAIL("media_operations_discovery", "allocation failed"); + return; + } + + req.media_operation_class = 0; + req.media_operation_subclass = 0; + req.dpa_range_count = 0; + req.discovery_osa.start_index = 0; + req.discovery_osa.num_ops = 16; + + rc = cxlmi_cmd_memdev_media_operations_discovery(ep, get_tunnel_info(), &req, rsp); + if (is_unsupported(rc)) { + TEST_SKIP("media_operations_discovery", "not supported"); + free(rsp); + return; + } + if (rc) { + TEST_FAIL("media_operations_discovery", rc_str(rc)); + free(rsp); + return; + } + + TEST_PASS("media_operations_discovery"); + if (verbose) { + printf(" dpa_range_granularity: %lu\n", + (unsigned long)rsp->dpa_range_granularity); + printf(" total_supported_ops: %u\n", + rsp->total_supported_ops); + printf(" num_supported_ops: %u\n", + rsp->num_supported_ops); + } + free(rsp); +} + +static void test_media_operations_sanitize(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_media_operations_sanitize_req *req; + int retries = 3; + int rc; + + req = calloc(1, sizeof(*req) + sizeof(req->dpa_range_list[0])); + if (!req) { + TEST_FAIL("media_operations_sanitize", "allocation failed"); + return; + } + + req->media_operation_class = 0; /* Sanitize class */ + req->media_operation_subclass = 0; + req->dpa_range_count = 1; + req->dpa_range_list[0].starting_dpa = 0; + req->dpa_range_list[0].length = 0x10000; + + do { + rc = cxlmi_cmd_memdev_media_operations_sanitize(ep, get_tunnel_info(), req); + if (rc == CXLMI_RET_BUSY) { + if (!wait_for_bg_done(ep, 5000)) + usleep(500000); + retries--; + continue; + } + break; + } while (retries > 0); + + if (is_unsupported(rc)) { + TEST_SKIP("media_operations_sanitize", "not supported"); + free(req); + return; + } + if (rc == CXLMI_RET_BUSY) { + TEST_SKIP("media_operations_sanitize", "device busy timeout"); + free(req); + return; + } + if (is_bg_success(rc)) { + TEST_PASS("media_operations_sanitize"); + free(req); + return; + } + TEST_FAIL("media_operations_sanitize", rc_str(rc)); + free(req); +} + +static void test_freeze_security_state(struct cxlmi_endpoint *ep) +{ + int rc; + + rc = cxlmi_cmd_memdev_freeze_security_state(ep, get_tunnel_info()); + if (is_unsupported(rc)) { + TEST_SKIP("freeze_security_state", "not supported"); + return; + } + if (rc == CXLMI_RET_SECURITY) { + TEST_SKIP("freeze_security_state", "invalid security state"); + return; + } + if (rc) { + TEST_FAIL("freeze_security_state", rc_str(rc)); + return; + } + + TEST_PASS("freeze_security_state"); +} + +static void test_add_dc_response(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_add_dc_response_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("add_dc_response", "allocation failed"); + return; + } + + req->updated_extent_list_size = 0; + req->flags = 0; + + rc = cxlmi_cmd_memdev_add_dc_response(ep, get_tunnel_info(), req); + if (is_unsupported(rc)) { + TEST_SKIP("add_dc_response", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("add_dc_response", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("add_dc_response"); + free(req); +} + +static void test_release_dc(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_release_dc_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("release_dc", "allocation failed"); + return; + } + + req->updated_extent_list_size = 0; + req->flags = 0; + + rc = cxlmi_cmd_memdev_release_dc(ep, get_tunnel_info(), req); + if (is_unsupported(rc)) { + TEST_SKIP("release_dc", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("release_dc", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("release_dc"); + free(req); +} + +static void test_set_passphrase(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_set_passphrase_req req = {0}; + int rc; + + /* Try to set user passphrase with null values - typically requires existing passphrase */ + req.passphrase_type = 0; /* User passphrase */ + + rc = cxlmi_cmd_memdev_set_passphrase(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("set_passphrase", "not supported"); + return; + } + if (rc) { + TEST_FAIL("set_passphrase", rc_str(rc)); + return; + } + + TEST_PASS("set_passphrase"); +} + +static void test_disable_passphrase(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_disable_passphrase_req req = {0}; + int rc; + + req.passphrase_type = 0; /* User passphrase */ + + rc = cxlmi_cmd_memdev_disable_passphrase(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("disable_passphrase", "not supported"); + return; + } + if (rc) { + TEST_FAIL("disable_passphrase", rc_str(rc)); + return; + } + + TEST_PASS("disable_passphrase"); +} + +static void test_unlock(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_unlock_req req = {0}; + int rc; + + /* Try unlock with null passphrase */ + rc = cxlmi_cmd_memdev_unlock(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("unlock", "not supported"); + return; + } + if (rc) { + TEST_FAIL("unlock", rc_str(rc)); + return; + } + + TEST_PASS("unlock"); +} + +static void test_passphrase_secure_erase(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_passphrase_secure_erase_req req = {0}; + int rc; + + req.passphrase_type = 0; /* User passphrase */ + + rc = cxlmi_cmd_memdev_passphrase_secure_erase(ep, get_tunnel_info(), &req); + if (is_unsupported(rc)) { + TEST_SKIP("passphrase_secure_erase", "not supported"); + return; + } + if (rc) { + TEST_FAIL("passphrase_secure_erase", rc_str(rc)); + return; + } + + TEST_PASS("passphrase_secure_erase"); +} + +static void test_security_send(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_security_send_req *req; + int rc; + + req = calloc(1, sizeof(*req)); + if (!req) { + TEST_FAIL("security_send", "allocation failed"); + return; + } + + req->security_protocol = 0; + req->sp_specific = 0; + + rc = cxlmi_cmd_memdev_security_send(ep, get_tunnel_info(), req, 0); + if (is_unsupported(rc)) { + TEST_SKIP("security_send", "not supported"); + free(req); + return; + } + if (rc) { + TEST_FAIL("security_send", rc_str(rc)); + free(req); + return; + } + + TEST_PASS("security_send"); + free(req); +} + +static void test_security_receive(struct cxlmi_endpoint *ep) +{ + struct cxlmi_cmd_memdev_security_receive_req req = {0}; + uint8_t rsp_buf[256] = {0}; + int rc; + + req.security_protocol = 0; + req.sp_specific = 0; + + rc = cxlmi_cmd_memdev_security_receive(ep, get_tunnel_info(), &req, rsp_buf); + if (is_unsupported(rc)) { + TEST_SKIP("security_receive", "not supported"); + return; + } + if (rc) { + TEST_FAIL("security_receive", rc_str(rc)); + return; + } + + TEST_PASS("security_receive"); +} + +static void run_tests(struct cxlmi_endpoint *ep) +{ + printf("\n[Memory Device Command Set]\n"); + + /* Identify and Capacity */ + test_identify(ep); + test_get_partition_info(ep); + + /* Label Storage Area */ + test_get_lsa(ep); + + /* Health and Alert */ + test_get_health_info(ep); + test_get_alert_config(ep); + test_set_alert_config(ep); + test_get_shutdown_state(ep); + + /* Poison */ + test_get_poison_list(ep); + test_inject_clear_poison(ep); + + /* Media Scan */ + test_get_scan_media_caps(ep); + + /* QoS */ + test_get_sld_qos_control(ep); + test_set_sld_qos_control(ep); + test_get_sld_qos_status(ep); + + /* Security */ + test_get_security_state(ep); + + /* Dynamic Capacity */ + test_get_dc_config(ep); + test_get_dc_extent_list(ep); + + /* Destructive Commands */ + test_set_partition_info(ep); + test_set_lsa(ep); + test_set_shutdown_state(ep); + test_scan_media(ep); + test_get_scan_media_results(ep); + test_sanitize(ep); + test_secure_erase(ep); + test_media_operations_discovery(ep); + test_media_operations_sanitize(ep); + test_freeze_security_state(ep); + test_add_dc_response(ep); + test_release_dc(ep); + + /* Security Commands (destructive) */ + test_set_passphrase(ep); + test_disable_passphrase(ep); + test_unlock(ep); + test_passphrase_secure_erase(ep); + test_security_send(ep); + test_security_receive(ep); +} + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options] \n", progname); + print_target_usage(); + print_tunnel_usage(); + fprintf(stderr, "\nGeneral Options:\n"); + fprintf(stderr, " -v, --verbose Show detailed response data\n"); + fprintf(stderr, " -h, --help Show this help\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " %s mctp:50 Direct to device at MCTP addr 50\n", progname); + fprintf(stderr, " %s -p 2 mctp:50 Via switch port 2 to Type3 device\n", progname); + fprintf(stderr, " %s -p 2 -l 0 mctp:50 Via switch port 2 to MLD LD0 (level 2)\n", progname); +} + +int main(int argc, char **argv) +{ + struct cxlmi_ctx *ctx; + struct cxlmi_endpoint *ep; + const char *target = NULL; + int rc = 1; + int argidx; + + for (argidx = 1; argidx < argc; argidx++) { + int consumed; + + if (strcmp(argv[argidx], "-h") == 0 || + strcmp(argv[argidx], "--help") == 0) { + usage(argv[0]); + return 0; + } + if (strcmp(argv[argidx], "-v") == 0 || + strcmp(argv[argidx], "--verbose") == 0) { + verbose = true; + continue; + } + consumed = parse_tunnel_arg(argc, argv, argidx); + if (consumed) { + argidx += consumed - 1; + continue; + } + if (argv[argidx][0] == '-') { + fprintf(stderr, "Unknown option: %s\n", argv[argidx]); + usage(argv[0]); + return 1; + } + target = argv[argidx]; + } + + if (!target) { + usage(argv[0]); + return 1; + } + + printf("========================================\n"); + printf(" libcxlmi Memory Device Command Tests\n"); + printf("========================================\n"); + printf("Target: %s\n", target); + print_tunnel_config(); + + ctx = cxlmi_new_ctx(stdout, LOG_WARNING); + if (!ctx) { + fprintf(stderr, "Failed to create context\n"); + return 1; + } + + ep = open_endpoint(ctx, target); + if (!ep) { + fprintf(stderr, "Failed to open endpoint: %s\n", target); + cxlmi_free_ctx(ctx); + return 1; + } + + run_tests(ep); + + TEST_PRINT_RESULTS(); + rc = TEST_EXIT_CODE(); + cxlmi_close(ep); + cxlmi_free_ctx(ctx); + return rc; +} diff --git a/tests/meson.build b/tests/meson.build index 82f0865..d78ba90 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -17,3 +17,24 @@ mock_tests = executable( ) test('mock-tests', mock_tests) + +executable( + 'cxl-test-generic', + ['cxl-test-generic.c'], + dependencies: libcxlmi_dep, + include_directories: [inc] +) + +executable( + 'cxl-test-memdev', + ['cxl-test-memdev.c'], + dependencies: libcxlmi_dep, + include_directories: [inc] +) + +executable( + 'cxl-test-fmapi', + ['cxl-test-fmapi.c'], + dependencies: libcxlmi_dep, + include_directories: [inc] +) diff --git a/tests/test-common.h b/tests/test-common.h new file mode 100644 index 0000000..7ee20a3 --- /dev/null +++ b/tests/test-common.h @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Common test infrastructure for libcxlmi unit tests + * + * This header provides shared macros, helper functions, and utilities + * used across all CXL command set test programs. + */ + +#ifndef TEST_COMMON_H +#define TEST_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Test counters and options - declare in each test file with TEST_DECLARE_COUNTERS + */ +#define TEST_DECLARE_COUNTERS \ + static int tests_run = 0; \ + static int tests_passed = 0; \ + static int tests_failed = 0; \ + static int tests_skipped = 0; \ + static bool verbose = false + +/* + * Tunneling configuration - declare in each test file with TEST_DECLARE_TUNNEL_CONFIG + * + * Tunneling modes: + * No options: Direct command (no tunneling) + * -p : Level 1 tunnel through switch to FM-owned LD (DEFINE_CXLMI_TUNNEL_SWITCH) + * -l : Level 1 tunnel to LD in MLD (DEFINE_CXLMI_TUNNEL_MLD) + * -p -l : Level 2 tunnel through switch to LD in MLD (DEFINE_CXLMI_TUNNEL_SWITCH_MLD) + * -m: Tunnel to MHD LD Pool CCI (DEFINE_CXLMI_TUNNEL_MHD) + */ +#define TEST_DECLARE_TUNNEL_CONFIG \ + static int tunnel_port = -1; \ + static int tunnel_ld = -1; \ + static bool tunnel_mhd = false; \ + \ + static struct cxlmi_tunnel_info *get_tunnel_info(void) \ + { \ + static struct cxlmi_tunnel_info ti; \ + \ + /* No tunneling options = direct command */ \ + if (tunnel_port < 0 && tunnel_ld < 0 && !tunnel_mhd) \ + return NULL; \ + \ + /* MHD tunneling (DEFINE_CXLMI_TUNNEL_MHD) */ \ + if (tunnel_mhd) { \ + ti.level = 1; \ + ti.port = 0; \ + ti.ld = -1; \ + ti.mhd = true; \ + return &ti; \ + } \ + \ + /* Level 2: switch + LD in MLD (DEFINE_CXLMI_TUNNEL_SWITCH_MLD) */ \ + if (tunnel_port >= 0 && tunnel_ld >= 0) { \ + ti.level = 2; \ + ti.port = tunnel_port; \ + ti.ld = tunnel_ld; \ + ti.mhd = false; \ + return &ti; \ + } \ + \ + /* Level 1: switch to FM-owned LD (DEFINE_CXLMI_TUNNEL_SWITCH) */ \ + if (tunnel_port >= 0) { \ + ti.level = 1; \ + ti.port = tunnel_port; \ + ti.ld = -1; \ + ti.mhd = false; \ + return &ti; \ + } \ + \ + /* Level 1: LD in MLD (DEFINE_CXLMI_TUNNEL_MLD) */ \ + if (tunnel_ld >= 0) { \ + ti.level = 1; \ + ti.port = 0; \ + ti.ld = tunnel_ld; \ + ti.mhd = false; \ + return &ti; \ + } \ + \ + return NULL; \ + } \ + \ + static void print_tunnel_config(void) \ + { \ + if (tunnel_mhd) { \ + printf("Tunneling: MHD LD Pool CCI\n"); \ + } else if (tunnel_port >= 0 && tunnel_ld >= 0) { \ + printf("Tunneling: switch port %d -> LD %d (level 2)\n", \ + tunnel_port, tunnel_ld); \ + } else if (tunnel_port >= 0) { \ + printf("Tunneling: switch port %d -> FM-owned LD (level 1)\n", \ + tunnel_port); \ + } else if (tunnel_ld >= 0) { \ + printf("Tunneling: LD %d in MLD (level 1)\n", tunnel_ld); \ + } \ + } \ + \ + static int parse_tunnel_arg(int argc, char **argv, int argidx) \ + { \ + if ((strcmp(argv[argidx], "-p") == 0 || \ + strcmp(argv[argidx], "--port") == 0) && argidx + 1 < argc) { \ + tunnel_port = atoi(argv[argidx + 1]); \ + return 2; \ + } \ + if ((strcmp(argv[argidx], "-l") == 0 || \ + strcmp(argv[argidx], "--ld") == 0) && argidx + 1 < argc) { \ + tunnel_ld = atoi(argv[argidx + 1]); \ + return 2; \ + } \ + if (strcmp(argv[argidx], "-m") == 0 || \ + strcmp(argv[argidx], "--mhd") == 0) { \ + tunnel_mhd = true; \ + return 1; \ + } \ + return 0; \ + } + +/* + * Print usage for common tunneling options + */ +static inline void print_tunnel_usage(void) +{ + fprintf(stderr, "\nTunneling options:\n"); + fprintf(stderr, " -p, --port Tunnel through switch to FM-owned LD (level 1)\n"); + fprintf(stderr, " -l, --ld Tunnel to LD in MLD (level 1)\n"); + fprintf(stderr, " -p -l Tunnel through switch to LD in MLD (level 2)\n"); + fprintf(stderr, " -m, --mhd Tunnel to MHD LD Pool CCI\n"); +} + +/* + * Test result macros + */ +#define TEST_PASS(name) do { \ + printf(" [PASS] %s\n", name); \ + tests_passed++; \ + tests_run++; \ +} while (0) + +#define TEST_FAIL(name, reason) do { \ + printf(" [FAIL] %s: %s\n", name, reason); \ + tests_failed++; \ + tests_run++; \ +} while (0) + +#define TEST_SKIP(name, reason) do { \ + printf(" [SKIP] %s: %s\n", name, reason); \ + tests_skipped++; \ +} while (0) + +/* + * Well-known log UUIDs (CXL r3.1 Section 8.2.9.4) + */ +static const uint8_t CEL_UUID[16] = { + 0x0d, 0xa9, 0xc0, 0xb5, 0xbf, 0x41, 0x4b, 0x78, + 0x8f, 0x79, 0x96, 0xb1, 0x62, 0x3b, 0x3f, 0x17 +}; + +static const uint8_t VENDOR_DEBUG_UUID[16] = { + 0xe1, 0x81, 0x9d, 0x9a, 0x48, 0x2e, 0x4e, 0x2a, + 0xab, 0x45, 0x13, 0x75, 0x7d, 0xa8, 0x48, 0xe5 +}; + +/* + * Check if return code indicates command not supported + */ +static inline bool is_unsupported(int rc) +{ + if (rc == CXLMI_RET_UNSUPPORTED || + rc == CXLMI_RET_MBUNSUPPORTED || + rc < 0) /* negative errno means kernel doesn't support this command */ + return true; + return false; +} + +/* + * Convert return code to string for error messages + */ +static inline const char *rc_str(int rc) +{ + if (rc < 0) + return "ioctl error"; + return cxlmi_cmd_retcode_tostr(rc); +} + +/* + * Wait for any ongoing background operation to complete + * + * Returns true if no background operation is running or wait succeeded, + * false if timeout was reached while operation still in progress. + */ +static inline bool wait_for_bg_done(struct cxlmi_endpoint *ep, int timeout_ms) +{ + struct cxlmi_cmd_bg_op_status_rsp rsp = {0}; + int elapsed = 0; + int rc; + + while (elapsed < timeout_ms) { + rc = cxlmi_cmd_bg_op_status(ep, NULL, &rsp); + if (rc) + return true; /* Can't check, assume done */ + if (rsp.status == 0) + return true; /* No background operation */ + usleep(100000); /* 100ms */ + elapsed += 100; + } + return false; /* Timeout */ +} + +/* + * Check if return code indicates success for a background operation command + * + * Background operations may return: + * 0 - Completed synchronously + * CXLMI_RET_BACKGROUND - Started as background operation (success) + */ +static inline bool is_bg_success(int rc) +{ + return (rc == 0 || rc == CXLMI_RET_BACKGROUND); +} + +/* + * Print usage information for transport target specification + */ +static inline void print_target_usage(void) +{ + fprintf(stderr, "\nTarget (one of):\n"); + fprintf(stderr, " CXL memory device name for ioctl (e.g., mem0)\n"); + fprintf(stderr, " mctp:, MCTP endpoint (e.g., mctp:1,0x1a)\n"); +} + +/* + * Open an endpoint supporting both ioctl and MCTP transports + * + * Target format: + * - ioctl transport (e.g., "mem0") + * mctp:, - MCTP transport (e.g., "mctp:1,0x1a") + */ +static inline struct cxlmi_endpoint *open_endpoint(struct cxlmi_ctx *ctx, + const char *target) +{ + unsigned int net; + unsigned int eid; + + /* Check for MCTP transport: mctp:, */ + if (strncmp(target, "mctp:", 5) == 0) { + if (sscanf(target + 5, "%u,%i", &net, &eid) != 2) { + fprintf(stderr, "Invalid MCTP target format: %s\n", target); + fprintf(stderr, "Expected: mctp:,\n"); + return NULL; + } + return cxlmi_open_mctp(ctx, net, (uint8_t)eid); + } + + /* Default to ioctl transport */ + return cxlmi_open(ctx, target); +} + +/* + * Print test results summary + */ +#define TEST_PRINT_RESULTS() do { \ + printf("\n========================================\n"); \ + printf(" Results: %d passed, %d failed, %d skipped\n", \ + tests_passed, tests_failed, tests_skipped); \ + printf("========================================\n"); \ +} while (0) + +/* + * Get test exit code: + * 0 = success (at least one pass, no failures) + * 1 = failure (at least one failure) + * 2 = skipped (no passes, no failures - all tests skipped) + */ +#define TEST_EXIT_CODE() \ + (tests_failed > 0 ? 1 : (tests_passed > 0 ? 0 : 2)) + +/* + * Common main() structure helper - starts test program + * + * Use this macro at the start of main() to handle common argument + * parsing and initialization. Sets up ctx and ep variables. + */ +#define TEST_MAIN_START(test_name, usage_func) \ + struct cxlmi_ctx *ctx; \ + struct cxlmi_endpoint *ep; \ + const char *target = NULL; \ + int rc = 1; \ + int argidx; \ + \ + for (argidx = 1; argidx < argc; argidx++) { \ + if (strcmp(argv[argidx], "-h") == 0 || \ + strcmp(argv[argidx], "--help") == 0) { \ + usage_func(argv[0]); \ + return 0; \ + } \ + if (strcmp(argv[argidx], "-v") == 0 || \ + strcmp(argv[argidx], "--verbose") == 0) { \ + verbose = true; \ + continue; \ + } \ + if (argv[argidx][0] == '-') { \ + fprintf(stderr, "Unknown option: %s\n", argv[argidx]); \ + usage_func(argv[0]); \ + return 1; \ + } \ + target = argv[argidx]; \ + } \ + \ + if (!target) { \ + usage_func(argv[0]); \ + return 1; \ + } \ + \ + printf("========================================\n"); \ + printf(" libcxlmi %s Tests\n", test_name); \ + printf("========================================\n"); \ + printf("Target: %s\n", target); \ + \ + ctx = cxlmi_new_ctx(stdout, LOG_WARNING); \ + if (!ctx) { \ + fprintf(stderr, "Failed to create context\n"); \ + return 1; \ + } \ + \ + ep = open_endpoint(ctx, target); \ + if (!ep) { \ + fprintf(stderr, "Failed to open endpoint: %s\n", target); \ + cxlmi_free_ctx(ctx); \ + return 1; \ + } + +/* + * Common main() structure helper - ends test program + * + * Use this macro at the end of main() after run_tests(ep). + */ +#define TEST_MAIN_END() \ + TEST_PRINT_RESULTS(); \ + rc = TEST_EXIT_CODE(); \ + cxlmi_close(ep); \ + cxlmi_free_ctx(ctx); \ + return rc + +#endif /* TEST_COMMON_H */