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)
+[](https://github.com/computexpresslink/libcxlmi/actions/workflows/build.yml)
+[](https://github.com/computexpresslink/libcxlmi/actions/workflows/test.yml)
+[](https://github.com/computexpresslink/libcxlmi/actions/workflows/analysis.yml)
+[](https://github.com/computexpresslink/libcxlmi/actions/workflows/abi.yml)
+[](https://github.com/computexpresslink/libcxlmi/actions/workflows/spelling.yml)
+[](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 */