[PATCH 2/2] tests/intel : Add Neg tests for VM_Bind Decomp

Smitha Balasubramanyam
2026-04-20T04:55:01Z

These tests verify handling for invalid UAPI
parameters in both fault and non-fault modes.
The tests also cover decompression verification with
ccs corruption, includes suspend and resume scenario too.

Signed-off-by: Smitha Balasubramanyam <smitha.balasubramanyam@intel.com>
---
tests/intel/xe_ccs.c | 747 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 742 insertions(+), 5 deletions(-)

diff --git a/tests/intel/xe_ccs.c b/tests/intel/xe_ccs.c
index 029b8bdaa..b1c169f0e 100644
--- a/tests/intel/xe_ccs.c
+++ b/tests/intel/xe_ccs.c
@@ -68,10 +68,28 @@
*
* SUBTEST: vm-bind-fault-mode-decompress
* Description: Validate VM_BIND with DECOMPRESS flag functionality in fault mode
+ *
+ * SUBTEST: vm_bind_decompress_uapi_bad_params
+ * Description: UAPI negative test — attempt VM_BIND with a combination of invalid params
+ *
+ * SUBTEST: vm_bind_decompress_uapi_bad_params_fault_mode
+ * Description: UAPI negative test attempt VM_BIND with a combination of
+ * invalid params - fault mode
+ *
+ * SUBTEST: vm_bind_decompress_ccs_corruption
+ * Description: Functional negative test — attempt VM_BIND after corrupting the CCS data;
+ * verify that decompression fails (data differs from original).
+ *
+ * SUBTEST: vm_bind_decompress_ccs_corruption-suspend-resume
+ * Description: Validate VM_BIND with DECOMPRESS flag with corrupted CCS with suspend/resume (S0)
*/

IGT_TEST_DESCRIPTION("Exercise gen12 blitter with and without flatccs compression on Xe");

+#ifndef INVALID_PAT_INDEX
+#define INVALID_PAT_INDEX 10U
+#endif
+
static struct param {
int compression_format;
int tiling;
@@ -100,6 +118,9 @@ struct test_config {
bool suspend_resume;
bool vm_bind_decompress;
bool vm_bind_fault_mode_decompress;
+ bool vm_bind_decompress_uapi_bad_params;
+ bool vm_bind_decompress_uapi_bad_params_fault_mode;
+ bool vm_bind_decompress_ccs_corruption;
int width_increment;
int width_steps;
int overwrite_width;
@@ -580,6 +601,211 @@ static int vm_bind_decomp_test_setup(int xe, intel_ctx_t *ctx, uint64_t ahnd,
return 0;
}

+static void vm_bind_decompress_uapi_bad_params(int xe,
+ intel_ctx_t *ctx,
+ u64 ahnd,
+ u32 region1,
+ u32 region2,
+ u32 width,
+ u32 height,
+ enum blt_tiling_type tiling,
+ const struct test_config *config)
+{
+ struct vm_bind_decomp_setup_resources allocated_resources = {};
+ int ret = 0;
+ void *mapped_data = MAP_FAILED;
+ u32 *mapped_ptr = NULL;
+ bool decompression_successful = false;
+ uint64_t addr;
+ u32 handle;
+ u64 use_map_size;
+ const uint64_t default_alignment = xe_get_default_alignment(xe);
+
+ /* Setup (this asserts+cleans on failure) */
+ ret = vm_bind_decomp_test_setup(xe, ctx, ahnd, region1, region2,
+ width, height, tiling, config, false,
+ &allocated_resources);
+ igt_assert_eq(ret, 0);
+
+ /* assign runtime values after setup */
+ addr = allocated_resources.vm_map_addr;
+ handle = allocated_resources.handle;
+ use_map_size = allocated_resources.map_size;
+
+ /* Build negative cases (runtime PAT lookups) */
+ struct vm_bind_decomp_neg_test_case bad_cases[] = {
+ /* PAT-based accepts (DECOMPRESS + Default PAT ) */
+ /* In the __xe_vmbind() wrapper, the DEFAULT_PAT_INDEX is updated to the appropriate GPU PAT index for the default caching type, so this case should succeed with a valid PAT even though we are passing the generic DEFAULT_PAT_INDEX value
+ */
+ { "decompress-default-pat", DEFAULT_PAT_INDEX, DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ false },
+
+ /* PAT-based accepts (DECOMPRESS + GPU WB PAT) */
+ { "decompress-gpu-wb-pat", intel_get_pat_idx_wb(xe), DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ false },
+
+ /* PAT-based rejects (DECOMPRESS + Invalid PAT ) */
+ { "decompress-invalid-pat", INVALID_PAT_INDEX, DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ true },
+
+ /*PAT-based rejects (DECOMPRESS + non-UC PAT)*/
+ { "decompress-gpu-wt-pat", intel_get_pat_idx_wt(xe), DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ true },
+
+ /* PAT-based rejects (DECOMPRESS + compressed PAT) */
+ { "decompress-gpu-compressed-pat", intel_get_pat_idx_uc_comp(xe),
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ { "no_decompress_flag", allocated_resources.uncompressed_pat,
+ 0, true },
+
+ /* Range smaller than the uncompressed size (should be rejected for DECOMPRESS) */
+ { "decompress-small-range", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ /* Misaligned virtual address for DECOMPRESS update */
+ { "decompress-misaligned-va", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ /* Wrong BO handle supplied (invalid handle) */
+ { "decompress-wrong-bo-handle", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+ };
+
+ /* Iterate the cases */
+ for (unsigned int i = 0; i < ARRAY_SIZE(bad_cases); i++) {
+ const struct vm_bind_decomp_neg_test_case *test_variant = &bad_cases[i];
+
+ u32 use_pat = test_variant->pat;
+ u32 flags = test_variant->flags;
+
+ /* Reset to known good values before each iteration */
+ addr = allocated_resources.vm_map_addr;
+ handle = allocated_resources.handle;
+ use_map_size = allocated_resources.map_size;
+
+ igt_info("VM_BIND variant: %s\n", test_variant->test_name);
+
+ /* Specialize the case parameters */
+ if (!strcmp(test_variant->test_name, "decompress-small-range")) {
+ use_map_size = allocated_resources.map_size - 1;
+ igt_debug("allocated_resources.size=%llu, default_alignment=%llu\n",
+ allocated_resources.size, default_alignment);
+ }
+ else if (!strcmp(test_variant->test_name, "decompress-misaligned-va")) {
+ addr = allocated_resources.vm_map_addr + 1; /* deliberately misaligned */
+
+ } else if (!strcmp(test_variant->test_name, "decompress-wrong-bo-handle")) {
+ /* flip some bits to make handle unlikely to be valid */
+ handle = allocated_resources.handle ^ 0xdeadbeef;
+ }
+ igt_debug("Issuing VM_BIND :addr=0x%llx map_size=%llu handle=%u pat=%u flags=0x%x for %s\n",
+ (unsigned long long)addr, (unsigned long long)use_map_size, handle, use_pat,
+ flags, test_variant->test_name);
+ errno = 0;
+ ret = __xe_vm_bind(xe, allocated_resources.vm,
+ 0, /* engine */
+ handle, /* handle (may be invalid) */
+ 0, /* offset in BO */
+ addr, /* VA */
+ use_map_size,
+ DRM_XE_VM_BIND_OP_MAP, /* update mapping */
+ flags,
+ NULL, 0, 0,
+ use_pat,
+ 0);
+
+ /* Evaluate outcome against expectation */
+ if (test_variant->expect_fail) {
+ if (ret == 0) {
+ /* Unexpected success — try to unmap and then fail the test (cleanup) */
+ igt_warn("VM_BIND unexpectedly SUCCEEDED for %s — attempting cleanup\n",
+ test_variant->test_name);
+
+ /* Attempt to unmap the mapping we may have created */
+ __xe_vm_bind(xe, allocated_resources.vm, 0, 0, 0, addr, use_map_size,
+ DRM_XE_VM_BIND_OP_UNMAP, 0, NULL, 0, 0, 0, 0);
+
+ /* Perform cleanup before asserting */
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "VM_BIND expected failure but succeeded for %s",
+ test_variant->test_name);
+ } else {
+ igt_info("VM_BIND rejected as expected for %s (errno=%d %s)\n",
+ test_variant->test_name, errno, strerror(errno));
+ }
+ } else {
+ /* For completeness: we expected success */
+ if (ret != 0) {
+ igt_warn("VM_BIND unexpectedly FAILED for %s: %d (%s)\n",
+ test_variant->test_name, ret, strerror(errno));
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "VM_BIND unexpected failure for %s",
+ test_variant->test_name);
+ } else {
+ igt_info("VM_BIND succeeded for %s (as expected)\n",
+ test_variant->test_name);
+ /* Only verify CPU-visible decompressed contents for these cases */
+ if (!strcmp(test_variant->test_name, "decompress-default-pat") ||
+ !strcmp(test_variant->test_name, "decompress-gpu-wb-pat")) {
+
+ igt_debug("Step 4: Verifying decompression by checking buffer data for %s\n", test_variant->test_name);
+
+ /* Map the BO for CPU access using helper */
+ mapped_data = xe_bo_map(xe, allocated_resources.handle,
+ allocated_resources.size);
+ if (mapped_data == MAP_FAILED) {
+ igt_warn("Mapping handle %u failed for verification\n",
+ allocated_resources.handle);
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "Failed to map BO for verification");
+ }
+ mapped_ptr = (uint32_t *)mapped_data;
+
+ igt_debug("Buffer data post page fault handling-first 64 bytes\n");
+ print_buffer_data(mapped_ptr, min_t(size_t, 64,
+ allocated_resources.size), "AFTER_FAULT", 4);
+
+ igt_debug("Checking if buffer contains decompressed data (original pattern)...\n");
+ decompression_successful = verify_test_pattern(mapped_ptr,
+ allocated_resources.size, "DECOMPRESSED");
+
+ if (!decompression_successful)
+ igt_debug("Decompression pattern verification FAILED for %s\n", test_variant->test_name);
+
+ if (memcmp(mapped_ptr, allocated_resources.src_obj->ptr,
+ allocated_resources.size) != 0) {
+ igt_debug("Decompressed data does not match original for %s\n", test_variant->test_name);
+ print_buffer_data(mapped_ptr, min_t(size_t, 256,
+ allocated_resources.size),
+ "CURRENT_STATE", 4);
+ print_buffer_data(allocated_resources.src_obj->ptr,
+ min_t(size_t, 256,
+ allocated_resources.size), "EXPECTED", 4);
+
+ /* tidy mapping and do full cleanup before failing */
+ munmap(mapped_data, allocated_resources.size);
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "Decompressed BO contents differ from original for %s",
+ test_variant->test_name);
+ }
+
+ igt_info("Decompression content matches original for %s\n",
+ test_variant->test_name);
+
+ /* tidy up mapping */
+ munmap(mapped_data, allocated_resources.size);
+ mapped_data = MAP_FAILED;
+ mapped_ptr = NULL;
+ }
+ }
+ }
+ }
+
+ /* final clean */
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+}
+
/* Helper: submit a tiny GPU batch that writes an immediate dword to the
* provided VA. This forces the kernel to fault in / decompress pages
* for that VA. Uses the provided `ctx` and `ahnd` (caller should have
@@ -648,6 +874,440 @@ static void trigger_page_fault_write(int xe, intel_ctx_t *ctx, uint64_t unused_a
igt_info("Faulting write batch completed successfully\n");
}

+/* Fault-mode variant of vm_bind_decompress_uapi_bad_params.
+ * It reuses your bad_cases but, for expected-success cases, triggers
+ * a GPU write to the mapped VA to force the kernel page-fault/decompress path.
+ */
+static void vm_bind_decompress_uapi_bad_params_fault_mode(int xe,
+ intel_ctx_t *ctx,
+ u64 ahnd,
+ u32 region1,
+ u32 region2,
+ u32 width,
+ u32 height,
+ enum blt_tiling_type tiling,
+ const struct test_config *config)
+{
+ struct vm_bind_decomp_setup_resources allocated_resources = {};
+ int ret = 0;
+ u64 addr;
+ u32 handle;
+ u64 use_map_size;
+ const uint64_t default_alignment = xe_get_default_alignment(xe);
+
+ /* Setup (this asserts+cleans on failure) */
+ ret = vm_bind_decomp_test_setup(xe, ctx, ahnd, region1, region2,
+ width, height, tiling, config, false,
+ &allocated_resources);
+ igt_assert_eq(ret, 0);
+ igt_assert_f(allocated_resources.vm != 0, "VM not created\n");
+
+ addr = allocated_resources.vm_map_addr;
+ handle = allocated_resources.handle;
+ use_map_size = allocated_resources.map_size;
+
+ struct vm_bind_decomp_neg_test_case bad_cases[] = {
+ /* PAT-based accepts (DECOMPRESS + Default PAT ) */
+ /* In the __xe_vmbind() wrapper, the DEFAULT_PAT_INDEX is updated to the appropriate GPU PAT index for the default caching type, so this case should succeed with a valid PAT even though we are passing the generic DEFAULT_PAT_INDEX value
+ */
+ { "decompress-default-pat", DEFAULT_PAT_INDEX, DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ false },
+
+ /* PAT-based accepts (DECOMPRESS + GPU WB PAT) */
+ { "decompress-gpu-wb-pat", intel_get_pat_idx_wb(xe), DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ false },
+
+ /* PAT-based rejects (DECOMPRESS + Invalid PAT ) */
+ { "decompress-invalid-pat", INVALID_PAT_INDEX, DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ true },
+
+ /*PAT-based rejects (DECOMPRESS + non-UC PAT)*/
+ { "decompress-gpu-wt-pat", intel_get_pat_idx_wt(xe), DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ true },
+
+ /* PAT-based rejects (DECOMPRESS + compressed PAT) */
+ { "decompress-gpu-compressed-pat", intel_get_pat_idx_uc_comp(xe),
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ { "no_decompress_flag", allocated_resources.uncompressed_pat,
+ 0, true },
+
+ /* Range smaller than the uncompressed size (should be rejected for DECOMPRESS) */
+ { "decompress-small-range", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ /* Misaligned virtual address for DECOMPRESS update */
+ { "decompress-misaligned-va", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+
+ /* Wrong BO handle supplied (invalid handle) */
+ { "decompress-wrong-bo-handle", allocated_resources.uncompressed_pat,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS, true },
+ };
+
+ /* Iterate cases */
+ for (unsigned int i = 0; i < ARRAY_SIZE(bad_cases); i++) {
+ const struct vm_bind_decomp_neg_test_case *test_variant = &bad_cases[i];
+
+ /* Reset per-case parameters */
+ u32 use_pat = test_variant->pat;
+ u32 flags = test_variant->flags;
+
+ addr = allocated_resources.vm_map_addr;
+ handle = allocated_resources.handle;
+ use_map_size = allocated_resources.map_size;
+
+ igt_info("VM_BIND variant (fault-mode): %s\n", test_variant->test_name);
+
+ /* Specialization */
+ if (!strcmp(test_variant->test_name, "decompress-small-range")) {
+ if (allocated_resources.map_size > default_alignment)
+ use_map_size = allocated_resources.map_size - 1;
+ igt_info("Using reduced map size: %llu\n",
+ (unsigned long long)use_map_size);
+ } else if (!strcmp(test_variant->test_name, "decompress-misaligned-va")) {
+ addr = allocated_resources.vm_map_addr + 1;
+ } else if (!strcmp(test_variant->test_name, "decompress-wrong-bo-handle")) {
+ handle = allocated_resources.handle ^ 0xdeadbeef;
+ }
+
+ errno = 0;
+ ret = __xe_vm_bind(xe, allocated_resources.vm,
+ 0, /* engine */
+ handle, /* handle (may be invalid) */
+ 0, /* offset in BO */
+ addr, /* VA */
+ use_map_size,
+ DRM_XE_VM_BIND_OP_MAP, /* update mapping */
+ flags,
+ NULL, 0, 0,
+ use_pat,
+ 0);
+
+ if (test_variant->expect_fail) {
+ if (ret == 0) {
+ /* Unexpected success — unmap and abort */
+ igt_warn("VM_BIND unexpectedly SUCCEEDED for %s — cleaning up\n",
+ test_variant->test_name);
+ __xe_vm_bind(xe, allocated_resources.vm, 0, 0, 0, addr,
+ use_map_size, DRM_XE_VM_BIND_OP_UNMAP,
+ 0, NULL, 0, 0, 0, 0);
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "VM_BIND expected failure but succeeded for %s",
+ test_variant->test_name);
+ } else {
+ igt_info("VM_BIND rejected as expected for %s "
+ "(ret=%d errno=%d %s)\n",
+ test_variant->test_name, ret, errno, strerror(errno));
+ }
+ } else {
+ /* Expected success path */
+ if (ret != 0) {
+ igt_warn("VM_BIND unexpectedly FAILED for %s: %d (%s)\n",
+ test_variant->test_name, ret, strerror(errno));
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "VM_BIND unexpected failure for %s",
+ test_variant->test_name);
+ }
+
+ igt_info("VM_BIND succeeded for %s — now triggering GPU page-fault "
+ " to exercise decompression\n",
+ test_variant->test_name);
+
+ /* Trigger page-fault via GPU write to the mapped VA */
+ trigger_page_fault_write(xe, ctx, ahnd, addr, region1);
+
+ /* Unmap to keep state clean for next iteration */
+ ret = __xe_vm_bind(xe, allocated_resources.vm, 0, 0, 0, addr, use_map_size,
+ DRM_XE_VM_BIND_OP_UNMAP, 0, NULL, 0, 0, 0, 0);
+ if (ret != 0)
+ igt_warn("Unmap after VM_BIND (fault-mode) failed: %d (%s)\n", ret,
+ strerror(errno));
+ }
+ }
+
+ /* Final cleanup */
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+}
+
+/* Functional negative test that validates VM_BIND Decompress with CCS corruption.
+ * It compresses src -> mid1/mid2, extracts CCS, corrupts mid1 CCS, writes back,
+ * reissues VM_BIND with DECOMPRESS for both and verifies mid1 != src and mid2 == src.
+ */
+static void vm_bind_decompress_ccs_corruption(int xe,
+ intel_ctx_t *ctx,
+ u64 ahnd,
+ u32 region1,
+ u32 region2,
+ u32 width,
+ u32 height, const struct test_config *config)
+{
+ struct drm_xe_gem_mmap_offset mmap_offset = {};
+ struct blt_copy_data blt = {};
+ struct blt_block_copy_data_ext ext = {};
+ struct blt_copy_object *src, *mid1, *mid2;
+ const u32 bpp = 32;
+ enum blt_compression_type comp_type = COMPRESSION_TYPE_3D;
+ u64 bb_size = xe_bb_size(xe, SZ_4K);
+ u64 size = (u64)width * height * 4;
+ u32 comp_pat = intel_get_pat_idx_uc_comp(xe);
+ int result;
+ void *mapped_mid1 = MAP_FAILED;
+ void *mapped_mid2 = MAP_FAILED;
+
+ struct drm_xe_engine_class_instance inst = { .engine_class = DRM_XE_ENGINE_CLASS_COPY };
+
+ /* Use vm_bind_decomp_test_setup to create a src (uncompressed) and a first
+ * compressed object (mid1). The helper also creates a VM and BB
+ * used for subsequent ctrl-surface operations.
+ */
+ struct vm_bind_decomp_setup_resources allocated_resources = {};
+
+ result = vm_bind_decomp_test_setup(xe, ctx, ahnd, region1, region2, width, height, T_LINEAR,
+ config, true, &allocated_resources);
+ igt_assert_eq(result, 0);
+
+ /* Reuse helper-created objects */
+ src = allocated_resources.src_obj;
+ mid1 = allocated_resources.comp_obj;
+
+ /* Create a second compressed object (mid2) and compress src->mid2 */
+ /* Init blit + batch */
+ blt_copy_init(xe, &blt);
+ mid2 = blt_create_object(&blt, region2, width, height,
+ bpp, intel_get_uc_mocs_index(xe), T_LINEAR,
+ COMPRESSION_ENABLED, comp_type, true);
+ igt_assert(mid2);
+
+ /* Use the BB provided by the helper */
+ blt_set_batch(&blt.bb, allocated_resources.bb, bb_size, allocated_resources.region_src);
+ blt.color_depth = CD_32bit;
+ blt.print_bb = param.print_bb;
+ blt_set_copy_object(&blt.src, src);
+ blt_set_copy_object(&blt.dst, mid2);
+ blt_set_object_ext(&ext.src, 0, width, height, SURFACE_TYPE_2D);
+ blt_set_object_ext(&ext.dst, param.compression_format, width, height, SURFACE_TYPE_2D);
+
+ blt_block_copy(xe, ctx, NULL, ahnd, &blt, &ext);
+ intel_ctx_xe_sync(ctx, true);
+
+ /* Verify compression occurred when platform supports it */
+ if (blt_platform_has_flat_ccs_enabled(xe)) {
+ bool is_compressed = blt_surface_is_compressed(xe, ctx, NULL, ahnd, mid2);
+
+ if (!is_compressed) {
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+ igt_assert_f(false, "Surface compression failed - "
+ " cannot test decompression\n");
+ }
+ }
+
+ /* Extract CCS for both via ctrl surf copy */
+ struct blt_ctrl_surf_copy_data surf = {};
+
+ u32 ccs1, ccs2;
+ uint32_t *ccsmap1 = NULL, *ccsmap2 = NULL;
+ uint64_t ccssize = mid1->size / CCS_RATIO(xe);
+ uint64_t ccs_bo_size = ALIGN(ccssize, xe_get_default_alignment(xe));
+
+ ccs1 = xe_bo_create_caching(xe, 0, ccs_bo_size, system_memory(xe), 0,
+ __xe_default_cpu_caching(xe, system_memory(xe), 0));
+ ccs2 = xe_bo_create_caching(xe, 0, ccs_bo_size, system_memory(xe), 0,
+ __xe_default_cpu_caching(xe, system_memory(xe), 0));
+
+ blt_ctrl_surf_copy_init(xe, &surf);
+ surf.print_bb = param.print_bb;
+
+ /* mid1 -> ccs1 */
+ blt_set_ctrl_surf_object(&surf.src, mid1->handle, mid1->region, mid1->size,
+ intel_get_uc_mocs_index(xe), comp_pat, BLT_INDIRECT_ACCESS);
+ blt_set_ctrl_surf_object(&surf.dst, ccs1, system_memory(xe), ccssize,
+ 0, DEFAULT_PAT_INDEX, DIRECT_ACCESS);
+
+ blt_set_batch(&surf.bb, allocated_resources.bb, bb_size, allocated_resources.region_src);
+
+ blt_ctrl_surf_copy(xe, ctx, NULL, ahnd, &surf);
+ intel_ctx_xe_sync(ctx, true);
+
+ /* mid2 -> ccs2 */
+ blt_set_ctrl_surf_object(&surf.src, mid2->handle, mid2->region, mid2->size,
+ intel_get_uc_mocs_index(xe), comp_pat, BLT_INDIRECT_ACCESS);
+ blt_set_ctrl_surf_object(&surf.dst, ccs2, system_memory(xe), ccssize,
+ 0, DEFAULT_PAT_INDEX, DIRECT_ACCESS);
+ blt_set_batch(&surf.bb, allocated_resources.bb, bb_size, allocated_resources.region_src);
+ blt_ctrl_surf_copy(xe, ctx, NULL, ahnd, &surf);
+ intel_ctx_xe_sync(ctx, true);
+ igt_info("Completed CCS extraction for mid1 and mid2\n");
+
+ /* --- Map, corrupt mid1's CCS, then write corrupted CCS back into mid1 --- */
+ /* Map ccs1 (the corrupted copy) */
+ ccsmap1 = xe_bo_map(xe, ccs1, ccssize);
+ // Verifying mapping
+ igt_assert(ccsmap1 != MAP_FAILED);
+
+ /* Optionally save original */
+ uint32_t *ccscopy = malloc(ccssize);
+
+ igt_assert(ccscopy);
+ memcpy(ccscopy, ccsmap1, ccssize);
+
+ /* Corrupt the CCS content (example corruption) */
+ for (size_t i = 0; i < ccssize / sizeof(uint32_t); i++)
+ ccsmap1[i] = (uint32_t)i ^ 0xdeadbeefu;
+
+ /* Ensure GPU sees the change by writing it back via ctrl-surf-copy:
+ * ccs1 -> mid1 (DIRECT_ACCESS -> BLT_INDIRECT_ACCESS)
+ */
+ munmap(ccsmap1, ccssize); /* unmap before submitting */
+ blt_set_ctrl_surf_object(&surf.src, ccs1, system_memory(xe), ccssize,
+ 0, DEFAULT_PAT_INDEX, DIRECT_ACCESS);
+ blt_set_ctrl_surf_object(&surf.dst, mid1->handle, mid1->region, mid1->size,
+ intel_get_uc_mocs_index(xe), comp_pat, BLT_INDIRECT_ACCESS);
+ blt_set_batch(&surf.bb, allocated_resources.bb, bb_size, allocated_resources.region_src);
+ blt_ctrl_surf_copy(xe, ctx, NULL, ahnd, &surf);
+ intel_ctx_xe_sync(ctx, true);
+
+ /* --- Write clean/unaltered CCS back to mid2 (optional, restoration) --- */
+ ccsmap2 = xe_bo_map(xe, ccs2, ccssize);
+ igt_assert(ccsmap2 != MAP_FAILED);
+ munmap(ccsmap2, ccssize);
+ blt_set_ctrl_surf_object(&surf.src, ccs2, system_memory(xe), ccssize,
+ 0, DEFAULT_PAT_INDEX, DIRECT_ACCESS);
+ blt_set_ctrl_surf_object(&surf.dst, mid2->handle, mid2->region, mid2->size,
+ intel_get_uc_mocs_index(xe), comp_pat, BLT_INDIRECT_ACCESS);
+ blt_set_batch(&surf.bb, allocated_resources.bb, bb_size, allocated_resources.region_src);
+ blt_ctrl_surf_copy(xe, ctx, NULL, ahnd, &surf);
+ intel_ctx_xe_sync(ctx, true);
+
+ igt_info("Corrupted CCS written back to mid1; clean CCS restored to mid2\n");
+
+ /* --- Suspend/Resume test (optional) --- */
+ if (config->suspend_resume) {
+ igt_info("Test Case has invoked a suspend-resume\n");
+
+ char *ccs1_before = NULL, *ccs2_before = NULL;
+ char *ccs1_after = NULL, *ccs2_after = NULL;
+ void *map;
+
+ /* Compute checksums of CCS BOs before suspend */
+ map = xe_bo_map(xe, ccs1, ccssize);
+ igt_assert(map != MAP_FAILED);
+ ccs1_before = g_compute_checksum_for_data(G_CHECKSUM_SHA1, map, ccssize);
+ munmap(map, ccssize);
+
+ map = xe_bo_map(xe, ccs2, ccssize);
+ igt_assert(map != MAP_FAILED);
+ ccs2_before = g_compute_checksum_for_data(G_CHECKSUM_SHA1, map, ccssize);
+ munmap(map, ccssize);
+
+ /* Ensure all GPU work is visible, then suspend/resume */
+ intel_ctx_xe_sync(ctx, true);
+ igt_info("Suspending system to test CCS persistence across S0 suspend/resume\n");
+ igt_system_suspend_autoresume(SUSPEND_STATE_FREEZE, SUSPEND_TEST_NONE);
+ igt_info("System resume complete — continuing VM_BIND tests\n");
+
+ /* Re-read CCS BOs after resume and compute checksums */
+ map = xe_bo_map(xe, ccs1, ccssize);
+ igt_assert(map != MAP_FAILED);
+ ccs1_after = g_compute_checksum_for_data(G_CHECKSUM_SHA1, map, ccssize);
+ munmap(map, ccssize);
+
+ map = xe_bo_map(xe, ccs2, ccssize);
+ igt_assert(map != MAP_FAILED);
+ ccs2_after = g_compute_checksum_for_data(G_CHECKSUM_SHA1, map, ccssize);
+ munmap(map, ccssize);
+
+ /* Expectations:
+ * - ccs1 (corrupted) should remain changed across suspend/resume
+ * - ccs2 (clean) should remain unchanged across suspend/resume
+ */
+ if (ccs1_before && ccs1_after && !strcmp(ccs1_before, ccs1_after))
+ igt_info("CCS1 (corrupted) persisted with same checksum (expected)\n");
+ else if (ccs1_before && ccs1_after)
+ igt_warn("CCS1 checksum changed across suspend/resume (unexpected)\n");
+
+ if (ccs2_before && ccs2_after && strcmp(ccs2_before, ccs2_after) == 0)
+ igt_info("CCS2 (clean) persisted with identical checksum (expected)\n");
+ else
+ igt_warn("CCS2 checksum changed across suspend/resume (unexpected)\n");
+
+ g_free(ccs1_before);
+ g_free(ccs1_after);
+ g_free(ccs2_before);
+ g_free(ccs2_after);
+ }
+
+ /* Cleanup */
+ free(ccscopy);
+ /* ccsmap1 was unmapped before writeback; avoid double unmap here */
+ gem_close(xe, ccs1);
+ gem_close(xe, ccs2);
+ put_offset(ahnd, ccs1);
+ put_offset(ahnd, ccs2);
+ igt_debug("Cleaned up CCS BOs\n");
+
+ /* Bind both mid1 and mid2 initially compressed */
+ result = __xe_vm_bind(xe, allocated_resources.vm, 0, mid1->handle, 0,
+ allocated_resources.vm_map_addr, allocated_resources.map_size,
+ DRM_XE_VM_BIND_OP_MAP, 0, NULL, 0, 0, comp_pat, 0);
+ igt_assert_eq(result, 0);
+ result = __xe_vm_bind(xe, allocated_resources.vm, 0, mid2->handle, 0,
+ allocated_resources.vm_map_addr + allocated_resources.map_size,
+ allocated_resources.map_size, DRM_XE_VM_BIND_OP_MAP, 0, NULL, 0, 0,
+ comp_pat, 0);
+ igt_assert_eq(result, 0);
+ /* Update mappings to request DECOMPRESS (UC PAT) */
+ result = __xe_vm_bind(xe, allocated_resources.vm, 0, mid1->handle, 0,
+ allocated_resources.vm_map_addr, allocated_resources.map_size,
+ DRM_XE_VM_BIND_OP_MAP, DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ NULL, 0, 0, allocated_resources.uncompressed_pat, 0);
+ igt_assert_eq(result, 0);
+ result = __xe_vm_bind(xe, allocated_resources.vm, 0, mid2->handle, 0,
+ allocated_resources.vm_map_addr + allocated_resources.map_size,
+ allocated_resources.map_size, DRM_XE_VM_BIND_OP_MAP,
+ DRM_XE_VM_BIND_FLAG_DECOMPRESS,
+ NULL, 0, 0, allocated_resources.uncompressed_pat, 0);
+ igt_assert_eq(result, 0);
+
+ /* Map both for CPU access and compare */
+ mmap_offset.handle = mid1->handle;
+ mmap_offset.flags = 0;
+ result = igt_ioctl(xe, DRM_IOCTL_XE_GEM_MMAP_OFFSET, &mmap_offset);
+
+ igt_assert_eq(result, 0);
+ mapped_mid1 = mmap(NULL, size, PROT_READ, MAP_SHARED, xe, mmap_offset.offset);
+ igt_assert(mapped_mid1 != MAP_FAILED);
+
+ mmap_offset.handle = mid2->handle;
+ mmap_offset.flags = 0;
+ result = igt_ioctl(xe, DRM_IOCTL_XE_GEM_MMAP_OFFSET, &mmap_offset);
+ igt_assert_eq(result, 0);
+ mapped_mid2 = mmap(NULL, size, PROT_READ, MAP_SHARED, xe, mmap_offset.offset);
+ igt_assert(mapped_mid2 != MAP_FAILED);
+ igt_info("Mapped both mid1 and mid2 after DECOMPRESS VM_BIND\n");
+
+ igt_info("Verifying decompressed surfaces (mid1: corrupted, mid2: clean)\n");
+ if (memcmp(mapped_mid1, src->ptr, size) == 0)
+ igt_assert_f(false, "mid1 decompressed matches src "
+ "(expected mismatch due to CCS corruption)");
+
+ if (memcmp(mapped_mid2, src->ptr, size) != 0)
+ igt_assert_f(false, "Decompressed MID2 does not match src "
+ "(expected match for clean CCS)");
+ igt_info("MID2 matches source; verifying recovered pattern...\n");
+ print_buffer_data(mapped_mid2, min_t(size_t, 256, size), "MID2_STATE", 16);
+ print_buffer_data(src->ptr, min_t(size_t, 256, size), "SRC", 16);
+ igt_info("MID2 decompressed data matches source pattern\n");
+
+ igt_info("CCS corruption decompression test completed successfully\n");
+ munmap(mapped_mid1, size);
+ munmap(mapped_mid2, size);
+
+ put_offset(ahnd, mid2->handle);
+ blt_destroy_object(xe, mid2);
+
+ vm_bind_decomp_test_cleanup(xe, ahnd, &allocated_resources);
+
}

struct blt_copy3_data {
@@ -1539,7 +2199,7 @@ static void single_copy(int xe, const struct test_config *config,
sync_bind = syncobj_create(xe, 0);
sync_out = syncobj_create(xe, 0);
ctx = intel_ctx_xe(xe, vm, exec_queue,
- 0, sync_bind, sync_out);
+ 0, sync_bind, sync_out);

if (config->vm_bind_fault_mode_decompress) {
ahnd = intel_allocator_open(xe, vm, INTEL_ALLOCATOR_RELOC);
@@ -1553,11 +2213,34 @@ static void single_copy(int xe, const struct test_config *config,
region1, region2, width,
height, tiling, config);
put_ahnd(ahnd);
- } else {
+ } else if (config->vm_bind_decompress_uapi_bad_params) {
+ ahnd = intel_allocator_open(xe, vm, INTEL_ALLOCATOR_RELOC);
+ vm_bind_decompress_uapi_bad_params(xe, ctx, ahnd,
+ region1, region2, width,
+ height, tiling, config);
+ put_ahnd(ahnd);
+ }
+
+ else if (config->vm_bind_decompress_uapi_bad_params_fault_mode) {
+ ahnd = intel_allocator_open(xe, vm, INTEL_ALLOCATOR_RELOC);
+ vm_bind_decompress_uapi_bad_params_fault_mode(xe, ctx, ahnd,
+ region1, region2, width,
+ height, tiling, config);
+ put_ahnd(ahnd);
+ }
+
+ else if (config->vm_bind_decompress_ccs_corruption) {
+ ahnd = intel_allocator_open(xe, vm, INTEL_ALLOCATOR_RELOC);
+ vm_bind_decompress_ccs_corruption(xe, ctx, ahnd, region1, region2,
+ width, height, config);
+ put_ahnd(ahnd);
+ }
+
+ else {
copyfns[copy_function].copyfn(xe, ctx,
- region1, region2,
- width, height,
- tiling, config);
+ region1, region2,
+ width, height,
+ tiling, config);
}

xe_exec_queue_destroy(xe, exec_queue);
@@ -1843,12 +2526,66 @@ int igt_main_args("bf:pst:W:H:", NULL, help_str, opt_handler, NULL)

single_copy(xe, &config, region1, region2, width, height, tiling, BLOCK_COPY);
}
+
+ igt_describe("Validate uAPI of VM_BIND with DECOMPRESS flag with bad params");
+ igt_subtest("vm_bind_decompress_uapi_bad_params") {
+ struct test_config config = { .compression = true,
+ .vm_bind_decompress_uapi_bad_params = true };
+ u32 region1 = system_memory(xe);
+ u32 region2 = vram_if_possible(xe, 0);
+ int tiling = T_LINEAR;
+ int width = param.width;
+ int height = param.height;
+
+ single_copy(xe, &config, region1, region2, width, height, tiling, BLOCK_COPY);
+ }

igt_describe("Validate VM_BIND with DECOMPRESS flag functionality in fault mode");
igt_subtest("vm-bind-fault-mode-decompress") {
struct test_config config = { .compression = true,
.vm_bind_fault_mode_decompress = true };
+ u32 region1 = system_memory(xe);
+ u32 region2 = vram_if_possible(xe, 0);
+ int tiling = T_LINEAR;
+ int width = param.width;
+ int height = param.height;

+ single_copy(xe, &config, region1, region2, width, height, tiling, BLOCK_COPY);
+ }
+
+ igt_describe("Validate uAPI of VM_BIND with DECOMPRESS flag with bad params in fault mode");
+ igt_subtest("vm_bind_decompress_uapi_bad_params_fault_mode") {
+ struct test_config config = { .compression = true,
+ .vm_bind_decompress_uapi_bad_params_fault_mode = true };
+ u32 region1 = system_memory(xe);
+ u32 region2 = vram_if_possible(xe, 0);
+ int tiling = T_LINEAR;
+ int width = param.width;
+ int height = param.height;
+
+ single_copy(xe, &config, region1, region2, width, height, tiling, BLOCK_COPY);
+ }
+
+ igt_describe("Validate VM_BIND with DECOMPRESS flag with corrupted CCS");
+ igt_subtest("vm_bind_decompress_ccs_corruption") {
+ struct test_config config = { .compression = true,
+ .vm_bind_decompress_ccs_corruption = true,
+ .suspend_resume = false };
+ u32 region1 = system_memory(xe);
+ u32 region2 = vram_if_possible(xe, 0);
+ int tiling = T_LINEAR;
+ int width = param.width;
+ int height = param.height;
+
+ single_copy(xe, &config, region1, region2, width, height, tiling, BLOCK_COPY);
+ }
+
+ igt_describe("Validate VM_BIND with DECOMPRESS flag "
+ "with corrupted CCS with suspend/resume");
+ igt_subtest_with_dynamic("vm_bind_decompress_ccs_corruption-suspend-resume") {
+ struct test_config config = { .compression = true,
+ .vm_bind_decompress_ccs_corruption = true,
+ .suspend_resume = true };
u32 region1 = system_memory(xe);
u32 region2 = vram_if_possible(xe, 0);
int tiling = T_LINEAR;
--
2.43.0