Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/bat.rs
5394 views
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
5
use std::ops::DerefMut;
6
use std::sync::Arc;
7
use std::time::Duration;
8
use std::time::SystemTime;
9
10
use acpi_tables::aml;
11
use acpi_tables::aml::Aml;
12
use anyhow::bail;
13
use anyhow::Context;
14
use base::error;
15
use base::warn;
16
use base::AsRawDescriptor;
17
use base::Event;
18
use base::EventToken;
19
use base::RawDescriptor;
20
use base::Tube;
21
use base::WaitContext;
22
use base::WorkerThread;
23
use power_monitor::BatteryStatus;
24
use power_monitor::CreatePowerClientFn;
25
use power_monitor::CreatePowerMonitorFn;
26
use power_monitor::PowerClient;
27
use remain::sorted;
28
use serde::Deserialize;
29
use serde::Serialize;
30
use snapshot::AnySnapshot;
31
use sync::Mutex;
32
use thiserror::Error;
33
use vm_control::BatControlCommand;
34
use vm_control::BatControlResult;
35
use vm_control::DeviceId;
36
use vm_control::PlatformDeviceId;
37
38
use crate::BusAccessInfo;
39
use crate::BusDevice;
40
use crate::IrqLevelEvent;
41
use crate::Suspendable;
42
43
/// Errors for battery devices.
44
#[sorted]
45
#[derive(Error, Debug)]
46
pub enum BatteryError {
47
#[error("Non 32-bit mmio address space")]
48
Non32BitMmioAddress,
49
}
50
51
type Result<T> = std::result::Result<T, BatteryError>;
52
53
/// the GoldFish Battery MMIO length.
54
pub const GOLDFISHBAT_MMIO_LEN: u64 = 0x1000;
55
56
/// Configuration of fake battery status information.
57
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, Copy)]
58
pub 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)]
72
struct 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
91
macro_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
103
impl 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
140
enum BatInitializationState {
141
NotYet,
142
Pending(Box<dyn PowerClient>),
143
Done,
144
}
145
146
/// GoldFish Battery state
147
pub 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)]
161
struct GoldfishBatterySnapshot {
162
state: GoldfishBatteryState,
163
mmio_base: u32,
164
irq_num: u32,
165
activated: bool,
166
}
167
168
/// Goldfish Battery MMIO offset
169
const BATTERY_INT_STATUS: u32 = 0;
170
const BATTERY_INT_ENABLE: u32 = 0x4;
171
const BATTERY_AC_ONLINE: u32 = 0x8;
172
const BATTERY_STATUS: u32 = 0xC;
173
const BATTERY_HEALTH: u32 = 0x10;
174
const BATTERY_PRESENT: u32 = 0x14;
175
const BATTERY_CAPACITY: u32 = 0x18;
176
const BATTERY_VOLTAGE: u32 = 0x1C;
177
const BATTERY_TEMP: u32 = 0x20;
178
const BATTERY_CHARGE_COUNTER: u32 = 0x24;
179
const BATTERY_VOLTAGE_MAX: u32 = 0x28;
180
const BATTERY_CURRENT_MAX: u32 = 0x2C;
181
const BATTERY_CURRENT_NOW: u32 = 0x30;
182
const BATTERY_CURRENT_AVG: u32 = 0x34;
183
const BATTERY_CHARGE_FULL_UAH: u32 = 0x38;
184
const BATTERY_CYCLE_COUNT: u32 = 0x40;
185
186
/// Goldfish Battery interrupt bits
187
const BATTERY_STATUS_CHANGED: u32 = 1 << 0;
188
const AC_STATUS_CHANGED: u32 = 1 << 1;
189
const BATTERY_INT_MASK: u32 = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED;
190
191
/// Goldfish Battery status
192
const BATTERY_STATUS_VAL_UNKNOWN: u32 = 0;
193
const BATTERY_STATUS_VAL_CHARGING: u32 = 1;
194
const BATTERY_STATUS_VAL_DISCHARGING: u32 = 2;
195
const BATTERY_STATUS_VAL_NOT_CHARGING: u32 = 3;
196
197
/// Goldfish Battery health
198
const BATTERY_HEALTH_VAL_UNKNOWN: u32 = 0;
199
200
// Goldfish ac online status
201
const AC_ONLINE_VAL_OFFLINE: u32 = 0;
202
203
#[derive(EventToken)]
204
pub(crate) enum Token {
205
Commands,
206
Resample,
207
Kill,
208
Monitor,
209
}
210
211
fn 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
361
impl 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
529
impl Drop for GoldfishBattery {
530
fn drop(&mut self) {
531
if let Err(e) = self.sleep() {
532
error!("{}", e);
533
};
534
}
535
}
536
537
impl BusDevice for GoldfishBattery {
538
fn device_id(&self) -> DeviceId {
539
PlatformDeviceId::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
644
impl 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
663
impl 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)]
705
mod 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
}
835
836