<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Wed, Aug 9, 2017 at 10:28 AM, Chris Wilson <span dir="ltr"><<a href="mailto:chris@chris-wilson.co.uk" target="_blank">chris@chris-wilson.co.uk</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Quoting Jason Ekstrand (2017-08-09 18:04:42)<br>
<span class="">> This adds both trivial error-checking tests as well as more complex<br>
> tests which actually test whether or not waits do what they're supposed<br>
> to do.  They only currently work on i915 but it should be simple to hook<br>
> them up for other drivers by simply implementing the little function<br>
> pointer hook provided at the top for triggering a syncobj.<br>
<br>
</span>Note that this requires a libdrm version more recent than is requested.<br>
<div><div class="h5"><br>
> ---<br>
>  tests/Makefile.sources |   1 +<br>
>  tests/syncobj_wait.c   | 624 ++++++++++++++++++++++++++++++<wbr>+++++++++++++++++++<br>
>  2 files changed, 625 insertions(+)<br>
>  create mode 100644 tests/syncobj_wait.c<br>
><br>
> diff --git a/tests/Makefile.sources b/tests/Makefile.sources<br>
> index bb013c7..430b637 100644<br>
> --- a/tests/Makefile.sources<br>
> +++ b/tests/Makefile.sources<br>
> @@ -230,6 +230,7 @@ TESTS_progs = \<br>
>         prime_vgem \<br>
>         sw_sync \<br>
>         syncobj_basic \<br>
> +       syncobj_wait \<br>
>         template \<br>
>         tools_test \<br>
>         vgem_basic \<br>
> diff --git a/tests/syncobj_wait.c b/tests/syncobj_wait.c<br>
> new file mode 100644<br>
> index 0000000..6689d34<br>
> --- /dev/null<br>
> +++ b/tests/syncobj_wait.c<br>
> @@ -0,0 +1,624 @@<br>
> +/*<br>
> + * Copyright © 2017 Intel Corporation<br>
> + *<br>
> + * Permission is hereby granted, free of charge, to any person obtaining a<br>
> + * copy of this software and associated documentation files (the "Software"),<br>
> + * to deal in the Software without restriction, including without limitation<br>
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,<br>
> + * and/or sell copies of the Software, and to permit persons to whom the<br>
> + * Software is furnished to do so, subject to the following conditions:<br>
> + *<br>
> + * The above copyright notice and this permission notice (including the next<br>
> + * paragraph) shall be included in all copies or substantial portions of the<br>
> + * Software.<br>
> + *<br>
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR<br>
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,<br>
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL<br>
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER<br>
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING<br>
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS<br>
> + * IN THE SOFTWARE.<br>
> + */<br>
> +<br>
> +#include "igt.h"<br>
> +#include <unistd.h><br>
> +#include <time.h><br>
> +#include <sys/ioctl.h><br>
> +#include "drm.h"<br>
> +<br>
> +IGT_TEST_DESCRIPTION("Tests for the drm sync object wait API");<br>
> +<br>
> +/* One one tenth of a second */<br>
> +#define SHORT_TIME_NSEC 100000000ull<br>
> +<br>
> +/** A per-platform function which triggers a set of sync objects<br>
> + *<br>
> + * If wait is set, the function should wait for the work to complete so<br>
> + * that an immediate call to SYNCOBJ_WAIT will return success.  If wait is<br>
> + * not set, then the function should try to submit enough work that an<br>
> + * immediate call to SYNCOBJ_WAIT with a timeout of 0 will time out.<br>
> + */<br>
> +void (*trigger_syncobj)(int fd, uint32_t *syncobjs, int count, bool wait);<br>
> +<br>
> +#define NSECS_PER_SEC 1000000000ull<br>
> +<br>
> +static uint64_t<br>
> +gettime_ns(void)<br>
> +{<br>
> +   struct timespec current;<br>
> +   clock_gettime(CLOCK_MONOTONIC, &current);<br>
> +   return (uint64_t)current.tv_sec * NSECS_PER_SEC + current.tv_nsec;<br>
> +}<br>
> +<br>
> +static uint64_t<br>
> +short_timeout(void)<br>
> +{<br>
> +       return gettime_ns() + SHORT_TIME_NSEC;<br>
> +}<br>
> +<br>
> +static uint32_t<br>
> +syncobj_create(int fd)<br>
> +{<br>
> +       struct drm_syncobj_create create = { 0 };<br>
> +       int ret;<br>
> +<br>
> +       ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_CREATE, &create);<br>
> +       igt_assert(ret == 0);<br>
> +       igt_assert(create.handle > 0);<br>
> +<br>
> +       return create.handle;<br>
> +}<br>
> +<br>
> +static void<br>
> +syncobj_destroy(int fd, uint32_t handle)<br>
> +{<br>
> +       struct drm_syncobj_destroy destroy = { 0 };<br>
> +       int ret;<br>
> +<br>
> +       destroy.handle = handle;<br>
> +       ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_DESTROY, &destroy);<br>
> +       igt_assert(ret == 0);<br>
> +}<br>
> +<br>
> +struct delayed_trigger {<br>
> +       int fd;<br>
> +       uint32_t *syncobjs;<br>
> +       int count;<br>
> +       uint64_t nsec;<br>
> +};<br>
> +<br>
> +static void *<br>
> +trigger_syncobj_delayed_func(<wbr>void *data)<br>
> +{<br>
> +       struct delayed_trigger *trigger = data;<br>
> +       struct timespec time;<br>
> +<br>
> +       time.tv_sec = trigger->nsec / NSECS_PER_SEC;<br>
> +       time.tv_nsec = trigger->nsec % NSECS_PER_SEC;<br>
> +<br>
> +       nanosleep(&time, NULL);<br>
> +       trigger_syncobj(trigger->fd, trigger->syncobjs, trigger->count, true);<br>
> +       free(data);<br>
> +<br>
> +       return NULL;<br>
> +}<br>
> +<br>
> +static pthread_t<br>
> +trigger_syncobj_delayed(int fd, uint32_t *syncobjs, int count, uint64_t nsec)<br>
> +{<br>
> +       struct delayed_trigger *trigger;<br>
> +       pthread_t thread;<br>
> +       int ret;<br>
> +<br>
> +       trigger = malloc(sizeof(*trigger));<br>
> +       trigger->fd = fd;<br>
> +       trigger->syncobjs = syncobjs;<br>
> +       trigger->count = count;<br>
> +       trigger->nsec = nsec;<br>
> +<br>
> +       ret = pthread_create(&thread, NULL,<br>
> +                            trigger_syncobj_delayed_func, trigger);<br>
<br>
</div></div>This is just a timer:<br>
<br>
        timer_t timer;<br>
        struct sigevent sev;<br>
        struct itimerspec its;<br>
<br>
        memset(&sev, 0, sizeof(sev));<br>
        sev.sigev_notify = SIGEV_THREAD;<br>
        sev.sigev_value.sival_ptr = tigger;<br>
        sev.sigev_notify_function = trigger_syncobj_delayed_func;<br>
        igt_assert(timer_create(CLOCK_<wbr>MONOTONIC, &sev, &timer) == 0);<br>
<br>
        memset(&its, 0, sizeof(its));<br>
        its.it_value.tv_sec = nsec / NSEC_PER_SEC;<br>
        its.it_value.tv_nsec = nsec % NSEC_PER_SEC;<br>
        igt_assert(timer_settime(<wbr>timer, 0, &its, NULL) == 0);<span class=""><br></span></blockquote><div><br></div><div>How do I do the equivalent of pthread_join() on said timer?<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +       igt_assert(ret == 0);<br>
> +<br>
> +       return thread;<br>
> +}<br>
<br>
</span>[snip]<br>
<br>
Key tests missing here are signal (SIGINT) handling, especially how the<br>
timeout parameters is handled on repeats, see igt_interruptible(), though<br>
you must use igt_ioctl(). Polling multiple handles is not<br>
checked, especially combinations of signaled/unsignaled syncojbs.<br>
<span class=""><br>
> +<br>
> +/******** i915 specific bits ******/<br>
> +struct {<br>
> +       uint32_t batch;<br>
> +} i915;<br>
> +<br>
> +#define I915_BATCH_COUNT 128<br>
<br>
</span>Is overrunning the ring/guc-wq and blocking a concern?<br>
<br>
Do 128 provide any more significance than 2, one for wait, one for signal?<br>
Do 2 provide any more than 1?<br></blockquote><div><br></div><div>1 appears to work reliably.  I just wanted to make sure.  I'll drop it to one.  It should only produce warnings at worst.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Does this provide any more value than the coverage (of execbuf +<br>
wait/signaling) in gem_exec_fence?<span class=""><br></span></blockquote><div><br></div><div>They're different wait mechanisms...<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +static void<br>
> +i915_trigger_syncobj(int fd, uint32_t *syncobjs, int count, bool wait)<br>
> +{<br>
> +       struct drm_i915_gem_exec_object2 exec_obj = { 0 };<br>
> +       struct drm_i915_gem_exec_fence *fence_array;<br>
> +       struct drm_i915_gem_execbuffer2 execbuf = { 0 };<br>
> +       struct drm_i915_gem_wait gem_wait;<br>
> +       int i, ret;<br>
> +<br>
> +       fence_array = calloc(count, sizeof(*fence_array));<br>
> +       for (i = 0; i < count; i++) {<br>
> +               fence_array[i].handle = syncobjs[i];<br>
> +               fence_array[i].flags = I915_EXEC_FENCE_SIGNAL;<br>
> +       }<br>
> +<br>
> +       exec_obj.handle = i915.batch;<br>
<br>
</span>Is there any advantage in keeping the handle around in a global?<span class=""><br></span></blockquote><div><br></div><div>Less work at trigger_syncobj time but that's all.  I can create and destroy it here if you'd rather.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +<br>
> +       execbuf.buffers_ptr = to_user_pointer(&exec_obj);<br>
> +       execbuf.buffer_count = 1;<br>
> +       execbuf.batch_start_offset = 0;<br>
> +       execbuf.batch_len = 8;<br>
> +       execbuf.flags = I915_EXEC_RENDER | I915_EXEC_FENCE_ARRAY;<br>
> +<br>
> +       for (i = 0; i < I915_BATCH_COUNT; i++) {<br>
> +               if (i == I915_BATCH_COUNT - 1) {<br>
> +                       execbuf.cliprects_ptr = to_user_pointer(fence_array);<br>
> +                       execbuf.num_cliprects = count;<br>
> +               }<br>
> +               ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_<wbr>EXECBUFFER2, &execbuf);<br>
<br>
</span>gem_execbuf<span class=""><br></span></blockquote><div><br>?<br> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +               igt_assert(ret == 0);<br>
> +       }<br>
> +<br>
> +       free(fence_array);<br>
> +<br>
> +       if (wait) {<br>
<br>
</span>This is just gem_sync() (actually just gem_wait, but you since you just<br>
want a sync, you can use gem_sync)<span class=""><br></span></blockquote><div><br></div><div>What's the difference between wait and sync?<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">
> +               gem_wait.bo_handle = i915.batch;<br>
> +               gem_wait.flags = 0;<br>
> +               gem_wait.timeout_ns = INT64_MAX;<br>
> +               ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait);<br>
> +               igt_assert(ret == 0);<br>
> +       }<br>
> +}<br>
> +<br>
> +static void<br>
> +i915_init(int fd)<br>
> +{<br>
> +       uint32_t batch_data[2] = {0, MI_BATCH_BUFFER_END};<br>
> +<br>
> +       i915.batch = gem_create(fd, 4096);<br>
> +       gem_write(fd, i915.batch, 0, batch_data, sizeof(batch_data));<br>
> +       trigger_syncobj = i915_trigger_syncobj;<br>
> +}<br>
> +/******** end of i915 bits ******/<br>
<br>
</span>If this used either vgem or sw_sync, you wouldn't need i915 specific details.<br><div><div class="h5"></div></div></blockquote><div><br></div><div>Those paths might be worth implementing as well but I wanted to make sure it worked for syncobjs triggered by the actual driver.  See also our discussion about default waits.  Also, that requires adding syncobj support to vgem.  Maybe a worth endeavor but I don't really see the point atm.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div><div class="h5">
> +<br>
> +static bool<br>
> +has_syncobj_wait(int fd)<br>
> +{<br>
> +       struct drm_syncobj_wait wait = { 0 };<br>
> +       uint64_t value;<br>
> +       int ret;<br>
> +<br>
> +       if (drmGetCap(fd, DRM_CAP_SYNCOBJ, &value))<br>
> +               return false;<br>
> +       if (!value)<br>
> +               return false;<br>
> +<br>
> +       /* Try waiting for zero sync objects should fail with EINVAL */<br>
> +       ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);<br>
> +       return ret == -1 && errno == EINVAL;<br>
> +}<br>
> +<br>
> +igt_main<br>
> +{<br>
> +       int fd;<br>
> +<br>
> +       igt_fixture {<br>
> +               fd = drm_open_driver_render(DRIVER_<wbr>INTEL);<br>
> +               igt_require_gem(fd);<br>
> +               igt_require(has_syncobj_wait(<wbr>fd));<br>
> +<br>
> +               if (is_i915_device(fd))<br>
> +                       i915_init(fd);<br>
<br>
</div></div>You asked for i915-only anyway, see DRIVER_INTEL. You meant DRIVER_ANY,<br>
but maybe just using DRIVER_VGEM and avoiding driver details would be<br>
better.<span class="HOEnZb"><font color="#888888"><br>
</font></span></blockquote></div><br></div><div class="gmail_extra">I was hoping that Dave would come along and add amdgpu bits and then we would need it.  But maybe the extra check here is overkill.<br></div></div>