xref: /aosp_15_r20/external/crosvm/power_monitor/src/powerd/monitor.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2024 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 //! Dbus monitor for polling signal from powerd to update power properties.
6 
7 use std::error::Error;
8 use std::os::unix::io::RawFd;
9 
10 use base::AsRawDescriptor;
11 use base::RawDescriptor;
12 use base::ReadNotifier;
13 use dbus::ffidisp::BusType;
14 use dbus::ffidisp::Connection;
15 use dbus::ffidisp::ConnectionItem;
16 use dbus::ffidisp::WatchEvent;
17 use protobuf::Message;
18 use remain::sorted;
19 use thiserror::Error;
20 
21 use crate::powerd::POWER_INTERFACE_NAME;
22 use crate::protos::power_supply_properties::PowerSupplyProperties;
23 use crate::BatteryData;
24 use crate::PowerData;
25 use crate::PowerMonitor;
26 
27 // Signal name from power_manager/dbus_constants.h.
28 const POLL_SIGNAL_NAME: &str = "PowerSupplyPoll";
29 
30 #[sorted]
31 #[derive(Error, Debug)]
32 pub enum DBusMonitorError {
33     #[error("failed to convert protobuf message: {0}")]
34     ConvertProtobuf(protobuf::Error),
35     #[error("failed to add D-Bus match rule: {0}")]
36     DBusAddMatch(dbus::Error),
37     #[error("failed to connect to D-Bus: {0}")]
38     DBusConnect(dbus::Error),
39     #[error("failed to read D-Bus message: {0}")]
40     DBusRead(dbus::arg::TypeMismatchError),
41     #[error("multiple D-Bus fds")]
42     MultipleDBusFd,
43     #[error("no D-Bus fd")]
44     NoDBusFd,
45 }
46 
47 pub struct DBusMonitor {
48     connection: Connection,
49     connection_fd: RawFd,
50     previous_data: Option<BatteryData>,
51 }
52 
53 impl DBusMonitor {
54     /// Connects and configures a new D-Bus connection to listen for powerd's PowerSupplyPoll
55     /// signal.
connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>>56     pub fn connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>> {
57         let connection =
58             Connection::get_private(BusType::System).map_err(DBusMonitorError::DBusConnect)?;
59         connection
60             .add_match(&format!(
61                 "interface='{}',member='{}'",
62                 POWER_INTERFACE_NAME, POLL_SIGNAL_NAME
63             ))
64             .map_err(DBusMonitorError::DBusAddMatch)?;
65         // Get the D-Bus connection's fd for async I/O. This should always return a single fd.
66         let fds: Vec<RawFd> = connection
67             .watch_fds()
68             .into_iter()
69             .filter(|w| w.readable())
70             .map(|w| w.fd())
71             .collect();
72         if fds.is_empty() {
73             return Err(DBusMonitorError::NoDBusFd.into());
74         }
75         if fds.len() > 1 {
76             return Err(DBusMonitorError::MultipleDBusFd.into());
77         }
78         Ok(Box::new(Self {
79             connection,
80             connection_fd: fds[0],
81             previous_data: None,
82         }))
83     }
84 }
85 
denoise_value(new_val: u32, prev_val: u32, margin: f64) -> u3286 fn denoise_value(new_val: u32, prev_val: u32, margin: f64) -> u32 {
87     if new_val.abs_diff(prev_val) as f64 / prev_val.min(new_val).max(1) as f64 >= margin {
88         new_val
89     } else {
90         prev_val
91     }
92 }
93 
94 impl PowerMonitor for DBusMonitor {
95     /// Returns the newest pending `PowerData` message, if any.
96     /// Callers should poll `PowerMonitor` to determine when messages are available.
read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>>97     fn read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>> {
98         // Select the newest available power message before converting to protobuf.
99         let newest_message: Option<dbus::Message> = self
100             .connection
101             .watch_handle(
102                 self.connection_fd,
103                 WatchEvent::Readable as std::os::raw::c_uint,
104             )
105             .fold(None, |last, item| match item {
106                 ConnectionItem::Signal(message) => {
107                     // Ignore non-matching signals: although match rules are configured, some system
108                     // signals can still get through, eg. NameAcquired.
109                     let interface = match message.interface() {
110                         Some(i) => i,
111                         None => {
112                             return last;
113                         }
114                     };
115 
116                     if &*interface != POWER_INTERFACE_NAME {
117                         return last;
118                     }
119 
120                     let member = match message.member() {
121                         Some(m) => m,
122                         None => {
123                             return last;
124                         }
125                     };
126 
127                     if &*member != POLL_SIGNAL_NAME {
128                         return last;
129                     }
130 
131                     Some(message)
132                 }
133                 _ => last,
134             });
135 
136         let previous_data = self.previous_data.take();
137         match newest_message {
138             Some(message) => {
139                 let data_bytes: Vec<u8> = message.read1().map_err(DBusMonitorError::DBusRead)?;
140                 let mut props = PowerSupplyProperties::new();
141                 props
142                     .merge_from_bytes(&data_bytes)
143                     .map_err(DBusMonitorError::ConvertProtobuf)?;
144                 let mut data: PowerData = props.into();
145                 if let (Some(new_data), Some(previous)) = (data.battery.as_mut(), previous_data) {
146                     // The raw information from powerd signals isn't really that useful to
147                     // the guest. Voltage/current are volatile values, so the .0333 hZ
148                     // snapshot provided by powerd isn't particularly meaningful. We do
149                     // need to provide *something*, but we might as well make it less noisy
150                     // to avoid having the guest try to process mostly useless information.
151                     // charge_counter is potentially useful to the guest, but it doesn't
152                     // need to be higher precision than battery.percent.
153                     new_data.voltage = denoise_value(new_data.voltage, previous.voltage, 0.1);
154                     new_data.current = denoise_value(new_data.current, previous.current, 0.1);
155                     new_data.charge_counter =
156                         denoise_value(new_data.charge_counter, previous.charge_counter, 0.01);
157                 }
158                 self.previous_data = data.battery;
159                 Ok(Some(data))
160             }
161             None => Ok(None),
162         }
163     }
164 }
165 
166 impl AsRawDescriptor for DBusMonitor {
as_raw_descriptor(&self) -> RawDescriptor167     fn as_raw_descriptor(&self) -> RawDescriptor {
168         self.connection_fd
169     }
170 }
171 
172 impl ReadNotifier for DBusMonitor {
get_read_notifier(&self) -> &dyn AsRawDescriptor173     fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
174         self
175     }
176 }
177