pc: add linux driver timer test

The test runs as lx_user task and uses several *delay and wait queue test
cases happened to be used in real ported linux drivers. The test shows
the time spent with several time sources, e.g. jiffies, rdtsc,
lx_time_counter_count etc.

Issue #4540
This commit is contained in:
Alexander Boettcher 2022-07-01 14:32:10 +02:00 committed by Christian Helmuth
parent 46b487c2f7
commit b03cb94b43
9 changed files with 891 additions and 0 deletions

View File

@ -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 {
<policy label_prefix="test-driver_time"> <device name="PS2"/> </policy>
}
}
#
# Generate config
#
append config {
<config verbose="yes" prio_levels="4">
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="TRACE"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="test-driver_time" priority="-1">
<resource name="RAM" quantum="2M"/>
</start>}
append_platform_drv_config
append_if $use_top config {
<start name="top">
<resource name="RAM" quantum="2M"/>
<config period_ms="40000"/>
</start>}
append config {
</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

View File

@ -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 <lx_emul/debug.h>
#include <linux/math64.h>
#include <linux/clocksource.h>
#include <linux/cpuhotplug.h>
#include <linux/kernel_stat.h>
#include <linux/kernfs.h>
#include <linux/kobject.h>
#include <linux/nls.h>
#include <linux/property.h>
#include <linux/random.h>
#include <linux/rcupdate.h>
#include <linux/sched/loadavg.h>
#include <linux/sched/signal.h>
#include <linux/syscore_ops.h>
#include <linux/timekeeper_internal.h>
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__);
}

View File

@ -0,0 +1,234 @@
/*
* \brief Dummy definitions of Linux Kernel functions
* \author Automatically generated file - do no edit
* \date 2022-05-06
*/
#include <lx_emul.h>
#include <linux/proc_fs.h>
void * PDE_DATA(const struct inode * inode)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/clk-provider.h>
const char * __clk_get_name(const struct clk * clk)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/printk.h>
int printk_deferred(const char * fmt,...)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/irq_work.h>
void irq_work_tick(void)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/printk.h>
asmlinkage __visible void dump_stack(void)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/sched.h>
int io_schedule_prepare(void)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/sched.h>
void io_schedule_finish(int token)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/sched.h>
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 <linux/kobject.h>
int kobject_synth_uevent(struct kobject * kobj,const char * buf,size_t count)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/srcutiny.h>
void synchronize_srcu(struct srcu_struct * ssp)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/srcu.h>
void __srcu_read_unlock(struct srcu_struct * ssp,int idx)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/kobject.h>
int add_uevent_var(struct kobj_uevent_env * env,const char * format,...)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/init.h>
bool initcall_debug;
#include <linux/irq.h>
struct irq_chip no_irq_chip;
#include <linux/kobject.h>
struct kobject *kernel_kobj;
#include <linux/fs.h>
void kill_anon_super(struct super_block * sb)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/property.h>
bool is_software_node(const struct fwnode_handle * fwnode)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/property.h>
void fwnode_remove_software_node(struct fwnode_handle * fwnode)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/sched/task.h>
void __put_task_struct(struct task_struct * tsk)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/kernel.h>
int get_option(char ** str,int * pint)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/sched/wake_q.h>
void wake_q_add_safe(struct wake_q_head * head,struct task_struct * task)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/capability.h>
bool file_ns_capable(const struct file * file,struct user_namespace * ns,int cap)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/seq_file.h>
void seq_printf(struct seq_file * m,const char * f,...)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/pseudo_fs.h>
struct pseudo_fs_context * init_pseudo(struct fs_context * fc,unsigned long magic)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/smp.h>
int smp_call_function_single(int cpu,void (* func)(void * info),void * info,int wait)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/srcutiny.h>
void srcu_drive_gp(struct work_struct * wp)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/uaccess.h>
unsigned long _copy_to_user(void __user * to,const void * from,unsigned long n)
{
lx_emul_trace_and_stop(__func__);
}
#include <linux/jump_label.h>
bool static_key_initialized;
#include <linux/string_helpers.h>
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__);
}

View File

@ -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

View File

@ -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 <linux/sched/task.h>
#include <linux/delay.h>
#include "i915_drv.h" /* test wait_for() macro */
#include <lx_emul/time.h>
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;
}

View File

@ -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 <base/attached_rom_dataspace.h>
#include <base/component.h>
/* emulation includes */
#include <lx_emul/init.h>
#include <lx_kit/env.h>
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();
}

View File

@ -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

View File

@ -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

View File

@ -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 <base/log.h>
#include <lx_kit/env.h>
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)) { ; }
}