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