/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "igt.h" #include "igt_core.h" #include "igt_perf.h" #include "igt_sysfs.h" #include "sw_sync.h" IGT_TEST_DESCRIPTION("Test the i915 pmu perf interface"); const double tolerance = 0.05f; const unsigned long batch_duration_ns = 500e6; static int open_pmu(uint64_t config) { int fd; fd = perf_i915_open(config); igt_skip_on(fd < 0 && errno == ENODEV); igt_assert(fd >= 0); return fd; } static int open_group(uint64_t config, int group) { int fd; fd = perf_i915_open_group(config, group); igt_skip_on(fd < 0 && errno == ENODEV); igt_assert(fd >= 0); return fd; } static void init(int gem_fd, const struct intel_execution_engine2 *e, uint8_t sample) { int fd; fd = open_pmu(__I915_PMU_ENGINE(e->class, e->instance, sample)); close(fd); } static uint64_t pmu_read_single(int fd) { uint64_t data[2]; igt_assert_eq(read(fd, data, sizeof(data)), sizeof(data)); return data[0]; } static void pmu_read_multi(int fd, unsigned int num, uint64_t *val) { uint64_t buf[2 + num]; unsigned int i; igt_assert_eq(read(fd, buf, sizeof(buf)), sizeof(buf)); for (i = 0; i < num; i++) val[i] = buf[2 + i]; } #define assert_within_epsilon(x, ref, tolerance) \ igt_assert_f((double)(x) <= (1.0 + (tolerance)) * (double)(ref) && \ (double)(x) >= (1.0 - (tolerance)) * (double)(ref), \ "'%s' != '%s' (%f not within %f%% tolerance of %f)\n",\ #x, #ref, (double)(x), (tolerance) * 100.0, (double)(ref)) /* * Helper for cases where we assert on time spent sleeping (directly or * indirectly), so make it more robust by ensuring the system sleep time * is within test tolerance to start with. */ static unsigned int measured_usleep(unsigned int usec) { struct timespec ts = { }; unsigned int slept; slept = igt_nsec_elapsed(&ts); igt_assert(slept == 0); do { usleep(usec - slept); slept = igt_nsec_elapsed(&ts) / 1000; } while (slept < usec); return igt_nsec_elapsed(&ts); } static unsigned int e2ring(int gem_fd, const struct intel_execution_engine2 *e) { return gem_class_instance_to_eb_flags(gem_fd, e->class, e->instance); } static void single(int gem_fd, const struct intel_execution_engine2 *e, bool busy) { unsigned long slept; igt_spin_t *spin; uint64_t val; int fd; fd = open_pmu(I915_PMU_ENGINE_BUSY(e->class, e->instance)); if (busy) spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); else spin = NULL; slept = measured_usleep(batch_duration_ns / 1000); igt_spin_batch_end(spin); val = pmu_read_single(fd); igt_spin_batch_free(gem_fd, spin); close(fd); assert_within_epsilon(val, busy ? slept : 0.f, tolerance); gem_quiescent_gpu(gem_fd); } static void busy_start(int gem_fd, const struct intel_execution_engine2 *e) { unsigned long slept; igt_spin_t *spin; uint64_t val; int fd; /* * Defeat the busy stats delayed disable, we need to guarantee we are * the first user. */ sleep(2); spin = __igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); /* * Sleep for a bit after making the engine busy to make sure the PMU * gets enabled when the batch is already running. */ usleep(500e3); fd = open_pmu(I915_PMU_ENGINE_BUSY(e->class, e->instance)); slept = measured_usleep(batch_duration_ns / 1000); val = pmu_read_single(fd); igt_spin_batch_free(gem_fd, spin); close(fd); assert_within_epsilon(val, slept, tolerance); gem_quiescent_gpu(gem_fd); } /* * This test has a potentially low rate of catching the issue it is trying to * catch. Or in other words, quite high rate of false negative successes. We * will depend on the CI systems running it a lot to detect issues. */ static void busy_double_start(int gem_fd, const struct intel_execution_engine2 *e) { unsigned long slept; igt_spin_t *spin[2]; uint64_t val, val2; uint32_t ctx; int fd; ctx = gem_context_create(gem_fd); /* * Defeat the busy stats delayed disable, we need to guarantee we are * the first user. */ sleep(2); /* * Submit two contexts, with a pause in between targeting the ELSP * re-submission in execlists mode. Make sure busyness is correctly * reported with the engine busy, and after the engine went idle. */ spin[0] = __igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); usleep(500e3); spin[1] = __igt_spin_batch_new(gem_fd, ctx, e2ring(gem_fd, e), 0); /* * Open PMU as fast as possible after the second spin batch in attempt * to be faster than the driver handling lite-restore. */ fd = open_pmu(I915_PMU_ENGINE_BUSY(e->class, e->instance)); slept = measured_usleep(batch_duration_ns / 1000); val = pmu_read_single(fd); igt_spin_batch_end(spin[0]); igt_spin_batch_end(spin[1]); /* Wait for GPU idle to verify PMU reports idle. */ gem_quiescent_gpu(gem_fd); val2 = pmu_read_single(fd); usleep(batch_duration_ns / 1000); val2 = pmu_read_single(fd) - val2; igt_info("busy=%lu idle=%lu\n", val, val2); igt_spin_batch_free(gem_fd, spin[0]); igt_spin_batch_free(gem_fd, spin[1]); close(fd); gem_context_destroy(gem_fd, ctx); assert_within_epsilon(val, slept, tolerance); igt_assert_eq(val2, 0); gem_quiescent_gpu(gem_fd); } static void log_busy(int fd, unsigned int num_engines, uint64_t *val) { char buf[1024]; int rem = sizeof(buf); unsigned int i; char *p = buf; for (i = 0; i < num_engines; i++) { int len; len = snprintf(p, rem, "%u=%" PRIu64 "\n", i, val[i]); igt_assert(len > 0); rem -= len; p += len; } igt_info("%s", buf); } static void busy_check_all(int gem_fd, const struct intel_execution_engine2 *e, const unsigned int num_engines) { const struct intel_execution_engine2 *e_; uint64_t val[num_engines]; int fd[num_engines]; unsigned long slept; igt_spin_t *spin; unsigned int busy_idx, i; i = 0; fd[0] = -1; for_each_engine_class_instance(fd, e_) { if (!gem_has_engine(gem_fd, e_->class, e_->instance)) continue; else if (e == e_) busy_idx = i; fd[i++] = open_group(I915_PMU_ENGINE_BUSY(e_->class, e_->instance), fd[0]); } igt_assert_eq(i, num_engines); spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); slept = measured_usleep(batch_duration_ns / 1000); igt_spin_batch_end(spin); pmu_read_multi(fd[0], num_engines, val); log_busy(fd[0], num_engines, val); igt_spin_batch_free(gem_fd, spin); close(fd[0]); assert_within_epsilon(val[busy_idx], slept, tolerance); for (i = 0; i < num_engines; i++) { if (i == busy_idx) continue; assert_within_epsilon(val[i], 0.0f, tolerance); } gem_quiescent_gpu(gem_fd); } static void most_busy_check_all(int gem_fd, const struct intel_execution_engine2 *e, const unsigned int num_engines) { const struct intel_execution_engine2 *e_; uint64_t val[num_engines]; int fd[num_engines]; unsigned long slept; igt_spin_t *spin = NULL; unsigned int idle_idx, i; gem_require_engine(gem_fd, e->class, e->instance); i = 0; for_each_engine_class_instance(fd, e_) { if (!gem_has_engine(gem_fd, e_->class, e_->instance)) continue; if (e == e_) { idle_idx = i; } else if (spin) { struct drm_i915_gem_exec_object2 obj = { .handle = spin->handle }; struct drm_i915_gem_execbuffer2 eb = { .buffer_count = 1, .buffers_ptr = to_user_pointer(&obj), .flags = e2ring(gem_fd, e_), }; gem_execbuf(gem_fd, &eb); } else { spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e_), 0); } val[i++] = I915_PMU_ENGINE_BUSY(e_->class, e_->instance); } igt_assert(i == num_engines); fd[0] = -1; for (i = 0; i < num_engines; i++) fd[i] = open_group(val[i], fd[0]); slept = measured_usleep(batch_duration_ns / 1000); igt_spin_batch_end(spin); pmu_read_multi(fd[0], num_engines, val); log_busy(fd[0], num_engines, val); igt_spin_batch_free(gem_fd, spin); close(fd[0]); for (i = 0; i < num_engines; i++) { if (i == idle_idx) assert_within_epsilon(val[i], 0.0f, tolerance); else assert_within_epsilon(val[i], slept, tolerance); } gem_quiescent_gpu(gem_fd); } static void all_busy_check_all(int gem_fd, const unsigned int num_engines) { const struct intel_execution_engine2 *e; uint64_t val[num_engines]; int fd[num_engines]; unsigned long slept; igt_spin_t *spin = NULL; unsigned int i; i = 0; for_each_engine_class_instance(fd, e) { if (!gem_has_engine(gem_fd, e->class, e->instance)) continue; if (spin) { struct drm_i915_gem_exec_object2 obj = { .handle = spin->handle }; struct drm_i915_gem_execbuffer2 eb = { .buffer_count = 1, .buffers_ptr = to_user_pointer(&obj), .flags = e2ring(gem_fd, e), }; gem_execbuf(gem_fd, &eb); } else { spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); } val[i++] = I915_PMU_ENGINE_BUSY(e->class, e->instance); } igt_assert(i == num_engines); fd[0] = -1; for (i = 0; i < num_engines; i++) fd[i] = open_group(val[i], fd[0]); slept = measured_usleep(batch_duration_ns / 1000); igt_spin_batch_end(spin); pmu_read_multi(fd[0], num_engines, val); log_busy(fd[0], num_engines, val); igt_spin_batch_free(gem_fd, spin); close(fd[0]); for (i = 0; i < num_engines; i++) assert_within_epsilon(val[i], slept, tolerance); gem_quiescent_gpu(gem_fd); } static void no_sema(int gem_fd, const struct intel_execution_engine2 *e, bool busy) { igt_spin_t *spin; uint64_t val[2]; int fd; fd = open_group(I915_PMU_ENGINE_SEMA(e->class, e->instance), -1); open_group(I915_PMU_ENGINE_WAIT(e->class, e->instance), fd); if (busy) { spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); igt_spin_batch_set_timeout(spin, batch_duration_ns); } else { usleep(batch_duration_ns / 1000); } if (busy) gem_sync(gem_fd, spin->handle); pmu_read_multi(fd, 2, val); if (busy) igt_spin_batch_free(gem_fd, spin); close(fd); assert_within_epsilon(val[0], 0.0f, tolerance); assert_within_epsilon(val[1], 0.0f, tolerance); } #define MI_INSTR(opcode, flags) (((opcode) << 23) | (flags)) #define MI_SEMAPHORE_WAIT MI_INSTR(0x1c, 2) /* GEN8+ */ #define MI_SEMAPHORE_POLL (1<<15) #define MI_SEMAPHORE_SAD_GTE_SDD (1<<12) static void sema_wait(int gem_fd, const struct intel_execution_engine2 *e) { struct drm_i915_gem_relocation_entry reloc[2] = {}; struct drm_i915_gem_exec_object2 obj[2] = {}; struct drm_i915_gem_execbuffer2 eb = {}; uint32_t bb_handle, obj_handle; unsigned long slept; uint32_t *obj_ptr; uint32_t batch[16]; uint64_t val[2]; int fd; igt_require(intel_gen(intel_get_drm_devid(gem_fd)) >= 8); /** * Setup up a batchbuffer with a polling semaphore wait command which * will wait on an value in a shared bo to change. This way we are able * to control how much time we will spend in this bb. */ bb_handle = gem_create(gem_fd, 4096); obj_handle = gem_create(gem_fd, 4096); obj_ptr = gem_mmap__wc(gem_fd, obj_handle, 0, 4096, PROT_WRITE); batch[0] = MI_STORE_DWORD_IMM; batch[1] = sizeof(*obj_ptr); batch[2] = 0; batch[3] = 1; batch[4] = MI_SEMAPHORE_WAIT | MI_SEMAPHORE_POLL | MI_SEMAPHORE_SAD_GTE_SDD; batch[5] = 1; batch[6] = 0x0; batch[7] = 0x0; batch[8] = MI_BATCH_BUFFER_END; gem_write(gem_fd, bb_handle, 0, batch, sizeof(batch)); reloc[0].target_handle = obj_handle; reloc[0].offset = 1 * sizeof(uint32_t); reloc[0].read_domains = I915_GEM_DOMAIN_RENDER; reloc[0].write_domain = I915_GEM_DOMAIN_RENDER; reloc[0].delta = sizeof(*obj_ptr); reloc[1].target_handle = obj_handle; reloc[1].offset = 6 * sizeof(uint32_t); reloc[1].read_domains = I915_GEM_DOMAIN_RENDER; obj[0].handle = obj_handle; obj[1].handle = bb_handle; obj[1].relocation_count = 2; obj[1].relocs_ptr = to_user_pointer(reloc); eb.buffer_count = 2; eb.buffers_ptr = to_user_pointer(obj); eb.flags = e2ring(gem_fd, e); /** * Start the semaphore wait PMU and after some known time let the above * semaphore wait command finish. Then check that the PMU is reporting * to expected time spent in semaphore wait state. */ fd = open_pmu(I915_PMU_ENGINE_SEMA(e->class, e->instance)); gem_execbuf(gem_fd, &eb); do { /* wait for the batch to start executing */ usleep(5e3); } while (!obj_ptr[1]); usleep(5e3); /* wait for the register sampling */ val[0] = pmu_read_single(fd); slept = measured_usleep(batch_duration_ns / 1000); val[1] = pmu_read_single(fd); igt_debug("slept %.3fms, sampled %.3fms\n", slept*1e-6, (val[1] - val[0])*1e-6); obj_ptr[0] = 1; gem_sync(gem_fd, bb_handle); munmap(obj_ptr, 4096); gem_close(gem_fd, obj_handle); gem_close(gem_fd, bb_handle); close(fd); assert_within_epsilon(val[1] - val[0], slept, tolerance); } #define MI_WAIT_FOR_PIPE_C_VBLANK (1<<21) #define MI_WAIT_FOR_PIPE_B_VBLANK (1<<11) #define MI_WAIT_FOR_PIPE_A_VBLANK (1<<3) typedef struct { igt_display_t display; struct igt_fb primary_fb; igt_output_t *output; enum pipe pipe; } data_t; static void prepare_crtc(data_t *data, int fd, igt_output_t *output) { drmModeModeInfo *mode; igt_display_t *display = &data->display; igt_plane_t *primary; /* select the pipe we want to use */ igt_output_set_pipe(output, data->pipe); /* create and set the primary plane fb */ mode = igt_output_get_mode(output); igt_create_color_fb(fd, mode->hdisplay, mode->vdisplay, DRM_FORMAT_XRGB8888, LOCAL_DRM_FORMAT_MOD_NONE, 0.0, 0.0, 0.0, &data->primary_fb); primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_plane_set_fb(primary, &data->primary_fb); igt_display_commit(display); igt_wait_for_vblank(fd, data->pipe); } static void cleanup_crtc(data_t *data, int fd, igt_output_t *output) { igt_display_t *display = &data->display; igt_plane_t *primary; igt_remove_fb(fd, &data->primary_fb); primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_plane_set_fb(primary, NULL); igt_output_set_pipe(output, PIPE_ANY); igt_display_commit(display); } static int wait_vblank(int fd, union drm_wait_vblank *vbl) { int err; err = 0; if (igt_ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl)) err = -errno; return err; } static void event_wait(int gem_fd, const struct intel_execution_engine2 *e) { struct drm_i915_gem_exec_object2 obj = { }; struct drm_i915_gem_execbuffer2 eb = { }; const uint32_t DERRMR = 0x44050; const uint32_t FORCEWAKE_MT = 0xa188; unsigned int valid_tests = 0; uint32_t batch[16], *b; uint16_t devid; igt_output_t *output; data_t data; enum pipe p; int fd; devid = intel_get_drm_devid(gem_fd); igt_require(intel_gen(devid) >= 7); igt_skip_on(IS_VALLEYVIEW(devid) || IS_CHERRYVIEW(devid)); kmstest_set_vt_graphics_mode(); igt_display_init(&data.display, gem_fd); /** * We will use the display to render event forwarind so need to * program the DERRMR register and restore it at exit. * Note we assume that the default/desired value for DERRMR will always * be ~0u (all routing disable). To be fancy, we could do a SRM of the * reg beforehand and then LRM at the end. * * We will emit a MI_WAIT_FOR_EVENT listening for vblank events, * have a background helper to indirectly enable vblank irqs, and * listen to the recorded time spent in engine wait state as reported * by the PMU. */ obj.handle = gem_create(gem_fd, 4096); b = batch; *b++ = MI_LOAD_REGISTER_IMM; *b++ = FORCEWAKE_MT; *b++ = 2 << 16 | 2; *b++ = MI_LOAD_REGISTER_IMM; *b++ = DERRMR; *b++ = ~0u; *b++ = MI_WAIT_FOR_EVENT; *b++ = MI_LOAD_REGISTER_IMM; *b++ = DERRMR; *b++ = ~0u; *b++ = MI_LOAD_REGISTER_IMM; *b++ = FORCEWAKE_MT; *b++ = 2 << 16; *b++ = MI_BATCH_BUFFER_END; eb.buffer_count = 1; eb.buffers_ptr = to_user_pointer(&obj); eb.flags = e2ring(gem_fd, e) | I915_EXEC_SECURE; for_each_pipe_with_valid_output(&data.display, p, output) { struct igt_helper_process waiter = { }; const unsigned int frames = 3; uint64_t val[2]; batch[6] = MI_WAIT_FOR_EVENT; switch (p) { case PIPE_A: batch[6] |= MI_WAIT_FOR_PIPE_A_VBLANK; batch[5] = ~(1 << 3); break; case PIPE_B: batch[6] |= MI_WAIT_FOR_PIPE_B_VBLANK; batch[5] = ~(1 << 11); break; case PIPE_C: batch[6] |= MI_WAIT_FOR_PIPE_C_VBLANK; batch[5] = ~(1 << 21); break; default: continue; } gem_write(gem_fd, obj.handle, 0, batch, sizeof(batch)); data.pipe = p; prepare_crtc(&data, gem_fd, output); fd = open_pmu(I915_PMU_ENGINE_WAIT(e->class, e->instance)); val[0] = pmu_read_single(fd); igt_fork_helper(&waiter) { const uint32_t pipe_id_flag = kmstest_get_vbl_flag(data.pipe); for (;;) { union drm_wait_vblank vbl = { }; vbl.request.type = DRM_VBLANK_RELATIVE; vbl.request.type |= pipe_id_flag; vbl.request.sequence = 1; igt_assert_eq(wait_vblank(gem_fd, &vbl), 0); } } for (unsigned int frame = 0; frame < frames; frame++) { gem_execbuf(gem_fd, &eb); gem_sync(gem_fd, obj.handle); } igt_stop_helper(&waiter); val[1] = pmu_read_single(fd); close(fd); cleanup_crtc(&data, gem_fd, output); valid_tests++; igt_assert(val[1] - val[0] > 0); } gem_close(gem_fd, obj.handle); igt_require_f(valid_tests, "no valid crtc/connector combinations found\n"); } static void multi_client(int gem_fd, const struct intel_execution_engine2 *e) { uint64_t config = I915_PMU_ENGINE_BUSY(e->class, e->instance); unsigned int slept; igt_spin_t *spin; uint64_t val[2]; int fd[2]; fd[0] = open_pmu(config); /* * Second PMU client which is initialized after the first one, * and exists before it, should not affect accounting as reported * in the first client. */ fd[1] = open_pmu(config); spin = igt_spin_batch_new(gem_fd, 0, e2ring(gem_fd, e), 0); igt_spin_batch_set_timeout(spin, 2 * batch_duration_ns); slept = measured_usleep(batch_duration_ns / 1000); val[1] = pmu_read_single(fd[1]); close(fd[1]); gem_sync(gem_fd, spin->handle); val[0] = pmu_read_single(fd[0]); igt_spin_batch_free(gem_fd, spin); close(fd[0]); assert_within_epsilon(val[0], 2 * batch_duration_ns, tolerance); assert_within_epsilon(val[1], slept, tolerance); } /** * Tests that i915 PMU corectly errors out in invalid initialization. * i915 PMU is uncore PMU, thus: * - sampling period is not supported * - pid > 0 is not supported since we can't count per-process (we count * per whole system) * - cpu != 0 is not supported since i915 PMU only allows running on one cpu * and that is normally CPU0. */ static void invalid_init(void) { struct perf_event_attr attr; #define ATTR_INIT() \ do { \ memset(&attr, 0, sizeof (attr)); \ attr.config = I915_PMU_ENGINE_BUSY(I915_ENGINE_CLASS_RENDER, 0); \ attr.type = i915_type_id(); \ igt_assert(attr.type != 0); \ errno = 0; \ } while(0) ATTR_INIT(); attr.sample_period = 100; igt_assert_eq(perf_event_open(&attr, -1, 0, -1, 0), -1); igt_assert_eq(errno, EINVAL); ATTR_INIT(); igt_assert_eq(perf_event_open(&attr, 0, 0, -1, 0), -1); igt_assert_eq(errno, EINVAL); ATTR_INIT(); igt_assert_eq(perf_event_open(&attr, -1, 1, -1, 0), -1); igt_assert_eq(errno, EINVAL); } static void init_other(unsigned int i, bool valid) { int fd; fd = perf_i915_open(__I915_PMU_OTHER(i)); igt_require(!(fd < 0 && errno == ENODEV)); if (valid) { igt_assert(fd >= 0); } else { igt_assert(fd < 0); return; } close(fd); } static void read_other(unsigned int i, bool valid) { int fd; fd = perf_i915_open(__I915_PMU_OTHER(i)); igt_require(!(fd < 0 && errno == ENODEV)); if (valid) { igt_assert(fd >= 0); } else { igt_assert(fd < 0); return; } (void)pmu_read_single(fd); close(fd); } static bool cpu0_hotplug_support(void) { return access("/sys/devices/system/cpu/cpu0/online", W_OK) == 0; } static void cpu_hotplug(int gem_fd) { struct timespec start = { }; igt_spin_t *spin; uint64_t val, ref; int fd; igt_require(cpu0_hotplug_support()); fd = perf_i915_open(I915_PMU_ENGINE_BUSY(I915_ENGINE_CLASS_RENDER, 0)); igt_assert(fd >= 0); spin = igt_spin_batch_new(gem_fd, 0, I915_EXEC_RENDER, 0); igt_nsec_elapsed(&start); /* * Toggle online status of all the CPUs in a child process and ensure * this has not affected busyness stats in the parent. */ igt_fork(child, 1) { int cpu = 0; for (;;) { char name[128]; int cpufd; sprintf(name, "/sys/devices/system/cpu/cpu%d/online", cpu); cpufd = open(name, O_WRONLY); if (cpufd == -1) { igt_assert(cpu > 0); break; } igt_assert_eq(write(cpufd, "0", 2), 2); usleep(1e6); igt_assert_eq(write(cpufd, "1", 2), 2); close(cpufd); cpu++; } } igt_waitchildren(); igt_spin_batch_end(spin); gem_sync(gem_fd, spin->handle); ref = igt_nsec_elapsed(&start); val = pmu_read_single(fd); igt_spin_batch_free(gem_fd, spin); close(fd); assert_within_epsilon(val, ref, tolerance); } static void test_interrupts(int gem_fd) { const unsigned int test_duration_ms = 1000; const int target = 30; igt_spin_t *spin[target]; struct pollfd pfd; uint64_t idle, busy; int fence_fd; int fd; gem_quiescent_gpu(gem_fd); fd = open_pmu(I915_PMU_INTERRUPTS); /* Queue spinning batches. */ for (int i = 0; i < target; i++) { spin[i] = __igt_spin_batch_new_fence(gem_fd, 0, I915_EXEC_RENDER); if (i == 0) { fence_fd = spin[i]->out_fence; } else { int old_fd = fence_fd; fence_fd = sync_fence_merge(old_fd, spin[i]->out_fence); close(old_fd); } igt_assert(fence_fd >= 0); } /* Wait for idle state. */ idle = pmu_read_single(fd); do { busy = idle; usleep(1e3); idle = pmu_read_single(fd); } while (idle != busy); /* Arm batch expiration. */ for (int i = 0; i < target; i++) igt_spin_batch_set_timeout(spin[i], (i + 1) * test_duration_ms * 1e6 / target); /* Wait for last batch to finish. */ pfd.events = POLLIN; pfd.fd = fence_fd; igt_assert_eq(poll(&pfd, 1, 2 * test_duration_ms), 1); close(fence_fd); /* Free batches. */ for (int i = 0; i < target; i++) igt_spin_batch_free(gem_fd, spin[i]); /* Check at least as many interrupts has been generated. */ busy = pmu_read_single(fd) - idle; close(fd); igt_assert_lte(target, busy); } static void test_interrupts_sync(int gem_fd) { const unsigned int test_duration_ms = 1000; const int target = 30; igt_spin_t *spin[target]; struct pollfd pfd; uint64_t idle, busy; int fd; gem_quiescent_gpu(gem_fd); fd = open_pmu(I915_PMU_INTERRUPTS); /* Queue spinning batches. */ for (int i = 0; i < target; i++) spin[i] = __igt_spin_batch_new_fence(gem_fd, 0, 0); /* Wait for idle state. */ idle = pmu_read_single(fd); do { busy = idle; usleep(1e3); idle = pmu_read_single(fd); } while (idle != busy); /* Process the batch queue. */ pfd.events = POLLIN; for (int i = 0; i < target; i++) { const unsigned int timeout_ms = test_duration_ms / target; pfd.fd = spin[i]->out_fence; igt_spin_batch_set_timeout(spin[i], timeout_ms * 1e6); igt_assert_eq(poll(&pfd, 1, 2 * timeout_ms), 1); igt_spin_batch_free(gem_fd, spin[i]); } /* Check at least as many interrupts has been generated. */ busy = pmu_read_single(fd) - idle; close(fd); igt_assert_lte(target, busy); } static void test_frequency(int gem_fd) { uint32_t min_freq, max_freq, boost_freq; uint64_t val[2], start[2]; double min[2], max[2]; unsigned long slept; igt_spin_t *spin; int fd, sysfs; sysfs = igt_sysfs_open(gem_fd, NULL); igt_require(sysfs >= 0); min_freq = igt_sysfs_get_u32(sysfs, "gt_RPn_freq_mhz"); max_freq = igt_sysfs_get_u32(sysfs, "gt_RP0_freq_mhz"); boost_freq = igt_sysfs_get_u32(sysfs, "gt_boost_freq_mhz"); igt_info("Frequency: min=%u, max=%u, boost=%u MHz\n", min_freq, max_freq, boost_freq); igt_require(min_freq > 0 && max_freq > 0 && boost_freq > 0); igt_require(max_freq > min_freq); igt_require(boost_freq > min_freq); fd = open_group(I915_PMU_REQUESTED_FREQUENCY, -1); open_group(I915_PMU_ACTUAL_FREQUENCY, fd); /* * Set GPU to min frequency and read PMU counters. */ igt_require(igt_sysfs_set_u32(sysfs, "gt_min_freq_mhz", min_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_min_freq_mhz") == min_freq); igt_require(igt_sysfs_set_u32(sysfs, "gt_max_freq_mhz", min_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_max_freq_mhz") == min_freq); igt_require(igt_sysfs_set_u32(sysfs, "gt_boost_freq_mhz", min_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_boost_freq_mhz") == min_freq); gem_quiescent_gpu(gem_fd); /* Idle to be sure the change takes effect */ spin = igt_spin_batch_new(gem_fd, 0, I915_EXEC_RENDER, 0); pmu_read_multi(fd, 2, start); slept = measured_usleep(batch_duration_ns / 1000); pmu_read_multi(fd, 2, val); min[0] = 1e9*(val[0] - start[0]) / slept; min[1] = 1e9*(val[1] - start[1]) / slept; igt_spin_batch_free(gem_fd, spin); gem_quiescent_gpu(gem_fd); /* Don't leak busy bo into the next phase */ usleep(1e6); /* * Set GPU to max frequency and read PMU counters. */ igt_require(igt_sysfs_set_u32(sysfs, "gt_max_freq_mhz", max_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_max_freq_mhz") == max_freq); igt_require(igt_sysfs_set_u32(sysfs, "gt_boost_freq_mhz", boost_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_boost_freq_mhz") == boost_freq); igt_require(igt_sysfs_set_u32(sysfs, "gt_min_freq_mhz", max_freq)); igt_require(igt_sysfs_get_u32(sysfs, "gt_min_freq_mhz") == max_freq); gem_quiescent_gpu(gem_fd); spin = igt_spin_batch_new(gem_fd, 0, I915_EXEC_RENDER, 0); pmu_read_multi(fd, 2, start); slept = measured_usleep(batch_duration_ns / 1000); pmu_read_multi(fd, 2, val); max[0] = 1e9*(val[0] - start[0]) / slept; max[1] = 1e9*(val[1] - start[1]) / slept; igt_spin_batch_free(gem_fd, spin); gem_quiescent_gpu(gem_fd); /* * Restore min/max. */ igt_sysfs_set_u32(sysfs, "gt_min_freq_mhz", min_freq); if (igt_sysfs_get_u32(sysfs, "gt_min_freq_mhz") != min_freq) igt_warn("Unable to restore min frequency to saved value [%u MHz], now %u MHz\n", min_freq, igt_sysfs_get_u32(sysfs, "gt_min_freq_mhz")); close(fd); igt_info("Min frequency: requested %.1f, actual %.1f\n", min[0], min[1]); igt_info("Max frequency: requested %.1f, actual %.1f\n", max[0], max[1]); assert_within_epsilon(min[0], min_freq, tolerance); assert_within_epsilon(max[0], max_freq, tolerance); } static bool wait_for_rc6(int fd) { struct timespec tv = {}; uint64_t start, now; /* First wait for roughly an RC6 Evaluation Interval */ usleep(160 * 1000); /* Then poll for RC6 to start ticking */ now = pmu_read_single(fd); do { start = now; usleep(5000); now = pmu_read_single(fd); if (now - start > 1e6) return true; } while (!igt_seconds_elapsed(&tv)); return false; } static void test_rc6(int gem_fd) { int64_t duration_ns = 2e9; uint64_t idle, busy, prev; unsigned int slept; int fd, fw; fd = open_pmu(I915_PMU_RC6_RESIDENCY); gem_quiescent_gpu(gem_fd); igt_require(wait_for_rc6(fd)); /* Go idle and check full RC6. */ prev = pmu_read_single(fd); slept = measured_usleep(duration_ns / 1000); idle = pmu_read_single(fd); assert_within_epsilon(idle - prev, slept, tolerance); /* Wake up device and check no RC6. */ fw = igt_open_forcewake_handle(gem_fd); igt_assert(fw >= 0); usleep(1e3); /* wait for the rc6 cycle counter to stop ticking */ prev = pmu_read_single(fd); usleep(duration_ns / 1000); busy = pmu_read_single(fd); close(fw); close(fd); assert_within_epsilon(busy - prev, 0.0, tolerance); } igt_main { const unsigned int num_other_metrics = I915_PMU_LAST - __I915_PMU_OTHER(0) + 1; unsigned int num_engines = 0; int fd = -1; const struct intel_execution_engine2 *e; unsigned int i; igt_fixture { fd = drm_open_driver_master(DRIVER_INTEL); igt_require_gem(fd); igt_require(i915_type_id() > 0); for_each_engine_class_instance(fd, e) { if (gem_has_engine(fd, e->class, e->instance)) num_engines++; } } /** * Test invalid access via perf API is rejected. */ igt_subtest("invalid-init") invalid_init(); for_each_engine_class_instance(fd, e) { /** * Test that a single engine metric can be initialized. */ igt_subtest_f("init-busy-%s", e->name) init(fd, e, I915_SAMPLE_BUSY); igt_subtest_f("init-wait-%s", e->name) init(fd, e, I915_SAMPLE_WAIT); igt_subtest_f("init-sema-%s", e->name) init(fd, e, I915_SAMPLE_SEMA); /** * Test that engines show no load when idle. */ igt_subtest_f("idle-%s", e->name) single(fd, e, false); /** * Test that a single engine reports load correctly. */ igt_subtest_f("busy-%s", e->name) single(fd, e, true); /** * Test that when one engine is loaded other report no load. */ igt_subtest_f("busy-check-all-%s", e->name) busy_check_all(fd, e, num_engines); /** * Test that when all except one engine are loaded all loads * are correctly reported. */ igt_subtest_f("most-busy-check-all-%s", e->name) most_busy_check_all(fd, e, num_engines); /** * Test that semphore counters report no activity on idle * or busy engines. */ igt_subtest_f("idle-no-semaphores-%s", e->name) no_sema(fd, e, false); igt_subtest_f("busy-no-semaphores-%s", e->name) no_sema(fd, e, true); /** * Test that semaphore waits are correctly reported. */ igt_subtest_f("semaphore-wait-%s", e->name) sema_wait(fd, e); /** * Test that event waits are correctly reported. */ if (e->class == I915_ENGINE_CLASS_RENDER) igt_subtest_f("event-wait-%s", e->name) event_wait(fd, e); /** * Check that two perf clients do not influence each others * observations. */ igt_subtest_f("multi-client-%s", e->name) multi_client(fd, e); /** * Check that reported usage is correct when PMU is enabled * after the batch is running. */ igt_subtest_f("busy-start-%s", e->name) busy_start(fd, e); /** * Check that reported usage is correct when PMU is enabled * after two batches are running. */ igt_subtest_f("busy-double-start-%s", e->name) busy_double_start(fd, e); } /** * Test that when all engines are loaded all loads are * correctly reported. */ igt_subtest("all-busy-check-all") all_busy_check_all(fd, num_engines); /** * Test that non-engine counters can be initialized and read. Apart * from the invalid metric which should fail. */ for (i = 0; i < num_other_metrics + 1; i++) { igt_subtest_f("other-init-%u", i) init_other(i, i < num_other_metrics); igt_subtest_f("other-read-%u", i) read_other(i, i < num_other_metrics); } /** * Test counters are not affected by CPU offline/online events. */ igt_subtest("cpu-hotplug") cpu_hotplug(fd); /** * Test GPU frequency. */ igt_subtest("frequency") test_frequency(fd); /** * Test interrupt count reporting. */ igt_subtest("interrupts") test_interrupts(fd); igt_subtest("interrupts-sync") test_interrupts_sync(fd); /** * Test RC6 residency reporting. */ igt_subtest("rc6") test_rc6(fd); /** * Check render nodes are counted. */ igt_subtest_group { int render_fd; igt_fixture { render_fd = drm_open_driver_render(DRIVER_INTEL); igt_require_gem(render_fd); gem_quiescent_gpu(fd); } for_each_engine_class_instance(fd, e) { igt_subtest_f("render-node-busy-%s", e->name) single(fd, e, true); } igt_fixture { close(render_fd); } } }