chipset/battery/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Virtual battery device.
5//!
6//! This virtual battery device simulates the host's battery state for the
7//! guest. Rather than modeling a real device, it's a Hyper-V specific design,
8//! tailored to meet the
9//! [ACPI Battery and Power Subsystem Firmware Implementation][acpi] and the
10//! additional requirements imposed by the
11//! [Windows Hardware Design Guidelines][whdg].
12//!
13//! This device was implemented alongside the ACPI code in Hyper-V UEFI. For
14//! more information, refer to the DSDT in the UEFI codebase: [mu_msvm][uefi].
15//!
16//! For historical context, this device was originally designed for x86 and was
17//! later adapted for ARM64.
18//!
19//! This device uses `LineInterrupts` to signal state changes to interested
20//! parties (e.g: the PM device on x86, a system IRQ on Aarch64). A mesh channel
21//! is used to receive battery state updates from the host platform.
22//!
23//! Refer to the following resources for more context:
24//! - [ACPI Battery and Power Subsystem Firmware Implementation][acpi]
25//! - [UEFI codebase (mu_msvm)][uefi]
26//! - [Windows Hardware Design Guidelines][whdg]
27//!
28//! [acpi]: https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/10_Power_Source_and_Power_Meter_Devices/Power_Source_and_Power_Meter_Devices.html#control-method-batteries
29//! [uefi]: https://github.com/Microsoft/mu_msvm
30//! [whdg]: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/acpi-battery-and-power-subsystem-firmware-implementation
31
32pub mod resolver;
33
34use chipset_device::ChipsetDevice;
35use chipset_device::io::IoError;
36use chipset_device::io::IoResult;
37use chipset_device::mmio::MmioIntercept;
38use chipset_device::poll_device::PollDevice;
39use chipset_resources::battery::HostBatteryUpdate;
40use futures::StreamExt;
41use inspect::Inspect;
42use inspect::InspectMut;
43use open_enum::open_enum;
44use std::ops::RangeInclusive;
45use vmcore::device_state::ChangeDeviceState;
46use vmcore::line_interrupt::LineInterrupt;
47
48// Battery MMIO constants
49pub const BATTERY_MMIO_REGION_BASE_ADDRESS_X64: u64 = 0xfed3f000;
50pub const BATTERY_MMIO_REGION_BASE_ADDRESS_ARM: u64 = 0xEFFEA000;
51pub const BATTERY_DEVICE_MMIO_REGION_SIZE: u64 = 0x20;
52pub const BATTERY_DEVICE_MMIO_REGION_MASK: u64 = BATTERY_DEVICE_MMIO_REGION_SIZE - 1;
53
54// Battery interrupt lines. For x64, use GPE0 bit 9.
55// For ARM64, use IRQ 4 [derived from 4 + 32 (SPI range start) = 36].
56pub const BATTERY_STATUS_GPE0_LINE: u32 = 9;
57pub const BATTERY_STATUS_IRQ_NO: u32 = 4;
58
59// Bits to set for notifications.
60//
61// NOTE: ACPI_DEVICE_NOTIFY_BST_CHANGED is deprecated and should not be used.
62// It is kept here for reference.
63//
64// No functionality is lost by not using this constant, but we prefer
65// ACPI_DEVICE_NOTIFY_BIX_CHANGED because it tells the guest to check for both
66// the BST and BIX registers.
67pub const ACPI_DEVICE_NOTIFY_BST_CHANGED: u32 = 0x1;
68pub const ACPI_DEVICE_NOTIFY_BIX_CHANGED: u32 = 0x2;
69
70// Defines what bits are allowed to be set for notifications
71pub const ACPI_DEVICE_NOTIFY_VALID_BITS: u32 = 0x3;
72
73// Virtual battery capacity, based on the UEFI DSDT's design capacity
74pub const VIRTUAL_BATTERY_CAPACITY: u32 = 5000;
75
76// Battery register offsets
77open_enum! {
78    #[derive(Inspect)]
79    #[inspect(debug)]
80    pub enum RegisterOffset: u64 {
81        STA_BATTERY_STATUS = 0x0,
82        BST_BATTERY_STATE = 0x4,
83        BST_BATTERY_PRESENT_RATE = 0x8,
84        BST_BATTERY_REMAINING_CAPACITY = 0xc,
85        PSR_AC_POWER_STATUS = 0x10,
86        BATTERY_ACPI_NOTIFY_STATUS = 0x14,
87        BATTERY_ACPI_NOTIFY_CLEAR = 0x18,
88    }
89}
90
91/// Various runtime objects used by the BatteryDevice
92pub struct BatteryRuntimeDeps {
93    pub battery_status_recv: mesh::Receiver<HostBatteryUpdate>,
94    pub notify_interrupt: LineInterrupt,
95}
96
97/// Virtual battery device.
98#[derive(InspectMut)]
99pub struct BatteryDevice {
100    // Runtime glue
101    #[inspect(skip)]
102    rt: BatteryRuntimeDeps,
103
104    // Static configuration
105    #[inspect(skip)]
106    mmio_region: (&'static str, RangeInclusive<u64>),
107    base_addr: u64,
108
109    // Volatile state
110    notify_bits: u32,
111    state: HostBatteryUpdate,
112}
113
114impl BatteryDevice {
115    /// Create a new battery device
116    pub fn new(platform: BatteryRuntimeDeps, base_addr: u64) -> Self {
117        BatteryDevice {
118            rt: platform,
119            mmio_region: (
120                "battery",
121                base_addr..=base_addr + (BATTERY_DEVICE_MMIO_REGION_SIZE - 1),
122            ),
123            base_addr,
124            state: HostBatteryUpdate::default(),
125            notify_bits: 0,
126        }
127    }
128
129    fn read_register(&self, offset: RegisterOffset) -> u32 {
130        match offset {
131            RegisterOffset::STA_BATTERY_STATUS => {
132                if self.state.battery_present {
133                    0x1F
134                } else {
135                    0xF
136                }
137            }
138            RegisterOffset::BST_BATTERY_STATE => {
139                if !self.state.battery_present {
140                    0
141                } else if self.state.charging {
142                    0x2
143                } else if self.state.discharging {
144                    0x1
145                } else {
146                    // Somehow, we got in some weird state, return default 0.
147                    tracelimit::warn_ratelimited!(
148                        "BST_BATTERY_STATE encountered a weird state, defaulting to 0"
149                    );
150                    0
151                }
152            }
153            RegisterOffset::BST_BATTERY_PRESENT_RATE => {
154                if self.state.battery_present && self.state.max_capacity != 0 {
155                    // Normalize the rate to the virtual battery's capacity.
156                    (self.state.rate.saturating_mul(VIRTUAL_BATTERY_CAPACITY))
157                        / self.state.max_capacity
158                } else {
159                    // Unknown rate.
160                    0xFFFFFFFF
161                }
162            }
163            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY => {
164                if self.state.battery_present && self.state.max_capacity != 0 {
165                    // Normalize the remaining capacity to the virtual battery's capacity.
166                    (self
167                        .state
168                        .remaining_capacity
169                        .saturating_mul(VIRTUAL_BATTERY_CAPACITY))
170                        / self.state.max_capacity
171                } else {
172                    // Unknown capacity.
173                    0xFFFFFFFF
174                }
175            }
176            RegisterOffset::PSR_AC_POWER_STATUS => {
177                if self.state.ac_online {
178                    0x1
179                } else {
180                    0x0
181                }
182            }
183            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS => self.notify_bits,
184            _ => 0,
185        }
186    }
187
188    fn write_register(&mut self, offset: RegisterOffset, value: u32) {
189        if offset != RegisterOffset::BATTERY_ACPI_NOTIFY_CLEAR {
190            // Only writes allowed are to clear notification bits.
191            tracelimit::warn_ratelimited!("Invalid write to battery device at offset {:?}", offset);
192            return;
193        }
194
195        // Clear any bits that were set to 1
196        self.notify_bits &= !value;
197        self.notify_bits &= ACPI_DEVICE_NOTIFY_VALID_BITS;
198
199        // Re-evaluate interrupt for any pending bits left
200        self.check_interrupt_assertion();
201    }
202
203    /// evaluates whether the battery's interrupt should be
204    /// asserted or de-asserted
205    fn check_interrupt_assertion(&self) {
206        self.rt.notify_interrupt.set_level(self.notify_bits != 0)
207    }
208}
209
210impl ChangeDeviceState for BatteryDevice {
211    fn start(&mut self) {}
212
213    async fn stop(&mut self) {}
214
215    async fn reset(&mut self) {
216        let Self {
217            rt,
218            mmio_region: _,
219            base_addr: _,
220            notify_bits,
221            state,
222        } = self;
223        *state = HostBatteryUpdate::default();
224        rt.notify_interrupt.set_level(false);
225        *notify_bits = 0;
226    }
227}
228
229impl ChipsetDevice for BatteryDevice {
230    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
231        Some(self)
232    }
233
234    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
235        Some(self)
236    }
237}
238
239impl MmioIntercept for BatteryDevice {
240    fn mmio_read(&mut self, address: u64, data: &mut [u8]) -> IoResult {
241        assert_eq!(address & !BATTERY_DEVICE_MMIO_REGION_MASK, self.base_addr);
242        if data.len() == size_of::<u32>() {
243            let value =
244                self.read_register(RegisterOffset(address & BATTERY_DEVICE_MMIO_REGION_MASK));
245            data.copy_from_slice(&value.to_ne_bytes());
246            IoResult::Ok
247        } else {
248            IoResult::Err(IoError::InvalidAccessSize)
249        }
250    }
251
252    fn mmio_write(&mut self, address: u64, data: &[u8]) -> IoResult {
253        assert_eq!(address & !BATTERY_DEVICE_MMIO_REGION_MASK, self.base_addr);
254        if let Ok(x) = data.try_into().map(u32::from_ne_bytes) {
255            self.write_register(RegisterOffset(address & BATTERY_DEVICE_MMIO_REGION_MASK), x);
256            IoResult::Ok
257        } else {
258            IoResult::Err(IoError::InvalidAccessSize)
259        }
260    }
261
262    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
263        std::slice::from_ref(&self.mmio_region)
264    }
265}
266
267impl PollDevice for BatteryDevice {
268    fn poll_device(&mut self, cx: &mut std::task::Context<'_>) {
269        while let std::task::Poll::Ready(Some(update)) =
270            self.rt.battery_status_recv.poll_next_unpin(cx)
271        {
272            self.state = update;
273            if self.state.battery_present && self.state.max_capacity == 0 {
274                // This configuration makes no sense. Just report no battery,
275                // and set AC power accordingly.
276                tracelimit::warn_ratelimited!(
277                    "BATTERY: Invalid state: max_capacity is 0 but battery is present. Marking battery as not present."
278                );
279                self.state.battery_present = false;
280            }
281            // Add the corresponding status bit to notification status
282            self.notify_bits |= ACPI_DEVICE_NOTIFY_BIX_CHANGED;
283            self.check_interrupt_assertion();
284        }
285    }
286}
287
288mod save_restore {
289    use super::*;
290    use vmcore::save_restore::RestoreError;
291    use vmcore::save_restore::SaveError;
292    use vmcore::save_restore::SaveRestore;
293
294    mod state {
295        use mesh::payload::Protobuf;
296        use vmcore::save_restore::SavedStateRoot;
297
298        #[derive(Protobuf, SavedStateRoot)]
299        #[mesh(package = "firmware.battery")]
300        pub struct SavedState {
301            #[mesh(1)]
302            pub notify_bits: u32,
303            #[mesh(2)]
304            pub battery_state: BatterySavedState,
305        }
306
307        #[derive(Protobuf, SavedStateRoot)]
308        #[mesh(package = "firmware.battery")]
309        pub struct BatterySavedState {
310            #[mesh(1)]
311            pub battery_present: bool,
312            #[mesh(2)]
313            pub charging: bool,
314            #[mesh(3)]
315            pub discharging: bool,
316            #[mesh(4)]
317            pub rate: u32,
318            #[mesh(5)]
319            pub remaining_capacity: u32,
320            #[mesh(6)]
321            pub max_capacity: u32,
322            #[mesh(7)]
323            pub ac_online: bool,
324        }
325    }
326
327    impl SaveRestore for BatteryDevice {
328        type SavedState = state::SavedState;
329
330        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
331            let Self {
332                rt: _,
333                mmio_region: _,
334                base_addr: _,
335                notify_bits,
336                state:
337                    HostBatteryUpdate {
338                        battery_present,
339                        charging,
340                        discharging,
341                        rate,
342                        remaining_capacity,
343                        max_capacity,
344                        ac_online,
345                    },
346            } = *self;
347
348            let saved_state = state::SavedState {
349                notify_bits,
350                battery_state: state::BatterySavedState {
351                    battery_present,
352                    charging,
353                    discharging,
354                    rate,
355                    remaining_capacity,
356                    max_capacity,
357                    ac_online,
358                },
359            };
360
361            Ok(saved_state)
362        }
363
364        fn restore(&mut self, saved_state: Self::SavedState) -> Result<(), RestoreError> {
365            let state::SavedState {
366                notify_bits,
367                battery_state:
368                    state::BatterySavedState {
369                        battery_present,
370                        charging,
371                        discharging,
372                        rate,
373                        remaining_capacity,
374                        max_capacity,
375                        ac_online,
376                    },
377            } = saved_state;
378
379            self.notify_bits = notify_bits;
380            self.state = HostBatteryUpdate {
381                battery_present,
382                charging,
383                discharging,
384                rate,
385                remaining_capacity,
386                max_capacity,
387                ac_online,
388            };
389
390            self.check_interrupt_assertion();
391
392            Ok(())
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use futures::task::Context;
401    use vmcore::line_interrupt::LineInterrupt;
402
403    fn create_test_platform() -> (BatteryDevice, mesh::Sender<HostBatteryUpdate>) {
404        let (tx, rx) = mesh::channel::<HostBatteryUpdate>();
405        let battery = BatteryDevice::new(
406            BatteryRuntimeDeps {
407                battery_status_recv: {
408                    tx.send(HostBatteryUpdate::default());
409                    rx
410                },
411                notify_interrupt: LineInterrupt::detached(),
412            },
413            BATTERY_MMIO_REGION_BASE_ADDRESS_X64,
414        );
415        (battery, tx)
416    }
417
418    fn mmio_read_helper(battery: &mut BatteryDevice, offset: u64) -> [u8; 4] {
419        let mut bytes = [0; 4];
420        battery
421            .mmio_read(battery.base_addr + offset, &mut bytes)
422            .unwrap();
423        bytes
424    }
425
426    fn check_mmio_read(battery: &mut BatteryDevice, offset: u64, expected_value: u32) {
427        assert_eq!(
428            u32::from_ne_bytes(mmio_read_helper(battery, offset)),
429            expected_value
430        );
431        assert_eq!(
432            battery.read_register(RegisterOffset(offset)),
433            expected_value
434        );
435    }
436
437    fn send_update(
438        battery: &mut BatteryDevice,
439        update: HostBatteryUpdate,
440        sender: &mesh::Sender<HostBatteryUpdate>,
441    ) {
442        sender.send(update);
443        battery.poll_device(&mut Context::from_waker(std::task::Waker::noop()));
444    }
445
446    /// Test basic battery mmio behavior
447    #[test]
448    fn test_basic_battery_mmio() {
449        // create battery, send channel, and a blank battery state
450        let (mut battery, tx) = create_test_platform();
451        let mut state = HostBatteryUpdate::default();
452
453        // test uninitialized state.
454        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0xF);
455        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0);
456        check_mmio_read(
457            &mut battery,
458            RegisterOffset::BST_BATTERY_PRESENT_RATE.0,
459            0xFFFFFFFF,
460        );
461        check_mmio_read(
462            &mut battery,
463            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
464            0xFFFFFFFF,
465        );
466        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0);
467        check_mmio_read(
468            &mut battery,
469            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
470            0,
471        );
472
473        // set battery to be present, with AC power and full capacity
474        state.battery_present = true;
475        state.remaining_capacity = 100;
476        state.max_capacity = 100;
477        state.ac_online = true;
478        send_update(&mut battery, state, &tx);
479
480        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0x1F);
481        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0);
482        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_PRESENT_RATE.0, 0);
483        check_mmio_read(
484            &mut battery,
485            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
486            (100 * VIRTUAL_BATTERY_CAPACITY) / 100,
487        );
488        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0x1);
489        check_mmio_read(
490            &mut battery,
491            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
492            2,
493        );
494
495        // set battery to be charging, with 50% capacity
496        state.charging = true;
497        state.remaining_capacity = 50;
498        state.rate = 40;
499        send_update(&mut battery, state, &tx);
500
501        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0x1F);
502        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0x2);
503        check_mmio_read(
504            &mut battery,
505            RegisterOffset::BST_BATTERY_PRESENT_RATE.0,
506            (40 * VIRTUAL_BATTERY_CAPACITY) / 100,
507        );
508        check_mmio_read(
509            &mut battery,
510            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
511            (50 * VIRTUAL_BATTERY_CAPACITY) / 100,
512        );
513        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0x1);
514        check_mmio_read(
515            &mut battery,
516            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
517            2,
518        );
519
520        // set battery to be discharging, no ac, 50% capacity
521        state.ac_online = false;
522        state.charging = false;
523        state.discharging = true;
524        state.rate = 45;
525        send_update(&mut battery, state, &tx);
526        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0x1F);
527        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0x1);
528        check_mmio_read(
529            &mut battery,
530            RegisterOffset::BST_BATTERY_PRESENT_RATE.0,
531            (45 * VIRTUAL_BATTERY_CAPACITY) / 100,
532        );
533        check_mmio_read(
534            &mut battery,
535            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
536            (50 * VIRTUAL_BATTERY_CAPACITY) / 100,
537        );
538        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0x0);
539        check_mmio_read(
540            &mut battery,
541            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
542            2,
543        );
544
545        // ensure that mmio_write clears the notify_bits
546        let data: u32 = 0x2;
547        let _ = battery.mmio_write(
548            battery.base_addr + RegisterOffset::BATTERY_ACPI_NOTIFY_CLEAR.0,
549            &data.to_ne_bytes(),
550        );
551        assert_eq!(battery.notify_bits, 0);
552    }
553
554    /// Test values when battery is not present
555    #[test]
556    fn test_battery_not_present() {
557        // create battery and send channel
558        let (mut battery, tx) = create_test_platform();
559
560        // set ac power online, no battery present
561        let state = HostBatteryUpdate {
562            ac_online: true,
563            ..HostBatteryUpdate::default()
564        };
565        send_update(&mut battery, state, &tx);
566        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0xF);
567        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0);
568        check_mmio_read(
569            &mut battery,
570            RegisterOffset::BST_BATTERY_PRESENT_RATE.0,
571            0xFFFFFFFF,
572        );
573        check_mmio_read(
574            &mut battery,
575            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
576            0xFFFFFFFF,
577        );
578        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0x1);
579        check_mmio_read(
580            &mut battery,
581            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
582            0x2,
583        );
584    }
585
586    /// Test bad register read/write operations and ensure they error
587    #[test]
588    fn test_bad_register_read_writes() {
589        // create battery and send channel
590        let (mut battery, _) = create_test_platform();
591
592        // Test reading with a data size not equal to size_of::<u32>()
593        let mut data = vec![0; size_of::<u32>() + 1];
594        match battery.mmio_read(battery.base_addr, &mut data) {
595            IoResult::Err(e) => assert!(matches!(e, IoError::InvalidAccessSize)),
596            _ => panic!("Expected error, but got Ok"),
597        }
598
599        // Test writing with a data size not equal to size_of::<u32>()
600        let data = vec![0; size_of::<u32>() + 1];
601        match battery.mmio_write(battery.base_addr, &data) {
602            IoResult::Err(e) => assert!(matches!(e, IoError::InvalidAccessSize)),
603            _ => panic!("Expected error, but got Ok"),
604        }
605
606        // Test writing with data that cannot be converted into a `u32`
607        let data = vec![0; size_of::<u32>() - 1];
608        match battery.mmio_write(battery.base_addr, &data) {
609            IoResult::Err(e) => assert!(matches!(e, IoError::InvalidAccessSize)),
610            _ => panic!("Expected error, but got Ok"),
611        }
612    }
613
614    /// Test bad capacity on a battery
615    #[test]
616    fn test_bad_battery_capacity() {
617        // create battery and send channel
618        let (mut battery, tx) = create_test_platform();
619
620        // set battery present with 50% capacity and 0 max capacity
621        let state = HostBatteryUpdate {
622            battery_present: true,
623            remaining_capacity: 50,
624            max_capacity: 0,
625            ac_online: true,
626            ..HostBatteryUpdate::default()
627        };
628        send_update(&mut battery, state, &tx);
629        check_mmio_read(&mut battery, RegisterOffset::STA_BATTERY_STATUS.0, 0xF);
630        check_mmio_read(&mut battery, RegisterOffset::BST_BATTERY_STATE.0, 0x0);
631        check_mmio_read(
632            &mut battery,
633            RegisterOffset::BST_BATTERY_PRESENT_RATE.0,
634            0xFFFFFFFF,
635        );
636        check_mmio_read(
637            &mut battery,
638            RegisterOffset::BST_BATTERY_REMAINING_CAPACITY.0,
639            0xFFFFFFFF,
640        );
641        check_mmio_read(&mut battery, RegisterOffset::PSR_AC_POWER_STATUS.0, 0x1);
642        check_mmio_read(
643            &mut battery,
644            RegisterOffset::BATTERY_ACPI_NOTIFY_STATUS.0,
645            0x2,
646        );
647    }
648}