summaryrefslogtreecommitdiff
path: root/net/bluetooth/mgmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/bluetooth/mgmt.c')
-rw-r--r--net/bluetooth/mgmt.c576
1 files changed, 403 insertions, 173 deletions
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 7fd87e7135b5..7998fb279165 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -38,7 +38,7 @@
#include "mgmt_util.h"
#define MGMT_VERSION 1
-#define MGMT_REVISION 9
+#define MGMT_REVISION 10
static const u16 mgmt_commands[] = {
MGMT_OP_READ_INDEX_LIST,
@@ -832,6 +832,20 @@ static struct mgmt_pending_cmd *pending_find_data(u16 opcode,
return mgmt_pending_find_data(HCI_CHANNEL_CONTROL, opcode, hdev, data);
}
+static u8 get_current_adv_instance(struct hci_dev *hdev)
+{
+ /* The "Set Advertising" setting supersedes the "Add Advertising"
+ * setting. Here we set the advertising data based on which
+ * setting was set. When neither apply, default to the global settings,
+ * represented by instance "0".
+ */
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
+ !hci_dev_test_flag(hdev, HCI_ADVERTISING))
+ return hdev->cur_adv_instance;
+
+ return 0x00;
+}
+
static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
{
u8 ad_len = 0;
@@ -858,19 +872,25 @@ static u8 create_default_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
return ad_len;
}
-static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 *ptr)
+static u8 create_instance_scan_rsp_data(struct hci_dev *hdev, u8 instance,
+ u8 *ptr)
{
+ struct adv_info *adv_instance;
+
+ adv_instance = hci_find_adv_instance(hdev, instance);
+ if (!adv_instance)
+ return 0;
+
/* TODO: Set the appropriate entries based on advertising instance flags
* here once flags other than 0 are supported.
*/
- memcpy(ptr, hdev->adv_instance.scan_rsp_data,
- hdev->adv_instance.scan_rsp_len);
+ memcpy(ptr, adv_instance->scan_rsp_data,
+ adv_instance->scan_rsp_len);
- return hdev->adv_instance.scan_rsp_len;
+ return adv_instance->scan_rsp_len;
}
-static void update_scan_rsp_data_for_instance(struct hci_request *req,
- u8 instance)
+static void update_inst_scan_rsp_data(struct hci_request *req, u8 instance)
{
struct hci_dev *hdev = req->hdev;
struct hci_cp_le_set_scan_rsp_data cp;
@@ -882,7 +902,7 @@ static void update_scan_rsp_data_for_instance(struct hci_request *req,
memset(&cp, 0, sizeof(cp));
if (instance)
- len = create_instance_scan_rsp_data(hdev, cp.data);
+ len = create_instance_scan_rsp_data(hdev, instance, cp.data);
else
len = create_default_scan_rsp_data(hdev, cp.data);
@@ -900,21 +920,7 @@ static void update_scan_rsp_data_for_instance(struct hci_request *req,
static void update_scan_rsp_data(struct hci_request *req)
{
- struct hci_dev *hdev = req->hdev;
- u8 instance;
-
- /* The "Set Advertising" setting supersedes the "Add Advertising"
- * setting. Here we set the scan response data based on which
- * setting was set. When neither apply, default to the global settings,
- * represented by instance "0".
- */
- if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
- !hci_dev_test_flag(hdev, HCI_ADVERTISING))
- instance = 0x01;
- else
- instance = 0x00;
-
- update_scan_rsp_data_for_instance(req, instance);
+ update_inst_scan_rsp_data(req, get_current_adv_instance(req->hdev));
}
static u8 get_adv_discov_flags(struct hci_dev *hdev)
@@ -941,20 +947,6 @@ static u8 get_adv_discov_flags(struct hci_dev *hdev)
return 0;
}
-static u8 get_current_adv_instance(struct hci_dev *hdev)
-{
- /* The "Set Advertising" setting supersedes the "Add Advertising"
- * setting. Here we set the advertising data based on which
- * setting was set. When neither apply, default to the global settings,
- * represented by instance "0".
- */
- if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
- !hci_dev_test_flag(hdev, HCI_ADVERTISING))
- return 0x01;
-
- return 0x00;
-}
-
static bool get_connectable(struct hci_dev *hdev)
{
struct mgmt_pending_cmd *cmd;
@@ -975,41 +967,65 @@ static bool get_connectable(struct hci_dev *hdev)
static u32 get_adv_instance_flags(struct hci_dev *hdev, u8 instance)
{
u32 flags;
+ struct adv_info *adv_instance;
- if (instance > 0x01)
- return 0;
+ if (instance == 0x00) {
+ /* Instance 0 always manages the "Tx Power" and "Flags"
+ * fields
+ */
+ flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS;
- if (instance == 0x01)
- return hdev->adv_instance.flags;
+ /* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting
+ * corresponds to the "connectable" instance flag.
+ */
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE))
+ flags |= MGMT_ADV_FLAG_CONNECTABLE;
- /* Instance 0 always manages the "Tx Power" and "Flags" fields */
- flags = MGMT_ADV_FLAG_TX_POWER | MGMT_ADV_FLAG_MANAGED_FLAGS;
+ return flags;
+ }
- /* For instance 0, the HCI_ADVERTISING_CONNECTABLE setting corresponds
- * to the "connectable" instance flag.
- */
- if (hci_dev_test_flag(hdev, HCI_ADVERTISING_CONNECTABLE))
- flags |= MGMT_ADV_FLAG_CONNECTABLE;
+ adv_instance = hci_find_adv_instance(hdev, instance);
- return flags;
+ /* Return 0 when we got an invalid instance identifier. */
+ if (!adv_instance)
+ return 0;
+
+ return adv_instance->flags;
}
-static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance)
+static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev)
{
- /* Ignore instance 0 and other unsupported instances */
- if (instance != 0x01)
+ u8 instance = get_current_adv_instance(hdev);
+ struct adv_info *adv_instance;
+
+ /* Ignore instance 0 */
+ if (instance == 0x00)
+ return 0;
+
+ adv_instance = hci_find_adv_instance(hdev, instance);
+ if (!adv_instance)
return 0;
/* TODO: Take into account the "appearance" and "local-name" flags here.
* These are currently being ignored as they are not supported.
*/
- return hdev->adv_instance.scan_rsp_len;
+ return adv_instance->scan_rsp_len;
}
static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
{
+ struct adv_info *adv_instance = NULL;
u8 ad_len = 0, flags = 0;
- u32 instance_flags = get_adv_instance_flags(hdev, instance);
+ u32 instance_flags;
+
+ /* Return 0 when the current instance identifier is invalid. */
+ if (instance) {
+ adv_instance = hci_find_adv_instance(hdev, instance);
+ if (!adv_instance)
+ return 0;
+ }
+
+ instance_flags = get_adv_instance_flags(hdev, instance);
/* The Add Advertising command allows userspace to set both the general
* and limited discoverable flags.
@@ -1043,12 +1059,11 @@ static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
}
}
- if (instance) {
- memcpy(ptr, hdev->adv_instance.adv_data,
- hdev->adv_instance.adv_data_len);
-
- ad_len += hdev->adv_instance.adv_data_len;
- ptr += hdev->adv_instance.adv_data_len;
+ if (adv_instance) {
+ memcpy(ptr, adv_instance->adv_data,
+ adv_instance->adv_data_len);
+ ad_len += adv_instance->adv_data_len;
+ ptr += adv_instance->adv_data_len;
}
/* Provide Tx Power only if we can provide a valid value for it */
@@ -1065,7 +1080,7 @@ static u8 create_instance_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr)
return ad_len;
}
-static void update_adv_data_for_instance(struct hci_request *req, u8 instance)
+static void update_inst_adv_data(struct hci_request *req, u8 instance)
{
struct hci_dev *hdev = req->hdev;
struct hci_cp_le_set_adv_data cp;
@@ -1093,10 +1108,7 @@ static void update_adv_data_for_instance(struct hci_request *req, u8 instance)
static void update_adv_data(struct hci_request *req)
{
- struct hci_dev *hdev = req->hdev;
- u8 instance = get_current_adv_instance(hdev);
-
- update_adv_data_for_instance(req, instance);
+ update_inst_adv_data(req, get_current_adv_instance(req->hdev));
}
int mgmt_update_adv_data(struct hci_dev *hdev)
@@ -1277,7 +1289,7 @@ static void enable_advertising(struct hci_request *req)
if (connectable)
cp.type = LE_ADV_IND;
- else if (get_adv_instance_scan_rsp_len(hdev, instance))
+ else if (get_cur_adv_instance_scan_rsp_len(hdev))
cp.type = LE_ADV_SCAN_IND;
else
cp.type = LE_ADV_NONCONN_IND;
@@ -1459,27 +1471,141 @@ static void advertising_removed(struct sock *sk, struct hci_dev *hdev,
mgmt_event(MGMT_EV_ADVERTISING_REMOVED, hdev, &ev, sizeof(ev), sk);
}
-static void clear_adv_instance(struct hci_dev *hdev)
+static int schedule_adv_instance(struct hci_request *req, u8 instance,
+ bool force) {
+ struct hci_dev *hdev = req->hdev;
+ struct adv_info *adv_instance = NULL;
+ u16 timeout;
+
+ if (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
+ !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
+ return -EPERM;
+
+ if (hdev->adv_instance_timeout)
+ return -EBUSY;
+
+ adv_instance = hci_find_adv_instance(hdev, instance);
+ if (!adv_instance)
+ return -ENOENT;
+
+ /* A zero timeout means unlimited advertising. As long as there is
+ * only one instance, duration should be ignored. We still set a timeout
+ * in case further instances are being added later on.
+ *
+ * If the remaining lifetime of the instance is more than the duration
+ * then the timeout corresponds to the duration, otherwise it will be
+ * reduced to the remaining instance lifetime.
+ */
+ if (adv_instance->timeout == 0 ||
+ adv_instance->duration <= adv_instance->remaining_time)
+ timeout = adv_instance->duration;
+ else
+ timeout = adv_instance->remaining_time;
+
+ /* The remaining time is being reduced unless the instance is being
+ * advertised without time limit.
+ */
+ if (adv_instance->timeout)
+ adv_instance->remaining_time =
+ adv_instance->remaining_time - timeout;
+
+ hdev->adv_instance_timeout = timeout;
+ queue_delayed_work(hdev->workqueue,
+ &hdev->adv_instance_expire,
+ msecs_to_jiffies(timeout * 1000));
+
+ /* If we're just re-scheduling the same instance again then do not
+ * execute any HCI commands. This happens when a single instance is
+ * being advertised.
+ */
+ if (!force && hdev->cur_adv_instance == instance &&
+ hci_dev_test_flag(hdev, HCI_LE_ADV))
+ return 0;
+
+ hdev->cur_adv_instance = instance;
+ update_adv_data(req);
+ update_scan_rsp_data(req);
+ enable_advertising(req);
+
+ return 0;
+}
+
+static void cancel_adv_timeout(struct hci_dev *hdev)
{
- struct hci_request req;
+ if (hdev->adv_instance_timeout) {
+ hdev->adv_instance_timeout = 0;
+ cancel_delayed_work(&hdev->adv_instance_expire);
+ }
+}
- if (!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
- return;
+/* For a single instance:
+ * - force == true: The instance will be removed even when its remaining
+ * lifetime is not zero.
+ * - force == false: the instance will be deactivated but kept stored unless
+ * the remaining lifetime is zero.
+ *
+ * For instance == 0x00:
+ * - force == true: All instances will be removed regardless of their timeout
+ * setting.
+ * - force == false: Only instances that have a timeout will be removed.
+ */
+static void clear_adv_instance(struct hci_dev *hdev, struct hci_request *req,
+ u8 instance, bool force)
+{
+ struct adv_info *adv_instance, *n, *next_instance = NULL;
+ int err;
+ u8 rem_inst;
- if (hdev->adv_instance.timeout)
- cancel_delayed_work(&hdev->adv_instance.timeout_exp);
+ /* Cancel any timeout concerning the removed instance(s). */
+ if (!instance || hdev->cur_adv_instance == instance)
+ cancel_adv_timeout(hdev);
- memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
- advertising_removed(NULL, hdev, 1);
- hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
+ /* Get the next instance to advertise BEFORE we remove
+ * the current one. This can be the same instance again
+ * if there is only one instance.
+ */
+ if (instance && hdev->cur_adv_instance == instance)
+ next_instance = hci_get_next_instance(hdev, instance);
- if (!hdev_is_powered(hdev) ||
+ if (instance == 0x00) {
+ list_for_each_entry_safe(adv_instance, n, &hdev->adv_instances,
+ list) {
+ if (!(force || adv_instance->timeout))
+ continue;
+
+ rem_inst = adv_instance->instance;
+ err = hci_remove_adv_instance(hdev, rem_inst);
+ if (!err)
+ advertising_removed(NULL, hdev, rem_inst);
+ }
+ hdev->cur_adv_instance = 0x00;
+ } else {
+ adv_instance = hci_find_adv_instance(hdev, instance);
+
+ if (force || (adv_instance && adv_instance->timeout &&
+ !adv_instance->remaining_time)) {
+ /* Don't advertise a removed instance. */
+ if (next_instance &&
+ next_instance->instance == instance)
+ next_instance = NULL;
+
+ err = hci_remove_adv_instance(hdev, instance);
+ if (!err)
+ advertising_removed(NULL, hdev, instance);
+ }
+ }
+
+ if (list_empty(&hdev->adv_instances)) {
+ hdev->cur_adv_instance = 0x00;
+ hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
+ }
+
+ if (!req || !hdev_is_powered(hdev) ||
hci_dev_test_flag(hdev, HCI_ADVERTISING))
return;
- hci_req_init(&req, hdev);
- disable_advertising(&req);
- hci_req_run(&req, NULL);
+ if (next_instance)
+ schedule_adv_instance(req, next_instance->instance, false);
}
static int clean_up_hci_state(struct hci_dev *hdev)
@@ -1497,8 +1623,7 @@ static int clean_up_hci_state(struct hci_dev *hdev)
hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
}
- if (hdev->adv_instance.timeout)
- clear_adv_instance(hdev);
+ clear_adv_instance(hdev, NULL, 0x00, false);
if (hci_dev_test_flag(hdev, HCI_LE_ADV))
disable_advertising(&req);
@@ -2453,6 +2578,9 @@ static int set_le(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
val = !!cp->val;
enabled = lmp_host_le_capable(hdev);
+ if (!val)
+ clear_adv_instance(hdev, NULL, 0x00, true);
+
if (!hdev_is_powered(hdev) || val == enabled) {
bool changed = false;
@@ -4087,6 +4215,7 @@ static bool trigger_le_scan(struct hci_request *req, u16 interval, u8 *status)
return false;
}
+ cancel_adv_timeout(hdev);
disable_advertising(req);
}
@@ -4669,6 +4798,9 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status,
{
struct cmd_lookup match = { NULL, hdev };
struct hci_request req;
+ u8 instance;
+ struct adv_info *adv_instance;
+ int err;
hci_dev_lock(hdev);
@@ -4694,18 +4826,31 @@ static void set_advertising_complete(struct hci_dev *hdev, u8 status,
sock_put(match.sk);
/* If "Set Advertising" was just disabled and instance advertising was
- * set up earlier, then enable the advertising instance.
+ * set up earlier, then re-enable multi-instance advertising.
*/
if (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
- !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
+ !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) ||
+ list_empty(&hdev->adv_instances))
goto unlock;
+ instance = hdev->cur_adv_instance;
+ if (!instance) {
+ adv_instance = list_first_entry_or_null(&hdev->adv_instances,
+ struct adv_info, list);
+ if (!adv_instance)
+ goto unlock;
+
+ instance = adv_instance->instance;
+ }
+
hci_req_init(&req, hdev);
- update_adv_data(&req);
- enable_advertising(&req);
+ err = schedule_adv_instance(&req, instance, true);
+
+ if (!err)
+ err = hci_req_run(&req, enable_advertising_instance);
- if (hci_req_run(&req, enable_advertising_instance) < 0)
+ if (err)
BT_ERR("Failed to re-configure advertising");
unlock:
@@ -4790,10 +4935,15 @@ static int set_advertising(struct sock *sk, struct hci_dev *hdev, void *data,
else
hci_dev_clear_flag(hdev, HCI_ADVERTISING_CONNECTABLE);
+ cancel_adv_timeout(hdev);
+
if (val) {
- /* Switch to instance "0" for the Set Advertising setting. */
- update_adv_data_for_instance(&req, 0);
- update_scan_rsp_data_for_instance(&req, 0);
+ /* Switch to instance "0" for the Set Advertising setting.
+ * We cannot use update_[adv|scan_rsp]_data() here as the
+ * HCI_ADVERTISING flag is not yet set.
+ */
+ update_inst_adv_data(&req, 0x00);
+ update_inst_scan_rsp_data(&req, 0x00);
enable_advertising(&req);
} else {
disable_advertising(&req);
@@ -6781,8 +6931,9 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
{
struct mgmt_rp_read_adv_features *rp;
size_t rp_len;
- int err;
+ int err, i;
bool instance;
+ struct adv_info *adv_instance;
u32 supported_flags;
BT_DBG("%s", hdev->name);
@@ -6795,12 +6946,9 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
rp_len = sizeof(*rp);
- /* Currently only one instance is supported, so just add 1 to the
- * response length.
- */
instance = hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE);
if (instance)
- rp_len++;
+ rp_len += hdev->adv_instance_cnt;
rp = kmalloc(rp_len, GFP_ATOMIC);
if (!rp) {
@@ -6813,14 +6961,18 @@ static int read_adv_features(struct sock *sk, struct hci_dev *hdev,
rp->supported_flags = cpu_to_le32(supported_flags);
rp->max_adv_data_len = HCI_MAX_AD_LENGTH;
rp->max_scan_rsp_len = HCI_MAX_AD_LENGTH;
- rp->max_instances = 1;
+ rp->max_instances = HCI_MAX_ADV_INSTANCES;
- /* Currently only one instance is supported, so simply return the
- * current instance number.
- */
if (instance) {
- rp->num_instances = 1;
- rp->instance[0] = 1;
+ i = 0;
+ list_for_each_entry(adv_instance, &hdev->adv_instances, list) {
+ if (i >= hdev->adv_instance_cnt)
+ break;
+
+ rp->instance[i] = adv_instance->instance;
+ i++;
+ }
+ rp->num_instances = hdev->adv_instance_cnt;
} else {
rp->num_instances = 0;
}
@@ -6882,7 +7034,10 @@ static void add_advertising_complete(struct hci_dev *hdev, u8 status,
u16 opcode)
{
struct mgmt_pending_cmd *cmd;
+ struct mgmt_cp_add_advertising *cp;
struct mgmt_rp_add_advertising rp;
+ struct adv_info *adv_instance, *n;
+ u8 instance;
BT_DBG("status %d", status);
@@ -6890,16 +7045,32 @@ static void add_advertising_complete(struct hci_dev *hdev, u8 status,
cmd = pending_find(MGMT_OP_ADD_ADVERTISING, hdev);
- if (status) {
+ if (status)
hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
- memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
- advertising_removed(cmd ? cmd->sk : NULL, hdev, 1);
+
+ list_for_each_entry_safe(adv_instance, n, &hdev->adv_instances, list) {
+ if (!adv_instance->pending)
+ continue;
+
+ if (!status) {
+ adv_instance->pending = false;
+ continue;
+ }
+
+ instance = adv_instance->instance;
+
+ if (hdev->cur_adv_instance == instance)
+ cancel_adv_timeout(hdev);
+
+ hci_remove_adv_instance(hdev, instance);
+ advertising_removed(cmd ? cmd->sk : NULL, hdev, instance);
}
if (!cmd)
goto unlock;
- rp.instance = 0x01;
+ cp = cmd->param;
+ rp.instance = cp->instance;
if (status)
mgmt_cmd_status(cmd->sk, cmd->index, cmd->opcode,
@@ -6914,15 +7085,28 @@ unlock:
hci_dev_unlock(hdev);
}
-static void adv_timeout_expired(struct work_struct *work)
+void mgmt_adv_timeout_expired(struct hci_dev *hdev)
{
- struct hci_dev *hdev = container_of(work, struct hci_dev,
- adv_instance.timeout_exp.work);
+ u8 instance;
+ struct hci_request req;
+
+ hdev->adv_instance_timeout = 0;
- hdev->adv_instance.timeout = 0;
+ instance = get_current_adv_instance(hdev);
+ if (instance == 0x00)
+ return;
hci_dev_lock(hdev);
- clear_adv_instance(hdev);
+ hci_req_init(&req, hdev);
+
+ clear_adv_instance(hdev, &req, instance, false);
+
+ if (list_empty(&hdev->adv_instances))
+ disable_advertising(&req);
+
+ if (!skb_queue_empty(&req.cmd_q))
+ hci_req_run(&req, NULL);
+
hci_dev_unlock(hdev);
}
@@ -6934,7 +7118,10 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
u32 flags;
u32 supported_flags;
u8 status;
- u16 timeout;
+ u16 timeout, duration;
+ unsigned int prev_instance_cnt = hdev->adv_instance_cnt;
+ u8 schedule_instance = 0;
+ struct adv_info *next_instance;
int err;
struct mgmt_pending_cmd *cmd;
struct hci_request req;
@@ -6948,12 +7135,13 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
flags = __le32_to_cpu(cp->flags);
timeout = __le16_to_cpu(cp->timeout);
+ duration = __le16_to_cpu(cp->duration);
- /* The current implementation only supports adding one instance and only
- * a subset of the specified flags.
+ /* The current implementation only supports a subset of the specified
+ * flags.
*/
supported_flags = get_supported_adv_flags(hdev);
- if (cp->instance != 0x01 || (flags & ~supported_flags))
+ if (flags & ~supported_flags)
return mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
MGMT_STATUS_INVALID_PARAMS);
@@ -6981,38 +7169,51 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
goto unlock;
}
- INIT_DELAYED_WORK(&hdev->adv_instance.timeout_exp, adv_timeout_expired);
-
- hdev->adv_instance.flags = flags;
- hdev->adv_instance.adv_data_len = cp->adv_data_len;
- hdev->adv_instance.scan_rsp_len = cp->scan_rsp_len;
-
- if (cp->adv_data_len)
- memcpy(hdev->adv_instance.adv_data, cp->data, cp->adv_data_len);
-
- if (cp->scan_rsp_len)
- memcpy(hdev->adv_instance.scan_rsp_data,
- cp->data + cp->adv_data_len, cp->scan_rsp_len);
-
- if (hdev->adv_instance.timeout)
- cancel_delayed_work(&hdev->adv_instance.timeout_exp);
+ err = hci_add_adv_instance(hdev, cp->instance, flags,
+ cp->adv_data_len, cp->data,
+ cp->scan_rsp_len,
+ cp->data + cp->adv_data_len,
+ timeout, duration);
+ if (err < 0) {
+ err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
+ MGMT_STATUS_FAILED);
+ goto unlock;
+ }
- hdev->adv_instance.timeout = timeout;
+ /* Only trigger an advertising added event if a new instance was
+ * actually added.
+ */
+ if (hdev->adv_instance_cnt > prev_instance_cnt)
+ advertising_added(sk, hdev, cp->instance);
- if (timeout)
- queue_delayed_work(hdev->workqueue,
- &hdev->adv_instance.timeout_exp,
- msecs_to_jiffies(timeout * 1000));
+ hci_dev_set_flag(hdev, HCI_ADVERTISING_INSTANCE);
- if (!hci_dev_test_and_set_flag(hdev, HCI_ADVERTISING_INSTANCE))
- advertising_added(sk, hdev, 1);
+ if (hdev->cur_adv_instance == cp->instance) {
+ /* If the currently advertised instance is being changed then
+ * cancel the current advertising and schedule the next
+ * instance. If there is only one instance then the overridden
+ * advertising data will be visible right away.
+ */
+ cancel_adv_timeout(hdev);
+
+ next_instance = hci_get_next_instance(hdev, cp->instance);
+ if (next_instance)
+ schedule_instance = next_instance->instance;
+ } else if (!hdev->adv_instance_timeout) {
+ /* Immediately advertise the new instance if no other
+ * instance is currently being advertised.
+ */
+ schedule_instance = cp->instance;
+ }
- /* If the HCI_ADVERTISING flag is set or the device isn't powered then
- * we have no HCI communication to make. Simply return.
+ /* If the HCI_ADVERTISING flag is set or the device isn't powered or
+ * there is no instance to be advertised then we have no HCI
+ * communication to make. Simply return.
*/
if (!hdev_is_powered(hdev) ||
- hci_dev_test_flag(hdev, HCI_ADVERTISING)) {
- rp.instance = 0x01;
+ hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
+ !schedule_instance) {
+ rp.instance = cp->instance;
err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
MGMT_STATUS_SUCCESS, &rp, sizeof(rp));
goto unlock;
@@ -7030,11 +7231,11 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
hci_req_init(&req, hdev);
- update_adv_data(&req);
- update_scan_rsp_data(&req);
- enable_advertising(&req);
+ err = schedule_adv_instance(&req, schedule_instance, true);
+
+ if (!err)
+ err = hci_req_run(&req, add_advertising_complete);
- err = hci_req_run(&req, add_advertising_complete);
if (err < 0)
mgmt_pending_remove(cmd);
@@ -7048,6 +7249,7 @@ static void remove_advertising_complete(struct hci_dev *hdev, u8 status,
u16 opcode)
{
struct mgmt_pending_cmd *cmd;
+ struct mgmt_cp_remove_advertising *cp;
struct mgmt_rp_remove_advertising rp;
BT_DBG("status %d", status);
@@ -7062,7 +7264,8 @@ static void remove_advertising_complete(struct hci_dev *hdev, u8 status,
if (!cmd)
goto unlock;
- rp.instance = 1;
+ cp = cmd->param;
+ rp.instance = cp->instance;
mgmt_cmd_complete(cmd->sk, cmd->index, cmd->opcode, MGMT_STATUS_SUCCESS,
&rp, sizeof(rp));
@@ -7077,21 +7280,21 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
{
struct mgmt_cp_remove_advertising *cp = data;
struct mgmt_rp_remove_advertising rp;
- int err;
struct mgmt_pending_cmd *cmd;
struct hci_request req;
+ int err;
BT_DBG("%s", hdev->name);
- /* The current implementation only allows modifying instance no 1. A
- * value of 0 indicates that all instances should be cleared.
- */
- if (cp->instance > 1)
- return mgmt_cmd_status(sk, hdev->id, MGMT_OP_REMOVE_ADVERTISING,
- MGMT_STATUS_INVALID_PARAMS);
-
hci_dev_lock(hdev);
+ if (cp->instance && !hci_find_adv_instance(hdev, cp->instance)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_REMOVE_ADVERTISING,
+ MGMT_STATUS_INVALID_PARAMS);
+ goto unlock;
+ }
+
if (pending_find(MGMT_OP_ADD_ADVERTISING, hdev) ||
pending_find(MGMT_OP_REMOVE_ADVERTISING, hdev) ||
pending_find(MGMT_OP_SET_LE, hdev)) {
@@ -7106,21 +7309,21 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
goto unlock;
}
- if (hdev->adv_instance.timeout)
- cancel_delayed_work(&hdev->adv_instance.timeout_exp);
-
- memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
+ hci_req_init(&req, hdev);
- advertising_removed(sk, hdev, 1);
+ clear_adv_instance(hdev, &req, cp->instance, true);
- hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
+ if (list_empty(&hdev->adv_instances))
+ disable_advertising(&req);
- /* If the HCI_ADVERTISING flag is set or the device isn't powered then
- * we have no HCI communication to make. Simply return.
+ /* If no HCI commands have been collected so far or the HCI_ADVERTISING
+ * flag is set or the device isn't powered then we have no HCI
+ * communication to make. Simply return.
*/
- if (!hdev_is_powered(hdev) ||
+ if (skb_queue_empty(&req.cmd_q) ||
+ !hdev_is_powered(hdev) ||
hci_dev_test_flag(hdev, HCI_ADVERTISING)) {
- rp.instance = 1;
+ rp.instance = cp->instance;
err = mgmt_cmd_complete(sk, hdev->id,
MGMT_OP_REMOVE_ADVERTISING,
MGMT_STATUS_SUCCESS, &rp, sizeof(rp));
@@ -7134,9 +7337,6 @@ static int remove_advertising(struct sock *sk, struct hci_dev *hdev,
goto unlock;
}
- hci_req_init(&req, hdev);
- disable_advertising(&req);
-
err = hci_req_run(&req, remove_advertising_complete);
if (err < 0)
mgmt_pending_remove(cmd);
@@ -7361,6 +7561,7 @@ static void powered_complete(struct hci_dev *hdev, u8 status, u16 opcode)
static int powered_update_hci(struct hci_dev *hdev)
{
struct hci_request req;
+ struct adv_info *adv_instance;
u8 link_sec;
hci_req_init(&req, hdev);
@@ -7400,14 +7601,27 @@ static int powered_update_hci(struct hci_dev *hdev)
* 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)) {
+ if (hci_dev_test_flag(hdev, HCI_LE_ENABLED) &&
+ (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
+ !hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))) {
update_adv_data(&req);
update_scan_rsp_data(&req);
}
- if (hci_dev_test_flag(hdev, HCI_ADVERTISING) ||
- hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
+ 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))
enable_advertising(&req);
+ else if (hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE) &&
+ hdev->cur_adv_instance)
+ schedule_adv_instance(&req, hdev->cur_adv_instance,
+ true);
restart_le_actions(&req);
}
@@ -7577,7 +7791,7 @@ void mgmt_new_ltk(struct hci_dev *hdev, struct smp_ltk *key, bool persistent)
memset(&ev, 0, sizeof(ev));
/* Devices using resolvable or non-resolvable random addresses
- * without providing an indentity resolving key don't require
+ * without providing an identity resolving key don't require
* to store long term keys. Their addresses will change the
* next time around.
*
@@ -7603,7 +7817,12 @@ void mgmt_new_ltk(struct hci_dev *hdev, struct smp_ltk *key, bool persistent)
if (key->type == SMP_LTK)
ev.key.master = 1;
- memcpy(ev.key.val, key->val, sizeof(key->val));
+ /* Make sure we copy only the significant bytes based on the
+ * encryption key size, and set the rest of the value to zeroes.
+ */
+ memcpy(ev.key.val, key->val, sizeof(key->enc_size));
+ memset(ev.key.val + key->enc_size, 0,
+ sizeof(ev.key.val) - key->enc_size);
mgmt_event(MGMT_EV_NEW_LONG_TERM_KEY, hdev, &ev, sizeof(ev), NULL);
}
@@ -7617,7 +7836,7 @@ void mgmt_new_irk(struct hci_dev *hdev, struct smp_irk *irk)
/* For identity resolving keys from devices that are already
* using a public address or static random address, do not
* ask for storing this key. The identity resolving key really
- * is only mandatory for devices using resovlable random
+ * is only mandatory for devices using resolvable random
* addresses.
*
* Storing all identity resolving keys has the downside that
@@ -7646,7 +7865,7 @@ void mgmt_new_csrk(struct hci_dev *hdev, struct smp_csrk *csrk,
memset(&ev, 0, sizeof(ev));
/* Devices using resolvable or non-resolvable random addresses
- * without providing an indentity resolving key don't require
+ * without providing an identity resolving key don't require
* to store signature resolving keys. Their addresses will change
* the next time around.
*
@@ -8387,13 +8606,24 @@ static void adv_enable_complete(struct hci_dev *hdev, u8 status, u16 opcode)
void mgmt_reenable_advertising(struct hci_dev *hdev)
{
struct hci_request req;
+ u8 instance;
if (!hci_dev_test_flag(hdev, HCI_ADVERTISING) &&
!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
return;
+ instance = get_current_adv_instance(hdev);
+
hci_req_init(&req, hdev);
- enable_advertising(&req);
+
+ if (instance) {
+ schedule_adv_instance(&req, instance, true);
+ } else {
+ update_adv_data(&req);
+ update_scan_rsp_data(&req);
+ enable_advertising(&req);
+ }
+
hci_req_run(&req, adv_enable_complete);
}