On 16.09.25 11:55, Janusz Krzysztofik wrote:
> CI reports kernel soft lockups when running a wait_backward test case of
> igt@dmabuf@all-tests@dma_fence_chain selftest on less powerful machines.
> A kernel fix has been developed that has proven to resolve the issue, but
> it hasn't been accepted upstream, with a recommendation for dropping that
> test case as a "nonsense".
>
> Before we decide to take that path, try to implement the problematic test
> case in user space as an IGT subtest. Since no kernel uAPIs have been
> found that allow strict reimplementation of exact algorithm of the
> problematic test case, where every link of a dma-fence chain is signaled
> one by one from a loop running in kernel space, provide two approximate
> variants, one that signals each fence with an individual system call, and
> one that signals them all in one shot with one system call.
Those tests are unrealistic outside of the syncobj framework.
E.g. a test which exercises signaling each fence individually would require HW
which would do that to happen in reality.
Regards,
Christian.
>
> For more comprehensive testing, also implement the _forward and _random
> scenarios from the original selftest, as well as simplified variants that
> don't enable signaling on each link of the dma-fence chain, and yet others
> that not only enable but also wait on every link of the chain.
>
> Signed-off-by: Janusz Krzysztofik <janusz.krzyszto...@linux.intel.com>
> ---
> tests/syncobj_timeline.c | 289 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 289 insertions(+)
>
> diff --git a/tests/syncobj_timeline.c b/tests/syncobj_timeline.c
> index a77896ec1d..80c5970687 100644
> --- a/tests/syncobj_timeline.c
> +++ b/tests/syncobj_timeline.c
> @@ -427,6 +427,61 @@
> *
> * SUBTEST: wait-zero-handles
> * Description: Verifies that waiting on an empty list of syncobj handles is
> accepted
> + *
> + * SUBTEST: stress-wait-last-signal-forward
> + * Description: Signals each fence of a large timeline while another thread
> is waiting on that timeline
> + *
> + * SUBTEST: stress-wait-last-signal-backward
> + * Description: Signals each fence of a large timeline in reverse order
> while another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-wait-last-signal-random
> + * Description: Signals each fence of a large timeline in random order while
> another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-wait-last-signal-all-forward
> + * Description: Signals all fences of a large timeline while another thread
> is waiting on that timeline
> + *
> + * SUBTEST: stress-wait-last-signal-all-backward
> + * Description: Signals all fences of a large reverse ordered timeline while
> another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-wait-last-signal-all-random
> + * Description: Signals all fences of a large randomly ordered timeline
> while another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-enable-all-signal-forward
> + * Description: Signals each fence of a large timeline with signaling
> enabled on each point while another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-enable-all-signal-backward
> + * Description: Signals each fence of a large timeline in reversed order
> with signaling enabled on each point while another thread is waiting on that
> timeline
> + *
> + * SUBTEST: stress-enable-all-signal-random
> + * Description: Signals each fence of a large timeline in random order with
> signaling enabled on each point while another thread is waiting on that
> timeline
> + *
> + * SUBTEST: stress-enable-all-signal-all-forward
> + * Description: Signals all fences of a large timeline with signaling
> enabled on each point while another thread is waiting on that timeline
> + *
> + * SUBTEST: stress-enable-all-signal-all-backward
> + * Description: Signals all fences of a large reversed ordered timeline with
> signaling enabled on each point while another thread is waiting on that
> timeline
> + *
> + * SUBTEST: stress-enable-all-signal-all-random
> + * Description: Signals all fences of a large randomly ordered timeline with
> signaling enabled on each point while another thread is waiting on that
> timeline
> + *
> + * SUBTEST: stress-wait-all-signal-forward
> + * Description: Signals each fence of a large timeline while another thread
> is waiting on each point of that timeline
> + *
> + * SUBTEST: stress-wait-all-signal-backward
> + * Description: Signals each fence of a large timeline in reversed order
> while another thread is waiting on each point of that timeline
> + *
> + * SUBTEST: stress-wait-all-signal-random
> + * Description: Signals each fence of a large timeline in random order while
> another thread is waiting on each point of that timeline
> + *
> + * SUBTEST: stress-wait-all-signal-all-forward
> + * Description: Signals all fences of a large timeline while another thread
> is waiting on each point of that timeline
> + *
> + * SUBTEST: stress-wait-all-signal-all-backward
> + * Description: Signals all fences of a large reversed ordered timeline
> while another thread is waiting on each point of that timeline
> + *
> + * SUBTEST: stress-wait-all-signal-all-random
> + * Description: Signals all fences of a large randomly ordered timeline
> while another thread is waiting on each point of that timeline
> + *
> */
>
> IGT_TEST_DESCRIPTION("Tests for the drm timeline sync object API");
> @@ -1675,6 +1730,217 @@ test_32bits_limit(int fd)
> close(timeline);
> }
>
> +#define STRESS_FLAGS_WAIT_ALL DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL
> +#define STRESS_FLAGS_ENABLE_ALL (STRESS_FLAGS_WAIT_ALL << 1)
> +#define STRESS_FLAGS_SIGNAL_ALL (STRESS_FLAGS_ENABLE_ALL << 1)
> +#define STRESS_FLAGS_SIGNAL_BACKWARD (STRESS_FLAGS_SIGNAL_ALL << 1)
> +#define STRESS_FLAGS_SIGNAL_RANDOM (STRESS_FLAGS_SIGNAL_BACKWARD << 1)
> +
> +const char *stress_descriptions[] = {
> + /* stress-wait-last-signal-forward */
> + [0] =
> + "Signals each fence of a large timeline while another thread is
> waiting on that timeline",
> + /* stress-wait-last-signal-backward */
> + [STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals each fence of a large timeline in reverse order while
> another thread is waiting on that timeline",
> + /* stress-wait-last-signal-random */
> + [STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals each fence of a large timeline in random order while
> another thread is waiting on that timeline",
> + /* stress-wait-last-signal-all-forward */
> + [STRESS_FLAGS_SIGNAL_ALL] =
> + "Signals all fences of a large timeline while another thread is
> waiting on that timeline",
> + /* stress-wait-last-signal-all-backward */
> + [STRESS_FLAGS_SIGNAL_ALL | STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals all fences of a large reverse ordered timeline while
> another thread is waiting on that timeline",
> + /* stress-wait-last-signal-all-random */
> + [STRESS_FLAGS_SIGNAL_ALL | STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals all fences of a large randomly ordered timeline while
> another thread is waiting on that timeline",
> + /* stress-enable-all-signal-forward */
> + [STRESS_FLAGS_ENABLE_ALL] =
> + "Signals each fence of a large timeline with signaling enabled
> on each point while another thread is waiting on that timeline",
> + /* stress-enable-all-signal-backward */
> + [STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals each fence of a large timeline in reversed order with
> signaling enabled on each point while another thread is waiting on that
> timeline",
> + /* stress-enable-all-signal-random */
> + [STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals each fence of a large timeline in random order with
> signaling enabled on each point while another thread is waiting on that
> timeline",
> + /* stress-enable-all-signal-all-forward */
> + [STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_ALL] =
> + "Signals all fences of a large timeline with signaling enabled
> on each point while another thread is waiting on that timeline",
> + /* stress-enable-all-signal-all-backward */
> + [STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_ALL |
> STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals all fences of a large reversed ordered timeline with
> signaling enabled on each point while another thread is waiting on that
> timeline",
> + /* stress-enable-all-signal-all-random */
> + [STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_ALL |
> STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals all fences of a large randomly ordered timeline with
> signaling enabled on each point while another thread is waiting on that
> timeline",
> + /* stress-wait-all-signal-forward */
> + [STRESS_FLAGS_WAIT_ALL] =
> + "Signals each fence of a large timeline while another thread is
> waiting on each point of that timeline",
> + /* stress-wait-all-signal-backward */
> + [STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals each fence of a large timeline in reversed order while
> another thread is waiting on each point of that timeline",
> + /* stress-wait-all-signal-random */
> + [STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals each fence of a large timeline in random order while
> another thread is waiting on each point of that timeline",
> + /* stress-wait-all-signal-all-forward */
> + [STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_ALL] =
> + "Signals all fences of a large timeline while another thread is
> waiting on each point of that timeline",
> + /* stress-wait-all-signal-all-backward */
> + [STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_ALL |
> STRESS_FLAGS_SIGNAL_BACKWARD] =
> + "Signals all fences of a large reversed ordered timeline while
> another thread is waiting on each point of that timeline",
> + /* stress-wait-all-signal-all-random */
> + [STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_ALL |
> STRESS_FLAGS_SIGNAL_RANDOM] =
> + "Signals all fences of a large randomly ordered timeline while
> another thread is waiting on each point of that timeline",
> +};
> +
> +#define TL_LENGTH 4096
> +
> +struct stress_timeline {
> + int fd;
> + int swsync;
> + uint32_t syncobj;
> + int tmp_fence;
> + uint32_t *syncobjs;
> + uint64_t *points;
> + unsigned int length;
> + unsigned int flags;
> + pthread_t thread;
> + int retval;
> +};
> +
> +static void stress_init(int fd, struct stress_timeline **timeline, unsigned
> int flags)
> +{
> + struct stress_timeline *tl;
> + uint64_t point;
> + int i;
> +
> + tl = calloc(TL_LENGTH, sizeof(*tl));
> + igt_assert(tl);
> + *timeline = tl;
> +
> + tl->fd = fd;
> + tl->tmp_fence = -1;
> + tl->length = TL_LENGTH;
> + tl->flags = flags;
> +
> + tl->swsync = sw_sync_timeline_create();
> + tl->syncobj = syncobj_create(fd, 0);
> +
> + tl->syncobjs = calloc(tl->length, sizeof(*tl->syncobjs));
> + igt_assert(tl->syncobjs);
> +
> + tl->points = calloc(tl->length, sizeof(*tl->points));
> + igt_assert(tl->points);
> +
> + for (i = 0; i < tl->length; i++)
> + tl->points[i] = (flags & STRESS_FLAGS_SIGNAL_BACKWARD) ?
> tl->length - 1 : i + 1;
> + if (flags & STRESS_FLAGS_SIGNAL_RANDOM)
> + igt_permute_array(tl->points, tl->length, igt_exchange_int64);
> +
> + for (i = 0; i < tl->length; i++) {
> + tl->tmp_fence = sw_sync_timeline_create_fence(tl->swsync,
> tl->points[i]);
> + tl->syncobjs[i] = syncobj_create(fd, 0);
> +
> + syncobj_import_sync_file(fd, tl->syncobjs[i], tl->tmp_fence);
> + close(tl->tmp_fence);
> + tl->tmp_fence = -1;
> +
> + syncobj_binary_to_timeline(fd, tl->syncobj, i + 1,
> tl->syncobjs[i]);
> + syncobj_destroy(fd, tl->syncobjs[i]);
> +
> + tl->syncobjs[i] = tl->syncobj;
> + tl->points[i] = i + 1;
> + }
> +
> + if (flags & STRESS_FLAGS_ENABLE_ALL)
> + igt_assert_eq(syncobj_timeline_wait_err(tl->fd, tl->syncobjs,
> + tl->points, tl->length,
> 0,
> +
> DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL), -ETIME);
> +
> + syncobj_timeline_query(fd, &tl->syncobj, &point, 1);
> + igt_assert_eq(point, 0);
> +}
> +
> +static void *stress_wait_syncobj_thread_func(void *data)
> +{
> + struct stress_timeline *tl = data;
> + unsigned int count = (tl->flags & STRESS_FLAGS_WAIT_ALL) ? tl->length :
> 1;
> + uint64_t *points = &tl->points[tl->length - count];
> +
> + tl->retval = -EINPROGRESS;
> +
> + /* Wait for the timeline signaled */
> + tl->retval = syncobj_timeline_wait_err(tl->fd, tl->syncobjs, points,
> count,
> + gettime_ns() + 600 *
> NSECS_PER_SEC,
> + tl->flags &
> DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL);
> +
> + return &tl->retval;
> +}
> +
> +static void test_stress_enable_wait_signal(int fd, struct stress_timeline
> **timeline,
> + unsigned int flags)
> +{
> + struct stress_timeline *tl;
> + int64_t dt;
> + int i;
> +
> + stress_init(fd, timeline, flags);
> + tl = *timeline;
> +
> + tl->retval = 0;
> + igt_assert_eq(pthread_create(&tl->thread, NULL,
> + stress_wait_syncobj_thread_func, tl), 0);
> + igt_assert_eq(sched_yield(), 0);
> + while (READ_ONCE(tl->retval) != -EINPROGRESS)
> + ;
> + igt_assert_eq(sched_yield(), 0);
> +
> + dt = -gettime_ns();
> + if (flags & STRESS_FLAGS_SIGNAL_ALL)
> + sw_sync_timeline_inc(tl->swsync, tl->length);
> + else
> + for (i = 0; i < tl->length; i++)
> + sw_sync_timeline_inc(tl->swsync, 1);
> + dt += gettime_ns();
> + igt_info("%s: %d signals in %ld ns\n", __func__, tl->length, dt);
> +
> + igt_assert_eq(pthread_join(tl->thread, NULL), 0);
> + tl->thread = 0;
> + igt_assert_eq(tl->retval, 0);
> +}
> +
> +static void stress_cleanup(struct stress_timeline *timeline)
> +{
> + if (!timeline)
> + return;
> +
> + if (timeline->thread)
> + igt_warn_on(pthread_join(timeline->thread, NULL));
> +
> + if (timeline->points)
> + free(timeline->points);
> +
> + if (timeline->syncobjs) {
> + int i;
> +
> + for (i = 0; i < timeline->length; i++)
> + if (timeline->syncobjs && timeline->syncobjs[i] !=
> timeline->syncobj)
> + syncobj_destroy(timeline->fd,
> timeline->syncobjs[i]);
> + free(timeline->syncobjs);
> + }
> +
> + if (timeline->tmp_fence >= 0)
> + igt_warn_on(close(timeline->tmp_fence));
> +
> + if (timeline->syncobj)
> + syncobj_destroy(timeline->fd, timeline->syncobj);
> +
> + if (timeline->swsync >= 0)
> + igt_warn_on(close(timeline->swsync));
> +
> + free(timeline);
> +}
> +
> static bool
> has_syncobj_timeline_wait(int fd)
> {
> @@ -1934,6 +2200,29 @@ igt_main
> igt_subtest("32bits-limit")
> test_32bits_limit(fd);
>
> + for (unsigned int flags = 0;
> + flags < (STRESS_FLAGS_WAIT_ALL | STRESS_FLAGS_SIGNAL_ALL |
> + STRESS_FLAGS_ENABLE_ALL | STRESS_FLAGS_SIGNAL_RANDOM);
> + flags++) {
> + struct stress_timeline *timeline = NULL;
> +
> + if (flags & STRESS_FLAGS_ENABLE_ALL && flags &
> STRESS_FLAGS_WAIT_ALL)
> + continue;
> +
> + igt_describe(stress_descriptions[flags]);
> + igt_subtest_f("stress-%s-%s-signal%s-%s",
> + (flags & STRESS_FLAGS_ENABLE_ALL) ? "enable" :
> "wait",
> + (flags & (STRESS_FLAGS_WAIT_ALL |
> STRESS_FLAGS_ENABLE_ALL)) ? "all" :
> +
> "last",
> + (flags & STRESS_FLAGS_SIGNAL_ALL) ? "-all" : "",
> + (flags & STRESS_FLAGS_SIGNAL_RANDOM) ? "random" :
> + (flags & STRESS_FLAGS_SIGNAL_BACKWARD) ?
> "backward" : "forward")
> + test_stress_enable_wait_signal(fd, &timeline, flags);
> +
> + igt_fixture
> + stress_cleanup(READ_ONCE(timeline));
> + }
> +
> igt_fixture {
> drm_close_driver(fd);
> }