|
@@ -32,10 +32,8 @@
|
|
|
#include <net/bluetooth/mgmt.h>
|
|
|
#include <net/bluetooth/smp.h>
|
|
|
|
|
|
-bool enable_hs;
|
|
|
-
|
|
|
#define MGMT_VERSION 1
|
|
|
-#define MGMT_REVISION 3
|
|
|
+#define MGMT_REVISION 4
|
|
|
|
|
|
static const u16 mgmt_commands[] = {
|
|
|
MGMT_OP_READ_INDEX_LIST,
|
|
@@ -76,6 +74,9 @@ static const u16 mgmt_commands[] = {
|
|
|
MGMT_OP_BLOCK_DEVICE,
|
|
|
MGMT_OP_UNBLOCK_DEVICE,
|
|
|
MGMT_OP_SET_DEVICE_ID,
|
|
|
+ MGMT_OP_SET_ADVERTISING,
|
|
|
+ MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_OP_SET_STATIC_ADDRESS,
|
|
|
};
|
|
|
|
|
|
static const u16 mgmt_events[] = {
|
|
@@ -339,6 +340,9 @@ static int read_index_list(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
if (test_bit(HCI_SETUP, &d->dev_flags))
|
|
|
continue;
|
|
|
|
|
|
+ if (test_bit(HCI_USER_CHANNEL, &d->dev_flags))
|
|
|
+ continue;
|
|
|
+
|
|
|
if (!mgmt_valid_hdev(d))
|
|
|
continue;
|
|
|
|
|
@@ -376,13 +380,13 @@ static u32 get_supported_settings(struct hci_dev *hdev)
|
|
|
settings |= MGMT_SETTING_DISCOVERABLE;
|
|
|
settings |= MGMT_SETTING_BREDR;
|
|
|
settings |= MGMT_SETTING_LINK_SECURITY;
|
|
|
- }
|
|
|
-
|
|
|
- if (enable_hs)
|
|
|
settings |= MGMT_SETTING_HS;
|
|
|
+ }
|
|
|
|
|
|
- if (lmp_le_capable(hdev))
|
|
|
+ if (lmp_le_capable(hdev)) {
|
|
|
settings |= MGMT_SETTING_LE;
|
|
|
+ settings |= MGMT_SETTING_ADVERTISING;
|
|
|
+ }
|
|
|
|
|
|
return settings;
|
|
|
}
|
|
@@ -406,7 +410,7 @@ static u32 get_current_settings(struct hci_dev *hdev)
|
|
|
if (test_bit(HCI_PAIRABLE, &hdev->dev_flags))
|
|
|
settings |= MGMT_SETTING_PAIRABLE;
|
|
|
|
|
|
- if (lmp_bredr_capable(hdev))
|
|
|
+ if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
|
|
|
settings |= MGMT_SETTING_BREDR;
|
|
|
|
|
|
if (test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
|
|
@@ -421,6 +425,9 @@ static u32 get_current_settings(struct hci_dev *hdev)
|
|
|
if (test_bit(HCI_HS_ENABLED, &hdev->dev_flags))
|
|
|
settings |= MGMT_SETTING_HS;
|
|
|
|
|
|
+ if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags))
|
|
|
+ settings |= MGMT_SETTING_ADVERTISING;
|
|
|
+
|
|
|
return settings;
|
|
|
}
|
|
|
|
|
@@ -804,6 +811,12 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
|
|
|
hci_dev_lock(hdev);
|
|
|
|
|
|
+ if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) {
|
|
|
+ err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED,
|
|
|
+ MGMT_STATUS_BUSY);
|
|
|
+ goto failed;
|
|
|
+ }
|
|
|
+
|
|
|
if (test_and_clear_bit(HCI_AUTO_OFF, &hdev->dev_flags)) {
|
|
|
cancel_delayed_work(&hdev->power_off);
|
|
|
|
|
@@ -820,12 +833,6 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
goto failed;
|
|
|
}
|
|
|
|
|
|
- if (mgmt_pending_find(MGMT_OP_SET_POWERED, hdev)) {
|
|
|
- err = cmd_status(sk, hdev->id, MGMT_OP_SET_POWERED,
|
|
|
- MGMT_STATUS_BUSY);
|
|
|
- goto failed;
|
|
|
- }
|
|
|
-
|
|
|
cmd = mgmt_pending_add(sk, MGMT_OP_SET_POWERED, hdev, data, len);
|
|
|
if (!cmd) {
|
|
|
err = -ENOMEM;
|
|
@@ -883,20 +890,71 @@ static int new_settings(struct hci_dev *hdev, struct sock *skip)
|
|
|
return mgmt_event(MGMT_EV_NEW_SETTINGS, hdev, &ev, sizeof(ev), skip);
|
|
|
}
|
|
|
|
|
|
+struct cmd_lookup {
|
|
|
+ struct sock *sk;
|
|
|
+ struct hci_dev *hdev;
|
|
|
+ u8 mgmt_status;
|
|
|
+};
|
|
|
+
|
|
|
+static void settings_rsp(struct pending_cmd *cmd, void *data)
|
|
|
+{
|
|
|
+ struct cmd_lookup *match = data;
|
|
|
+
|
|
|
+ send_settings_rsp(cmd->sk, cmd->opcode, match->hdev);
|
|
|
+
|
|
|
+ list_del(&cmd->list);
|
|
|
+
|
|
|
+ if (match->sk == NULL) {
|
|
|
+ match->sk = cmd->sk;
|
|
|
+ sock_hold(match->sk);
|
|
|
+ }
|
|
|
+
|
|
|
+ mgmt_pending_free(cmd);
|
|
|
+}
|
|
|
+
|
|
|
+static void cmd_status_rsp(struct pending_cmd *cmd, void *data)
|
|
|
+{
|
|
|
+ u8 *status = data;
|
|
|
+
|
|
|
+ cmd_status(cmd->sk, cmd->index, cmd->opcode, *status);
|
|
|
+ mgmt_pending_remove(cmd);
|
|
|
+}
|
|
|
+
|
|
|
+static u8 mgmt_bredr_support(struct hci_dev *hdev)
|
|
|
+{
|
|
|
+ if (!lmp_bredr_capable(hdev))
|
|
|
+ return MGMT_STATUS_NOT_SUPPORTED;
|
|
|
+ else if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
|
|
|
+ return MGMT_STATUS_REJECTED;
|
|
|
+ else
|
|
|
+ return MGMT_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
+static u8 mgmt_le_support(struct hci_dev *hdev)
|
|
|
+{
|
|
|
+ if (!lmp_le_capable(hdev))
|
|
|
+ return MGMT_STATUS_NOT_SUPPORTED;
|
|
|
+ else if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
|
|
|
+ return MGMT_STATUS_REJECTED;
|
|
|
+ else
|
|
|
+ return MGMT_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
static int set_discoverable(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
u16 len)
|
|
|
{
|
|
|
struct mgmt_cp_set_discoverable *cp = data;
|
|
|
struct pending_cmd *cmd;
|
|
|
u16 timeout;
|
|
|
- u8 scan;
|
|
|
+ u8 scan, status;
|
|
|
int err;
|
|
|
|
|
|
BT_DBG("request for %s", hdev->name);
|
|
|
|
|
|
- if (!lmp_bredr_capable(hdev))
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status);
|
|
|
|
|
|
if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_DISCOVERABLE,
|
|
@@ -1045,14 +1103,15 @@ static int set_connectable(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
struct mgmt_mode *cp = data;
|
|
|
struct pending_cmd *cmd;
|
|
|
struct hci_request req;
|
|
|
- u8 scan;
|
|
|
+ u8 scan, status;
|
|
|
int err;
|
|
|
|
|
|
BT_DBG("request for %s", hdev->name);
|
|
|
|
|
|
- if (!lmp_bredr_capable(hdev))
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status);
|
|
|
|
|
|
if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_CONNECTABLE,
|
|
@@ -1168,14 +1227,15 @@ static int set_link_security(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
{
|
|
|
struct mgmt_mode *cp = data;
|
|
|
struct pending_cmd *cmd;
|
|
|
- u8 val;
|
|
|
+ u8 val, status;
|
|
|
int err;
|
|
|
|
|
|
BT_DBG("request for %s", hdev->name);
|
|
|
|
|
|
- if (!lmp_bredr_capable(hdev))
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status);
|
|
|
|
|
|
if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_LINK_SECURITY,
|
|
@@ -1236,11 +1296,15 @@ static int set_ssp(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
{
|
|
|
struct mgmt_mode *cp = data;
|
|
|
struct pending_cmd *cmd;
|
|
|
- u8 val;
|
|
|
+ u8 val, status;
|
|
|
int err;
|
|
|
|
|
|
BT_DBG("request for %s", hdev->name);
|
|
|
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status)
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP, status);
|
|
|
+
|
|
|
if (!lmp_ssp_capable(hdev))
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_SSP,
|
|
|
MGMT_STATUS_NOT_SUPPORTED);
|
|
@@ -1302,23 +1366,64 @@ failed:
|
|
|
static int set_hs(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
{
|
|
|
struct mgmt_mode *cp = data;
|
|
|
+ bool changed;
|
|
|
+ u8 status;
|
|
|
+ int err;
|
|
|
|
|
|
BT_DBG("request for %s", hdev->name);
|
|
|
|
|
|
- if (!enable_hs)
|
|
|
- return cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status)
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_HS, status);
|
|
|
|
|
|
if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
|
|
|
MGMT_STATUS_INVALID_PARAMS);
|
|
|
|
|
|
- if (cp->val)
|
|
|
- set_bit(HCI_HS_ENABLED, &hdev->dev_flags);
|
|
|
- else
|
|
|
- clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
|
|
|
+ hci_dev_lock(hdev);
|
|
|
+
|
|
|
+ if (cp->val) {
|
|
|
+ changed = !test_and_set_bit(HCI_HS_ENABLED, &hdev->dev_flags);
|
|
|
+ } else {
|
|
|
+ if (hdev_is_powered(hdev)) {
|
|
|
+ err = cmd_status(sk, hdev->id, MGMT_OP_SET_HS,
|
|
|
+ MGMT_STATUS_REJECTED);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ changed = test_and_clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
|
|
|
+ }
|
|
|
+
|
|
|
+ err = send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
|
|
|
+ if (err < 0)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ if (changed)
|
|
|
+ err = new_settings(hdev, sk);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ hci_dev_unlock(hdev);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static void le_enable_complete(struct hci_dev *hdev, u8 status)
|
|
|
+{
|
|
|
+ struct cmd_lookup match = { NULL, hdev };
|
|
|
|
|
|
- return send_settings_rsp(sk, MGMT_OP_SET_HS, hdev);
|
|
|
+ if (status) {
|
|
|
+ u8 mgmt_err = mgmt_status(status);
|
|
|
+
|
|
|
+ mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, cmd_status_rsp,
|
|
|
+ &mgmt_err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match);
|
|
|
+
|
|
|
+ new_settings(hdev, match.sk);
|
|
|
+
|
|
|
+ if (match.sk)
|
|
|
+ sock_put(match.sk);
|
|
|
}
|
|
|
|
|
|
static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
@@ -1326,6 +1431,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
struct mgmt_mode *cp = data;
|
|
|
struct hci_cp_write_le_host_supported hci_cp;
|
|
|
struct pending_cmd *cmd;
|
|
|
+ struct hci_request req;
|
|
|
int err;
|
|
|
u8 val, enabled;
|
|
|
|
|
@@ -1340,7 +1446,7 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
MGMT_STATUS_INVALID_PARAMS);
|
|
|
|
|
|
/* LE-only devices do not allow toggling LE on/off */
|
|
|
- if (!lmp_bredr_capable(hdev))
|
|
|
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
|
|
MGMT_STATUS_REJECTED);
|
|
|
|
|
@@ -1357,6 +1463,11 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
changed = true;
|
|
|
}
|
|
|
|
|
|
+ if (!val && test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
|
|
|
+ clear_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+
|
|
|
err = send_settings_rsp(sk, MGMT_OP_SET_LE, hdev);
|
|
|
if (err < 0)
|
|
|
goto unlock;
|
|
@@ -1367,7 +1478,8 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- if (mgmt_pending_find(MGMT_OP_SET_LE, hdev)) {
|
|
|
+ if (mgmt_pending_find(MGMT_OP_SET_LE, hdev) ||
|
|
|
+ mgmt_pending_find(MGMT_OP_SET_ADVERTISING, hdev)) {
|
|
|
err = cmd_status(sk, hdev->id, MGMT_OP_SET_LE,
|
|
|
MGMT_STATUS_BUSY);
|
|
|
goto unlock;
|
|
@@ -1386,8 +1498,15 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
hci_cp.simul = lmp_le_br_capable(hdev);
|
|
|
}
|
|
|
|
|
|
- err = hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp),
|
|
|
- &hci_cp);
|
|
|
+ hci_req_init(&req, hdev);
|
|
|
+
|
|
|
+ if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags) && !val)
|
|
|
+ hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(val), &val);
|
|
|
+
|
|
|
+ hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(hci_cp),
|
|
|
+ &hci_cp);
|
|
|
+
|
|
|
+ err = hci_req_run(&req, le_enable_complete);
|
|
|
if (err < 0)
|
|
|
mgmt_pending_remove(cmd);
|
|
|
|
|
@@ -1706,6 +1825,12 @@ static int load_link_keys(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
u16 key_count, expected_len;
|
|
|
int i;
|
|
|
|
|
|
+ BT_DBG("request for %s", hdev->name);
|
|
|
+
|
|
|
+ if (!lmp_bredr_capable(hdev))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LINK_KEYS,
|
|
|
+ MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+
|
|
|
key_count = __le16_to_cpu(cp->key_count);
|
|
|
|
|
|
expected_len = sizeof(*cp) + key_count *
|
|
@@ -2685,6 +2810,7 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
|
|
|
struct hci_request req;
|
|
|
/* General inquiry access code (GIAC) */
|
|
|
u8 lap[3] = { 0x33, 0x8b, 0x9e };
|
|
|
+ u8 status;
|
|
|
int err;
|
|
|
|
|
|
BT_DBG("%s", hdev->name);
|
|
@@ -2721,9 +2847,10 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
|
|
|
|
|
|
switch (hdev->discovery.type) {
|
|
|
case DISCOV_TYPE_BREDR:
|
|
|
- if (!lmp_bredr_capable(hdev)) {
|
|
|
+ status = mgmt_bredr_support(hdev);
|
|
|
+ if (status) {
|
|
|
err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status);
|
|
|
mgmt_pending_remove(cmd);
|
|
|
goto failed;
|
|
|
}
|
|
@@ -2745,15 +2872,16 @@ static int start_discovery(struct sock *sk, struct hci_dev *hdev,
|
|
|
|
|
|
case DISCOV_TYPE_LE:
|
|
|
case DISCOV_TYPE_INTERLEAVED:
|
|
|
- if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags)) {
|
|
|
+ status = mgmt_le_support(hdev);
|
|
|
+ if (status) {
|
|
|
err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
|
|
|
- MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+ status);
|
|
|
mgmt_pending_remove(cmd);
|
|
|
goto failed;
|
|
|
}
|
|
|
|
|
|
if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED &&
|
|
|
- !lmp_bredr_capable(hdev)) {
|
|
|
+ !test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
|
|
|
err = cmd_status(sk, hdev->id, MGMT_OP_START_DISCOVERY,
|
|
|
MGMT_STATUS_NOT_SUPPORTED);
|
|
|
mgmt_pending_remove(cmd);
|
|
@@ -3065,6 +3193,135 @@ static int set_device_id(struct sock *sk, struct hci_dev *hdev, void *data,
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+static void set_advertising_complete(struct hci_dev *hdev, u8 status)
|
|
|
+{
|
|
|
+ struct cmd_lookup match = { NULL, hdev };
|
|
|
+
|
|
|
+ if (status) {
|
|
|
+ u8 mgmt_err = mgmt_status(status);
|
|
|
+
|
|
|
+ mgmt_pending_foreach(MGMT_OP_SET_ADVERTISING, hdev,
|
|
|
+ cmd_status_rsp, &mgmt_err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ mgmt_pending_foreach(MGMT_OP_SET_ADVERTISING, hdev, settings_rsp,
|
|
|
+ &match);
|
|
|
+
|
|
|
+ new_settings(hdev, match.sk);
|
|
|
+
|
|
|
+ if (match.sk)
|
|
|
+ sock_put(match.sk);
|
|
|
+}
|
|
|
+
|
|
|
+static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
+{
|
|
|
+ struct mgmt_mode *cp = data;
|
|
|
+ struct pending_cmd *cmd;
|
|
|
+ struct hci_request req;
|
|
|
+ u8 val, enabled, status;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ BT_DBG("request for %s", hdev->name);
|
|
|
+
|
|
|
+ status = mgmt_le_support(hdev);
|
|
|
+ if (status)
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
|
|
|
+ status);
|
|
|
+
|
|
|
+ if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
|
|
|
+ MGMT_STATUS_INVALID_PARAMS);
|
|
|
+
|
|
|
+ hci_dev_lock(hdev);
|
|
|
+
|
|
|
+ val = !!cp->val;
|
|
|
+ enabled = test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
|
|
|
+
|
|
|
+ if (!hdev_is_powered(hdev) || val == enabled) {
|
|
|
+ bool changed = false;
|
|
|
+
|
|
|
+ if (val != test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
|
|
|
+ change_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags);
|
|
|
+ changed = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = send_settings_rsp(sk, MGMT_OP_SET_ADVERTISING, hdev);
|
|
|
+ if (err < 0)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ if (changed)
|
|
|
+ err = new_settings(hdev, sk);
|
|
|
+
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mgmt_pending_find(MGMT_OP_SET_ADVERTISING, hdev) ||
|
|
|
+ mgmt_pending_find(MGMT_OP_SET_LE, hdev)) {
|
|
|
+ err = cmd_status(sk, hdev->id, MGMT_OP_SET_ADVERTISING,
|
|
|
+ MGMT_STATUS_BUSY);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_ADVERTISING, hdev, data, len);
|
|
|
+ if (!cmd) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ hci_req_init(&req, hdev);
|
|
|
+
|
|
|
+ hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(val), &val);
|
|
|
+
|
|
|
+ err = hci_req_run(&req, set_advertising_complete);
|
|
|
+ if (err < 0)
|
|
|
+ mgmt_pending_remove(cmd);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ hci_dev_unlock(hdev);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int set_static_address(struct sock *sk, struct hci_dev *hdev,
|
|
|
+ void *data, u16 len)
|
|
|
+{
|
|
|
+ struct mgmt_cp_set_static_address *cp = data;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ BT_DBG("%s", hdev->name);
|
|
|
+
|
|
|
+ if (!lmp_le_capable(hdev))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS,
|
|
|
+ MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+
|
|
|
+ if (hdev_is_powered(hdev))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS,
|
|
|
+ MGMT_STATUS_REJECTED);
|
|
|
+
|
|
|
+ if (bacmp(&cp->bdaddr, BDADDR_ANY)) {
|
|
|
+ if (!bacmp(&cp->bdaddr, BDADDR_NONE))
|
|
|
+ return cmd_status(sk, hdev->id,
|
|
|
+ MGMT_OP_SET_STATIC_ADDRESS,
|
|
|
+ MGMT_STATUS_INVALID_PARAMS);
|
|
|
+
|
|
|
+ /* Two most significant bits shall be set */
|
|
|
+ if ((cp->bdaddr.b[5] & 0xc0) != 0xc0)
|
|
|
+ return cmd_status(sk, hdev->id,
|
|
|
+ MGMT_OP_SET_STATIC_ADDRESS,
|
|
|
+ MGMT_STATUS_INVALID_PARAMS);
|
|
|
+ }
|
|
|
+
|
|
|
+ hci_dev_lock(hdev);
|
|
|
+
|
|
|
+ bacpy(&hdev->static_addr, &cp->bdaddr);
|
|
|
+
|
|
|
+ err = cmd_complete(sk, hdev->id, MGMT_OP_SET_STATIC_ADDRESS, 0, NULL, 0);
|
|
|
+
|
|
|
+ hci_dev_unlock(hdev);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
static void fast_connectable_complete(struct hci_dev *hdev, u8 status)
|
|
|
{
|
|
|
struct pending_cmd *cmd;
|
|
@@ -3108,7 +3365,8 @@ static int set_fast_connectable(struct sock *sk, struct hci_dev *hdev,
|
|
|
|
|
|
BT_DBG("%s", hdev->name);
|
|
|
|
|
|
- if (!lmp_bredr_capable(hdev) || hdev->hci_ver < BLUETOOTH_VER_1_2)
|
|
|
+ if (!test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags) ||
|
|
|
+ hdev->hci_ver < BLUETOOTH_VER_1_2)
|
|
|
return cmd_status(sk, hdev->id, MGMT_OP_SET_FAST_CONNECTABLE,
|
|
|
MGMT_STATUS_NOT_SUPPORTED);
|
|
|
|
|
@@ -3162,6 +3420,121 @@ unlock:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+static void set_bredr_complete(struct hci_dev *hdev, u8 status)
|
|
|
+{
|
|
|
+ struct pending_cmd *cmd;
|
|
|
+
|
|
|
+ BT_DBG("status 0x%02x", status);
|
|
|
+
|
|
|
+ hci_dev_lock(hdev);
|
|
|
+
|
|
|
+ cmd = mgmt_pending_find(MGMT_OP_SET_BREDR, hdev);
|
|
|
+ if (!cmd)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ if (status) {
|
|
|
+ u8 mgmt_err = mgmt_status(status);
|
|
|
+
|
|
|
+ /* We need to restore the flag if related HCI commands
|
|
|
+ * failed.
|
|
|
+ */
|
|
|
+ clear_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
|
|
|
+
|
|
|
+ cmd_status(cmd->sk, cmd->index, cmd->opcode, mgmt_err);
|
|
|
+ } else {
|
|
|
+ send_settings_rsp(cmd->sk, MGMT_OP_SET_BREDR, hdev);
|
|
|
+ new_settings(hdev, cmd->sk);
|
|
|
+ }
|
|
|
+
|
|
|
+ mgmt_pending_remove(cmd);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ hci_dev_unlock(hdev);
|
|
|
+}
|
|
|
+
|
|
|
+static int set_bredr(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
|
|
|
+{
|
|
|
+ struct mgmt_mode *cp = data;
|
|
|
+ struct pending_cmd *cmd;
|
|
|
+ struct hci_request req;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ BT_DBG("request for %s", hdev->name);
|
|
|
+
|
|
|
+ if (!lmp_bredr_capable(hdev) || !lmp_le_capable(hdev))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+
|
|
|
+ if (!test_bit(HCI_LE_ENABLED, &hdev->dev_flags))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_STATUS_REJECTED);
|
|
|
+
|
|
|
+ if (cp->val != 0x00 && cp->val != 0x01)
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_STATUS_INVALID_PARAMS);
|
|
|
+
|
|
|
+ hci_dev_lock(hdev);
|
|
|
+
|
|
|
+ if (cp->val == test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags)) {
|
|
|
+ err = send_settings_rsp(sk, MGMT_OP_SET_BREDR, hdev);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!hdev_is_powered(hdev)) {
|
|
|
+ if (!cp->val) {
|
|
|
+ clear_bit(HCI_CONNECTABLE, &hdev->dev_flags);
|
|
|
+ clear_bit(HCI_DISCOVERABLE, &hdev->dev_flags);
|
|
|
+ clear_bit(HCI_SSP_ENABLED, &hdev->dev_flags);
|
|
|
+ clear_bit(HCI_LINK_SECURITY, &hdev->dev_flags);
|
|
|
+ clear_bit(HCI_FAST_CONNECTABLE, &hdev->dev_flags);
|
|
|
+ clear_bit(HCI_HS_ENABLED, &hdev->dev_flags);
|
|
|
+ }
|
|
|
+
|
|
|
+ change_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
|
|
|
+
|
|
|
+ err = send_settings_rsp(sk, MGMT_OP_SET_BREDR, hdev);
|
|
|
+ if (err < 0)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ err = new_settings(hdev, sk);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Reject disabling when powered on */
|
|
|
+ if (!cp->val) {
|
|
|
+ err = cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_STATUS_REJECTED);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mgmt_pending_find(MGMT_OP_SET_BREDR, hdev)) {
|
|
|
+ err = cmd_status(sk, hdev->id, MGMT_OP_SET_BREDR,
|
|
|
+ MGMT_STATUS_BUSY);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ cmd = mgmt_pending_add(sk, MGMT_OP_SET_BREDR, hdev, data, len);
|
|
|
+ if (!cmd) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* We need to flip the bit already here so that hci_update_ad
|
|
|
+ * generates the correct flags.
|
|
|
+ */
|
|
|
+ set_bit(HCI_BREDR_ENABLED, &hdev->dev_flags);
|
|
|
+
|
|
|
+ hci_req_init(&req, hdev);
|
|
|
+ hci_update_ad(&req);
|
|
|
+ err = hci_req_run(&req, set_bredr_complete);
|
|
|
+ if (err < 0)
|
|
|
+ mgmt_pending_remove(cmd);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ hci_dev_unlock(hdev);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
static bool ltk_is_valid(struct mgmt_ltk_info *key)
|
|
|
{
|
|
|
if (key->authenticated != 0x00 && key->authenticated != 0x01)
|
|
@@ -3180,6 +3553,12 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
|
|
|
u16 key_count, expected_len;
|
|
|
int i, err;
|
|
|
|
|
|
+ BT_DBG("request for %s", hdev->name);
|
|
|
+
|
|
|
+ if (!lmp_le_capable(hdev))
|
|
|
+ return cmd_status(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS,
|
|
|
+ MGMT_STATUS_NOT_SUPPORTED);
|
|
|
+
|
|
|
key_count = __le16_to_cpu(cp->key_count);
|
|
|
|
|
|
expected_len = sizeof(*cp) + key_count *
|
|
@@ -3276,6 +3655,9 @@ static const struct mgmt_handler {
|
|
|
{ block_device, false, MGMT_BLOCK_DEVICE_SIZE },
|
|
|
{ unblock_device, false, MGMT_UNBLOCK_DEVICE_SIZE },
|
|
|
{ set_device_id, false, MGMT_SET_DEVICE_ID_SIZE },
|
|
|
+ { set_advertising, false, MGMT_SETTING_SIZE },
|
|
|
+ { set_bredr, false, MGMT_SETTING_SIZE },
|
|
|
+ { set_static_address, false, MGMT_SET_STATIC_ADDRESS_SIZE },
|
|
|
};
|
|
|
|
|
|
|
|
@@ -3320,6 +3702,12 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen)
|
|
|
MGMT_STATUS_INVALID_INDEX);
|
|
|
goto done;
|
|
|
}
|
|
|
+
|
|
|
+ if (test_bit(HCI_USER_CHANNEL, &hdev->dev_flags)) {
|
|
|
+ err = cmd_status(sk, index, opcode,
|
|
|
+ MGMT_STATUS_INVALID_INDEX);
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (opcode >= ARRAY_SIZE(mgmt_handlers) ||
|
|
@@ -3365,14 +3753,6 @@ done:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static void cmd_status_rsp(struct pending_cmd *cmd, void *data)
|
|
|
-{
|
|
|
- u8 *status = data;
|
|
|
-
|
|
|
- cmd_status(cmd->sk, cmd->index, cmd->opcode, *status);
|
|
|
- mgmt_pending_remove(cmd);
|
|
|
-}
|
|
|
-
|
|
|
int mgmt_index_added(struct hci_dev *hdev)
|
|
|
{
|
|
|
if (!mgmt_valid_hdev(hdev))
|
|
@@ -3393,28 +3773,6 @@ int mgmt_index_removed(struct hci_dev *hdev)
|
|
|
return mgmt_event(MGMT_EV_INDEX_REMOVED, hdev, NULL, 0, NULL);
|
|
|
}
|
|
|
|
|
|
-struct cmd_lookup {
|
|
|
- struct sock *sk;
|
|
|
- struct hci_dev *hdev;
|
|
|
- u8 mgmt_status;
|
|
|
-};
|
|
|
-
|
|
|
-static void settings_rsp(struct pending_cmd *cmd, void *data)
|
|
|
-{
|
|
|
- struct cmd_lookup *match = data;
|
|
|
-
|
|
|
- send_settings_rsp(cmd->sk, cmd->opcode, match->hdev);
|
|
|
-
|
|
|
- list_del(&cmd->list);
|
|
|
-
|
|
|
- if (match->sk == NULL) {
|
|
|
- match->sk = cmd->sk;
|
|
|
- sock_hold(match->sk);
|
|
|
- }
|
|
|
-
|
|
|
- mgmt_pending_free(cmd);
|
|
|
-}
|
|
|
-
|
|
|
static void set_bredr_scan(struct hci_request *req)
|
|
|
{
|
|
|
struct hci_dev *hdev = req->hdev;
|
|
@@ -3481,6 +3839,22 @@ static int powered_update_hci(struct hci_dev *hdev)
|
|
|
cp.simul != lmp_host_le_br_capable(hdev))
|
|
|
hci_req_add(&req, HCI_OP_WRITE_LE_HOST_SUPPORTED,
|
|
|
sizeof(cp), &cp);
|
|
|
+
|
|
|
+ /* In case BR/EDR was toggled during the AUTO_OFF phase */
|
|
|
+ hci_update_ad(&req);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (lmp_le_capable(hdev)) {
|
|
|
+ /* Set random address to static address if configured */
|
|
|
+ if (bacmp(&hdev->static_addr, BDADDR_ANY))
|
|
|
+ hci_req_add(&req, HCI_OP_LE_SET_RANDOM_ADDR, 6,
|
|
|
+ &hdev->static_addr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (test_bit(HCI_LE_PERIPHERAL, &hdev->dev_flags)) {
|
|
|
+ u8 adv = 0x01;
|
|
|
+
|
|
|
+ hci_req_add(&req, HCI_OP_LE_SET_ADV_ENABLE, sizeof(adv), &adv);
|
|
|
}
|
|
|
|
|
|
link_sec = test_bit(HCI_LINK_SECURITY, &hdev->dev_flags);
|
|
@@ -3489,7 +3863,8 @@ static int powered_update_hci(struct hci_dev *hdev)
|
|
|
sizeof(link_sec), &link_sec);
|
|
|
|
|
|
if (lmp_bredr_capable(hdev)) {
|
|
|
- set_bredr_scan(&req);
|
|
|
+ if (test_bit(HCI_BREDR_ENABLED, &hdev->dev_flags))
|
|
|
+ set_bredr_scan(&req);
|
|
|
update_class(&req);
|
|
|
update_name(&req);
|
|
|
update_eir(&req);
|
|
@@ -4132,44 +4507,6 @@ int mgmt_read_local_oob_data_reply_complete(struct hci_dev *hdev, u8 *hash,
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-int mgmt_le_enable_complete(struct hci_dev *hdev, u8 enable, u8 status)
|
|
|
-{
|
|
|
- struct cmd_lookup match = { NULL, hdev };
|
|
|
- bool changed = false;
|
|
|
- int err = 0;
|
|
|
-
|
|
|
- if (status) {
|
|
|
- u8 mgmt_err = mgmt_status(status);
|
|
|
-
|
|
|
- if (enable && test_and_clear_bit(HCI_LE_ENABLED,
|
|
|
- &hdev->dev_flags))
|
|
|
- err = new_settings(hdev, NULL);
|
|
|
-
|
|
|
- mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, cmd_status_rsp,
|
|
|
- &mgmt_err);
|
|
|
-
|
|
|
- return err;
|
|
|
- }
|
|
|
-
|
|
|
- if (enable) {
|
|
|
- if (!test_and_set_bit(HCI_LE_ENABLED, &hdev->dev_flags))
|
|
|
- changed = true;
|
|
|
- } else {
|
|
|
- if (test_and_clear_bit(HCI_LE_ENABLED, &hdev->dev_flags))
|
|
|
- changed = true;
|
|
|
- }
|
|
|
-
|
|
|
- mgmt_pending_foreach(MGMT_OP_SET_LE, hdev, settings_rsp, &match);
|
|
|
-
|
|
|
- if (changed)
|
|
|
- err = new_settings(hdev, match.sk);
|
|
|
-
|
|
|
- if (match.sk)
|
|
|
- sock_put(match.sk);
|
|
|
-
|
|
|
- return err;
|
|
|
-}
|
|
|
-
|
|
|
int mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type,
|
|
|
u8 addr_type, u8 *dev_class, s8 rssi, u8 cfm_name, u8
|
|
|
ssp, u8 *eir, u16 eir_len)
|
|
@@ -4286,6 +4623,3 @@ int mgmt_device_unblocked(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type)
|
|
|
return mgmt_event(MGMT_EV_DEVICE_UNBLOCKED, hdev, &ev, sizeof(ev),
|
|
|
cmd ? cmd->sk : NULL);
|
|
|
}
|
|
|
-
|
|
|
-module_param(enable_hs, bool, 0644);
|
|
|
-MODULE_PARM_DESC(enable_hs, "Enable High Speed support");
|