code sketch: updating RTC in 2.2.12

Ulrich Windl (wiu09524@pc3103.klinik.uni-regensburg.de)
Mon, 6 Dec 1999 10:26:39 +0100 (CET)


Hello,

recently there was some heaty discussion on kernel bloat and setting
the RTC (CMOS clock). I did think on it several days, but then I
implemented it:

The patch difference from below sketches the essential changes (i386
architecture). two new routines ktime_to_rtc() and rtc_to_ktime() do
the work, supported by sys_tz and rtc_runs_localtime (default: no).

These variables are settable directly via sysctl(), because we need
it. I don't like the sysctl implementation a lot, but it's a quick
patch anyway.

I think that pattern isn't much of a kernel bloat, and it fixes more
problems than it creates.

I know it will never go into the kernel, but it's there. For a
complete patch see linux.kernel.org in pub/linux/daemons/ntp/PPS for
PPSkit-0.8.1-0.9.0-cand1.diff.gz. Make sure to read the first 10 lines
of the patch...

Regards,
Ulrich

Index: linux/arch/i386/kernel/time.c
-diff -u linux/arch/i386/kernel/time.c:1.1.1.7 linux/arch/i386/kernel/time.c:1.1.1.7.6.4
+diff -u linux/arch/i386/kernel/time.c:1.1.1.7 linux/arch/i386/kernel/time.c:1.1.1.7.6.6
--- linux/arch/i386/kernel/time.c:1.1.1.7 Wed May 12 21:42:36 1999
-+++ linux/arch/i386/kernel/time.c Thu Oct 14 21:17:43 1999
++++ linux/arch/i386/kernel/time.c Sat Dec 4 21:30:51 1999
-@@ -377,6 +379,7 @@
+- * In order to set the CMOS clock precisely, set_rtc_mmss has to be
+- * called 500 ms after the second nowtime has started, because when
+- * nowtime is written into the registers of the CMOS clock, it will
++ * In order to set the CMOS clock precisely, update_rtc has to be
++ * called 500 ms after the current second has started, because when
++ * the time is written into the registers of the CMOS clock, it will
+ * jump to the next second precisely 500 ms later. Check the Motorola
+ * MC146818A or Dallas DS12887 data sheet for details.
+- *
+- * BUG: This routine does not handle hour overflow properly; it just
+- * sets the minutes. Usually you'll only notice that after reboot!
+ */
+-static int set_rtc_mmss(unsigned long nowtime)
++static void update_rtc(void)
+ {
+- int retval = 0;
+- int real_seconds, real_minutes, cmos_minutes;
++ int ss, mi, hh, dd, mo, yy;
+ unsigned char save_control, save_freq_select;
+
+- save_control = CMOS_READ(RTC_CONTROL); /* tell the clock it's being set */
++ /* tell the clock it's being set */
++ save_control = CMOS_READ(RTC_CONTROL);
+ CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);
+
+- save_freq_select = CMOS_READ(RTC_FREQ_SELECT); /* stop and reset prescaler */
++ /* stop and reset prescaler */
++ save_freq_select = CMOS_READ(RTC_FREQ_SELECT);
+ CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);
+
+- cmos_minutes = CMOS_READ(RTC_MINUTES);
++ mi = CMOS_READ(RTC_MINUTES);
+ if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+- BCD_TO_BIN(cmos_minutes);
++ BCD_TO_BIN(mi);
+
+- /*
+- * since we're only adjusting minutes and seconds,
+- * don't interfere with hour overflow. This avoids
+- * messing with unknown time zones but requires your
+- * RTC not to be off by more than 15 minutes
+- */
+- real_seconds = nowtime % 60;
+- real_minutes = nowtime / 60;
+- if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1)
+- real_minutes += 30; /* correct for half hour time zone */
+- real_minutes %= 60;
+-
+- if (abs(real_minutes - cmos_minutes) < 30) {
+- if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
+- BIN_TO_BCD(real_seconds);
+- BIN_TO_BCD(real_minutes);
+- }
+- CMOS_WRITE(real_seconds,RTC_SECONDS);
+- CMOS_WRITE(real_minutes,RTC_MINUTES);
+- } else {
+- printk(KERN_WARNING
+- "set_rtc_mmss: can't update from %d to %d\n",
+- cmos_minutes, real_minutes);
+- retval = -1;
++ ktime_to_rtc(xtime.tv_sec, &ss, &dd);
++ hh = ss >> 16;
++ mi = (ss >> 8) & 0xff;
++ ss &= 0xff;
++ yy = (dd >> 16) % 100;
++ mo = (dd >> 8) & 0xff;
++ dd &= 0xff;
++ printk(KERN_DEBUG "update_rtc: %d-%02d-%02d %02d:%02d:%02d\n",
++ yy, mo, dd, hh, mi, ss);
++
++ if (!(save_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
++ BIN_TO_BCD(ss);
++ BIN_TO_BCD(mi);
++ BIN_TO_BCD(hh);
++ BIN_TO_BCD(dd);
++ BIN_TO_BCD(mo);
++ BIN_TO_BCD(yy);
+ }
++ CMOS_WRITE(ss, RTC_SECONDS);
++ CMOS_WRITE(mi, RTC_MINUTES);
++ CMOS_WRITE(hh, RTC_HOURS);
++ CMOS_WRITE(dd, RTC_DAY_OF_MONTH);
++ CMOS_WRITE(mo, RTC_MONTH);
++ CMOS_WRITE(yy, RTC_YEAR);
+
+ /* The following flags have to be released exactly in this order,
+ * otherwise the DS12887 (popular MC146818A clone with integrated
+@@ -346,8 +353,6 @@
+ */
+ CMOS_WRITE(save_control, RTC_CONTROL);
+ CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
+-
+- return retval;
+ }
+
+ /* last time the cmos clock got updated */
+@@ -377,20 +382,21 @@
smp_local_timer_interrupt(regs);
#endif

+#ifdef CONFIG_NTP
/*
* If we have an externally synchronized Linux clock, then update
- * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
-@@ -384,13 +387,14 @@
+- * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
++ * CMOS clock accordingly every ~11 minutes. Update_rtc() has to be
+ * called as close as possible to 500 ms before the new second starts.
*/
- if ((time_status & STA_UNSYNC) == 0 &&
- xtime.tv_sec > last_rtc_update + 660 &&
+- if ((time_status & STA_UNSYNC) == 0 &&
+- xtime.tv_sec > last_rtc_update + 660 &&
- xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&
- xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
-+ xtime.tv_nsec >= NANOSECOND / 2 - ((unsigned long) time_tick) / 2 &&
-+ xtime.tv_nsec <= NANOSECOND / 2 + ((unsigned long) time_tick) / 2) {
- if (set_rtc_mmss(xtime.tv_sec) == 0)
- last_rtc_update = xtime.tv_sec;
+- if (set_rtc_mmss(xtime.tv_sec) == 0)
+- last_rtc_update = xtime.tv_sec;
- else
- last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
-+ else /* do it again in 10 minutes */
-+ last_rtc_update = xtime.tv_sec - 600;
++ if (rtc_update_slave &&
++ xtime.tv_sec > last_rtc_update + rtc_update_slave &&
++ xtime.tv_nsec >= NANOSECOND / 2 - ((unsigned long) time_tick) / 2 &&
++ xtime.tv_nsec <= NANOSECOND / 2 + ((unsigned long) time_tick) / 2) {
++ update_rtc();
++ last_rtc_update = xtime.tv_sec;
++ rtc_update_slave = 0;
}
+#endif

#ifdef CONFIG_MCA
if( MCA_bus ) {
@@ -1629,6 +1777,7 @@
- }
- if ((year += 1900) < 1970)
- year += 100;
+- return mktime(year, mon, day, hour, min, sec);
+ {
+ BCD_TO_BIN(sec);
+ BCD_TO_BIN(min);
@@ -1639,7 +1788,7 @@
+ }
+ if ((year += 1900) < 1970) /* Fix for year 2000 rollover */
+ year += 100; /* Doesn't the CMOS have a century? */
- return mktime(year, mon, day, hour, min, sec);
++ return rtc_to_ktime(year, mon, day, hour, min, sec);
}

static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
Index: linux/kernel/time.c
-diff -u linux/kernel/time.c:1.1.1.3 linux/kernel/time.c:1.1.1.3.14.6
+diff -u linux/kernel/time.c:1.1.1.3 linux/kernel/time.c:1.1.1.3.14.14
--- linux/kernel/time.c:1.1.1.3 Wed Mar 24 20:31:47 1999
-+++ linux/kernel/time.c Thu Oct 7 20:13:58 1999
-@@ -7,53 +7,457 @@
++++ linux/kernel/time.c Sat Dec 4 21:19:04 1999
@@ -6018,6 +6562,31 @@
+#define ADJ_SERVED_MODE_BITS (ADJ_TICKADJ|ADJ_TIMETICK|ADJ_ADJTIME)
+#endif
+
++/* Define entries for `time' subdirectory.
++ * WARNING: writable values are not checked for plausibility!
++ */
++ctl_table kern_time_table[] = {
++ {KERN_TIME_TIMEZONE, "timezone", &sys_tz, sizeof(sys_tz),
++ 0644, NULL, &proc_dointvec},
++ {KERN_TIME_RTC_UPDATE, "rtc_runs_localtime", &rtc_runs_localtime,
++ sizeof(rtc_runs_localtime),
++ 0644, NULL, &proc_dointvec},
++ {KERN_TIME_RTC_UPDATE, "rtc_update", &rtc_update, sizeof(rtc_update),
++ 0644, NULL, &proc_dointvec},
++ {KERN_TIME_TIME_TICK, "time_tick", &time_tick, sizeof(time_tick),
++ 0444, NULL, &proc_dointvec},
++ {KERN_TIME_TICKADJ, "tickadj", &tickadj, sizeof(tickadj),
++ 0444, NULL, &proc_dointvec},
++#ifdef CONFIG_NTP
++#ifdef CONFIG_NTP_PPS
++ {KERN_TIME_PPS_VAR, "pps", &pps, sizeof(pps),
++ 0444, NULL, &proc_dointvec},
++#endif
++#endif
++ {0}
++};
++
++
+/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
+ * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
+ * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
@@ -6051,7 +6620,61 @@
}

-void (*do_get_fast_time)(struct timeval *) = do_normal_gettime;

--
++/* Convert Gregorian calendar information from RTC to kernel time
++ * (uses mktime).
++ */
++time_t rtc_to_ktime(unsigned int year, unsigned int mon,
++		     unsigned int day, unsigned int hour,
++		     unsigned int min, unsigned int sec)
++{
++	time_t result = mktime(year, mon, day, hour, min, sec);
++	if (rtc_runs_localtime) {		/* apply effective timezone */
++		result += sys_tz.tz_minuteswest * 60;
++		if (sys_tz.tz_dsttime)
++			result -= 3600;
++	}
++	return result;
++}
+ 
++/* Build time and date components from system time, intended to set the RTC
++ * (based on code found in fs/fat/misc.c):
++ * ``*hhmmss = seconds + (minutes << 8) + (hour << 16)''
++ * ``*yyyymmdd = mday + (month << 8) + (year << 16)''
++ */
++void ktime_to_rtc(time_t t, int *hhmmss, int *yyyymmdd)
++{
++	/* Table to map days in year to months (non leap-years, zero-based) */
++	static int dmt[] = {
++		0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0,0,0,0
++	/*    Jan Feb Mar Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec */
++	};
++	int day, year, nl_day, month;
++	if (rtc_runs_localtime) {		/* apply effective timezone */
++		t -= sys_tz.tz_minuteswest * 60;
++		if (sys_tz.tz_dsttime)
++			t += 3600;
++	}
++	*hhmmss = (t % 60) + (((t / 60) % 60) << 8) + (((t / 3600) % 24) << 16);
++	day = t / 86400;		/* days since epoch (1970) */
++	year = day / 365;		/* non-leap years since epoch */
++	/* correct for leap years in epoch: 1972 (2) was the first */
++	if ((year + 2) / 4 + 365 * year > day)
++		year--;
++	/* reduce to day in current year */
++	day -= (year + 2) / 4 + 365 * year;
++	/* map March, 1st to Feburary, 29nd for leap years */
++	if (day == 59 && ((year + 2) & 3) == 0) {
++		nl_day = day;
++		month = 2;
++	} else {			/* determine month and day of month */
++		nl_day = ((year + 2) & 3) || day < 59 ? day : day - 1;
++		for (month = 0; month < 12; month++)
++			if (dmt[month] > nl_day)
++				break;
++	}
++	*yyyymmdd = nl_day - dmt[month - 1] + 1 + (month << 8) + ((year + 1970) << 16);
++}
++
  /*
 - * Generic way to access 'xtime' (the current time of day).
 - * This can be changed if the platform provides a more accurate (and fast!) 

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/