Pass generic time-of-day information from Linux to higher domains From: Wolfgang Mauerer Introduce a mechanism to pass all information required to implement a gettimeofday() call with NTP corrections as delivered by the Linux domain to other domains. Essentially, this is an equivalent of the Linux vsyscall to perform this very action. We need to ensure that updates of the timing information are atomic wrt. non-Linux domains. While a simple solution would be to place an appropriate lock around the updates, this would eliminate the speed advantages of the kernel's seqlock. Therefore, we implement a transactional mechanism using two copies of the timing information that does not interfere with the updates of the kernel itself. The timing information is only updated from a single CPU in the Linux domain, and updates preempted by Xenomai are protected by the transactional mechanism. However, when a reader on another CPU runs in parallel with an update on CPU 0, an inconsistent state can arise. This is mended by using a sequence counter to ensure that the data structure was not modified during the read. Note that since updating the structure takes a constant amount of time that is much smaller than the interval between updates, the number of iterations in the retry loop (in case of modifications during the read) is bounded: TODO: Explain why this is the case --- include/linux/clocksource.h | 8 ++++++++ include/linux/ipipe_tickdev.h | 20 ++++++++++++++++++++ kernel/ipipe/core.c | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 0 deletions(-) diff --git a/include/linux/clocksource.h b/include/linux/clocksource.h index 64b1a4c..5ac07a7 100644 --- a/include/linux/clocksource.h +++ b/include/linux/clocksource.h @@ -281,12 +281,20 @@ extern void clocksource_resume(void); extern struct clocksource * __init __weak clocksource_default_clock(void); extern void clocksource_mark_unstable(struct clocksource *cs); +/* Including ipipe_tickdev.h that declares update_ipipe_gtod() + would lead to a cycle, so we declare update_ipipe_gtod() here */ +void update_ipipe_gtod(struct timespec *ts, struct clocksource *c); + #ifdef CONFIG_GENERIC_TIME_VSYSCALL extern void update_vsyscall(struct timespec *ts, struct clocksource *c); extern void update_vsyscall_tz(void); #else + static inline void update_vsyscall(struct timespec *ts, struct clocksource *c) { +#ifdef CONFIG_IPIPE + update_ipipe_gtod(ts, c); +#endif } static inline void update_vsyscall_tz(void) diff --git a/include/linux/ipipe_tickdev.h b/include/linux/ipipe_tickdev.h index 4a1cb1b..68d9100 100644 --- a/include/linux/ipipe_tickdev.h +++ b/include/linux/ipipe_tickdev.h @@ -25,6 +25,7 @@ #if defined(CONFIG_IPIPE) && defined(CONFIG_GENERIC_CLOCKEVENTS) #include +#include struct tick_device; @@ -44,6 +45,25 @@ struct ipipe_tick_device { int real_shift; }; +struct ipipe_gtod_data { + seqcount_t lock; + cycle_t (*vread)(void); + time_t wall_time_sec; + u32 wall_time_nsec; + struct timespec wall_to_monotonic; + cycle_t cycle_last; + cycle_t mask; + u32 mult; + u32 shift; +}; + +struct gtod_data_exchange { + int gtod_active; + struct ipipe_gtod_data gtod_data[2]; +}; + +void ipipe_set_gtod_data_exchange(struct gtod_data_exchange *exchg); + int ipipe_request_tickdev(const char *devname, void (*emumode)(enum clock_event_mode mode, struct clock_event_device *cdev), diff --git a/kernel/ipipe/core.c b/kernel/ipipe/core.c index 63deaf9..53d9654 100644 --- a/kernel/ipipe/core.c +++ b/kernel/ipipe/core.c @@ -48,6 +48,12 @@ static unsigned long __ipipe_domain_slot_map; struct ipipe_domain ipipe_root; +static struct gtod_data_exchange *gtod_data_exchange = NULL; + +void ipipe_set_gtod_data_exchange(struct gtod_data_exchange *exchg) { + gtod_data_exchange = exchg; +} + #ifndef CONFIG_SMP /* * Create an alias to the unique root status, so that arch-dep code @@ -203,6 +209,36 @@ void ipipe_release_tickdev(int cpu) ipipe_critical_exit(flags); } +#define UPDATE_GTOD_DATA(base) \ + base.vread = clock->vread; \ + base.cycle_last = clock->cycle_last; \ + base.mask = clock->mask; \ + base.mult = clock->mult; \ + base.shift = clock->shift; \ + base.wall_time_sec = wall_time->tv_sec; \ + base.wall_time_nsec = wall_time->tv_nsec; \ + base.wall_to_monotonic = wall_to_monotonic; + +void update_ipipe_gtod(struct timespec *wall_time, struct clocksource *clock) +{ + if (!gtod_data_exchange) + return; + + /* Activate second instance, update first */ + gtod_data_exchange->gtod_active = 1; + + write_seqcount_begin(>od_data_exchange->gtod_data[0].lock); + UPDATE_GTOD_DATA(gtod_data_exchange->gtod_data[0]); + write_seqcount_end(>od_data_exchange->gtod_data[0].lock); + + /* Activate first instance, update second */ + gtod_data_exchange->gtod_active = 0; + + write_seqcount_begin(>od_data_exchange->gtod_data[1].lock); + UPDATE_GTOD_DATA(gtod_data_exchange->gtod_data[1]); + write_seqcount_end(>od_data_exchange->gtod_data[1].lock); +}; + #endif /* CONFIG_GENERIC_CLOCKEVENTS */ void __init ipipe_init_early(void)