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