diff options
Diffstat (limited to 'drivers/rtc/interface.c')
| -rw-r--r-- | drivers/rtc/interface.c | 83 | 
1 files changed, 82 insertions, 1 deletions
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c index 8adcab3c365..de0da545c7a 100644 --- a/drivers/rtc/interface.c +++ b/drivers/rtc/interface.c @@ -100,7 +100,7 @@ int rtc_set_mmss(struct rtc_device *rtc, unsigned long secs)  }  EXPORT_SYMBOL_GPL(rtc_set_mmss); -int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) +static int rtc_read_alarm_internal(struct rtc_device *rtc, struct rtc_wkalrm *alarm)  {  	int err; @@ -120,6 +120,87 @@ int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)  	mutex_unlock(&rtc->ops_lock);  	return err;  } + +int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) +{ +	int err; +	struct rtc_time before, now; +	int first_time = 1; + +	/* The lower level RTC driver may not be capable of filling +	 * in all fields of the rtc_time struct (eg. rtc-cmos), +	 * and so might instead return -1 in some fields. +	 * We deal with that here by grabbing a current RTC timestamp +	 * and using values from that for any missing (-1) values. +	 * +	 * But this can be racey, because some fields of the RTC timestamp +	 * may have wrapped in the interval since we read the RTC alarm, +	 * which would lead to us inserting inconsistent values in place +	 * of the -1 fields. +	 * +	 * Reading the alarm and timestamp in the reverse sequence +	 * would have the same race condition, and not solve the issue. +	 * +	 * So, we must first read the RTC timestamp, +	 * then read the RTC alarm value, +	 * and then read a second RTC timestamp. +	 * +	 * If any fields of the second timestamp have changed +	 * when compared with the first timestamp, then we know +	 * our timestamp may be inconsistent with that used by +	 * the low-level rtc_read_alarm_internal() function. +	 * +	 * So, when the two timestamps disagree, we just loop and do +	 * the process again to get a fully consistent set of values. +	 * +	 * This could all instead be done in the lower level driver, +	 * but since more than one lower level RTC implementation needs it, +	 * then it's probably best best to do it here instead of there.. +	 */ + +	/* Get the "before" timestamp */ +	err = rtc_read_time(rtc, &before); +	if (err < 0) +		return err; +	do { +		if (!first_time) +			memcpy(&before, &now, sizeof(struct rtc_time)); +		first_time = 0; + +		/* get the RTC alarm values, which may be incomplete */ +		err = rtc_read_alarm_internal(rtc, alarm); +		if (err) +			return err; +		if (!alarm->enabled) +			return 0; + +		/* get the "after" timestamp, to detect wrapped fields */ +		err = rtc_read_time(rtc, &now); +		if (err < 0) +			return err; + +		/* note that tm_sec is a "don't care" value here: */ +	} while (   before.tm_min   != now.tm_min +		 || before.tm_hour  != now.tm_hour +		 || before.tm_mon   != now.tm_mon +		 || before.tm_year  != now.tm_year +		 || before.tm_isdst != now.tm_isdst); + +	/* Fill in any missing alarm fields using the timestamp */ +	if (alarm->time.tm_sec == -1) +		alarm->time.tm_sec = now.tm_sec; +	if (alarm->time.tm_min == -1) +		alarm->time.tm_min = now.tm_min; +	if (alarm->time.tm_hour == -1) +		alarm->time.tm_hour = now.tm_hour; +	if (alarm->time.tm_mday == -1) +		alarm->time.tm_mday = now.tm_mday; +	if (alarm->time.tm_mon == -1) +		alarm->time.tm_mon = now.tm_mon; +	if (alarm->time.tm_year == -1) +		alarm->time.tm_year = now.tm_year; +	return 0; +}  EXPORT_SYMBOL_GPL(rtc_read_alarm);  int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)  |