diff options
author | Dave Gordon <david.s.gordon@intel.com> | 2014-07-15 15:24:38 +0100 |
---|---|---|
committer | John Harrison <John.C.Harrison@Intel.com> | 2016-04-20 16:42:31 +0100 |
commit | 44760411db89365707932f2ec2fb51503dacfee2 (patch) | |
tree | 293b96c502488633dd5712f464c313fc7a75365b | |
parent | 03ce9629f41a81c4fb9a2613cdd779b1929fc1fb (diff) |
igt/gem_exec_preempt: new test for scheduler/preemptionIGT
Signed-off-by: Dave Gordon <david.s.gordon@intel.com>
-rw-r--r-- | tests/Makefile.sources | 1 | ||||
-rw-r--r-- | tests/gem_ctx_param_basic.c | 2 | ||||
-rw-r--r-- | tests/gem_exec_preempt.c | 462 |
3 files changed, 465 insertions, 0 deletions
diff --git a/tests/Makefile.sources b/tests/Makefile.sources index 0c0f877f5274..a6613d1d496a 100644 --- a/tests/Makefile.sources +++ b/tests/Makefile.sources @@ -144,6 +144,7 @@ TESTS_progs = \ gem_exec_big \ gem_exec_blt \ gem_exec_lut_handle \ + gem_exec_preempt \ gem_fd_exhaustion \ gem_gtt_cpu_tlb \ gem_gtt_hog \ diff --git a/tests/gem_ctx_param_basic.c b/tests/gem_ctx_param_basic.c index 1a10c018a1c7..001e6ddfe45f 100644 --- a/tests/gem_ctx_param_basic.c +++ b/tests/gem_ctx_param_basic.c @@ -60,7 +60,9 @@ igt_main igt_subtest("basic") { ctx_param.context = ctx; TEST_SUCCESS(LOCAL_IOCTL_I915_GEM_CONTEXT_GETPARAM); +printf( "\x1B[33;1mBoo!\x1B[0m\n" ); TEST_SUCCESS(LOCAL_IOCTL_I915_GEM_CONTEXT_SETPARAM); +printf( "\x1B[33mBaa!\x1B[0m\n" ); } igt_subtest("basic-default") { diff --git a/tests/gem_exec_preempt.c b/tests/gem_exec_preempt.c new file mode 100644 index 000000000000..d60c543a9c45 --- /dev/null +++ b/tests/gem_exec_preempt.c @@ -0,0 +1,462 @@ +/* + * Copyright © 2011 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. + * + * Authors: + * Chris Wilson <chris@chris-wilson.co.uk> + * Dave Gordon <david.s.gordon@intel.com> + * + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <inttypes.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include "drm.h" +#include "i915_drm.h" +#include "drmtest.h" +#include "../lib/ioctl_wrappers.h" +/*#include "intel_gpu_tools.h"*/ + +// #define I915_CONTEXT_PARAM_PRIORITY 0x3 +// #define LOCAL_IOCTL_I915_GEM_CONTEXT_SETPARAM DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_CONTEXT_SETPARAM, struct drm_i915_gem_context_param) + + +#define LOCAL_I915_EXEC_VEBOX (4<<0) + +#define BATCH_WORDS 1024 +#define BATCH_TOP 1000 +uint32_t batch[BATCH_WORDS]; +uint32_t handle0, handle1, handle2; +uint32_t ctx0, ctx1; +int drm_fd; + +extern time_t time(time_t *t); + +#if 0 +#define CONTEXT_DESTROY_IOCTL DRM_IOWR(DRM_COMMAND_BASE + 0x2e, struct local_drm_i915_gem_context_destroy) +#ifndef DRM_IOCTL_I915_GEM_CONTEXT_DESTROY +#define DRM_IOCTL_I915_GEM_CONTEXT_DESTROY DRM_IOW(DRM_COMMAND_BASE + DRM_I915_GEM_CONTEXT_DESTROY, struct drm_i915_gem_context_destroy) +#endif +#else +#define MI_INSTR(x, y) (((x) << 23) | (y)) +#define MI_ARB_CHECK MI_INSTR(0x05, 0) +#define MI_STORE_DWORD_INDEX MI_INSTR(0x21, 1) +#define MI_LOAD_REGISTER_IMM(x) MI_INSTR(0x22, 2*(x)-1) +#define REG_NOPID 0x2094 +#endif + +static void +context_destroy(int fd, uint32_t ctx_id) +{ + struct drm_i915_gem_context_destroy destroy = { .ctx_id = ctx_id }; + int ret; + + errno = 0; + ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_CONTEXT_DESTROY, &destroy); +#if 1 + fprintf(stderr, "%s(%d, 0x%x): drmIoctl(%d, 0x%lx, %p) = %d, errno %d\n", + __func__, fd, ctx_id, fd, DRM_IOCTL_I915_GEM_CONTEXT_DESTROY, &destroy, ret, errno); +#endif +} + +static double +elapsed(const struct timeval *start, const struct timeval *end, int loops) +{ + return (1e6*(end->tv_sec - start->tv_sec) + (end->tv_usec - start->tv_usec))/loops; +} + +/* + * Do an ioctl; retry on certain (non-)errors. There's an + * implicit one-second timeout (10000*100us) to prevent lockups. + */ +static int +checked_ioctl(int fd, int cmd, void *ptr) +{ + int i, ret; + + for (i = 0; i < 10000; ++i) { + errno = 0; + ret = ioctl(fd, cmd, ptr); + if (ret == 0) + return ret; + + if (ret > 0) { + fprintf(stderr, "checked_ioctl: ioctl(%d, 0x%x, %p) = %d, errno %d\n", + fd, cmd, ptr, ret, errno); + return ret; + } + + /* ret < 0 :-( */ + switch (errno) { + case EAGAIN: + case EBUSY: + usleep(100); + case EINTR: + continue; + + default: + fprintf(stderr, "checked_ioctl: failed ioctl(%d, 0x%x, %p) = %d, errno %d\n", + fd, cmd, ptr, ret, errno); + return errno; + } + } + + fprintf(stderr, "checked_ioctl: timeout ioctl(%d, 0x%x, %p) = %d, errno %d\n", + fd, cmd, ptr, ret, errno); + return errno; +} + +/* + * Do the EXECBUFFER2 ioctl. + */ +static int +exec_ioctl(int fd, struct drm_i915_gem_execbuffer2 *eb2p) +{ + return checked_ioctl(fd, DRM_IOCTL_I915_GEM_EXECBUFFER2, eb2p); +} + +/* + * Wait for the completion of a buffer, by trying to change its domain + */ +static void +exec_sync(int fd, uint32_t handle) +{ + struct drm_i915_gem_set_domain set_domain; + + set_domain.handle = handle; + set_domain.read_domains = I915_GEM_DOMAIN_GTT; + set_domain.write_domain = I915_GEM_DOMAIN_GTT; + + igt_assert(checked_ioctl(fd, DRM_IOCTL_I915_GEM_SET_DOMAIN, &set_domain) == 0); +} + +/*struct drm_i915_gem_context_param { + __u32 ctx_id; + __u32 size; + __u64 param; +#define I915_CONTEXT_PARAM_BAN_PERIOD 0x1 +#define I915_CONTEXT_PARAM_NO_ZEROMAP 0x2 +#define I915_CONTEXT_PARAM_PRIORITY 0x3 + __u64 value; +};*/ + +static void enable_preempt( int fd, uint32_t ctx_id, bool enable ) +{ + struct drm_i915_gem_context_param param; + + memset(¶m, 0, sizeof(param)); + + param.ctx_id = ctx_id; + param.size = 0; + param.param = I915_CONTEXT_PARAM_PRIORITY; + param.value = enable ? 1000 : -100; + igt_assert(checked_ioctl(fd, DRM_IOCTL_I915_GEM_CONTEXT_SETPARAM, ¶m) == 0); +} + +/* + * Submit the predefined batch for execution some number of times, + * optionally followed by a preemptive batch and then maybe some + * more non-preemptive batches. + */ +static int +exec_batch(int fd, int loops, unsigned ring_id, int preempt, bool mid) +{ + int ret = 0; + struct drm_i915_gem_exec_object2 gx0 = + { + .handle = handle0, + }; + struct drm_i915_gem_execbuffer2 eb0 = + { /* Preemptive batch */ + .buffers_ptr = (uintptr_t)&gx0, + .buffer_count = 1, + .batch_start_offset = BATCH_TOP*sizeof(batch[0]), + .batch_len = sizeof(batch) - BATCH_TOP*sizeof(batch[0]), + .flags = ring_id, + }; + + struct drm_i915_gem_exec_object2 gx1 = + { + .handle = handle1, + }; + struct drm_i915_gem_execbuffer2 eb1 = + { /* Preemptible batch */ + .buffers_ptr = (uintptr_t)&gx1, + .buffer_count = 1, + .batch_start_offset = 0, + .batch_len = sizeof(batch), + .flags = ring_id, + }; + + struct drm_i915_gem_exec_object2 gx2 = + { + .handle = handle2, + }; + struct drm_i915_gem_execbuffer2 eb2 = + { /* Mid-preemptible batch */ + .buffers_ptr = (uintptr_t)&gx2, + .buffer_count = 1, + .batch_start_offset = 0, + .batch_len = sizeof(batch), + .flags = ring_id, + }; + + /* Only the render ring supports non-default contexts */ + if (ring_id == I915_EXEC_RENDER) { + i915_execbuffer2_set_context_id(eb0, ctx0); + i915_execbuffer2_set_context_id(eb1, ctx1); + i915_execbuffer2_set_context_id(eb2, ctx1); + } + + /* Submit some non-preemptive batches */ + while (ret == 0 && loops-- > 0) + if (mid && loops%4 == 0) + ret = exec_ioctl(fd, &eb2); + else + ret = exec_ioctl(fd, &eb1); + + if (ret == 0 && preempt != 0) { + enable_preempt(fd, ctx0, true); + + /* This time, invoke preemption */ + ret = exec_ioctl(fd, &eb0); + + enable_preempt(fd, ctx0, false); + + /* Then maybe submit some more non-preemptive batches */ + for (; ret == 0 && preempt > 0; --preempt) + ret = exec_ioctl(fd, &eb1); + + /* Wait for preemption to finish */ + if (preempt >= -1) + exec_sync(fd, handle0); + } + + /* Wait for execution to finish */ + if (preempt >= -1) + exec_sync(fd, handle1); + return ret; +} + +static void +loop_exec_batch(int fd, unsigned ring_id, const char *ring_name, int preempt, bool mid) +{ + int count; + + gem_require_ring(fd, ring_id); + + for (count = 1; count <= SLOW_QUICK(1<<17, 1<<4); count <<= 1) { + struct timeval start, end; + + gettimeofday(&start, NULL); + if (exec_batch(fd, count, ring_id, preempt, mid)) + igt_fail(1); + gettimeofday(&end, NULL); + printf("Time to exec x %d: %7.3fµs (ring=%s)\n", + count, elapsed(&start, &end, count), ring_name); + fflush(stdout); + } +} + +static void +exec_one_preemptive(int fd, unsigned ring_id, const char *ring_name) +{ + struct timeval start, end; + + gem_require_ring(fd, ring_id); + + gettimeofday(&start, NULL); + if (exec_batch(fd, 0, ring_id, -2, 0)) + igt_fail(1); + gettimeofday(&end, NULL); + printf("Time to exec x %d: %7.3fµs (ring=%s)\n", + 1, elapsed(&start, &end, 1), ring_name); + fflush(stdout); +} + +static void +batch_preempt_setup(int fd) +{ + int i, j, k, t; + + /* + * The batch buffer is filled with NOOPs nearly up to the end, + * followed by a BATCH BUFFER END command and one more NOOP. + * Then we overwrite one of the NOOPs (at offset BATCH_TOP-1) + * with another BATCH BUFFER END. This effectively gives us two + * different sequences within the same buffer, one at offsets + * 0..BATCH_TOP-1, and another at BATCH_TOP..BATCH_WORDS, and + * we can then select either by choosing the offset from which + * to start execution. As coded above, we use the longer head + * section for the default batch, and the shorter tail section + * for preemptive batches. + */ + for (i = 0; i < BATCH_WORDS-2; ++i) + batch[i] = MI_NOOP | MI_NOOP_WRITE_ID | ((0x1000+i) & MI_NOOP_ID_MASK); + batch[i] = MI_BATCH_BUFFER_END; + batch[++i] = MI_NOOP; + + /* handle0 = preemptive batch */ + gem_write(fd, handle0, 0, batch, sizeof(batch)); + + /* handle1 = default batch */ +#if 1 + for (i = 7; i < BATCH_TOP-2; i += 16) + batch[i] = MI_ARB_CHECK; +#endif /*1*/ + batch[BATCH_TOP-1] = MI_BATCH_BUFFER_END; + gem_write(fd, handle1, 0, batch, sizeof(batch)); + + /* + * Now we create a completly different batch of commands for + * testing mid-batch preemption. This batch first clears a + * range of words in the Hardware Status Page, then writes + * a distinct value to each in turn, allowing preemption after + * each write. + */ +#define HWSP_TGT_MIN 0x40 +#define HWSP_TGT_MAX 0x80 + /* These instructions start at offset 0 in the batch buffer */ + for (i = 0, j = HWSP_TGT_MIN; j < HWSP_TGT_MAX; ++j) + { +#if 0 + batch[i++] = MI_STORE_DWORD_INDEX; + batch[i++] = j*sizeof(int); + batch[i++] = 0; +#else + batch[i++] = MI_LOAD_REGISTER_IMM(1); + batch[i++] = REG_NOPID; + batch[i++] = j*sizeof(int); +#endif + } + /* These instructions start at offset (3*64*4 = 0x300) */ + for (k = 0; k < 3; ++k) + { + for (j = HWSP_TGT_MIN; j < HWSP_TGT_MAX; ++j) + { + t = 0xd5900000 + (i << 8) + j; +#if 0 + batch[i++] = MI_STORE_DWORD_INDEX; + batch[i++] = j*sizeof(int); + batch[i++] = t; +#else + batch[i++] = MI_LOAD_REGISTER_IMM(1); + batch[i++] = REG_NOPID; + batch[i++] = t; +#endif + batch[i++] = MI_ARB_CHECK; + } + } + /* These instructions end at offset ((3+4+4+4)*64*4 = 0xf00) */ + gem_write(fd, handle2, 0, batch, sizeof(batch)); +} + +static void +global_init(void) +{ + errno = 0; + drm_fd = drm_open_driver(DRIVER_INTEL); +#if 1 + fprintf(stderr, "opened fd %d, errno %d\n", drm_fd, errno); +#endif + + errno = 0; + ctx0 = gem_context_create(drm_fd); + ctx1 = gem_context_create(drm_fd); +#if 1 + fprintf(stderr, "created contexts 0x%x and 0x%x, errno %d\n", + ctx0, ctx1, errno); +#endif + + errno = 0; + handle0 = gem_create(drm_fd, sizeof(batch)); + handle1 = gem_create(drm_fd, sizeof(batch)); + handle2 = gem_create(drm_fd, sizeof(batch)); +#if 1 + fprintf(stderr, "created handles %d, %d and %d, errno %d\n", + handle0, handle1, handle2, errno); +#endif +} + +static void +global_fini(void) +{ + context_destroy(drm_fd, ctx1); + context_destroy(drm_fd, ctx0); + gem_close(drm_fd, handle2); + gem_close(drm_fd, handle1); + gem_close(drm_fd, handle0); + close(drm_fd); +} + +int +main(int argc, char **argv) +{ + igt_subtest_init(argc, argv); + + igt_fixture { + global_init(); + batch_preempt_setup(drm_fd); + } + +/* + igt_subtest("render") + loop_exec_batch(drm_fd, I915_EXEC_RENDER, "render", 0, 0); + + igt_subtest("bsd") + loop_exec_batch(drm_fd, I915_EXEC_BSD, "bsd", 0, 0); + + igt_subtest("blt") + loop_exec_batch(drm_fd, I915_EXEC_BLT, "blt", 0, 0); + + igt_subtest("vebox") + loop_exec_batch(drm_fd, LOCAL_I915_EXEC_VEBOX, "vebox", 0, 0); +*/ + + igt_subtest("preempt_0") + loop_exec_batch(drm_fd, I915_EXEC_RENDER, "render", -1, 0); + + igt_subtest("preempt_1") + loop_exec_batch(drm_fd, I915_EXEC_RENDER, "render", 1, 0); + + igt_subtest("preempt_2") + loop_exec_batch(drm_fd, I915_EXEC_RENDER, "render", 8, 0); + + igt_subtest("preempt_mid") + loop_exec_batch(drm_fd, I915_EXEC_RENDER, "render", 1, 1); + + igt_subtest("preempt_nowait") + exec_one_preemptive(drm_fd, I915_EXEC_RENDER, "render"); + + igt_fixture { + global_fini(); + } + + igt_exit(); +} |