aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/rtc/interface.c
diff options
context:
space:
mode:
authorMark Lord <lkml@rtr.ca>2007-10-16 04:28:21 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-10-16 12:43:13 -0400
commit0e36a9a4a788e4e92407774df76c545910810d35 (patch)
treea235d316c46a1951498d09d78719476b5c47470a /drivers/rtc/interface.c
parentbf4994d781c69cc15844d63122320e46ddde6464 (diff)
rtc: fix readback from /sys/class/rtc/rtc?/wakealarm
Fix readback of RTC alarms on platforms which return -1 in non-hardware-supported RTC alarm fields. To fill in the missing (-1) values, we grab an RTC timestamp along with the RTC alarm value, and use the timestamp fields to populate the missing alarm fields. To counter field-wrap races (since the timestamp and alarm are not read together atomically), we read the RTC timestamp both before and after reading the RTC alarm value, and then check for wrapped fields --> if any have wrapped, we know we have a possible inconsistency, so we loop and reread the timestamp and alarm again. Wrapped fields in the RTC timestamps are an issue because rtc-cmos.c, for example, also gets/uses an RTC timestamp internally while fetching the RTC alarm. If our timestamp here wasn't the same (minutes and higher) as what was used internally there, then we might end up populating the -1 fields with inconsistent values. This fixes readbacks from /sys/class/rtc/rtc?/wakealarm, as well as other code paths which call rtc_read_alarm(). Signed-off-by: Mark Lord <mlord@pobox.com> Cc: David Brownell <david-b@pacbell.net> Cc: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc/interface.c')
-rw-r--r--drivers/rtc/interface.c83
1 files changed, 82 insertions, 1 deletions
diff --git a/drivers/rtc/interface.c b/drivers/rtc/interface.c
index 8adcab3c3653..de0da545c7a1 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)
100} 100}
101EXPORT_SYMBOL_GPL(rtc_set_mmss); 101EXPORT_SYMBOL_GPL(rtc_set_mmss);
102 102
103int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) 103static int rtc_read_alarm_internal(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
104{ 104{
105 int err; 105 int err;
106 106
@@ -120,6 +120,87 @@ int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
120 mutex_unlock(&rtc->ops_lock); 120 mutex_unlock(&rtc->ops_lock);
121 return err; 121 return err;
122} 122}
123
124int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
125{
126 int err;
127 struct rtc_time before, now;
128 int first_time = 1;
129
130 /* The lower level RTC driver may not be capable of filling
131 * in all fields of the rtc_time struct (eg. rtc-cmos),
132 * and so might instead return -1 in some fields.
133 * We deal with that here by grabbing a current RTC timestamp
134 * and using values from that for any missing (-1) values.
135 *
136 * But this can be racey, because some fields of the RTC timestamp
137 * may have wrapped in the interval since we read the RTC alarm,
138 * which would lead to us inserting inconsistent values in place
139 * of the -1 fields.
140 *
141 * Reading the alarm and timestamp in the reverse sequence
142 * would have the same race condition, and not solve the issue.
143 *
144 * So, we must first read the RTC timestamp,
145 * then read the RTC alarm value,
146 * and then read a second RTC timestamp.
147 *
148 * If any fields of the second timestamp have changed
149 * when compared with the first timestamp, then we know
150 * our timestamp may be inconsistent with that used by
151 * the low-level rtc_read_alarm_internal() function.
152 *
153 * So, when the two timestamps disagree, we just loop and do
154 * the process again to get a fully consistent set of values.
155 *
156 * This could all instead be done in the lower level driver,
157 * but since more than one lower level RTC implementation needs it,
158 * then it's probably best best to do it here instead of there..
159 */
160
161 /* Get the "before" timestamp */
162 err = rtc_read_time(rtc, &before);
163 if (err < 0)
164 return err;
165 do {
166 if (!first_time)
167 memcpy(&before, &now, sizeof(struct rtc_time));
168 first_time = 0;
169
170 /* get the RTC alarm values, which may be incomplete */
171 err = rtc_read_alarm_internal(rtc, alarm);
172 if (err)
173 return err;
174 if (!alarm->enabled)
175 return 0;
176
177 /* get the "after" timestamp, to detect wrapped fields */
178 err = rtc_read_time(rtc, &now);
179 if (err < 0)
180 return err;
181
182 /* note that tm_sec is a "don't care" value here: */
183 } while ( before.tm_min != now.tm_min
184 || before.tm_hour != now.tm_hour
185 || before.tm_mon != now.tm_mon
186 || before.tm_year != now.tm_year
187 || before.tm_isdst != now.tm_isdst);
188
189 /* Fill in any missing alarm fields using the timestamp */
190 if (alarm->time.tm_sec == -1)
191 alarm->time.tm_sec = now.tm_sec;
192 if (alarm->time.tm_min == -1)
193 alarm->time.tm_min = now.tm_min;
194 if (alarm->time.tm_hour == -1)
195 alarm->time.tm_hour = now.tm_hour;
196 if (alarm->time.tm_mday == -1)
197 alarm->time.tm_mday = now.tm_mday;
198 if (alarm->time.tm_mon == -1)
199 alarm->time.tm_mon = now.tm_mon;
200 if (alarm->time.tm_year == -1)
201 alarm->time.tm_year = now.tm_year;
202 return 0;
203}
123EXPORT_SYMBOL_GPL(rtc_read_alarm); 204EXPORT_SYMBOL_GPL(rtc_read_alarm);
124 205
125int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm) 206int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)