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