/* * Copyright © 2018 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 #include #include #include #include #include "i915/gem.h" #include "i915/gem_create.h" #include "i915/gem_submission.h" #include "igt.h" #include "sw_sync.h" /* To help craft commands known to be invalid across all engines */ #define INSTR_CLIENT_SHIFT 29 #define INSTR_INVALID_CLIENT 0x7 #define MI_ARB_ON_OFF (0x8 << 23) #define MI_USER_INTERRUPT (0x02 << 23) #define MI_FLUSH_DW (0x26 << 23) #define MI_REPORT_HEAD (0x07 << 23) #define MI_SUSPEND_FLUSH (0x0b << 23) #define MI_LOAD_SCAN_LINES_EXCL (0x13 << 23) #define MI_UPDATE_GTT (0x23 << 23) #define BCS_SWCTRL 0x22200 #define BCS_GPR_BASE 0x22600 #define BCS_GPR(n) (0x22600 + (n) * 8) #define BCS_GPR_UDW(n) (0x22600 + (n) * 8 + 4) #define HANDLE_SIZE 4096 static int __checked_execbuf(int i915, struct drm_i915_gem_execbuffer2 *eb) { int fence; int err; igt_assert(!(eb->flags & I915_EXEC_FENCE_OUT)); eb->flags |= I915_EXEC_FENCE_OUT; err = __gem_execbuf_wr(i915, eb); eb->flags &= ~I915_EXEC_FENCE_OUT; if (err) return err; fence = eb->rsvd2 >> 32; sync_fence_wait(fence, -1); err = sync_fence_status(fence); close(fence); if (err < 0) return err; return 0; } static int __exec_batch_patched(int i915, const intel_ctx_t *ctx, int engine, uint32_t cmd_bo, const uint32_t *cmds, int size, uint32_t target_bo, uint64_t target_offset, uint64_t target_delta) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[2]; struct drm_i915_gem_relocation_entry reloc[1]; gem_write(i915, cmd_bo, 0, cmds, size); memset(obj, 0, sizeof(obj)); obj[0].handle = target_bo; obj[1].handle = cmd_bo; memset(reloc, 0, sizeof(reloc)); reloc[0].offset = target_offset; reloc[0].target_handle = target_bo; reloc[0].delta = target_delta; reloc[0].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[0].write_domain = I915_GEM_DOMAIN_COMMAND; reloc[0].presumed_offset = -1; obj[1].relocs_ptr = to_user_pointer(reloc); obj[1].relocation_count = 1; memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 2; execbuf.batch_len = size; execbuf.rsvd1 = ctx->id; execbuf.flags = engine; return __checked_execbuf(i915, &execbuf); } static void exec_batch_patched(int i915, const intel_ctx_t *ctx, int engine, uint32_t cmd_bo, const uint32_t *cmds, int size, int patch_offset, long int expected_value) { const uint32_t target_bo = gem_create(i915, HANDLE_SIZE); uint64_t actual_value = 0; long int ret; ret = __exec_batch_patched(i915, ctx, engine, cmd_bo, cmds, size, target_bo, patch_offset, 0); if (ret) { igt_assert_lt(ret, 0); gem_close(i915, target_bo); igt_assert_eq(ret, expected_value); return; } gem_read(i915, target_bo, 0, &actual_value, sizeof(actual_value)); gem_close(i915, target_bo); igt_assert_eq(actual_value, expected_value); } static int __exec_batch(int i915, const intel_ctx_t *ctx, int engine, uint32_t cmd_bo, const uint32_t *cmds, int size) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[1]; gem_write(i915, cmd_bo, 0, cmds, size); memset(obj, 0, sizeof(obj)); obj[0].handle = cmd_bo; memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 1; execbuf.batch_len = size; execbuf.rsvd1 = ctx->id; execbuf.flags = engine; return __checked_execbuf(i915, &execbuf); } #if 0 static void print_batch(const uint32_t *cmds, const uint32_t sz) { const int commands = sz / 4; int i; igt_info("Batch size %d\n", sz); for (i = 0; i < commands; i++) igt_info("0x%08x: 0x%08x\n", i, cmds[i]); } #else #define print_batch(cmds, size) #endif #define exec_batch(i915, ctx, engine, bo, cmds, sz, expected) \ print_batch(cmds, sz); \ igt_assert_eq(__exec_batch(i915, ctx, engine, bo, cmds, sz), expected) static void exec_split_batch(int i915, const intel_ctx_t *ctx, int engine, const uint32_t *cmds, int size, int expected_ret) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[1]; uint32_t cmd_bo; const uint32_t noop[1024] = { 0 }; const int alloc_size = 4096 * 2; const int actual_start_offset = 4096-sizeof(uint32_t); /* Allocate and fill a 2-page batch with noops */ cmd_bo = gem_create(i915, alloc_size); gem_write(i915, cmd_bo, 0, noop, sizeof(noop)); gem_write(i915, cmd_bo, 4096, noop, sizeof(noop)); /* Write the provided commands such that the first dword * of the command buffer is the last dword of the first * page (i.e. the command is split across the two pages). */ gem_write(i915, cmd_bo, actual_start_offset, cmds, size); memset(obj, 0, sizeof(obj)); obj[0].handle = cmd_bo; memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 1; /* NB: We want batch_start_offset and batch_len to point to the block * of the actual commands (i.e. at the last dword of the first page), * but have to adjust both the start offset and length to meet the * kernel driver's requirements on the alignment of those fields. */ execbuf.batch_start_offset = actual_start_offset & ~0x7; execbuf.batch_len = ALIGN(size + actual_start_offset - execbuf.batch_start_offset, 0x8); execbuf.rsvd1 = ctx->id; execbuf.flags = engine; igt_assert_eq(__checked_execbuf(i915, &execbuf), expected_ret); gem_close(i915, cmd_bo); } static void exec_batch_chained(int i915, const intel_ctx_t *ctx, int engine, uint32_t cmd_bo, const uint32_t *cmds, int size, int patch_offset, uint64_t expected_value, int expected_return) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[3]; struct drm_i915_gem_relocation_entry reloc[1]; struct drm_i915_gem_relocation_entry first_level_reloc; const uint32_t target_bo = gem_create(i915, 4096); const uint32_t first_level_bo = gem_create(i915, 4096); uint64_t actual_value = 0; int ret; const uint32_t first_level_cmds[] = { MI_BATCH_BUFFER_START | MI_BATCH_NON_SECURE_I965 | 1, 0, 0, MI_BATCH_BUFFER_END, }; gem_write(i915, first_level_bo, 0, first_level_cmds, sizeof(first_level_cmds)); gem_write(i915, cmd_bo, 0, cmds, size); memset(obj, 0, sizeof(obj)); obj[0].handle = target_bo; obj[1].handle = cmd_bo; obj[2].handle = first_level_bo; memset(reloc, 0, sizeof(reloc)); reloc[0].offset = patch_offset; reloc[0].delta = 0; reloc[0].target_handle = target_bo; reloc[0].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[0].write_domain = I915_GEM_DOMAIN_COMMAND; reloc[0].presumed_offset = -1; obj[1].relocation_count = 1; obj[1].relocs_ptr = to_user_pointer(&reloc); memset(&first_level_reloc, 0, sizeof(first_level_reloc)); first_level_reloc.offset = 4; first_level_reloc.delta = 0; first_level_reloc.target_handle = cmd_bo; first_level_reloc.read_domains = I915_GEM_DOMAIN_COMMAND; first_level_reloc.write_domain = 0; obj[2].relocation_count = 1; obj[2].relocs_ptr = to_user_pointer(&first_level_reloc); memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 3; execbuf.batch_len = sizeof(first_level_cmds); execbuf.rsvd1 = ctx->id; execbuf.flags = engine; ret = __checked_execbuf(i915, &execbuf); if (expected_return && ret == expected_return) goto out; gem_read(i915,target_bo, 0, &actual_value, sizeof(actual_value)); out: if (!expected_return) igt_assert_eq(expected_value, actual_value); else igt_assert_neq(expected_value, actual_value); gem_close(i915, first_level_bo); gem_close(i915, target_bo); } static void test_secure_batches(const int i915) { int v = -1; drm_i915_getparam_t gp; gp.param = I915_PARAM_HAS_SECURE_BATCHES; gp.value = &v; igt_assert_eq(drmIoctl(i915, DRM_IOCTL_I915_GETPARAM, &gp), 0); igt_assert_eq(v, 0); igt_assert_f(gem_uses_full_ppgtt(i915), "full-ppgtt required for read-only post-validated batches\n"); } struct cmd { uint32_t cmd; int len; const char *name; }; #define CMD(C, L) { .cmd = (C), .len = (L), .name = #C } #define CMD_N(C) { .cmd = (C), .len = 1, .name = #C } static const struct cmd allowed_cmds[] = { CMD_N(MI_NOOP), CMD_N(MI_USER_INTERRUPT), CMD_N(MI_WAIT_FOR_EVENT), CMD(MI_FLUSH_DW, 5), CMD_N(MI_ARB_CHECK), CMD_N(MI_REPORT_HEAD), CMD_N(MI_FLUSH), CMD_N(MI_ARB_ON_OFF), CMD_N(MI_SUSPEND_FLUSH), CMD(MI_LOAD_SCAN_LINES_INCL, 2), CMD(MI_LOAD_SCAN_LINES_EXCL, 2), }; static uint32_t *inject_cmd(uint32_t *batch, const uint32_t cmd, int len) { int i = 0; batch[i++] = cmd; while (--len) batch[i++] = 0; return &batch[i]; } static unsigned long batch_num_cmds(const uint32_t * const batch_start, const uint32_t * const batch_end) { igt_assert_lte((unsigned long)batch_start, (unsigned long)batch_end); return batch_end - batch_start; } static unsigned long batch_bytes(const uint32_t * const batch_start, const uint32_t * const batch_end) { const unsigned long bytes = batch_num_cmds(batch_start, batch_end) * 4; return ALIGN(bytes, 8); } static void test_allowed_all(const int i915, const uint32_t handle) { uint32_t batch[4096]; uint32_t *b = &batch[0]; for (int i = 0; i < ARRAY_SIZE(allowed_cmds); i++) b = inject_cmd(b, allowed_cmds[i].cmd, allowed_cmds[i].len); b = inject_cmd(b, MI_BATCH_BUFFER_END, 1); exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, batch, batch_bytes(batch, b), 0); } static void test_allowed_single(const int i915, const uint32_t handle) { uint32_t batch[4096]; for (int i = 0; i < ARRAY_SIZE(allowed_cmds); i++) { uint32_t *b = &batch[0]; b = inject_cmd(b, allowed_cmds[i].cmd, allowed_cmds[i].len); b = inject_cmd(b, MI_BATCH_BUFFER_END, 1); igt_assert_eq(__exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, batch, batch_bytes(batch, b)), 0); }; } static void test_bb_secure(const int i915, const uint32_t handle) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[1]; struct drm_i915_gem_relocation_entry reloc[1]; const uint32_t batch_secure[] = { MI_BATCH_BUFFER_START | 1, 12, 0, MI_NOOP, MI_NOOP, MI_BATCH_BUFFER_END, }; gem_write(i915, handle, 0, batch_secure, sizeof(batch_secure)); memset(obj, 0, sizeof(obj)); obj[0].handle = handle; memset(reloc, 0, sizeof(reloc)); reloc[0].offset = 1 * sizeof(uint32_t); reloc[0].target_handle = handle; reloc[0].delta = 4 * sizeof(uint32_t); reloc[0].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[0].write_domain = 0; reloc[0].presumed_offset = -1; obj[0].relocs_ptr = to_user_pointer(reloc); obj[0].relocation_count = 1; memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 1; execbuf.batch_len = sizeof(batch_secure); execbuf.flags = I915_EXEC_BLT; igt_assert_eq(__checked_execbuf(i915, &execbuf), -EACCES); } #define BB_START_PARAM 0 #define BB_START_OUT 1 #define BB_START_CMD 2 #define BB_START_FAR 3 static void test_bb_start(const int i915, const uint32_t handle, int test) { struct drm_i915_gem_execbuffer2 execbuf; struct drm_i915_gem_exec_object2 obj[2]; struct drm_i915_gem_relocation_entry reloc[4]; const uint32_t target_bo = gem_create(i915, 4096); unsigned int jump_off, footer_pos; uint32_t batch[1024] = { MI_NOOP, MI_NOOP, MI_NOOP, MI_NOOP, MI_STORE_DWORD_IMM, 0, 0, 1, MI_STORE_DWORD_IMM, 4, 0, 2, MI_COND_BATCH_BUFFER_END | MI_DO_COMPARE | 2, 0, 0, 0, MI_ARB_CHECK, }; const uint32_t batch_footer[] = { MI_BATCH_BUFFER_START | MI_BATCH_NON_SECURE_I965 | 1, 0, 0, MI_BATCH_BUFFER_END, }; uint32_t *dst; igt_require(gem_can_store_dword(i915, I915_EXEC_BLT)); switch (test) { case BB_START_PARAM: jump_off = 5 * sizeof(uint32_t); break; case BB_START_CMD: case BB_START_FAR: jump_off = 8 * sizeof(uint32_t); break; default: jump_off = 0xf00d0000; break; } if (test == BB_START_FAR) footer_pos = sizeof(batch) - sizeof(batch_footer); else footer_pos = 17 * sizeof(uint32_t); memcpy(batch + footer_pos / sizeof(uint32_t), batch_footer, sizeof(batch_footer)); gem_write(i915, handle, 0, batch, sizeof(batch)); memset(obj, 0, sizeof(obj)); obj[0].handle = target_bo; obj[1].handle = handle; memset(reloc, 0, sizeof(reloc)); reloc[0].offset = 5 * sizeof(uint32_t); reloc[0].target_handle = obj[0].handle; reloc[0].delta = 0; reloc[0].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[0].write_domain = I915_GEM_DOMAIN_COMMAND; reloc[1].offset = 9 * sizeof(uint32_t); reloc[1].target_handle = obj[0].handle; reloc[1].delta = 1 * sizeof(uint32_t); reloc[1].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[1].write_domain = I915_GEM_DOMAIN_COMMAND; reloc[2].offset = 14 * sizeof(uint32_t); reloc[2].target_handle = obj[0].handle; reloc[2].delta = 0; reloc[2].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[2].write_domain = 0; reloc[3].offset = footer_pos + 1 * sizeof(uint32_t); reloc[3].target_handle = obj[1].handle; reloc[3].delta = jump_off; reloc[3].read_domains = I915_GEM_DOMAIN_COMMAND; reloc[3].write_domain = 0; reloc[3].presumed_offset = -1; obj[1].relocs_ptr = to_user_pointer(reloc); obj[1].relocation_count = ARRAY_SIZE(reloc); memset(&execbuf, 0, sizeof(execbuf)); execbuf.buffers_ptr = to_user_pointer(obj); execbuf.buffer_count = 2; execbuf.batch_len = sizeof(batch); execbuf.flags = I915_EXEC_BLT; dst = gem_mmap__wc(i915, obj[0].handle, 0, 4096, PROT_WRITE); igt_assert_eq(dst[0], 0); igt_assert_eq(dst[1], 0); switch (test) { case BB_START_PARAM: case BB_START_OUT: igt_assert_eq(__checked_execbuf(i915, &execbuf), -EINVAL); break; case BB_START_CMD: case BB_START_FAR: gem_execbuf(i915, &execbuf); while (READ_ONCE(dst[0]) == 0) ; while (READ_ONCE(dst[1]) == 0) ; igt_assert_eq(dst[0], 1); igt_assert_eq(dst[1], 2); dst[0] = 0; __sync_synchronize(); break; } gem_munmap(dst, 4096); gem_close(i915, target_bo); } static void test_bb_large(int i915) { const uint32_t bbe = MI_BATCH_BUFFER_END; static const uint32_t sizes[] = { (1ull << 30) - 4096, (1ull << 30) + 4096, (2ull << 30) - 4096, (2ull << 30) + 4096, (3ull << 30) - 4096, (3ull << 30) + 4096, (4ull << 30) - 4096 /* upper bound of execbuf2 uAPI */ }; struct drm_i915_gem_exec_object2 obj = {}; struct drm_i915_gem_execbuffer2 execbuf = { .buffers_ptr = to_user_pointer(&obj), .buffer_count = 1, .flags = I915_EXEC_BLT, }; uint64_t required, total; int i; for (i = 0; i < ARRAY_SIZE(sizes); i++) { if (!__igt_check_memory(2, sizes[i], CHECK_RAM, &required, &total)) break; igt_debug("Using object size %#x\n", sizes[i]); obj.handle = gem_create(i915, sizes[i]), gem_write(i915, obj.handle, sizes[i] - 64, &bbe, sizeof(bbe)); execbuf.batch_start_offset = 0; igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); execbuf.batch_start_offset = sizes[i] - 64; igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); gem_close(i915, obj.handle); } igt_require_f(i > 0 && sizes[i - 1] > 1ull << 31, "Insufficient free memory, require at least %'"PRIu64"MiB but only have %'"PRIu64"MiB available\n", required >> 20, total >> 20); } static void test_bb_oversize(int i915) { const uint32_t bbe = MI_BATCH_BUFFER_END; struct drm_i915_gem_exec_object2 obj = { .handle = gem_create(i915, 8ull << 30), .flags = EXEC_OBJECT_SUPPORTS_48B_ADDRESS }; struct drm_i915_gem_execbuffer2 execbuf = { .buffers_ptr = to_user_pointer(&obj), .buffer_count = 1, .batch_start_offset = (4ull << 30) - 4096, .flags = I915_EXEC_BLT, }; igt_require_memory(2, 8ull << 30, CHECK_RAM); gem_write(i915, obj.handle, (4ull << 30) - sizeof(bbe), &bbe, sizeof(bbe)); igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); for (int i = 13; i <= 32; i++) { igt_debug("Checking length %#llx\n", 1ull << i); execbuf.batch_len = (1ull << i) - 4096; igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); execbuf.batch_len = (1ull << i) + 4096; /* will wrap */ igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); } execbuf.batch_len = 0; igt_assert_eq(__checked_execbuf(i915, &execbuf), 0); gem_close(i915, obj.handle); } static void test_bb_chained(const int i915, const uint32_t handle) { const uint32_t batch[] = { (0x20 << 23) | 2, /* MI_STORE_DATA_IMM */ 0, 0, 0xbaadf00d, MI_NOOP, MI_BATCH_BUFFER_END, }; exec_batch_chained(i915, intel_ctx_0(i915), I915_EXEC_RENDER, handle, batch, sizeof(batch), 4, 0xbaadf00d, 0); exec_batch_chained(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, batch, sizeof(batch), 4, 0xbaadf00d, EPERM); } static void test_cmd_crossing_page(const int i915, const uint32_t handle) { const uint32_t lri_ok[] = { MI_LOAD_REGISTER_IMM, BCS_GPR(0), 0xbaadf00d, MI_BATCH_BUFFER_END, }; const uint32_t store_reg[] = { MI_STORE_REGISTER_MEM | 2, BCS_GPR(0), 0, /* reloc */ 0, /* reloc */ MI_NOOP, MI_BATCH_BUFFER_END, }; exec_split_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, lri_ok, sizeof(lri_ok), 0); exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), 2 * sizeof(uint32_t), /* reloc */ 0xbaadf00d); } static void test_invalid_length(const int i915, const uint32_t handle) { const uint32_t ok_val = 0xbaadf00d; const uint32_t bad_val = 0xf00dbaad; const uint32_t noops[8192] = { 0, }; const uint32_t lri_ok[] = { MI_LOAD_REGISTER_IMM, BCS_GPR(0), ok_val, MI_BATCH_BUFFER_END, }; const uint32_t lri_bad[] = { MI_LOAD_REGISTER_IMM, BCS_GPR(0), bad_val, MI_BATCH_BUFFER_END, }; const uint32_t store_reg[] = { MI_STORE_REGISTER_MEM | 2, BCS_GPR(0), 0, /* reloc */ 0, /* reloc */ MI_NOOP, MI_BATCH_BUFFER_END, }; exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri_ok, sizeof(lri_ok), 0); exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), 2 * sizeof(uint32_t), /* reloc */ ok_val); exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri_bad, 0, 0); exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), 2 * sizeof(uint32_t), /* reloc */ ok_val); exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri_ok, 4096, 0); igt_assert_eq(__gem_write(i915, handle, 0, noops, 4097), -EINVAL); } struct reg { uint32_t addr; uint32_t mask; bool masked_write; bool privileged; }; #define REG_M(ADDR, MASK, WM, P) { (ADDR), (MASK), (WM), (P) } #define REG(ADDR) REG_M(ADDR, 0xffffffff, false, false) #define REG_P(ADDR) REG_M(ADDR, 0xffffffff, false, true) static const struct reg regs[] = { REG_M(BCS_SWCTRL, 0x3, true, false), REG(BCS_GPR(0)), REG(BCS_GPR_UDW(0)), REG(BCS_GPR(1)), REG(BCS_GPR_UDW(1)), REG(BCS_GPR(2)), REG(BCS_GPR_UDW(2)), REG(BCS_GPR(3)), REG(BCS_GPR_UDW(3)), REG(BCS_GPR(4)), REG(BCS_GPR_UDW(4)), REG(BCS_GPR(5)), REG(BCS_GPR_UDW(5)), REG(BCS_GPR(6)), REG(BCS_GPR_UDW(6)), REG(BCS_GPR(7)), REG(BCS_GPR_UDW(7)), REG(BCS_GPR(8)), REG(BCS_GPR_UDW(8)), REG(BCS_GPR(9)), REG(BCS_GPR_UDW(9)), REG(BCS_GPR(10)), REG(BCS_GPR_UDW(10)), REG(BCS_GPR(11)), REG(BCS_GPR_UDW(11)), REG(BCS_GPR(12)), REG(BCS_GPR_UDW(12)), REG(BCS_GPR(13)), REG(BCS_GPR_UDW(13)), REG(BCS_GPR(14)), REG(BCS_GPR_UDW(14)), REG(BCS_GPR(15)), REG(BCS_GPR_UDW(15)), REG_P(0), REG_P(200000), REG_P(BCS_SWCTRL - 1), REG_P(BCS_SWCTRL - 2), REG_P(BCS_SWCTRL - 3), REG_P(BCS_SWCTRL - 4), REG_P(BCS_SWCTRL + 4), REG_P(BCS_GPR(0) - 1), REG_P(BCS_GPR(0) - 2), REG_P(BCS_GPR(0) - 3), REG_P(BCS_GPR(0) - 4), REG_P(BCS_GPR_UDW(15) + 4), }; static void test_register(const int i915, const uint32_t handle, const struct reg *r) { const uint32_t lri_zero[] = { MI_LOAD_REGISTER_IMM, r->addr, r->masked_write ? 0xffff0000 : 0, MI_BATCH_BUFFER_END, }; const uint32_t lri_mask[] = { MI_LOAD_REGISTER_IMM, r->addr, r->masked_write ? (r->mask << 16) | r->mask : r->mask, MI_BATCH_BUFFER_END, }; const uint32_t store_reg[] = { MI_STORE_REGISTER_MEM | 2, r->addr, 0, /* reloc */ 0, /* reloc */ MI_NOOP, MI_BATCH_BUFFER_END, }; exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri_mask, sizeof(lri_mask), r->privileged ? -EACCES : 0); exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), 2 * sizeof(uint32_t), /* reloc */ r->privileged ? -EACCES : r->mask); exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri_zero, sizeof(lri_zero), r->privileged ? -EACCES : 0); exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), 2 * sizeof(uint32_t), /* reloc */ r->privileged ? -EACCES : 0); } static void test_valid_registers(const int i915, const uint32_t handle) { int i; for (i = 0; i < ARRAY_SIZE(regs); i++) test_register(i915, handle, ®s[i]); } static long int read_reg(const int i915, const uint32_t handle, const uint32_t addr) { const uint32_t store_reg[] = { MI_STORE_REGISTER_MEM | 2, addr, 0, /* reloc */ 0, /* reloc */ MI_NOOP, MI_BATCH_BUFFER_END, }; uint32_t target_bo; uint32_t value; long int ret; target_bo = gem_create(i915, HANDLE_SIZE); ret = __exec_batch_patched(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, store_reg, sizeof(store_reg), target_bo, 2 * sizeof(uint32_t), 0); if (ret) { igt_assert_lt(ret, 0); gem_close(i915, target_bo); return ret; } gem_read(i915, target_bo, 0, &value, sizeof(value)); gem_close(i915, target_bo); return value; } static int write_reg(const int i915, const uint32_t handle, const uint32_t addr, const uint32_t val) { const uint32_t lri[] = { MI_LOAD_REGISTER_IMM, addr, val, MI_BATCH_BUFFER_END, }; return __exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, lri, sizeof(lri)); } static void test_unaligned_access(const int i915, const uint32_t handle) { const uint32_t addr = BCS_GPR(4); const uint32_t val = 0xbaadfead; const uint32_t pre = 0x12345678; const uint32_t post = 0x87654321; igt_assert_eq(write_reg(i915, handle, addr - 4, pre), 0); igt_assert_eq(write_reg(i915, handle, addr, val), 0); igt_assert_eq(write_reg(i915, handle, addr + 4, post), 0); igt_assert_eq(read_reg(i915, handle, addr - 4), pre); igt_assert_eq(read_reg(i915, handle, addr), val); igt_assert_eq(read_reg(i915, handle, addr + 4), post); for (int i = 0; i < 4; i++) { igt_assert_eq(write_reg(i915, handle, addr + i, val), 0); igt_assert_eq(read_reg(i915, handle, addr), val); igt_assert_eq(read_reg(i915, handle, addr + 1), val); igt_assert_eq(read_reg(i915, handle, addr + 2), val); igt_assert_eq(read_reg(i915, handle, addr + 3), val); igt_assert_eq(read_reg(i915, handle, addr + 4), post); igt_assert_eq(read_reg(i915, handle, addr - 3), pre); igt_assert_eq(read_reg(i915, handle, addr - 2), pre); igt_assert_eq(read_reg(i915, handle, addr - 1), pre); } } static void test_unaligned_jump(const int i915, const uint32_t handle) { uint32_t xy[] = { 2 << 29 | 0x53 << 22 | (10 - 2), /* XY_SRC_COPY */ 0, 0, 0, 0, 0, 0, 0, 0, 0, }; uint32_t batch[] = { MI_BATCH_BUFFER_START | MI_BATCH_NON_SECURE_I965 | 1, 0, 0, }; struct drm_i915_gem_relocation_entry reloc = { .target_handle = handle, .offset = 1001 * sizeof(uint32_t), }; struct drm_i915_gem_exec_object2 obj = { .handle = handle, .relocs_ptr = to_user_pointer(&reloc), .relocation_count = 1, }; struct drm_i915_gem_execbuffer2 execbuf = { .buffers_ptr = to_user_pointer(&obj), .buffer_count = 1, .flags = I915_EXEC_BLT, }; for (reloc.delta = 0; reloc.delta < 4000; reloc.delta += sizeof(xy)) gem_write(i915, handle, reloc.delta, xy, sizeof(xy)); for (reloc.delta = 0; reloc.delta < 4000; reloc.delta += 4) { uint64_t offset = reloc.delta + reloc.presumed_offset; if ((reloc.delta % sizeof(xy)) == 0) continue; memcpy(&batch[1], &offset, sizeof(offset)); gem_write(i915, handle, 4000, batch, sizeof(batch)); igt_assert_f(__checked_execbuf(i915, &execbuf) == -EINVAL, "unaligned jump accepted to %d; batch=%08x\n", reloc.delta, batch[reloc.delta / 4]); } } static void test_reject_on_engine(int i915, const intel_ctx_t *ctx, unsigned int engine, uint32_t handle) { const uint32_t invalid_cmd[] = { INSTR_INVALID_CLIENT << INSTR_CLIENT_SHIFT, MI_BATCH_BUFFER_END, }; const uint32_t invalid_set_context[] = { MI_SET_CONTEXT | 32, /* invalid length */ MI_BATCH_BUFFER_END, }; exec_batch(i915, ctx, engine, handle, invalid_cmd, sizeof(invalid_cmd), -EINVAL); exec_batch(i915, ctx, engine, handle, invalid_set_context, sizeof(invalid_set_context), -EINVAL); } static void test_rejected(int i915, uint32_t handle, bool ctx_param) { if (ctx_param) { intel_ctx_cfg_t cfg = {}; const intel_ctx_t *ctx; int i; for (i = 0; i < GEM_MAX_ENGINES; i++) { cfg.engines[i].engine_class = I915_ENGINE_CLASS_COPY; cfg.engines[i].engine_instance = 0; } cfg.num_engines = GEM_MAX_ENGINES; ctx = intel_ctx_create(i915, &cfg); for (i = 0; i <= I915_EXEC_RING_MASK; i++) test_reject_on_engine(i915, ctx, i, handle); intel_ctx_destroy(i915, ctx); } else { test_reject_on_engine(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle); } } #define PAGE_SHIFT 12 #define PAGE_SIZE (1ULL << 12) static inline uint32_t fill_and_copy_shadow(uint32_t *batch, uint32_t len, uintptr_t src, uintptr_t dst) { unsigned int i = 0; #define XY_COLOR_BLT_CMD (2 << 29 | 0x50 << 22) #define BLT_WRITE_ALPHA (1<<21) #define BLT_WRITE_RGB (1<<20) batch[i++] = XY_COLOR_BLT_CMD | BLT_WRITE_ALPHA | BLT_WRITE_RGB | (7 - 2); batch[i++] = 0xf0 << 16 | 1 << 25 | 1 << 24 | PAGE_SIZE; batch[i++] = 0; batch[i++] = len >> PAGE_SHIFT << 16 | PAGE_SIZE / 4; batch[i++] = lower_32_bits(dst); batch[i++] = upper_32_bits(dst); batch[i++] = 0xdeadbeaf; batch[i++] = 0; #define COPY_BLT_CMD (2<<29|0x53<<22) batch[i++] = COPY_BLT_CMD | BLT_WRITE_ALPHA | BLT_WRITE_RGB | 8; batch[i++] = 0xcc << 16 | 1 << 25 | 1 << 24 | PAGE_SIZE; batch[i++] = 0; batch[i++] = len >> PAGE_SHIFT << 16 | PAGE_SIZE / 4; batch[i++] = lower_32_bits(dst); batch[i++] = upper_32_bits(dst); batch[i++] = 0; batch[i++] = PAGE_SIZE; batch[i++] = lower_32_bits(src); batch[i++] = upper_32_bits(src); batch[i++] = MI_BATCH_BUFFER_END; batch[i++] = 0; return i * sizeof(uint32_t); } static inline uint64_t sign_extend(uint64_t x, int index) { int shift = 63 - index; return (int64_t)(x << shift) >> shift; } static uint64_t gen8_canonical_address(uint64_t address) { return sign_extend(address, 47); } static void test_shadow_peek(int fd) { uint64_t size = PAGE_SIZE; struct drm_i915_gem_exec_object2 exec[2] = {}; struct drm_i915_gem_execbuffer2 execbuf = { .buffers_ptr = to_user_pointer(exec), .buffer_count = 2, }; uint32_t *vaddr; uint32_t len; int i; exec[0].handle = gem_create(fd, size); /* scratch for shadow */ exec[0].flags = EXEC_OBJECT_PINNED | EXEC_OBJECT_SUPPORTS_48B_ADDRESS | EXEC_OBJECT_PAD_TO_SIZE; exec[0].offset = 0; /* * Ensure the shadow has no place to hide, if say it were placed * randomly within the address space. We leave enough space for our * batch, which leaves exactly one perfect sized hole for the shadow to * occupy later. * * Note that pad_to_size is just the node.size for the vma, which means * we can easily occupy the entire 48b ppGTT, if we want, without * needing an insane amount of physical memory. */ exec[0].pad_to_size = gem_aperture_size(fd) - 2 * size; exec[1].handle = gem_create(fd, size); /* batch */ exec[1].flags = EXEC_OBJECT_PINNED | EXEC_OBJECT_SUPPORTS_48B_ADDRESS; exec[1].offset = gen8_canonical_address(exec[0].pad_to_size); vaddr = gem_mmap__wc(fd, exec[1].handle, 0, size, PROT_WRITE); len = fill_and_copy_shadow(vaddr, size, exec[0].pad_to_size + size, /* shadow location */ exec[0].offset); munmap(vaddr, size); execbuf.flags = I915_EXEC_BLT; execbuf.batch_start_offset = 0; execbuf.batch_len = len; igt_assert_eq(__gem_execbuf(fd, &execbuf), 0); gem_sync(fd, exec[1].handle); gem_set_domain(fd, exec[0].handle, I915_GEM_DOMAIN_CPU, I915_GEM_DOMAIN_CPU); vaddr = gem_mmap__cpu(fd, exec[0].handle, 0, size, PROT_READ); /* Shadow batch is meant to be read-only */ for (i = 0; i < len / sizeof(uint32_t); i++) { if (i != 6) igt_assert_neq_u32(vaddr[i], 0xdeadbeaf); } /* * Since batch_len is smaller than PAGE_SIZE, we should expect the extra * dwords to be zeroed. Even though this doesn't affect execution, we * don't want to be leaking stuff by accident. */ for (i = len / sizeof(uint32_t); i < size / sizeof(uint32_t); i++) igt_assert_eq_u32(vaddr[i], 0); munmap(vaddr, size); for (i = 0; i < ARRAY_SIZE(exec); i++) gem_close(fd, exec[i].handle); } igt_main { uint32_t handle; int i915; igt_fixture { i915 = drm_open_driver(DRIVER_INTEL); igt_require_gem(i915); gem_require_blitter(i915); igt_require(gem_cmdparser_version(i915) >= 10); igt_require(intel_gen(intel_get_drm_devid(i915)) == 9); handle = gem_create(i915, HANDLE_SIZE); igt_fork_hang_detector(i915); } igt_subtest("secure-batches") test_secure_batches(i915); igt_subtest("allowed-all") test_allowed_all(i915, handle); igt_subtest("allowed-single") test_allowed_single(i915, handle); igt_subtest("bb-start-param") test_bb_start(i915, handle, BB_START_PARAM); igt_subtest("bb-start-out") test_bb_start(i915, handle, BB_START_OUT); igt_subtest("bb-secure") test_bb_secure(i915, handle); igt_subtest("bb-chained") test_bb_chained(i915, handle); igt_subtest("cmd-crossing-page") test_cmd_crossing_page(i915, handle); igt_subtest("batch-without-end") { const uint32_t noop[1024] = { 0 }; exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, noop, sizeof(noop), -EINVAL); } igt_subtest("batch-zero-length") { const uint32_t noop[] = { 0, MI_BATCH_BUFFER_END }; exec_batch(i915, intel_ctx_0(i915), I915_EXEC_BLT, handle, noop, 0, -EINVAL); } igt_subtest("batch-invalid-length") { gem_require_pread_pwrite(i915); test_invalid_length(i915, handle); } igt_subtest("basic-rejected") test_rejected(i915, handle, false); igt_subtest("basic-rejected-ctx-param") test_rejected(i915, handle, true); igt_subtest("valid-registers") test_valid_registers(i915, handle); igt_subtest("unaligned-access") test_unaligned_access(i915, handle); igt_subtest("unaligned-jump") test_unaligned_jump(i915, handle); igt_subtest("bb-start-cmd") test_bb_start(i915, handle, BB_START_CMD); igt_subtest("bb-start-far") test_bb_start(i915, handle, BB_START_FAR); igt_subtest("bb-large") test_bb_large(i915); igt_subtest("bb-oversize") test_bb_oversize(i915); igt_subtest("shadow-peek") test_shadow_peek(i915); igt_fixture { igt_stop_hang_detector(); gem_close(i915, handle); close(i915); } }