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