lib/igt_aux: Divert ioctls for signal injection

To simplify and speed up running interruptible tests, use a custom
ioctl() function that control the signaling and detect when we need no
more iterations to trigger an interruption.

We use a realtime timer to inject the signal after a certain delay,
increasing the delay on every loop to try and exercise different code
paths within the function. The first delay is very short such that we
hopefully enter the kernel with a pending signal.

Clients should use

struct igt_sigiter iter = {};
while (igt_sigiter_repeat(&iter, enable_interrupts=true))
	do_test()

to automatically repeat the test until we can inject no more signals
into the ioctls. This is condensed into a macro

igt_interruptible(enable_interrupts=true)
	do_test();

for convenience.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
This commit is contained in:
Chris Wilson 2016-03-19 13:04:02 +00:00
parent c1fed522ae
commit d545610861
10 changed files with 217 additions and 7 deletions

View File

@ -3,7 +3,7 @@ include Makefile.sources
AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/lib
AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) $(CAIRO_CFLAGS) $(LIBUNWIND_CFLAGS)
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS) -lm
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS) -lm
benchmarks_LTLIBRARIES = gem_exec_tracer.la
gem_exec_tracer_la_LDFLAGS = -module -avoid-version -no-undefined

View File

@ -66,6 +66,20 @@ AC_CHECK_TYPES([sighandler_t],[],[],[AC_INCLUDES_DEFAULT
AC_CHECK_FUNCS([swapctl])
AC_CHECK_FUNCS([asprintf])
dnl Check for POSIX timers
AC_CHECK_FUNCS(timer_create, [], [
AC_CHECK_LIB(rt, timer_create, [
AC_DEFINE(HAVE_TIMER_CREATE, 1)
TIMER_LIBS="-lrt"
], [
AC_CHECK_LIB(pthread, timer_create, [
AC_DEFINE(HAVE_TIMER_CREATE, 1)
TIMER_LIBS="-lpthread"
])
])
])
AC_SUBST(TIMER_LIBS)
# Initialize libtool
AC_DISABLE_STATIC
AC_PROG_LIBTOOL

View File

@ -15,4 +15,4 @@ AM_CFLAGS = \
$(LIBUNWIND_CFLAGS) \
$(CWARNFLAGS)
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS)
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS)

View File

@ -4,4 +4,4 @@ bin_PROGRAMS = \
AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/lib
AM_CFLAGS = $(DRM_CFLAGS) $(PCIACCESS_CFLAGS) $(CWARNFLAGS) $(CAIRO_CFLAGS) $(LIBUNWIND_CFLAGS)
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS)
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS)

View File

@ -21,6 +21,6 @@ AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) $(LIBUNWIND_CFLAGS) $(DEBUG_CFLAGS) \
-DIGT_LOG_DOMAIN=\""$(subst _,-,$*)"\" \
-pthread
LDADD = $(CAIRO_LIBS) $(LIBUNWIND_LIBS) -lm
LDADD = $(CAIRO_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS) -lm
AM_CFLAGS += $(CAIRO_CFLAGS)

View File

@ -40,6 +40,7 @@
#include <signal.h>
#include <pciaccess.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
@ -73,6 +74,193 @@
/* signal interrupt helpers */
#define MSEC_PER_SEC (1000)
#define USEC_PER_SEC (1000*MSEC_PER_SEC)
#define NSEC_PER_SEC (1000*USEC_PER_SEC)
/* signal interrupt helpers */
#define gettid() syscall(__NR_gettid)
#define sigev_notify_thread_id _sigev_un._tid
static struct __igt_sigiter {
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));
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 sigevent sev;
struct sigaction act;
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(&act, 0, sizeof(act));
act.sa_sigaction = sigiter;
act.sa_flags = SA_SIGINFO;
igt_assert(sigaction(SIGRTMIN, &act, NULL) == 0);
__igt_sigiter.offset.tv_sec = 0;
__igt_sigiter.offset.tv_nsec = 50;
}
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;
}
/**
* igt_sigiter_continue:
* @iter: the control struct
* @enable: a boolean as to whether or not we want to enable interruptions
*
* Provides control flow such that all drmIoctl() (strictly igt_ioctl())
* within the loop are forcibly injected with signals (SIGRTMIN).
*
* This is useful to exercise ioctl error paths, at least where those can be
* exercises by interrupting blocking waits, like stalling for the gpu.
*
* igt_sigiter_continue() returns false when it has detected that it
* cannot inject any more signals in the ioctls from previous runs.
*
* Typical usage is
* struct igt_sigiter iter = {};
* while (igt_sigiter_continue(&iter, test_flags & TEST_INTERRUPTIBLE))
* do_test();
*
* This is condensed into the igt_interruptible() macro.
*
* Note that since this overloads the igt_ioctl(), this method is not useful
* for widespread signal injection, for example providing coverage of
* pagefaults. To interrupt everything, see igt_fork_signal_helper().
*/
bool igt_sigiter_continue(struct igt_sigiter *iter, bool enable)
{
if (iter->pass++ == 0)
return igt_sigiter_start(iter, enable);
if (__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;
static void __attribute__((noreturn)) signal_helper_process(pid_t pid)

View File

@ -40,6 +40,14 @@ extern int num_trash_bos;
void igt_fork_signal_helper(void);
void igt_stop_signal_helper(void);
struct igt_sigiter {
unsigned pass;
};
bool igt_sigiter_continue(struct igt_sigiter *iter, bool interrupt);
#define igt_interruptible(E) \
for (struct igt_sigiter iter__={}; igt_sigiter_continue(&iter__, (E)); )
void igt_exchange_int(void *array, unsigned i, unsigned j);
void igt_permute_array(void *array, unsigned size,
void (*exchange_func)(void *array,

View File

@ -13,7 +13,7 @@ AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) $(DEBUG_CFLAGS) \
-DIGT_DATADIR=\""$(abs_srcdir)"\" \
$(NULL)
LDADD = ../libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS)
LDADD = ../libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS)
LDADD += $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(GLIB_LIBS) -lm
AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS)

View File

@ -56,7 +56,7 @@ AM_CFLAGS = $(DRM_CFLAGS) $(CWARNFLAGS) $(DEBUG_CFLAGS)\
$(LIBUNWIND_CFLAGS) \
$(NULL)
LDADD = ../lib/libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS)
LDADD = ../lib/libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS)
LDADD += $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(GLIB_LIBS) -lm
AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS)

View File

@ -4,7 +4,7 @@ SUBDIRS = null_state_gen registers
AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/lib
AM_CFLAGS = $(DEBUG_CFLAGS) $(DRM_CFLAGS) $(PCIACCESS_CFLAGS) $(CWARNFLAGS) $(CAIRO_CFLAGS) $(LIBUNWIND_CFLAGS) -DPKGDATADIR=\"$(pkgdatadir)\"
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(LIBUNWIND_LIBS) -lm
LDADD = $(top_builddir)/lib/libintel_tools.la $(DRM_LIBS) $(PCIACCESS_LIBS) $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(LIBUNWIND_LIBS) $(TIMER_LIBS) -lm
AM_LDFLAGS = -Wl,--as-needed