Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 24 Mar 2018 20:40:16 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org
Subject:   svn commit: r331496 - in stable/11/sys: dev/iicbus kern sys x86/isa
Message-ID:  <201803242040.w2OKeG3W048040@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Sat Mar 24 20:40:16 2018
New Revision: 331496
URL: https://svnweb.freebsd.org/changeset/base/331496

Log:
  MFC r306288, r314936, r325527, r327971, r328005, r328039, r328068-r328069,
      r328301-r328303
  
  r306288:
  Fix ds1307 probing
  
  'compat' can never be NULL, because the compatible check loop ends when
  compat->ocd_str is NULL.  This causes ds1307 to attach to any unclaimed i2c
  device.
  
  r314936:
  Validate values read from the RTC before trying BCD decoding
  
  Submitted by:	cem
  Reported by:	Michael Gmelin <freebsd@grem.de>
  Tested by:	Oleksandr Tymoshenko <gonzo@bluezbox.com>
  Sponsored by:	Dell EMC
  
  r325527:
  DS1307: Add the mcp7941x enable bit
  
  Summary:
  Existing code recognizes the mcp7941x RTC, but this RTC has an
  enable bit at the same location as the "Clock Halt" bit on the ds1307, with an
  opposite assertion (set == on, whereas CH set == clock stopped).  Thus the
  current code halts the clock, with no way to enable it.
  
  Reviewed By:	ian
  Differential Revision: https://reviews.freebsd.org/D12961
  
  r327971:
  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)
  
  r328005:
  Convert the x86 RTC driver to use new validated BCD<->timespec conversions.
  
  New common routines were added to kern/subr_clock.c for converting between
  calendrical time expressed in BCD and struct timespec. The new functions
  return EINVAL on error, as expected when the clock hardware does not provide
  valid time.
  
  PR:		224813
  Differential Revision:	https://reviews.freebsd.org/D13731 (no reviewers)
  
  r328039:
  Add static inline rtcin_locked() and rtcout_locked() functions for doing a
  related series of operations without doing a lock/unlock for each byte.
  Use them when reading and writing the entire set of time registers.
  
  The original rtcin() and writertc() functions which do lock/unlock on each
  byte still exist, because they are public and called by outside code.
  
  r328068:
  Move some code around and rename a couple variables; no functional changes.
  
  The static atrtc_set() function was called only from clock_settime(), so
  just move its contents entirely into clock_settime() and delete atrtc_set().
  
  Rename the struct bcd_clocktime variables from 'ct' to 'bct'.  I had
  originally wanted to emphasize how identical the clocktime and bcd_clocktime
  structs were, but things evolved to the point where the structs are not at
  all identical anymore, so now emphasizing the difference seems better.
  
  r328069:
  Remove redundant critical_enter/exit() calls.  The block of code delimited
  by these calls is now protected by a spin mutex (obscured within the
  RTC_LOCK/RTC_UNLOCK macros).
  
  Reported by:	bde@
  
  r328301:
  Switch to using the bcd_clocktime conversion functinos that validate the BCD
  data without panicking, and have common code for handling AM/PM mode.
  
  r328302:
  Switch to using the bcd_clocktime conversion functions that validate the BCD
  data without panicking, and have common code for handling AM/PM mode.
  
  r328303:
  Switch to using the bcd_clocktime conversion functions that validate the BCD
  data without panicking, and have common code for handling AM/PM mode.

Modified:
  stable/11/sys/dev/iicbus/ds1307.c
  stable/11/sys/dev/iicbus/ds1307reg.h
  stable/11/sys/dev/iicbus/ds13rtc.c
  stable/11/sys/dev/iicbus/nxprtc.c
  stable/11/sys/kern/subr_clock.c
  stable/11/sys/sys/clock.h
  stable/11/sys/x86/isa/atrtc.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/sys/dev/iicbus/ds1307.c
==============================================================================
--- stable/11/sys/dev/iicbus/ds1307.c	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/dev/iicbus/ds1307.c	Sat Mar 24 20:40:16 2018	(r331496)
@@ -217,7 +217,7 @@ ds1307_probe(device_t dev)
 
 	compat = ofw_bus_search_compatible(dev, ds1307_compat_data);
 
-	if (compat == NULL)
+	if (compat->ocd_str == NULL)
 		return (ENXIO);
 
 	device_set_desc(dev, (const char *)compat->ocd_data);
@@ -272,6 +272,7 @@ ds1307_start(void *xdev)
 	struct sysctl_oid *tree_node;
 	struct sysctl_oid_list *tree;
 	uint8_t secs;
+	uint8_t osc_en;
 
 	dev = (device_t)xdev;
 	sc = device_get_softc(dev);
@@ -286,7 +287,12 @@ ds1307_start(void *xdev)
 		device_printf(sc->sc_dev, "cannot read from RTC.\n");
 		return;
 	}
-	if ((secs & DS1307_SECS_CH) != 0) {
+	if (sc->sc_mcp7941x)
+		osc_en = 0x80;
+	else
+		osc_en = 0x00;
+
+	if (((secs & DS1307_SECS_CH) ^ osc_en) != 0) {
 		device_printf(sc->sc_dev,
 		    "WARNING: RTC clock stopped, check the battery.\n");
 	}
@@ -316,9 +322,9 @@ static int
 ds1307_gettime(device_t dev, struct timespec *ts)
 {
 	int error;
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct ds1307_softc *sc;
-	uint8_t data[7], hourmask;
+	uint8_t data[7], hourmask, st_mask;
 
 	sc = device_get_softc(dev);
 	error = iicdev_readfrom(sc->sc_dev, DS1307_SECS, data, sizeof(data),
@@ -329,7 +335,12 @@ ds1307_gettime(device_t dev, struct timespec *ts)
 	}
 
 	/* If the clock halted, we don't have good data. */
-	if (data[DS1307_SECS] & DS1307_SECS_CH)
+	if (sc->sc_mcp7941x)
+		st_mask = 0x80;
+	else
+		st_mask = 0x00;
+
+	if (((data[DS1307_SECS] & DS1307_SECS_CH) ^ st_mask) != 0)
 		return (EINVAL);
 
 	/* If chip is in AM/PM mode remember that. */
@@ -339,30 +350,24 @@ ds1307_gettime(device_t dev, struct timespec *ts)
 	} else
 		hourmask = DS1307_HOUR_MASK_24HR;
 
-	ct.nsec = 0;
-	ct.sec  = FROMBCD(data[DS1307_SECS]  & DS1307_SECS_MASK);
-	ct.min  = FROMBCD(data[DS1307_MINS]  & DS1307_MINS_MASK);
-	ct.hour = FROMBCD(data[DS1307_HOUR]  & hourmask);
-	ct.day  = FROMBCD(data[DS1307_DATE]  & DS1307_DATE_MASK);
-	ct.mon  = FROMBCD(data[DS1307_MONTH] & DS1307_MONTH_MASK);
-	ct.year = FROMBCD(data[DS1307_YEAR]  & DS1307_YEAR_MASK);
+	bct.nsec = 0;
+	bct.ispm = (data[DS1307_HOUR] & DS1307_HOUR_IS_PM) != 0;
+	bct.sec  = data[DS1307_SECS]  & DS1307_SECS_MASK;
+	bct.min  = data[DS1307_MINS]  & DS1307_MINS_MASK;
+	bct.hour = data[DS1307_HOUR]  & hourmask;
+	bct.day  = data[DS1307_DATE]  & DS1307_DATE_MASK;
+	bct.mon  = data[DS1307_MONTH] & DS1307_MONTH_MASK;
+	bct.year = data[DS1307_YEAR]  & DS1307_YEAR_MASK;
 
-	if (sc->sc_use_ampm) {
-		if (ct.hour == 12)
-			ct.hour = 0;
-		if (data[DS1307_HOUR] & DS1307_HOUR_IS_PM)
-			ct.hour += 12;
-	}
-
-	return (clock_ct_to_ts(&ct, ts));
+	return (clock_bcd_to_ts(&bct, ts, sc->sc_use_ampm));
 }
 
 static int
 ds1307_settime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct ds1307_softc *sc;
-	int error;
+	int error, year;
 	uint8_t data[7];
 	uint8_t pmflags;
 
@@ -373,27 +378,30 @@ ds1307_settime(device_t dev, struct timespec *ts)
 	 * disables utc adjustment, so apply that ourselves.
 	 */
 	ts->tv_sec -= utc_offset();
-	clock_ts_to_ct(ts, &ct);
+	clock_ts_to_bcd(ts, &bct, sc->sc_use_ampm);
 
 	/* If the chip is in AM/PM mode, adjust hour and set flags as needed. */
 	if (sc->sc_use_ampm) {
 		pmflags = DS1307_HOUR_USE_AMPM;
-		if (ct.hour >= 12) {
-			ct.hour -= 12;
+		if (bct.ispm)
 			pmflags |= DS1307_HOUR_IS_PM;
-		}
-		if (ct.hour == 0)
-			ct.hour = 12;
 	} else
 		pmflags = 0;
 
-	data[DS1307_SECS]    = TOBCD(ct.sec);
-	data[DS1307_MINS]    = TOBCD(ct.min);
-	data[DS1307_HOUR]    = TOBCD(ct.hour) | pmflags;
-	data[DS1307_DATE]    = TOBCD(ct.day);
-	data[DS1307_WEEKDAY] = ct.dow;
-	data[DS1307_MONTH]   = TOBCD(ct.mon);
-	data[DS1307_YEAR]    = TOBCD(ct.year % 100);
+	data[DS1307_SECS]    = bct.sec;
+	data[DS1307_MINS]    = bct.min;
+	data[DS1307_HOUR]    = bct.hour | pmflags;
+	data[DS1307_DATE]    = bct.day;
+	data[DS1307_WEEKDAY] = bct.dow;
+	data[DS1307_MONTH]   = bct.mon;
+	data[DS1307_YEAR]    = bct.year & 0xff;
+	if (sc->sc_mcp7941x) {
+		data[DS1307_SECS] |= MCP7941X_SECS_ST;
+		data[DS1307_WEEKDAY] |= MCP7941X_WEEKDAY_VBATEN;
+		year = bcd2bin(bct.year >> 8) * 100 + bcd2bin(bct.year & 0xff);
+		if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
+			data[DS1307_MONTH] |= MCP7941X_MONTH_LPYR;
+	}
 	/* Write the time back to RTC. */
 	error = iicdev_writeto(sc->sc_dev, DS1307_SECS, data, sizeof(data),
 	    IIC_INTRWAIT);

Modified: stable/11/sys/dev/iicbus/ds1307reg.h
==============================================================================
--- stable/11/sys/dev/iicbus/ds1307reg.h	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/dev/iicbus/ds1307reg.h	Sat Mar 24 20:40:16 2018	(r331496)
@@ -36,6 +36,7 @@
 #define	DS1307_SECS		0x00
 #define	DS1307_SECS_MASK		0x7f
 #define	DS1307_SECS_CH			0x80
+#define	MCP7941X_SECS_ST		0x80
 #define	DS1307_MINS		0x01
 #define	DS1307_MINS_MASK		0x7f
 #define	DS1307_HOUR		0x02
@@ -44,10 +45,12 @@
 #define	DS1307_HOUR_IS_PM		0x20
 #define	DS1307_HOUR_USE_AMPM		0x40
 #define	DS1307_WEEKDAY		0x03
+#define	MCP7941X_WEEKDAY_VBATEN		0x08
 #define	DS1307_WEEKDAY_MASK		0x07
 #define	DS1307_DATE		0x04
 #define	DS1307_DATE_MASK		0x3f
 #define	DS1307_MONTH		0x05
+#define	MCP7941X_MONTH_LPYR		0x20
 #define	DS1307_MONTH_MASK		0x1f
 #define	DS1307_YEAR		0x06
 #define	DS1307_YEAR_MASK		0xff

Modified: stable/11/sys/dev/iicbus/ds13rtc.c
==============================================================================
--- stable/11/sys/dev/iicbus/ds13rtc.c	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/dev/iicbus/ds13rtc.c	Sat Mar 24 20:40:16 2018	(r331496)
@@ -345,7 +345,7 @@ ds13rtc_start(void *arg)
 static int
 ds13rtc_gettime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct time_regs tregs;
 	struct ds13rtc_softc *sc;
 	int err;
@@ -379,29 +379,23 @@ ds13rtc_gettime(device_t dev, struct timespec *ts)
 	else
 		hourmask = DS13xx_M_24HOUR;
 
-	ct.sec  = FROMBCD(tregs.sec   & DS13xx_M_SECOND);
-	ct.min  = FROMBCD(tregs.min   & DS13xx_M_MINUTE);
-	ct.hour = FROMBCD(tregs.hour  & hourmask);
-	ct.day  = FROMBCD(tregs.day   & DS13xx_M_DAY);
-	ct.mon  = FROMBCD(tregs.month & DS13xx_M_MONTH);
-	ct.year = FROMBCD(tregs.year  & DS13xx_M_YEAR);
-	ct.nsec = 0;
+	bct.nsec = 0;
+	bct.ispm = tregs.hour  & DS13xx_B_HOUR_PM;
+	bct.sec  = tregs.sec   & DS13xx_M_SECOND;
+	bct.min  = tregs.min   & DS13xx_M_MINUTE;
+	bct.hour = tregs.hour  & hourmask;
+	bct.day  = tregs.day   & DS13xx_M_DAY;
+	bct.mon  = tregs.month & DS13xx_M_MONTH;
+	bct.year = tregs.year  & DS13xx_M_YEAR;
 
-	if (sc->use_ampm) {
-		if (ct.hour == 12)
-			ct.hour = 0;
-		if (tregs.hour & DS13xx_B_HOUR_PM)
-			ct.hour += 12;
-	}
-
 	/*
 	 * If this chip has a century bit, honor it.  Otherwise let
 	 * clock_ct_to_ts() infer the century from the 2-digit year.
 	 */
 	if (sc->use_century)
-		ct.year += (tregs.month & DS13xx_B_MONTH_CENTURY) ? 2000 : 1900;
+		bct.year += (tregs.month & DS13xx_B_MONTH_CENTURY) ? 0x100 : 0;
 
-	err = clock_ct_to_ts(&ct, ts);
+	err = clock_bcd_to_ts(&bct, ts, sc->use_ampm);
 
 	return (err);
 }
@@ -409,7 +403,7 @@ ds13rtc_gettime(device_t dev, struct timespec *ts)
 static int
 ds13rtc_settime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct time_regs tregs;
 	struct ds13rtc_softc *sc;
 	int err;
@@ -427,34 +421,30 @@ ds13rtc_settime(device_t dev, struct timespec *ts)
 	if (sc->is_binary_counter)
 		return (write_timeword(sc, ts->tv_sec));
 
-	clock_ts_to_ct(ts, &ct);
+	clock_ts_to_bcd(ts, &bct, sc->use_ampm);
 
 	/* If the chip is in AMPM mode deal with the PM flag. */
 	pmflags = 0;
 	if (sc->use_ampm) {
 		pmflags = DS13xx_B_HOUR_AMPM;
-		if (ct.hour >= 12) {
-			ct.hour -= 12;
+		if (bct.ispm)
 			pmflags |= DS13xx_B_HOUR_PM;
-		}
-		if (ct.hour == 0)
-			ct.hour = 12;
 	}
 
 	/* If the chip has a century bit, set it as needed. */
 	cflag = 0;
 	if (sc->use_century) {
-		if (ct.year >= 2000)
+		if (bct.year >= 2000)
 			cflag |= DS13xx_B_MONTH_CENTURY;
 	}
 
-	tregs.sec   = TOBCD(ct.sec);
-	tregs.min   = TOBCD(ct.min);
-	tregs.hour  = TOBCD(ct.hour) | pmflags;
-	tregs.day   = TOBCD(ct.day);
-	tregs.month = TOBCD(ct.mon) | cflag;
-	tregs.year  = TOBCD(ct.year % 100);
-	tregs.wday  = ct.dow;
+	tregs.sec   = bct.sec;
+	tregs.min   = bct.min;
+	tregs.hour  = bct.hour | pmflags;
+	tregs.day   = bct.day;
+	tregs.month = bct.mon | cflag;
+	tregs.year  = bct.year & 0xff;
+	tregs.wday  = bct.dow;
 
 	/*
 	 * Set the time.  Reset the OSF bit if it is on and it is not part of

Modified: stable/11/sys/dev/iicbus/nxprtc.c
==============================================================================
--- stable/11/sys/dev/iicbus/nxprtc.c	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/dev/iicbus/nxprtc.c	Sat Mar 24 20:40:16 2018	(r331496)
@@ -192,10 +192,10 @@ struct nxprtc_softc {
 	uint8_t		secaddr;	/* Address of seconds register */
 	uint8_t		tmcaddr;	/* Address of timer count register */
 	bool		use_timer;	/* Use timer for fractional sec */
+	bool		use_ampm;	/* Chip is set to use am/pm mode */
 };
 
 #define	SC_F_CPOL	(1 << 0)	/* Century bit means 19xx */
-#define	SC_F_AMPM	(1 << 1)	/* Use PM flag in hours reg */
 
 /*
  * We use the compat_data table to look up hint strings in the non-FDT case, so
@@ -382,10 +382,10 @@ pcf8523_start(struct nxprtc_softc *sc)
 	/* Remember whether we're running in AM/PM mode. */
 	if (is2129) {
 		if (cs1 & PCF2129_B_CS1_12HR)
-			sc->flags |= SC_F_AMPM;
+			sc->use_ampm = true;
 	} else {
 		if (cs1 & PCF8523_B_CS1_12HR)
-			sc->flags |= SC_F_AMPM;
+			sc->use_ampm = true;
 	}
 
 	return (0);
@@ -543,7 +543,7 @@ nxprtc_start(void *dev)
 static int
 nxprtc_gettime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct time_regs tregs;
 	struct nxprtc_softc *sc;
 	int err;
@@ -570,21 +570,19 @@ nxprtc_gettime(device_t dev, struct timespec *ts)
 		return (EINVAL); /* hardware is good, time is not. */
 	}
 
-	if (sc->flags & SC_F_AMPM)
+	if (sc->use_ampm)
 		hourmask = PCF85xx_M_12HOUR;
 	else
 		hourmask = PCF85xx_M_24HOUR;
 
-	ct.nsec = ((uint64_t)tmrcount * 1000000000) / TMR_TICKS_SEC;
-	ct.sec  = FROMBCD(tregs.sec   & PCF85xx_M_SECOND);
-	ct.min  = FROMBCD(tregs.min   & PCF85xx_M_MINUTE);
-	ct.hour = FROMBCD(tregs.hour  & hourmask);
-	ct.day  = FROMBCD(tregs.day   & PCF85xx_M_DAY);
-	ct.mon  = FROMBCD(tregs.month & PCF85xx_M_MONTH);
-	ct.year = FROMBCD(tregs.year  & PCF85xx_M_YEAR);
-	ct.year += 1900;
-	if (ct.year < POSIX_BASE_YEAR)
-		ct.year += 100;	/* assume [1970, 2069] */
+	bct.nsec = ((uint64_t)tmrcount * 1000000000) / TMR_TICKS_SEC;
+	bct.ispm = (tregs.hour & PCF8523_B_HOUR_PM) != 0;
+	bct.sec  = tregs.sec   & PCF85xx_M_SECOND;
+	bct.min  = tregs.min   & PCF85xx_M_MINUTE;
+	bct.hour = tregs.hour  & hourmask;
+	bct.day  = tregs.day   & PCF85xx_M_DAY;
+	bct.mon  = tregs.month & PCF85xx_M_MONTH;
+	bct.year = tregs.year  & PCF85xx_M_YEAR;
 
 	/*
 	 * Old PCF8563 datasheets recommended that the C bit be 1 for 19xx and 0
@@ -594,21 +592,13 @@ nxprtc_gettime(device_t dev, struct timespec *ts)
 	 */
 	if (sc->chiptype == TYPE_PCF8563) {
 		if (tregs.month & PCF8563_B_MONTH_C) {
-			if (ct.year >= 2000)
+			if (bct.year < 0x70)
 				sc->flags |= SC_F_CPOL;
-		} else if (ct.year < 2000)
+		} else if (bct.year >= 0x70)
 				sc->flags |= SC_F_CPOL;
 	}
 
-	/* If this chip is running in 12-hour/AMPM mode, deal with it. */
-	if (sc->flags & SC_F_AMPM) {
-		if (ct.hour == 12)
-			ct.hour = 0;
-		if (tregs.hour & PCF8523_B_HOUR_PM)
-			ct.hour += 12;
-	}
-
-	err = clock_ct_to_ts(&ct, ts);
+	err = clock_bcd_to_ts(&bct, ts, sc->use_ampm);
 	ts->tv_sec += utc_offset();
 
 	return (err);
@@ -617,11 +607,11 @@ nxprtc_gettime(device_t dev, struct timespec *ts)
 static int
 nxprtc_settime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 	struct time_regs tregs;
 	struct nxprtc_softc *sc;
 	int err;
-	uint8_t cflag, cs1, pmflag;
+	uint8_t cflag, cs1;
 
 	sc = device_get_softc(dev);
 
@@ -647,36 +637,25 @@ nxprtc_settime(device_t dev, struct timespec *ts)
 	getnanotime(ts);
 	ts->tv_sec -= utc_offset();
 	ts->tv_nsec = 0;
-	clock_ts_to_ct(ts, &ct);
+	clock_ts_to_bcd(ts, &bct, sc->use_ampm);
 
-	/* If the chip is in AMPM mode deal with the PM flag. */
-	pmflag = 0;
-	if (sc->flags & SC_F_AMPM) {
-		if (ct.hour >= 12) {
-			ct.hour -= 12;
-			pmflag = PCF8523_B_HOUR_PM;
-		}
-		if (ct.hour == 0)
-			ct.hour = 12;
-	}
-
 	/* On 8563 set the century based on the polarity seen when reading. */
 	cflag = 0;
 	if (sc->chiptype == TYPE_PCF8563) {
 		if ((sc->flags & SC_F_CPOL) != 0) {
-			if (ct.year >= 2000)
+			if (bct.year >= 0x2000)
 				cflag = PCF8563_B_MONTH_C;
-		} else if (ct.year < 2000)
+		} else if (bct.year < 0x2000)
 				cflag = PCF8563_B_MONTH_C;
 	}
 
-	tregs.sec   = TOBCD(ct.sec);
-	tregs.min   = TOBCD(ct.min);
-	tregs.hour  = TOBCD(ct.hour) | pmflag;
-	tregs.day   = TOBCD(ct.day);
-	tregs.month = TOBCD(ct.mon);
-	tregs.year  = TOBCD(ct.year % 100) | cflag;
-	tregs.wday  = ct.dow;
+	tregs.sec   = bct.sec;
+	tregs.min   = bct.min;
+	tregs.hour  = bct.hour | (bct.ispm ? PCF8523_B_HOUR_PM : 0);
+	tregs.day   = bct.day;
+	tregs.month = bct.mon;
+	tregs.year  = (bct.year & 0xff) | cflag;
+	tregs.wday  = bct.dow;
 
 	/*
 	 * Set the time, reset the timer count register, then start the clocks.

Modified: stable/11/sys/kern/subr_clock.c
==============================================================================
--- stable/11/sys/kern/subr_clock.c	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/kern/subr_clock.c	Sat Mar 24 20:40:16 2018	(r331496)
@@ -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)
 {
@@ -244,6 +293,34 @@ clock_ts_to_ct(struct timespec *ts, struct clocktime *
 		print_ct(ct);
 		printf("\n");
 	}
+}
+
+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: stable/11/sys/sys/clock.h
==============================================================================
--- stable/11/sys/sys/clock.h	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/sys/clock.h	Sat Mar 24 20:40:16 2018	(r331496)
@@ -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.

Modified: stable/11/sys/x86/isa/atrtc.c
==============================================================================
--- stable/11/sys/x86/isa/atrtc.c	Sat Mar 24 19:57:08 2018	(r331495)
+++ stable/11/sys/x86/isa/atrtc.c	Sat Mar 24 20:40:16 2018	(r331496)
@@ -74,28 +74,23 @@ static	u_char	rtc_statusb = RTCSB_24HR;
  * RTC support routines
  */
 
-int
-rtcin(int reg)
+static inline u_char
+rtcin_locked(int reg)
 {
-	u_char val;
 
-	RTC_LOCK;
 	if (rtc_reg != reg) {
 		inb(0x84);
 		outb(IO_RTC, reg);
 		rtc_reg = reg;
 		inb(0x84);
 	}
-	val = inb(IO_RTC + 1);
-	RTC_UNLOCK;
-	return (val);
+	return (inb(IO_RTC + 1));
 }
 
-void
-writertc(int reg, u_char val)
+static inline void
+rtcout_locked(int reg, u_char val)
 {
 
-	RTC_LOCK;
 	if (rtc_reg != reg) {
 		inb(0x84);
 		outb(IO_RTC, reg);
@@ -104,13 +99,26 @@ writertc(int reg, u_char val)
 	}
 	outb(IO_RTC + 1, val);
 	inb(0x84);
+}
+
+int
+rtcin(int reg)
+{
+	u_char val;
+
+	RTC_LOCK;
+	val = rtcin_locked(reg);
 	RTC_UNLOCK;
+	return (val);
 }
 
-static __inline int
-readrtc(int port)
+void
+writertc(int reg, u_char val)
 {
-	return(bcd2bin(rtcin(port)));
+
+	RTC_LOCK;
+	rtcout_locked(reg, val);
+	RTC_UNLOCK;
 }
 
 static void
@@ -159,37 +167,6 @@ atrtc_restore(void)
 	rtcin(RTC_INTR);
 }
 
-static void
-atrtc_set(struct timespec *ts)
-{
-	struct clocktime ct;
-
-	clock_ts_to_ct(ts, &ct);
-
-	mtx_lock(&atrtc_time_lock);
-
-	/* Disable RTC updates and interrupts. */
-	writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR);
-
-	writertc(RTC_SEC, bin2bcd(ct.sec)); 		/* Write back Seconds */
-	writertc(RTC_MIN, bin2bcd(ct.min)); 		/* Write back Minutes */
-	writertc(RTC_HRS, bin2bcd(ct.hour));		/* Write back Hours   */
-
-	writertc(RTC_WDAY, ct.dow + 1);			/* Write back Weekday */
-	writertc(RTC_DAY, bin2bcd(ct.day));		/* Write back Day */
-	writertc(RTC_MONTH, bin2bcd(ct.mon));           /* Write back Month   */
-	writertc(RTC_YEAR, bin2bcd(ct.year % 100));	/* Write back Year    */
-#ifdef USE_RTC_CENTURY
-	writertc(RTC_CENTURY, bin2bcd(ct.year / 100));	/* ... and Century    */
-#endif
-
-	/* Re-enable RTC updates and interrupts. */
-	writertc(RTC_STATUSB, rtc_statusb);
-	rtcin(RTC_INTR);
-
-	mtx_unlock(&atrtc_time_lock);
-}
-
 /**********************************************************************
  * RTC driver for subr_rtc
  */
@@ -336,15 +313,44 @@ atrtc_resume(device_t dev)
 static int
 atrtc_settime(device_t dev __unused, struct timespec *ts)
 {
+	struct bcd_clocktime bct;
 
-	atrtc_set(ts);
+	clock_ts_to_bcd(ts, &bct, false);
+
+	mtx_lock(&atrtc_time_lock);
+	RTC_LOCK;
+
+	/* Disable RTC updates and interrupts.  */
+	rtcout_locked(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR);
+
+	/* Write all the time registers. */
+	rtcout_locked(RTC_SEC,   bct.sec);
+	rtcout_locked(RTC_MIN,   bct.min);
+	rtcout_locked(RTC_HRS,   bct.hour);
+	rtcout_locked(RTC_WDAY,  bct.dow + 1);
+	rtcout_locked(RTC_DAY,   bct.day);
+	rtcout_locked(RTC_MONTH, bct.mon);
+	rtcout_locked(RTC_YEAR,  bct.year & 0xff);
+#ifdef USE_RTC_CENTURY
+	rtcout_locked(RTC_CENTURY, bct.year >> 8);
+#endif
+
+	/*
+	 * Re-enable RTC updates and interrupts.
+	 */
+	rtcout_locked(RTC_STATUSB, rtc_statusb);
+	rtcin_locked(RTC_INTR);
+
+	RTC_UNLOCK;
+	mtx_unlock(&atrtc_time_lock);
+
 	return (0);
 }
 
 static int
 atrtc_gettime(device_t dev, struct timespec *ts)
 {
-	struct clocktime ct;
+	struct bcd_clocktime bct;
 
 	/* Look if we have a RTC present and the time is valid */
 	if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) {
@@ -362,25 +368,22 @@ atrtc_gettime(device_t dev, struct timespec *ts)
 	mtx_lock(&atrtc_time_lock);
 	while (rtcin(RTC_STATUSA) & RTCSA_TUP)
 		continue;
-	critical_enter();
-	ct.nsec = 0;
-	ct.sec = readrtc(RTC_SEC);
-	ct.min = readrtc(RTC_MIN);
-	ct.hour = readrtc(RTC_HRS);
-	ct.day = readrtc(RTC_DAY);
-	ct.dow = readrtc(RTC_WDAY) - 1;
-	ct.mon = readrtc(RTC_MONTH);
-	ct.year = readrtc(RTC_YEAR);
+	RTC_LOCK;
+	bct.sec  = rtcin_locked(RTC_SEC);
+	bct.min  = rtcin_locked(RTC_MIN);
+	bct.hour = rtcin_locked(RTC_HRS);
+	bct.day  = rtcin_locked(RTC_DAY);
+	bct.mon  = rtcin_locked(RTC_MONTH);
+	bct.year = rtcin_locked(RTC_YEAR);
 #ifdef USE_RTC_CENTURY
-	ct.year += readrtc(RTC_CENTURY) * 100;
-#else
-	ct.year += (ct.year < 80 ? 2000 : 1900);
+	bct.year |= rtcin_locked(RTC_CENTURY) << 8;
 #endif
-	critical_exit();
+	RTC_UNLOCK;
 	mtx_unlock(&atrtc_time_lock);
-	/* Set dow = -1 because some clocks don't set it correctly. */
-	ct.dow = -1;
-	return (clock_ct_to_ts(&ct, ts));
+	/* dow is unused in timespec conversion and we have no nsec info. */
+	bct.dow  = 0;
+	bct.nsec = 0;
+	return (clock_bcd_to_ts(&bct, ts, false));
 }
 
 static device_method_t atrtc_methods[] = {



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