diff --git a/repos/pc/run/driver_time.run b/repos/pc/run/driver_time.run
new file mode 100644
index 0000000000..edd4ad863b
--- /dev/null
+++ b/repos/pc/run/driver_time.run
@@ -0,0 +1,96 @@
+#
+# Build
+#
+
+if {[expr ![have_spec x86_64]]} {
+ puts "Run script is not supported on this platform."
+ exit 0
+}
+
+set use_top 0
+
+set build_components {
+ core init timer
+ test/driver_time
+}
+
+
+append_if $use_top build_components { app/top }
+
+source ${genode_dir}/repos/base/run/platform_drv.inc
+append_platform_drv_build_components
+
+build $build_components
+
+create_boot_directory
+
+# override default platform driver policy
+proc platform_drv_policy {} {
+ return {
+
+ }
+}
+
+#
+# Generate config
+#
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+append_platform_drv_config
+
+append_if $use_top config {
+
+
+
+ }
+
+append config {
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+# generic modules
+set boot_modules {
+ core ld.lib.so init timer
+ test-driver_time
+}
+
+append_if $use_top boot_modules { top }
+
+append_platform_drv_boot_modules
+
+build_boot_image $boot_modules
+
+append qemu_args "-nographic "
+
+run_genode_until forever
diff --git a/repos/pc/src/test/driver_time/dummies.c b/repos/pc/src/test/driver_time/dummies.c
new file mode 100644
index 0000000000..12522c1b78
--- /dev/null
+++ b/repos/pc/src/test/driver_time/dummies.c
@@ -0,0 +1,71 @@
+/*
+ * \brief Dummy definitions of Linux Kernel functions - handled manually
+ * \author Alexander Boettcher
+ * \date 2022-07-01
+ */
+
+/*
+ * Copyright (C) 2022 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+void calc_load_nohz_start(void)
+{
+ lx_emul_trace(__func__);
+}
+
+
+void calc_load_nohz_stop(void)
+{
+ lx_emul_trace(__func__);
+}
+
+
+void account_idle_ticks(unsigned long ticks)
+{
+ lx_emul_trace(__func__);
+}
+
+
+
+bool irq_work_needs_cpu(void)
+{
+ return false;
+}
+
+
+int ___ratelimit(struct ratelimit_state * rs, const char * func)
+{
+ /*
+ * from lib/ratelimit.c:
+ * " 0 means callbacks will be suppressed.
+ * 1 means go ahead and do it. "
+ */
+ lx_emul_trace(__func__);
+ return 1;
+}
+
+void register_syscore_ops(struct syscore_ops * ops)
+{
+ lx_emul_trace(__func__);
+}
diff --git a/repos/pc/src/test/driver_time/generated_dummies.c b/repos/pc/src/test/driver_time/generated_dummies.c
new file mode 100644
index 0000000000..ffd8e13205
--- /dev/null
+++ b/repos/pc/src/test/driver_time/generated_dummies.c
@@ -0,0 +1,234 @@
+/*
+ * \brief Dummy definitions of Linux Kernel functions
+ * \author Automatically generated file - do no edit
+ * \date 2022-05-06
+ */
+
+#include
+
+
+#include
+
+void * PDE_DATA(const struct inode * inode)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+const char * __clk_get_name(const struct clk * clk)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int printk_deferred(const char * fmt,...)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void irq_work_tick(void)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+asmlinkage __visible void dump_stack(void)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int io_schedule_prepare(void)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void io_schedule_finish(int token)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+long io_schedule_timeout(long timeout)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+extern void ack_bad_irq(unsigned int irq);
+void ack_bad_irq(unsigned int irq)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int kobject_synth_uevent(struct kobject * kobj,const char * buf,size_t count)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void synchronize_srcu(struct srcu_struct * ssp)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void __srcu_read_unlock(struct srcu_struct * ssp,int idx)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int add_uevent_var(struct kobj_uevent_env * env,const char * format,...)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+bool initcall_debug;
+
+
+#include
+
+struct irq_chip no_irq_chip;
+
+
+#include
+
+struct kobject *kernel_kobj;
+
+
+#include
+
+void kill_anon_super(struct super_block * sb)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+bool is_software_node(const struct fwnode_handle * fwnode)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void fwnode_remove_software_node(struct fwnode_handle * fwnode)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void __put_task_struct(struct task_struct * tsk)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int get_option(char ** str,int * pint)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void wake_q_add_safe(struct wake_q_head * head,struct task_struct * task)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+bool file_ns_capable(const struct file * file,struct user_namespace * ns,int cap)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void seq_printf(struct seq_file * m,const char * f,...)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+struct pseudo_fs_context * init_pseudo(struct fs_context * fc,unsigned long magic)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+int smp_call_function_single(int cpu,void (* func)(void * info),void * info,int wait)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+void srcu_drive_gp(struct work_struct * wp)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+unsigned long _copy_to_user(void __user * to,const void * from,unsigned long n)
+{
+ lx_emul_trace_and_stop(__func__);
+}
+
+
+#include
+
+bool static_key_initialized;
+
+
+#include
+
+int string_escape_mem(const char * src,size_t isz,char * dst,size_t osz,unsigned int flags,const char * only)
+{
+ lx_emul_trace_and_stop(__func__);
+}
diff --git a/repos/pc/src/test/driver_time/lx_emul.h b/repos/pc/src/test/driver_time/lx_emul.h
new file mode 100644
index 0000000000..acee561257
--- /dev/null
+++ b/repos/pc/src/test/driver_time/lx_emul.h
@@ -0,0 +1,23 @@
+/**
+ * \brief Dummy definitions of Linux Kernel functions
+ * \author Alexander Boettcher
+ * \date 2022-07-01
+ */
+
+/*
+ * Copyright (C) 2022 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void lx_emul_time_udelay(unsigned long usec);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/repos/pc/src/test/driver_time/lx_user.c b/repos/pc/src/test/driver_time/lx_user.c
new file mode 100644
index 0000000000..8f69ca0b3a
--- /dev/null
+++ b/repos/pc/src/test/driver_time/lx_user.c
@@ -0,0 +1,270 @@
+/*
+ * \brief Post kernel activity
+ * \author Alexander Boettcher
+ * \date 2022-07-01
+ */
+
+/*
+ * Copyright (C) 2022 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+
+#include
+
+#include
+
+#include "i915_drv.h" /* test wait_for() macro */
+
+#include
+
+
+extern uint64_t tsc_freq_khz;
+
+
+static int timing_tests(void *);
+
+
+void lx_user_init(void)
+{
+ kernel_thread(timing_tests, NULL, CLONE_FS | CLONE_FILES);
+}
+
+
+struct measure {
+ uint64_t start;
+ uint64_t end;
+ uint64_t diff;
+};
+
+#define test_timing(fn_test, fn_evaluation) \
+{ \
+ struct measure m_lxemul, m_jiffies, m_rdtsc; \
+ uint64_t jiffies_in_us; \
+\
+ m_lxemul.start = lx_emul_time_counter(); \
+ m_jiffies.start = jiffies_64; \
+ m_rdtsc.start = rdtsc(); \
+\
+ { \
+ fn_test \
+ } \
+\
+ m_rdtsc.end = rdtsc(); \
+ m_jiffies.end = jiffies_64; \
+ m_lxemul.end = lx_emul_time_counter(); \
+\
+ m_rdtsc.diff = m_rdtsc.end - m_rdtsc.start; \
+ m_lxemul.diff = m_lxemul.end - m_lxemul.start; \
+ m_jiffies.diff = m_jiffies.end - m_jiffies.start; \
+\
+ jiffies_in_us = (m_jiffies.diff) * (1000ull * 1000 / CONFIG_HZ); \
+\
+ { \
+ fn_evaluation \
+ } \
+\
+}
+
+#define test_timing_no_ret(text, fn_test) \
+{ \
+ test_timing( \
+ fn_test \
+ , \
+ if (rdtsc_freq_mhz) \
+ printk(text \
+ " %6llu:%10llu:%10llu:%10llu:%8lld\n", \
+ m_jiffies.diff, jiffies_in_us, m_lxemul.diff, \
+ m_rdtsc.diff / rdtsc_freq_mhz, \
+ jiffies_in_us - m_lxemul.diff); \
+ else \
+ printk(text \
+ " %6llu:%10llu:%10llu:%8lld\n", \
+ m_jiffies.diff, jiffies_in_us, m_lxemul.diff, \
+ jiffies_in_us - m_lxemul.diff); \
+ ); \
+ /* trigger to update jiffies to avoid printk time part of next test */ \
+ msleep(1); \
+}
+
+
+#define test_timing_with_ret(text, fn_test) \
+{ \
+ test_timing( \
+ fn_test \
+ , \
+ if (rdtsc_freq_mhz) \
+ printk(text \
+ " %6llu:%10llu:%10llu:%10llu:%8lld " \
+ "ret=%d%s\n", \
+ m_jiffies.diff, jiffies_in_us, m_lxemul.diff, \
+ m_rdtsc.diff / rdtsc_freq_mhz, \
+ jiffies_in_us - m_lxemul.diff, \
+ ret, ret == -ETIMEDOUT ? " (ETIMEDOUT)" : ""); \
+ else \
+ printk(text \
+ " %6llu:%10llu:%10llu:%8lld " \
+ "ret=%d%s\n", \
+ m_jiffies.diff, jiffies_in_us, m_lxemul.diff, \
+ jiffies_in_us - m_lxemul.diff, \
+ ret, ret == -ETIMEDOUT ? " (ETIMEDOUT)" : ""); \
+ ); \
+ /* trigger to update jiffies to avoid printk time part of next test */ \
+ msleep(1); \
+}
+
+
+static int timing_tests(void * data)
+{
+ DEFINE_WAIT(wait);
+ wait_queue_head_t wq;
+ int ret;
+ uint64_t const rdtsc_freq_mhz = tsc_freq_khz / 1000;
+
+ init_waitqueue_head(&wq);
+
+ while (true) {
+ if (rdtsc_freq_mhz)
+ printk("test(parameters) -> "
+ "jiffies:jiff_us:lx_time_us:rdtsc_us:diff_jiff_lx_time "
+ "tsc=%lluMhz\n", rdtsc_freq_mhz);
+ else
+ printk("test(parameters) -> "
+ "jiffies:jiff_us:lx_time_us:diff_jiff_lx_time\n");
+
+ test_timing_no_ret ("udelay(40) ->",
+ udelay(40);
+ );
+
+ test_timing_no_ret ("ndelay(4000) ->",
+ ndelay(4000);
+ );
+
+ test_timing_no_ret ("msleep(5000) ->",
+ msleep(5000);
+ );
+
+ test_timing_with_ret("wait_for(cond,10ms) A ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 10);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,5ms) B ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 5);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,2ms) C ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 2);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,10ms) D ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 10);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ /* do some work, so that jiffies becomes a bit outdated */
+ {
+ unsigned long long i = 0;
+ printk("cause some long running load in task ...\n");
+ for (i = 0; i < (1ull << 24); i++) {
+ asm volatile("pause":::"memory");
+ }
+ }
+
+ /* display driver test case -> waking up too early before irq triggered */
+ test_timing_with_ret("wait_for(cond,10ms) E ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 10);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,5ms) F ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 5);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,2ms) G ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 2);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,10ms) H ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 10);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,5000ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 5000);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,4000ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 4000);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,3000ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 3000);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,2000ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 2000);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,500ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 500);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,200ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 200);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,100ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 100);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ test_timing_with_ret("wait_for(cond,50ms) ->",
+ add_wait_queue(&wq, &wait);
+ ret = wait_for((0), 50);
+ remove_wait_queue(&wq, &wait);
+ );
+
+ /* audio driver test case -> sleeping too short or long is bad */
+ test_timing_no_ret ("usleep_range(20,21) ->",
+ usleep_range(20, 21);
+ );
+
+ test_timing_no_ret ("usleep_range(40,41) ->",
+ usleep_range(40, 41);
+ );
+
+ test_timing_no_ret ("usleep_range(400,410) ->",
+ usleep_range(400, 410);
+ );
+ }
+
+ return 0;
+}
diff --git a/repos/pc/src/test/driver_time/main.cc b/repos/pc/src/test/driver_time/main.cc
new file mode 100644
index 0000000000..3bcbee4c0b
--- /dev/null
+++ b/repos/pc/src/test/driver_time/main.cc
@@ -0,0 +1,67 @@
+/*
+ * \brief Linux test driver
+ * \author Alexander Boettcher
+ * \date 2022-07-01
+ */
+
+/*
+ * Copyright (C) 2022 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#include
+#include
+
+/* emulation includes */
+#include
+#include
+
+
+namespace Test {
+ using namespace Genode;
+ struct Driver;
+}
+
+
+unsigned long long tsc_freq_khz;
+
+
+struct Test::Driver
+{
+ Env &env;
+
+ Driver(Env &env) : env(env)
+ {
+ Lx_kit::initialize(env);
+
+ env.exec_static_constructors();
+
+ try {
+ Attached_rom_dataspace info(env, "platform_info");
+ tsc_freq_khz = info.xml().sub_node("hardware").sub_node("tsc")
+ .attribute_value("freq_khz", 0ULL);
+ } catch (...) { };
+ }
+
+ void start()
+ {
+ log("--- Test driver started ---");
+
+ lx_emul_start_kernel(nullptr);
+ }
+};
+
+
+static Test::Driver &driver(Genode::Env & env)
+{
+ static Test::Driver driver(env);
+ return driver;
+}
+
+
+void Component::construct(Genode::Env &env)
+{
+ driver(env).start();
+}
diff --git a/repos/pc/src/test/driver_time/source.list b/repos/pc/src/test/driver_time/source.list
new file mode 100644
index 0000000000..de86489245
--- /dev/null
+++ b/repos/pc/src/test/driver_time/source.list
@@ -0,0 +1,77 @@
+drivers/base/bus.c
+drivers/base/class.c
+drivers/base/component.c
+drivers/base/core.c
+drivers/base/dd.c
+drivers/base/devres.c
+drivers/base/driver.c
+drivers/base/platform.c
+drivers/base/property.c
+kernel/async.c
+kernel/irq/chip.c
+kernel/irq/devres.c
+kernel/irq/handle.c
+kernel/irq/irqdesc.c
+kernel/irq/irqdomain.c
+kernel/irq/manage.c
+kernel/irq/resend.c
+kernel/kthread.c
+kernel/locking/mutex.c
+kernel/locking/osq_lock.c
+kernel/locking/rtmutex.c
+kernel/locking/rwsem.c
+kernel/notifier.c
+kernel/panic.c
+kernel/resource.c
+kernel/sched/clock.c
+kernel/sched/completion.c
+kernel/sched/swait.c
+kernel/sched/wait.c
+kernel/sched/wait_bit.c
+kernel/smpboot.c
+kernel/time/clockevents.c
+kernel/time/clocksource.c
+kernel/time/hrtimer.c
+kernel/time/jiffies.c
+kernel/time/ntp.c
+kernel/time/tick-broadcast.c
+kernel/time/tick-common.c
+kernel/time/tick-oneshot.c
+kernel/time/tick-sched.c
+kernel/time/time.c
+kernel/time/timeconv.c
+kernel/time/timecounter.c
+kernel/time/timekeeping.c
+kernel/time/timer.c
+kernel/time/timer_list.c
+kernel/workqueue.c
+lib/bitmap.c
+lib/crc32.c
+lib/ctype.c
+lib/debug_locks.c
+lib/dec_and_lock.c
+lib/find_bit.c
+lib/hexdump.c
+lib/hweight.c
+lib/idr.c
+lib/iomap.c
+lib/irq_regs.c
+lib/kasprintf.c
+lib/klist.c
+lib/kobject.c
+lib/kstrtox.c
+lib/list_sort.c
+lib/llist.c
+lib/radix-tree.c
+lib/rbtree.c
+lib/refcount.c
+lib/scatterlist.c
+lib/siphash.c
+lib/sort.c
+lib/string.c
+lib/timerqueue.c
+lib/uuid.c
+lib/vsprintf.c
+lib/xarray.c
+mm/mempool.c
+mm/util.c
diff --git a/repos/pc/src/test/driver_time/target.mk b/repos/pc/src/test/driver_time/target.mk
new file mode 100644
index 0000000000..9f7387e135
--- /dev/null
+++ b/repos/pc/src/test/driver_time/target.mk
@@ -0,0 +1,28 @@
+REQUIRES := x86_64
+
+TARGET := test-driver_time
+LIBS := base pc_lx_emul jitterentropy
+
+SRC_CC += main.cc time.cc
+SRC_C += lx_user.c
+SRC_C += dummies.c
+SRC_C += generated_dummies.c
+
+SRC_C += lx_emul/common_dummies.c
+SRC_C += lx_emul/shadow/lib/kobject_uevent.c
+SRC_C += lx_emul/shadow/drivers/char/random.c
+SRC_C += lx_emul/shadow/kernel/softirq.c
+
+vpath %.c $(REP_DIR)/src/lib/pc
+vpath %.cc $(REP_DIR)/src/lib/pc
+
+LX_SRC_DIR := $(call select_from_ports,linux)/src/linux
+ifeq ($(wildcard $(LX_SRC_DIR)),)
+LX_SRC_DIR := $(call select_from_repositories,src/linux)
+endif
+ifeq ($(wildcard $(LX_SRC_DIR)),)
+fail
+endif
+
+INC_DIR += $(PRG_DIR)
+INC_DIR += $(LX_SRC_DIR)/drivers/gpu/drm/i915
diff --git a/repos/pc/src/test/driver_time/time.cc b/repos/pc/src/test/driver_time/time.cc
new file mode 100644
index 0000000000..0f8f40ad69
--- /dev/null
+++ b/repos/pc/src/test/driver_time/time.cc
@@ -0,0 +1,25 @@
+/*
+ * \brief Lx_emul udelay function for very short delays
+ * \author Stefan Kalkowski
+ * \date 2021-07-10
+ */
+
+/*
+ * Copyright (C) 2021 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#include
+#include
+
+extern "C" void lx_emul_time_udelay(unsigned long usec);
+extern "C" void lx_emul_time_udelay(unsigned long usec)
+{
+ if (usec > 100)
+ Genode::error("Cannot delay that long ", usec, " microseconds");
+
+ unsigned long long start = Lx_kit::env().timer.curr_time().trunc_to_plain_us().value;
+ while (Lx_kit::env().timer.curr_time().trunc_to_plain_us().value < (start + usec)) { ; }
+}