summaryrefslogtreecommitdiff
path: root/lib/igt_aux.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/igt_aux.c')
-rw-r--r--lib/igt_aux.c699
1 files changed, 699 insertions, 0 deletions
diff --git a/lib/igt_aux.c b/lib/igt_aux.c
new file mode 100644
index 0000000..131ff4b
--- /dev/null
+++ b/lib/igt_aux.c
@@ -0,0 +1,699 @@
+/*
+ * Copyright © 2007, 2011, 2013, 2014, 2015 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:
+ * Eric Anholt <eric@anholt.net>
+ * Daniel Vetter <daniel.vetter@ffwll.ch>
+ *
+ */
+
+#ifndef ANDROID
+#define _GNU_SOURCE
+#else
+#include <libgen.h>
+#endif
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <pciaccess.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/utsname.h>
+#include <termios.h>
+
+#include "drmtest.h"
+#include "i915_drm.h"
+#include "intel_chipset.h"
+#include "igt_aux.h"
+#include "igt_debugfs.h"
+#include "igt_gt.h"
+#include "config.h"
+#include "intel_reg.h"
+#include "ioctl_wrappers.h"
+#include "igt_kms.h"
+
+/**
+ * SECTION:igt_aux
+ * @short_description: Auxiliary libraries and support functions
+ * @title: i-g-t aux
+ * @include: igt_aux.h
+ *
+ * This library provides various auxiliary helper functions that don't really
+ * fit into any other topic.
+ */
+
+
+/* signal interrupt helpers */
+static struct igt_helper_process signal_helper;
+long long int sig_stat;
+static void __attribute__((noreturn)) signal_helper_process(pid_t pid)
+{
+ /* Interrupt the parent process at 500Hz, just to be annoying */
+ while (1) {
+ usleep(1000 * 1000 / 500);
+ if (kill(pid, SIGUSR1)) /* Parent has died, so must we. */
+ exit(0);
+ }
+}
+
+static void sig_handler(int i)
+{
+ sig_stat++;
+}
+
+/**
+ * igt_fork_signal_helper:
+ *
+ * Fork a child process using #igt_fork_helper to interrupt the parent process
+ * with a SIGUSR1 signal at regular quick intervals. The corresponding dummy
+ * signal handler is installed in the parent process.
+ *
+ * This is useful to exercise ioctl error paths, at least where those can be
+ * exercises by interrupting blocking waits, like stalling for the gpu. This
+ * helper can also be used from children spawned with #igt_fork.
+ *
+ * In tests with subtests this function can be called outside of failure
+ * catching code blocks like #igt_fixture or #igt_subtest.
+ */
+void igt_fork_signal_helper(void)
+{
+ if (igt_only_list_subtests())
+ return;
+
+ signal(SIGUSR1, sig_handler);
+
+ igt_fork_helper(&signal_helper) {
+ signal_helper_process(getppid());
+ }
+}
+
+/**
+ * igt_stop_signal_helper:
+ *
+ * Stops the child process spawned with igt_fork_signal_helper() again.
+ *
+ * In tests with subtests this function can be called outside of failure
+ * catching code blocks like #igt_fixture or #igt_subtest.
+ */
+void igt_stop_signal_helper(void)
+{
+ if (igt_only_list_subtests())
+ return;
+
+ igt_stop_helper(&signal_helper);
+
+ sig_stat = 0;
+}
+
+/**
+ * igt_check_boolean_env_var:
+ * @env_var: environment variable name
+ * @default_value: default value for the environment variable
+ *
+ * This function should be used to parse boolean environment variable options.
+ *
+ * Returns:
+ * The boolean value of the environment variable @env_var as decoded by atoi()
+ * if it is set and @default_value if the variable is not set.
+ */
+bool igt_check_boolean_env_var(const char *env_var, bool default_value)
+{
+ char *val;
+
+ val = getenv(env_var);
+ if (!val)
+ return default_value;
+
+ return atoi(val) != 0;
+}
+
+/**
+ * igt_aub_dump_enabled:
+ *
+ * Returns:
+ * True if AUB dumping is enabled with IGT_DUMP_AUB=1 in the environment, false
+ * otherwise.
+ */
+bool igt_aub_dump_enabled(void)
+{
+ static int dump_aub = -1;
+
+ if (dump_aub == -1)
+ dump_aub = igt_check_boolean_env_var("IGT_DUMP_AUB", false);
+
+ return dump_aub;
+}
+
+/* other helpers */
+/**
+ * igt_exchange_int:
+ * @array: pointer to the array of integers
+ * @i: first position
+ * @j: second position
+ *
+ * Exchanges the two values at array indices @i and @j. Useful as an exchange
+ * function for igt_permute_array().
+ */
+void igt_exchange_int(void *array, unsigned i, unsigned j)
+{
+ int *int_arr, tmp;
+ int_arr = array;
+
+ tmp = int_arr[i];
+ int_arr[i] = int_arr[j];
+ int_arr[j] = tmp;
+}
+
+static uint32_t
+hars_petruska_f54_1_random_unsafe(void)
+{
+ static uint32_t state = 0x12345678;
+#define rol(x,k) ((x << k) | (x >> (32-k)))
+ return state = (state ^ rol (state, 5) ^ rol (state, 24)) + 0x37798849;
+#undef rol
+}
+
+/**
+ * igt_permute_array:
+ * @array: pointer to array
+ * @size: size of the array
+ * @exchange_func: function to exchange array elements
+ *
+ * This function randomly permutes the array using random() as the PRNG source.
+ * The @exchange_func function is called to exchange two elements in the array
+ * when needed.
+ */
+void igt_permute_array(void *array, unsigned size,
+ void (*exchange_func)(void *array,
+ unsigned i,
+ unsigned j))
+{
+ int i;
+
+ for (i = size - 1; i > 1; i--) {
+ /* yes, not perfectly uniform, who cares */
+ long l = hars_petruska_f54_1_random_unsafe() % (i +1);
+ if (i != l)
+ exchange_func(array, i, l);
+ }
+}
+
+__attribute__((format(printf, 1, 2)))
+static void igt_interactive_info(const char *format, ...)
+{
+ va_list args;
+
+ if (!isatty(STDERR_FILENO))
+ return;
+
+ if (igt_log_level > IGT_LOG_INFO)
+ return;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+
+/**
+ * igt_progress:
+ * @header: header string to prepend to the progress indicator
+ * @i: work processed thus far
+ * @total: total amount of work
+ *
+ * This function draws a progress indicator, which is useful for running
+ * long-winded tests manually on the console. To avoid spamming logfiles in
+ * automated runs the progress indicator is suppressed when not running on a
+ * terminal.
+ */
+void igt_progress(const char *header, uint64_t i, uint64_t total)
+{
+ int divider = 200;
+
+ if (i+1 >= total) {
+ igt_interactive_info("\r%s100%%\n", header);
+ return;
+ }
+
+ if (total / 200 == 0)
+ divider = 1;
+
+ /* only bother updating about every 0.5% */
+ if (i % (total / divider) == 0)
+ igt_interactive_info("\r%s%3llu%%", header,
+ (long long unsigned)i * 100 / total);
+}
+
+/**
+ * igt_print_activity:
+ *
+ * Print a '.' to indicate activity. This is printed without a newline and
+ * only if output is to a terminal.
+ */
+void igt_print_activity(void)
+{
+ igt_interactive_info(".");
+}
+
+/* mappable aperture trasher helper */
+drm_intel_bo **trash_bos;
+int num_trash_bos;
+
+/**
+ * igt_init_aperture_trashers:
+ * @bufmgr: libdrm buffer manager
+ *
+ * Initialize the aperture trasher using @bufmgr, which can then be run with
+ * igt_trash_aperture().
+ */
+void igt_init_aperture_trashers(drm_intel_bufmgr *bufmgr)
+{
+ int i;
+
+ num_trash_bos = gem_mappable_aperture_size() / (1024*1024);
+
+ trash_bos = malloc(num_trash_bos * sizeof(drm_intel_bo *));
+ igt_assert(trash_bos);
+
+ for (i = 0; i < num_trash_bos; i++)
+ trash_bos[i] = drm_intel_bo_alloc(bufmgr, "trash bo", 1024*1024, 4096);
+}
+
+/**
+ * igt_trash_aperture:
+ *
+ * Trash the aperture by walking a set of GTT memory mapped objects.
+ */
+void igt_trash_aperture(void)
+{
+ int i;
+ uint8_t *gtt_ptr;
+
+ for (i = 0; i < num_trash_bos; i++) {
+ drm_intel_gem_bo_map_gtt(trash_bos[i]);
+ gtt_ptr = trash_bos[i]->virtual;
+ *gtt_ptr = 0;
+ drm_intel_gem_bo_unmap_gtt(trash_bos[i]);
+ }
+}
+
+/**
+ * igt_cleanup_aperture_trashers:
+ *
+ * Clean up all aperture trasher state set up with igt_init_aperture_trashers().
+ */
+void igt_cleanup_aperture_trashers(void)
+{
+ int i;
+
+ for (i = 0; i < num_trash_bos; i++)
+ drm_intel_bo_unreference(trash_bos[i]);
+
+ free(trash_bos);
+}
+
+/**
+ * igt_system_suspend_autoresume:
+ *
+ * Execute a system suspend-to-mem cycle and automatically wake up again using
+ * the firmware's resume timer.
+ *
+ * This is very handy for implementing any kind of suspend/resume test.
+ */
+void igt_system_suspend_autoresume(void)
+{
+ int ret;
+
+ /* FIXME: Simulation doesn't like suspend/resume, and not even a lighter
+ * approach using /sys/power/pm_test to just test our driver's callbacks
+ * seems to fare better. We need to investigate what's going on. */
+ igt_skip_on_simulation();
+
+ ret = system("rtcwake -s 30 -m mem");
+ igt_assert_f(ret == 0, "This failure means that something is wrong with the rtcwake tool "
+ "or how your distro is set up. This is not a i915.ko or i-g-t bug.");
+}
+
+/**
+ * igt_system_hibernate_autoresume:
+ *
+ * Execute a system suspend-to-disk cycle and automatically wake up again using
+ * the firmware's resume timer.
+ *
+ * This is very handy for implementing any kind of hibernate/resume test.
+ */
+void igt_system_hibernate_autoresume(void)
+{
+ int ret;
+
+ /* FIXME: I'm guessing simulation behaves the same way as with
+ * suspend/resume, but it might be prudent to make sure
+ */
+ /* FIXME: Simulation doesn't like suspend/resume, and not even a lighter
+ * approach using /sys/power/pm_test to just test our driver's callbacks
+ * seems to fare better. We need to investigate what's going on. */
+ igt_skip_on_simulation();
+
+ /* The timeout might need to be adjusted if hibernation takes too long
+ * or if we have to wait excessively long before resume
+ */
+ ret = system("rtcwake -s 90 -m disk");
+ igt_assert_f(ret == 0, "This failure means that something is wrong with the rtcwake tool "
+ "or how your distro is set up. This is not a i915.ko or i-g-t bug.");
+}
+
+/**
+ * igt_drop_root:
+ *
+ * Drop root privileges and make sure it actually worked. Useful for tests
+ * which need to check security constraints. Note that this should only be
+ * called from manually forked processes, since the lack of root privileges
+ * will wreak havoc with the automatic cleanup handlers.
+ */
+void igt_drop_root(void)
+{
+ igt_assert(getuid() == 0);
+
+ igt_assert(setgid(2) == 0);
+ igt_assert(setuid(2) == 0);
+
+ igt_assert(getgid() == 2);
+ igt_assert(getuid() == 2);
+}
+
+/**
+ * igt_debug_wait_for_keypress:
+ * @var: var lookup to to enable this wait
+ *
+ * Waits for a key press when run interactively and when the corresponding debug
+ * var is set in the --interactive-debug=&lt;var&gt; variable. Multiple keys
+ * can be specified as a comma-separated list or alternatively "all" if a wait
+ * should happen for all cases.
+ *
+ * When not connected to a terminal interactive_debug is ignored
+ * and execution immediately continues.
+ *
+ * This is useful for display tests where under certain situation manual
+ * inspection of the display is useful. Or when running a testcase in the
+ * background.
+ */
+void igt_debug_wait_for_keypress(const char *var)
+{
+ struct termios oldt, newt;
+
+ if (!isatty(STDIN_FILENO))
+ return;
+
+ if (!igt_interactive_debug)
+ return;
+
+ if (!strstr(igt_interactive_debug, var) &&
+ !strstr(igt_interactive_debug, "all"))
+ return;
+
+ igt_info("Press any key to continue ...\n");
+
+ tcgetattr ( STDIN_FILENO, &oldt );
+ newt = oldt;
+ newt.c_lflag &= ~( ICANON | ECHO );
+ tcsetattr ( STDIN_FILENO, TCSANOW, &newt );
+ getchar();
+ tcsetattr ( STDIN_FILENO, TCSANOW, &oldt );
+}
+
+#define POWER_DIR "/sys/devices/pci0000:00/0000:00:02.0/power"
+/* We just leak this on exit ... */
+int pm_status_fd = -1;
+
+/**
+ * igt_setup_runtime_pm:
+ *
+ * Sets up the runtime PM helper functions and enables runtime PM. To speed up
+ * tests the autosuspend delay is set to 0.
+ *
+ * Returns:
+ * True if runtime pm is available, false otherwise.
+ */
+bool igt_setup_runtime_pm(void)
+{
+ int fd;
+ ssize_t size;
+ char buf[6];
+
+ if (pm_status_fd >= 0)
+ return true;
+
+ /* The Audio driver can get runtime PM references, so we need to make
+ * sure its runtime PM is enabled, so it can release the refs and
+ * actually enable us to runtime suspend. */
+ fd = open("/sys/module/snd_hda_intel/parameters/power_save", O_WRONLY);
+ if (fd >= 0) {
+ igt_assert(write(fd, "1\n", 2) == 2);
+ close(fd);
+ }
+ fd = open("/sys/bus/pci/devices/0000:00:03.0/power/control", O_WRONLY);
+ if (fd >= 0) {
+ igt_assert(write(fd, "auto\n", 5) == 5);
+ close(fd);
+ }
+
+ /* Our implementation uses autosuspend. Try to set it to 0ms so the test
+ * suite goes faster and we have a higher probability of triggering race
+ * conditions. */
+ fd = open(POWER_DIR "/autosuspend_delay_ms", O_WRONLY);
+ igt_assert_f(fd >= 0,
+ "Can't open " POWER_DIR "/autosuspend_delay_ms\n");
+
+ /* If we fail to write to the file, it means this system doesn't support
+ * runtime PM. */
+ size = write(fd, "0\n", 2);
+
+ close(fd);
+
+ if (size != 2)
+ return false;
+
+ /* We know we support runtime PM, let's try to enable it now. */
+ fd = open(POWER_DIR "/control", O_RDWR);
+ igt_assert_f(fd >= 0, "Can't open " POWER_DIR "/control\n");
+
+ size = write(fd, "auto\n", 5);
+ igt_assert(size == 5);
+
+ lseek(fd, 0, SEEK_SET);
+ size = read(fd, buf, ARRAY_SIZE(buf));
+ igt_assert(size == 5);
+ igt_assert(strncmp(buf, "auto\n", 5) == 0);
+
+ close(fd);
+
+ pm_status_fd = open(POWER_DIR "/runtime_status", O_RDONLY);
+ igt_assert_f(pm_status_fd >= 0,
+ "Can't open " POWER_DIR "/runtime_status\n");
+
+ return true;
+}
+
+/**
+ * igt_get_runtime_pm_status:
+ *
+ * Returns: The current runtime PM status.
+ */
+enum igt_runtime_pm_status igt_get_runtime_pm_status(void)
+{
+ ssize_t n_read;
+ char buf[32];
+
+ lseek(pm_status_fd, 0, SEEK_SET);
+ n_read = read(pm_status_fd, buf, ARRAY_SIZE(buf));
+ igt_assert(n_read >= 0);
+ buf[n_read] = '\0';
+
+ if (strncmp(buf, "suspended\n", n_read) == 0)
+ return IGT_RUNTIME_PM_STATUS_SUSPENDED;
+ else if (strncmp(buf, "active\n", n_read) == 0)
+ return IGT_RUNTIME_PM_STATUS_ACTIVE;
+ else if (strncmp(buf, "suspending\n", n_read) == 0)
+ return IGT_RUNTIME_PM_STATUS_SUSPENDING;
+ else if (strncmp(buf, "resuming\n", n_read) == 0)
+ return IGT_RUNTIME_PM_STATUS_RESUMING;
+
+ igt_assert_f(false, "Unknown status %s\n", buf);
+ return IGT_RUNTIME_PM_STATUS_UNKNOWN;
+}
+
+/**
+ * igt_wait_for_pm_status:
+ * @status: desired runtime PM status
+ *
+ * Waits until for the driver to switch to into the desired runtime PM status,
+ * with a 10 second timeout.
+ *
+ * Some subtests call this function while the signal helper is active, so we
+ * can't assume each usleep() call will sleep for 100ms.
+ *
+ * Returns:
+ * True if the desired runtime PM status was attained, false if the operation
+ * timed out.
+ */
+bool igt_wait_for_pm_status(enum igt_runtime_pm_status status)
+{
+ struct timeval start, end, diff;
+
+ igt_assert(gettimeofday(&start, NULL) == 0);
+ do {
+ if (igt_get_runtime_pm_status() == status)
+ return true;
+
+ usleep(100 * 1000);
+
+ igt_assert(gettimeofday(&end, NULL) == 0);
+ timersub(&end, &start, &diff);
+ } while (diff.tv_sec < 10);
+
+ return false;
+}
+
+/* Functions with prefix kmstest_ independent of cairo library are pulled out
+ * from file igt_kms.c since this file is skipped in lib/Android.mk when flag
+ * ANDROID_HAS_CAIRO is 0. This ensures the usability of these functions even
+ * when cairo library is not present on Android.
+ */
+
+struct type_name {
+ int type;
+ const char *name;
+};
+
+#define type_name_fn(res) \
+const char * kmstest_##res##_str(int type) { \
+ unsigned int i; \
+ for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \
+ if (res##_names[i].type == type) \
+ return res##_names[i].name; \
+ } \
+ return "(invalid)"; \
+}
+
+struct type_name encoder_type_names[] = {
+ { DRM_MODE_ENCODER_NONE, "none" },
+ { DRM_MODE_ENCODER_DAC, "DAC" },
+ { DRM_MODE_ENCODER_TMDS, "TMDS" },
+ { DRM_MODE_ENCODER_LVDS, "LVDS" },
+ { DRM_MODE_ENCODER_TVDAC, "TVDAC" },
+};
+
+type_name_fn(encoder_type)
+
+struct type_name connector_status_names[] = {
+ { DRM_MODE_CONNECTED, "connected" },
+ { DRM_MODE_DISCONNECTED, "disconnected" },
+ { DRM_MODE_UNKNOWNCONNECTION, "unknown" },
+};
+
+type_name_fn(connector_status)
+
+struct type_name connector_type_names[] = {
+ { DRM_MODE_CONNECTOR_Unknown, "unknown" },
+ { DRM_MODE_CONNECTOR_VGA, "VGA" },
+ { DRM_MODE_CONNECTOR_DVII, "DVI-I" },
+ { DRM_MODE_CONNECTOR_DVID, "DVI-D" },
+ { DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
+ { DRM_MODE_CONNECTOR_Composite, "composite" },
+ { DRM_MODE_CONNECTOR_SVIDEO, "s-video" },
+ { DRM_MODE_CONNECTOR_LVDS, "LVDS" },
+ { DRM_MODE_CONNECTOR_Component, "component" },
+ { DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN" },
+ { DRM_MODE_CONNECTOR_DisplayPort, "DP" },
+ { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
+ { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
+ { DRM_MODE_CONNECTOR_TV, "TV" },
+ { DRM_MODE_CONNECTOR_eDP, "eDP" },
+};
+
+type_name_fn(connector_type)
+
+
+/**
+ * igt_lock_mem:
+ * @size: the amount of memory to lock into RAM, in MB
+ *
+ * Allocate @size MB of memory and lock it into RAM. This releases any
+ * previously locked memory.
+ *
+ * Use #igt_unlock_mem to release the currently locked memory.
+ */
+static char *locked_mem;
+static size_t locked_size;
+
+void igt_lock_mem(size_t size)
+{
+ long pagesize = sysconf(_SC_PAGESIZE);
+ size_t i;
+ int ret;
+
+ if (size == 0) {
+ return;
+ }
+
+ if (locked_mem) {
+ igt_unlock_mem();
+ igt_warn("Unlocking previously locked memory.\n");
+ }
+
+ locked_size = size * 1024 * 1024;
+
+ locked_mem = malloc(locked_size);
+ igt_require_f(locked_mem,
+ "Could not allocate enough memory to lock.\n");
+
+ /* write into each page to ensure it is allocated */
+ for (i = 0; i < locked_size; i += pagesize)
+ locked_mem[i] = i;
+
+ ret = mlock(locked_mem, locked_size);
+ igt_assert_f(ret == 0, "Could not lock memory into RAM.\n");
+}
+
+/**
+ * igt_unlock_mem:
+ *
+ * Release and free the RAM used by #igt_lock_mem.
+ */
+void igt_unlock_mem(void)
+{
+ if (!locked_mem)
+ return;
+
+ munlock(locked_mem, locked_size);
+
+ free(locked_mem);
+ locked_mem = NULL;
+}