1use anyhow::Error;
2use chrono::{DateTime, Datelike, Duration, Timelike, Utc};
3use nix::{ioctl_none, ioctl_read, ioctl_write_ptr};
4use std::collections::BTreeMap;
5use std::fs::File;
6use std::mem;
7use std::os::unix::io::AsRawFd;
8use std::path::Path;
9
10ioctl_read!(rtc_read_alarm, b'p', 0x10, RtcWkalrm);
11ioctl_write_ptr!(rtc_write_alarm, b'p', 0x0f, RtcWkalrm);
12ioctl_none!(rtc_disable_alarm, b'p', 0x02);
13
14#[repr(C)]
15#[derive(Debug, Clone)]
16pub struct RtcTime {
17 tm_sec: libc::c_int,
18 tm_min: libc::c_int,
19 tm_hour: libc::c_int,
20 tm_mday: libc::c_int,
21 tm_mon: libc::c_int,
22 tm_year: libc::c_int,
23 tm_wday: libc::c_int,
24 tm_yday: libc::c_int,
25 tm_isdst: libc::c_int,
26}
27
28impl Default for RtcWkalrm {
29 fn default() -> Self {
30 unsafe { mem::zeroed() }
31 }
32}
33
34#[repr(C)]
35#[derive(Debug, Clone)]
36pub struct RtcWkalrm {
37 enabled: libc::c_uchar,
38 pending: libc::c_uchar,
39 time: RtcTime,
40}
41
42impl RtcTime {
43 fn year(&self) -> i32 {
44 1900 + self.tm_year as i32
45 }
46}
47
48impl RtcWkalrm {
49 pub fn enabled(&self) -> bool {
50 self.enabled == 1
51 }
52
53 pub fn year(&self) -> i32 {
54 self.time.year()
55 }
56}
57
58pub struct Rtc(File);
59
60impl Rtc {
61 pub fn new<P: AsRef<Path>>(path: P) -> Result<Rtc, Error> {
62 let file = File::open(path)?;
63 Ok(Rtc(file))
64 }
65
66 pub fn alarm(&self) -> Result<RtcWkalrm, Error> {
67 let mut rwa = RtcWkalrm::default();
68 unsafe {
69 rtc_read_alarm(self.0.as_raw_fd(), &mut rwa)
70 .map(|_| rwa)
71 .map_err(|e| e.into())
72 }
73 }
74
75 pub fn set_alarm(&self, wake_time: DateTime<Utc>) -> Result<i32, Error> {
76 let rwa = RtcWkalrm {
77 enabled: 1,
78 pending: 0,
79 time: RtcTime {
80 tm_sec: wake_time.second() as libc::c_int,
81 tm_min: wake_time.minute() as libc::c_int,
82 tm_hour: wake_time.hour() as libc::c_int,
83 tm_mday: wake_time.day() as libc::c_int,
84 tm_mon: wake_time.month0() as libc::c_int,
85 tm_year: (wake_time.year() - 1900) as libc::c_int,
86 tm_wday: -1,
87 tm_yday: -1,
88 tm_isdst: -1,
89 },
90 };
91 unsafe { rtc_write_alarm(self.0.as_raw_fd(), &rwa).map_err(|e| e.into()) }
92 }
93
94 pub fn disable_alarm(&self) -> Result<i32, Error> {
95 unsafe { rtc_disable_alarm(self.0.as_raw_fd()).map_err(|e| e.into()) }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
101pub enum AlarmType {
102 AutoPowerOff,
103 CalendarUpdate,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum PastDueAction {
110 Reschedule,
112 Cancel,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum EnsureAlarmOutcome {
120 Scheduled,
122 AlreadyScheduled,
124 PastDue,
130}
131
132impl AlarmType {
133 pub fn alarms_to_cancel_after_resume() -> [Self; 2] {
134 [Self::AutoPowerOff, Self::CalendarUpdate]
135 }
136}
137
138pub struct ScheduledAlarm {
139 pub alarm_type: AlarmType,
140 pub wake_time: DateTime<Utc>,
141}
142
143pub struct AlarmManager {
151 rtc: Rtc,
152 scheduled_alarms: BTreeMap<AlarmType, ScheduledAlarm>,
153}
154
155impl AlarmManager {
156 pub fn new(rtc: Rtc) -> Self {
157 AlarmManager {
158 rtc,
159 scheduled_alarms: BTreeMap::new(),
160 }
161 }
162
163 pub fn schedule_alarm(
168 &mut self,
169 alarm_type: AlarmType,
170 duration: Duration,
171 ) -> Result<(), Error> {
172 let wake_time = Utc::now() + duration;
173 self.scheduled_alarms.insert(
174 alarm_type,
175 ScheduledAlarm {
176 alarm_type,
177 wake_time,
178 },
179 );
180 self.update_hardware_alarm()?;
181 Ok(())
182 }
183
184 pub fn cancel_alarm(&mut self, alarm_type: AlarmType) -> Result<(), Error> {
189 self.scheduled_alarms.remove(&alarm_type);
190 self.update_hardware_alarm()?;
191 Ok(())
192 }
193
194 pub fn is_alarm_scheduled(&self, alarm_type: AlarmType) -> bool {
196 self.scheduled_alarms
197 .get(&alarm_type)
198 .map(|alarm| alarm.wake_time > Utc::now())
199 .unwrap_or(false)
200 }
201
202 pub fn has_alarm(&self, alarm_type: AlarmType) -> bool {
204 self.scheduled_alarms.contains_key(&alarm_type)
205 }
206
207 pub fn ensure_scheduled(
218 &mut self,
219 alarm_type: AlarmType,
220 duration: Duration,
221 past_due_action: PastDueAction,
222 ) -> Result<EnsureAlarmOutcome, Error> {
223 if !self.has_alarm(alarm_type) {
224 self.schedule_alarm(alarm_type, duration)?;
225 return Ok(EnsureAlarmOutcome::Scheduled);
226 }
227
228 if self.is_alarm_scheduled(alarm_type) {
229 return Ok(EnsureAlarmOutcome::AlreadyScheduled);
230 }
231
232 self.cancel_alarm(alarm_type)?;
233
234 match past_due_action {
235 PastDueAction::Reschedule => {
236 self.schedule_alarm(alarm_type, duration)?;
237 Ok(EnsureAlarmOutcome::Scheduled)
238 }
239 PastDueAction::Cancel => Ok(EnsureAlarmOutcome::PastDue),
240 }
241 }
242
243 pub fn time_until_alarm(&self, alarm_type: AlarmType) -> Option<i64> {
246 self.scheduled_alarms.get(&alarm_type).map(|alarm| {
247 alarm
248 .wake_time
249 .signed_duration_since(Utc::now())
250 .num_seconds()
251 })
252 }
253
254 pub fn check_fired_alarms(
263 &mut self,
264 before: DateTime<Utc>,
265 after: DateTime<Utc>,
266 ) -> Result<Vec<AlarmType>, Error> {
267 let mut fired_types = Vec::new();
268
269 if let Some((_, earliest_alarm)) = self
270 .scheduled_alarms
271 .iter()
272 .min_by_key(|(_, alarm)| &alarm.wake_time)
273 {
274 let expected_duration = earliest_alarm.wake_time.signed_duration_since(before);
275
276 let rwa = self.rtc.alarm()?;
277 let hardware_alarm_fired = !rwa.enabled()
278 || (rwa.year() <= 1970
279 && ((after - before) - expected_duration).num_seconds().abs() < 3);
280
281 if hardware_alarm_fired {
282 let mut removed: Vec<(AlarmType, ScheduledAlarm)> = Vec::new();
283
284 for (alarm_type, scheduled_alarm) in &self.scheduled_alarms {
285 if (after - scheduled_alarm.wake_time).abs().num_milliseconds() <= 3000 {
286 fired_types.push(*alarm_type);
287 removed.push((
288 *alarm_type,
289 ScheduledAlarm {
290 alarm_type: scheduled_alarm.alarm_type,
291 wake_time: scheduled_alarm.wake_time,
292 },
293 ));
294 }
295 }
296
297 for (alarm_type, _) in &removed {
298 self.scheduled_alarms.remove(alarm_type);
299 }
300
301 if let Err(e) = self.update_hardware_alarm() {
302 for (alarm_type, alarm) in removed {
303 self.scheduled_alarms.insert(alarm_type, alarm);
304 }
305 return Err(e);
306 }
307
308 return Ok(fired_types);
309 }
310 }
311
312 self.update_hardware_alarm()?;
313 Ok(fired_types)
314 }
315
316 fn update_hardware_alarm(&self) -> Result<(), Error> {
317 let now = Utc::now();
318
319 if let Some((_, earliest_alarm)) = self
320 .scheduled_alarms
321 .iter()
322 .filter(|(_, alarm)| alarm.wake_time > now)
323 .min_by_key(|(_, alarm)| &alarm.wake_time)
324 {
325 self.rtc.set_alarm(earliest_alarm.wake_time)?;
326 } else {
327 self.rtc.disable_alarm()?;
328 }
329
330 Ok(())
331 }
332}