1use 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
32struct 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 pub fn new(clock: Box<dyn InspectableLocalClock>) -> Self {
89 Self {
90 clock,
91 timezone: EfiTimezone(0),
92 daylight: EfiDaylight::new(),
93 }
94 }
95
96 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 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 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 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}