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/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); ``` diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..a1aa2ce --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,1169 @@ +# 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 section explains the architecture, benefits, +limitations, and usage of the mock testing infrastructure. + +### Mock 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/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 44dde48..145a5d0 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); @@ -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); @@ -1033,20 +1034,20 @@ 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; } 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 2e88f6a..3cf67eb 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 @@ -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; } @@ -453,7 +460,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/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/src/cxlmi/commands.c b/src/cxlmi/commands.c index 12b08fb..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; @@ -632,7 +639,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; } @@ -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) @@ -1520,7 +1532,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)); @@ -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/cxlmi/cxlmi.c b/src/cxlmi/cxlmi.c index 6891378..aaba8eb 100644 --- a/src/cxlmi/cxlmi.c +++ b/src/cxlmi/cxlmi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include "private.h" +#include "mock.h" #if !defined(AF_MCTP) #define AF_MCTP 45 @@ -294,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 { @@ -384,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; @@ -553,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; @@ -667,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; @@ -692,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; @@ -712,7 +731,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; } @@ -861,7 +880,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; } @@ -882,7 +901,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, @@ -971,7 +990,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); @@ -1022,7 +1041,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); @@ -1078,7 +1097,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); @@ -1488,8 +1510,6 @@ int cxlmi_scan_mctp(struct cxlmi_ctx *ctx) /* objects container */ dbus_message_iter_recurse(&args, &objs); - rc = 0; - do { DBusMessageIter ent; int opened; @@ -1532,7 +1552,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 +1613,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] == '.') @@ -1653,7 +1673,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", @@ -1686,7 +1706,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) { @@ -1703,11 +1723,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/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; 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/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, 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/libcxlmi.h b/src/libcxlmi.h index 51b97ec..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) \ @@ -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, 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/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 707af54..d78ba90 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -8,3 +8,33 @@ 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) + +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/mock-tests.c b/tests/mock-tests.c new file mode 100644 index 0000000..236e93a --- /dev/null +++ b/tests/mock-tests.c @@ -0,0 +1,10168 @@ +// 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, 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) + goto cleanup; + + 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; + + 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(); + + 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 result; +} + +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, result = 1; + + rsp.opcode = 0x0001; /* Identify */ + rsp.command_effect = 0x0000; + + req.offset = 0; + req.length = sizeof(rsp); + + ret = calloc(1, sizeof(*ret) + req.length); + if (!ret) { + fprintf(stderr, "\n ASSERT FAILED: allocation failed\n"); + return 1; + } + + 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(); + + 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 result; +} + +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, result = 1; + + /* 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); + if (!ret) { + fprintf(stderr, "\n ASSERT FAILED: alloc failed\n"); + return 1; + } + + 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(); + + 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 result; +} + +/* + * 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; +} 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 */