blob: 1f9a5864c177e8d7ccf8c1e4a1af9110379138e3 [file] [log] [blame]
use btstack::bluetooth_qa::BluetoothQA;
use clap::{App, AppSettings, Arg};
use dbus::{channel::MatchingReceiver, message::MatchRule};
use dbus_crossroads::Crossroads;
use dbus_tokio::connection;
use futures::future;
use lazy_static::lazy_static;
use nix::sys::signal;
use std::error::Error;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use tokio::time;
// Necessary to link right entries.
#[allow(unused_imports)]
use bt_shim;
use bt_topshim::{btif::get_btinterface, topstack};
use btstack::{
battery_manager::BatteryManager,
battery_provider_manager::BatteryProviderManager,
battery_service::BatteryService,
bluetooth::{Bluetooth, IBluetooth, SigData},
bluetooth_admin::BluetoothAdmin,
bluetooth_gatt::BluetoothGatt,
bluetooth_logging::BluetoothLogging,
bluetooth_media::BluetoothMedia,
dis::DeviceInformation,
socket_manager::BluetoothSocketManager,
suspend::Suspend,
Message, Stack,
};
use dbus_projection::DisconnectWatcher;
use tokio::sync::mpsc::Sender;
mod dbus_arg;
mod iface_battery_manager;
mod iface_battery_provider_manager;
mod iface_bluetooth;
mod iface_bluetooth_admin;
mod iface_bluetooth_gatt;
mod iface_bluetooth_media;
mod iface_bluetooth_qa;
mod iface_bluetooth_telephony;
mod iface_logging;
const DBUS_SERVICE_NAME: &str = "org.chromium.bluetooth";
const ADMIN_SETTINGS_FILE_PATH: &str = "/var/lib/bluetooth/admin_policy.json";
// The maximum ACL disconnect timeout is 3.5s defined by BTA_DM_DISABLE_TIMER_MS
// and BTA_DM_DISABLE_TIMER_RETRIAL_MS
const STACK_TURN_OFF_TIMEOUT_MS: Duration = Duration::from_millis(4000);
// Time bt_stack_manager waits for cleanup
const STACK_CLEANUP_TIMEOUT_MS: Duration = Duration::from_millis(1000);
const VERBOSE_ONLY_LOG_TAGS: &[&str] = &[
"bt_bta_av", // AV apis
"btm_sco", // SCO data path logs
"l2c_csm", // L2CAP state machine
"l2c_link", // L2CAP link layer logs
"sco_hci", // SCO over HCI
"uipc", // Userspace IPC implementation
];
fn make_object_name(idx: i32, name: &str) -> String {
String::from(format!("/org/chromium/bluetooth/hci{}/{}", idx, name))
}
/// Runs the Bluetooth daemon serving D-Bus IPC.
fn main() -> Result<(), Box<dyn Error>> {
let matches = App::new("Bluetooth Adapter Daemon")
// Allows multiple INIT_ flags to be given at the end of the arguments.
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name("hci")
.long("hci")
.value_name("HCI")
.takes_value(true)
.help("The HCI index"),
)
.arg(
Arg::with_name("index")
.long("index")
.value_name("INDEX")
.takes_value(true)
.help("The Virtual index"),
)
.arg(Arg::with_name("debug").long("debug").short("d").help("Enables debug level logs"))
.arg(
Arg::with_name("verbose-debug")
.long("verbose-debug")
.short("v")
.help("Enables VERBOSE and additional tags for debug logging. Use with --debug."),
)
.arg(Arg::from_usage("[init-flags] 'Fluoride INIT_ flags'").multiple(true))
.arg(
Arg::with_name("log-output")
.long("log-output")
.takes_value(true)
.possible_values(&["syslog", "stderr"])
.default_value("syslog")
.help("Select log output"),
)
.get_matches();
let is_debug = matches.is_present("debug");
let is_verbose_debug = matches.is_present("verbose-debug");
let log_output = matches.value_of("log-output").unwrap_or("syslog");
let adapter_index = matches.value_of("index").map_or(0, |idx| idx.parse::<i32>().unwrap_or(0));
let hci_index = matches.value_of("hci").map_or(0, |idx| idx.parse::<i32>().unwrap_or(0));
// The remaining flags are passed down to Fluoride as is.
let mut init_flags: Vec<String> = match matches.values_of("init-flags") {
Some(args) => args.map(|s| String::from(s)).collect(),
None => vec![],
};
// Set GD debug flag if debug is enabled.
if is_debug {
// Limit tags if verbose debug logging isn't enabled.
if !is_verbose_debug {
init_flags.push(format!(
"INIT_logging_debug_disabled_for_tags={}",
VERBOSE_ONLY_LOG_TAGS.join(",")
));
init_flags.push(String::from("INIT_default_log_level_str=LOG_DEBUG"));
} else {
init_flags.push(String::from("INIT_default_log_level_str=LOG_VERBOSE"));
}
}
// Forward --hci to Fluoride.
init_flags.push(format!("--hci={}", hci_index));
let logging = Arc::new(Mutex::new(Box::new(BluetoothLogging::new(is_debug, log_output))));
// Always treat discovery as classic only
init_flags.push(String::from("INIT_classic_discovery_only=true"));
let (tx, rx) = Stack::create_channel();
let sig_notifier = Arc::new(SigData {
enabled: Mutex::new(false),
enabled_notify: Condvar::new(),
thread_attached: Mutex::new(false),
thread_notify: Condvar::new(),
});
let intf = Arc::new(Mutex::new(get_btinterface().unwrap()));
let bluetooth_gatt =
Arc::new(Mutex::new(Box::new(BluetoothGatt::new(intf.clone(), tx.clone()))));
let battery_provider_manager =
Arc::new(Mutex::new(Box::new(BatteryProviderManager::new(tx.clone()))));
let battery_service = Arc::new(Mutex::new(Box::new(BatteryService::new(
bluetooth_gatt.clone(),
battery_provider_manager.clone(),
tx.clone(),
))));
let battery_manager = Arc::new(Mutex::new(Box::new(BatteryManager::new(
battery_provider_manager.clone(),
tx.clone(),
))));
let bluetooth_media = Arc::new(Mutex::new(Box::new(BluetoothMedia::new(
tx.clone(),
intf.clone(),
battery_provider_manager.clone(),
))));
let bluetooth_admin = Arc::new(Mutex::new(Box::new(BluetoothAdmin::new(
String::from(ADMIN_SETTINGS_FILE_PATH),
tx.clone(),
))));
let bluetooth = Arc::new(Mutex::new(Box::new(Bluetooth::new(
adapter_index,
hci_index,
tx.clone(),
sig_notifier.clone(),
intf.clone(),
bluetooth_admin.clone(),
bluetooth_gatt.clone(),
bluetooth_media.clone(),
))));
let suspend = Arc::new(Mutex::new(Box::new(Suspend::new(
bluetooth.clone(),
intf.clone(),
bluetooth_gatt.clone(),
bluetooth_media.clone(),
tx.clone(),
))));
let bt_sock_mgr = Arc::new(Mutex::new(Box::new(BluetoothSocketManager::new(
tx.clone(),
bluetooth_admin.clone(),
))));
let bluetooth_qa = Arc::new(Mutex::new(Box::new(BluetoothQA::new(tx.clone()))));
let dis =
Arc::new(Mutex::new(Box::new(DeviceInformation::new(bluetooth_gatt.clone(), tx.clone()))));
topstack::get_runtime().block_on(async {
// Connect to D-Bus system bus.
let (resource, conn) = connection::new_system_sync()?;
// The `resource` is a task that should be spawned onto a tokio compatible
// reactor ASAP. If the resource ever finishes, we lost connection to D-Bus.
tokio::spawn(async {
let err = resource.await;
panic!("Lost connection to D-Bus: {}", err);
});
// Request a service name and quit if not able to.
conn.request_name(DBUS_SERVICE_NAME, false, true, false).await?;
// Prepare D-Bus interfaces.
let cr = Arc::new(Mutex::new(Crossroads::new()));
cr.lock().unwrap().set_async_support(Some((
conn.clone(),
Box::new(|x| {
tokio::spawn(x);
}),
)));
// Announce the exported adapter objects so that clients can properly detect the readiness
// of the adapter APIs.
cr.lock().unwrap().set_object_manager_support(Some(conn.clone()));
let object_manager = cr.lock().unwrap().object_manager();
cr.lock().unwrap().insert("/", &[object_manager], {});
// Run the stack main dispatch loop.
topstack::get_runtime().spawn(Stack::dispatch(
rx,
bluetooth.clone(),
bluetooth_gatt.clone(),
battery_service.clone(),
battery_manager.clone(),
battery_provider_manager.clone(),
bluetooth_media.clone(),
suspend.clone(),
bt_sock_mgr.clone(),
bluetooth_admin.clone(),
dis.clone(),
bluetooth_qa.clone(),
));
// Set up the disconnect watcher to monitor client disconnects.
let disconnect_watcher = Arc::new(Mutex::new(DisconnectWatcher::new()));
disconnect_watcher.lock().unwrap().setup_watch(conn.clone()).await;
// Set up handling of D-Bus methods. This must be done before exporting interfaces so that
// clients that rely on InterfacesAdded signal can rely on us being ready to handle methods
// on those exported interfaces.
let cr_clone = cr.clone();
conn.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
cr_clone.lock().unwrap().handle_message(msg, conn).unwrap();
true
}),
);
// Register D-Bus method handlers of IBluetooth.
let adapter_iface = iface_bluetooth::export_bluetooth_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let qa_iface = iface_bluetooth_qa::export_bluetooth_qa_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let qa_legacy_iface = iface_bluetooth::export_bluetooth_qa_legacy_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let socket_mgr_iface = iface_bluetooth::export_socket_mgr_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let suspend_iface = iface_bluetooth::export_suspend_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let logging_iface = iface_logging::export_bluetooth_logging_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
// Register D-Bus method handlers of IBluetoothGatt.
let gatt_iface = iface_bluetooth_gatt::export_bluetooth_gatt_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let media_iface = iface_bluetooth_media::export_bluetooth_media_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let telephony_iface = iface_bluetooth_telephony::export_bluetooth_telephony_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let battery_provider_manager_iface =
iface_battery_provider_manager::export_battery_provider_manager_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let battery_manager_iface = iface_battery_manager::export_battery_manager_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
let admin_iface = iface_bluetooth_admin::export_bluetooth_admin_dbus_intf(
conn.clone(),
&mut cr.lock().unwrap(),
disconnect_watcher.clone(),
);
// Create mixin object for Bluetooth + Suspend interfaces.
let mixin = Box::new(iface_bluetooth::BluetoothMixin {
adapter: bluetooth.clone(),
qa: bluetooth.clone(),
suspend: suspend.clone(),
socket_mgr: bt_sock_mgr.clone(),
});
cr.lock().unwrap().insert(
make_object_name(adapter_index, "adapter"),
&[adapter_iface, qa_legacy_iface, socket_mgr_iface, suspend_iface],
mixin,
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "gatt"),
&[gatt_iface],
bluetooth_gatt.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "media"),
&[media_iface],
bluetooth_media.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "telephony"),
&[telephony_iface],
bluetooth_media.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "battery_provider_manager"),
&[battery_provider_manager_iface],
battery_provider_manager.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "battery_manager"),
&[battery_manager_iface],
battery_manager.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "admin"),
&[admin_iface],
bluetooth_admin.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "logging"),
&[logging_iface],
logging.clone(),
);
cr.lock().unwrap().insert(
make_object_name(adapter_index, "qa"),
&[qa_iface],
bluetooth_qa.clone(),
);
// Hold locks and initialize all interfaces. This must be done AFTER DBus is
// initialized so DBus can properly enforce user policies.
{
let adapter = bluetooth.clone();
bluetooth_media.lock().unwrap().set_adapter(adapter.clone());
bluetooth_admin.lock().unwrap().set_adapter(adapter.clone());
let mut bluetooth = bluetooth.lock().unwrap();
bluetooth.init(init_flags);
bluetooth.init_profiles();
bluetooth.enable();
bluetooth_gatt.lock().unwrap().init_profiles(tx.clone(), adapter.clone());
// TODO(b/247093293): Gatt topshim api is only usable some
// time after init. Investigate why this delay is needed
// and make it a blocking part of init before removing
// this.
tokio::spawn(async move {
time::sleep(Duration::from_millis(500)).await;
battery_service.lock().unwrap().init();
});
bt_sock_mgr.lock().unwrap().initialize(intf.clone());
// Install SIGTERM handler so that we can properly shutdown
*SIG_DATA.lock().unwrap() = Some((tx.clone(), sig_notifier.clone()));
let sig_action = signal::SigAction::new(
signal::SigHandler::Handler(handle_sigterm),
signal::SaFlags::empty(),
signal::SigSet::empty(),
);
unsafe {
signal::sigaction(signal::SIGTERM, &sig_action).unwrap();
}
}
// Serve clients forever.
future::pending::<()>().await;
unreachable!()
})
}
lazy_static! {
/// Data needed for signal handling.
static ref SIG_DATA: Mutex<Option<(Sender<Message>, Arc<SigData>)>> = Mutex::new(None);
}
extern "C" fn handle_sigterm(_signum: i32) {
let guard = SIG_DATA.lock().unwrap();
if let Some((tx, notifier)) = guard.as_ref() {
log::debug!("Handling SIGTERM by disabling the adapter!");
let txl = tx.clone();
tokio::spawn(async move {
// Send the shutdown message here.
let _ = txl.send(Message::Shutdown).await;
});
let guard = notifier.enabled.lock().unwrap();
if *guard {
log::debug!("Waiting for stack to turn off for {:?}", STACK_TURN_OFF_TIMEOUT_MS);
let _ = notifier.enabled_notify.wait_timeout(guard, STACK_TURN_OFF_TIMEOUT_MS);
}
log::debug!("SIGTERM cleaning up the stack.");
let txl = tx.clone();
tokio::spawn(async move {
// Send the cleanup message here.
let _ = txl.send(Message::Cleanup).await;
});
let guard = notifier.thread_attached.lock().unwrap();
if *guard {
log::debug!("Waiting for stack to clean up for {:?}", STACK_CLEANUP_TIMEOUT_MS);
let _ = notifier.thread_notify.wait_timeout(guard, STACK_CLEANUP_TIMEOUT_MS);
}
}
log::debug!("Sigterm completed");
std::process::exit(0);
}