From: S Sebinraj <s.sebinraj@intel.com>
To: igt-dev@lists.freedesktop.org
Cc: carlos.santa@intel.com, karthik.b.s@intel.com,
krzysztof.karas@intel.com, kamil.konieczny@intel.com,
zbigniew.kempczynski@intel.com, S Sebinraj <s.sebinraj@intel.com>
Subject: [PATCH i-g-t v4] tests/kms_explict_fence: Add Test for Explicit Fencing
Date: Tue, 17 Feb 2026 13:29:53 +0530 [thread overview]
Message-ID: <20260217075953.3220732-1-s.sebinraj@intel.com> (raw)
Add a new test to validate explicit fencing.
This test handles multiple planes in a single atomic commit.
This test addresses a critical scenario where drivers must correctly
synchronize planes with mixed fence states.
Test Scenario:
- Creates three planes: one primary, two overlay
- Attaches fences with different states:
- Primary plane: signaled fence (simulating completed GPU work)
- Overlay-1 plane: signaled fence (simulating completed GPU work)
- Overlay-2 plane: unsignaled fence (simulating long-running GPU job)
- Submits all planes in a single atomic commit with IN_FENCE_FD set
- Validates that display waits for ALL fences before updating ANY plane
Bug Detection:
This test catches driver bugs where:
- Planes with signaled fences update prematurely before unsignaled
fences complete, breaking atomic commit semantics
- Fence states are mismanaged across multiple planes causing visual
glitches or corruption
- The driver fails to wait on all IN_FENCE_FDs atomically
The test uses sw_sync timelines to create controllable fence states,
allowing precise validation of fence synchronization behavior. An
OUT_FENCE_PTR is requested to monitor when the display update actually
completes, enabling detection of premature updates.
Implementation Details:
- Uses sw_sync for creating fences with controlled signaling
- Monitors OUT_FENCE to detect premature display updates
- Validates atomic commit integrity across multiple planes
Background:
- A bug regressed for Xe driver on PTL (Android) i915/display handling
of explicit fences when provided two or more buffers at once,
with 1+ already-signaled fences and 1+ not-yet-signaled fences.
https://patchwork.freedesktop.org/patch/662982/
- Adding this test so that IGT contains sufficient testing for the
use of explicit fencing in the KMS Atomic Modesetting APIs.
Changes:
-v4:
- Fix build errors as per latest API name
-v3
- Add crc check on display update
- Adding proper License entry
- Reduce test description
-v2
- Add crc check instead of visual inspection
- Reduce debug logging which are not needed
- Add a function for initial modset
- Make test not specific to Intel drivers alone
- Sort the included libs in alphabetical order
- Modify set_output func as we want only one
output per pipe
Signed-off-by: S Sebinraj <s.sebinraj@intel.com>
---
tests/kms_explicit_fence.c | 343 +++++++++++++++++++++++++++++++++++++
tests/meson.build | 1 +
2 files changed, 344 insertions(+)
create mode 100644 tests/kms_explicit_fence.c
diff --git a/tests/kms_explicit_fence.c b/tests/kms_explicit_fence.c
new file mode 100644
index 000000000..ee55f0e36
--- /dev/null
+++ b/tests/kms_explicit_fence.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+/**
+ * TEST: kms explicit fence multiplane
+ * Category: Display
+ * Description: Test explicit fencing with multiple planes and mixed fence states
+ * Driver requirement: i915, xe
+ * Mega feature: General Display Features
+ *
+ * This test validates correct handling of explicit fences when multiple planes
+ * are updated in a single atomic commit with different fence states. This is
+ * critical for catching fence synchronization bugs where the driver might:
+ * 1. Prematurely display buffers with signaled fences before unsignaled fences complete
+ * 2. Mismanage fence states across multiple planes causing visual glitches
+ * 3. Fail to wait on all fences atomically before updating the display
+ */
+
+#include <errno.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/eventfd.h>
+
+#include "igt.h"
+#include "igt_aux.h"
+#include "sw_sync.h"
+
+/**
+ * SUBTEST: multiplane-atomic-fence-wait
+ * Description: Test 3 planes (1 primary, 2 overlay) with mixed fence states:
+ * 2 planes with signaled fences and 1 plane with unsignaled fence.
+ * Validates that display waits for all fences before updating any plane.
+ */
+
+/* Test configuration macros */
+#define NUM_PLANES 3
+#define PLANE_PRIMARY_IDX 0
+#define PLANE_OVERLAY1_IDX 1
+#define PLANE_OVERLAY2_IDX 2
+#define OVERLAY_COUNT (NUM_PLANES - 1)
+
+#define FENCE_TIMEOUT_MS 2000
+
+#define OVERLAY_SIZE_DIVISOR 3
+#define OVERLAY1_POS_X 100
+#define OVERLAY1_POS_Y 100
+#define OVERLAY2_OFFSET 100
+
+typedef struct {
+ int drm_fd;
+ igt_display_t display;
+ igt_output_t *output;
+ enum pipe pipe;
+ igt_plane_t *primary;
+ igt_plane_t *overlay1;
+ igt_plane_t *overlay2;
+ struct igt_fb primary_fb;
+ struct igt_fb overlay1_fb;
+ struct igt_fb overlay2_fb;
+ drmModeModeInfo *mode;
+ int width, height;
+ igt_pipe_crc_t *pipe_crc;
+} data_t;
+
+static void setup_output(data_t *data)
+{
+ igt_display_t *display = &data->display;
+ igt_crtc_t *crtc;
+ igt_output_t *output;
+
+ for_each_crtc_with_single_output(display, crtc, output) {
+
+ data->primary = igt_crtc_get_plane_type(crtc,
+ DRM_PLANE_TYPE_PRIMARY);
+ data->overlay1 = igt_crtc_get_plane_type_index(crtc,
+ DRM_PLANE_TYPE_OVERLAY,
+ 0);
+ data->overlay2 = igt_crtc_get_plane_type_index(crtc,
+ DRM_PLANE_TYPE_OVERLAY,
+ 1);
+
+ /* We need at least OVERLAY_COUNT overlay planes for this test */
+ if (data->overlay1 && data->overlay2) {
+ data->output = output;
+ data->pipe = crtc->pipe;
+ data->mode = igt_output_get_mode(output);
+ data->width = data->mode->hdisplay;
+ data->height = data->mode->vdisplay;
+ return;
+ }
+ }
+
+ igt_skip("Need at least %d overlay planes for this test\n", OVERLAY_COUNT);
+}
+
+static void cleanup_crtc(data_t *data)
+{
+ igt_display_reset(&data->display);
+
+ igt_remove_fb(data->drm_fd, &data->primary_fb);
+ igt_remove_fb(data->drm_fd, &data->overlay1_fb);
+ igt_remove_fb(data->drm_fd, &data->overlay2_fb);
+}
+
+static void create_fbs(data_t *data)
+{
+ int overlay_width = data->width / OVERLAY_SIZE_DIVISOR;
+ int overlay_height = data->height / OVERLAY_SIZE_DIVISOR;
+
+ /* Primary plane - Blue background */
+ igt_create_color_fb(data->drm_fd, data->width, data->height,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR,
+ 0.0, 0.0, 1.0, /* Blue */
+ &data->primary_fb);
+
+ /* Overlay 1 - Red square */
+ igt_create_color_fb(data->drm_fd, overlay_width, overlay_height,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR,
+ 1.0, 0.0, 0.0, /* Red */
+ &data->overlay1_fb);
+
+ /* Overlay 2 - Green square */
+ igt_create_color_fb(data->drm_fd, overlay_width, overlay_height,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR,
+ 0.0, 1.0, 0.0, /* Green */
+ &data->overlay2_fb);
+}
+
+static void setup_initial_modeset(data_t *data)
+{
+ /* Initial modeset to establish baseline (no fences) */
+ igt_output_set_crtc(data->output, igt_crtc_for_pipe(&data->display, data->pipe));
+ igt_plane_set_fb(data->primary, &data->primary_fb);
+ igt_plane_set_fb(data->overlay1, &data->overlay1_fb);
+ igt_plane_set_position(data->overlay1, OVERLAY1_POS_X, OVERLAY1_POS_Y);
+ igt_plane_set_fb(data->overlay2, &data->overlay2_fb);
+ igt_plane_set_position(data->overlay2, data->width - OVERLAY2_OFFSET -
+ data->overlay2_fb.width, OVERLAY1_POS_Y);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+}
+
+/**
+ * multiplane_atomic_fence_wait - Test atomic commit with mixed fence states
+ *
+ * This test reproduces the critical scenario where multiple planes are updated
+ * in a single atomic commit with different fence states. The key validation is
+ * that the display does NOT update any plane until ALL fences are signaled.
+ *
+ * Test setup:
+ * - Primary plane: buffer with a signaled fence (already retired)
+ * - Overlay 1 plane: buffer with a signaled fence (already retired)
+ * - Overlay 2 plane: buffer with an UNSIGNALED fence (long-running operation)
+ *
+ * Expected behavior:
+ * - Atomic commit succeeds immediately (non-blocking)
+ * - Display waits internally for ALL fences before updating
+ * - No plane shows its new buffer until the unsignaled fence completes
+ * - After signaling the last fence, all planes update atomically
+ *
+ * Bug detection:
+ * If the driver has a bug, it will either:
+ * 1. Show the primary and overlay1 buffers prematurely (before overlay2's fence signals)
+ * 2. Cause visual glitches from fence mismanagement
+ * 3. Fail the atomic commit
+ */
+static void multiplane_atomic_fence_wait(data_t *data)
+{
+ int timelines[NUM_PLANES];
+ int fences[NUM_PLANES];
+ int ret;
+ int out_fence;
+ igt_plane_t *planes[NUM_PLANES];
+ bool should_signal[NUM_PLANES] = { true, true, false };
+ igt_crc_t crc_before, crc_after, crc_final;
+
+ igt_require_sw_sync();
+
+ setup_initial_modeset(data);
+
+ /* Setup arrays for loop-based processing */
+ planes[PLANE_PRIMARY_IDX] = data->primary;
+ planes[PLANE_OVERLAY1_IDX] = data->overlay1;
+ planes[PLANE_OVERLAY2_IDX] = data->overlay2;
+
+ /* Start CRC capture to verify no premature display updates */
+ igt_pipe_crc_start(data->pipe_crc);
+ igt_pipe_crc_get_current(data->drm_fd, data->pipe_crc, &crc_before);
+
+ /* Create timelines and fences for all planes */
+ for (int i = 0; i < NUM_PLANES; i++) {
+ /* Create timeline */
+ timelines[i] = sw_sync_timeline_create();
+ igt_assert(timelines[i] >= 0);
+
+ /* Create fence at sequence 1 */
+ fences[i] = sw_sync_timeline_create_fence(timelines[i], 1);
+ igt_assert(fences[i] >= 0);
+
+ /* Signal fence (first 2 planes) */
+ if (should_signal[i]) {
+ sw_sync_timeline_inc(timelines[i], 1);
+ igt_assert_eq(sync_fence_status(fences[i]), 1);
+ } else {
+ /* Do not signal this fence yet, 3rd plane */
+ igt_assert_eq(sync_fence_status(fences[i]), 0);
+ }
+
+ /* Attach IN_FENCE_FD to plane */
+ igt_plane_set_fence_fd(planes[i], fences[i]);
+ }
+
+ /* Swap overlay colors to detect scanout changes via CRC */
+ igt_plane_set_fb(data->overlay1, &data->overlay2_fb);
+ igt_plane_set_position(data->overlay1, OVERLAY1_POS_X, OVERLAY1_POS_Y);
+ igt_plane_set_fb(data->overlay2, &data->overlay1_fb);
+ igt_plane_set_position(data->overlay2, data->width - OVERLAY2_OFFSET -
+ data->overlay1_fb.width, OVERLAY1_POS_Y);
+
+ /* Request OUT_FENCE to track when display update completes */
+ igt_crtc_request_out_fence(igt_crtc_for_pipe(&data->display, data->pipe));
+
+ /*
+ * The atomic commit should succeed immediately (NONBLOCK mode),
+ * but the display should NOT update any plane until ALL fences
+ * (including the unsignaled overlay2 fence) are signaled.
+ *
+ * A buggy driver might:
+ * - Show primary and overlay1 immediately
+ * - Cause visual corruption
+ * - Fail the commit
+ */
+ ret = igt_display_try_commit_atomic(&data->display,
+ DRM_MODE_ATOMIC_NONBLOCK,
+ NULL);
+ igt_assert_eq(ret, 0);
+
+ /* Get the out fence to monitor completion */
+ out_fence = igt_crtc_for_pipe(&data->display, data->pipe)->out_fence_fd;
+ igt_assert(out_fence >= 0);
+
+ /* Verify overlay2 fence (last one) is still unsignaled */
+ igt_assert_eq(sync_fence_status(fences[PLANE_OVERLAY2_IDX]), 0);
+
+ /* Verify out fence is also unsignaled (display hasn't updated yet) */
+ ret = sync_fence_status(out_fence);
+ igt_assert_f(ret != 1,
+ "OUT_FENCE already signaled while overlay2 fence is unsignaled - "
+ "driver did not wait for all IN_FENCEs!");
+
+ /*
+ * Wait briefly and verify display hasn't updated via CRC check.
+ * In a buggy implementation, the display might update prematurely.
+ */
+ usleep(100000); /* 100ms */
+
+ /* Check if out fence signaled prematurely */
+ ret = sync_fence_status(out_fence);
+ igt_assert_f(ret != 1,
+ "OUT_FENCE signaled before overlay2 fence - "
+ "driver updated display without waiting for all fences");
+
+ /* CRC verify - check if display has not updated prematurely */
+ igt_pipe_crc_get_current(data->drm_fd, data->pipe_crc, &crc_after);
+ igt_assert_crc_equal(&crc_before, &crc_after);
+
+ /* Now signal the blocking fence (overlay2) */
+ sw_sync_timeline_inc(timelines[PLANE_OVERLAY2_IDX], 1);
+
+ /* Wait for overlay2 fence to be signaled */
+ ret = sync_fence_wait(fences[PLANE_OVERLAY2_IDX], FENCE_TIMEOUT_MS);
+ igt_assert_eq(ret, 0);
+ igt_assert_eq(sync_fence_status(fences[PLANE_OVERLAY2_IDX]), 1);
+
+ /* Now wait for the display update to complete (out fence signals) */
+ ret = sync_fence_wait(out_fence, FENCE_TIMEOUT_MS);
+ igt_assert_eq(ret, 0);
+ igt_assert_eq(sync_fence_status(out_fence), 1);
+
+ /* Verify display has now updated (CRC should differ from baseline) */
+ igt_pipe_crc_get_current(data->drm_fd, data->pipe_crc, &crc_final);
+ igt_assert_f(!igt_check_crc_equal(&crc_before, &crc_final),
+ "Display did not update after all fences signaled");
+
+ igt_pipe_crc_stop(data->pipe_crc);
+
+ /* Cleanup */
+ for (int i = 0; i < NUM_PLANES; i++) {
+ close(fences[i]);
+ close(timelines[i]);
+ igt_plane_set_fence_fd(planes[i], -1);
+ }
+ close(out_fence);
+ igt_crtc_for_pipe(&data->display, data->pipe)->out_fence_fd = -1;
+}
+
+static void reset_display_state(data_t *data)
+{
+ igt_plane_set_fb(data->primary, NULL);
+ igt_plane_set_fb(data->overlay1, NULL);
+ igt_plane_set_fb(data->overlay2, NULL);
+ igt_output_set_crtc(data->output, NULL);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+}
+
+int igt_main()
+{
+ data_t data = {};
+
+ igt_fixture() {
+ data.drm_fd = drm_open_driver_master(DRIVER_ANY);
+ kmstest_set_vt_graphics_mode();
+ igt_display_require(&data.display, data.drm_fd);
+ igt_display_require_output(&data.display);
+
+ setup_output(&data);
+ create_fbs(&data);
+
+ data.pipe_crc = igt_pipe_crc_new(data.drm_fd, data.pipe,
+ IGT_PIPE_CRC_SOURCE_AUTO);
+ }
+
+ igt_describe("Test atomic commit with 3 planes (1 primary, 2 overlay) "
+ "where 2 planes have signaled fences and 1 plane has an "
+ "unsignaled fence. Validates that display does not update "
+ "any plane until all fences are signaled");
+ igt_subtest("multiplane-atomic-fence-wait")
+ multiplane_atomic_fence_wait(&data);
+
+ igt_fixture() {
+ reset_display_state(&data);
+ igt_pipe_crc_free(data.pipe_crc);
+ cleanup_crtc(&data);
+ igt_display_fini(&data.display);
+ drm_close_driver(data.drm_fd);
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index 7f356de9b..eb7c8b13b 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -36,6 +36,7 @@ test_progs = [
'kms_dither',
'kms_display_modes',
'kms_dp_aux_dev',
+ 'kms_explicit_fence',
'kms_feature_discovery',
'kms_flip',
'kms_flip_event_leak',
--
2.43.0
next reply other threads:[~2026-02-17 8:12 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-17 7:59 S Sebinraj [this message]
2026-02-17 20:13 ` ✓ Xe.CI.BAT: success for tests/kms_explict_fence: Add Test for Explicit Fencing (rev4) Patchwork
2026-02-17 20:36 ` ✓ i915.CI.BAT: " Patchwork
2026-02-17 23:05 ` ✗ Xe.CI.FULL: failure " Patchwork
2026-02-19 4:29 ` Sebinraj, S
2026-02-18 2:10 ` ✗ i915.CI.Full: " Patchwork
2026-02-19 4:32 ` Sebinraj, S
2026-02-25 9:46 ` ✓ Xe.CI.BAT: success for tests/kms_explict_fence: Add Test for Explicit Fencing (rev5) Patchwork
2026-02-25 9:56 ` ✗ i915.CI.BAT: failure " Patchwork
2026-02-25 12:49 ` ✗ Xe.CI.FULL: " Patchwork
2026-02-26 8:51 ` [PATCH i-g-t v4] tests/kms_explict_fence: Add Test for Explicit Fencing Krzysztof Karas
2026-02-26 9:44 ` Sebinraj, S
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260217075953.3220732-1-s.sebinraj@intel.com \
--to=s.sebinraj@intel.com \
--cc=carlos.santa@intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=kamil.konieczny@intel.com \
--cc=karthik.b.s@intel.com \
--cc=krzysztof.karas@intel.com \
--cc=zbigniew.kempczynski@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox