summaryrefslogtreecommitdiffstats
path: root/net/bluetooth/hci_request.c
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2015-11-25 15:15:44 +0100
committerMarcel Holtmann <marcel@holtmann.org>2015-12-10 00:51:49 +0100
commit2ff13894cfb877cb3d02d96a8402202f0a6f3efd (patch)
treefeb1092db11f30427f75af9694909fbdf6178c9a /net/bluetooth/hci_request.c
parentBluetooth: Move fast connectable code to hci_request.c (diff)
downloadlinux-2ff13894cfb877cb3d02d96a8402202f0a6f3efd.tar.xz
linux-2ff13894cfb877cb3d02d96a8402202f0a6f3efd.zip
Bluetooth: Perform HCI update for power on synchronously
The request to update HCI during power on is always coming either from hdev->req_workqueue or through an ioctl, so it's safe to use hci_req_sync for it. This way we also eliminate potential races with incoming mgmt commands or other actions while powering on. Part of this refactoring is the splitting of mgmt_powered() into mgmt_power_on() and __mgmt_power_off() functions. The main reason is the different requirements as far as hdev locking is concerned, as highlighted with the __ prefix of the power off API. Since the power on in the case of clearing the AUTO_OFF flag cannot be done synchronously in the set_powered mgmt handler, the hci_power_on work callback is extended to cover this (which also simplifies the set_powered helper a lot). Signed-off-by: Johan Hedberg <johan.hedberg@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Diffstat (limited to 'net/bluetooth/hci_request.c')
-rw-r--r--net/bluetooth/hci_request.c100
1 files changed, 100 insertions, 0 deletions
diff --git a/net/bluetooth/hci_request.c b/net/bluetooth/hci_request.c
index 0abd83ddd4fb..7cc24f1448bd 100644
--- a/net/bluetooth/hci_request.c
+++ b/net/bluetooth/hci_request.c
@@ -2181,6 +2181,106 @@ static void discov_off(struct work_struct *work)
mgmt_new_settings(hdev);
}
+static int powered_update_hci(struct hci_request *req, unsigned long opt)
+{
+ struct hci_dev *hdev = req->hdev;
+ struct adv_info *adv_instance;
+ u8 link_sec;
+
+ hci_dev_lock(hdev);
+
+ if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED) &&
+ !lmp_host_ssp_capable(hdev)) {
+ u8 mode = 0x01;
+
+ hci_req_add(req, HCI_OP_WRITE_SSP_MODE, sizeof(mode), &mode);
+
+ if (bredr_sc_enabled(hdev) && !lmp_host_sc_capable(hdev)) {
+ u8 support = 0x01;
+
+ hci_req_add(req, HCI_OP_WRITE_SC_SUPPORT,
+ sizeof(support), &support);
+ }
+ }
+
+ if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) &&
+ lmp_bredr_capable(hdev)) {
+ struct hci_cp_write_le_host_supported cp;
+
+ cp.le = 0x01;
+ cp.simul = 0x00;
+
+ /* Check first if we already have the right
+ * host state (host features set)
+ */
+ if (cp.le != lmp_host_le_capable(hdev) ||
+ cp.simul != lmp_host_le_br_capable(hdev))
+ hci_req_add(req, HCI_OP_WRITE_LE_HOST_SUPPORTED,
+ sizeof(cp), &cp);
+ }
+
+ if (lmp_le_capable(hdev)) {
+ /* Make sure the controller has a good default for
+ * advertising data. This also applies to the case
+ * where BR/EDR was toggled during the AUTO_OFF phase.
+ */
+ if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) &&
+ (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
+ !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) {
+ __hci_req_update_adv_data(req, HCI_ADV_CURRENT);
+ __hci_req_update_scan_rsp_data(req, HCI_ADV_CURRENT);
+ }
+
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
+ hdev->cur_adv_instance == 0x00 &&
+ !list_empty(&hdev->adv_instances)) {
+ adv_instance = list_first_entry(&hdev->adv_instances,
+ struct adv_info, list);
+ hdev->cur_adv_instance = adv_instance->instance;
+ }
+
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING))
+ __hci_req_enable_advertising(req);
+ else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
+ hdev->cur_adv_instance)
+ __hci_req_schedule_adv_instance(req,
+ hdev->cur_adv_instance,
+ true);
+ }
+
+ link_sec = hci_dev_test_flag(hdev, HCI_LINK_SECURITY);
+ if (link_sec != test_bit(HCI_AUTH, &hdev->flags))
+ hci_req_add(req, HCI_OP_WRITE_AUTH_ENABLE,
+ sizeof(link_sec), &link_sec);
+
+ if (lmp_bredr_capable(hdev)) {
+ if (hci_dev_test_flag(hdev, HCI_FAST_CONNECTABLE))
+ __hci_req_write_fast_connectable(req, true);
+ else
+ __hci_req_write_fast_connectable(req, false);
+ __hci_req_update_scan(req);
+ __hci_req_update_class(req);
+ __hci_req_update_name(req);
+ __hci_req_update_eir(req);
+ }
+
+ hci_dev_unlock(hdev);
+ return 0;
+}
+
+int __hci_req_hci_power_on(struct hci_dev *hdev)
+{
+ /* Register the available SMP channels (BR/EDR and LE) only when
+ * successfully powering on the controller. This late
+ * registration is required so that LE SMP can clearly decide if
+ * the public address or static address is used.
+ */
+ smp_register(hdev);
+
+ return __hci_req_sync(hdev, powered_update_hci, 0, HCI_CMD_TIMEOUT,
+ NULL);
+}
+
void hci_request_setup(struct hci_dev *hdev)
{
INIT_WORK(&hdev->discov_update, discov_update);