return ptr;
}
-static u8 create_ad(struct hci_dev *hdev, u8 *ptr)
+static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev)
{
- u8 ad_len = 0, flags = 0;
+ struct pending_cmd *cmd;
+
+ list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
+ if (cmd->opcode == opcode)
+ return cmd;
+ }
+
+ return NULL;
+}
+
+static u8 create_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
+{
+ u8 ad_len = 0;
size_t name_len;
- if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
- flags |= LE_AD_GENERAL;
+ name_len = strlen(hdev->dev_name);
+ if (name_len > 0) {
+ size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
+
+ if (name_len > max_len) {
+ name_len = max_len;
+ ptr[1] = EIR_NAME_SHORT;
+ } else
+ ptr[1] = EIR_NAME_COMPLETE;
+
+ ptr[0] = name_len + 1;
+
+ memcpy(ptr + 2, hdev->dev_name, name_len);
+
+ ad_len += (name_len + 2);
+ ptr += (name_len + 2);
+ }
+
+ return ad_len;
+}
+
+static void update_scan_rsp_data(struct hci_request *req)
+{
+ struct hci_dev *hdev = req->hdev;
+ struct hci_cp_le_set_scan_rsp_data cp;
+ u8 len;
+
+ if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
+ return;
+
+ memset(&cp, 0, sizeof(cp));
+
+ len = create_scan_rsp_data(hdev, cp.data);
+
+ if (hdev->scan_rsp_data_len == len &&
+ memcmp(cp.data, hdev->scan_rsp_data, len) == 0)
+ return;
+
+ memcpy(hdev->scan_rsp_data, cp.data, sizeof(cp.data));
+ hdev->scan_rsp_data_len = len;
+
+ cp.length = len;
+
+ hci_req_add(req, HCI_OP_LE_SET_SCAN_RSP_DATA, sizeof(cp), &cp);
+}
+
+static u8 get_adv_discov_flags(struct hci_dev *hdev)
+{
+ struct pending_cmd *cmd;
+
+ /* If there's a pending mgmt command the flags will not yet have
+ * their final values, so check for this first.
+ */
+ cmd = mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev);
+ if (cmd) {
+ struct mgmt_mode *cp = cmd->param;
+ if (cp->val == 0x01)
+ return LE_AD_GENERAL;
+ else if (cp->val == 0x02)
+ return LE_AD_LIMITED;
+ } else {
+ if (test_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags))
+ return LE_AD_LIMITED;
+ else if (test_bit(HCI_DISCOVERABLE, &hdev->dev_flags))
+ return LE_AD_GENERAL;
+ }
+
+ return 0;
+}
+
+static u8 create_adv_data(struct hci_dev *hdev, u8 *ptr)
+{
+ u8 ad_len = 0, flags = 0;
+
+ flags |= get_adv_discov_flags(hdev);
if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
if (lmp_le_br_capable(hdev))
ptr += 3;
}
- name_len = strlen(hdev->dev_name);
- if (name_len > 0) {
- size_t max_len = HCI_MAX_AD_LENGTH - ad_len - 2;
-
- if (name_len > max_len) {
- name_len = max_len;
- ptr[1] = EIR_NAME_SHORT;
- } else
- ptr[1] = EIR_NAME_COMPLETE;
-
- ptr[0] = name_len + 1;
-
- memcpy(ptr + 2, hdev->dev_name, name_len);
-
- ad_len += (name_len + 2);
- ptr += (name_len + 2);
- }
-
return ad_len;
}
-static void update_ad(struct hci_request *req)
+static void update_adv_data(struct hci_request *req)
{
struct hci_dev *hdev = req->hdev;
struct hci_cp_le_set_adv_data cp;
u8 len;
- if (!lmp_le_capable(hdev))
+ if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
return;
memset(&cp, 0, sizeof(cp));
- len = create_ad(hdev, cp.data);
+ len = create_adv_data(hdev, cp.data);
if (hdev->adv_data_len == len &&
memcmp(cp.data, hdev->adv_data, len) == 0)
if (!hdev_is_powered(hdev))
return;
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+ return;
+
if (test_bit(HCI_SERVICE_CACHE, &hdev->dev_flags))
return;
}
}
-static struct pending_cmd *mgmt_pending_find(u16 opcode, struct hci_dev *hdev)
-{
- struct pending_cmd *cmd;
-
- list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
- if (cmd->opcode == opcode)
- return cmd;
- }
-
- return NULL;
-}
-
static void mgmt_pending_remove(struct pending_cmd *cmd)
{
list_del(&cmd->list);
struct pending_cmd *cmd;
struct hci_request req;
u16 timeout;
- u8 scan, status;
+ u8 scan;
int err;
BT_DBG("request for %s", hdev->name);
- status = mgmt_bredr_support(hdev);
- if (status)
+ if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags) &&
+ !test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
- status);
+ MGMT_STATUS_REJECTED);
if (cp->val != 0x00 && cp->val != 0x01 && cp->val != 0x02)
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
cancel_delayed_work(&hdev->discov_off);
hdev->discov_timeout = timeout;
+ /* Limited discoverable mode */
+ if (cp->val == 0x02)
+ set_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+ else
+ clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+
hci_req_init(&req, hdev);
+ /* The procedure for LE-only controllers is much simpler - just
+ * update the advertising data.
+ */
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+ goto update_ad;
+
scan = SCAN_PAGE;
if (cp->val) {
if (cp->val == 0x02) {
/* Limited discoverable mode */
- set_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
-
- hci_cp.num_iac = 2;
+ hci_cp.num_iac = min_t(u8, hdev->num_iac, 2);
hci_cp.iac_lap[0] = 0x00; /* LIAC */
hci_cp.iac_lap[1] = 0x8b;
hci_cp.iac_lap[2] = 0x9e;
hci_cp.iac_lap[5] = 0x9e;
} else {
/* General discoverable mode */
- clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
-
hci_cp.num_iac = 1;
hci_cp.iac_lap[0] = 0x33; /* GIAC */
hci_cp.iac_lap[1] = 0x8b;
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
+update_ad:
+ update_adv_data(&req);
+
err = hci_req_run(&req, set_discoverable_complete);
if (err < 0)
mgmt_pending_remove(cmd);
struct hci_cp_write_page_scan_activity acp;
u8 type;
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
+ return;
+
if (hdev->hci_ver < BLUETOOTH_VER_1_2)
return;
cp.min_interval = __constant_cpu_to_le16(0x0800);
cp.max_interval = __constant_cpu_to_le16(0x0800);
cp.type = get_adv_type(hdev);
- if (bacmp(&hdev->bdaddr, BDADDR_ANY))
- cp.own_address_type = ADDR_LE_DEV_PUBLIC;
- else
- cp.own_address_type = ADDR_LE_DEV_RANDOM;
+ cp.own_address_type = hdev->own_addr_type;
cp.channel_map = 0x07;
hci_req_add(req, HCI_OP_LE_SET_ADV_PARAM, sizeof(cp), &cp);
hci_dev_unlock(hdev);
}
+static int set_connectable_update_settings(struct hci_dev *hdev,
+ struct sock *sk, u8 val)
+{
+ bool changed = false;
+ int err;
+
+ if (!!val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
+ changed = true;
+
+ if (val) {
+ set_bit(HCI_CONNECTABLE, &hdev->dev_flags);
+ } else {
+ clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
+ clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+ }
+
+ err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev);
+ if (err < 0)
+ return err;
+
+ if (changed)
+ return new_settings(hdev, sk);
+
+ return 0;
+}
+
static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
u16 len)
{
hci_dev_lock(hdev);
if (!hdev_is_powered(hdev)) {
- bool changed = false;
-
- if (!!cp->val != test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
- changed = true;
-
- if (cp->val) {
- set_bit(HCI_CONNECTABLE, &hdev->dev_flags);
- } else {
- clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
- clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
- }
-
- err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE, hdev);
- if (err < 0)
- goto failed;
-
- if (changed)
- err = new_settings(hdev, sk);
-
+ err = set_connectable_update_settings(hdev, sk, cp->val);
goto failed;
}
hci_req_init(&req, hdev);
- if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags) &&
- cp->val != test_bit(HCI_PSCAN, &hdev->flags)) {
+ /* If BR/EDR is not enabled and we disable advertising as a
+ * by-product of disabling connectable, we need to update the
+ * advertising flags.
+ */
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
+ if (!cp->val) {
+ clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+ clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+ }
+ update_adv_data(&req);
+ } else if (cp->val != test_bit(HCI_PSCAN, &hdev->flags)) {
if (cp->val) {
scan = SCAN_PAGE;
} else {
if (err < 0) {
mgmt_pending_remove(cmd);
if (err == -ENODATA)
- err = send_settings_rsp(sk, MGMT_OP_SET_CONNECTABLE,
- hdev);
+ err = set_connectable_update_settings(hdev, sk,
+ cp->val);
goto failed;
}
hci_dev_lock(hdev);
hci_req_init(&req, hdev);
- update_ad(&req);
+ update_adv_data(&req);
+ update_scan_rsp_data(&req);
hci_req_run(&req, NULL);
hci_dev_unlock(hdev);
update_eir(&req);
}
+ /* The name is stored in the scan response data and so
+ * no need to udpate the advertising data here.
+ */
if (lmp_le_capable(hdev))
- update_ad(&req);
+ update_scan_rsp_data(&req);
err = hci_req_run(&req, set_name_complete);
if (err < 0)
param_cp.type = LE_SCAN_ACTIVE;
param_cp.interval = cpu_to_le16(DISCOV_LE_SCAN_INT);
param_cp.window = cpu_to_le16(DISCOV_LE_SCAN_WIN);
- if (bacmp(&hdev->bdaddr, BDADDR_ANY))
- param_cp.own_address_type = ADDR_LE_DEV_PUBLIC;
- else
- param_cp.own_address_type = ADDR_LE_DEV_RANDOM;
+ param_cp.own_address_type = hdev->own_addr_type;
hci_req_add(&req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(param_cp),
¶m_cp);
goto unlock;
}
- /* We need to flip the bit already here so that update_ad
+ /* We need to flip the bit already here so that update_adv_data
* generates the correct flags.
*/
set_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
if (test_bit(HCI_CONNECTABLE, &hdev->dev_flags))
set_bredr_scan(&req);
- update_ad(&req);
+ /* Since only the advertising data flags will change, there
+ * is no need to update the scan response data.
+ */
+ update_adv_data(&req);
err = hci_req_run(&req, set_bredr_complete);
if (err < 0)
* advertising data. This also applies to the case
* where BR/EDR was toggled during the AUTO_OFF phase.
*/
- if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
- update_ad(&req);
+ if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) {
+ update_adv_data(&req);
+ update_scan_rsp_data(&req);
+ }
if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
enable_advertising(&req);
void mgmt_discoverable_timeout(struct hci_dev *hdev)
{
struct hci_request req;
- u8 scan = SCAN_PAGE;
hci_dev_lock(hdev);
* safe to unconditionally clear the flag.
*/
clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
+ clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
hci_req_init(&req, hdev);
- hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
+ if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
+ u8 scan = SCAN_PAGE;
+ hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE,
+ sizeof(scan), &scan);
+ }
update_class(&req);
+ update_adv_data(&req);
hci_req_run(&req, NULL);
hdev->discov_timeout = 0;
+ new_settings(hdev, NULL);
+
hci_dev_unlock(hdev);
}
if (mgmt_pending_find(MGMT_OP_SET_DISCOVERABLE, hdev))
return;
- if (discoverable)
+ if (discoverable) {
changed = !test_and_set_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
- else
+ } else {
+ clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
changed = test_and_clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
+ }
+
+ if (changed) {
+ struct hci_request req;
+
+ /* In case this change in discoverable was triggered by
+ * a disabling of connectable there could be a need to
+ * update the advertising flags.
+ */
+ hci_req_init(&req, hdev);
+ update_adv_data(&req);
+ hci_req_run(&req, NULL);
- if (changed)
new_settings(hdev, NULL);
+ }
}
void mgmt_connectable(struct hci_dev *hdev, u8 connectable)