1 use btstack::suspend::{ISuspendCallback, SuspendType};
2 use btstack::RPCProxy;
3 use dbus::channel::MatchingReceiver;
4 use dbus::message::MatchRule;
5 use dbus::nonblock::SyncConnection;
6 use dbus_crossroads::Crossroads;
7 use dbus_projection::DisconnectWatcher;
8 use protobuf::{CodedInputStream, CodedOutputStream, Message};
9 use std::sync::{Arc, Mutex};
10 use std::time::Duration;
11 
12 use crate::dbus_iface::{export_suspend_callback_dbus_intf, SuspendDBus};
13 use crate::service_watcher::ServiceWatcher;
14 use crate::suspend::{
15     RegisterSuspendDelayReply, RegisterSuspendDelayRequest, SuspendDone, SuspendImminent,
16     SuspendImminent_Reason, SuspendReadinessInfo,
17 };
18 
19 const POWERD_SERVICE: &str = "org.chromium.PowerManager";
20 const POWERD_INTERFACE: &str = "org.chromium.PowerManager";
21 const POWERD_PATH: &str = "/org/chromium/PowerManager";
22 const ADAPTER_SERVICE: &str = "org.chromium.bluetooth";
23 const ADAPTER_SUSPEND_INTERFACE: &str = "org.chromium.bluetooth.Suspend";
24 const SUSPEND_IMMINENT_SIGNAL: &str = "SuspendImminent";
25 const SUSPEND_DONE_SIGNAL: &str = "SuspendDone";
26 const BTMANAGERD_NAME: &str = "Bluetooth Manager";
27 // powerd might take more than 2 seconds at initialization, and thus we use
28 // D-Bus default timeout duration herer to cover this case, as other D-Bus
29 // clients of powerd do.
30 const POWERD_DBUS_TIMEOUT: Duration = Duration::from_secs(20);
31 const BLUEZ_SERVICE: &str = "org.bluez";
32 
33 #[derive(Debug)]
34 enum SuspendManagerMessage {
35     PowerdStarted,
36     PowerdStopped,
37     SuspendImminentReceived(SuspendImminent),
38     SuspendDoneReceived(SuspendDone),
39     AdapterFound(dbus::Path<'static>),
40     AdapterRemoved,
41 }
42 
43 struct PowerdSession {
44     delay_id: i32,
45     powerd_proxy: dbus::nonblock::Proxy<'static, Arc<SyncConnection>>,
46 }
47 
48 /// Callback container for suspend interface callbacks.
49 pub(crate) struct SuspendCallback {
50     objpath: String,
51 
52     dbus_connection: Arc<SyncConnection>,
53     dbus_crossroads: Arc<Mutex<Crossroads>>,
54 
55     context: Arc<Mutex<SuspendManagerContext>>,
56 }
57 
58 impl SuspendCallback {
new( objpath: String, dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, context: Arc<Mutex<SuspendManagerContext>>, ) -> Self59     pub(crate) fn new(
60         objpath: String,
61         dbus_connection: Arc<SyncConnection>,
62         dbus_crossroads: Arc<Mutex<Crossroads>>,
63         context: Arc<Mutex<SuspendManagerContext>>,
64     ) -> Self {
65         Self { objpath, dbus_connection, dbus_crossroads, context }
66     }
67 }
68 
generate_proto_bytes<T: protobuf::Message>(request: &T) -> Option<Vec<u8>>69 fn generate_proto_bytes<T: protobuf::Message>(request: &T) -> Option<Vec<u8>> {
70     let mut proto_bytes: Vec<u8> = vec![];
71     let mut output_stream = CodedOutputStream::vec(&mut proto_bytes);
72     let write_result = request.write_to_with_cached_sizes(&mut output_stream);
73     if let Err(e) = write_result {
74         log::error!("Error serializing proto to bytes: {}", e);
75         return None;
76     }
77     Some(proto_bytes)
78 }
79 
80 // Convenient function to call HandleSuspendReadiness to powerd when we want to tell it that
81 // Bluetooth is ready to suspend.
send_handle_suspend_readiness( powerd_proxy: dbus::nonblock::Proxy<'static, Arc<SyncConnection>>, delay_id: i32, suspend_id: i32, )82 fn send_handle_suspend_readiness(
83     powerd_proxy: dbus::nonblock::Proxy<'static, Arc<SyncConnection>>,
84     delay_id: i32,
85     suspend_id: i32,
86 ) {
87     let mut suspend_readiness_info = SuspendReadinessInfo::new();
88     suspend_readiness_info.set_delay_id(delay_id);
89     suspend_readiness_info.set_suspend_id(suspend_id);
90 
91     if let Some(suspend_readiness_info_proto) = generate_proto_bytes(&suspend_readiness_info) {
92         tokio::spawn(async move {
93             log::debug!(
94                 "Sending HandleSuspendReadiness, delay id = {}, suspend id = {}",
95                 suspend_readiness_info.get_delay_id(),
96                 suspend_readiness_info.get_suspend_id()
97             );
98             let ret: Result<(), dbus::Error> = powerd_proxy
99                 .method_call(
100                     POWERD_INTERFACE,
101                     "HandleSuspendReadiness",
102                     (suspend_readiness_info_proto,),
103                 )
104                 .await;
105 
106             log::debug!("HandleSuspendReadiness returns {:?}", ret);
107             if let Err(e) = ret {
108                 log::error!("Error calling HandleSuspendReadiness: {}", e)
109             }
110         });
111     } else {
112         log::error!("Error writing SuspendReadinessInfo");
113     }
114 }
115 
116 impl ISuspendCallback for SuspendCallback {
on_callback_registered(&mut self, callback_id: u32)117     fn on_callback_registered(&mut self, callback_id: u32) {
118         log::debug!("Suspend callback registered, callback_id = {}", callback_id);
119     }
120 
on_suspend_ready(&mut self, suspend_id: i32)121     fn on_suspend_ready(&mut self, suspend_id: i32) {
122         // Received when adapter is ready to suspend. Tell powerd that suspend is ready.
123         log::debug!("Suspend ready, adapter suspend_id = {}", suspend_id);
124 
125         {
126             let context = self.context.lock().unwrap();
127 
128             if context.powerd_session.is_none() {
129                 log::warn!("No powerd session!");
130                 return;
131             }
132 
133             if let (Some(pending_suspend_imminent), Some(powerd_session)) =
134                 (&context.pending_suspend_imminent, &context.powerd_session)
135             {
136                 send_handle_suspend_readiness(
137                     powerd_session.powerd_proxy.clone(),
138                     powerd_session.delay_id,
139                     pending_suspend_imminent.get_suspend_id(),
140                 );
141             } else if let (Some(adapter_suspend_dbus), None) =
142                 (&context.adapter_suspend_dbus, &context.pending_suspend_imminent)
143             {
144                 log::info!("Suspend was aborted (imminent signal missing), so calling resume");
145                 let mut suspend_dbus_rpc = adapter_suspend_dbus.rpc.clone();
146                 tokio::spawn(async move {
147                     let result = suspend_dbus_rpc.resume().await;
148                     log::debug!("Adapter resume call, success = {}", result.unwrap_or(false));
149                 });
150             } else {
151                 log::warn!(
152                     "Suspend ready but powerd session valid={}, adapter available={}.",
153                     context.powerd_session.is_some(),
154                     context.adapter_suspend_dbus.is_some()
155                 );
156             }
157         }
158     }
159 
on_resumed(&mut self, suspend_id: i32)160     fn on_resumed(&mut self, suspend_id: i32) {
161         // Received when adapter is ready to suspend. This is just for our information and powerd
162         // doesn't need to know about this.
163         log::debug!("Suspend resumed, adapter suspend_id = {}", suspend_id);
164     }
165 }
166 
167 impl RPCProxy for SuspendCallback {
get_object_id(&self) -> String168     fn get_object_id(&self) -> String {
169         self.objpath.clone()
170     }
171 
export_for_rpc(self: Box<Self>)172     fn export_for_rpc(self: Box<Self>) {
173         let cr = self.dbus_crossroads.clone();
174         let iface = export_suspend_callback_dbus_intf(
175             self.dbus_connection.clone(),
176             &mut cr.lock().unwrap(),
177             Arc::new(Mutex::new(DisconnectWatcher::new())),
178         );
179         cr.lock().unwrap().insert(self.get_object_id(), &[iface], Arc::new(Mutex::new(self)));
180     }
181 }
182 
183 /// Holds the necessary information to coordinate suspend between powerd and btadapterd.
184 pub struct SuspendManagerContext {
185     dbus_crossroads: Arc<Mutex<Crossroads>>,
186     powerd_session: Option<PowerdSession>,
187     adapter_suspend_dbus: Option<SuspendDBus>,
188     pending_suspend_imminent: Option<SuspendImminent>,
189     pub tablet_mode: bool,
190 }
191 
192 /// Coordinates suspend events of Chromium OS's powerd with btadapter Suspend API.
193 pub struct PowerdSuspendManager {
194     context: Arc<Mutex<SuspendManagerContext>>,
195     conn: Arc<SyncConnection>,
196     tx: tokio::sync::mpsc::Sender<SuspendManagerMessage>,
197     rx: tokio::sync::mpsc::Receiver<SuspendManagerMessage>,
198 }
199 
200 impl PowerdSuspendManager {
201     /// Instantiates the suspend manager.
202     ///
203     /// `conn` and `dbus_crossroads` are D-Bus objects from `dbus` crate, to be used for both
204     /// communication with powerd and btadapterd.
new(conn: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>) -> Self205     pub fn new(conn: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>) -> Self {
206         let (tx, rx) = tokio::sync::mpsc::channel::<SuspendManagerMessage>(10);
207         Self {
208             context: Arc::new(Mutex::new(SuspendManagerContext {
209                 dbus_crossroads,
210                 powerd_session: None,
211                 adapter_suspend_dbus: None,
212                 pending_suspend_imminent: None,
213                 tablet_mode: false,
214             })),
215             conn,
216             tx,
217             rx,
218         }
219     }
220 
get_suspend_manager_context(&mut self) -> Arc<Mutex<SuspendManagerContext>>221     pub fn get_suspend_manager_context(&mut self) -> Arc<Mutex<SuspendManagerContext>> {
222         return self.context.clone();
223     }
224 
225     /// Sets up all required D-Bus listeners.
init(&mut self)226     pub async fn init(&mut self) {
227         // Watch events of powerd appearing or disappearing.
228         let powerd_watcher = ServiceWatcher::new(self.conn.clone(), String::from(POWERD_SERVICE));
229         let tx1 = self.tx.clone();
230         let tx2 = self.tx.clone();
231         powerd_watcher
232             .start_watch(
233                 Box::new(move || {
234                     let tx_clone = tx1.clone();
235                     tokio::spawn(async move {
236                         let _ = tx_clone.send(SuspendManagerMessage::PowerdStarted).await;
237                     });
238                 }),
239                 Box::new(move || {
240                     let tx_clone = tx2.clone();
241                     tokio::spawn(async move {
242                         let _ = tx_clone.send(SuspendManagerMessage::PowerdStopped).await;
243                     });
244                 }),
245             )
246             .await;
247 
248         // Watch events of btadapterd appearing or disappearing.
249         let mut adapter_watcher =
250             ServiceWatcher::new(self.conn.clone(), String::from(ADAPTER_SERVICE));
251         let tx1 = self.tx.clone();
252         let tx2 = self.tx.clone();
253         adapter_watcher
254             .start_watch_interface(
255                 String::from(ADAPTER_SUSPEND_INTERFACE),
256                 Box::new(move |path| {
257                     let tx_clone = tx1.clone();
258                     tokio::spawn(async move {
259                         let _ = tx_clone.send(SuspendManagerMessage::AdapterFound(path)).await;
260                     });
261                 }),
262                 Box::new(move || {
263                     let tx_clone = tx2.clone();
264                     tokio::spawn(async move {
265                         let _ = tx_clone.send(SuspendManagerMessage::AdapterRemoved).await;
266                     });
267                 }),
268             )
269             .await;
270 
271         // Watch events of bluez appearing or disappearing.
272         // This is with the assumption that only one instance of btadapterd and bluez can be alive
273         // at a time.
274         let mut bluez_watcher = ServiceWatcher::new(self.conn.clone(), String::from(BLUEZ_SERVICE));
275         let tx1 = self.tx.clone();
276         let tx2 = self.tx.clone();
277         bluez_watcher
278             .start_watch_interface(
279                 String::from(ADAPTER_SUSPEND_INTERFACE),
280                 Box::new(move |path| {
281                     let tx_clone = tx1.clone();
282                     tokio::spawn(async move {
283                         let _ = tx_clone.send(SuspendManagerMessage::AdapterFound(path)).await;
284                     });
285                 }),
286                 Box::new(move || {
287                     let tx_clone = tx2.clone();
288                     tokio::spawn(async move {
289                         let _ = tx_clone.send(SuspendManagerMessage::AdapterRemoved).await;
290                     });
291                 }),
292             )
293             .await;
294 
295         // Watch for SuspendImminent signal from powerd.
296         let mr = MatchRule::new_signal(POWERD_INTERFACE, SUSPEND_IMMINENT_SIGNAL)
297             .with_sender(POWERD_SERVICE)
298             .with_path(POWERD_PATH);
299         self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
300 
301         let tx = self.tx.clone();
302         self.conn.start_receive(
303             mr,
304             Box::new(move |msg, _conn| {
305                 if let Some(bytes) = msg.get1::<Vec<u8>>() {
306                     let mut suspend_imminent = SuspendImminent::new();
307                     let mut input_stream = CodedInputStream::from_bytes(&bytes[..]);
308                     let decode_result = suspend_imminent.merge_from(&mut input_stream);
309                     if let Err(e) = decode_result {
310                         log::error!("Error decoding SuspendImminent signal: {}", e);
311                     } else {
312                         let tx_clone = tx.clone();
313                         tokio::spawn(async move {
314                             let _ = tx_clone
315                                 .send(SuspendManagerMessage::SuspendImminentReceived(
316                                     suspend_imminent,
317                                 ))
318                                 .await;
319                         });
320                     }
321                 } else {
322                     log::warn!("received empty SuspendImminent signal");
323                 }
324 
325                 true
326             }),
327         );
328 
329         // Watch for SuspendDone signal from powerd.
330         let mr = MatchRule::new_signal(POWERD_INTERFACE, SUSPEND_DONE_SIGNAL)
331             .with_sender(POWERD_SERVICE)
332             .with_path(POWERD_PATH);
333         self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
334         let tx = self.tx.clone();
335         self.conn.start_receive(
336             mr,
337             Box::new(move |msg, _conn| {
338                 if let Some(bytes) = msg.get1::<Vec<u8>>() {
339                     let mut suspend_done = SuspendDone::new();
340                     let mut input_stream = CodedInputStream::from_bytes(&bytes[..]);
341                     let decode_result = suspend_done.merge_from(&mut input_stream);
342                     if let Err(e) = decode_result {
343                         log::error!("Error decoding SuspendDone signal: {}", e);
344                     } else {
345                         let tx_clone = tx.clone();
346                         tokio::spawn(async move {
347                             let _ = tx_clone
348                                 .send(SuspendManagerMessage::SuspendDoneReceived(suspend_done))
349                                 .await;
350                         });
351                     }
352                 } else {
353                     log::warn!("received empty SuspendDone signal");
354                 }
355 
356                 true
357             }),
358         );
359     }
360 
361     /// Starts the event handlers.
mainloop(&mut self)362     pub async fn mainloop(&mut self) {
363         loop {
364             let m = self.rx.recv().await;
365 
366             if let Some(msg) = m {
367                 match msg {
368                     SuspendManagerMessage::PowerdStarted => self.on_powerd_started().await,
369                     SuspendManagerMessage::PowerdStopped => self.on_powerd_stopped(),
370                     SuspendManagerMessage::SuspendImminentReceived(suspend_imminent) => {
371                         self.on_suspend_imminent(suspend_imminent)
372                     }
373                     SuspendManagerMessage::SuspendDoneReceived(suspend_done) => {
374                         self.on_suspend_done(suspend_done)
375                     }
376                     SuspendManagerMessage::AdapterFound(object_path) => {
377                         self.on_adapter_found(object_path)
378                     }
379                     SuspendManagerMessage::AdapterRemoved => self.on_adapter_removed(),
380                 }
381             } else {
382                 log::debug!("Exiting suspend manager mainloop");
383                 break;
384             }
385         }
386     }
387 
on_powerd_started(&mut self)388     async fn on_powerd_started(&mut self) {
389         // As soon as powerd is available, we need to register to be a suspend readiness reporter.
390 
391         log::debug!("powerd started, initializing suspend manager");
392 
393         if self.context.lock().unwrap().powerd_session.is_some() {
394             log::warn!("powerd session already exists, cleaning up first");
395             self.on_powerd_stopped();
396         }
397 
398         let conn = self.conn.clone();
399         let powerd_proxy =
400             dbus::nonblock::Proxy::new(POWERD_SERVICE, POWERD_PATH, POWERD_DBUS_TIMEOUT, conn);
401 
402         let mut request = RegisterSuspendDelayRequest::new();
403         request.set_description(String::from(BTMANAGERD_NAME));
404 
405         if let Some(register_suspend_delay_proto) = generate_proto_bytes(&request) {
406             let result: Result<(Vec<u8>,), dbus::Error> = powerd_proxy
407                 .method_call(
408                     POWERD_INTERFACE,
409                     "RegisterSuspendDelay",
410                     (register_suspend_delay_proto,),
411                 )
412                 .await;
413 
414             match result {
415                 Err(e) => {
416                     log::error!("D-Bus error: {:?}", e);
417                 }
418                 Ok((return_proto,)) => {
419                     let mut reply = RegisterSuspendDelayReply::new();
420                     let mut input_stream = CodedInputStream::from_bytes(&return_proto[..]);
421                     let decode_result = reply.merge_from(&mut input_stream);
422                     if let Err(e) = decode_result {
423                         log::error!("Error decoding RegisterSuspendDelayReply {:?}", e);
424                     }
425 
426                     log::debug!("Suspend delay id = {}", reply.get_delay_id());
427 
428                     self.context.lock().unwrap().powerd_session =
429                         Some(PowerdSession { delay_id: reply.get_delay_id(), powerd_proxy });
430                 }
431             }
432         } else {
433             log::error!("Error writing RegisterSuspendDelayRequest");
434         }
435     }
436 
on_powerd_stopped(&mut self)437     fn on_powerd_stopped(&mut self) {
438         // TODO: Consider an edge case where powerd unexpectedly is stopped (maybe crashes) but we
439         // still have pending SuspendImminent.
440         log::debug!("powerd stopped, cleaning up");
441 
442         {
443             let mut context = self.context.lock().unwrap();
444 
445             match context.powerd_session {
446                 None => log::warn!("powerd session does not exist, ignoring"),
447                 Some(_) => context.powerd_session = None,
448             }
449         }
450     }
451 
on_suspend_imminent(&mut self, suspend_imminent: SuspendImminent)452     fn on_suspend_imminent(&mut self, suspend_imminent: SuspendImminent) {
453         // powerd is telling us that system is about to suspend, if available tell btadapterd to
454         // prepare for suspend.
455 
456         log::debug!(
457             "received suspend imminent: suspend_id = {:?}, reason = {:?}",
458             suspend_imminent.get_suspend_id(),
459             suspend_imminent.get_reason()
460         );
461 
462         if self.context.lock().unwrap().pending_suspend_imminent.is_some() {
463             log::warn!("SuspendImminent signal received while there is a pending suspend imminent");
464         }
465 
466         self.context.lock().unwrap().pending_suspend_imminent = Some(suspend_imminent.clone());
467 
468         {
469             // Anonymous block to contain locked `self.context` which needs to be called multiple
470             // times in the `if let` block below. Prevent deadlock by locking only once.
471             let mut context_locked = self.context.lock().unwrap();
472             let tablet_mode = context_locked.tablet_mode;
473 
474             if let Some(adapter_suspend_dbus) = &mut context_locked.adapter_suspend_dbus {
475                 let mut suspend_dbus_rpc = adapter_suspend_dbus.rpc.clone();
476                 tokio::spawn(async move {
477                     let result = suspend_dbus_rpc
478                         .suspend(
479                             match (tablet_mode, suspend_imminent.get_reason()) {
480                                 // No wakes allowed on tablet mode.
481                                 (true, _) => SuspendType::NoWakesAllowed,
482 
483                                 // When not in tablet mode, choose wake type based on suspend
484                                 // reason.
485                                 (false, SuspendImminent_Reason::IDLE) => {
486                                     SuspendType::AllowWakeFromHid
487                                 }
488                                 (false, SuspendImminent_Reason::LID_CLOSED) => {
489                                     SuspendType::NoWakesAllowed
490                                 }
491                                 (false, SuspendImminent_Reason::OTHER) => SuspendType::Other,
492                             },
493                             suspend_imminent.get_suspend_id(),
494                         )
495                         .await;
496 
497                     log::debug!("Adapter suspend call, success = {}", result.is_ok());
498                 });
499             } else {
500                 // If there is no adapter, that means Bluetooth is not active and we should always
501                 // tell powerd that we are ready to suspend.
502                 log::debug!("Adapter not available, suspend is ready.");
503                 if let Some(session) = &context_locked.powerd_session {
504                     send_handle_suspend_readiness(
505                         session.powerd_proxy.clone(),
506                         session.delay_id,
507                         suspend_imminent.get_suspend_id(),
508                     );
509                 } else {
510                     log::warn!("SuspendImminent is received when there is no powerd session");
511                 }
512             }
513         }
514     }
515 
on_suspend_done(&mut self, suspend_done: SuspendDone)516     fn on_suspend_done(&mut self, suspend_done: SuspendDone) {
517         // powerd is telling us that suspend is done (system has resumed), so we tell btadapterd
518         // to resume too.
519         // powerd also sends the suspend done signal when the suspend is aborted. The suspend
520         // imminent signal is cancelled here, so the cleanup is done after the suspend is ready.
521 
522         log::debug!("SuspendDone received: {:?}", suspend_done);
523 
524         if self.context.lock().unwrap().pending_suspend_imminent.is_none() {
525             log::warn!("Received SuspendDone signal when there is no pending SuspendImminent");
526         }
527 
528         self.context.lock().unwrap().pending_suspend_imminent = None;
529 
530         if let Some(adapter_suspend_dbus) = &self.context.lock().unwrap().adapter_suspend_dbus {
531             let mut suspend_dbus_rpc = adapter_suspend_dbus.rpc.clone();
532             tokio::spawn(async move {
533                 let result = suspend_dbus_rpc.resume().await;
534                 log::debug!("Adapter resume call, success = {}", result.unwrap_or(false));
535             });
536         } else {
537             log::debug!("Adapter is not available, nothing to resume.");
538         }
539     }
540 
on_adapter_found(&mut self, path: dbus::Path<'static>)541     fn on_adapter_found(&mut self, path: dbus::Path<'static>) {
542         log::debug!("Found adapter suspend {:?}", path);
543 
544         let conn = self.conn.clone();
545         self.context.lock().unwrap().adapter_suspend_dbus =
546             Some(SuspendDBus::new(conn.clone(), path));
547 
548         let crossroads = self.context.lock().unwrap().dbus_crossroads.clone();
549 
550         if let Some(adapter_suspend_dbus) = &mut self.context.lock().unwrap().adapter_suspend_dbus {
551             let mut suspend_dbus_rpc = adapter_suspend_dbus.rpc.clone();
552             let context = self.context.clone();
553             tokio::spawn(async move {
554                 let suspend_cb_objpath: String =
555                     format!("/org/chromium/bluetooth/Manager/suspend_callback");
556                 let status = suspend_dbus_rpc
557                     .register_callback(Box::new(SuspendCallback::new(
558                         suspend_cb_objpath,
559                         conn,
560                         crossroads,
561                         context.clone(),
562                     )))
563                     .await;
564                 log::debug!("Suspend::RegisterCallback success = {}", status.unwrap_or(false));
565             });
566         }
567     }
568 
on_adapter_removed(&mut self)569     fn on_adapter_removed(&mut self) {
570         log::debug!("Adapter suspend removed");
571         self.context.lock().unwrap().adapter_suspend_dbus = None;
572     }
573 }
574