Commit 310a3d48 authored by Marcel Holtmann's avatar Marcel Holtmann Committed by Johan Hedberg

Bluetooth: Add support for entering limited discoverable mode

The limited discoverable mode should be used when a device is only
discoverable for a certain amount of time and after that it returns
back into being non-discoverable.

This adds another option to the set discoverable management command
to clearly distinguish limited discoverable from general discoverable
mode.

While the general discoverable mode can be set with a specific
timeout or as permanent setting, the limited discoverable mode
requires a timeout. The timeout is flexible and the kernel will
not enforce any specific limitations. That GAP part of this is
required by userspace to enforce according to the Bluetooth core
specification.

Devices in limited discoverable mode can still be found by the
general discovery procedure. It is mandatory that a device sets
both GIAC and LIAC when entering limited discoverable mode.
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
Signed-off-by: default avatarJohan Hedberg <johan.hedberg@intel.com>
parent 3d505312
...@@ -1282,6 +1282,7 @@ static int hci_dev_do_close(struct hci_dev *hdev) ...@@ -1282,6 +1282,7 @@ static int hci_dev_do_close(struct hci_dev *hdev)
cancel_delayed_work(&hdev->discov_off); cancel_delayed_work(&hdev->discov_off);
hdev->discov_timeout = 0; hdev->discov_timeout = 0;
clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags); clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
} }
if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags)) if (test_and_clear_bit(HCI_SERVICE_CACHE, &hdev->dev_flags))
...@@ -1717,6 +1718,13 @@ static void hci_discov_off(struct work_struct *work) ...@@ -1717,6 +1718,13 @@ static void hci_discov_off(struct work_struct *work)
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan); hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
hci_req_run(&req, NULL); hci_req_run(&req, NULL);
/* When discoverable timeout triggers, then just make sure
* the limited discoverable flag is cleared. Even in the case
* of a timeout triggered from general discoverable, it is
* safe to unconditionally clear the flag.
*/
clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
hdev->discov_timeout = 0; hdev->discov_timeout = 0;
hci_dev_unlock(hdev); hci_dev_unlock(hdev);
......
...@@ -1039,6 +1039,7 @@ static void set_discoverable_complete(struct hci_dev *hdev, u8 status) ...@@ -1039,6 +1039,7 @@ static void set_discoverable_complete(struct hci_dev *hdev, u8 status)
if (status) { if (status) {
u8 mgmt_err = mgmt_status(status); u8 mgmt_err = mgmt_status(status);
cmd_status(cmd->sk, cmd->index, cmd->opcode, mgmt_err); cmd_status(cmd->sk, cmd->index, cmd->opcode, mgmt_err);
clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
goto remove_cmd; goto remove_cmd;
} }
...@@ -1094,12 +1095,17 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, ...@@ -1094,12 +1095,17 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
status); status);
if (cp->val != 0x00 && cp->val != 0x01) if (cp->val != 0x00 && cp->val != 0x01 && cp->val != 0x02)
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
MGMT_STATUS_INVALID_PARAMS); MGMT_STATUS_INVALID_PARAMS);
timeout = __le16_to_cpu(cp->timeout); timeout = __le16_to_cpu(cp->timeout);
if (!cp->val && timeout > 0)
/* Disabling discoverable requires that no timeout is set,
* and enabling limited discoverable requires a timeout.
*/
if ((cp->val == 0x00 && timeout > 0) ||
(cp->val == 0x02 && timeout == 0))
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE, return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
MGMT_STATUS_INVALID_PARAMS); MGMT_STATUS_INVALID_PARAMS);
...@@ -1127,6 +1133,10 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, ...@@ -1127,6 +1133,10 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
if (!hdev_is_powered(hdev)) { if (!hdev_is_powered(hdev)) {
bool changed = false; bool changed = false;
/* Setting limited discoverable when powered off is
* not a valid operation since it requires a timeout
* and so no need to check HCI_LIMITED_DISCOVERABLE.
*/
if (!!cp->val != test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { if (!!cp->val != test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) {
change_bit(HCI_DISCOVERABLE, &hdev->dev_flags); change_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
changed = true; changed = true;
...@@ -1142,7 +1152,13 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, ...@@ -1142,7 +1152,13 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
goto failed; goto failed;
} }
if (!!cp->val == test_bit(HCI_DISCOVERABLE, &hdev->dev_flags)) { /* If the current mode is the same, then just update the timeout
* value with the new value. And if only the timeout gets updated,
* then no need for any HCI transactions.
*/
if (!!cp->val == test_bit(HCI_DISCOVERABLE, &hdev->dev_flags) &&
(cp->val == 0x02) == test_bit(HCI_LIMITED_DISCOVERABLE,
&hdev->dev_flags)) {
cancel_delayed_work(&hdev->discov_off); cancel_delayed_work(&hdev->discov_off);
hdev->discov_timeout = timeout; hdev->discov_timeout = timeout;
...@@ -1162,24 +1178,55 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data, ...@@ -1162,24 +1178,55 @@ static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
goto failed; goto failed;
} }
/* Cancel any potential discoverable timeout that might be
* still active and store new timeout value. The arming of
* the timeout happens in the complete handler.
*/
cancel_delayed_work(&hdev->discov_off);
hdev->discov_timeout = timeout;
hci_req_init(&req, hdev); hci_req_init(&req, hdev);
scan = SCAN_PAGE; scan = SCAN_PAGE;
if (cp->val) if (cp->val) {
struct hci_cp_write_current_iac_lap hci_cp;
if (cp->val == 0x02) {
/* Limited discoverable mode */
set_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
hci_cp.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[3] = 0x33; /* GIAC */
hci_cp.iac_lap[4] = 0x8b;
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_cp.iac_lap[2] = 0x9e;
}
hci_req_add(&req, HCI_OP_WRITE_CURRENT_IAC_LAP,
(hci_cp.num_iac * 3) + 1, &hci_cp);
scan |= SCAN_INQUIRY; scan |= SCAN_INQUIRY;
else } else {
cancel_delayed_work(&hdev->discov_off); clear_bit(HCI_LIMITED_DISCOVERABLE, &hdev->dev_flags);
}
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan); hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, sizeof(scan), &scan);
err = hci_req_run(&req, set_discoverable_complete); err = hci_req_run(&req, set_discoverable_complete);
if (err < 0) if (err < 0)
mgmt_pending_remove(cmd); mgmt_pending_remove(cmd);
if (cp->val)
hdev->discov_timeout = timeout;
failed: failed:
hci_dev_unlock(hdev); hci_dev_unlock(hdev);
return err; return err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment