From patchwork Fri Aug 25 18:00:36 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Jason Ekstrand X-Patchwork-Id: 9922647 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 2AFDD602BD for ; Fri, 25 Aug 2017 18:00:51 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1848F28414 for ; Fri, 25 Aug 2017 18:00:51 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0D2BE28438; Fri, 25 Aug 2017 18:00:51 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id CA79E28414 for ; Fri, 25 Aug 2017 18:00:49 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E3CAD6E8E1; Fri, 25 Aug 2017 18:00:48 +0000 (UTC) X-Original-To: intel-gfx@lists.freedesktop.org Delivered-To: intel-gfx@lists.freedesktop.org Received: from mail-pg0-x244.google.com (mail-pg0-x244.google.com [IPv6:2607:f8b0:400e:c05::244]) by gabe.freedesktop.org (Postfix) with ESMTPS id 1A21A6E8E3 for ; Fri, 25 Aug 2017 18:00:47 +0000 (UTC) Received: by mail-pg0-x244.google.com with SMTP id a7so677305pgn.4 for ; Fri, 25 Aug 2017 11:00:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jlekstrand-net.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Ijf+gaCD2ZDJTG3psAyZ52HbC8PRL3JXpYJ2clgfbgA=; b=qzuKk9x5drUgCOEyNLHiOar05tUrR84kPZxCeHSUCMotXjWAinGSz8S9n6PLlEfRF5 G9oo7mxYkwwlSIZXQT3dEQBk9NFq2tiLFOceAvz0IaPPszieVG2rJYHgfl23NNkI2mKz 5qaJEz4gvTfMQ0inYzRuO/Ihe1E/OGIGIFa2nQIa5I/KxMLkwwYXUntfGF6Ls8RqHp6Y BJqVpgNAFPzgBWvOWIvYB9YX1H8mqCptxU15QLKuSSrvCq+K4K0lOyzHw2X16GywTmST nxfgpzelLEeKz0R01PvI2XbspvKhsDAYIj4en9LgtMnwTKyW4y3vFyzj2PO4AzJv1WKD jxRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Ijf+gaCD2ZDJTG3psAyZ52HbC8PRL3JXpYJ2clgfbgA=; b=S2G+BQM08YQ1/1k8T2Ai6uMsvGvUFqhUKVePBYBRGX8xnvYi+7ib06PIKI2NVyi+l1 zsMroHRzkQM0YrFA8moXaTouwGY/8YfIk3N2d9Akglnf7fD4YAlPIrmkPs+XqmGrT1nb MPeIoTBq7uE8Q0LZ+Qm2TXX2lQ0ql5NJhwUaZvSSub9bJVVBtle2Q6H4Q2WoZuE/2BGd du9CRqpZrWRbmg8ZHjDpF2LNvs9v5dMdJPbjvYEn/R27I0R94PLoB4HGXXONu50bgr4z mCjxFx/ZuzUvbLZu5iupgudvybs3Nkr4XgzavjClKqedjYfBxjjTRpD9GajONm/OX0M2 dkgQ== X-Gm-Message-State: AHYfb5hElzMV0H0SYfxQsf+zPn1hvYdqDnrjC/6GQTpWoS/z3frIfspT q9P4kayAXCwft2NI8YCr1Q== X-Received: by 10.99.111.132 with SMTP id k126mr10436228pgc.76.1503684046379; Fri, 25 Aug 2017 11:00:46 -0700 (PDT) Received: from omlet.jlekstrand.net (static-50-43-41-117.bvtn.or.frontiernet.net. [50.43.41.117]) by smtp.gmail.com with ESMTPSA id l22sm12517474pfj.138.2017.08.25.11.00.44 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 25 Aug 2017 11:00:45 -0700 (PDT) From: Jason Ekstrand X-Google-Original-From: Jason Ekstrand To: intel-gfx@lists.freedesktop.org Date: Fri, 25 Aug 2017 11:00:36 -0700 Message-Id: <1503684037-32006-3-git-send-email-jason.ekstrand@intel.com> X-Mailer: git-send-email 2.5.0.400.gff86faf In-Reply-To: <1503684037-32006-1-git-send-email-jason.ekstrand@intel.com> References: <1503684037-32006-1-git-send-email-jason.ekstrand@intel.com> MIME-Version: 1.0 Cc: Jason Ekstrand Subject: [Intel-gfx] [PATCH i-g-t 3/4] tests/syncobj: Add some wait and reset tests (v6) X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Virus-Scanned: ClamAV using ClamSMTP This adds both trivial error-checking tests as well as more complex tests which actually test whether or not waits do what they're supposed to do. They only currently work on i915 but it should be simple to hook them up for other drivers by simply implementing the little function pointer hook provided at the top for triggering a syncobj. v2: - Actually add the reset tests. v3: - Only do one execbuf for trigger - Use do_ioctl and do_ioctl_err - Better check for syncobj support - Add local_/LOCAL_ defines of things - Use a timer instead of a pthread v4: - Use ioctl wrappers - Use VGEM instead of i915 - Combine a bunch of the simple tests into one function v5: - Combinatorially generate basic tests - Use sw_sync instead of using vgem directly - Add even more tests Signed-off-by: Jason Ekstrand --- tests/Makefile.sources | 1 + tests/syncobj_wait.c | 909 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 910 insertions(+) create mode 100644 tests/syncobj_wait.c diff --git a/tests/Makefile.sources b/tests/Makefile.sources index bb013c7..430b637 100644 --- a/tests/Makefile.sources +++ b/tests/Makefile.sources @@ -230,6 +230,7 @@ TESTS_progs = \ prime_vgem \ sw_sync \ syncobj_basic \ + syncobj_wait \ template \ tools_test \ vgem_basic \ diff --git a/tests/syncobj_wait.c b/tests/syncobj_wait.c new file mode 100644 index 0000000..2cb8f14 --- /dev/null +++ b/tests/syncobj_wait.c @@ -0,0 +1,909 @@ +/* + * Copyright © 2017 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "igt.h" +#include "sw_sync.h" +#include "igt_syncobj.h" +#include +#include +#include +#include "drm.h" + +IGT_TEST_DESCRIPTION("Tests for the drm sync object wait API"); + +/* One tenth of a second */ +#define SHORT_TIME_NSEC 100000000ull + +#define NSECS_PER_SEC 1000000000ull + +static uint64_t +gettime_ns(void) +{ + struct timespec current; + clock_gettime(CLOCK_MONOTONIC, ¤t); + return (uint64_t)current.tv_sec * NSECS_PER_SEC + current.tv_nsec; +} + +static void +sleep_nsec(uint64_t time_nsec) +{ + struct timespec t; + t.tv_sec = time_nsec / NSECS_PER_SEC; + t.tv_nsec = time_nsec % NSECS_PER_SEC; + igt_assert_eq(nanosleep(&t, NULL), 0); +} + +static uint64_t +short_timeout(void) +{ + return gettime_ns() + SHORT_TIME_NSEC; +} + +static int +syncobj_attach_sw_sync(int fd, uint32_t handle) +{ + struct drm_syncobj_handle; + int timeline, fence; + + timeline = sw_sync_timeline_create(); + fence = sw_sync_timeline_create_fence(timeline, 1); + syncobj_import_sync_file(fd, handle, fence); + close(fence); + + return timeline; +} + +static void +syncobj_trigger(int fd, uint32_t handle) +{ + int timeline = syncobj_attach_sw_sync(fd, handle); + sw_sync_timeline_inc(timeline, 1); + close(timeline); +} + +static timer_t +set_timer(void (*cb)(union sigval), void *ptr, int i, uint64_t nsec) +{ + timer_t timer; + struct sigevent sev; + struct itimerspec its; + + memset(&sev, 0, sizeof(sev)); + sev.sigev_notify = SIGEV_THREAD; + if (ptr) + sev.sigev_value.sival_ptr = ptr; + else + sev.sigev_value.sival_int = i; + sev.sigev_notify_function = cb; + igt_assert(timer_create(CLOCK_MONOTONIC, &sev, &timer) == 0); + + memset(&its, 0, sizeof(its)); + its.it_value.tv_sec = nsec / NSEC_PER_SEC; + its.it_value.tv_nsec = nsec % NSEC_PER_SEC; + igt_assert(timer_settime(timer, 0, &its, NULL) == 0); + + return timer; +} + +struct fd_handle_pair { + int fd; + uint32_t handle; +}; + +static void +timeline_inc_func(union sigval sigval) +{ + sw_sync_timeline_inc(sigval.sival_int, 1); +} + +static void +syncobj_trigger_free_pair_func(union sigval sigval) +{ + struct fd_handle_pair *pair = sigval.sival_ptr; + syncobj_trigger(pair->fd, pair->handle); + free(pair); +} + +static timer_t +syncobj_trigger_delayed(int fd, uint32_t syncobj, uint64_t nsec) +{ + struct fd_handle_pair *pair = malloc(sizeof(*pair)); + + pair->fd = fd; + pair->handle = syncobj; + + return set_timer(syncobj_trigger_free_pair_func, pair, 0, nsec); +} + +static void +test_wait_bad_flags(int fd) +{ + struct local_syncobj_wait wait = { 0 }; + wait.flags = 0xdeadbeef; + igt_assert_eq(__syncobj_wait(fd, &wait), -EINVAL); +} + +static void +test_wait_zero_handles(int fd) +{ + struct local_syncobj_wait wait = { 0 }; + igt_assert_eq(__syncobj_wait(fd, &wait), -EINVAL); +} + +static void +test_wait_illegal_handle(int fd) +{ + struct local_syncobj_wait wait = { 0 }; + uint32_t handle = 0; + + wait.count_handles = 1; + wait.handles = to_user_pointer(&handle); + igt_assert_eq(__syncobj_wait(fd, &wait), -ENOENT); +} + +static void +test_reset_zero_handles(int fd) +{ + struct local_syncobj_array array = { 0 }; + int ret; + + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &array); + igt_assert(ret == -1 && errno == EINVAL); +} + +static void +test_reset_illegal_handle(int fd) +{ + struct local_syncobj_array array = { 0 }; + uint32_t handle = 0; + int ret; + + array.count_handles = 1; + array.handles = to_user_pointer(&handle); + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &array); + igt_assert(ret == -1 && errno == ENOENT); +} + +static void +test_reset_one_illegal_handle(int fd) +{ + struct local_syncobj_array array = { 0 }; + uint32_t syncobjs[3]; + int ret; + + syncobjs[0] = syncobj_create(fd, LOCAL_SYNCOBJ_CREATE_SIGNALED); + syncobjs[1] = 0; + syncobjs[2] = syncobj_create(fd, LOCAL_SYNCOBJ_CREATE_SIGNALED); + + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[0], 1, 0, 0), 0); + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[2], 1, 0, 0), 0); + + array.count_handles = 3; + array.handles = to_user_pointer(syncobjs); + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &array); + igt_assert(ret == -1 && errno == ENOENT); + + /* Assert that we didn't actually reset anything */ + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[0], 1, 0, 0), 0); + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[2], 1, 0, 0), 0); + + syncobj_destroy(fd, syncobjs[0]); + syncobj_destroy(fd, syncobjs[2]); +} + +static void +test_reset_bad_pad(int fd) +{ + struct local_syncobj_array array = { 0 }; + int ret; + + array.pad = 0xdeadbeef; + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &array); + igt_assert(ret == -1 && errno == EINVAL); +} + +static void +test_signal_zero_handles(int fd) +{ + struct local_syncobj_array array = { 0 }; + int ret; + + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_SIGNAL, &array); + igt_assert(ret == -1 && errno == EINVAL); +} + +static void +test_signal_illegal_handle(int fd) +{ + struct local_syncobj_array array = { 0 }; + uint32_t handle = 0; + int ret; + + array.count_handles = 1; + array.handles = to_user_pointer(&handle); + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_SIGNAL, &array); + igt_assert(ret == -1 && errno == ENOENT); +} + +static void +test_signal_one_illegal_handle(int fd) +{ + struct local_syncobj_array array = { 0 }; + uint32_t syncobjs[3]; + int ret; + + syncobjs[0] = syncobj_create(fd, 0); + syncobjs[1] = 0; + syncobjs[2] = syncobj_create(fd, 0); + + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[0], 1, 0, 0), -EINVAL); + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[2], 1, 0, 0), -EINVAL); + + array.count_handles = 3; + array.handles = to_user_pointer(syncobjs); + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_SIGNAL, &array); + igt_assert(ret == -1 && errno == ENOENT); + + /* Assert that we didn't actually reset anything */ + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[0], 1, 0, 0), -EINVAL); + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[2], 1, 0, 0), -EINVAL); + + syncobj_destroy(fd, syncobjs[0]); + syncobj_destroy(fd, syncobjs[2]); +} + +static void +test_signal_bad_pad(int fd) +{ + struct local_syncobj_array array = { 0 }; + int ret; + + array.pad = 0xdeadbeef; + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_SIGNAL, &array); + igt_assert(ret == -1 && errno == EINVAL); +} + +#define WAIT_FOR_SUBMIT (1 << 0) +#define WAIT_ALL (1 << 1) +#define WAIT_UNSUBMITTED (1 << 2) +#define WAIT_SUBMITTED (1 << 3) +#define WAIT_SIGNALED (1 << 4) +#define WAIT_FLAGS_MAX (1 << 5) - 1 + +static uint32_t +flags_for_test_flags(uint32_t test_flags) +{ + uint32_t flags = 0; + + if (test_flags & WAIT_FOR_SUBMIT) + flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT; + + if (test_flags & WAIT_ALL) + flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL; + + return flags; +} + +static void +test_single_wait(int fd, uint32_t test_flags, int expect) +{ + uint32_t syncobj = syncobj_create(fd, 0); + uint32_t flags = flags_for_test_flags(test_flags); + int timeline; + + if (test_flags & (WAIT_SUBMITTED | WAIT_SIGNALED)) + timeline = syncobj_attach_sw_sync(fd, syncobj); + + if (test_flags & WAIT_SIGNALED) + sw_sync_timeline_inc(timeline, 1); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, flags), expect); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, short_timeout(), + flags), expect); + + if (expect != -ETIME) { + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, UINT64_MAX, + flags), expect); + } + + syncobj_destroy(fd, syncobj); +} + +static void +test_wait_delayed_signal(int fd, uint32_t test_flags) +{ + uint32_t syncobj = syncobj_create(fd, 0); + uint32_t flags = flags_for_test_flags(test_flags); + int timeline; + timer_t timer; + + if (test_flags & WAIT_FOR_SUBMIT) { + timer = syncobj_trigger_delayed(fd, syncobj, SHORT_TIME_NSEC); + } else { + timeline = syncobj_attach_sw_sync(fd, syncobj); + timer = set_timer(timeline_inc_func, NULL, + timeline, SHORT_TIME_NSEC); + } + + igt_assert(syncobj_wait(fd, &syncobj, 1, + gettime_ns() + SHORT_TIME_NSEC * 2, + flags, NULL)); + + timer_delete(timer); + + if (!(test_flags & WAIT_FOR_SUBMIT)) + close(timeline); + + syncobj_destroy(fd, syncobj); +} + +static void +test_reset_unsignaled(int fd) +{ + uint32_t syncobj = syncobj_create(fd, 0); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL); + + syncobj_reset(fd, &syncobj, 1); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL); + + syncobj_destroy(fd, syncobj); +} + +static void +test_reset_signaled(int fd) +{ + uint32_t syncobj = syncobj_create(fd, 0); + + syncobj_trigger(fd, syncobj); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), 0); + + syncobj_reset(fd, &syncobj, 1); + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL); + + syncobj_destroy(fd, syncobj); +} + +static void +test_reset_multiple_signaled(int fd) +{ + uint32_t syncobjs[3]; + int i; + + for (i = 0; i < 3; i++) { + syncobjs[i] = syncobj_create(fd, 0); + syncobj_trigger(fd, syncobjs[i]); + } + + igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, 0, 0), 0); + + syncobj_reset(fd, syncobjs, 3); + + for (i = 0; i < 3; i++) { + igt_assert_eq(syncobj_wait_err(fd, &syncobjs[i], 1, + 0, 0), -EINVAL); + syncobj_destroy(fd, syncobjs[i]); + } +} + +static void +reset_and_trigger_func(union sigval sigval) +{ + struct fd_handle_pair *pair = sigval.sival_ptr; + syncobj_reset(pair->fd, &pair->handle, 1); + syncobj_trigger(pair->fd, pair->handle); +} + +static void +test_reset_during_wait_for_submit(int fd) +{ + uint32_t syncobj = syncobj_create(fd, 0); + uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT; + struct fd_handle_pair pair; + timer_t timer; + + pair.fd = fd; + pair.handle = syncobj; + timer = set_timer(reset_and_trigger_func, &pair, 0, SHORT_TIME_NSEC); + + /* A reset should be a no-op even if we're in the middle of a wait */ + igt_assert(syncobj_wait(fd, &syncobj, 1, + gettime_ns() + SHORT_TIME_NSEC * 2, + flags, NULL)); + + timer_delete(timer); + + syncobj_destroy(fd, syncobj); +} + +static void +test_signal(int fd) +{ + uint32_t syncobj = syncobj_create(fd, 0); + uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT; + + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL); + igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, flags), -ETIME); + + syncobj_signal(fd, &syncobj, 1); + + igt_assert(syncobj_wait(fd, &syncobj, 1, 0, 0, NULL)); + igt_assert(syncobj_wait(fd, &syncobj, 1, 0, flags, NULL)); + + syncobj_destroy(fd, syncobj); +} + +static void +test_multi_wait(int fd, uint32_t test_flags, int expect) +{ + uint32_t syncobjs[3]; + uint32_t tflag, flags; + int i, fidx, timeline; + + syncobjs[0] = syncobj_create(fd, 0); + syncobjs[1] = syncobj_create(fd, 0); + syncobjs[2] = syncobj_create(fd, 0); + + flags = flags_for_test_flags(test_flags); + test_flags &= ~(WAIT_ALL | WAIT_FOR_SUBMIT); + + for (i = 0; i < 3; i++) { + fidx = ffs(test_flags) - 1; + tflag = (1 << fidx); + + if (test_flags & ~tflag) + test_flags &= ~tflag; + + if (tflag & (WAIT_SUBMITTED | WAIT_SIGNALED)) + timeline = syncobj_attach_sw_sync(fd, syncobjs[i]); + if (tflag & WAIT_SIGNALED) + sw_sync_timeline_inc(timeline, 1); + } + + igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, 0, flags), expect); + + igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, short_timeout(), + flags), expect); + + if (expect != -ETIME) { + igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, UINT64_MAX, + flags), expect); + } + + syncobj_destroy(fd, syncobjs[0]); + syncobj_destroy(fd, syncobjs[1]); + syncobj_destroy(fd, syncobjs[2]); +} + +struct wait_thread_data { + int fd; + struct local_syncobj_wait wait; +}; + +static void * +wait_thread_func(void *data) +{ + struct wait_thread_data *wait = data; + igt_assert_eq(__syncobj_wait(wait->fd, &wait->wait), 0); + return NULL; +} + +static void +test_wait_snapshot(int fd, uint32_t test_flags) +{ + struct wait_thread_data wait = { 0 }; + uint32_t syncobjs[2]; + int timelines[2] = { -1, -1 }; + pthread_t thread; + + syncobjs[0] = syncobj_create(fd, 0); + syncobjs[1] = syncobj_create(fd, 0); + + if (!(test_flags & WAIT_FOR_SUBMIT)) { + timelines[0] = syncobj_attach_sw_sync(fd, syncobjs[0]); + timelines[1] = syncobj_attach_sw_sync(fd, syncobjs[1]); + } + + wait.fd = fd; + wait.wait.handles = to_user_pointer(syncobjs); + wait.wait.count_handles = 2; + wait.wait.timeout_nsec = short_timeout(); + wait.wait.flags = flags_for_test_flags(test_flags); + + igt_assert_eq(pthread_create(&thread, NULL, wait_thread_func, &wait), 0); + + sleep_nsec(SHORT_TIME_NSEC / 5); + + /* Try to fake the kernel out by triggering or partially triggering + * the first fence. + */ + if (test_flags & WAIT_ALL) { + /* If it's WAIT_ALL, actually trigger it */ + if (timelines[0] == -1) + syncobj_trigger(fd, syncobjs[0]); + else + sw_sync_timeline_inc(timelines[0], 1); + } else if (test_flags & WAIT_FOR_SUBMIT) { + timelines[0] = syncobj_attach_sw_sync(fd, syncobjs[0]); + } + + sleep_nsec(SHORT_TIME_NSEC / 5); + + /* Then reset it */ + syncobj_reset(fd, &syncobjs[0], 1); + + sleep_nsec(SHORT_TIME_NSEC / 5); + + /* Then "submit" it in a way that will never trigger. This way, if + * the kernel picks up on the new fence (it shouldn't), we'll get a + * timeout. + */ + close(timelines[0]); + timelines[0] = syncobj_attach_sw_sync(fd, syncobjs[0]); + + sleep_nsec(SHORT_TIME_NSEC / 5); + + /* Now trigger the second fence to complete the wait */ + + if (timelines[1] == -1) + syncobj_trigger(fd, syncobjs[1]); + else + sw_sync_timeline_inc(timelines[1], 1); + + pthread_join(thread, NULL); + + if (!(test_flags & WAIT_ALL)) + igt_assert_eq(wait.wait.first_signaled, 1); + + close(timelines[0]); + close(timelines[1]); + syncobj_destroy(fd, syncobjs[0]); + syncobj_destroy(fd, syncobjs[1]); +} + +/* The numbers 0-7, each repeated 5x and shuffled. */ +static const unsigned shuffled_0_7_x4[] = { + 2, 0, 6, 1, 1, 4, 5, 2, 0, 7, 1, 7, 6, 3, 4, 5, + 0, 2, 7, 3, 5, 4, 0, 6, 7, 3, 2, 5, 6, 1, 4, 3, +}; + +enum syncobj_stage { + STAGE_UNSUBMITTED, + STAGE_SUBMITTED, + STAGE_SIGNALED, + STAGE_RESET, + STAGE_RESUBMITTED, +}; + +static void +test_wait_complex(int fd, uint32_t test_flags) +{ + struct wait_thread_data wait = { 0 }; + uint32_t syncobjs[8]; + enum syncobj_stage stage[8]; + int i, j, timelines[8]; + uint32_t first_signaled, num_signaled; + pthread_t thread; + + for (i = 0; i < 8; i++) { + stage[i] = STAGE_UNSUBMITTED; + syncobjs[i] = syncobj_create(fd, 0); + } + + if (test_flags & WAIT_FOR_SUBMIT) { + for (i = 0; i < 8; i++) + timelines[i] = -1; + } else { + for (i = 0; i < 8; i++) + timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i]); + } + + wait.fd = fd; + wait.wait.handles = to_user_pointer(syncobjs); + wait.wait.count_handles = 2; + wait.wait.timeout_nsec = gettime_ns() + NSECS_PER_SEC; + wait.wait.flags = flags_for_test_flags(test_flags); + + igt_assert_eq(pthread_create(&thread, NULL, wait_thread_func, &wait), 0); + + sleep_nsec(NSECS_PER_SEC / 50); + + num_signaled = 0; + for (j = 0; j < ARRAY_SIZE(shuffled_0_7_x4); j++) { + i = shuffled_0_7_x4[j]; + igt_assert_lt(i, ARRAY_SIZE(syncobjs)); + + switch (stage[i]++) { + case STAGE_UNSUBMITTED: + /* We need to submit attach a fence */ + if (!(test_flags & WAIT_FOR_SUBMIT)) { + /* We had to attach one up-front */ + igt_assert_neq(timelines[i], -1); + break; + } + timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i]); + break; + + case STAGE_SUBMITTED: + /* We have a fence, trigger it */ + igt_assert_neq(timelines[i], -1); + sw_sync_timeline_inc(timelines[i], 1); + close(timelines[i]); + timelines[i] = -1; + if (num_signaled == 0) + first_signaled = i; + num_signaled++; + break; + + case STAGE_SIGNALED: + /* We're already signaled, reset */ + syncobj_reset(fd, &syncobjs[i], 1); + break; + + case STAGE_RESET: + /* We're reset, submit and don't signal */ + timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i]); + break; + + case STAGE_RESUBMITTED: + igt_assert(!"Should not reach this stage"); + break; + } + + if (test_flags & WAIT_ALL) { + if (num_signaled == ARRAY_SIZE(syncobjs)) + break; + } else { + if (num_signaled > 0) + break; + } + + sleep_nsec(NSECS_PER_SEC / 100); + } + + pthread_join(thread, NULL); + + if (test_flags & WAIT_ALL) { + igt_assert_eq(num_signaled, ARRAY_SIZE(syncobjs)); + } else { + igt_assert_eq(num_signaled, 1); + igt_assert_eq(wait.wait.first_signaled, first_signaled); + } + + for (i = 0; i < 8; i++) { + close(timelines[i]); + syncobj_destroy(fd, syncobjs[i]); + } +} + +static void +test_wait_interrupted(int fd, uint32_t test_flags) +{ + struct local_syncobj_wait wait = { 0 }; + uint32_t syncobj = syncobj_create(fd, 0); + int ret, timeline; + + wait.handles = to_user_pointer(&syncobj); + wait.count_handles = 1; + wait.flags = flags_for_test_flags(test_flags); + + if (test_flags & WAIT_FOR_SUBMIT) { + wait.timeout_nsec = short_timeout(); + igt_while_interruptible(true) { + ret = igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait); + igt_assert(ret == -1 && errno == ETIME); + } + } + + timeline = syncobj_attach_sw_sync(fd, syncobj); + close(timeline); + + wait.timeout_nsec = short_timeout(); + igt_while_interruptible(true) { + ret = igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait); + igt_assert(ret == -1 && errno == ETIME); + } + + syncobj_destroy(fd, syncobj); +} + +static bool +has_syncobj_wait(int fd) +{ + struct local_syncobj_wait wait = { 0 }; + uint32_t handle = 0; + uint64_t value; + int ret; + + if (drmGetCap(fd, DRM_CAP_SYNCOBJ, &value)) + return false; + if (!value) + return false; + + /* Try waiting for zero sync objects should fail with EINVAL */ + wait.count_handles = 1; + wait.handles = to_user_pointer(&handle); + ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait); + return ret == -1 && errno == ENOENT; +} + +igt_main +{ + int fd; + + igt_fixture { + fd = drm_open_driver(DRIVER_ANY); + igt_require(has_syncobj_wait(fd)); + igt_require_sw_sync(); + } + + igt_subtest("invalid-wait-bad-flags") + test_wait_bad_flags(fd); + + igt_subtest("invalid-wait-zero-handles") + test_wait_zero_handles(fd); + + igt_subtest("invalid-wait-illegal-handle") + test_wait_illegal_handle(fd); + + igt_subtest("invalid-reset-zero-handles") + test_reset_zero_handles(fd); + + igt_subtest("invalid-reset-illegal-handle") + test_reset_illegal_handle(fd); + + igt_subtest("invalid-reset-one-illegal-handle") + test_reset_one_illegal_handle(fd); + + igt_subtest("invalid-reset-bad-pad") + test_reset_bad_pad(fd); + + igt_subtest("invalid-signal-zero-handles") + test_signal_zero_handles(fd); + + igt_subtest("invalid-signal-illegal-handle") + test_signal_illegal_handle(fd); + + igt_subtest("invalid-signal-one-illegal-handle") + test_signal_one_illegal_handle(fd); + + igt_subtest("invalid-signal-bad-pad") + test_signal_bad_pad(fd); + + for (unsigned flags = 0; flags < WAIT_FLAGS_MAX; flags++) { + int err; + + /* Only one wait mode for single-wait tests */ + if (__builtin_popcount(flags & (WAIT_UNSUBMITTED | + WAIT_SUBMITTED | + WAIT_SIGNALED)) != 1) + continue; + + if ((flags & WAIT_UNSUBMITTED) && !(flags & WAIT_FOR_SUBMIT)) + err = -EINVAL; + else if (!(flags & WAIT_SIGNALED)) + err = -ETIME; + else + err = 0; + + igt_subtest_f("%ssingle-wait%s%s%s%s%s", + err == -EINVAL ? "invalid-" : "", + (flags & WAIT_ALL) ? "-all" : "", + (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "", + (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "", + (flags & WAIT_SUBMITTED) ? "-submitted" : "", + (flags & WAIT_SIGNALED) ? "-signaled" : "") + test_single_wait(fd, flags, err); + } + + igt_subtest("wait-delayed-signal") + test_wait_delayed_signal(fd, 0); + + igt_subtest("wait-for-submit-delayed-submit") + test_wait_delayed_signal(fd, WAIT_FOR_SUBMIT); + + igt_subtest("wait-all-delayed-signal") + test_wait_delayed_signal(fd, WAIT_ALL); + + igt_subtest("wait-all-for-submit-delayed-submit") + test_wait_delayed_signal(fd, WAIT_ALL | WAIT_FOR_SUBMIT); + + igt_subtest("reset-unsignaled") + test_reset_unsignaled(fd); + + igt_subtest("reset-signaled") + test_reset_signaled(fd); + + igt_subtest("reset-multiple-signaled") + test_reset_multiple_signaled(fd); + + igt_subtest("reset-during-wait-for-submit") + test_reset_during_wait_for_submit(fd); + + igt_subtest("signal") + test_signal(fd); + + for (unsigned flags = 0; flags < WAIT_FLAGS_MAX; flags++) { + int err; + + /* At least one wait mode for multi-wait tests */ + if (!(flags & (WAIT_UNSUBMITTED | + WAIT_SUBMITTED | + WAIT_SIGNALED))) + continue; + + err = 0; + if ((flags & WAIT_UNSUBMITTED) && !(flags & WAIT_FOR_SUBMIT)) { + err = -EINVAL; + } else if (flags & WAIT_ALL) { + if (flags & (WAIT_UNSUBMITTED | WAIT_SUBMITTED)) + err = -ETIME; + } else { + if (!(flags & WAIT_SIGNALED)) + err = -ETIME; + } + + igt_subtest_f("%smulti-wait%s%s%s%s%s", + err == -EINVAL ? "invalid-" : "", + (flags & WAIT_ALL) ? "-all" : "", + (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "", + (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "", + (flags & WAIT_SUBMITTED) ? "-submitted" : "", + (flags & WAIT_SIGNALED) ? "-signaled" : "") + test_multi_wait(fd, flags, err); + } + + igt_subtest("wait-any-snapshot") + test_wait_snapshot(fd, 0); + + igt_subtest("wait-all-snapshot") + test_wait_snapshot(fd, WAIT_ALL); + + igt_subtest("wait-for-submit-snapshot") + test_wait_snapshot(fd, WAIT_FOR_SUBMIT); + + igt_subtest("wait-all-for-submit-snapshot") + test_wait_snapshot(fd, WAIT_ALL | WAIT_FOR_SUBMIT); + + igt_subtest("wait-any-complex") + test_wait_complex(fd, 0); + + igt_subtest("wait-all-complex") + test_wait_complex(fd, WAIT_ALL); + + igt_subtest("wait-for-submit-complex") + test_wait_complex(fd, WAIT_FOR_SUBMIT); + + igt_subtest("wait-all-for-submit-complex") + test_wait_complex(fd, WAIT_ALL | WAIT_FOR_SUBMIT); + + igt_subtest("wait-any-interrupted") + test_wait_interrupted(fd, 0); + + igt_subtest("wait-all-interrupted") + test_wait_interrupted(fd, WAIT_ALL); +}