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>