• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Suspend/Resume API.
2 
3 use crate::bluetooth::{
4     AdapterActions, Bluetooth, BluetoothDevice, BtifBluetoothCallbacks, IBluetooth,
5     IBluetoothConnectionCallback,
6 };
7 use crate::bluetooth_media::BluetoothMedia;
8 use crate::callbacks::Callbacks;
9 use crate::{BluetoothGatt, Message, RPCProxy};
10 use bt_topshim::btif::{BluetoothInterface, BtStatus, RawAddress};
11 use bt_topshim::metrics;
12 use log::warn;
13 use num_derive::{FromPrimitive, ToPrimitive};
14 use std::collections::HashSet;
15 use std::iter::FromIterator;
16 use std::sync::{Arc, Mutex, MutexGuard};
17 use tokio::sync::mpsc::Sender;
18 use tokio::task::JoinHandle;
19 use tokio::time;
20 use tokio::time::Duration;
21 
22 use bt_utils::socket::{BtSocket, HciChannels, MgmtCommand, HCI_DEV_NONE};
23 
24 /// Defines the Suspend/Resume API.
25 ///
26 /// This API is exposed by `btadapterd` and independent of the suspend/resume detection mechanism
27 /// which depends on the actual operating system the daemon runs on. Possible clients of this API
28 /// include `btmanagerd` with Chrome OS `powerd` integration, `btmanagerd` with systemd Inhibitor
29 /// interface, or any script hooked to suspend/resume events.
30 pub trait ISuspend {
31     /// Adds an observer to suspend events.
32     ///
33     /// Returns true if the callback can be registered.
register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool34     fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool;
35 
36     /// Removes an observer to suspend events.
37     ///
38     /// Returns true if the callback can be removed, false if `callback_id` is not recognized.
unregister_callback(&mut self, callback_id: u32) -> bool39     fn unregister_callback(&mut self, callback_id: u32) -> bool;
40 
41     /// Prepares the stack for suspend, identified by `suspend_id`.
42     ///
43     /// Returns a positive number identifying the suspend if it can be started. If there is already
44     /// a suspend, that active suspend id is returned.
suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)45     fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32);
46 
47     /// Undoes previous suspend preparation identified by `suspend_id`.
48     ///
49     /// Returns true if suspend can be resumed, and false if there is no suspend to resume.
resume(&mut self) -> bool50     fn resume(&mut self) -> bool;
51 }
52 
53 /// Suspend events.
54 pub trait ISuspendCallback: RPCProxy {
55     /// Triggered when a callback is registered and given an identifier `callback_id`.
on_callback_registered(&mut self, callback_id: u32)56     fn on_callback_registered(&mut self, callback_id: u32);
57 
58     /// Triggered when the stack is ready for suspend and tell the observer the id of the suspend.
on_suspend_ready(&mut self, suspend_id: i32)59     fn on_suspend_ready(&mut self, suspend_id: i32);
60 
61     /// Triggered when the stack has resumed the previous suspend.
on_resumed(&mut self, suspend_id: i32)62     fn on_resumed(&mut self, suspend_id: i32);
63 }
64 
65 /// When we resume, we will want to reconnect audio devices that were previously connected.
66 /// However, we will need to delay a few seconds to avoid co-ex issues with Wi-Fi reconnection.
67 const RECONNECT_AUDIO_ON_RESUME_DELAY_MS: u64 = 3000;
68 
69 /// Delay sending suspend ready signal by some time because HCI commands are async and we could
70 /// still receive some commands/events after all LibBluetooth functions have returned.
71 const SUSPEND_READY_DELAY_MS: u64 = 100;
72 
notify_suspend_state(hci_index: u16, suspended: bool)73 fn notify_suspend_state(hci_index: u16, suspended: bool) {
74     log::debug!("Notify kernel suspend status: {} for hci{}", suspended, hci_index);
75     let mut btsock = BtSocket::new();
76     match btsock.open() {
77         -1 => {
78             panic!(
79                 "Bluetooth socket unavailable (errno {}). Try loading the kernel module first.",
80                 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
81             );
82         }
83         x => log::debug!("notify suspend Socket open at fd: {}", x),
84     }
85     // Bind to control channel (which is used for mgmt commands). We provide
86     // HCI_DEV_NONE because we don't actually need a valid HCI dev for some MGMT commands.
87     match btsock.bind_channel(HciChannels::Control, HCI_DEV_NONE) {
88         -1 => {
89             panic!(
90                 "Failed to bind control channel with errno={}",
91                 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
92             );
93         }
94         _ => (),
95     };
96 
97     let command = MgmtCommand::FlossNotifySuspendState(hci_index, suspended);
98     let bytes_written = btsock.write_mgmt_packet(command.into());
99     if bytes_written <= 0 {
100         log::error!("Failed to notify suspend state on hci:{} to {}", hci_index, suspended);
101     }
102 }
103 
104 pub enum SuspendActions {
105     CallbackRegistered(u32),
106     CallbackDisconnected(u32),
107     SuspendReady(i32),
108     ResumeReady(i32),
109     AudioReconnectOnResumeComplete,
110     DeviceDisconnected(RawAddress),
111 }
112 
113 #[derive(Debug, FromPrimitive, ToPrimitive)]
114 #[repr(u32)]
115 pub enum SuspendType {
116     NoWakesAllowed,
117     AllowWakeFromHid,
118     Other,
119 }
120 
121 struct SuspendState {
122     suspend_expected: bool,
123     suspend_id: Option<i32>,
124     wake_allowed: bool,
125 
126     disconnect_expected: HashSet<RawAddress>,
127     disconnect_timeout_timer: Option<JoinHandle<()>>,
128 
129     delay_timer: Option<JoinHandle<()>>,
130 }
131 
132 impl SuspendState {
new() -> SuspendState133     fn new() -> SuspendState {
134         Self {
135             suspend_expected: false,
136             suspend_id: None,
137             wake_allowed: false,
138             disconnect_expected: HashSet::default(),
139             disconnect_timeout_timer: None,
140             delay_timer: None,
141         }
142     }
143 
144     // The tasks should remove their timer when they are done, so if all are None then we're ready.
ready_to_suspend(&self) -> bool145     fn ready_to_suspend(&self) -> bool {
146         self.delay_timer.is_none() && self.disconnect_timeout_timer.is_none()
147     }
148 }
149 
150 /// Implementation of the suspend API.
151 pub struct Suspend {
152     bt: Arc<Mutex<Box<Bluetooth>>>,
153     intf: Arc<Mutex<BluetoothInterface>>,
154     gatt: Arc<Mutex<Box<BluetoothGatt>>>,
155     media: Arc<Mutex<Box<BluetoothMedia>>>,
156     tx: Sender<Message>,
157     callbacks: Callbacks<dyn ISuspendCallback + Send>,
158 
159     /// This list keeps track of audio devices that had an audio profile before
160     /// suspend so that we can attempt to connect after suspend.
161     audio_reconnect_list: Vec<BluetoothDevice>,
162 
163     /// Active reconnection attempt after resume.
164     audio_reconnect_joinhandle: Option<JoinHandle<()>>,
165 
166     suspend_state: Arc<Mutex<SuspendState>>,
167 }
168 
169 impl Suspend {
new( bt: Arc<Mutex<Box<Bluetooth>>>, intf: Arc<Mutex<BluetoothInterface>>, gatt: Arc<Mutex<Box<BluetoothGatt>>>, media: Arc<Mutex<Box<BluetoothMedia>>>, tx: Sender<Message>, ) -> Suspend170     pub fn new(
171         bt: Arc<Mutex<Box<Bluetooth>>>,
172         intf: Arc<Mutex<BluetoothInterface>>,
173         gatt: Arc<Mutex<Box<BluetoothGatt>>>,
174         media: Arc<Mutex<Box<BluetoothMedia>>>,
175         tx: Sender<Message>,
176     ) -> Suspend {
177         bt.lock()
178             .unwrap()
179             .register_connection_callback(Box::new(BluetoothConnectionCallbacks::new(tx.clone())));
180         Self {
181             bt,
182             intf,
183             gatt,
184             media,
185             tx: tx.clone(),
186             callbacks: Callbacks::new(tx.clone(), |id| {
187                 Message::SuspendActions(SuspendActions::CallbackDisconnected(id))
188             }),
189             audio_reconnect_list: Vec::new(),
190             audio_reconnect_joinhandle: None,
191             suspend_state: Arc::new(Mutex::new(SuspendState::new())),
192         }
193     }
194 
handle_action(&mut self, action: SuspendActions)195     pub(crate) fn handle_action(&mut self, action: SuspendActions) {
196         match action {
197             SuspendActions::CallbackRegistered(id) => {
198                 self.callback_registered(id);
199             }
200             SuspendActions::CallbackDisconnected(id) => {
201                 self.remove_callback(id);
202             }
203             SuspendActions::SuspendReady(suspend_id) => {
204                 self.suspend_ready(suspend_id);
205             }
206             SuspendActions::ResumeReady(suspend_id) => {
207                 self.resume_ready(suspend_id);
208             }
209             SuspendActions::AudioReconnectOnResumeComplete => {
210                 self.audio_reconnect_complete();
211             }
212             SuspendActions::DeviceDisconnected(addr) => {
213                 self.device_disconnected(addr);
214             }
215         }
216     }
217 
callback_registered(&mut self, id: u32)218     fn callback_registered(&mut self, id: u32) {
219         match self.callbacks.get_by_id_mut(id) {
220             Some(callback) => callback.on_callback_registered(id),
221             None => warn!("Suspend callback {} does not exist", id),
222         }
223     }
224 
remove_callback(&mut self, id: u32) -> bool225     fn remove_callback(&mut self, id: u32) -> bool {
226         self.callbacks.remove_callback(id)
227     }
228 
suspend_ready(&mut self, suspend_id: i32)229     fn suspend_ready(&mut self, suspend_id: i32) {
230         let mut suspend_state = self.suspend_state.lock().unwrap();
231         if !suspend_state.ready_to_suspend() {
232             return;
233         }
234         if !suspend_state.suspend_expected {
235             // We might already send out a ready.
236             return;
237         }
238         suspend_state.suspend_expected = false;
239         let hci_index = self.bt.lock().unwrap().get_hci_index();
240         notify_suspend_state(hci_index, true);
241         self.callbacks.for_all_callbacks(|callback| {
242             callback.on_suspend_ready(suspend_id);
243         });
244     }
245 
resume_ready(&mut self, suspend_id: i32)246     fn resume_ready(&mut self, suspend_id: i32) {
247         self.callbacks.for_all_callbacks(|callback| {
248             callback.on_resumed(suspend_id);
249         });
250     }
251 
252     /// On resume, we attempt to reconnect to any audio devices connected during suspend.
253     /// This marks this attempt as completed and we should clear the pending reconnects here.
audio_reconnect_complete(&mut self)254     fn audio_reconnect_complete(&mut self) {
255         self.audio_reconnect_list.clear();
256         self.audio_reconnect_joinhandle = None;
257     }
258 
device_disconnected(&mut self, addr: RawAddress)259     fn device_disconnected(&mut self, addr: RawAddress) {
260         let mut suspend_state = self.suspend_state.lock().unwrap();
261         if !suspend_state.disconnect_expected.remove(&addr) {
262             // Not interested device, or we are not suspending.
263             return;
264         }
265         if !suspend_state.disconnect_expected.is_empty() {
266             return;
267         }
268         if let Some(h) = suspend_state.disconnect_timeout_timer.take() {
269             h.abort();
270         }
271         Self::all_acls_disconnected(
272             self.tx.clone(),
273             &mut suspend_state,
274             self.suspend_state.clone(),
275             self.intf.clone(),
276         );
277     }
278 
279     /// Continues the suspend process after all ACLs are disconnected.
280     ///
281     /// Two SuspendState are passed as arguments here because we want the mutex to be held during
282     /// the whole device disconnect process. Thus, the caller of this function should provide a
283     /// MutexGuard, while an Arc is also needed for this function to compose an async task.
284     ///
285     /// After ReentrantLock (https://github.com/rust-lang/rust/issues/121440) is supported, we
286     /// shall replace them with a single Arc<ReentrantLock<SuspendState>>.
all_acls_disconnected( tx: Sender<Message>, suspend_state: &mut MutexGuard<SuspendState>, suspend_state_cloned: Arc<Mutex<SuspendState>>, intf: Arc<Mutex<BluetoothInterface>>, )287     fn all_acls_disconnected(
288         tx: Sender<Message>,
289         suspend_state: &mut MutexGuard<SuspendState>,
290         suspend_state_cloned: Arc<Mutex<SuspendState>>,
291         intf: Arc<Mutex<BluetoothInterface>>,
292     ) {
293         let suspend_id = suspend_state
294             .suspend_id
295             .expect("life cycle of suspend_id must be longer than disconnect_timeout_timer");
296         let wake_allowed = suspend_state.wake_allowed;
297         suspend_state.disconnect_timeout_timer = Some(tokio::spawn(async move {
298             if wake_allowed {
299                 intf.lock().unwrap().allow_wake_by_hid();
300                 // Allow wake is async. Wait for a little while.
301                 time::sleep(Duration::from_millis(SUSPEND_READY_DELAY_MS)).await;
302             }
303             suspend_state_cloned.lock().unwrap().disconnect_timeout_timer = None;
304             let _result =
305                 tx.send(Message::SuspendActions(SuspendActions::SuspendReady(suspend_id))).await;
306         }));
307     }
308 
get_connected_audio_devices(&self) -> Vec<BluetoothDevice>309     fn get_connected_audio_devices(&self) -> Vec<BluetoothDevice> {
310         let bonded_connected = self.bt.lock().unwrap().get_bonded_and_connected_devices();
311         self.media.lock().unwrap().filter_to_connected_audio_devices_from(&bonded_connected)
312     }
313 }
314 
315 impl ISuspend for Suspend {
register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool316     fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool {
317         let id = self.callbacks.add_callback(callback);
318 
319         let tx = self.tx.clone();
320         tokio::spawn(async move {
321             let _result =
322                 tx.send(Message::SuspendActions(SuspendActions::CallbackRegistered(id))).await;
323         });
324 
325         true
326     }
327 
unregister_callback(&mut self, callback_id: u32) -> bool328     fn unregister_callback(&mut self, callback_id: u32) -> bool {
329         self.remove_callback(callback_id)
330     }
331 
suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)332     fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32) {
333         let mut suspend_state = self.suspend_state.lock().unwrap();
334         // Set suspend state as true, prevent an early resume.
335         suspend_state.suspend_expected = true;
336         suspend_state.suspend_id = Some(suspend_id);
337         // Treat Other the same as AllowWakeFromHid
338         suspend_state.wake_allowed =
339             matches!(suspend_type, SuspendType::AllowWakeFromHid | SuspendType::Other);
340 
341         self.bt.lock().unwrap().scan_mode_enter_suspend();
342         self.intf.lock().unwrap().clear_event_filter();
343         self.intf.lock().unwrap().clear_filter_accept_list();
344 
345         self.bt.lock().unwrap().discovery_enter_suspend();
346         self.gatt.lock().unwrap().advertising_enter_suspend();
347         self.gatt.lock().unwrap().scan_enter_suspend();
348 
349         // Track connected audio devices and queue them for reconnect on resume.
350         // If we still have the previous reconnect list left-over, do not try
351         // to collect a new list here.
352         if self.audio_reconnect_list.is_empty() {
353             self.audio_reconnect_list = self.get_connected_audio_devices();
354         }
355 
356         // Cancel any active reconnect task.
357         if let Some(joinhandle) = &self.audio_reconnect_joinhandle {
358             joinhandle.abort();
359             self.audio_reconnect_joinhandle = None;
360         }
361 
362         // Now we have some async tasks to do and need to wait for some events.
363         // For each task we need to schedule a timeout timer to ensure we suspend eventually.
364         // When a task is done, it should remove the timer and send a SuspendReady event.
365         // |ready_to_suspend| shall check that all tasks have done (removed their timer).
366 
367         // This doesn't do anything but simply wait for a short delay to ensure the above functions
368         // have finished (LibBluetooth may leave some async tasks).
369         if let Some(h) = suspend_state.delay_timer.take() {
370             log::warn!("Suspend: Found a leftover timer for delay task");
371             h.abort();
372         }
373         let tx = self.tx.clone();
374         let suspend_state_cloned = self.suspend_state.clone();
375         suspend_state.delay_timer = Some(tokio::spawn(async move {
376             time::sleep(Duration::from_millis(SUSPEND_READY_DELAY_MS)).await;
377             suspend_state_cloned.lock().unwrap().delay_timer = None;
378             let _result =
379                 tx.send(Message::SuspendActions(SuspendActions::SuspendReady(suspend_id))).await;
380         }));
381 
382         // Disconnect all ACLs and wait until all devices have disconnected.
383         if let Some(h) = suspend_state.disconnect_timeout_timer.take() {
384             log::warn!("Suspend: Found a leftover timer for disconnect");
385             h.abort();
386         }
387         suspend_state.disconnect_expected = HashSet::from_iter(
388             self.bt.lock().unwrap().get_connected_devices().iter().map(|d| d.address),
389         );
390         let tx = self.tx.clone();
391         let suspend_state_cloned = self.suspend_state.clone();
392         let intf_cloned = self.intf.clone();
393         if suspend_state.disconnect_expected.is_empty() {
394             // No need to set a timeout timer as no disconnection is expected.
395             Self::all_acls_disconnected(tx, &mut suspend_state, suspend_state_cloned, intf_cloned);
396         } else {
397             self.intf.lock().unwrap().disconnect_all_acls();
398             suspend_state.disconnect_timeout_timer = Some(tokio::spawn(async move {
399                 time::sleep(Duration::from_millis(2000)).await;
400                 log::error!("Suspend disconnect did not complete in 2s, continuing anyway.");
401                 let mut suspend_state = suspend_state_cloned.lock().unwrap();
402                 // Cleanup disconnect_expected so |device_disconnected| won't be triggered.
403                 suspend_state.disconnect_expected = HashSet::default();
404                 // Continue the suspend. There might be some disconnection events later and if the
405                 // device is not in the lid-closed state, the device might be awaken. This shall be
406                 // a really rare case and it's hard to handle as setting a event mask could break
407                 // the state machine in LibBluetooth.
408                 // We could consider increase the timeout in the future if this happens too often.
409                 Self::all_acls_disconnected(
410                     tx,
411                     &mut suspend_state,
412                     suspend_state_cloned.clone(),
413                     intf_cloned,
414                 );
415             }));
416         }
417     }
418 
resume(&mut self) -> bool419     fn resume(&mut self) -> bool {
420         let mut suspend_state = self.suspend_state.lock().unwrap();
421         // Suspend is not ready (e.g. aborted early), delay cleanup after SuspendReady.
422         if suspend_state.suspend_expected {
423             log::error!("Suspend is expected but not ready, abort resume.");
424             return false;
425         }
426 
427         // Suspend ID state 0: NoRecord, 1: Recorded
428         let suspend_id = match suspend_state.suspend_id {
429             None => {
430                 log::error!("No suspend id saved at resume.");
431                 metrics::suspend_complete_state(0);
432                 // If no suspend id is saved here, it means floss did not receive the
433                 // SuspendImminent signal and as a result, the suspend flow was not run.
434                 // Skip the resume flow and return after logging the metrics.
435                 return true;
436             }
437             Some(id) => {
438                 metrics::suspend_complete_state(1);
439                 id
440             }
441         };
442 
443         let hci_index = self.bt.lock().unwrap().get_hci_index();
444         notify_suspend_state(hci_index, false);
445 
446         self.intf.lock().unwrap().set_default_event_mask_except(0u64, 0u64);
447 
448         // Restore event filter and accept list to normal.
449         self.intf.lock().unwrap().clear_event_filter();
450         self.intf.lock().unwrap().clear_filter_accept_list();
451         self.intf.lock().unwrap().restore_filter_accept_list();
452         self.bt.lock().unwrap().scan_mode_exit_suspend();
453 
454         if !self.audio_reconnect_list.is_empty() {
455             let reconnect_list = self.audio_reconnect_list.clone();
456             let txl = self.tx.clone();
457 
458             // Cancel any existing reconnect attempt.
459             if let Some(joinhandle) = &self.audio_reconnect_joinhandle {
460                 joinhandle.abort();
461                 self.audio_reconnect_joinhandle = None;
462             }
463 
464             self.audio_reconnect_joinhandle = Some(tokio::spawn(async move {
465                 // Wait a few seconds to avoid co-ex issues with wi-fi.
466                 time::sleep(Duration::from_millis(RECONNECT_AUDIO_ON_RESUME_DELAY_MS)).await;
467 
468                 // Queue up connections.
469                 for device in reconnect_list {
470                     let _unused: Option<()> = txl
471                         .send(Message::AdapterActions(AdapterActions::ConnectAllProfiles(device)))
472                         .await
473                         .ok();
474                 }
475 
476                 // Mark that we're done.
477                 let _unused: Option<()> = txl
478                     .send(Message::SuspendActions(SuspendActions::AudioReconnectOnResumeComplete))
479                     .await
480                     .ok();
481             }));
482         }
483 
484         self.bt.lock().unwrap().discovery_exit_suspend();
485         self.gatt.lock().unwrap().advertising_exit_suspend();
486         self.gatt.lock().unwrap().scan_exit_suspend();
487 
488         let tx = self.tx.clone();
489         tokio::spawn(async move {
490             let _result =
491                 tx.send(Message::SuspendActions(SuspendActions::ResumeReady(suspend_id))).await;
492         });
493 
494         true
495     }
496 }
497 
498 struct BluetoothConnectionCallbacks {
499     tx: Sender<Message>,
500 }
501 
502 impl BluetoothConnectionCallbacks {
new(tx: Sender<Message>) -> Self503     fn new(tx: Sender<Message>) -> Self {
504         Self { tx }
505     }
506 }
507 
508 impl IBluetoothConnectionCallback for BluetoothConnectionCallbacks {
on_device_connected(&mut self, _device: BluetoothDevice)509     fn on_device_connected(&mut self, _device: BluetoothDevice) {}
510 
on_device_disconnected(&mut self, device: BluetoothDevice)511     fn on_device_disconnected(&mut self, device: BluetoothDevice) {
512         let tx = self.tx.clone();
513         tokio::spawn(async move {
514             let _result = tx
515                 .send(Message::SuspendActions(SuspendActions::DeviceDisconnected(device.address)))
516                 .await;
517         });
518     }
519 
on_device_connection_failed(&mut self, _device: BluetoothDevice, _status: BtStatus)520     fn on_device_connection_failed(&mut self, _device: BluetoothDevice, _status: BtStatus) {}
521 }
522 
523 impl RPCProxy for BluetoothConnectionCallbacks {
get_object_id(&self) -> String524     fn get_object_id(&self) -> String {
525         "Bluetooth Connection Callback".to_string()
526     }
527 }
528