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)
This commit is contained in:
parent
0c2094e599
commit
9c7cac637a
@ -199,6 +199,55 @@ clock_ct_to_ts(struct clocktime *ct, struct timespec *ts)
|
||||
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)
|
||||
{
|
||||
@ -260,6 +309,34 @@ clock_ts_to_ct(struct timespec *ts, struct clocktime *ct)
|
||||
("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
|
||||
utc_offset(void)
|
||||
{
|
||||
|
@ -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) */
|
||||
@ -78,6 +91,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.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user