Skip to content

Commit cd17420

Browse files
matjonalexandrebelloni
authored andcommitted
rtc: cmos: avoid UIP when writing alarm time
Some Intel chipsets disconnect the time and date RTC registers when the clock update is in progress: during this time reads may return bogus values and writes fail silently. This includes the RTC alarm registers. [1] cmos_set_alarm() did not take account for that, fix it. [1] 7th Generation Intel ® Processor Family I/O for U/Y Platforms [...] Datasheet, Volume 1 of 2 (Intel's Document Number: 334658-006) Page 208 https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/7th-and-8th-gen-core-family-mobile-u-y-processor-lines-i-o-datasheet-vol-1.pdf "If a RAM read from the ten time and date bytes is attempted during an update cycle, the value read do not necessarily represent the true contents of those locations. Any RAM writes under the same conditions are ignored." Signed-off-by: Mateusz Jończyk <[email protected]> Cc: Alessandro Zummo <[email protected]> Cc: Alexandre Belloni <[email protected]> Signed-off-by: Alexandre Belloni <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent cdedc45 commit cd17420

File tree

1 file changed

+66
-41
lines changed

1 file changed

+66
-41
lines changed

drivers/rtc/rtc-cmos.c

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,57 @@ static int cmos_validate_alarm(struct device *dev, struct rtc_wkalrm *t)
470470
return 0;
471471
}
472472

473+
struct cmos_set_alarm_callback_param {
474+
struct cmos_rtc *cmos;
475+
unsigned char mon, mday, hrs, min, sec;
476+
struct rtc_wkalrm *t;
477+
};
478+
479+
/* Note: this function may be executed by mc146818_avoid_UIP() more then
480+
* once
481+
*/
482+
static void cmos_set_alarm_callback(unsigned char __always_unused seconds,
483+
void *param_in)
484+
{
485+
struct cmos_set_alarm_callback_param *p =
486+
(struct cmos_set_alarm_callback_param *)param_in;
487+
488+
/* next rtc irq must not be from previous alarm setting */
489+
cmos_irq_disable(p->cmos, RTC_AIE);
490+
491+
/* update alarm */
492+
CMOS_WRITE(p->hrs, RTC_HOURS_ALARM);
493+
CMOS_WRITE(p->min, RTC_MINUTES_ALARM);
494+
CMOS_WRITE(p->sec, RTC_SECONDS_ALARM);
495+
496+
/* the system may support an "enhanced" alarm */
497+
if (p->cmos->day_alrm) {
498+
CMOS_WRITE(p->mday, p->cmos->day_alrm);
499+
if (p->cmos->mon_alrm)
500+
CMOS_WRITE(p->mon, p->cmos->mon_alrm);
501+
}
502+
503+
if (use_hpet_alarm()) {
504+
/*
505+
* FIXME the HPET alarm glue currently ignores day_alrm
506+
* and mon_alrm ...
507+
*/
508+
hpet_set_alarm_time(p->t->time.tm_hour, p->t->time.tm_min,
509+
p->t->time.tm_sec);
510+
}
511+
512+
if (p->t->enabled)
513+
cmos_irq_enable(p->cmos, RTC_AIE);
514+
}
515+
473516
static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
474517
{
475518
struct cmos_rtc *cmos = dev_get_drvdata(dev);
476-
unsigned char mon, mday, hrs, min, sec, rtc_control;
519+
struct cmos_set_alarm_callback_param p = {
520+
.cmos = cmos,
521+
.t = t
522+
};
523+
unsigned char rtc_control;
477524
int ret;
478525

479526
/* This not only a rtc_op, but also called directly */
@@ -484,55 +531,33 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
484531
if (ret < 0)
485532
return ret;
486533

487-
mon = t->time.tm_mon + 1;
488-
mday = t->time.tm_mday;
489-
hrs = t->time.tm_hour;
490-
min = t->time.tm_min;
491-
sec = t->time.tm_sec;
534+
p.mon = t->time.tm_mon + 1;
535+
p.mday = t->time.tm_mday;
536+
p.hrs = t->time.tm_hour;
537+
p.min = t->time.tm_min;
538+
p.sec = t->time.tm_sec;
492539

493540
spin_lock_irq(&rtc_lock);
494541
rtc_control = CMOS_READ(RTC_CONTROL);
495542
spin_unlock_irq(&rtc_lock);
496543

497544
if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
498545
/* Writing 0xff means "don't care" or "match all". */
499-
mon = (mon <= 12) ? bin2bcd(mon) : 0xff;
500-
mday = (mday >= 1 && mday <= 31) ? bin2bcd(mday) : 0xff;
501-
hrs = (hrs < 24) ? bin2bcd(hrs) : 0xff;
502-
min = (min < 60) ? bin2bcd(min) : 0xff;
503-
sec = (sec < 60) ? bin2bcd(sec) : 0xff;
504-
}
505-
506-
spin_lock_irq(&rtc_lock);
507-
508-
/* next rtc irq must not be from previous alarm setting */
509-
cmos_irq_disable(cmos, RTC_AIE);
510-
511-
/* update alarm */
512-
CMOS_WRITE(hrs, RTC_HOURS_ALARM);
513-
CMOS_WRITE(min, RTC_MINUTES_ALARM);
514-
CMOS_WRITE(sec, RTC_SECONDS_ALARM);
515-
516-
/* the system may support an "enhanced" alarm */
517-
if (cmos->day_alrm) {
518-
CMOS_WRITE(mday, cmos->day_alrm);
519-
if (cmos->mon_alrm)
520-
CMOS_WRITE(mon, cmos->mon_alrm);
521-
}
522-
523-
if (use_hpet_alarm()) {
524-
/*
525-
* FIXME the HPET alarm glue currently ignores day_alrm
526-
* and mon_alrm ...
527-
*/
528-
hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min,
529-
t->time.tm_sec);
546+
p.mon = (p.mon <= 12) ? bin2bcd(p.mon) : 0xff;
547+
p.mday = (p.mday >= 1 && p.mday <= 31) ? bin2bcd(p.mday) : 0xff;
548+
p.hrs = (p.hrs < 24) ? bin2bcd(p.hrs) : 0xff;
549+
p.min = (p.min < 60) ? bin2bcd(p.min) : 0xff;
550+
p.sec = (p.sec < 60) ? bin2bcd(p.sec) : 0xff;
530551
}
531552

532-
if (t->enabled)
533-
cmos_irq_enable(cmos, RTC_AIE);
534-
535-
spin_unlock_irq(&rtc_lock);
553+
/*
554+
* Some Intel chipsets disconnect the alarm registers when the clock
555+
* update is in progress - during this time writes fail silently.
556+
*
557+
* Use mc146818_avoid_UIP() to avoid this.
558+
*/
559+
if (!mc146818_avoid_UIP(cmos_set_alarm_callback, &p))
560+
return -EIO;
536561

537562
cmos->alarm_expires = rtc_tm_to_time64(&t->time);
538563

0 commit comments

Comments
 (0)