From 40a5eabf88e45863ea45e4200cc3c7437bbb41c8 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Fri, 8 Apr 2022 12:19:57 +0200 Subject: [PATCH] pc: shadow schedule_timeout in intel_fb_drv Issue #4450 --- repos/dde_linux/src/include/lx_emul/time.h | 2 + repos/dde_linux/src/lib/lx_emul/clocksource.c | 109 ++++++++++++++++++ .../drivers/framebuffer/intel/pc/target.inc | 7 ++ .../drivers/framebuffer/intel/pc/timeout.c | 33 ++++++ 4 files changed, 151 insertions(+) create mode 100644 repos/pc/src/drivers/framebuffer/intel/pc/timeout.c diff --git a/repos/dde_linux/src/include/lx_emul/time.h b/repos/dde_linux/src/include/lx_emul/time.h index 7f560b75c0..924527685c 100644 --- a/repos/dde_linux/src/include/lx_emul/time.h +++ b/repos/dde_linux/src/include/lx_emul/time.h @@ -28,6 +28,8 @@ unsigned long long lx_emul_time_counter(void); void lx_emul_time_handle(void); +void lx_emul_force_jiffies_update(void); + #ifdef __cplusplus } #endif diff --git a/repos/dde_linux/src/lib/lx_emul/clocksource.c b/repos/dde_linux/src/lib/lx_emul/clocksource.c index fe6cbedcc2..b5bdd7de79 100644 --- a/repos/dde_linux/src/lib/lx_emul/clocksource.c +++ b/repos/dde_linux/src/lib/lx_emul/clocksource.c @@ -131,3 +131,112 @@ void lx_emul_register_of_clk_initcall(char const *compat, void *fn) count++; } + + +/* + * The functions lx_emul_force_jiffies_update, lx_clockevents_program_event + * and lx_clockevents_program_event implemented here are stripped down + * versions of the Linux internal time handling code, when clock is used in + * periodic mode. + * + * Normally time proceeds due to + * -> lx_emul/shadow/kernel/sched/core.c calls lx_emul_time_handle() + * -> repos/dde_linux/src/lib/lx_emul/clocksource.c:lx_emul_time_handle() calls + * -> dde_clock_event_device->event_handle() == tick_handle_periodic() + * -> kernel/time/tick-common.c -> tick_handle_periodic() + * -> kernel/time/clockevents.c -> clockevents_program_event() + * -> which fast forwards jiffies in a loop until it matches wall clock + * + * lx_emul_force_jiffies_update can be used to update jiffies to current, + * before invoking schedule_timeout(), which expects current jiffies values. + * Without current jiffies, the programmed timeouts are too short, which leads + * to timeouts firing too early. + */ + + +/* kernel/time/timekeeping.h */ +extern int timekeeping_valid_for_hres(void); +extern void do_timer(unsigned long ticks); + + +/** + * based on kernel/time/clockevents.c clockevents_program_event() + */ +static int lx_clockevents_program_event(struct clock_event_device *dev, + ktime_t expires) +{ + int64_t delta; + + if (WARN_ON_ONCE(expires < 0)) + return 0; + + dev->next_event = expires; + + if (clockevent_state_shutdown(dev)) + return 0; + + if (dev->features & CLOCK_EVT_FEAT_KTIME) + return 0; + + delta = ktime_to_ns(ktime_sub(expires, ktime_get())); + if (delta <= 0) { + int res = -ETIME; + return res; + } + + return 0; +} + + +/* + * private kernel/time header needed for internal functions + * used in lx_emul_force_jiffies_update + */ +#include <../kernel/time/tick-internal.h> + +/** + * based on kernel/time/tick-common.c tick_handle_periodic() + */ +void lx_emul_force_jiffies_update(void) +{ + struct clock_event_device *dev = dde_clock_event_device; + + ktime_t next = dev->next_event; + +#if defined(CONFIG_HIGH_RES_TIMERS) || defined(CONFIG_NO_HZ_COMMON) + /* + * The cpu might have transitioned to HIGHRES or NOHZ mode via + * update_process_times() -> run_local_timers() -> + * hrtimer_run_queues(). + */ + if (dev->event_handler != tick_handle_periodic) + return; +#endif + + if (!clockevent_state_oneshot(dev)) + return; + + for (;;) { + /* + * Setup the next period for devices, which do not have + * periodic mode: + */ + next = ktime_add_ns(next, TICK_NSEC); + + if (!lx_clockevents_program_event(dev, next)) + return; + + /* + * Have to be careful here. If we're in oneshot mode, + * before we call tick_periodic() in a loop, we need + * to be sure we're using a real hardware clocksource. + * Otherwise we could get trapped in an infinite + * loop, as the tick_periodic() increments jiffies, + * which then will increment time, possibly causing + * the loop to trigger again and again. + */ + if (timekeeping_valid_for_hres()) { + do_timer(1); /* tick_periodic(cpu); */ + } + } +} diff --git a/repos/pc/src/drivers/framebuffer/intel/pc/target.inc b/repos/pc/src/drivers/framebuffer/intel/pc/target.inc index ed74f5240e..0947a6fc36 100644 --- a/repos/pc/src/drivers/framebuffer/intel/pc/target.inc +++ b/repos/pc/src/drivers/framebuffer/intel/pc/target.inc @@ -18,6 +18,7 @@ SRC_C += $(notdir $(wildcard $(REL_PRG_DIR)/generated_dummies.c)) SRC_C += fb.c SRC_C += lx_user.c SRC_C += gem.c +SRC_C += timeout.c SRC_C += common_dummies.c SRC_C += lx_emul/spec/x86/pci.c SRC_C += lx_emul/shadow/mm/page_alloc.c @@ -38,6 +39,12 @@ INC_DIR += $(LX_SRC_DIR)/drivers/gpu/drm/i915 CC_C_OPT += -Wno-unused-label +# +# Original symbol is renamed to __real_* and references to original symbol +# are replaced by __wrap_*. Used to shadow schedule_timeout, see timeout.c +# +LD_OPT += --wrap=schedule_timeout + # # Genode C-API backends # diff --git a/repos/pc/src/drivers/framebuffer/intel/pc/timeout.c b/repos/pc/src/drivers/framebuffer/intel/pc/timeout.c new file mode 100644 index 0000000000..dec42f6995 --- /dev/null +++ b/repos/pc/src/drivers/framebuffer/intel/pc/timeout.c @@ -0,0 +1,33 @@ +/* + * \brief Wrapper to update jiffies before invoking schedule_timeout(). + * Schedule_timeout() expects that the jiffie value is current, + * in order to setup the timeouts. Without current jiffies, + * the programmed timeouts are too short, which leads to timeouts + * firing too early. The Intel driver uses this mechanism frequently + * by utilizing wait_queue_timeout*() in order to wait for hardware + * state changes, e.g. connectors hotplug. The schedule_timeout is + * shadowed by the Linker feature '--wrap'. This code can be removed + * as soon as the timeout handling is implemented by lx_emul/lx_kit + * instead of using the original Linux sources of kernel/time/timer.c. + * \author Alexander Boettcher + * \date 2022-04-04 + */ + +/* + * Copyright (C) 2022 Genode Labs GmbH + * + * This file is distributed under the terms of the GNU General Public License + * version 2. + */ + +#include + + +signed long __real_schedule_timeout(signed long timeout); + + +signed long __wrap_schedule_timeout(signed long timeout) +{ + lx_emul_force_jiffies_update(); + return __real_schedule_timeout(timeout); +}