aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Garrett <matthew.garrett@nebula.com>2015-05-14 01:01:59 +0000
committerJohannes Weiner <hannes@cmpxchg.org>2015-05-14 01:01:59 +0000
commit1cdc6ea620f5243506bb81f4911e94bbb27e350d (patch)
treefe431d382e3e2283446060916bd2d5f31206ef4c
parent381d486dc40979490409e2d91cf87ef5eb5546ec (diff)
downloadmm-next-1cdc6ea620f5243506bb81f4911e94bbb27e350d.tar.gz
rtc: restore alarm after resume
Some platform firmware may interfere with the RTC alarm over suspend, resulting in the kernel and hardware having different ideas about system state but also potentially causing problems with firmware that assumes the OS will clean this case up. This patch saves the RTC alarm state on suspend and will restore it on resume if the alarm has not yet fired - if it has, it will clear the RTC alarm. The case we've seen is Intel Rapid Start, which is a firmware-mediated feature that automatically transitions systems from suspend-to-RAM to suspend-to-disk without OS involvement. It does this by setting the RTC alarm and a flag that indicates that on wake it should perform the transition rather than re-starting the OS. However, if the OS has set a wakeup alarm that would wake the machine earlier, it refuses to overwrite it and allows the system to wake instead. This fails in the following situation: 1) User configures Intel Rapid Start to transition after (say) 15 minutes 2) User suspends to RAM. Firmware sets the wakeup alarm for 15 minutes in the future 3) User resumes after 5 minutes. Firmware does not reset the alarm, and as such it is still set for 10 minutes in the future 4) User suspends after 5 minutes. Firmware notices that the alarm is set for 5 minutes in the future, which is less than the 15 minute transition threshold. It therefore assumes that the user wants the machine to wake in 5 minutes 5) System resumes after 5 minutes The worst case scenario here is that the user may have put the system in a bag between (4) and (5), resulting in it running in a confined space and potentially overheating. This seems reasonably important. The Rapid Start support code got added in 3.11, but it can be configured in the firmware regardless of kernel support. Signed-off-by: Matthew Garrett <matthew.garrett@nebula.com> Tested-by: Gabriele Mazzotta <gabriele.mzt@gmail.com> Cc: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
-rw-r--r--drivers/rtc/class.c24
-rw-r--r--include/linux/rtc.h4
2 files changed, 28 insertions, 0 deletions
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c
index ea2a315df6b7bb..272fc934ce6b9b 100644
--- a/drivers/rtc/class.c
+++ b/drivers/rtc/class.c
@@ -61,6 +61,8 @@ static int rtc_suspend(struct device *dev)
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
return 0;
+ rtc->valid_alarm = !rtc_read_alarm(rtc, &rtc->alarm);
+
/* snapshot the current RTC and system time at suspend*/
err = rtc_read_time(rtc, &tm);
if (err < 0) {
@@ -105,6 +107,27 @@ static int rtc_resume(struct device *dev)
if (timekeeping_rtc_skipresume())
return 0;
+ /*
+ * Ensure that the platform hasn't overwritten a pending alarm while
+ * suspended
+ */
+ if (rtc->valid_alarm) {
+ long now, scheduled;
+
+ rtc_read_time(rtc, &tm);
+ rtc_tm_to_time(&rtc->alarm.time, &scheduled);
+ rtc_tm_to_time(&tm, &now);
+
+ /* Clear the alarm registers if it went off during suspend */
+ if (scheduled <= now) {
+ rtc_time_to_tm(0, &rtc->alarm.time);
+ rtc->alarm.enabled = 0;
+ }
+
+ if (rtc->ops && rtc->ops->set_alarm)
+ rtc->ops->set_alarm(rtc->dev.parent, &rtc->alarm);
+ }
+
rtc_hctosys_ret = -ENODEV;
if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
return 0;
@@ -141,6 +164,7 @@ static int rtc_resume(struct device *dev)
if (sleep_time.tv_sec >= 0)
timekeeping_inject_sleeptime64(&sleep_time);
rtc_hctosys_ret = 0;
+
return 0;
}
diff --git a/include/linux/rtc.h b/include/linux/rtc.h
index 8dcf6825fa88bb..c8b68741994e71 100644
--- a/include/linux/rtc.h
+++ b/include/linux/rtc.h
@@ -134,6 +134,10 @@ struct rtc_device
/* Some hardware can't support UIE mode */
int uie_unsupported;
+#ifdef CONFIG_PM_SLEEP
+ struct rtc_wkalrm alarm;
+ bool valid_alarm;
+#endif
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;
struct timer_list uie_timer;