1 //! Suspend/Resume API.
2 
3 use crate::bluetooth::{Bluetooth, BluetoothDevice, BtifBluetoothCallbacks, DelayedActions};
4 use crate::bluetooth_media::BluetoothMedia;
5 use crate::callbacks::Callbacks;
6 use crate::{BluetoothGatt, Message, RPCProxy};
7 use bt_topshim::btif::BluetoothInterface;
8 use bt_topshim::metrics;
9 use log::warn;
10 use num_derive::{FromPrimitive, ToPrimitive};
11 use std::sync::{Arc, Mutex};
12 use tokio::sync::mpsc::Sender;
13 
14 use bt_utils::socket::{BtSocket, HciChannels, MgmtCommand, HCI_DEV_NONE};
15 
16 /// Defines the Suspend/Resume API.
17 ///
18 /// This API is exposed by `btadapterd` and independent of the suspend/resume detection mechanism
19 /// which depends on the actual operating system the daemon runs on. Possible clients of this API
20 /// include `btmanagerd` with Chrome OS `powerd` integration, `btmanagerd` with systemd Inhibitor
21 /// interface, or any script hooked to suspend/resume events.
22 pub trait ISuspend {
23     /// Adds an observer to suspend events.
24     ///
25     /// Returns true if the callback can be registered.
register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool26     fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool;
27 
28     /// Removes an observer to suspend events.
29     ///
30     /// Returns true if the callback can be removed, false if `callback_id` is not recognized.
unregister_callback(&mut self, callback_id: u32) -> bool31     fn unregister_callback(&mut self, callback_id: u32) -> bool;
32 
33     /// Prepares the stack for suspend, identified by `suspend_id`.
34     ///
35     /// Returns a positive number identifying the suspend if it can be started. If there is already
36     /// a suspend, that active suspend id is returned.
suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)37     fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32);
38 
39     /// Undoes previous suspend preparation identified by `suspend_id`.
40     ///
41     /// Returns true if suspend can be resumed, and false if there is no suspend to resume.
resume(&mut self) -> bool42     fn resume(&mut self) -> bool;
43 }
44 
45 /// Suspend events.
46 pub trait ISuspendCallback: RPCProxy {
47     /// Triggered when a callback is registered and given an identifier `callback_id`.
on_callback_registered(&mut self, callback_id: u32)48     fn on_callback_registered(&mut self, callback_id: u32);
49 
50     /// 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)51     fn on_suspend_ready(&mut self, suspend_id: i32);
52 
53     /// Triggered when the stack has resumed the previous suspend.
on_resumed(&mut self, suspend_id: i32)54     fn on_resumed(&mut self, suspend_id: i32);
55 }
56 
57 /// Events that are disabled when we go into suspend. This prevents spurious wakes from
58 /// events we know can happen but are not useful.
59 /// Bit 4 = Disconnect Complete.
60 /// Bit 19 = Mode Change.
61 const MASKED_EVENTS_FOR_SUSPEND: u64 = (1u64 << 4) | (1u64 << 19);
62 
63 /// When we resume, we will want to reconnect audio devices that were previously connected.
64 /// However, we will need to delay a few seconds to avoid co-ex issues with Wi-Fi reconnection.
65 const RECONNECT_AUDIO_ON_RESUME_DELAY_MS: u64 = 3000;
66 
67 /// TODO(b/286268874) Remove after the synchronization issue is resolved.
68 /// Delay sending suspend ready signal by some time.
69 const LE_RAND_CB_SUSPEND_READY_DELAY_MS: u64 = 100;
70 
notify_suspend_state(hci_index: u16, suspended: bool)71 fn notify_suspend_state(hci_index: u16, suspended: bool) {
72     log::debug!("Notify kernel suspend status: {} for hci{}", suspended, hci_index);
73     let mut btsock = BtSocket::new();
74     match btsock.open() {
75         -1 => {
76             panic!(
77                 "Bluetooth socket unavailable (errno {}). Try loading the kernel module first.",
78                 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
79             );
80         }
81         x => log::debug!("notify suspend Socket open at fd: {}", x),
82     }
83     // Bind to control channel (which is used for mgmt commands). We provide
84     // HCI_DEV_NONE because we don't actually need a valid HCI dev for some MGMT commands.
85     match btsock.bind_channel(HciChannels::Control, HCI_DEV_NONE) {
86         -1 => {
87             panic!(
88                 "Failed to bind control channel with errno={}",
89                 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
90             );
91         }
92         _ => (),
93     };
94 
95     let command = MgmtCommand::FlossNotifySuspendState(hci_index, suspended);
96     let bytes_written = btsock.write_mgmt_packet(command.into());
97     if bytes_written <= 0 {
98         log::error!("Failed to notify suspend state on hci:{} to {}", hci_index, suspended);
99     }
100 }
101 
102 #[derive(Debug, FromPrimitive, ToPrimitive)]
103 #[repr(u32)]
104 pub enum SuspendType {
105     NoWakesAllowed,
106     AllowWakeFromHid,
107     Other,
108 }
109 
110 struct SuspendState {
111     le_rand_expected: bool,
112     suspend_expected: bool,
113     resume_expected: bool,
114     suspend_id: Option<i32>,
115 }
116 
117 impl SuspendState {
new() -> SuspendState118     pub fn new() -> SuspendState {
119         Self {
120             le_rand_expected: false,
121             suspend_expected: false,
122             resume_expected: false,
123             suspend_id: None,
124         }
125     }
126 }
127 
128 /// Implementation of the suspend API.
129 pub struct Suspend {
130     bt: Arc<Mutex<Box<Bluetooth>>>,
131     intf: Arc<Mutex<BluetoothInterface>>,
132     gatt: Arc<Mutex<Box<BluetoothGatt>>>,
133     media: Arc<Mutex<Box<BluetoothMedia>>>,
134     tx: Sender<Message>,
135     callbacks: Callbacks<dyn ISuspendCallback + Send>,
136 
137     /// This list keeps track of audio devices that had an audio profile before
138     /// suspend so that we can attempt to connect after suspend.
139     audio_reconnect_list: Vec<BluetoothDevice>,
140 
141     /// Active reconnection attempt after resume.
142     audio_reconnect_joinhandle: Option<tokio::task::JoinHandle<()>>,
143 
144     suspend_timeout_joinhandle: Option<tokio::task::JoinHandle<()>>,
145     suspend_state: Arc<Mutex<SuspendState>>,
146 }
147 
148 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>, ) -> Suspend149     pub fn new(
150         bt: Arc<Mutex<Box<Bluetooth>>>,
151         intf: Arc<Mutex<BluetoothInterface>>,
152         gatt: Arc<Mutex<Box<BluetoothGatt>>>,
153         media: Arc<Mutex<Box<BluetoothMedia>>>,
154         tx: Sender<Message>,
155     ) -> Suspend {
156         Self {
157             bt,
158             intf,
159             gatt,
160             media,
161             tx: tx.clone(),
162             callbacks: Callbacks::new(tx.clone(), Message::SuspendCallbackDisconnected),
163             audio_reconnect_list: Vec::new(),
164             audio_reconnect_joinhandle: None,
165             suspend_timeout_joinhandle: None,
166             suspend_state: Arc::new(Mutex::new(SuspendState::new())),
167         }
168     }
169 
callback_registered(&mut self, id: u32)170     pub(crate) fn callback_registered(&mut self, id: u32) {
171         match self.callbacks.get_by_id_mut(id) {
172             Some(callback) => callback.on_callback_registered(id),
173             None => warn!("Suspend callback {} does not exist", id),
174         }
175     }
176 
remove_callback(&mut self, id: u32) -> bool177     pub(crate) fn remove_callback(&mut self, id: u32) -> bool {
178         self.callbacks.remove_callback(id)
179     }
180 
suspend_ready(&mut self, suspend_id: i32)181     pub(crate) fn suspend_ready(&mut self, suspend_id: i32) {
182         let hci_index = self.bt.lock().unwrap().get_hci_index();
183         notify_suspend_state(hci_index, true);
184         self.callbacks.for_all_callbacks(|callback| {
185             callback.on_suspend_ready(suspend_id);
186         });
187     }
188 
resume_ready(&mut self, suspend_id: i32)189     pub(crate) fn resume_ready(&mut self, suspend_id: i32) {
190         self.callbacks.for_all_callbacks(|callback| {
191             callback.on_resumed(suspend_id);
192         });
193     }
194 
195     /// On resume, we attempt to reconnect to any audio devices connected during suspend.
196     /// This marks this attempt as completed and we should clear the pending reconnects here.
audio_reconnect_complete(&mut self)197     pub(crate) fn audio_reconnect_complete(&mut self) {
198         self.audio_reconnect_list.clear();
199         self.audio_reconnect_joinhandle = None;
200     }
201 
get_connected_audio_devices(&self) -> Vec<BluetoothDevice>202     pub(crate) fn get_connected_audio_devices(&self) -> Vec<BluetoothDevice> {
203         let bonded_connected = self.bt.lock().unwrap().get_bonded_and_connected_devices();
204         self.media.lock().unwrap().filter_to_connected_audio_devices_from(&bonded_connected)
205     }
206 }
207 
208 impl ISuspend for Suspend {
register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool209     fn register_callback(&mut self, callback: Box<dyn ISuspendCallback + Send>) -> bool {
210         let id = self.callbacks.add_callback(callback);
211 
212         let tx = self.tx.clone();
213         tokio::spawn(async move {
214             let _result = tx.send(Message::SuspendCallbackRegistered(id)).await;
215         });
216 
217         true
218     }
219 
unregister_callback(&mut self, callback_id: u32) -> bool220     fn unregister_callback(&mut self, callback_id: u32) -> bool {
221         self.remove_callback(callback_id)
222     }
223 
suspend(&mut self, suspend_type: SuspendType, suspend_id: i32)224     fn suspend(&mut self, suspend_type: SuspendType, suspend_id: i32) {
225         // Set suspend state as true, prevent an early resume.
226         self.suspend_state.lock().unwrap().suspend_expected = true;
227         // Set suspend event mask
228         self.intf.lock().unwrap().set_default_event_mask_except(MASKED_EVENTS_FOR_SUSPEND, 0u64);
229 
230         self.bt.lock().unwrap().scan_mode_enter_suspend();
231         self.intf.lock().unwrap().clear_event_filter();
232         self.intf.lock().unwrap().clear_filter_accept_list();
233 
234         self.bt.lock().unwrap().discovery_enter_suspend();
235         self.gatt.lock().unwrap().advertising_enter_suspend();
236         self.gatt.lock().unwrap().scan_enter_suspend();
237 
238         // Track connected audio devices and queue them for reconnect on resume.
239         // If we still have the previous reconnect list left-over, do not try
240         // to collect a new list here.
241         if self.audio_reconnect_list.is_empty() {
242             self.audio_reconnect_list = self.get_connected_audio_devices();
243         }
244 
245         // Cancel any active reconnect task.
246         if let Some(joinhandle) = &self.audio_reconnect_joinhandle {
247             joinhandle.abort();
248             self.audio_reconnect_joinhandle = None;
249         }
250 
251         self.intf.lock().unwrap().disconnect_all_acls();
252 
253         // Handle wakeful cases (Connected/Other)
254         // Treat Other the same as Connected
255         match suspend_type {
256             SuspendType::AllowWakeFromHid | SuspendType::Other => {
257                 self.intf.lock().unwrap().allow_wake_by_hid();
258             }
259             _ => {}
260         }
261         self.suspend_state.lock().unwrap().le_rand_expected = true;
262         self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id);
263 
264         if let Some(join_handle) = &self.suspend_timeout_joinhandle {
265             join_handle.abort();
266             self.suspend_timeout_joinhandle = None;
267         }
268 
269         let tx = self.tx.clone();
270         let suspend_state = self.suspend_state.clone();
271         self.suspend_timeout_joinhandle = Some(tokio::spawn(async move {
272             tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await;
273             log::error!("Suspend did not complete in 2 seconds, continuing anyway.");
274             suspend_state.lock().unwrap().le_rand_expected = false;
275             suspend_state.lock().unwrap().suspend_expected = false;
276             tokio::spawn(async move {
277                 let _result = tx.send(Message::SuspendReady(suspend_id)).await;
278             });
279         }));
280 
281         // Call LE Rand at the end of suspend. The callback of LE Rand will reset the
282         // suspend state, cancel the suspend timeout and send suspend ready signal.
283         self.bt.lock().unwrap().le_rand();
284     }
285 
resume(&mut self) -> bool286     fn resume(&mut self) -> bool {
287         // Suspend is not ready (e.g. aborted early), delay cleanup after SuspendReady.
288         if self.suspend_state.lock().unwrap().suspend_expected == true {
289             log::error!("Suspend is expected but not ready, abort resume.");
290             return false;
291         }
292 
293         // Suspend ID state 0: NoRecord, 1: Recorded
294         let suspend_id_state = match self.suspend_state.lock().unwrap().suspend_id {
295             None => {
296                 log::error!("No suspend id saved at resume.");
297                 0
298             }
299             Some(_) => 1,
300         };
301         metrics::suspend_complete_state(suspend_id_state);
302         // If no suspend id is saved here, it means floss did not receive the SuspendImminent
303         // signal and as a result, the suspend flow was not run.
304         // Skip the resume flow and return after logging the metrics.
305         if suspend_id_state == 0 {
306             return true;
307         }
308 
309         let hci_index = self.bt.lock().unwrap().get_hci_index();
310         notify_suspend_state(hci_index, false);
311 
312         self.intf.lock().unwrap().set_default_event_mask_except(0u64, 0u64);
313 
314         // Restore event filter and accept list to normal.
315         self.intf.lock().unwrap().clear_event_filter();
316         self.intf.lock().unwrap().clear_filter_accept_list();
317         self.intf.lock().unwrap().restore_filter_accept_list();
318         self.bt.lock().unwrap().scan_mode_exit_suspend();
319 
320         if !self.audio_reconnect_list.is_empty() {
321             let reconnect_list = self.audio_reconnect_list.clone();
322             let txl = self.tx.clone();
323 
324             // Cancel any existing reconnect attempt.
325             if let Some(joinhandle) = &self.audio_reconnect_joinhandle {
326                 joinhandle.abort();
327                 self.audio_reconnect_joinhandle = None;
328             }
329 
330             self.audio_reconnect_joinhandle = Some(tokio::spawn(async move {
331                 // Wait a few seconds to avoid co-ex issues with wi-fi.
332                 tokio::time::sleep(tokio::time::Duration::from_millis(
333                     RECONNECT_AUDIO_ON_RESUME_DELAY_MS,
334                 ))
335                 .await;
336 
337                 // Queue up connections.
338                 for device in reconnect_list {
339                     let _unused: Option<()> = txl
340                         .send(Message::DelayedAdapterActions(DelayedActions::ConnectAllProfiles(
341                             device,
342                         )))
343                         .await
344                         .ok();
345                 }
346 
347                 // Mark that we're done.
348                 let _unused: Option<()> =
349                     txl.send(Message::AudioReconnectOnResumeComplete).await.ok();
350             }));
351         }
352 
353         self.bt.lock().unwrap().discovery_exit_suspend();
354         self.gatt.lock().unwrap().advertising_exit_suspend();
355         self.gatt.lock().unwrap().scan_exit_suspend();
356 
357         self.suspend_state.lock().unwrap().le_rand_expected = true;
358         self.suspend_state.lock().unwrap().resume_expected = true;
359 
360         let tx = self.tx.clone();
361         let suspend_state = self.suspend_state.clone();
362         let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap();
363         self.suspend_timeout_joinhandle = Some(tokio::spawn(async move {
364             tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await;
365             log::error!("Resume did not complete in 2 seconds, continuing anyway.");
366 
367             suspend_state.lock().unwrap().le_rand_expected = false;
368             suspend_state.lock().unwrap().resume_expected = false;
369             tokio::spawn(async move {
370                 let _result = tx.send(Message::ResumeReady(suspend_id)).await;
371             });
372         }));
373 
374         // Call LE Rand at the end of resume. The callback of LE Rand will reset the
375         // resume state and send resume ready signal.
376         self.bt.lock().unwrap().le_rand();
377 
378         true
379     }
380 }
381 
382 impl BtifBluetoothCallbacks for Suspend {
le_rand_cb(&mut self, _random: u64)383     fn le_rand_cb(&mut self, _random: u64) {
384         // TODO(b/232547719): Suspend readiness may not depend only on LeRand, make a generic state
385         // machine to support waiting for other conditions.
386         if !self.suspend_state.lock().unwrap().le_rand_expected {
387             log::warn!("Unexpected LE Rand callback, ignoring.");
388             return;
389         }
390         self.suspend_state.lock().unwrap().le_rand_expected = false;
391 
392         if let Some(join_handle) = &self.suspend_timeout_joinhandle {
393             join_handle.abort();
394             self.suspend_timeout_joinhandle = None;
395         }
396 
397         let suspend_id = self.suspend_state.lock().unwrap().suspend_id.unwrap();
398 
399         if self.suspend_state.lock().unwrap().suspend_expected {
400             let suspend_state = self.suspend_state.clone();
401             let tx = self.tx.clone();
402             tokio::spawn(async move {
403                 // TODO(b/286268874) Add a short delay because HCI commands are not
404                 // synchronized. LE Rand is the last command, so wait for other
405                 // commands to finish. Remove after synchronization is fixed.
406                 tokio::time::sleep(tokio::time::Duration::from_millis(
407                     LE_RAND_CB_SUSPEND_READY_DELAY_MS,
408                 ))
409                 .await;
410                 // TODO(b/286268874) Reset suspend state after the delay. Prevent
411                 // resume until the suspend ready signal is sent.
412                 suspend_state.lock().unwrap().suspend_expected = false;
413                 let _result = tx.send(Message::SuspendReady(suspend_id)).await;
414             });
415         }
416 
417         self.suspend_state.lock().unwrap().suspend_id = Some(suspend_id);
418         if self.suspend_state.lock().unwrap().resume_expected {
419             self.suspend_state.lock().unwrap().resume_expected = false;
420             let tx = self.tx.clone();
421             tokio::spawn(async move {
422                 let _result = tx.send(Message::ResumeReady(suspend_id)).await;
423             });
424         }
425     }
426 }
427