[igt-dev] [PATCH i-g-t] Use the new procps library libproc2
Petri Latvala
petri.latvala at intel.com
Thu Nov 17 08:41:29 UTC 2022
From: Craig Small <csmall at dropbear.xyz>
---
lib/igt_aux.c | 246 +++++-
lib/igt_aux.c.orig | 1921 ++++++++++++++++++++++++++++++++++++++++++
lib/meson.build | 7 +-
lib/meson.build.orig | 358 ++++++++
meson.build | 10 +-
5 files changed, 2500 insertions(+), 42 deletions(-)
create mode 100644 lib/igt_aux.c.orig
create mode 100644 lib/meson.build.orig
diff --git a/lib/igt_aux.c b/lib/igt_aux.c
index 15e30440..2aee1275 100644
--- a/lib/igt_aux.c
+++ b/lib/igt_aux.c
@@ -52,8 +52,16 @@
#include <assert.h>
#include <grp.h>
+#ifdef HAVE_LIBPROCPS
#include <proc/readproc.h>
+#endif
+#ifdef HAVE_LIBPROC2
+#include <libproc2/pids.h>
+#endif
+
#include <libudev.h>
+#include <linux/limits.h>
+#include <dirent.h>
#include "drmtest.h"
#include "i915_drm.h"
@@ -1217,6 +1225,7 @@ void igt_unlock_mem(void)
*/
int igt_is_process_running(const char *comm)
{
+#if HAVE_LIBPROCPS
PROCTAB *proc;
proc_t *proc_info;
bool found = false;
@@ -1235,6 +1244,26 @@ int igt_is_process_running(const char *comm)
closeproc(proc);
return found;
+#endif
+#if HAVE_LIBPROC2
+ enum pids_item Item[] = { PIDS_CMD };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+ char *pid_comm;
+ bool found = false;
+
+ if (procps_pids_new(&info, Item, 1) < 0)
+ return false;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ pid_comm = PIDS_VAL(0, str, stack, info);
+ if (!strncasecmp(pid_comm, comm, strlen(pid_comm))) {
+ found = true;
+ break;
+ }
+ }
+ procps_pids_unref(&info);
+ return found;
+#endif
}
/**
@@ -1251,6 +1280,7 @@ int igt_is_process_running(const char *comm)
*/
int igt_terminate_process(int sig, const char *comm)
{
+#if HAVE_LIBPROCPS
PROCTAB *proc;
proc_t *proc_info;
int err = 0;
@@ -1272,6 +1302,29 @@ int igt_terminate_process(int sig, const char *comm)
closeproc(proc);
return err;
+#endif
+#if HAVE_LIBPROC2
+ enum pids_item Items[] = { PIDS_ID_PID, PIDS_CMD };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+ char *pid_comm;
+ int pid;
+ int err = 0;
+
+ if (procps_pids_new(&info, Items, 2) < 0)
+ return -errno;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ pid = PIDS_VAL(0, s_int, stack, info);
+ pid_comm = PIDS_VAL(1, str, stack, info);
+ if (!strncasecmp(pid_comm, comm, strlen(pid_comm))) {
+ if (kill(pid, sig) < 0)
+ err = -errno;
+ break;
+ }
+ }
+ procps_pids_unref(&info);
+ return err;
+#endif
}
struct pinfo {
@@ -1341,9 +1394,9 @@ igt_show_stat_header(void)
}
static void
-igt_show_stat(proc_t *info, int *state, const char *fn)
+igt_show_stat(const pid_t tid, const char *cmd, int *state, const char *fn)
{
- struct pinfo p = { .pid = info->tid, .comm = info->cmd, .fn = fn };
+ struct pinfo p = { .pid = tid, .comm = cmd, .fn = fn };
if (!*state)
igt_show_stat_header();
@@ -1353,7 +1406,7 @@ igt_show_stat(proc_t *info, int *state, const char *fn)
}
static void
-__igt_lsof_fds(proc_t *proc_info, int *state, char *proc_path, const char *dir)
+__igt_lsof_fds(const pid_t tid, const char *cmd, int *state, char *proc_path, const char *dir)
{
struct dirent *d;
struct stat st;
@@ -1400,7 +1453,7 @@ again:
dirn = dirname(copy_fd_lnk);
if (!strncmp(dir, dirn, strlen(dir)))
- igt_show_stat(proc_info, state, fd_lnk);
+ igt_show_stat(tid, cmd, state, fd_lnk);
free(copy_fd_lnk);
free(fd_lnk);
@@ -1416,13 +1469,14 @@ again:
static void
__igt_lsof(const char *dir)
{
- PROCTAB *proc;
- proc_t *proc_info;
-
char path[30];
char *name_lnk;
struct stat st;
int state = 0;
+#ifdef HAVE_LIBPROCPS
+ PROCTAB *proc;
+ proc_t *proc_info;
+
proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
igt_assert(proc != NULL);
@@ -1443,19 +1497,57 @@ __igt_lsof(const char *dir)
name_lnk[read] = '\0';
if (!strncmp(dir, name_lnk, strlen(dir)))
- igt_show_stat(proc_info, &state, name_lnk);
+ igt_show_stat(proc_info->tid, proc_info->cmd, &state, name_lnk);
/* check also fd, seems that lsof(8) doesn't look here */
memset(path, 0, sizeof(path));
snprintf(path, sizeof(path), "/proc/%d/fd", proc_info->tid);
- __igt_lsof_fds(proc_info, &state, path, dir);
+ __igt_lsof_fds(proc_info->tid, proc_info->cmd, &state, path, dir);
free(name_lnk);
freeproc(proc_info);
}
closeproc(proc);
+#endif
+#ifdef HAVE_LIBPROC2
+ enum pids_item Items[] = { PIDS_ID_PID, PIDS_CMD };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+
+ if (procps_pids_new(&info, Items, 2) < 0)
+ return;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ ssize_t read;
+ int tid = PIDS_VAL(0, s_int, stack, info);
+ char *pid_comm = PIDS_VAL(1, str, stack, info);
+
+ /* check current working directory */
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "/proc/%d/cwd", tid);
+
+ if (stat(path, &st) == -1)
+ continue;
+
+ name_lnk = malloc(st.st_size + 1);
+
+ igt_assert((read = readlink(path, name_lnk, st.st_size + 1)));
+ name_lnk[read] = '\0';
+
+ if (!strncmp(dir, name_lnk, strlen(dir)))
+ igt_show_stat(tid, pid_comm, &state, name_lnk);
+
+ /* check also fd, seems that lsof(8) doesn't look here */
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "/proc/%d/fd", tid);
+
+ __igt_lsof_fds(tid, pid_comm, &state, path, dir);
+
+ free(name_lnk);
+ }
+ procps_pids_unref(&info);
+#endif
}
/**
@@ -1490,7 +1582,7 @@ igt_lsof(const char *dpath)
free(sanitized);
}
-static void pulseaudio_unload_module(proc_t *proc_info)
+static void pulseaudio_unload_module(const uid_t euid, const gid_t egid)
{
struct igt_helper_process pa_proc = {};
char xdg_dir[PATH_MAX];
@@ -1498,14 +1590,14 @@ static void pulseaudio_unload_module(proc_t *proc_info)
struct passwd *pw;
igt_fork_helper(&pa_proc) {
- pw = getpwuid(proc_info->euid);
+ pw = getpwuid(euid);
homedir = pw->pw_dir;
- snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", proc_info->euid);
+ snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", euid);
igt_info("Request pulseaudio to stop using audio device\n");
- setgid(proc_info->egid);
- setuid(proc_info->euid);
+ setgid(egid);
+ setuid(euid);
clearenv();
setenv("HOME", homedir, 1);
setenv("XDG_RUNTIME_DIR",xdg_dir, 1);
@@ -1524,10 +1616,13 @@ static void pipewire_reserve_wait(void)
char xdg_dir[PATH_MAX];
const char *homedir;
struct passwd *pw;
- proc_t *proc_info;
- PROCTAB *proc;
+ int tid=0, euid, egid;
+#ifdef HAVE_LIBPROCPS
igt_fork_helper(&pw_reserve_proc) {
+ proc_t *proc_info;
+ PROCTAB *proc;
+
igt_info("Preventing pipewire-pulse to use the audio drivers\n");
proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
@@ -1540,19 +1635,44 @@ static void pipewire_reserve_wait(void)
}
closeproc(proc);
+ tid = proc_info->tid;
+ euid = proc_info->euid;
+ egid = proc_info->egid;
+ freeproc(proc_info);
+#endif
+#ifdef HAVE_LIBPROC2
+ igt_fork(child, 1) {
+ enum pids_item Items[] = { PIDS_ID_PID, PIDS_ID_EUID, PIDS_ID_EGID };
+ enum rel_items { EU_PID, EU_EUID, EU_EGID };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+
+ igt_info("Preventing pipewire-pulse to use the audio drivers\n");
+
+ if (procps_pids_new(&info, Items, 3) < 0)
+ return;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ tid = PIDS_VAL(EU_PID, s_int, stack, info);
+ if (pipewire_pulse_pid == tid)
+ break;
+ }
+ euid = PIDS_VAL(EU_EUID, s_int, stack, info);
+ egid = PIDS_VAL(EU_EGID, s_int, stack, info);
+ procps_pids_unref(&info);
+#endif
+
/* Sanity check: if it can't find the process, it means it has gone */
- if (pipewire_pulse_pid != proc_info->tid)
+ if (pipewire_pulse_pid != tid)
exit(0);
- pw = getpwuid(proc_info->euid);
+ pw = getpwuid(euid);
homedir = pw->pw_dir;
- snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", proc_info->euid);
- setgid(proc_info->egid);
- setuid(proc_info->euid);
+ snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", euid);
+ setgid(egid);
+ setuid(euid);
clearenv();
setenv("HOME", homedir, 1);
setenv("XDG_RUNTIME_DIR",xdg_dir, 1);
- freeproc(proc_info);
/*
* pw-reserve will run in background. It will only exit when
@@ -1570,9 +1690,7 @@ static void pipewire_reserve_wait(void)
int pipewire_pulse_start_reserve(void)
{
bool is_pw_reserve_running = false;
- proc_t *proc_info;
int attempts = 0;
- PROCTAB *proc;
if (!pipewire_pulse_pid)
return 0;
@@ -1584,6 +1702,10 @@ int pipewire_pulse_start_reserve(void)
* pipewire version 0.3.50 or upper.
*/
for (attempts = 0; attempts < PIPEWIRE_RESERVE_MAX_TIME; attempts++) {
+#ifdef HAVE_LIBPROCPS
+ PROCTAB *proc;
+ proc_t *proc_info;
+
usleep(1000);
proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
igt_assert(proc != NULL);
@@ -1598,6 +1720,25 @@ int pipewire_pulse_start_reserve(void)
freeproc(proc_info);
}
closeproc(proc);
+#endif
+#ifdef HAVE_LIBPROC2
+ enum pids_item Items[] = { PIDS_ID_PID, PIDS_CMD };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+
+ usleep(1000);
+
+ if (procps_pids_new(&info, Items, 2) < 0)
+ return 1;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ if (!strcmp(PIDS_VAL(1, str, stack, info), "pw-reserve")) {
+ is_pw_reserve_running = true;
+ pipewire_pw_reserve_pid = PIDS_VAL(0, s_int, stack, info);
+ break;
+ }
+ }
+ procps_pids_unref(&info);
+#endif
if (is_pw_reserve_running)
break;
}
@@ -1645,7 +1786,7 @@ void pipewire_pulse_stop_reserve(void)
* If the check fails, it means that the process can simply be killed.
*/
static int
-__igt_lsof_audio_and_kill_proc(proc_t *proc_info, char *proc_path)
+__igt_lsof_audio_and_kill_proc(const pid_t tid, const char *cmd, const uid_t euid, const gid_t egid, char *proc_path)
{
const char *audio_dev = "/dev/snd/";
char path[PATH_MAX * 2];
@@ -1670,10 +1811,10 @@ __igt_lsof_audio_and_kill_proc(proc_t *proc_info, char *proc_path)
* 2) unload/unbind the the audio driver(s);
* 3) stop the pw-reserve thread.
*/
- if (!strcmp(proc_info->cmd, "pipewire-pulse")) {
+ if (!strcmp(cmd, "pipewire-pulse")) {
igt_info("process %d (%s) is using audio device. Should be requested to stop using them.\n",
- proc_info->tid, proc_info->cmd);
- pipewire_pulse_pid = proc_info->tid;
+ tid, cmd);
+ pipewire_pulse_pid = tid;
return 0;
}
/*
@@ -1685,9 +1826,9 @@ __igt_lsof_audio_and_kill_proc(proc_t *proc_info, char *proc_path)
* will respawn them. So, just ignore here, they'll honor pw-reserve,
* when the time comes.
*/
- if (!strcmp(proc_info->cmd, "pipewire-media-session"))
+ if (!strcmp(cmd, "pipewire-media-session"))
return 0;
- if (!strcmp(proc_info->cmd, "wireplumber"))
+ if (!strcmp(cmd, "wireplumber"))
return 0;
dp = opendir(proc_path);
@@ -1723,22 +1864,22 @@ __igt_lsof_audio_and_kill_proc(proc_t *proc_info, char *proc_path)
* enough to unbind audio modules and won't cause race issues
* with systemd trying to reload it.
*/
- if (!strcmp(proc_info->cmd, "pulseaudio")) {
- pulseaudio_unload_module(proc_info);
+ if (!strcmp(cmd, "pulseaudio")) {
+ pulseaudio_unload_module(euid, egid);
break;
}
/* For all other processes, just kill them */
igt_info("process %d (%s) is using audio device. Should be terminated.\n",
- proc_info->tid, proc_info->cmd);
+ tid, cmd);
- if (kill(proc_info->tid, SIGTERM) < 0) {
+ if (kill(tid, SIGTERM) < 0) {
igt_info("Fail to terminate %s (pid: %d) with SIGTERM\n",
- proc_info->cmd, proc_info->tid);
- if (kill(proc_info->tid, SIGABRT) < 0) {
+ cmd, tid);
+ if (kill(tid, SIGABRT) < 0) {
fail++;
igt_info("Fail to terminate %s (pid: %d) with SIGABRT\n",
- proc_info->cmd, proc_info->tid);
+ cmd, tid);
}
}
@@ -1760,23 +1901,48 @@ int
igt_lsof_kill_audio_processes(void)
{
char path[PATH_MAX];
+ int fail = 0;
+
+#ifdef HAVE_LIBPROCPS
proc_t *proc_info;
PROCTAB *proc;
- int fail = 0;
proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
igt_assert(proc != NULL);
pipewire_pulse_pid = 0;
-
while ((proc_info = readproc(proc, NULL))) {
if (snprintf(path, sizeof(path), "/proc/%d/fd", proc_info->tid) < 1)
fail++;
else
- fail += __igt_lsof_audio_and_kill_proc(proc_info, path);
+ fail += __igt_lsof_audio_and_kill_proc(proc_info->tid, proc_info->cmd, proc_info->euid, proc_info->egid, path);
freeproc(proc_info);
}
closeproc(proc);
+#endif
+#ifdef HAVE_LIBPROC2
+ enum pids_item Items[] = { PIDS_ID_PID, PIDS_CMD, PIDS_ID_EUID, PIDS_ID_EGID };
+ enum rel_items { EU_PID, EU_CMD, EU_EUID, EU_EGID };
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+ pid_t tid;
+
+ if (procps_pids_new(&info, Items, 4) < 0)
+ return 1;
+ while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
+ tid = PIDS_VAL(EU_PID, s_int, stack, info);
+
+ if (snprintf(path, sizeof(path), "/proc/%d/fd", tid) < 1)
+ fail++;
+ else
+ fail += __igt_lsof_audio_and_kill_proc(tid,
+ PIDS_VAL(EU_CMD, str, stack, info),
+ PIDS_VAL(EU_EUID, s_int, stack, info),
+ PIDS_VAL(EU_EGID, s_int, stack, info),
+ path);
+ }
+ procps_pids_unref(&info);
+#endif
return fail;
}
diff --git a/lib/igt_aux.c.orig b/lib/igt_aux.c.orig
new file mode 100644
index 00000000..15e30440
--- /dev/null
+++ b/lib/igt_aux.c.orig
@@ -0,0 +1,1921 @@
+/*
+ * 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 at anholt.net>
+ * Daniel Vetter <daniel.vetter at ffwll.ch>
+ *
+ */
+
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+#include <stdio.h>
+#include <fcntl.h>
+#include <pwd.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 <time.h>
+#include <unistd.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/utsname.h>
+#include <termios.h>
+#include <assert.h>
+#include <grp.h>
+
+#include <proc/readproc.h>
+#include <libudev.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 "igt_params.h"
+#include "igt_rand.h"
+#include "igt_sysfs.h"
+#include "config.h"
+#include "intel_reg.h"
+#include "ioctl_wrappers.h"
+#include "igt_kms.h"
+#include "igt_stats.h"
+#include "igt_sysfs.h"
+
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h> /* for dirname() */
+#endif
+
+/**
+ * SECTION:igt_aux
+ * @short_description: Auxiliary libraries and support functions
+ * @title: aux
+ * @include: igt.h
+ *
+ * This library provides various auxiliary helper functions that don't really
+ * fit into any other topic.
+ */
+
+static struct __igt_sigiter_global {
+ pid_t tid;
+ timer_t timer;
+ struct timespec offset;
+ struct {
+ long hit, miss;
+ long ioctls, signals;
+ } stat;
+} __igt_sigiter;
+
+static void sigiter(int sig, siginfo_t *info, void *arg)
+{
+ __igt_sigiter.stat.signals++;
+}
+
+#if 0
+#define SIG_ASSERT(expr) igt_assert(expr)
+#else
+#define SIG_ASSERT(expr)
+#endif
+
+static int
+sig_ioctl(int fd, unsigned long request, void *arg)
+{
+ struct itimerspec its;
+ int ret;
+
+ SIG_ASSERT(__igt_sigiter.timer);
+ SIG_ASSERT(__igt_sigiter.tid == gettid());
+
+ memset(&its, 0, sizeof(its));
+ if (timer_settime(__igt_sigiter.timer, 0, &its, NULL)) {
+ /* oops, we didn't undo the interrupter (i.e. !unwound abort) */
+ igt_ioctl = drmIoctl;
+ return drmIoctl(fd, request, arg);
+ }
+
+ its.it_value = __igt_sigiter.offset;
+ do {
+ long serial;
+
+ __igt_sigiter.stat.ioctls++;
+
+ ret = 0;
+ serial = __igt_sigiter.stat.signals;
+ igt_assert(timer_settime(__igt_sigiter.timer, 0, &its, NULL) == 0);
+ if (ioctl(fd, request, arg))
+ ret = errno;
+ if (__igt_sigiter.stat.signals == serial)
+ __igt_sigiter.stat.miss++;
+ if (ret == 0)
+ break;
+
+ if (ret == EINTR) {
+ __igt_sigiter.stat.hit++;
+
+ its.it_value.tv_sec *= 2;
+ its.it_value.tv_nsec *= 2;
+ while (its.it_value.tv_nsec >= NSEC_PER_SEC) {
+ its.it_value.tv_nsec -= NSEC_PER_SEC;
+ its.it_value.tv_sec += 1;
+ }
+
+ SIG_ASSERT(its.it_value.tv_nsec >= 0);
+ SIG_ASSERT(its.it_value.tv_sec >= 0);
+ }
+ } while (ret == EAGAIN || ret == EINTR);
+
+ memset(&its, 0, sizeof(its));
+ timer_settime(__igt_sigiter.timer, 0, &its, NULL);
+
+ errno = ret;
+ return ret ? -1 : 0;
+}
+
+static bool igt_sigiter_start(struct __igt_sigiter *iter, bool enable)
+{
+ /* Note that until we can automatically clean up on failed/skipped
+ * tests, we cannot assume the state of the igt_ioctl indirection.
+ */
+ SIG_ASSERT(igt_ioctl == drmIoctl);
+ igt_ioctl = drmIoctl;
+
+ if (enable) {
+ struct timespec start, end;
+ struct sigevent sev;
+ struct sigaction act;
+ struct itimerspec its;
+
+ igt_ioctl = sig_ioctl;
+ __igt_sigiter.tid = gettid();
+
+ memset(&sev, 0, sizeof(sev));
+ sev.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID;
+ sev.sigev_notify_thread_id = __igt_sigiter.tid;
+ sev.sigev_signo = SIGRTMIN;
+ igt_assert(timer_create(CLOCK_MONOTONIC, &sev, &__igt_sigiter.timer) == 0);
+
+ memset(&its, 0, sizeof(its));
+ igt_assert(timer_settime(__igt_sigiter.timer, 0, &its, NULL) == 0);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_sigaction = sigiter;
+ act.sa_flags = SA_SIGINFO;
+ igt_assert(sigaction(SIGRTMIN, &act, NULL) == 0);
+
+ /* Try to find the approximate delay required to skip over
+ * the timer_setttime and into the following ioctl() to try
+ * and avoid the timer firing before we enter the drmIoctl.
+ */
+ igt_assert(clock_gettime(CLOCK_MONOTONIC, &start) == 0);
+ igt_assert(timer_settime(__igt_sigiter.timer, 0, &its, NULL) == 0);
+ igt_assert(clock_gettime(CLOCK_MONOTONIC, &end) == 0);
+
+ __igt_sigiter.offset.tv_sec = end.tv_sec - start.tv_sec;
+ __igt_sigiter.offset.tv_nsec = end.tv_nsec - start.tv_nsec;
+ if (__igt_sigiter.offset.tv_nsec < 0) {
+ __igt_sigiter.offset.tv_nsec += NSEC_PER_SEC;
+ __igt_sigiter.offset.tv_sec -= 1;
+ }
+ if (__igt_sigiter.offset.tv_sec < 0) {
+ __igt_sigiter.offset.tv_nsec = 0;
+ __igt_sigiter.offset.tv_sec = 0;
+ }
+ igt_assert(__igt_sigiter.offset.tv_sec == 0);
+
+ igt_debug("Initial delay for interruption: %ld.%09lds\n",
+ __igt_sigiter.offset.tv_sec,
+ __igt_sigiter.offset.tv_nsec);
+ }
+
+ return true;
+}
+
+static bool igt_sigiter_stop(struct __igt_sigiter *iter, bool enable)
+{
+ if (enable) {
+ struct sigaction act;
+
+ SIG_ASSERT(igt_ioctl == sig_ioctl);
+ SIG_ASSERT(__igt_sigiter.tid == gettid());
+ igt_ioctl = drmIoctl;
+
+ timer_delete(__igt_sigiter.timer);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGRTMIN, &act, NULL);
+
+ memset(&__igt_sigiter, 0, sizeof(__igt_sigiter));
+ }
+
+ memset(iter, 0, sizeof(*iter));
+ return false;
+}
+
+bool __igt_sigiter_continue(struct __igt_sigiter *iter, bool enable)
+{
+ if (iter->pass++ == 0)
+ return igt_sigiter_start(iter, enable);
+
+ /* If nothing reported SIGINT, nothing will on the next pass, so
+ * give up! Also give up if everything is now executing faster
+ * than current sigtimer.
+ */
+ if (__igt_sigiter.stat.hit == 0 ||
+ __igt_sigiter.stat.miss == __igt_sigiter.stat.ioctls)
+ return igt_sigiter_stop(iter, enable);
+
+ igt_debug("%s: pass %d, missed %ld/%ld\n",
+ __func__, iter->pass - 1,
+ __igt_sigiter.stat.miss,
+ __igt_sigiter.stat.ioctls);
+
+ SIG_ASSERT(igt_ioctl == sig_ioctl);
+ SIG_ASSERT(__igt_sigiter.timer);
+
+ __igt_sigiter.offset.tv_sec *= 2;
+ __igt_sigiter.offset.tv_nsec *= 2;
+ while (__igt_sigiter.offset.tv_nsec >= NSEC_PER_SEC) {
+ __igt_sigiter.offset.tv_nsec -= NSEC_PER_SEC;
+ __igt_sigiter.offset.tv_sec += 1;
+ }
+ SIG_ASSERT(__igt_sigiter.offset.tv_nsec >= 0);
+ SIG_ASSERT(__igt_sigiter.offset.tv_sec >= 0);
+
+ memset(&__igt_sigiter.stat, 0, sizeof(__igt_sigiter.stat));
+ return true;
+}
+
+static struct igt_helper_process signal_helper;
+long long int sig_stat;
+__noreturn static void 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, SIGCONT)) /* 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 SIGCONT 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.
+ *
+ * Note that this just spews signals at the current process unconditionally and
+ * hence incurs quite a bit of overhead. For a more focused approach, with less
+ * overhead, look at the #igt_while_interruptible code block macro.
+ */
+void igt_fork_signal_helper(void)
+{
+ if (igt_only_list_subtests())
+ return;
+
+ /* We pick SIGCONT as it is a "safe" signal - if we send SIGCONT to
+ * an unexpecting process it spuriously wakes up and does nothing.
+ * Most other signals (e.g. SIGUSR1) cause the process to die if they
+ * are not handled. This is an issue in case the sighandler is not
+ * inherited correctly (or if there is a race in the inheritance
+ * and we send the signal at exactly the wrong time).
+ */
+ signal(SIGCONT, sig_handler);
+ setpgid(0, 0); /* define a new process group for the tests */
+
+ igt_fork_helper(&signal_helper) {
+ setpgid(0, 0); /* Escape from the test process group */
+
+ /* Pass along the test process group identifier,
+ * negative pid => send signal to everyone in the group.
+ */
+ 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_suspend_signal_helper:
+ *
+ * Suspends the child process spawned with igt_fork_signal_helper(). This
+ * should be called before a critical section of code that has difficulty to
+ * make progress if interrupted frequently, like the clone() syscall called
+ * from a largish executable. igt_resume_signal_helper() must be called after
+ * the critical section to restart interruptions for the test.
+ */
+void igt_suspend_signal_helper(void)
+{
+ int status;
+
+ if (!signal_helper.running)
+ return;
+
+ kill(signal_helper.pid, SIGSTOP);
+ while (waitpid(signal_helper.pid, &status, WUNTRACED) == -1 &&
+ errno == EINTR)
+ ;
+}
+
+/**
+ * igt_resume_signal_helper:
+ *
+ * Resumes the child process spawned with igt_fork_signal_helper().
+ *
+ * This should be paired with igt_suspend_signal_helper() and called after the
+ * problematic code sensitive to signals.
+ */
+void igt_resume_signal_helper(void)
+{
+ if (!signal_helper.running)
+ return;
+
+ kill(signal_helper.pid, SIGCONT);
+}
+
+static struct igt_helper_process shrink_helper;
+__noreturn static void shrink_helper_process(int fd, pid_t pid)
+{
+ while (1) {
+ igt_drop_caches_set(fd, DROP_SHRINK_ALL);
+ usleep(1000 * 1000 / 50);
+ if (kill(pid, 0)) /* Parent has died, so must we. */
+ exit(0);
+ }
+}
+
+/**
+ * igt_fork_shrink_helper:
+ *
+ * Fork a child process using #igt_fork_helper to force all available objects
+ * to be paged out (via i915_gem_shrink()).
+ *
+ * This is useful to exercise swapping paths, without requiring us to hit swap.
+ *
+ * This should only be used from an igt_fixture.
+ */
+void igt_fork_shrink_helper(int drm_fd)
+{
+ assert(!igt_only_list_subtests());
+ igt_require(igt_drop_caches_has(drm_fd, DROP_SHRINK_ALL));
+ igt_fork_helper(&shrink_helper)
+ shrink_helper_process(drm_fd, getppid());
+}
+
+/**
+ * igt_stop_shrink_helper:
+ *
+ * Stops the child process spawned with igt_fork_shrink_helper().
+ */
+void igt_stop_shrink_helper(void)
+{
+ igt_stop_helper(&shrink_helper);
+}
+
+static void show_kernel_stack(pid_t pid)
+{
+ char buf[80], *str;
+ int dir;
+
+ snprintf(buf, sizeof(buf), "/proc/%d", pid);
+ dir = open(buf, O_RDONLY);
+ if (dir < 0)
+ return;
+
+ str = igt_sysfs_get(dir, "stack");
+ if (str) {
+ igt_debug("Kernel stack for pid %d:\n%s\n", pid, str);
+ free(str);
+ }
+
+ close(dir);
+}
+
+static struct igt_helper_process hang_detector;
+__noreturn static void
+hang_detector_process(int fd, pid_t pid, dev_t rdev)
+{
+ struct udev_monitor *mon =
+ udev_monitor_new_from_netlink(udev_new(), "kernel");
+ struct pollfd pfd;
+ int ret;
+
+ udev_monitor_filter_add_match_subsystem_devtype(mon, "drm", NULL);
+ udev_monitor_enable_receiving(mon);
+
+ pfd.fd = udev_monitor_get_fd(mon);
+ pfd.events = POLLIN;
+
+ while ((ret = poll(&pfd, 1, 2000)) >= 0) {
+ struct udev_device *dev;
+ dev_t devnum;
+
+ if (kill(pid, 0)) { /* Parent has died, so must we. */
+ igt_warn("Parent died without killing its children (%s)\n",
+ __func__);
+ break;
+ }
+
+ dev = NULL;
+ if (ret > 0)
+ dev = udev_monitor_receive_device(mon);
+ if (dev == NULL)
+ continue;
+
+ devnum = udev_device_get_devnum(dev);
+ if (memcmp(&rdev, &devnum, sizeof(dev_t)) == 0) {
+ const char *str;
+
+ str = udev_device_get_property_value(dev, "ERROR");
+ if (str && atoi(str) == 1) {
+ show_kernel_stack(pid);
+ kill(pid, SIGIO);
+ }
+ }
+
+ udev_device_unref(dev);
+ }
+
+ exit(0);
+}
+
+__noreturn static void sig_abort(int sig)
+{
+ errno = 0; /* inside a signal, last errno reporting is confusing */
+ igt_assert(!"GPU hung");
+}
+
+void igt_fork_hang_detector(int fd)
+{
+ struct stat st;
+
+ igt_assert(fstat(fd, &st) == 0);
+
+ /*
+ * Disable per-engine reset to force an error uevent. We don't
+ * expect to get any hangs whilst the detector is enabled (if we do
+ * they are a test failure!) and so the loss of per-engine reset
+ * functionality is not an issue.
+ */
+ igt_assert(igt_params_set(fd, "reset", "%d", 1 /* only global reset */));
+
+ signal(SIGIO, sig_abort);
+ igt_fork_helper(&hang_detector)
+ hang_detector_process(fd, getppid(), st.st_rdev);
+}
+
+void igt_stop_hang_detector(void)
+{
+ /*
+ * Give the uevent time to arrive. No sleep at all misses about 20% of
+ * hangs (at least, in the i915_hangman/detector test). A sleep of 1ms
+ * seems to miss about 2%, 10ms loses <1%, so 100ms should be safe.
+ */
+ usleep(100 * 1000);
+
+ igt_stop_helper(&hang_detector);
+}
+
+/**
+ * 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;
+}
+
+/**
+ * igt_exchange_int64:
+ * @array: pointer to the array of int64_t
+ * @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_int64(void *array, unsigned i, unsigned j)
+{
+ int64_t *a = array;
+
+ igt_swap(a[i], a[j]);
+}
+
+/**
+ * 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 > 0; 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) || __igt_plain_output) {
+ errno = 0; /* otherwise would be either ENOTTY or EBADF */
+ 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 log files 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(".");
+}
+
+static int autoresume_delay;
+
+static const char *suspend_state_name[] = {
+ [SUSPEND_STATE_FREEZE] = "freeze",
+ [SUSPEND_STATE_STANDBY] = "standby",
+ [SUSPEND_STATE_S3] = "mem", /* Forces Suspend-to-Ram (S3) */
+ [SUSPEND_STATE_MEM] = "mem", /* Respects system default */
+ [SUSPEND_STATE_DISK] = "disk",
+};
+
+static const char *suspend_test_name[] = {
+ [SUSPEND_TEST_NONE] = "none",
+ [SUSPEND_TEST_FREEZER] = "freezer",
+ [SUSPEND_TEST_DEVICES] = "devices",
+ [SUSPEND_TEST_PLATFORM] = "platform",
+ [SUSPEND_TEST_PROCESSORS] = "processors",
+ [SUSPEND_TEST_CORE] = "core",
+};
+
+static const char *mem_sleep_name[] = {
+ [MEM_SLEEP_S2IDLE] = "s2idle",
+ [MEM_SLEEP_SHALLOW] = "shallow",
+ [MEM_SLEEP_DEEP] = "deep"
+};
+
+static enum igt_suspend_test get_suspend_test(int power_dir)
+{
+ char *test_line;
+ char *test_name;
+ enum igt_suspend_test test;
+
+ if (faccessat(power_dir, "pm_test", R_OK, 0))
+ return SUSPEND_TEST_NONE;
+
+ igt_assert((test_line = igt_sysfs_get(power_dir, "pm_test")));
+ for (test_name = strtok(test_line, " "); test_name;
+ test_name = strtok(NULL, " "))
+ if (test_name[0] == '[') {
+ test_name[strlen(test_name) - 1] = '\0';
+ test_name++;
+ break;
+ }
+
+ if (!test_name) {
+ free(test_line);
+ return SUSPEND_TEST_NONE;
+ }
+
+ for (test = SUSPEND_TEST_NONE; test < SUSPEND_TEST_NUM; test++)
+ if (strcmp(suspend_test_name[test], test_name) == 0)
+ break;
+
+ igt_assert(test < SUSPEND_TEST_NUM);
+
+ free(test_line);
+ return test;
+}
+
+static void set_suspend_test(int power_dir, enum igt_suspend_test test)
+{
+ igt_assert(test < SUSPEND_TEST_NUM);
+
+ if (faccessat(power_dir, "pm_test", W_OK, 0)) {
+ igt_require(test == SUSPEND_TEST_NONE);
+ return;
+ }
+
+ igt_assert(igt_sysfs_set(power_dir, "pm_test", suspend_test_name[test]));
+}
+
+#define SQUELCH ">/dev/null 2>&1"
+
+static void suspend_via_rtcwake(enum igt_suspend_state state)
+{
+ char cmd[128];
+ int delay, ret;
+
+ igt_assert(state < SUSPEND_STATE_NUM);
+
+ delay = igt_get_autoresume_delay(state);
+
+ /*
+ * Skip if rtcwake would fail for a reason not related to the kernel's
+ * suspend functionality.
+ */
+ snprintf(cmd, sizeof(cmd), "rtcwake -n -s %d -m %s " SQUELCH,
+ delay, suspend_state_name[state]);
+ ret = igt_system(cmd);
+ igt_require_f(ret == 0, "rtcwake test failed with %i\n"
+ "This failure could mean that something is wrong with "
+ "the rtcwake tool or how your distro is set up.\n",
+ ret);
+
+ snprintf(cmd, sizeof(cmd), "rtcwake -s %d -m %s ",
+ delay, suspend_state_name[state]);
+ ret = igt_system(cmd);
+ if (ret) {
+ const char *path = "suspend_stats";
+ char *info;
+ int dir;
+
+ igt_warn("rtcwake failed with %i\n"
+ "Check dmesg for further details.\n",
+ ret);
+
+ dir = open(igt_debugfs_mount(), O_RDONLY);
+ info = igt_sysfs_get(dir, path);
+ close(dir);
+ if (info) {
+ igt_debug("%s:\n%s\n", path, info);
+ free(info);
+ }
+ }
+ igt_assert_eq(ret, 0);
+}
+
+static void suspend_via_sysfs(int power_dir, enum igt_suspend_state state)
+{
+ igt_assert(state < SUSPEND_STATE_NUM);
+ igt_assert(igt_sysfs_set(power_dir, "state",
+ suspend_state_name[state]));
+}
+
+static bool is_state_supported(int power_dir, enum igt_suspend_state state)
+{
+ const char *str;
+ char *states;
+
+ igt_assert((states = igt_sysfs_get(power_dir, "state")));
+
+ str = strstr(states, suspend_state_name[state]);
+
+ if (!str)
+ igt_info("State %s not supported.\nSupported States: %s\n",
+ suspend_state_name[state], states);
+
+ free(states);
+ return str;
+}
+
+static int get_mem_sleep(void)
+{
+ char *mem_sleep_states;
+ char *mem_sleep_state;
+ enum igt_mem_sleep mem_sleep;
+ int power_dir;
+
+ igt_require((power_dir = open("/sys/power", O_RDONLY)) >= 0);
+
+ if (faccessat(power_dir, "mem_sleep", R_OK, 0))
+ return MEM_SLEEP_NONE;
+
+ igt_assert((mem_sleep_states = igt_sysfs_get(power_dir, "mem_sleep")));
+ for (mem_sleep_state = strtok(mem_sleep_states, " "); mem_sleep_state;
+ mem_sleep_state = strtok(NULL, " ")) {
+ if (mem_sleep_state[0] == '[') {
+ mem_sleep_state[strlen(mem_sleep_state) - 1] = '\0';
+ mem_sleep_state++;
+ break;
+ }
+ }
+
+ if (!mem_sleep_state) {
+ free(mem_sleep_states);
+ return MEM_SLEEP_NONE;
+ }
+
+ for (mem_sleep = MEM_SLEEP_S2IDLE; mem_sleep < MEM_SLEEP_NUM; mem_sleep++) {
+ if (strcmp(mem_sleep_name[mem_sleep], mem_sleep_state) == 0)
+ break;
+ }
+
+ igt_assert_f(mem_sleep < MEM_SLEEP_NUM, "Invalid mem_sleep state\n");
+
+ free(mem_sleep_states);
+ close(power_dir);
+ return mem_sleep;
+}
+
+static void set_mem_sleep(int power_dir, enum igt_mem_sleep sleep)
+{
+ igt_assert(sleep < MEM_SLEEP_NUM);
+
+ igt_assert_eq(faccessat(power_dir, "mem_sleep", W_OK, 0), 0);
+
+ igt_assert(igt_sysfs_set(power_dir, "mem_sleep",
+ mem_sleep_name[sleep]));
+}
+
+static bool is_mem_sleep_state_supported(int power_dir, enum igt_mem_sleep state)
+{
+ const char *str;
+ char *mem_sleep_states;
+
+ igt_assert((mem_sleep_states = igt_sysfs_get(power_dir, "mem_sleep")));
+
+ str = strstr(mem_sleep_states, mem_sleep_name[state]);
+
+ if (!str)
+ igt_info("mem_sleep state %s not supported.\nSupported mem_sleep states: %s\n",
+ mem_sleep_name[state], mem_sleep_states);
+
+ free(mem_sleep_states);
+ return str;
+}
+
+/**
+ * igt_system_suspend_autoresume:
+ * @state: an #igt_suspend_state, the target suspend state
+ * @test: an #igt_suspend_test, test point at which to complete the suspend
+ * cycle
+ *
+ * Execute a system suspend cycle targeting the given @state optionally
+ * completing the cycle at the given @test point and automaically wake up
+ * again. Waking up is either achieved using the RTC wake-up alarm for a full
+ * suspend cycle or a kernel timer for a suspend test cycle. The kernel timer
+ * delay for a test cycle can be configured by the suspend.pm_test_delay
+ * kernel parameter (5 sec by default).
+ *
+ * #SUSPEND_TEST_NONE specifies a full suspend cycle.
+ * The #SUSPEND_TEST_FREEZER..#SUSPEND_TEST_CORE test points can make it
+ * possible to collect error logs in case a full suspend cycle would prevent
+ * this by hanging the machine, or they can provide an idea of the faulty
+ * component by comparing fail/no-fail results at different test points.
+ *
+ * This is very handy for implementing any kind of suspend/resume test.
+ */
+void igt_system_suspend_autoresume(enum igt_suspend_state state,
+ enum igt_suspend_test test)
+{
+ int power_dir;
+ enum igt_suspend_test orig_test;
+ enum igt_mem_sleep orig_mem_sleep = MEM_SLEEP_NONE;
+
+ igt_require((power_dir = open("/sys/power", O_RDONLY)) >= 0);
+ igt_require(is_state_supported(power_dir, state));
+ igt_require(test == SUSPEND_TEST_NONE ||
+ faccessat(power_dir, "pm_test", R_OK | W_OK, 0) == 0);
+
+ igt_skip_on_f(state == SUSPEND_STATE_DISK &&
+ !igt_get_total_swap_mb(),
+ "Suspend to disk requires swap space.\n");
+
+ orig_test = get_suspend_test(power_dir);
+
+ if (state == SUSPEND_STATE_S3) {
+ orig_mem_sleep = get_mem_sleep();
+ igt_skip_on_f(!is_mem_sleep_state_supported(power_dir, MEM_SLEEP_DEEP),
+ "S3 not supported in this system.\n");
+ set_mem_sleep(power_dir, MEM_SLEEP_DEEP);
+ igt_skip_on_f(get_mem_sleep() != MEM_SLEEP_DEEP,
+ "S3 not possible in this system.\n");
+ }
+
+ set_suspend_test(power_dir, test);
+
+ if (test == SUSPEND_TEST_NONE)
+ suspend_via_rtcwake(state);
+ else
+ suspend_via_sysfs(power_dir, state);
+
+ if (orig_mem_sleep)
+ set_mem_sleep(power_dir, orig_mem_sleep);
+
+ set_suspend_test(power_dir, orig_test);
+ close(power_dir);
+}
+
+static int original_autoresume_delay;
+
+static void igt_restore_autoresume_delay(int sig)
+{
+ int delay_fd;
+ char delay_str[10];
+
+ igt_require((delay_fd = open("/sys/module/suspend/parameters/pm_test_delay",
+ O_WRONLY)) >= 0);
+
+ snprintf(delay_str, sizeof(delay_str), "%d", original_autoresume_delay);
+ igt_require(write(delay_fd, delay_str, strlen(delay_str)));
+
+ close(delay_fd);
+}
+
+/**
+ * igt_set_autoresume_delay:
+ * @delay_secs: The delay in seconds before resuming the system
+ *
+ * Sets how long we wait to resume the system after suspending it, using the
+ * suspend.pm_test_delay variable. On exit, the original delay value is
+ * restored.
+ */
+void igt_set_autoresume_delay(int delay_secs)
+{
+ int delay_fd;
+ char delay_str[10];
+
+ delay_fd = open("/sys/module/suspend/parameters/pm_test_delay", O_RDWR);
+
+ if (delay_fd >= 0) {
+ if (!original_autoresume_delay) {
+ igt_require(read(delay_fd, delay_str,
+ sizeof(delay_str)));
+ original_autoresume_delay = atoi(delay_str);
+ igt_install_exit_handler(igt_restore_autoresume_delay);
+ }
+
+ snprintf(delay_str, sizeof(delay_str), "%d", delay_secs);
+ igt_require(write(delay_fd, delay_str, strlen(delay_str)));
+
+ close(delay_fd);
+ }
+
+ autoresume_delay = delay_secs;
+}
+
+/**
+ * igt_get_autoresume_delay:
+ * @state: an #igt_suspend_state, the target suspend state
+ *
+ * Retrieves how long we wait to resume the system after suspending it.
+ * This can either be set through igt_set_autoresume_delay or be a default
+ * value that depends on the suspend state.
+ *
+ * Returns: The autoresume delay, in seconds.
+ */
+int igt_get_autoresume_delay(enum igt_suspend_state state)
+{
+ int delay;
+
+ if (autoresume_delay)
+ delay = autoresume_delay;
+ else
+ delay = state == SUSPEND_STATE_DISK ? 30 : 15;
+
+ return delay;
+}
+
+/**
+ * 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_eq(getuid(), 0);
+
+ igt_assert_eq(setgroups(0, NULL), 0);
+ igt_assert_eq(setgid(2), 0);
+ igt_assert_eq(setuid(2), 0);
+
+ igt_assert_eq(getgroups(0, NULL), 0);
+ igt_assert_eq(getgid(), 2);
+ igt_assert_eq(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=$var variable. Multiple keys
+ * can be specified as a comma-separated list or alternatively "all" if a wait
+ * should happen for all cases. Calling this function with "all" will assert.
+ *
+ * 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)) {
+ errno = 0; /* otherwise would be either ENOTTY or EBADF */
+ return;
+ }
+
+ if (!igt_interactive_debug)
+ return;
+
+ if (strstr(var, "all"))
+ igt_assert_f(false, "Bug in test: Do not call igt_debug_wait_for_keypress with \"all\"\n");
+
+ 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 );
+}
+
+/**
+ * igt_debug_interactive_mode_check:
+ * @var: var lookup to to enable this wait
+ * @expected: message to be printed as expected behaviour before wait for keys Y/n
+ *
+ * Waits for a key press when run interactively and when the corresponding debug
+ * var is set in the --interactive-debug=$var variable. Multiple vars
+ * can be specified as a comma-separated list or alternatively "all" if a wait
+ * should happen for all cases.
+ *
+ * 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.
+ *
+ * When not connected to a terminal interactive_debug is ignored
+ * and execution immediately continues. For this reason by default this function
+ * returns true. It returns false only when N/n is pressed indicating the
+ * user isn't seeing what was expected.
+ *
+ * Force test fail when N/n is pressed.
+ */
+void igt_debug_interactive_mode_check(const char *var, const char *expected)
+{
+ struct termios oldt, newt;
+ char key;
+
+ if (!isatty(STDIN_FILENO)) {
+ errno = 0; /* otherwise would be either ENOTTY or EBADF */
+ return;
+ }
+
+ if (!igt_interactive_debug)
+ return;
+
+ if (!strstr(igt_interactive_debug, var) &&
+ !strstr(igt_interactive_debug, "all"))
+ return;
+
+ igt_info("Is %s [Y/n]", expected);
+
+ tcgetattr ( STDIN_FILENO, &oldt );
+ newt = oldt;
+ newt.c_lflag &= ~ICANON;
+ tcsetattr ( STDIN_FILENO, TCSANOW, &newt );
+ key = getchar();
+ tcsetattr ( STDIN_FILENO, TCSANOW, &oldt );
+
+ igt_info("\n");
+
+ igt_assert(key != 'n' && key != 'N');
+}
+
+/**
+ * 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 malloc %zdMiB for locking.\n", size);
+
+ /* 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 mlock %zdMiB.\n", size);
+}
+
+/**
+ * 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;
+}
+
+/**
+ * igt_is_process_running:
+ * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
+ * chars)
+ *
+ * Returns: true in case the process has been found, false otherwise.
+ *
+ * This function checks in the process table for an entry with the name @comm.
+ */
+int igt_is_process_running(const char *comm)
+{
+ PROCTAB *proc;
+ proc_t *proc_info;
+ bool found = false;
+
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT);
+ igt_assert(proc != NULL);
+
+ while ((proc_info = readproc(proc, NULL))) {
+ if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
+ freeproc(proc_info);
+ found = true;
+ break;
+ }
+ freeproc(proc_info);
+ }
+
+ closeproc(proc);
+ return found;
+}
+
+/**
+ * igt_terminate_process:
+ * @sig: Signal to send
+ * @comm: Name of process in the form found in /proc/pid/comm (limited to 15
+ * chars)
+ *
+ * Returns: 0 in case the process is not found running or the signal has been
+ * sent successfully or -errno otherwise.
+ *
+ * This function sends the signal @sig for a process found in process table
+ * with name @comm.
+ */
+int igt_terminate_process(int sig, const char *comm)
+{
+ PROCTAB *proc;
+ proc_t *proc_info;
+ int err = 0;
+
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+ igt_assert(proc != NULL);
+
+ while ((proc_info = readproc(proc, NULL))) {
+ if (!strncasecmp(proc_info->cmd, comm, sizeof(proc_info->cmd))) {
+
+ if (kill(proc_info->tid, sig) < 0)
+ err = -errno;
+
+ freeproc(proc_info);
+ break;
+ }
+ freeproc(proc_info);
+ }
+
+ closeproc(proc);
+ return err;
+}
+
+struct pinfo {
+ pid_t pid;
+ const char *comm;
+ const char *fn;
+};
+
+static void
+__igt_show_stat(struct pinfo *info)
+{
+ const char *comm, *fn;
+ const char *type = "";
+ struct stat st;
+
+ pid_t pid = info->pid;
+ igt_assert((comm = info->comm));
+ igt_assert((fn = info->fn));
+
+ if (lstat(fn, &st) == -1)
+ return;
+
+ igt_info("%20.20s ", comm);
+ igt_info("%10d ", pid);
+
+ switch (st.st_mode & S_IFMT) {
+ case S_IFBLK:
+ type = "block";
+ break;
+ case S_IFCHR:
+ type = "character";
+ break;
+ case S_IFDIR:
+ type = "directory";
+ break;
+ case S_IFIFO:
+ type = "FIFO/pipe";
+ break;
+ case S_IFLNK:
+ type = "symlink";
+ break;
+ case S_IFREG:
+ type = "file";
+ break;
+ case S_IFSOCK:
+ type = "socket";
+ break;
+ default:
+ type = "unknown?";
+ break;
+ }
+ igt_info("%20.20s ", type);
+
+ igt_info("%10ld%10ld ", (long) st.st_uid, (long) st.st_gid);
+
+ igt_info("%15lld bytes ", (long long) st.st_size);
+ igt_info("%30.30s", fn);
+ igt_info("\n");
+}
+
+
+static void
+igt_show_stat_header(void)
+{
+ igt_info("%20.20s%11.11s%21.21s%11.11s%10.10s%22.22s%31.31s\n",
+ "COMM", "PID", "Type", "UID", "GID", "Size", "Filename");
+}
+
+static void
+igt_show_stat(proc_t *info, int *state, const char *fn)
+{
+ struct pinfo p = { .pid = info->tid, .comm = info->cmd, .fn = fn };
+
+ if (!*state)
+ igt_show_stat_header();
+
+ __igt_show_stat(&p);
+ ++*state;
+}
+
+static void
+__igt_lsof_fds(proc_t *proc_info, int *state, char *proc_path, const char *dir)
+{
+ struct dirent *d;
+ struct stat st;
+ char path[PATH_MAX];
+ char *fd_lnk;
+
+ /* default fds or kernel threads */
+ const char *default_fds[] = { "/dev/pts", "/dev/null" };
+
+ DIR *dp = opendir(proc_path);
+ igt_assert(dp);
+again:
+ while ((d = readdir(dp))) {
+ char *copy_fd_lnk;
+ char *dirn;
+
+ unsigned int i;
+ ssize_t read;
+
+ if (*d->d_name == '.')
+ continue;
+
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "%s/%s", proc_path, d->d_name);
+
+ if (lstat(path, &st) == -1)
+ continue;
+
+ fd_lnk = malloc(st.st_size + 1);
+
+ igt_assert((read = readlink(path, fd_lnk, st.st_size + 1)));
+ fd_lnk[read] = '\0';
+
+ for (i = 0; i < ARRAY_SIZE(default_fds); ++i) {
+ if (!strncmp(default_fds[i],
+ fd_lnk,
+ strlen(default_fds[i]))) {
+ free(fd_lnk);
+ goto again;
+ }
+ }
+
+ copy_fd_lnk = strdup(fd_lnk);
+ dirn = dirname(copy_fd_lnk);
+
+ if (!strncmp(dir, dirn, strlen(dir)))
+ igt_show_stat(proc_info, state, fd_lnk);
+
+ free(copy_fd_lnk);
+ free(fd_lnk);
+ }
+
+ closedir(dp);
+}
+
+/*
+ * This functions verifies, for each process running on the machine, if the
+ * current working directory or the fds matches the one supplied in dir.
+ */
+static void
+__igt_lsof(const char *dir)
+{
+ PROCTAB *proc;
+ proc_t *proc_info;
+
+ char path[30];
+ char *name_lnk;
+ struct stat st;
+ int state = 0;
+
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+ igt_assert(proc != NULL);
+
+ while ((proc_info = readproc(proc, NULL))) {
+ ssize_t read;
+
+ /* check current working directory */
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "/proc/%d/cwd", proc_info->tid);
+
+ if (stat(path, &st) == -1)
+ continue;
+
+ name_lnk = malloc(st.st_size + 1);
+
+ igt_assert((read = readlink(path, name_lnk, st.st_size + 1)));
+ name_lnk[read] = '\0';
+
+ if (!strncmp(dir, name_lnk, strlen(dir)))
+ igt_show_stat(proc_info, &state, name_lnk);
+
+ /* check also fd, seems that lsof(8) doesn't look here */
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "/proc/%d/fd", proc_info->tid);
+
+ __igt_lsof_fds(proc_info, &state, path, dir);
+
+ free(name_lnk);
+ freeproc(proc_info);
+ }
+
+ closeproc(proc);
+}
+
+/**
+ * igt_lsof: Lists information about files opened by processes.
+ * @dpath: Path to look under. A valid directory is required.
+ *
+ * This function mimics (a restrictive form of) lsof(8), but also shows
+ * information about opened fds.
+ */
+void
+igt_lsof(const char *dpath)
+{
+ struct stat st;
+ size_t len = strlen(dpath);
+ char *sanitized;
+
+ if (stat(dpath, &st) == -1)
+ return;
+
+ if (!S_ISDIR(st.st_mode)) {
+ igt_warn("%s not a directory!\n", dpath);
+ return;
+ }
+
+ sanitized = strdup(dpath);
+ /* remove last '/' so matching is easier */
+ if (len > 1 && dpath[len - 1] == '/')
+ sanitized[len - 1] = '\0';
+
+ __igt_lsof(sanitized);
+
+ free(sanitized);
+}
+
+static void pulseaudio_unload_module(proc_t *proc_info)
+{
+ struct igt_helper_process pa_proc = {};
+ char xdg_dir[PATH_MAX];
+ const char *homedir;
+ struct passwd *pw;
+
+ igt_fork_helper(&pa_proc) {
+ pw = getpwuid(proc_info->euid);
+ homedir = pw->pw_dir;
+ snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", proc_info->euid);
+
+ igt_info("Request pulseaudio to stop using audio device\n");
+
+ setgid(proc_info->egid);
+ setuid(proc_info->euid);
+ clearenv();
+ setenv("HOME", homedir, 1);
+ setenv("XDG_RUNTIME_DIR",xdg_dir, 1);
+
+ system("for i in $(pacmd list-sources|grep module:|cut -d : -f 2); do pactl unload-module $i; done");
+ }
+ igt_wait_helper(&pa_proc);
+}
+
+static int pipewire_pulse_pid = 0;
+static int pipewire_pw_reserve_pid = 0;
+static struct igt_helper_process pw_reserve_proc = {};
+
+static void pipewire_reserve_wait(void)
+{
+ char xdg_dir[PATH_MAX];
+ const char *homedir;
+ struct passwd *pw;
+ proc_t *proc_info;
+ PROCTAB *proc;
+
+ igt_fork_helper(&pw_reserve_proc) {
+ igt_info("Preventing pipewire-pulse to use the audio drivers\n");
+
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+ igt_assert(proc != NULL);
+
+ while ((proc_info = readproc(proc, NULL))) {
+ if (pipewire_pulse_pid == proc_info->tid)
+ break;
+ freeproc(proc_info);
+ }
+ closeproc(proc);
+
+ /* Sanity check: if it can't find the process, it means it has gone */
+ if (pipewire_pulse_pid != proc_info->tid)
+ exit(0);
+
+ pw = getpwuid(proc_info->euid);
+ homedir = pw->pw_dir;
+ snprintf(xdg_dir, sizeof(xdg_dir), "/run/user/%d", proc_info->euid);
+ setgid(proc_info->egid);
+ setuid(proc_info->euid);
+ clearenv();
+ setenv("HOME", homedir, 1);
+ setenv("XDG_RUNTIME_DIR",xdg_dir, 1);
+ freeproc(proc_info);
+
+ /*
+ * pw-reserve will run in background. It will only exit when
+ * igt_kill_children() is called later on. So, it shouldn't
+ * call igt_waitchildren(). Instead, just exit with the return
+ * code from pw-reserve.
+ */
+ exit(system("pw-reserve -n Audio0 -r"));
+ }
+}
+
+/* Maximum time waiting for pw-reserve to start running */
+#define PIPEWIRE_RESERVE_MAX_TIME 1000 /* milisseconds */
+
+int pipewire_pulse_start_reserve(void)
+{
+ bool is_pw_reserve_running = false;
+ proc_t *proc_info;
+ int attempts = 0;
+ PROCTAB *proc;
+
+ if (!pipewire_pulse_pid)
+ return 0;
+
+ pipewire_reserve_wait();
+
+ /*
+ * Note: using pw-reserve to stop using audio only works with
+ * pipewire version 0.3.50 or upper.
+ */
+ for (attempts = 0; attempts < PIPEWIRE_RESERVE_MAX_TIME; attempts++) {
+ usleep(1000);
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+ igt_assert(proc != NULL);
+
+ while ((proc_info = readproc(proc, NULL))) {
+ if (!strcmp(proc_info->cmd, "pw-reserve")) {
+ is_pw_reserve_running = true;
+ pipewire_pw_reserve_pid = proc_info->tid;
+ freeproc(proc_info);
+ break;
+ }
+ freeproc(proc_info);
+ }
+ closeproc(proc);
+ if (is_pw_reserve_running)
+ break;
+ }
+ if (!is_pw_reserve_running) {
+ igt_warn("Failed to remove audio drivers from pipewire\n");
+ return 1;
+ }
+ /* Let's grant some time for pw_reserve to notify pipewire via D-BUS */
+ usleep(50000);
+
+ /*
+ * pw-reserve is running, and should have stopped using the audio
+ * drivers. We can now remove the driver.
+ */
+
+ return 0;
+}
+
+void pipewire_pulse_stop_reserve(void)
+{
+ if (!pipewire_pulse_pid)
+ return;
+
+ igt_stop_helper(&pw_reserve_proc);
+}
+
+/**
+ * __igt_lsof_audio_and_kill_proc() - check if a given process is using an
+ * audio device. If so, stop or prevent them to use such devices.
+ *
+ * @proc_info: process struct, as returned by readproc()
+ * @proc_path: path of the process under procfs
+ * @pipewire_pulse_pid: PID of pipewire-pulse process
+ *
+ * No processes can be using an audio device by the time it gets removed.
+ * This function checks if a process is using an audio device from /dev/snd.
+ * If so, it will check:
+ * - if the process is pulseaudio, it can't be killed, as systemd will
+ * respawn it. So, instead, send a request for it to stop bind the
+ * audio devices.
+ * - if the process is pipewire-pulse, it can't be killed, as systemd will
+ * respawn it. So, instead, the caller should call pw-reserve, remove
+ * the kernel driver and then stop pw-reserve. On such case, this
+ * function returns the PID of pipewire-pulse, but won't touch it.
+ * If the check fails, it means that the process can simply be killed.
+ */
+static int
+__igt_lsof_audio_and_kill_proc(proc_t *proc_info, char *proc_path)
+{
+ const char *audio_dev = "/dev/snd/";
+ char path[PATH_MAX * 2];
+ struct dirent *d;
+ struct stat st;
+ char *fd_lnk;
+ int fail = 0;
+ ssize_t read;
+ DIR *dp;
+
+ /*
+ * Terminating pipewire-pulse require an special procedure, which
+ * is only available at version 0.3.50 and upper. Just trying to
+ * kill pipewire will start a race between IGT and systemd. If IGT
+ * wins, the audio driver will be unloaded before systemd tries to
+ * reload it, but if systemd wins, the audio device will be re-opened
+ * and used before IGT has a chance to remove the audio driver.
+ * Pipewire version 0.3.50 should bring a standard way:
+ *
+ * 1) start a thread running:
+ * pw-reserve -n Audio0 -r
+ * 2) unload/unbind the the audio driver(s);
+ * 3) stop the pw-reserve thread.
+ */
+ if (!strcmp(proc_info->cmd, "pipewire-pulse")) {
+ igt_info("process %d (%s) is using audio device. Should be requested to stop using them.\n",
+ proc_info->tid, proc_info->cmd);
+ pipewire_pulse_pid = proc_info->tid;
+ return 0;
+ }
+ /*
+ * pipewire-pulse itself doesn't hook into a /dev/snd device. Instead,
+ * the actual binding happens at the Pipewire Session Manager, e.g.
+ * either wireplumber or pipewire media-session.
+ *
+ * Just killing such processes won't produce any effect, as systemd
+ * will respawn them. So, just ignore here, they'll honor pw-reserve,
+ * when the time comes.
+ */
+ if (!strcmp(proc_info->cmd, "pipewire-media-session"))
+ return 0;
+ if (!strcmp(proc_info->cmd, "wireplumber"))
+ return 0;
+
+ dp = opendir(proc_path);
+ if (!dp && errno == ENOENT)
+ return 0;
+ igt_assert(dp);
+
+ while ((d = readdir(dp))) {
+ if (*d->d_name == '.')
+ continue;
+
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "%s/%s", proc_path, d->d_name);
+
+ if (lstat(path, &st) == -1)
+ continue;
+
+ fd_lnk = malloc(st.st_size + 1);
+
+ igt_assert((read = readlink(path, fd_lnk, st.st_size + 1)));
+ fd_lnk[read] = '\0';
+
+ if (strncmp(audio_dev, fd_lnk, strlen(audio_dev))) {
+ free(fd_lnk);
+ continue;
+ }
+
+ free(fd_lnk);
+
+ /*
+ * In order to avoid racing against pa/systemd, ensure that
+ * pulseaudio will close all audio files. This should be
+ * enough to unbind audio modules and won't cause race issues
+ * with systemd trying to reload it.
+ */
+ if (!strcmp(proc_info->cmd, "pulseaudio")) {
+ pulseaudio_unload_module(proc_info);
+ break;
+ }
+
+ /* For all other processes, just kill them */
+ igt_info("process %d (%s) is using audio device. Should be terminated.\n",
+ proc_info->tid, proc_info->cmd);
+
+ if (kill(proc_info->tid, SIGTERM) < 0) {
+ igt_info("Fail to terminate %s (pid: %d) with SIGTERM\n",
+ proc_info->cmd, proc_info->tid);
+ if (kill(proc_info->tid, SIGABRT) < 0) {
+ fail++;
+ igt_info("Fail to terminate %s (pid: %d) with SIGABRT\n",
+ proc_info->cmd, proc_info->tid);
+ }
+ }
+
+ break;
+ }
+
+ closedir(dp);
+ return fail;
+}
+
+/*
+ * This function identifies each process running on the machine that is
+ * opening an audio device and tries to stop it.
+ *
+ * Special care should be taken with pipewire and pipewire-pulse, as those
+ * daemons are respanned if they got killed.
+ */
+int
+igt_lsof_kill_audio_processes(void)
+{
+ char path[PATH_MAX];
+ proc_t *proc_info;
+ PROCTAB *proc;
+ int fail = 0;
+
+ proc = openproc(PROC_FILLCOM | PROC_FILLSTAT | PROC_FILLARG);
+ igt_assert(proc != NULL);
+ pipewire_pulse_pid = 0;
+
+ while ((proc_info = readproc(proc, NULL))) {
+ if (snprintf(path, sizeof(path), "/proc/%d/fd", proc_info->tid) < 1)
+ fail++;
+ else
+ fail += __igt_lsof_audio_and_kill_proc(proc_info, path);
+
+ freeproc(proc_info);
+ }
+ closeproc(proc);
+
+ return fail;
+}
+
+static struct igt_siglatency {
+ timer_t timer;
+ struct timespec target;
+ struct sigaction oldact;
+ struct igt_mean mean;
+
+ int sig;
+} igt_siglatency;
+
+static long delay(void)
+{
+ return hars_petruska_f54_1_random_unsafe() % (NSEC_PER_SEC / 1000);
+}
+
+static double elapsed(const struct timespec *now, const struct timespec *last)
+{
+ double nsecs;
+
+ nsecs = now->tv_nsec - last ->tv_nsec;
+ nsecs += 1e9*(now->tv_sec - last->tv_sec);
+
+ return nsecs;
+}
+
+static void siglatency(int sig, siginfo_t *info, void *arg)
+{
+ struct itimerspec its;
+
+ clock_gettime(CLOCK_MONOTONIC, &its.it_value);
+ if (info)
+ igt_mean_add(&igt_siglatency.mean,
+ elapsed(&its.it_value, &igt_siglatency.target));
+ igt_siglatency.target = its.it_value;
+
+ its.it_value.tv_nsec += 100 * 1000;
+ its.it_value.tv_nsec += delay();
+ if (its.it_value.tv_nsec >= NSEC_PER_SEC) {
+ its.it_value.tv_nsec -= NSEC_PER_SEC;
+ its.it_value.tv_sec += 1;
+ }
+ its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
+ timer_settime(igt_siglatency.timer, TIMER_ABSTIME, &its, NULL);
+}
+
+void igt_start_siglatency(int sig)
+{
+ struct sigevent sev;
+ struct sigaction act;
+
+ if (sig <= 0)
+ sig = SIGRTMIN;
+
+ if (igt_siglatency.sig)
+ (void)igt_stop_siglatency(NULL);
+ igt_assert(igt_siglatency.sig == 0);
+ igt_siglatency.sig = sig;
+
+ memset(&sev, 0, sizeof(sev));
+ sev.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID;
+ sev.sigev_notify_thread_id = gettid();
+ sev.sigev_signo = sig;
+ timer_create(CLOCK_MONOTONIC, &sev, &igt_siglatency.timer);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_sigaction = siglatency;
+ sigaction(sig, &act, &igt_siglatency.oldact);
+
+ siglatency(sig, NULL, NULL);
+}
+
+double igt_stop_siglatency(struct igt_mean *result)
+{
+ double mean = igt_mean_get(&igt_siglatency.mean);
+
+ if (result)
+ *result = igt_siglatency.mean;
+
+ sigaction(igt_siglatency.sig, &igt_siglatency.oldact, NULL);
+ timer_delete(igt_siglatency.timer);
+ memset(&igt_siglatency, 0, sizeof(igt_siglatency));
+
+ return mean;
+}
+
+bool igt_allow_unlimited_files(void)
+{
+ struct rlimit rlim;
+ unsigned nofile_rlim = 1024*1024;
+
+ FILE *file = fopen("/proc/sys/fs/nr_open", "r");
+ if (file) {
+ igt_assert(fscanf(file, "%u", &nofile_rlim) == 1);
+ igt_info("System limit for open files is %u\n", nofile_rlim);
+ fclose(file);
+ }
+
+ if (getrlimit(RLIMIT_NOFILE, &rlim))
+ return false;
+
+ rlim.rlim_cur = nofile_rlim;
+ rlim.rlim_max = nofile_rlim;
+ return setrlimit(RLIMIT_NOFILE, &rlim) == 0;
+}
+
+/**
+ * vfs_file_max: report maximum number of files
+ *
+ * Get the global system-wide maximum of open files the kernel allows,
+ * by reading /proc/sys/fs/file-max. Fails the current subtest if
+ * reading the file fails, and returns a suitable best guess if it
+ * cannot be opened.
+ *
+ * Returns: System-wide maximum of open files, or a best effort guess.
+ */
+uint64_t vfs_file_max(void)
+{
+ static long long unsigned max;
+ if (max == 0) {
+ FILE *file = fopen("/proc/sys/fs/file-max", "r");
+ max = 80000;
+ if (file) {
+ igt_assert(fscanf(file, "%llu", &max) == 1);
+ fclose(file);
+ }
+ }
+ return max;
+}
+
+void *igt_memdup(const void *ptr, size_t len)
+{
+ void *dup;
+
+ dup = malloc(len);
+ if (dup)
+ memcpy(dup, ptr, len);
+
+ return dup;
+}
diff --git a/lib/meson.build b/lib/meson.build
index cef2d0ff..cb83ee99 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -103,7 +103,6 @@ lib_deps = [
libdrm,
libdw,
libkmod,
- libprocps,
libudev,
math,
pciaccess,
@@ -173,6 +172,12 @@ if chamelium.found()
lib_sources += 'monitor_edids/monitor_edids_helper.c'
endif
+if libprocps.found()
+ lib_deps += libprocps
+else
+ lib_deps += libproc2
+endif
+
if get_option('srcdir') != ''
srcdir = join_paths(get_option('srcdir'), 'tests')
else
diff --git a/lib/meson.build.orig b/lib/meson.build.orig
new file mode 100644
index 00000000..cef2d0ff
--- /dev/null
+++ b/lib/meson.build.orig
@@ -0,0 +1,358 @@
+lib_sources = [
+ 'drmtest.c',
+ 'huc_copy.c',
+ 'i915/gem.c',
+ 'i915/gem_context.c',
+ 'i915/gem_create.c',
+ 'i915/gem_engine_topology.c',
+ 'i915/gem_scheduler.c',
+ 'i915/gem_submission.c',
+ 'i915/gem_ring.c',
+ 'i915/gem_mman.c',
+ 'i915/gem_vm.c',
+ 'i915/intel_memory_region.c',
+ 'i915/intel_mocs.c',
+ 'i915/i915_blt.c',
+ 'i915/i915_crc.c',
+ 'igt_collection.c',
+ 'igt_color_encoding.c',
+ 'igt_crc.c',
+ 'igt_debugfs.c',
+ 'igt_device.c',
+ 'igt_device_scan.c',
+ 'igt_drm_fdinfo.c',
+ 'igt_aux.c',
+ 'igt_gt.c',
+ 'igt_halffloat.c',
+ 'igt_hwmon.c',
+ 'igt_io.c',
+ 'igt_matrix.c',
+ 'igt_os.c',
+ 'igt_params.c',
+ 'igt_perf.c',
+ 'igt_pipe_crc.c',
+ 'igt_power.c',
+ 'igt_primes.c',
+ 'igt_rand.c',
+ 'igt_stats.c',
+ 'igt_syncobj.c',
+ 'igt_sysfs.c',
+ 'igt_sysrq.c',
+ 'igt_taints.c',
+ 'igt_thread.c',
+ 'igt_types.c',
+ 'igt_vec.c',
+ 'igt_vgem.c',
+ 'igt_x86.c',
+ 'instdone.c',
+ 'intel_allocator.c',
+ 'intel_allocator_msgchannel.c',
+ 'intel_allocator_random.c',
+ 'intel_allocator_reloc.c',
+ 'intel_allocator_simple.c',
+ 'intel_batchbuffer.c',
+ 'intel_bufops.c',
+ 'intel_chipset.c',
+ 'intel_ctx.c',
+ 'intel_device_info.c',
+ 'intel_mmio.c',
+ 'ioctl_wrappers.c',
+ 'media_spin.c',
+ 'media_fill.c',
+ 'gpgpu_fill.c',
+ 'gpu_cmds.c',
+ 'rendercopy_i915.c',
+ 'rendercopy_i830.c',
+ 'rendercopy_gen4.c',
+ 'rendercopy_gen6.c',
+ 'rendercopy_gen7.c',
+ 'rendercopy_gen8.c',
+ 'rendercopy_gen9.c',
+ 'runnercomms.c',
+ 'sw_sync.c',
+ 'intel_aux_pgtable.c',
+ 'intel_reg_map.c',
+ 'intel_iosf.c',
+ 'igt_kms.c',
+ 'igt_fb.c',
+ 'igt_core.c',
+ 'igt_draw.c',
+ 'igt_list.c',
+ 'igt_map.c',
+ 'igt_pm.c',
+ 'igt_dummyload.c',
+ 'igt_store.c',
+ 'uwildmat/uwildmat.c',
+ 'igt_kmod.c',
+ 'igt_panfrost.c',
+ 'igt_v3d.c',
+ 'igt_vc4.c',
+ 'igt_psr.c',
+ 'igt_amd.c',
+ 'igt_edid.c',
+ 'igt_eld.c',
+ 'igt_infoframe.c',
+ 'veboxcopy_gen12.c',
+ 'igt_msm.c',
+]
+
+lib_deps = [
+ cairo,
+ glib,
+ libatomic,
+ libdrm,
+ libdw,
+ libkmod,
+ libprocps,
+ libudev,
+ math,
+ pciaccess,
+ pixman,
+ pthreads,
+ realtime,
+ zlib
+]
+
+if libdrm_intel.found()
+ lib_deps += libdrm_intel
+else
+ lib_sources += 'stubs/drm/intel_bufmgr.c'
+ inc = [ inc, include_directories('stubs/drm') ]
+endif
+
+if libdrm_nouveau.found()
+ lib_deps += libdrm_nouveau
+ lib_sources += [
+ 'igt_nouveau.c',
+ 'nouveau/cea0b5.c'
+ ]
+endif
+
+if libdrm_amdgpu.found()
+ lib_deps += libdrm_amdgpu
+ lib_sources += [
+ 'amdgpu/amd_memory.c',
+ 'amdgpu/amd_command_submission.c',
+ 'amdgpu/amd_compute.c',
+ 'amdgpu/amd_gfx.c',
+ 'amdgpu/amd_ip_blocks.c',
+ 'amdgpu/amd_shaders.c',
+ 'amdgpu/amd_gfx_v8_0.c',
+ 'amdgpu/amd_gfx_v9_0.c',
+ 'amdgpu/amd_dispatch_helpers.c',
+ 'amdgpu/amd_dispatch.c',
+ 'amdgpu/amd_deadlock_helpers.c',
+ 'amdgpu/amd_pci_unplug.c',
+ 'amdgpu/xalloc.h'
+ ]
+endif
+
+if libunwind.found()
+ lib_deps += libunwind
+else
+ inc = [ inc, include_directories('stubs/libunwind') ]
+endif
+
+if valgrind.found()
+ lib_deps += valgrind
+endif
+
+if gsl.found()
+ lib_deps += gsl
+ lib_sources += [ 'igt_frame.c', 'igt_audio.c' ]
+endif
+
+if alsa.found()
+ lib_deps += alsa
+ lib_sources += 'igt_alsa.c'
+endif
+
+if chamelium.found()
+ lib_deps += chamelium
+ lib_sources += [ 'igt_chamelium.c', 'igt_chamelium_stream.c' ]
+ lib_sources += 'monitor_edids/monitor_edids_helper.c'
+endif
+
+if get_option('srcdir') != ''
+ srcdir = join_paths(get_option('srcdir'), 'tests')
+else
+ srcdir = join_paths(meson.source_root(), 'tests')
+endif
+
+if get_option('version_hash') != ''
+ vcs_command = ['echo', get_option('version_hash')]
+else
+ vcs_command = [ 'git', 'log', '-n1', '--pretty=format:g%h' ]
+endif
+
+lib_version = vcs_tag(input : 'version.h.in', output : 'version.h',
+ fallback : 'NO-GIT',
+ command : vcs_command )
+
+lib_intermediates = []
+foreach f: lib_sources
+ name = f.underscorify()
+ lib = static_library('igt-' + name,
+ [ f, lib_version ],
+ include_directories: inc,
+ dependencies : lib_deps,
+ c_args : [
+ '-DIGT_DATADIR="@0@"'.format(join_paths(prefix, datadir)),
+ '-DIGT_SRCDIR="@0@"'.format(srcdir),
+ '-DIGT_LOG_DOMAIN="@0@"'.format(f.split('.')[0]),
+ ])
+
+ lib_intermediates += lib
+endforeach
+
+lib_igt_build = shared_library('igt',
+ ['dummy.c'],
+ link_whole: lib_intermediates,
+ dependencies: lib_deps,
+ install : true,
+ soversion : '0',
+)
+
+lib_igt = declare_dependency(link_with : lib_igt_build,
+ include_directories : inc)
+
+igt_deps = [ lib_igt ] + lib_deps
+
+lin_igt_chipset_build = static_library('igt_chipset',
+ ['intel_chipset.c',
+ 'intel_device_info.c'],
+ include_directories : inc)
+
+lib_igt_chipset = declare_dependency(link_with : lin_igt_chipset_build,
+ include_directories : inc)
+
+lib_igt_perf_build = static_library('igt_perf',
+ ['igt_perf.c'],
+ include_directories : inc)
+
+lib_igt_perf = declare_dependency(link_with : lib_igt_perf_build,
+ include_directories : inc)
+
+scan_dep = [
+ glib,
+ libudev,
+]
+
+lib_igt_device_scan_build = static_library('igt_device_scan',
+ ['igt_device_scan.c',
+ 'igt_list.c',
+ 'igt_tools_stub.c',
+ 'intel_device_info.c',
+ ],
+ dependencies : scan_dep,
+ include_directories : inc)
+
+lib_igt_device_scan = declare_dependency(link_with : lib_igt_device_scan_build,
+ include_directories : inc)
+
+lib_igt_drm_fdinfo_build = static_library('igt_drm_fdinfo',
+ ['igt_drm_fdinfo.c'],
+ include_directories : inc)
+
+lib_igt_drm_fdinfo = declare_dependency(link_with : lib_igt_drm_fdinfo_build,
+ include_directories : inc)
+i915_perf_files = [
+ 'igt_list.c',
+ 'i915/perf.c',
+ 'i915/perf_data_reader.c',
+]
+
+i915_perf_hardware = [
+ 'hsw',
+ 'bdw', 'chv',
+ 'sklgt2', 'sklgt3', 'sklgt4',
+ 'kblgt2', 'kblgt3',
+ 'cflgt2', 'cflgt3',
+ 'bxt', 'glk',
+ 'cnl',
+ 'icl', 'ehl',
+ 'tglgt1', 'tglgt2', 'rkl', 'dg1', 'adl',
+ 'acmgt1', 'acmgt2', 'acmgt3',
+]
+
+i915_xml_files = []
+foreach hw : i915_perf_hardware
+ i915_xml_files += files('i915/perf-configs/oa- at 0@.xml'.format(hw))
+endforeach
+
+i915_perf_files += custom_target(
+ 'i915-perf-equations',
+ input : [ 'i915/perf-configs/perf-equations-codegen.py' ] + i915_xml_files,
+ output : [ 'i915_perf_equations.c', 'i915_perf_equations.h' ],
+ command : [
+ python3, '@INPUT0@',
+ '--code', '@OUTPUT0@',
+ '--header', '@OUTPUT1@',
+ i915_xml_files,
+ ])
+
+foreach hw : i915_perf_hardware
+ i915_perf_files += custom_target(
+ 'i915-perf-registers- at 0@'.format(hw),
+ input : [ 'i915/perf-configs/perf-registers-codegen.py',
+ 'i915/perf-configs/oa- at 0@.xml'.format(hw) ],
+ output : [ 'i915_perf_registers_ at 0@.c'.format(hw),
+ 'i915_perf_registers_ at 0@.h'.format(hw), ],
+ command : [
+ python3, '@INPUT0@',
+ '--code', '@OUTPUT0@',
+ '--header', '@OUTPUT1@',
+ '--xml-file', '@INPUT1@'
+ ])
+ i915_perf_files += custom_target(
+ 'i915-perf-metrics- at 0@'.format(hw),
+ input : [ 'i915/perf-configs/perf-metricset-codegen.py',
+ 'i915/perf-configs/oa- at 0@.xml'.format(hw) ],
+ output : [ 'i915_perf_metrics_ at 0@.c'.format(hw),
+ 'i915_perf_metrics_ at 0@.h'.format(hw), ],
+ command : [
+ python3, '@INPUT0@',
+ '--code', '@OUTPUT0@',
+ '--header', '@OUTPUT1@',
+ '--equations-include', 'i915_perf_equations.h',
+ '--registers-include', 'i915_perf_registers_ at 0@.h'.format(hw),
+ '--xml-file', '@INPUT1@',
+ ])
+endforeach
+
+lib_igt_i915_perf_build = shared_library(
+ 'i915_perf',
+ i915_perf_files,
+ dependencies: lib_igt_chipset,
+ include_directories : inc,
+ install: true,
+ soversion: '1.5')
+
+lib_igt_i915_perf = declare_dependency(
+ link_with : lib_igt_i915_perf_build,
+ include_directories : inc)
+
+install_headers(
+ 'igt_list.h',
+ 'intel_chipset.h',
+ 'i915/perf.h',
+ 'i915/perf_data.h',
+ 'i915/perf_data_reader.h',
+ subdir : 'i915-perf'
+)
+
+pkgconf = configuration_data()
+
+pkgconf.set('prefix', get_option('prefix'))
+pkgconf.set('exec_prefix', '${prefix}')
+pkgconf.set('libdir', '${prefix}/@0@'.format(get_option('libdir')))
+pkgconf.set('includedir', '${prefix}/@0@'.format(get_option('includedir')))
+pkgconf.set('i915_perf_version', '1.5.1')
+
+configure_file(
+ input : 'i915-perf.pc.in',
+ output : 'i915-perf.pc',
+ configuration : pkgconf,
+ install_dir : pkgconfigdir)
+
+subdir('tests')
diff --git a/meson.build b/meson.build
index 3e937f5a..475680ac 100644
--- a/meson.build
+++ b/meson.build
@@ -121,7 +121,15 @@ build_info += 'With libdrm: ' + ','.join(libdrm_info)
pciaccess = dependency('pciaccess', version : '>=0.10')
libkmod = dependency('libkmod')
-libprocps = dependency('libprocps', required : true)
+libprocps = dependency('libprocps', required : false)
+libproc2 = dependency('libproc2', required : false)
+if libprocps.found()
+ config.set('HAVE_LIBPROCPS', 1)
+elif libproc2.found()
+ config.set('HAVE_LIBPROC2', 1)
+else
+ error('Either libprocps or libproc2 is required')
+endif
libunwind = dependency('libunwind', required : get_option('libunwind'))
build_info += 'With libunwind: @0@'.format(libunwind.found())
--
2.30.2
More information about the igt-dev
mailing list