devices/
bat.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::ops::DerefMut;
6use std::sync::Arc;
7use std::time::Duration;
8use std::time::SystemTime;
9
10use acpi_tables::aml;
11use acpi_tables::aml::Aml;
12use anyhow::bail;
13use anyhow::Context;
14use base::error;
15use base::warn;
16use base::AsRawDescriptor;
17use base::Event;
18use base::EventToken;
19use base::RawDescriptor;
20use base::Tube;
21use base::WaitContext;
22use base::WorkerThread;
23use power_monitor::BatteryStatus;
24use power_monitor::CreatePowerClientFn;
25use power_monitor::CreatePowerMonitorFn;
26use power_monitor::PowerClient;
27use remain::sorted;
28use serde::Deserialize;
29use serde::Serialize;
30use snapshot::AnySnapshot;
31use sync::Mutex;
32use thiserror::Error;
33use vm_control::BatControlCommand;
34use vm_control::BatControlResult;
35
36use crate::pci::CrosvmDeviceId;
37use crate::BusAccessInfo;
38use crate::BusDevice;
39use crate::DeviceId;
40use crate::IrqLevelEvent;
41use crate::Suspendable;
42
43/// Errors for battery devices.
44#[sorted]
45#[derive(Error, Debug)]
46pub enum BatteryError {
47    #[error("Non 32-bit mmio address space")]
48    Non32BitMmioAddress,
49}
50
51type Result<T> = std::result::Result<T, BatteryError>;
52
53/// the GoldFish Battery MMIO length.
54pub const GOLDFISHBAT_MMIO_LEN: u64 = 0x1000;
55
56/// Configuration of fake battery status information.
57#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Copy)]
58pub enum BatConfig {
59    /// Propagates host's battery status
60    #[default]
61    Real,
62    /// Fake on battery status. Simulates a disconnected AC adapter.
63    /// This forces ac_online to false and sets the battery status
64    /// to DISCHARGING
65    Fake {
66        // Sets the maximum battery capacity reported to the guest
67        max_capacity: u32,
68    },
69}
70
71#[derive(Clone, Serialize, Deserialize)]
72struct GoldfishBatteryState {
73    // interrupt state
74    int_status: u32,
75    int_enable: u32,
76    // AC state
77    ac_online: u32,
78    // Battery state
79    status: u32,
80    health: u32,
81    present: u32,
82    capacity: u32,
83    voltage: u32,
84    current: u32,
85    charge_counter: u32,
86    charge_full: u32,
87    // bat_config is used for goldfish battery to report fake battery to the guest.
88    bat_config: BatConfig,
89}
90
91macro_rules! create_battery_func {
92    // $property: the battery property which is going to be modified.
93    // $ty: the type annotation of value argument
94    // $int: the interrupt status which is going to be set to notify the guest.
95    ($fn:ident, $property:ident, $ty:ty, $int:ident) => {
96        pub(crate) fn $fn(&mut self, value: $ty) -> bool {
97            let old = std::mem::replace(&mut self.$property, value);
98            old != self.$property && self.set_int_status($int)
99        }
100    };
101}
102
103impl GoldfishBatteryState {
104    fn set_int_status(&mut self, mask: u32) -> bool {
105        let old = self.int_status;
106        self.int_status |= self.int_enable & mask;
107        old != self.int_status
108    }
109
110    fn int_status(&self) -> u32 {
111        self.int_status
112    }
113
114    create_battery_func!(set_ac_online, ac_online, u32, AC_STATUS_CHANGED);
115
116    create_battery_func!(set_status, status, u32, BATTERY_STATUS_CHANGED);
117
118    create_battery_func!(set_health, health, u32, BATTERY_STATUS_CHANGED);
119
120    create_battery_func!(set_present, present, u32, BATTERY_STATUS_CHANGED);
121
122    create_battery_func!(set_capacity, capacity, u32, BATTERY_STATUS_CHANGED);
123
124    create_battery_func!(set_voltage, voltage, u32, BATTERY_STATUS_CHANGED);
125
126    create_battery_func!(set_current, current, u32, BATTERY_STATUS_CHANGED);
127
128    create_battery_func!(
129        set_charge_counter,
130        charge_counter,
131        u32,
132        BATTERY_STATUS_CHANGED
133    );
134
135    create_battery_func!(set_charge_full, charge_full, u32, BATTERY_STATUS_CHANGED);
136
137    create_battery_func!(set_bat_config, bat_config, BatConfig, BATTERY_INT_MASK);
138}
139
140enum BatInitializationState {
141    NotYet,
142    Pending(Box<dyn PowerClient>),
143    Done,
144}
145
146/// GoldFish Battery state
147pub struct GoldfishBattery {
148    state: Arc<Mutex<GoldfishBatteryState>>,
149    mmio_base: u32,
150    irq_num: u32,
151    irq_evt: IrqLevelEvent,
152    activated: bool,
153    monitor_thread: Option<WorkerThread<()>>,
154    tube: Option<Tube>,
155    create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
156    create_powerd_client: Option<Box<dyn CreatePowerClientFn>>,
157    init_state: Arc<Mutex<BatInitializationState>>,
158}
159
160#[derive(Serialize, Deserialize)]
161struct GoldfishBatterySnapshot {
162    state: GoldfishBatteryState,
163    mmio_base: u32,
164    irq_num: u32,
165    activated: bool,
166}
167
168/// Goldfish Battery MMIO offset
169const BATTERY_INT_STATUS: u32 = 0;
170const BATTERY_INT_ENABLE: u32 = 0x4;
171const BATTERY_AC_ONLINE: u32 = 0x8;
172const BATTERY_STATUS: u32 = 0xC;
173const BATTERY_HEALTH: u32 = 0x10;
174const BATTERY_PRESENT: u32 = 0x14;
175const BATTERY_CAPACITY: u32 = 0x18;
176const BATTERY_VOLTAGE: u32 = 0x1C;
177const BATTERY_TEMP: u32 = 0x20;
178const BATTERY_CHARGE_COUNTER: u32 = 0x24;
179const BATTERY_VOLTAGE_MAX: u32 = 0x28;
180const BATTERY_CURRENT_MAX: u32 = 0x2C;
181const BATTERY_CURRENT_NOW: u32 = 0x30;
182const BATTERY_CURRENT_AVG: u32 = 0x34;
183const BATTERY_CHARGE_FULL_UAH: u32 = 0x38;
184const BATTERY_CYCLE_COUNT: u32 = 0x40;
185
186/// Goldfish Battery interrupt bits
187const BATTERY_STATUS_CHANGED: u32 = 1 << 0;
188const AC_STATUS_CHANGED: u32 = 1 << 1;
189const BATTERY_INT_MASK: u32 = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED;
190
191/// Goldfish Battery status
192const BATTERY_STATUS_VAL_UNKNOWN: u32 = 0;
193const BATTERY_STATUS_VAL_CHARGING: u32 = 1;
194const BATTERY_STATUS_VAL_DISCHARGING: u32 = 2;
195const BATTERY_STATUS_VAL_NOT_CHARGING: u32 = 3;
196
197/// Goldfish Battery health
198const BATTERY_HEALTH_VAL_UNKNOWN: u32 = 0;
199
200// Goldfish ac online status
201const AC_ONLINE_VAL_OFFLINE: u32 = 0;
202
203#[derive(EventToken)]
204pub(crate) enum Token {
205    Commands,
206    Resample,
207    Kill,
208    Monitor,
209}
210
211fn command_monitor(
212    tube: Tube,
213    irq_evt: IrqLevelEvent,
214    kill_evt: Event,
215    state: Arc<Mutex<GoldfishBatteryState>>,
216    create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
217    init_state: Arc<Mutex<BatInitializationState>>,
218) {
219    let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
220        (&tube, Token::Commands),
221        (irq_evt.get_resample(), Token::Resample),
222        (&kill_evt, Token::Kill),
223    ]) {
224        Ok(pc) => pc,
225        Err(e) => {
226            error!("failed to build WaitContext: {}", e);
227            return;
228        }
229    };
230
231    let mut power_monitor = match create_power_monitor {
232        Some(f) => match f() {
233            Ok(p) => match wait_ctx.add(p.get_read_notifier(), Token::Monitor) {
234                Ok(()) => Some(p),
235                Err(e) => {
236                    error!("failed to add power monitor to poll context: {}", e);
237                    None
238                }
239            },
240            Err(e) => {
241                error!("failed to create power monitor: {}", e);
242                None
243            }
244        },
245        None => None,
246    };
247
248    'poll: loop {
249        let events = match wait_ctx.wait() {
250            Ok(v) => v,
251            Err(e) => {
252                error!("error while polling for events: {}", e);
253                break;
254            }
255        };
256
257        for event in events.iter().filter(|e| e.is_readable) {
258            match event.token {
259                Token::Commands => {
260                    let req = match tube.recv() {
261                        Ok(req) => req,
262                        Err(e) => {
263                            error!("failed to receive request: {}", e);
264                            continue;
265                        }
266                    };
267
268                    let mut bat_state = state.lock();
269                    let inject_irq = match req {
270                        BatControlCommand::SetStatus(status) => bat_state.set_status(status.into()),
271                        BatControlCommand::SetHealth(health) => bat_state.set_health(health.into()),
272                        BatControlCommand::SetPresent(present) => {
273                            let v = present != 0;
274                            bat_state.set_present(v.into())
275                        }
276                        BatControlCommand::SetCapacity(capacity) => {
277                            let v = std::cmp::min(capacity, 100);
278                            bat_state.set_capacity(v)
279                        }
280                        BatControlCommand::SetACOnline(ac_online) => {
281                            let v = ac_online != 0;
282                            bat_state.set_ac_online(v.into())
283                        }
284                        BatControlCommand::SetFakeBatConfig(max_capacity) => {
285                            let max_capacity = std::cmp::min(max_capacity, 100);
286                            bat_state.set_bat_config(BatConfig::Fake { max_capacity })
287                        }
288                        BatControlCommand::CancelFakeConfig => {
289                            bat_state.set_bat_config(BatConfig::Real)
290                        }
291                    };
292
293                    if inject_irq {
294                        let _ = irq_evt.trigger();
295                    }
296
297                    if let Err(e) = tube.send(&BatControlResult::Ok) {
298                        error!("failed to send response: {}", e);
299                    }
300                }
301
302                Token::Monitor => {
303                    // Safe because power_monitor must be populated if Token::Monitor is triggered.
304                    let power_monitor = power_monitor.as_mut().unwrap();
305
306                    let data = match power_monitor.read_message() {
307                        Ok(Some(d)) => d,
308                        Ok(None) => continue,
309                        Err(e) => {
310                            error!("failed to read new power data: {}", e);
311                            continue;
312                        }
313                    };
314
315                    let mut bat_state = state.lock();
316
317                    // Each set_* function called below returns true when interrupt bits
318                    // (*_STATUS_CHANGED) changed. If `inject_irq` is true after we attempt to
319                    // update each field, inject an interrupt.
320                    let mut inject_irq = bat_state.set_ac_online(data.ac_online.into());
321
322                    match data.battery {
323                        Some(battery_data) => {
324                            inject_irq |= bat_state.set_capacity(battery_data.percent);
325                            let battery_status = match battery_data.status {
326                                BatteryStatus::Unknown => BATTERY_STATUS_VAL_UNKNOWN,
327                                BatteryStatus::Charging => BATTERY_STATUS_VAL_CHARGING,
328                                BatteryStatus::Discharging => BATTERY_STATUS_VAL_DISCHARGING,
329                                BatteryStatus::NotCharging => BATTERY_STATUS_VAL_NOT_CHARGING,
330                            };
331                            inject_irq |= bat_state.set_status(battery_status);
332                            inject_irq |= bat_state.set_voltage(battery_data.voltage);
333                            inject_irq |= bat_state.set_current(battery_data.current);
334                            inject_irq |= bat_state.set_charge_counter(battery_data.charge_counter);
335                            inject_irq |= bat_state.set_charge_full(battery_data.charge_full);
336                        }
337                        None => {
338                            inject_irq |= bat_state.set_present(0);
339                        }
340                    }
341                    *init_state.lock() = BatInitializationState::Done;
342
343                    if inject_irq {
344                        let _ = irq_evt.trigger();
345                    }
346                }
347
348                Token::Resample => {
349                    irq_evt.clear_resample();
350                    if state.lock().int_status() != 0 {
351                        let _ = irq_evt.trigger();
352                    }
353                }
354
355                Token::Kill => break 'poll,
356            }
357        }
358    }
359}
360
361impl GoldfishBattery {
362    /// The interval in milli seconds between DBus requests to powerd.  This is used to rate-limit
363    /// requests to avoid overwhelming the power daemon.
364    pub(crate) const POWERD_REQ_INTERVAL_MS: u64 = 1000;
365
366    /// Create GoldfishBattery device model
367    ///
368    /// * `mmio_base` - The 32-bit mmio base address.
369    /// * `irq_num` - The corresponding interrupt number of the irq_evt which will be put into the
370    ///   ACPI DSDT.
371    /// * `irq_evt` - The interrupt event used to notify driver about the battery properties
372    ///   changing.
373    /// * `socket` - Battery control socket
374    pub fn new(
375        mmio_base: u64,
376        irq_num: u32,
377        irq_evt: IrqLevelEvent,
378        tube: Tube,
379        create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
380        create_powerd_client: Option<Box<dyn CreatePowerClientFn>>,
381    ) -> Result<Self> {
382        if mmio_base + GOLDFISHBAT_MMIO_LEN - 1 > u32::MAX as u64 {
383            return Err(BatteryError::Non32BitMmioAddress);
384        }
385        let state = Arc::new(Mutex::new(GoldfishBatteryState {
386            capacity: 50,
387            health: BATTERY_HEALTH_VAL_UNKNOWN,
388            present: 1,
389            status: BATTERY_STATUS_VAL_UNKNOWN,
390            ac_online: 1,
391            int_enable: 0,
392            int_status: 0,
393            voltage: 0,
394            current: 0,
395            charge_counter: 0,
396            charge_full: 0,
397            bat_config: BatConfig::Real,
398        }));
399
400        Ok(GoldfishBattery {
401            state,
402            mmio_base: mmio_base as u32,
403            irq_num,
404            irq_evt,
405            activated: false,
406            monitor_thread: None,
407            tube: Some(tube),
408            create_power_monitor,
409            create_powerd_client,
410            init_state: Arc::new(Mutex::new(BatInitializationState::NotYet)),
411        })
412    }
413
414    /// return the descriptors used by this device
415    pub fn keep_rds(&self) -> Vec<RawDescriptor> {
416        let mut rds = vec![
417            self.irq_evt.get_trigger().as_raw_descriptor(),
418            self.irq_evt.get_resample().as_raw_descriptor(),
419        ];
420
421        if let Some(tube) = &self.tube {
422            rds.push(tube.as_raw_descriptor());
423        }
424
425        rds
426    }
427
428    /// start a monitor thread to monitor the events from host
429    fn start_monitor(&mut self) {
430        if self.activated {
431            return;
432        }
433
434        if let Some(tube) = self.tube.take() {
435            let irq_evt = self.irq_evt.try_clone().unwrap();
436            let bat_state = self.state.clone();
437            let create_monitor_fn = self.create_power_monitor.take();
438            let init_state = self.init_state.clone();
439            self.monitor_thread = Some(WorkerThread::start(self.debug_label(), move |kill_evt| {
440                command_monitor(
441                    tube,
442                    irq_evt,
443                    kill_evt,
444                    bat_state,
445                    create_monitor_fn,
446                    init_state,
447                )
448            }));
449            self.activated = true;
450        }
451    }
452
453    fn initialize_battery_state(&mut self) -> anyhow::Result<()> {
454        let mut init_state = self.init_state.lock();
455        let power_client = match (init_state.deref_mut(), &self.create_powerd_client) {
456            (BatInitializationState::NotYet, None) => {
457                // No need to initialize the state via DBus.
458                return Ok(());
459            }
460            (BatInitializationState::NotYet, Some(f)) => {
461                // Initialize power_client
462                let client = match f() {
463                    Ok(c) => c,
464                    Err(e) => bail!("failed to connect to the powerd: {:#}", e),
465                };
466                // Save power_client to init_state
467                *init_state = BatInitializationState::Pending(client);
468
469                let power_client = match init_state.deref_mut() {
470                    BatInitializationState::Pending(ref mut power_client) => power_client.as_mut(),
471                    _ => unreachable!("init_state should be Pending"),
472                };
473                power_client
474            }
475            (BatInitializationState::Pending(ref mut power_client), _) => power_client.as_mut(),
476            (BatInitializationState::Done, _) => bail!("battery status already intialized"),
477        };
478
479        if let Some(prev_call) = power_client.last_request_timestamp() {
480            // Fail if the last request was sent within 1 second.
481            let now = SystemTime::now();
482            let duration = now
483                .duration_since(prev_call)
484                .context("failed to calculate time for dbus request")?;
485            if duration < Duration::from_millis(Self::POWERD_REQ_INTERVAL_MS) {
486                return Ok(());
487            }
488        }
489
490        match power_client.get_power_data() {
491            Ok(data) => {
492                let mut bat_state = self.state.lock();
493                bat_state.set_ac_online(data.ac_online.into());
494
495                match data.battery {
496                    Some(battery_data) => {
497                        bat_state.set_capacity(battery_data.percent);
498                        let battery_status = match battery_data.status {
499                            BatteryStatus::Unknown => BATTERY_STATUS_VAL_UNKNOWN,
500                            BatteryStatus::Charging => BATTERY_STATUS_VAL_CHARGING,
501                            BatteryStatus::Discharging => BATTERY_STATUS_VAL_DISCHARGING,
502                            BatteryStatus::NotCharging => BATTERY_STATUS_VAL_NOT_CHARGING,
503                        };
504                        bat_state.set_status(battery_status);
505                        bat_state.set_voltage(battery_data.voltage);
506                        bat_state.set_current(battery_data.current);
507                        bat_state.set_charge_counter(battery_data.charge_counter);
508                        bat_state.set_charge_full(battery_data.charge_full);
509                    }
510                    None => {
511                        bat_state.set_present(0);
512                    }
513                }
514            }
515            Err(e) => {
516                bail!("failed to get response from powerd: {:#}", e);
517            }
518        };
519        // Release powerd_client if the initialization data is obtained.
520        *init_state = BatInitializationState::Done;
521        Ok(())
522    }
523
524    fn battery_init_done(&self) -> bool {
525        matches!(*self.init_state.lock(), BatInitializationState::Done)
526    }
527}
528
529impl Drop for GoldfishBattery {
530    fn drop(&mut self) {
531        if let Err(e) = self.sleep() {
532            error!("{}", e);
533        };
534    }
535}
536
537impl BusDevice for GoldfishBattery {
538    fn device_id(&self) -> DeviceId {
539        CrosvmDeviceId::GoldfishBattery.into()
540    }
541
542    fn debug_label(&self) -> String {
543        "GoldfishBattery".to_owned()
544    }
545
546    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
547        if data.len() != std::mem::size_of::<u32>() {
548            warn!(
549                "{}: unsupported read length {}, only support 4bytes read",
550                self.debug_label(),
551                data.len()
552            );
553            return;
554        }
555
556        // Before first read, we try to ask powerd the actual power data to initialize `self.state`.
557        if !self.battery_init_done() {
558            if let Err(e) = self.initialize_battery_state() {
559                error!(
560                    "{}: failed to initialize bettery state (info={:?}): {:#}",
561                    self.debug_label(),
562                    info,
563                    e
564                );
565            }
566        }
567
568        let val = match info.offset as u32 {
569            BATTERY_INT_STATUS => {
570                // read to clear the interrupt status
571                std::mem::replace(&mut self.state.lock().int_status, 0)
572            }
573            BATTERY_INT_ENABLE => self.state.lock().int_enable,
574            BATTERY_AC_ONLINE => {
575                let bat_config = self.state.lock().bat_config;
576                match bat_config {
577                    BatConfig::Real => self.state.lock().ac_online,
578                    BatConfig::Fake { max_capacity: _ } => AC_ONLINE_VAL_OFFLINE,
579                }
580            }
581            BATTERY_STATUS => {
582                let bat_config = self.state.lock().bat_config;
583                match bat_config {
584                    BatConfig::Real => self.state.lock().status,
585                    BatConfig::Fake { max_capacity: _ } => BATTERY_STATUS_VAL_DISCHARGING,
586                }
587            }
588            BATTERY_HEALTH => self.state.lock().health,
589            BATTERY_PRESENT => self.state.lock().present,
590            BATTERY_CAPACITY => {
591                let max_capacity = match self.state.lock().bat_config {
592                    BatConfig::Real => 100,
593                    BatConfig::Fake { max_capacity } => max_capacity,
594                };
595                std::cmp::min(max_capacity, self.state.lock().capacity)
596            }
597            BATTERY_VOLTAGE => self.state.lock().voltage,
598            BATTERY_TEMP => 0,
599            BATTERY_CHARGE_COUNTER => self.state.lock().charge_counter,
600            BATTERY_VOLTAGE_MAX => 0,
601            BATTERY_CURRENT_MAX => 0,
602            BATTERY_CURRENT_NOW => self.state.lock().current,
603            BATTERY_CURRENT_AVG => 0,
604            BATTERY_CHARGE_FULL_UAH => self.state.lock().charge_full,
605            BATTERY_CYCLE_COUNT => 0,
606            _ => {
607                warn!("{}: unsupported read address {}", self.debug_label(), info);
608                return;
609            }
610        };
611
612        let val_arr = val.to_ne_bytes();
613        data.copy_from_slice(&val_arr);
614    }
615
616    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
617        if data.len() != std::mem::size_of::<u32>() {
618            warn!(
619                "{}: unsupported write length {}, only support 4bytes write",
620                self.debug_label(),
621                data.len()
622            );
623            return;
624        }
625
626        let mut val_arr = u32::to_ne_bytes(0u32);
627        val_arr.copy_from_slice(data);
628        let val = u32::from_ne_bytes(val_arr);
629
630        match info.offset as u32 {
631            BATTERY_INT_ENABLE => {
632                self.state.lock().int_enable = val;
633                if (val & BATTERY_INT_MASK) != 0 && !self.activated {
634                    self.start_monitor();
635                }
636            }
637            _ => {
638                warn!("{}: Bad write to address {}", self.debug_label(), info);
639            }
640        };
641    }
642}
643
644impl Aml for GoldfishBattery {
645    fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
646        aml::Device::new(
647            "GFBY".into(),
648            vec![
649                &aml::Name::new("_HID".into(), &"GFSH0001"),
650                &aml::Name::new(
651                    "_CRS".into(),
652                    &aml::ResourceTemplate::new(vec![
653                        &aml::Memory32Fixed::new(true, self.mmio_base, GOLDFISHBAT_MMIO_LEN as u32),
654                        &aml::Interrupt::new(true, false, false, true, self.irq_num),
655                    ]),
656                ),
657            ],
658        )
659        .to_aml_bytes(bytes);
660    }
661}
662
663impl Suspendable for GoldfishBattery {
664    fn sleep(&mut self) -> anyhow::Result<()> {
665        if let Some(thread) = self.monitor_thread.take() {
666            thread.stop();
667        }
668        Ok(())
669    }
670
671    fn wake(&mut self) -> anyhow::Result<()> {
672        if self.activated {
673            // Set activated to false for start_monitor to start monitoring again.
674            self.activated = false;
675            self.start_monitor();
676        }
677        Ok(())
678    }
679
680    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
681        AnySnapshot::to_any(GoldfishBatterySnapshot {
682            state: self.state.lock().clone(),
683            mmio_base: self.mmio_base,
684            irq_num: self.irq_num,
685            activated: self.activated,
686        })
687        .context("failed to snapshot GoldfishBattery")
688    }
689
690    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
691        let deser: GoldfishBatterySnapshot =
692            AnySnapshot::from_any(data).context("failed to deserialize GoldfishBattery")?;
693        {
694            let mut locked_state = self.state.lock();
695            *locked_state = deser.state;
696        }
697        self.mmio_base = deser.mmio_base;
698        self.irq_num = deser.irq_num;
699        self.activated = deser.activated;
700        Ok(())
701    }
702}
703
704#[cfg(test)]
705mod tests {
706    use std::time::SystemTime;
707
708    use super::*;
709    use crate::suspendable_tests;
710
711    fn modify_device(battery: &mut GoldfishBattery) {
712        let mut state = battery.state.lock();
713        state.set_capacity(70);
714    }
715
716    suspendable_tests! {
717        battery, GoldfishBattery::new(
718            0,
719            0,
720            IrqLevelEvent::new().unwrap(),
721            Tube::pair().unwrap().1,
722            None,
723            None,
724        ).unwrap(),
725        modify_device
726    }
727
728    // Mock power client for testing rate limiting.
729    struct MockPowerClient {
730        last_request_time: Option<SystemTime>,
731    }
732
733    impl MockPowerClient {
734        fn new(last_request_time: Option<SystemTime>) -> Self {
735            Self { last_request_time }
736        }
737    }
738
739    impl power_monitor::PowerClient for MockPowerClient {
740        fn last_request_timestamp(&self) -> Option<SystemTime> {
741            self.last_request_time
742        }
743
744        fn get_power_data(
745            &mut self,
746        ) -> std::result::Result<power_monitor::PowerData, Box<dyn std::error::Error>> {
747            self.last_request_time = Some(SystemTime::now());
748            Ok(power_monitor::PowerData {
749                ac_online: true,
750                battery: Some(power_monitor::BatteryData {
751                    percent: 50,
752                    status: power_monitor::BatteryStatus::Unknown,
753                    voltage: 0,
754                    current: 0,
755                    charge_counter: 0,
756                    charge_full: 0,
757                }),
758            })
759        }
760    }
761
762    fn create_mock_power_client(last_request_time: Option<SystemTime>) -> MockPowerClient {
763        MockPowerClient::new(last_request_time)
764    }
765
766    #[test]
767    fn test_initialize_battery_state_rate_limiting() {
768        let mmio_base = 0;
769        let irq_num = 0;
770        let irq_evt = IrqLevelEvent::new().unwrap();
771        let tube = Tube::pair().unwrap().1;
772
773        let now = SystemTime::now();
774        let recent_time =
775            now - Duration::from_millis(GoldfishBattery::POWERD_REQ_INTERVAL_MS - 500);
776
777        let mut battery = GoldfishBattery::new(
778            mmio_base,
779            irq_num,
780            irq_evt,
781            tube,
782            None,
783            Some(Box::new(move || {
784                Ok(Box::new(create_mock_power_client(Some(recent_time))))
785            })),
786        )
787        .unwrap();
788
789        assert!(matches!(battery.initialize_battery_state(), Ok(())));
790
791        // First initialization status should be pending due to rate limiting
792        assert!(matches!(
793            *battery.init_state.lock(),
794            BatInitializationState::Pending(_)
795        ));
796
797        *battery.init_state.lock() = BatInitializationState::NotYet;
798        let old_time = now - Duration::from_millis(GoldfishBattery::POWERD_REQ_INTERVAL_MS + 500);
799        // Replace the factory function to simulate an older last_request_time
800        battery.create_powerd_client = Some(Box::new(move || {
801            Ok(Box::new(create_mock_power_client(Some(old_time))))
802        }));
803
804        // Second call with old time should succeed.
805        assert!(matches!(battery.initialize_battery_state(), Ok(())));
806
807        assert!(matches!(
808            *battery.init_state.lock(),
809            BatInitializationState::Done,
810        ));
811
812        // Check if values are correctly updated.
813        let state = battery.state.lock();
814        assert_eq!(state.ac_online, 1);
815        assert_eq!(state.capacity, 50);
816    }
817
818    #[test]
819    fn test_initialize_battery_state_no_powerd_client() {
820        let mmio_base = 0;
821        let irq_num = 0;
822        let irq_evt = IrqLevelEvent::new().unwrap();
823        let tube = Tube::pair().unwrap().1;
824
825        let mut battery =
826            GoldfishBattery::new(mmio_base, irq_num, irq_evt, tube, None, None).unwrap();
827
828        assert!(matches!(battery.initialize_battery_state(), Ok(())));
829        assert!(matches!(
830            *battery.init_state.lock(),
831            BatInitializationState::NotYet,
832        ));
833    }
834}