Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 14 Jan 2018 17:01:38 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r327971 - in head/sys: kern sys
Message-ID:  <201801141701.w0EH1cIb047803@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Sun Jan 14 17:01:37 2018
New Revision: 327971
URL: https://svnweb.freebsd.org/changeset/base/327971

Log:
  Add RTC clock conversions for BCD values, with non-panic validation.
  
  RTC clock hardware frequently uses BCD numbers.  Currently the low-level
  bcd2bin() and bin2bcd() functions will KASSERT if given out-of-range BCD
  values.  Every RTC driver must implement its own code for validating the
  unreliable data coming from the hardware to avoid a potential kernel panic.
  
  This change introduces two new functions, clock_bcd_to_ts() and
  clock_ts_to_bcd().  The former validates its inputs and returns EINVAL if any
  values are out of range. The latter guarantees the returned data will be
  valid BCD in a known format (4-digit years, etc).
  
  A new bcd_clocktime structure is used with the new functions.  It is similar
  to the original clocktime structure, but defines the fields holding BCD
  values as uint8_t (uint16_t for year), and adds a PM flag for handling hours
  using AM/PM mode.
  
  PR:		224813
  Differential Revision:	https://reviews.freebsd.org/D13730 (no reviewers)

Modified:
  head/sys/kern/subr_clock.c
  head/sys/sys/clock.h

Modified: head/sys/kern/subr_clock.c
==============================================================================
--- head/sys/kern/subr_clock.c	Sun Jan 14 16:55:14 2018	(r327970)
+++ head/sys/kern/subr_clock.c	Sun Jan 14 17:01:37 2018	(r327971)
@@ -199,6 +199,55 @@ clock_ct_to_ts(struct clocktime *ct, struct timespec *
 	return (0);
 }
 
+int
+clock_bcd_to_ts(struct bcd_clocktime *bct, struct timespec *ts, bool ampm)
+{
+	struct clocktime ct;
+	int bcent, byear;
+
+	/*
+	 * Year may come in as 2-digit or 4-digit BCD.  Split the value into
+	 * separate BCD century and year values for validation and conversion.
+	 */
+	bcent = bct->year >> 8;
+	byear = bct->year & 0xff;
+
+	/*
+	 * Ensure that all values are valid BCD numbers, to avoid assertions in
+	 * the BCD-to-binary conversion routines.  clock_ct_to_ts() will further
+	 * validate the field ranges (such as 0 <= min <= 59) during conversion.
+	 */
+	if (!validbcd(bcent) || !validbcd(byear) || !validbcd(bct->mon) ||
+	    !validbcd(bct->day) || !validbcd(bct->hour) ||
+	    !validbcd(bct->min) || !validbcd(bct->sec)) {
+		if (ct_debug)
+			printf("clock_bcd_to_ts: bad BCD: "
+			    "[%04x-%02x-%02x %02x:%02x:%02x]\n",
+			    bct->year, bct->mon, bct->day,
+			    bct->hour, bct->min, bct->sec);
+		return (EINVAL);
+	}
+
+	ct.year = FROMBCD(byear) + FROMBCD(bcent) * 100;
+	ct.mon  = FROMBCD(bct->mon);
+	ct.day  = FROMBCD(bct->day);
+	ct.hour = FROMBCD(bct->hour);
+	ct.min  = FROMBCD(bct->min);
+	ct.sec  = FROMBCD(bct->sec);
+	ct.dow  = bct->dow;
+	ct.nsec = bct->nsec;
+
+	/* If asked to handle am/pm, convert from 12hr+pmflag to 24hr. */
+	if (ampm) {
+		if (ct.hour == 12)
+			ct.hour = 0;
+		if (bct->ispm)
+			ct.hour += 12;
+	}
+
+	return (clock_ct_to_ts(&ct, ts));
+}
+
 void
 clock_ts_to_ct(struct timespec *ts, struct clocktime *ct)
 {
@@ -258,6 +307,34 @@ clock_ts_to_ct(struct timespec *ts, struct clocktime *
 	/* Not sure if this interface needs to handle leapseconds or not. */
 	KASSERT(ct->sec >= 0 && ct->sec <= 60,
 	    ("seconds %d not in 0-60", ct->sec));
+}
+
+void
+clock_ts_to_bcd(struct timespec *ts, struct bcd_clocktime *bct, bool ampm)
+{
+	struct clocktime ct;
+
+	clock_ts_to_ct(ts, &ct);
+
+	/* If asked to handle am/pm, convert from 24hr to 12hr+pmflag. */
+	bct->ispm = false;
+	if (ampm) {
+		if (ct.hour >= 12) {
+			ct.hour -= 12;
+			bct->ispm = true;
+		}
+		if (ct.hour == 0)
+			ct.hour = 12;
+	}
+
+	bct->year = TOBCD(ct.year % 100) | (TOBCD(ct.year / 100) << 8);
+	bct->mon  = TOBCD(ct.mon);
+	bct->day  = TOBCD(ct.day);
+	bct->hour = TOBCD(ct.hour);
+	bct->min  = TOBCD(ct.min);
+	bct->sec  = TOBCD(ct.sec);
+	bct->dow  = ct.dow;
+	bct->nsec = ct.nsec;
 }
 
 int

Modified: head/sys/sys/clock.h
==============================================================================
--- head/sys/sys/clock.h	Sun Jan 14 16:55:14 2018	(r327970)
+++ head/sys/sys/clock.h	Sun Jan 14 17:01:37 2018	(r327971)
@@ -60,9 +60,22 @@ extern int tz_dsttime;
 int utc_offset(void);
 
 /*
- * Structure to hold the values typically reported by time-of-day clocks.
- * This can be passed to the generic conversion functions to be converted
- * to a struct timespec.
+ * Structure to hold the values typically reported by time-of-day clocks,
+ * expressed as binary integers (see below for a BCD version).  This can be
+ * passed to the conversion functions to be converted to/from a struct timespec.
+ *
+ * On input, the year is interpreted as follows:
+ *       0 -   69 = 2000 - 2069
+ *      70 -   99 = 1970 - 1999
+ *     100 -  199 = 2000 - 2099 (Supports hardware "century bit".)
+ *     200 - 1969 = Invalid.
+ *    1970 - 9999 = Full 4-digit century+year.
+ *
+ * The dow field is ignored (not even validated) on input, but is always
+ * populated with day-of-week on output.
+ *
+ * clock_ct_to_ts() returns EINVAL if any values are out of range.  The year
+ * field will always be 4-digit on output.
  */
 struct clocktime {
 	int	year;			/* year (4 digit year) */
@@ -77,6 +90,43 @@ struct clocktime {
 
 int clock_ct_to_ts(struct clocktime *, struct timespec *);
 void clock_ts_to_ct(struct timespec *, struct clocktime *);
+
+/*
+ * Structure to hold the values typically reported by time-of-day clocks,
+ * expressed as BCD.  This can be passed to the conversion functions to be
+ * converted to/from a struct timespec.
+ *
+ * The clock_bcd_to_ts() function interprets the values in the year through sec
+ * fields as BCD numbers, and returns EINVAL if any BCD values are out of range.
+ * After conversion to binary, the values are passed to clock_ct_to_ts() and
+ * undergo further validation as described above.  Year may be 2 or 4-digit BCD,
+ * interpreted as described above.  The nsec field is binary.  If the ampm arg
+ * is true, the incoming hour and ispm values are interpreted as 12-hour am/pm
+ * representation of the hour, otherwise hour is interpreted as 24-hour and ispm
+ * is ignored.
+ *
+ * The clock_ts_to_bcd() function converts the timespec to BCD values stored
+ * into year through sec.  The value in year will be 4-digit BCD (e.g.,
+ * 0x2017). The mon through sec values will be 2-digit BCD.  The nsec field will
+ * be binary, and the range of dow makes its binary and BCD values identical.
+ * If the ampm arg is true, the hour and ispm fields are set to the 12-hour
+ * time plus a pm flag, otherwise the hour is set to 24-hour time and ispm is
+ * set to false.
+ */
+struct bcd_clocktime {
+	uint16_t year;			/* year (2 or 4 digit year) */
+	uint8_t  mon;			/* month (1 - 12) */
+	uint8_t  day;			/* day (1 - 31) */
+	uint8_t  hour;			/* hour (0 - 23 or 1 - 12) */
+	uint8_t  min;			/* minute (0 - 59) */
+	uint8_t  sec;			/* second (0 - 59) */
+	uint8_t  dow;			/* day of week (0 - 6; 0 = Sunday) */
+	long     nsec;			/* nanoseconds */
+	bool     ispm;			/* true if hour represents pm time */
+};
+
+int clock_bcd_to_ts(struct bcd_clocktime *, struct timespec *, bool ampm);
+void clock_ts_to_bcd(struct timespec *, struct bcd_clocktime *, bool ampm);
 
 /*
  * Time-of-day clock functions and flags.  These functions might sleep.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201801141701.w0EH1cIb047803>