This is a generic implementation of rtc_read_persistent_clock() and rtc_update_persistent_clock() suitable for platforms to be used for read_persistent_clock() and update_persistent_clock() calls. An RTC device selected by the user with the RTC_HCTOSYS_DEVICE option is used. As rtc_read_persistent_clock() is not available at the time timekeeping_init() is called, it will now be disabled if the class device is to be used as a reference. In this case rtc_hctosys(), already present, will be used to set up the system time at the late initcall time. This call has now been rewritten to make use of rtc_read_persistent_clock(). As rtc_set_mmss() used by rtc_update_persistent_clock() may sleep for some hardware, the call is now made from a work queue scheduled by the timer originally used for the entire function. However, following DavidW's suggestion, the mutex in rtc_set_mmss() is now obtained with mutex_trylock() a failure of which makes the function terminate, so that latency from the caller to the point where hardware is accessed is minimised. Signed-off-by: Maciej W. Rozycki <macro@xxxxxxxxxxxxxx> --- patch-2.6.26-rc1-20080505-rtc-persistent-clock-14 diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/drivers/rtc/Kconfig linux-2.6.26-rc1-20080505/drivers/rtc/Kconfig --- linux-2.6.26-rc1-20080505.macro/drivers/rtc/Kconfig 2008-05-05 02:55:40.000000000 +0000 +++ linux-2.6.26-rc1-20080505/drivers/rtc/Kconfig 2008-05-05 21:41:34.000000000 +0000 @@ -21,24 +21,30 @@ menuconfig RTC_CLASS if RTC_CLASS config RTC_HCTOSYS - bool "Set system time from RTC on startup and resume" + bool "Use time from RTC as a reference for the system time" depends on RTC_CLASS = y default y help - If you say yes here, the system time (wall clock) will be set using - the value read from a specified RTC device. This is useful to avoid - unnecessary fsck runs at boot time, and to network better. + If you say yes here, the specified RTC device will be used + as a reference to the system time (wall clock). The device + will be used to set the system time as required during + startup, suspend and, for platforms that support such usage, + for NTP timekeeping. + + This is useful to avoid unnecessary fsck runs at boot time, + and to network better. config RTC_HCTOSYS_DEVICE - string "RTC used to set the system time" + string "RTC used as a reference for the system time" depends on RTC_HCTOSYS = y default "rtc0" help - The RTC device that will be used to (re)initialize the system + The RTC device that will be used as a reference for the system clock, usually rtc0. Initialization is done when the system - starts up, and when it resumes from a low power state. This - device should record time in UTC, since the kernel won't do - timezone correction. + starts up, and when it resumes from a low power state. Also, + if supported by the platform, NTP timekeeping uses this device + to record the system time periodically. This device should + record time in UTC, since the kernel won't do timezone correction. The driver for this RTC device must be loaded before late_initcall functions run, so it must usually be statically linked. diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/drivers/rtc/hctosys.c linux-2.6.26-rc1-20080505/drivers/rtc/hctosys.c --- linux-2.6.26-rc1-20080505.macro/drivers/rtc/hctosys.c 2008-05-05 02:55:40.000000000 +0000 +++ linux-2.6.26-rc1-20080505/drivers/rtc/hctosys.c 2008-05-11 19:18:19.000000000 +0000 @@ -1,8 +1,9 @@ /* - * RTC subsystem, initialize system time on startup + * RTC subsystem, persistent clock and startup initialization support * * Copyright (C) 2005 Tower Technologies * Author: Alessandro Zummo <a.zummo@xxxxxxxxxxxx> + * Copyright (C) 2008 Maciej W. Rozycki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -22,46 +23,86 @@ * the best guess is to add 0.5s. */ -static int __init rtc_hctosys(void) +/* + * Note the unusual API: + * zero returned means a failure, anything else is seconds from epoch. + */ +unsigned long rtc_read_persistent_clock(void) { - int err; - struct rtc_time tm; struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + struct rtc_time tm; + unsigned long time = 0; if (rtc == NULL) { - printk("%s: unable to open rtc device (%s)\n", - __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); - return -ENODEV; + printk(KERN_ERR "hctosys: unable to open rtc device (%s)\n", + CONFIG_RTC_HCTOSYS_DEVICE); + goto out; + } + if (rtc_read_time(rtc, &tm) < 0) { + dev_err(rtc->dev.parent, + "hctosys: unable to read the hardware clock\n"); + goto out_close; } + if (rtc_valid_tm(&tm) < 0) { + dev_err(rtc->dev.parent, "hctosys: invalid date/time\n"); + goto out_close; + } + + rtc_tm_to_time(&tm, &time); + +out_close: + rtc_class_close(rtc); +out: + return time; +} + +int rtc_update_persistent_clock(struct timespec now) +{ + struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + int err; - err = rtc_read_time(rtc, &tm); - if (err == 0) { - err = rtc_valid_tm(&tm); - if (err == 0) { - struct timespec tv; - - tv.tv_nsec = NSEC_PER_SEC >> 1; - - rtc_tm_to_time(&tm, &tv.tv_sec); - - do_settimeofday(&tv); - - dev_info(rtc->dev.parent, - "setting system clock to " - "%d-%02d-%02d %02d:%02d:%02d UTC (%u)\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - (unsigned int) tv.tv_sec); - } - else + if (rtc == NULL) { + printk(KERN_ERR "hctosys: unable to open rtc device (%s)\n", + CONFIG_RTC_HCTOSYS_DEVICE); + err = -ENXIO; + goto out; + } + err = rtc_set_mmss(rtc, now.tv_sec); + if (err < 0) { + if (err != -EBUSY) dev_err(rtc->dev.parent, - "hctosys: invalid date/time\n"); + "hctosys: unable to set the hardware clock\n"); + goto out_close; } - else - dev_err(rtc->dev.parent, - "hctosys: unable to read the hardware clock\n"); + err = 0; + +out_close: rtc_class_close(rtc); +out: + return err; +} + +static int __init rtc_hctosys(void) +{ + struct rtc_time tm; + struct timespec tv; + unsigned long time; + + time = rtc_read_persistent_clock(); + if (!time) + return -ENODEV; + + tv.tv_nsec = NSEC_PER_SEC >> 1; + tv.tv_sec = time; + do_settimeofday(&tv); + + rtc_time_to_tm(time, &tm); + pr_info("%s: setting system clock to " + "%d-%02d-%02d %02d:%02d:%02d UTC (%lu)\n", + CONFIG_RTC_HCTOSYS_DEVICE, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, (unsigned long)tv.tv_sec); return 0; } diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/drivers/rtc/interface.c linux-2.6.26-rc1-20080505/drivers/rtc/interface.c --- linux-2.6.26-rc1-20080505.macro/drivers/rtc/interface.c 2008-05-05 02:55:40.000000000 +0000 +++ linux-2.6.26-rc1-20080505/drivers/rtc/interface.c 2008-05-11 19:32:40.000000000 +0000 @@ -64,8 +64,7 @@ int rtc_set_mmss(struct rtc_device *rtc, { int err; - err = mutex_lock_interruptible(&rtc->ops_lock); - if (err) + if (!mutex_trylock(&rtc->ops_lock)) return -EBUSY; if (!rtc->ops) diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/include/linux/rtc.h linux-2.6.26-rc1-20080505/include/linux/rtc.h --- linux-2.6.26-rc1-20080505.macro/include/linux/rtc.h 2008-05-05 02:55:59.000000000 +0000 +++ linux-2.6.26-rc1-20080505/include/linux/rtc.h 2008-05-05 21:10:50.000000000 +0000 @@ -200,6 +200,9 @@ extern int rtc_irq_set_state(struct rtc_ extern int rtc_irq_set_freq(struct rtc_device *rtc, struct rtc_task *task, int freq); +extern unsigned long rtc_read_persistent_clock(void); +extern int rtc_update_persistent_clock(struct timespec now); + typedef struct rtc_task { void (*func)(void *private_data); void *private_data; diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/kernel/time/ntp.c linux-2.6.26-rc1-20080505/kernel/time/ntp.c --- linux-2.6.26-rc1-20080505.macro/kernel/time/ntp.c 2008-05-05 02:56:03.000000000 +0000 +++ linux-2.6.26-rc1-20080505/kernel/time/ntp.c 2008-05-05 21:10:50.000000000 +0000 @@ -3,6 +3,8 @@ * * NTP state machine interfaces and logic. * + * Copyright (c) 2008 Maciej W. Rozycki + * * This code was mainly moved from kernel/timer.c and kernel/time.c * Please see those files for relevant copyright info and historical * changelogs. @@ -17,6 +19,7 @@ #include <linux/capability.h> #include <linux/math64.h> #include <linux/clocksource.h> +#include <linux/workqueue.h> #include <asm/timex.h> /* @@ -218,11 +221,13 @@ void second_overflow(void) /* Disable the cmos update - used by virtualization and embedded */ int no_sync_cmos_clock __read_mostly; -static void sync_cmos_clock(unsigned long dummy); +static void sync_cmos_clock(unsigned long data); +static void do_sync_cmos_clock(struct work_struct *work); static DEFINE_TIMER(sync_cmos_timer, sync_cmos_clock, 0, 0); +static DECLARE_WORK(sync_cmos_work, do_sync_cmos_clock); -static void sync_cmos_clock(unsigned long dummy) +static void do_sync_cmos_clock(struct work_struct *work) { struct timespec now, next; int fail = 1; @@ -261,6 +266,12 @@ static void sync_cmos_clock(unsigned lon mod_timer(&sync_cmos_timer, jiffies + timespec_to_jiffies(&next)); } +static void sync_cmos_clock(unsigned long data) +{ + /* Some implementations of update_persistent_clock() may sleep. */ + schedule_work(&sync_cmos_work); +} + static void notify_cmos_timer(void) { if (!no_sync_cmos_clock) diff -up --recursive --new-file linux-2.6.26-rc1-20080505.macro/kernel/time/timekeeping.c linux-2.6.26-rc1-20080505/kernel/time/timekeeping.c --- linux-2.6.26-rc1-20080505.macro/kernel/time/timekeeping.c 2008-05-05 02:56:03.000000000 +0000 +++ linux-2.6.26-rc1-20080505/kernel/time/timekeeping.c 2008-05-05 21:10:50.000000000 +0000 @@ -242,7 +242,11 @@ unsigned long __attribute__((weak)) read void __init timekeeping_init(void) { unsigned long flags; - unsigned long sec = read_persistent_clock(); + unsigned long sec = 0; + +#ifndef CONFIG_RTC_HCTOSYS + sec = read_persistent_clock(); +#endif write_seqlock_irqsave(&xtime_lock, flags);