| /* |
| * Copyright © 2012 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: |
| * Ben Widawsky <ben@bwidawsk.net> |
| * Jeff McGee <jeff.mcgee@intel.com> |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <time.h> |
| #include <sys/wait.h> |
| |
| #include "igt.h" |
| #include "igt_dummyload.h" |
| |
| IGT_TEST_DESCRIPTION("Render P-States tests - verify GPU frequency changes"); |
| |
| static int drm_fd; |
| |
| static const char sysfs_base_path[] = "/sys/class/drm/card%d/gt_%s_freq_mhz"; |
| enum { |
| CUR, |
| MIN, |
| MAX, |
| RP0, |
| RP1, |
| RPn, |
| BOOST, |
| NUMFREQ |
| }; |
| |
| static int origfreqs[NUMFREQ]; |
| |
| struct sysfs_file { |
| const char *name; |
| const char *mode; |
| FILE *filp; |
| } sysfs_files[] = { |
| { "cur", "r", NULL }, |
| { "min", "rb+", NULL }, |
| { "max", "rb+", NULL }, |
| { "RP0", "r", NULL }, |
| { "RP1", "r", NULL }, |
| { "RPn", "r", NULL }, |
| { "boost", "rb+", NULL }, |
| { NULL, NULL, NULL } |
| }; |
| |
| static int readval(FILE *filp) |
| { |
| int val; |
| int scanned; |
| |
| rewind(filp); |
| scanned = fscanf(filp, "%d", &val); |
| igt_assert_eq(scanned, 1); |
| |
| return val; |
| } |
| |
| static void read_freqs(int *freqs) |
| { |
| int i; |
| |
| for (i = 0; i < NUMFREQ; i++) |
| freqs[i] = readval(sysfs_files[i].filp); |
| } |
| |
| static void nsleep(unsigned long ns) |
| { |
| struct timespec ts; |
| int ret; |
| |
| ts.tv_sec = 0; |
| ts.tv_nsec = ns; |
| do { |
| struct timespec rem; |
| |
| ret = nanosleep(&ts, &rem); |
| igt_assert(ret == 0 || errno == EINTR); |
| ts = rem; |
| } while (ret && errno == EINTR); |
| } |
| |
| static void wait_freq_settle(void) |
| { |
| int timeout = 10; |
| |
| while (1) { |
| int freqs[NUMFREQ]; |
| |
| read_freqs(freqs); |
| if (freqs[CUR] >= freqs[MIN] && freqs[CUR] <= freqs[MAX]) |
| break; |
| nsleep(1000000); |
| if (!timeout--) |
| break; |
| } |
| } |
| |
| static int do_writeval(FILE *filp, int val, int lerrno, bool readback_check) |
| { |
| int ret, orig; |
| |
| orig = readval(filp); |
| rewind(filp); |
| ret = fprintf(filp, "%d", val); |
| |
| if (lerrno) { |
| /* Expecting specific error */ |
| igt_assert(ret == EOF && errno == lerrno); |
| if (readback_check) |
| igt_assert_eq(readval(filp), orig); |
| } else { |
| /* Expecting no error */ |
| igt_assert_lt(0, ret); |
| wait_freq_settle(); |
| if (readback_check) |
| igt_assert_eq(readval(filp), val); |
| } |
| |
| return ret; |
| } |
| #define writeval(filp, val) do_writeval(filp, val, 0, true) |
| #define writeval_inval(filp, val) do_writeval(filp, val, EINVAL, true) |
| #define writeval_nocheck(filp, val) do_writeval(filp, val, 0, false) |
| |
| static void check_freq_constraints(const int *freqs) |
| { |
| igt_assert_lte(freqs[MIN], freqs[MAX]); |
| igt_assert_lte(freqs[CUR], freqs[MAX]); |
| igt_assert_lte(freqs[RPn], freqs[CUR]); |
| igt_assert_lte(freqs[RPn], freqs[MIN]); |
| igt_assert_lte(freqs[MAX], freqs[RP0]); |
| igt_assert_lte(freqs[RP1], freqs[RP0]); |
| igt_assert_lte(freqs[RPn], freqs[RP1]); |
| igt_assert_neq(freqs[RP0], 0); |
| igt_assert_neq(freqs[RP1], 0); |
| } |
| |
| static void dump(const int *freqs) |
| { |
| int i; |
| |
| igt_debug("gt freq (MHz):"); |
| for (i = 0; i < NUMFREQ; i++) |
| igt_debug(" %s=%d", sysfs_files[i].name, freqs[i]); |
| |
| igt_debug("\n"); |
| } |
| |
| enum load { |
| LOW = 0, |
| HIGH |
| }; |
| |
| static struct load_helper { |
| enum load load; |
| bool exit; |
| struct igt_helper_process igt_proc; |
| } lh; |
| |
| static void load_helper_signal_handler(int sig) |
| { |
| if (sig == SIGUSR2) { |
| lh.load = !lh.load; |
| igt_debug("Switching background load to %s\n", lh.load ? "high" : "low"); |
| } else |
| lh.exit = true; |
| } |
| |
| #define LOAD_HELPER_PAUSE_USEC 500 |
| #define LOAD_HELPER_BO_SIZE (16*1024*1024) |
| static void load_helper_set_load(enum load load) |
| { |
| igt_assert(lh.igt_proc.running); |
| |
| if (lh.load == load) |
| return; |
| |
| lh.load = load; |
| kill(lh.igt_proc.pid, SIGUSR2); |
| |
| usleep(1000); /* wait for load-helper to switch */ |
| } |
| |
| static void load_helper_run(enum load load) |
| { |
| /* |
| * FIXME fork helpers won't get cleaned up when started from within a |
| * subtest, so handle the case where it sticks around a bit too long. |
| */ |
| if (lh.igt_proc.running) { |
| load_helper_set_load(load); |
| return; |
| } |
| |
| lh.exit = false; |
| lh.load = load; |
| |
| igt_fork_helper(&lh.igt_proc) { |
| igt_spin_t *spin[2] = {}; |
| uint32_t handle; |
| |
| signal(SIGUSR1, load_helper_signal_handler); |
| signal(SIGUSR2, load_helper_signal_handler); |
| |
| igt_debug("Applying %s load...\n", lh.load ? "high" : "low"); |
| |
| spin[0] = igt_spin_batch_new(drm_fd, 0, 0, 0); |
| if (lh.load == HIGH) |
| spin[1] = __igt_spin_batch_new(drm_fd, 0, 0, 0); |
| while (!lh.exit) { |
| handle = spin[0]->handle; |
| igt_spin_batch_end(spin[0]); |
| while (gem_bo_busy(drm_fd, handle)) |
| usleep(100); |
| |
| igt_spin_batch_free(drm_fd, spin[0]); |
| usleep(100); |
| |
| spin[0] = spin[1]; |
| spin[lh.load == HIGH] = |
| __igt_spin_batch_new(drm_fd, 0, 0, 0); |
| } |
| |
| handle = spin[0]->handle; |
| igt_spin_batch_end(spin[0]); |
| |
| if (spin[1]) { |
| handle = spin[1]->handle; |
| igt_spin_batch_end(spin[1]); |
| } |
| |
| /* Wait for completion without boosting */ |
| usleep(1000); |
| while (gem_bo_busy(drm_fd, handle)) |
| usleep(1000); |
| |
| /* |
| * Idle/boost logic is tied with request retirement. |
| * Speed up detection of idle state and ensure deboost |
| * after removing load. |
| */ |
| igt_drop_caches_set(drm_fd, DROP_RETIRE); |
| |
| igt_spin_batch_free(drm_fd, spin[1]); |
| igt_spin_batch_free(drm_fd, spin[0]); |
| } |
| } |
| |
| static void load_helper_stop(void) |
| { |
| kill(lh.igt_proc.pid, SIGUSR1); |
| igt_assert(igt_wait_helper(&lh.igt_proc) == 0); |
| } |
| |
| static void do_load_gpu(void) |
| { |
| load_helper_run(LOW); |
| nsleep(10000000); |
| load_helper_stop(); |
| } |
| |
| /* Return a frequency rounded by HW to the nearest supported value */ |
| static int get_hw_rounded_freq(int target) |
| { |
| int freqs[NUMFREQ]; |
| int old_freq; |
| int idx; |
| int ret; |
| |
| read_freqs(freqs); |
| |
| if (freqs[MIN] > target) |
| idx = MIN; |
| else |
| idx = MAX; |
| |
| old_freq = freqs[idx]; |
| writeval_nocheck(sysfs_files[idx].filp, target); |
| read_freqs(freqs); |
| ret = freqs[idx]; |
| writeval_nocheck(sysfs_files[idx].filp, old_freq); |
| |
| return ret; |
| } |
| |
| /* |
| * Modify softlimit MIN and MAX freqs to valid and invalid levels. Depending |
| * on subtest run different check after each modification. |
| */ |
| static void min_max_config(void (*check)(void), bool load_gpu) |
| { |
| int fmid = (origfreqs[RPn] + origfreqs[RP0]) / 2; |
| |
| /* |
| * hw (and so kernel) rounds to the nearest value supported by |
| * the given platform. |
| */ |
| fmid = get_hw_rounded_freq(fmid); |
| |
| igt_debug("\nCheck original min and max...\n"); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nSet min=RPn and max=RP0...\n"); |
| writeval(sysfs_files[MIN].filp, origfreqs[RPn]); |
| writeval(sysfs_files[MAX].filp, origfreqs[RP0]); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nIncrease min to midpoint...\n"); |
| writeval(sysfs_files[MIN].filp, fmid); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nIncrease min to RP0...\n"); |
| writeval(sysfs_files[MIN].filp, origfreqs[RP0]); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nIncrease min above RP0 (invalid)...\n"); |
| writeval_inval(sysfs_files[MIN].filp, origfreqs[RP0] + 1000); |
| check(); |
| |
| igt_debug("\nDecrease max to RPn (invalid)...\n"); |
| writeval_inval(sysfs_files[MAX].filp, origfreqs[RPn]); |
| check(); |
| |
| igt_debug("\nDecrease min to midpoint...\n"); |
| writeval(sysfs_files[MIN].filp, fmid); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nDecrease min to RPn...\n"); |
| writeval(sysfs_files[MIN].filp, origfreqs[RPn]); |
| if (load_gpu) |
| do_load_gpu(); |
| check(); |
| |
| igt_debug("\nDecrease min below RPn (invalid)...\n"); |
| writeval_inval(sysfs_files[MIN].filp, 0); |
| check(); |
| |
| igt_debug("\nDecrease max to midpoint...\n"); |
| writeval(sysfs_files[MAX].filp, fmid); |
| check(); |
| |
| igt_debug("\nDecrease max to RPn...\n"); |
| writeval(sysfs_files[MAX].filp, origfreqs[RPn]); |
| check(); |
| |
| igt_debug("\nDecrease max below RPn (invalid)...\n"); |
| writeval_inval(sysfs_files[MAX].filp, 0); |
| check(); |
| |
| igt_debug("\nIncrease min to RP0 (invalid)...\n"); |
| writeval_inval(sysfs_files[MIN].filp, origfreqs[RP0]); |
| check(); |
| |
| igt_debug("\nIncrease max to midpoint...\n"); |
| writeval(sysfs_files[MAX].filp, fmid); |
| check(); |
| |
| igt_debug("\nIncrease max to RP0...\n"); |
| writeval(sysfs_files[MAX].filp, origfreqs[RP0]); |
| check(); |
| |
| igt_debug("\nIncrease max above RP0 (invalid)...\n"); |
| writeval_inval(sysfs_files[MAX].filp, origfreqs[RP0] + 1000); |
| check(); |
| |
| writeval(sysfs_files[MIN].filp, origfreqs[MIN]); |
| writeval(sysfs_files[MAX].filp, origfreqs[MAX]); |
| } |
| |
| static void basic_check(void) |
| { |
| int freqs[NUMFREQ]; |
| |
| read_freqs(freqs); |
| dump(freqs); |
| check_freq_constraints(freqs); |
| } |
| |
| #define IDLE_WAIT_TIMESTEP_MSEC 250 |
| #define IDLE_WAIT_TIMEOUT_MSEC 2500 |
| static void idle_check(void) |
| { |
| int freqs[NUMFREQ]; |
| int wait = 0; |
| |
| /* Monitor frequencies until cur settles down to min, which should |
| * happen within the allotted time */ |
| do { |
| read_freqs(freqs); |
| dump(freqs); |
| check_freq_constraints(freqs); |
| if (freqs[CUR] == freqs[RPn]) |
| break; |
| usleep(1000 * IDLE_WAIT_TIMESTEP_MSEC); |
| wait += IDLE_WAIT_TIMESTEP_MSEC; |
| } while (wait < IDLE_WAIT_TIMEOUT_MSEC); |
| |
| igt_debugfs_dump(drm_fd, "i915_rps_boost_info"); |
| igt_assert_eq(freqs[CUR], freqs[RPn]); |
| igt_debug("Required %d msec to reach cur=idle\n", wait); |
| } |
| |
| #define LOADED_WAIT_TIMESTEP_MSEC 100 |
| #define LOADED_WAIT_TIMEOUT_MSEC 3000 |
| static void loaded_check(void) |
| { |
| int freqs[NUMFREQ]; |
| int wait = 0; |
| |
| /* Monitor frequencies until cur increases to max, which should |
| * happen within the allotted time */ |
| do { |
| read_freqs(freqs); |
| dump(freqs); |
| check_freq_constraints(freqs); |
| if (freqs[CUR] >= freqs[MAX]) |
| break; |
| usleep(1000 * LOADED_WAIT_TIMESTEP_MSEC); |
| wait += LOADED_WAIT_TIMESTEP_MSEC; |
| } while (wait < LOADED_WAIT_TIMEOUT_MSEC); |
| |
| igt_debugfs_dump(drm_fd, "i915_rps_boost_info"); |
| igt_assert_lte(freqs[MAX], freqs[CUR]); |
| igt_debug("Required %d msec to reach cur=max\n", wait); |
| } |
| |
| #define STABILIZE_WAIT_TIMESTEP_MSEC 250 |
| #define STABILIZE_WAIT_TIMEOUT_MSEC 15000 |
| static void stabilize_check(int *out) |
| { |
| int freqs[NUMFREQ]; |
| int wait = 0; |
| |
| /* Monitor frequencies until HW will stabilize cur frequency. |
| * It should happen within allotted time */ |
| read_freqs(freqs); |
| dump(freqs); |
| usleep(1000 * STABILIZE_WAIT_TIMESTEP_MSEC); |
| do { |
| read_freqs(out); |
| dump(out); |
| |
| if (memcmp(freqs, out, sizeof(freqs)) == 0) |
| break; |
| |
| memcpy(freqs, out, sizeof(freqs)); |
| wait += STABILIZE_WAIT_TIMESTEP_MSEC; |
| } while (wait < STABILIZE_WAIT_TIMEOUT_MSEC); |
| |
| igt_debugfs_dump(drm_fd, "i915_rps_boost_info"); |
| igt_debug("Waited %d msec to stabilize cur\n", wait); |
| } |
| |
| static void resubmit_batch(int fd, uint32_t handle, int count) |
| { |
| struct drm_i915_gem_exec_object2 obj = { |
| .handle = handle |
| }; |
| struct drm_i915_gem_execbuffer2 eb = { |
| .buffer_count = 1, |
| .buffers_ptr = to_user_pointer(&obj), |
| }; |
| while (count--) |
| gem_execbuf(fd, &eb); |
| } |
| |
| static void boost_freq(int fd, int *boost_freqs) |
| { |
| int64_t timeout = 1; |
| igt_spin_t *load; |
| |
| load = igt_spin_batch_new(fd, 0, 0, 0); |
| resubmit_batch(fd, load->handle, 16); |
| |
| /* Waiting will grant us a boost to maximum */ |
| gem_wait(fd, load->handle, &timeout); |
| |
| read_freqs(boost_freqs); |
| dump(boost_freqs); |
| |
| /* Avoid downlocking till boost request is pending */ |
| igt_spin_batch_end(load); |
| gem_sync(fd, load->handle); |
| igt_spin_batch_free(fd, load); |
| } |
| |
| static void waitboost(int fd, bool reset) |
| { |
| int pre_freqs[NUMFREQ]; |
| int boost_freqs[NUMFREQ]; |
| int post_freqs[NUMFREQ]; |
| int fmid = (origfreqs[RPn] + origfreqs[RP0]) / 2; |
| fmid = get_hw_rounded_freq(fmid); |
| |
| load_helper_run(LOW); |
| |
| igt_debug("Apply low load...\n"); |
| sleep(1); |
| stabilize_check(pre_freqs); |
| |
| if (reset) { |
| igt_debug("Reset gpu...\n"); |
| igt_force_gpu_reset(fd); |
| sleep(1); |
| } |
| |
| /* Set max freq to less than boost freq */ |
| writeval(sysfs_files[MAX].filp, fmid); |
| |
| /* When we wait upon the GPU, we want to temporarily boost it |
| * to maximum. |
| */ |
| boost_freq(fd, boost_freqs); |
| |
| /* Set max freq to original softmax */ |
| writeval(sysfs_files[MAX].filp, origfreqs[MAX]); |
| |
| igt_debug("Apply low load again...\n"); |
| sleep(1); |
| stabilize_check(post_freqs); |
| |
| igt_debug("Removing load...\n"); |
| load_helper_stop(); |
| idle_check(); |
| |
| igt_assert_lt(pre_freqs[CUR], pre_freqs[MAX]); |
| igt_assert_eq(boost_freqs[CUR], boost_freqs[BOOST]); |
| igt_assert_lt(post_freqs[CUR], post_freqs[MAX]); |
| } |
| |
| static void pm_rps_exit_handler(int sig) |
| { |
| if (origfreqs[MIN] > readval(sysfs_files[MAX].filp)) { |
| writeval(sysfs_files[MAX].filp, origfreqs[MAX]); |
| writeval(sysfs_files[MIN].filp, origfreqs[MIN]); |
| } else { |
| writeval(sysfs_files[MIN].filp, origfreqs[MIN]); |
| writeval(sysfs_files[MAX].filp, origfreqs[MAX]); |
| } |
| |
| if (lh.igt_proc.running) |
| load_helper_stop(); |
| |
| close(drm_fd); |
| } |
| |
| igt_main |
| { |
| igt_skip_on_simulation(); |
| |
| igt_fixture { |
| const int device = drm_get_card(); |
| struct sysfs_file *sysfs_file = sysfs_files; |
| int ret; |
| |
| /* Use drm_open_driver to verify device existence */ |
| drm_fd = drm_open_driver(DRIVER_INTEL); |
| igt_require_gem(drm_fd); |
| igt_require(gem_can_store_dword(drm_fd, 0)); |
| |
| do { |
| int val = -1; |
| char *path; |
| |
| ret = asprintf(&path, sysfs_base_path, device, sysfs_file->name); |
| igt_assert(ret != -1); |
| sysfs_file->filp = fopen(path, sysfs_file->mode); |
| igt_require(sysfs_file->filp); |
| setbuf(sysfs_file->filp, NULL); |
| |
| val = readval(sysfs_file->filp); |
| igt_assert(val >= 0); |
| sysfs_file++; |
| } while (sysfs_file->name != NULL); |
| |
| read_freqs(origfreqs); |
| |
| igt_install_exit_handler(pm_rps_exit_handler); |
| } |
| |
| igt_subtest("basic-api") |
| min_max_config(basic_check, false); |
| |
| /* Verify the constraints, check if we can reach idle */ |
| igt_subtest("min-max-config-idle") |
| min_max_config(idle_check, true); |
| |
| /* Verify the constraints with high load, check if we can reach max */ |
| igt_subtest("min-max-config-loaded") { |
| load_helper_run(HIGH); |
| min_max_config(loaded_check, false); |
| load_helper_stop(); |
| } |
| |
| /* Checks if we achieve boost using gem_wait */ |
| igt_subtest("waitboost") |
| waitboost(drm_fd, false); |
| |
| /* Test boost frequency after GPU reset */ |
| igt_subtest("reset") { |
| igt_hang_t hang = igt_allow_hang(drm_fd, 0, 0); |
| waitboost(drm_fd, true); |
| igt_disallow_hang(drm_fd, hang); |
| } |
| } |