]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - kernel/time/ntp.c
system timer: fix crash in <100Hz system timer
[linux-2.6-omap-h63xx.git] / kernel / time / ntp.c
index b5e352597cbb628091718d2ac486b59564a95cfc..e64efaf957e8485b4d233412a69fdf45a1225691 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <linux/mm.h>
 #include <linux/time.h>
+#include <linux/timer.h>
 #include <linux/timex.h>
 #include <linux/jiffies.h>
 #include <linux/hrtimer.h>
@@ -175,12 +176,64 @@ u64 current_tick_length(void)
        return tick_length;
 }
 
+#ifdef CONFIG_GENERIC_CMOS_UPDATE
 
-void __attribute__ ((weak)) notify_arch_cmos_timer(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 DEFINE_TIMER(sync_cmos_timer, sync_cmos_clock, 0, 0);
+
+static void sync_cmos_clock(unsigned long dummy)
 {
-       return;
+       struct timespec now, next;
+       int fail = 1;
+
+       /*
+        * If we have an externally synchronized Linux clock, then update
+        * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
+        * called as close as possible to 500 ms before the new second starts.
+        * This code is run on a timer.  If the clock is set, that timer
+        * may not expire at the correct time.  Thus, we adjust...
+        */
+       if (!ntp_synced())
+               /*
+                * Not synced, exit, do not restart a timer (if one is
+                * running, let it run out).
+                */
+               return;
+
+       getnstimeofday(&now);
+       if (abs(now.tv_nsec - (NSEC_PER_SEC / 2)) <= tick_nsec / 2)
+               fail = update_persistent_clock(now);
+
+       next.tv_nsec = (NSEC_PER_SEC / 2) - now.tv_nsec;
+       if (next.tv_nsec <= 0)
+               next.tv_nsec += NSEC_PER_SEC;
+
+       if (!fail)
+               next.tv_sec = 659;
+       else
+               next.tv_sec = 0;
+
+       if (next.tv_nsec >= NSEC_PER_SEC) {
+               next.tv_sec++;
+               next.tv_nsec -= NSEC_PER_SEC;
+       }
+       mod_timer(&sync_cmos_timer, jiffies + timespec_to_jiffies(&next));
+}
+
+static void notify_cmos_timer(void)
+{
+       if (!no_sync_cmos_clock)
+               mod_timer(&sync_cmos_timer, jiffies + 1);
 }
 
+#else
+static inline void notify_cmos_timer(void) { }
+#endif
+
 /* adjtimex mainly allows reading (and writing, if superuser) of
  * kernel time-keeping variables. used by xntpd.
  */
@@ -196,10 +249,12 @@ int do_adjtimex(struct timex *txc)
 
        /* Now we validate the data before disabling interrupts */
 
-       if ((txc->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT)
+       if ((txc->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT) {
          /* singleshot must not be used with any other mode bits */
-               if (txc->modes != ADJ_OFFSET_SINGLESHOT)
+               if (txc->modes != ADJ_OFFSET_SINGLESHOT &&
+                                       txc->modes != ADJ_OFFSET_SS_READ)
                        return -EINVAL;
+       }
 
        if (txc->modes != ADJ_OFFSET_SINGLESHOT && (txc->modes & ADJ_OFFSET))
          /* adjustment Offset limited to +- .512 seconds */
@@ -319,7 +374,8 @@ int do_adjtimex(struct timex *txc)
 leave: if ((time_status & (STA_UNSYNC|STA_CLOCKERR)) != 0)
                result = TIME_ERROR;
 
-       if ((txc->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT)
+       if ((txc->modes == ADJ_OFFSET_SINGLESHOT) ||
+                       (txc->modes == ADJ_OFFSET_SS_READ))
                txc->offset = save_adjust;
        else
                txc->offset = ((long)shift_right(time_offset, SHIFT_UPDATE)) *
@@ -345,6 +401,6 @@ leave:      if ((time_status & (STA_UNSYNC|STA_CLOCKERR)) != 0)
        txc->stbcnt        = 0;
        write_sequnlock_irq(&xtime_lock);
        do_gettimeofday(&txc->time);
-       notify_arch_cmos_timer();
+       notify_cmos_timer();
        return(result);
 }