firmware_uefi/service/
time.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Real Time Clock Interface used by the UEFI Time Service
5//!
6//! Provides an interface to persist the time set by the guest (with UEFI
7//! SetTime) so that the time retrieved by the guest (with UEFI GetTime)
8//! reflects the time that has elapsed. Currently only used on ARM64.
9
10use crate::UefiDevice;
11use guestmem::GuestMemoryError;
12use inspect::InspectMut;
13use local_clock::InspectableLocalClock;
14use thiserror::Error;
15use time::OffsetDateTime;
16use uefi_specs::hyperv::time::VmEfiTime;
17use uefi_specs::uefi::common::EfiStatus;
18use uefi_specs::uefi::time::EFI_TIME;
19use uefi_specs::uefi::time::EfiDaylight;
20use uefi_specs::uefi::time::EfiTimezone;
21
22#[derive(Debug, Error)]
23pub enum TimeServiceError {
24    #[error("Invalid Argument")]
25    InvalidArg,
26    #[error("Time")]
27    Time(#[from] time::Error),
28    #[error("Overflow")]
29    Overflow,
30}
31
32/// The same information as [`EFI_TIME`] but backed by [`OffsetDateTime`]
33/// instead of raw numbers to make the values easier to manipulate.
34struct EfiOffsetDateTime {
35    datetime: OffsetDateTime,
36    timezone: EfiTimezone,
37    daylight: EfiDaylight,
38}
39
40impl TryFrom<EFI_TIME> for EfiOffsetDateTime {
41    type Error = time::Error;
42
43    fn try_from(v: EFI_TIME) -> Result<Self, Self::Error> {
44        Ok(Self {
45            datetime: OffsetDateTime::from_unix_timestamp(0)?
46                .replace_year(v.year as i32)?
47                .replace_month(v.month.try_into()?)?
48                .replace_day(v.day)?
49                .replace_hour(v.hour)?
50                .replace_minute(v.minute)?
51                .replace_second(v.second)?
52                .replace_nanosecond(v.nanosecond)?,
53            timezone: v.timezone,
54            daylight: v.daylight,
55        })
56    }
57}
58
59impl From<EfiOffsetDateTime> for EFI_TIME {
60    fn from(v: EfiOffsetDateTime) -> Self {
61        Self {
62            year: v.datetime.year() as u16,
63            month: v.datetime.month().into(),
64            day: v.datetime.day(),
65            hour: v.datetime.hour(),
66            minute: v.datetime.minute(),
67            second: v.datetime.second(),
68            pad1: 0,
69            nanosecond: v.datetime.nanosecond(),
70            timezone: v.timezone,
71            daylight: v.daylight,
72            pad2: 0,
73        }
74    }
75}
76
77#[derive(InspectMut)]
78pub struct TimeServices {
79    clock: Box<dyn InspectableLocalClock>,
80    timezone: EfiTimezone,
81    daylight: EfiDaylight,
82}
83
84impl TimeServices {
85    /// Create a new time service using the provided clock source.
86    /// SaveRestore for the clock should be handled externally.
87    pub fn new(clock: Box<dyn InspectableLocalClock>) -> Self {
88        Self {
89            clock,
90            timezone: EfiTimezone(0),
91            daylight: EfiDaylight::new(),
92        }
93    }
94
95    /// Get the [`LocalClock`](local_clock::LocalClock) time as [`EFI_TIME`].
96    ///
97    /// The clock implementation should handle any time delta between the host
98    /// and guest, including timezone and daylight.
99    pub fn get_time(&mut self) -> Result<EFI_TIME, TimeServiceError> {
100        if !self.daylight.valid() || !self.timezone.valid() {
101            return Err(TimeServiceError::InvalidArg);
102        }
103
104        let datetime: OffsetDateTime = self
105            .clock
106            .get_time()
107            .try_into()
108            .map_err(|_| TimeServiceError::Overflow)?;
109
110        Ok(EfiOffsetDateTime {
111            datetime,
112            timezone: self.timezone,
113            daylight: self.daylight,
114        }
115        .into())
116    }
117
118    /// Set the [`LocalClock`](local_clock::LocalClock) time from [`EFI_TIME`].
119    ///
120    /// The timezone and daylight information are saved so they can be retrieved
121    /// by the guest, but not processed.
122    pub fn set_time(&mut self, new_time: EFI_TIME) -> Result<(), TimeServiceError> {
123        let EfiOffsetDateTime {
124            datetime,
125            timezone,
126            daylight,
127        } = new_time.try_into()?;
128
129        if !daylight.valid() || !timezone.valid() {
130            return Err(TimeServiceError::InvalidArg);
131        }
132
133        self.timezone = timezone;
134        self.daylight = daylight;
135        self.clock.set_time(datetime.into());
136
137        Ok(())
138    }
139}
140
141impl UefiDevice {
142    /// Writes the time and status to the address specified.
143    pub(crate) fn get_time(&mut self, gpa: u64) -> Result<(), GuestMemoryError> {
144        let vm_time = match self.service.time.get_time() {
145            Ok(time) => VmEfiTime {
146                status: EfiStatus::SUCCESS.into(),
147                time,
148            },
149            Err(e) => {
150                tracing::error!("get_time: {}", e);
151                VmEfiTime {
152                    status: EfiStatus::DEVICE_ERROR.into(),
153                    time: Default::default(),
154                }
155            }
156        };
157
158        tracing::debug!("get_time: {:?}", vm_time);
159        self.gm.write_plain(gpa, &vm_time)
160    }
161
162    /// Reads the time from address specified, updates internal state,
163    /// and writes back the status.
164    pub(crate) fn set_time(&mut self, gpa: u64) -> Result<(), GuestMemoryError> {
165        let vm_time = self.gm.read_plain::<VmEfiTime>(gpa)?;
166        let status = match self.service.time.set_time(vm_time.time) {
167            Ok(_) => EfiStatus::SUCCESS,
168            Err(e) => {
169                tracing::error!("set_time: {}", e);
170                match e {
171                    TimeServiceError::InvalidArg => EfiStatus::INVALID_PARAMETER,
172                    _ => EfiStatus::DEVICE_ERROR,
173                }
174            }
175        };
176
177        let vm_time = VmEfiTime {
178            time: vm_time.time,
179            status: status.into(),
180        };
181        tracing::debug!("set_time: {:?}", vm_time);
182        self.gm.write_plain(gpa, &vm_time)
183    }
184}
185
186mod save_restore {
187    use super::*;
188    use vmcore::save_restore::RestoreError;
189    use vmcore::save_restore::SaveError;
190    use vmcore::save_restore::SaveRestore;
191
192    mod state {
193        use mesh::payload::Protobuf;
194        use vmcore::save_restore::SavedStateRoot;
195
196        #[derive(Protobuf, SavedStateRoot)]
197        #[mesh(package = "firmware.uefi.time")]
198        pub struct SavedState {
199            #[mesh(1)]
200            pub timezone: i16,
201            #[mesh(2)]
202            pub daylight: u8,
203        }
204    }
205
206    impl SaveRestore for TimeServices {
207        type SavedState = state::SavedState;
208
209        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
210            Ok(state::SavedState {
211                timezone: self.timezone.0,
212                daylight: self.daylight.into(),
213            })
214        }
215
216        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
217            let state::SavedState { timezone, daylight } = state;
218            self.timezone = EfiTimezone(timezone);
219            self.daylight = EfiDaylight::from(daylight);
220            Ok(())
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use local_clock::MockLocalClock;
229    use local_clock::MockLocalClockAccessor;
230    use std::time::Duration;
231    use test_with_tracing::test;
232
233    fn new_test_time_service() -> (MockLocalClockAccessor, TimeServices) {
234        let time = MockLocalClock::new();
235        let time_access = time.accessor();
236        let service = TimeServices::new(Box::new(time));
237        (time_access, service)
238    }
239
240    #[test]
241    fn test_basic_get_time_set_time() {
242        let (time_access, mut service) = new_test_time_service();
243        let mut init_time = EFI_TIME {
244            year: 2023,
245            month: 11,
246            day: 9,
247            hour: 10,
248            minute: 42,
249            second: 27,
250            pad1: 0,
251            nanosecond: 0,
252            timezone: EfiTimezone(-480),
253            daylight: EfiDaylight::new().with_adjust_daylight(true),
254            pad2: 0,
255        };
256        service.set_time(init_time).unwrap();
257        time_access.tick(Duration::from_secs(2));
258        init_time.second += 2;
259        let new_time = service.get_time().unwrap();
260        assert_eq!(init_time, new_time);
261    }
262
263    #[test]
264    #[should_panic]
265    fn test_validate_timezone() {
266        let (_, mut service) = new_test_time_service();
267        let init_time = EFI_TIME {
268            year: 2023,
269            month: 11,
270            day: 9,
271            hour: 10,
272            minute: 42,
273            second: 27,
274            pad1: 0,
275            nanosecond: 0,
276            timezone: EfiTimezone(1500),
277            daylight: EfiDaylight::new().with_adjust_daylight(true),
278            pad2: 0,
279        };
280        service.set_time(init_time).unwrap();
281    }
282
283    #[test]
284    #[should_panic]
285    fn test_validate_daylight() {
286        let (_, mut service) = new_test_time_service();
287        let init_time = EFI_TIME {
288            year: 2023,
289            month: 11,
290            day: 9,
291            hour: 10,
292            minute: 42,
293            second: 27,
294            pad1: 0,
295            nanosecond: 0,
296            timezone: EfiTimezone(-480),
297            daylight: EfiDaylight::from(4),
298            pad2: 0,
299        };
300        service.set_time(init_time).unwrap();
301    }
302}