Bluetooth: Add support for adv instance timeout
authorArman Uguray <armansito@chromium.org>
Mon, 23 Mar 2015 22:57:15 +0000 (15:57 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Tue, 24 Mar 2015 00:53:47 +0000 (01:53 +0100)
This patch implements support for the timeout parameter of the
Add Advertising command.

Signed-off-by: Arman Uguray <armansito@chromium.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
include/net/bluetooth/hci_core.h
net/bluetooth/mgmt.c

index 3a6d4e3d68fe60665e9144e204402206d9308370..540c07feece7fa4c19fcfa11e89d389b2087fd84 100644 (file)
@@ -156,8 +156,10 @@ struct oob_data {
 };
 
 struct adv_info {
+       struct delayed_work timeout_exp;
        __u8    instance;
        __u32   flags;
+       __u16   timeout;
        __u16   adv_data_len;
        __u8    adv_data[HCI_MAX_AD_LENGTH];
        __u16   scan_rsp_len;
index 762ca9be9806222eb0a4db5e2e212954eba5f57c..eda52397a6488cc153f5e1647e7f12a472517831 100644 (file)
@@ -1336,6 +1336,49 @@ static bool hci_stop_discovery(struct hci_request *req)
        return false;
 }
 
+static void advertising_added(struct sock *sk, struct hci_dev *hdev,
+                             u8 instance)
+{
+       struct mgmt_ev_advertising_added ev;
+
+       ev.instance = instance;
+
+       mgmt_event(MGMT_EV_ADVERTISING_ADDED, hdev, &ev, sizeof(ev), sk);
+}
+
+static void advertising_removed(struct sock *sk, struct hci_dev *hdev,
+                               u8 instance)
+{
+       struct mgmt_ev_advertising_removed ev;
+
+       ev.instance = instance;
+
+       mgmt_event(MGMT_EV_ADVERTISING_REMOVED, hdev, &ev, sizeof(ev), sk);
+}
+
+static void clear_adv_instance(struct hci_dev *hdev)
+{
+       struct hci_request req;
+
+       if (!hci_dev_test_flag(hdev, HCI_ADVERTISING_INSTANCE))
+               return;
+
+       if (hdev->adv_instance.timeout)
+               cancel_delayed_work(&hdev->adv_instance.timeout_exp);
+
+       memset(&hdev->adv_instance, 0, sizeof(hdev->adv_instance));
+       advertising_removed(NULL, hdev, 1);
+       hci_dev_clear_flag(hdev, HCI_ADVERTISING_INSTANCE);
+
+       if (!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);
+}
+
 static int clean_up_hci_state(struct hci_dev *hdev)
 {
        struct hci_request req;
@@ -1351,6 +1394,9 @@ 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);
+
        if (hci_dev_test_flag(hdev, HCI_LE_ADV))
                disable_advertising(&req);
 
@@ -6468,26 +6514,6 @@ static bool tlv_data_is_valid(struct hci_dev *hdev, u32 adv_flags, u8 *data,
        return true;
 }
 
-static void advertising_added(struct sock *sk, struct hci_dev *hdev,
-                             u8 instance)
-{
-       struct mgmt_ev_advertising_added ev;
-
-       ev.instance = instance;
-
-       mgmt_event(MGMT_EV_ADVERTISING_ADDED, hdev, &ev, sizeof(ev), sk);
-}
-
-static void advertising_removed(struct sock *sk, struct hci_dev *hdev,
-                               u8 instance)
-{
-       struct mgmt_ev_advertising_removed ev;
-
-       ev.instance = instance;
-
-       mgmt_event(MGMT_EV_ADVERTISING_REMOVED, hdev, &ev, sizeof(ev), sk);
-}
-
 static void add_advertising_complete(struct hci_dev *hdev, u8 status,
                                     u16 opcode)
 {
@@ -6524,6 +6550,18 @@ unlock:
        hci_dev_unlock(hdev);
 }
 
+static void adv_timeout_expired(struct work_struct *work)
+{
+       struct hci_dev *hdev = container_of(work, struct hci_dev,
+                                           adv_instance.timeout_exp.work);
+
+       hdev->adv_instance.timeout = 0;
+
+       hci_dev_lock(hdev);
+       clear_adv_instance(hdev);
+       hci_dev_unlock(hdev);
+}
+
 static int add_advertising(struct sock *sk, struct hci_dev *hdev,
                           void *data, u16 data_len)
 {
@@ -6531,6 +6569,7 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
        struct mgmt_rp_add_advertising rp;
        u32 flags;
        u8 status;
+       u16 timeout;
        int err;
        struct mgmt_pending_cmd *cmd;
        struct hci_request req;
@@ -6543,6 +6582,7 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
                                       status);
 
        flags = __le32_to_cpu(cp->flags);
+       timeout = __le16_to_cpu(cp->timeout);
 
        /* The current implementation only supports adding one instance and
         * doesn't support flags.
@@ -6553,6 +6593,12 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
 
        hci_dev_lock(hdev);
 
+       if (timeout && !hdev_is_powered(hdev)) {
+               err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_ADD_ADVERTISING,
+                                     MGMT_STATUS_REJECTED);
+               goto unlock;
+       }
+
        if (pending_find(MGMT_OP_ADD_ADVERTISING, hdev) ||
            pending_find(MGMT_OP_REMOVE_ADVERTISING, hdev) ||
            pending_find(MGMT_OP_SET_LE, hdev)) {
@@ -6569,6 +6615,8 @@ 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;
@@ -6580,6 +6628,16 @@ static int add_advertising(struct sock *sk, struct hci_dev *hdev,
                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);
+
+       hdev->adv_instance.timeout = timeout;
+
+       if (timeout)
+               queue_delayed_work(hdev->workqueue,
+                                  &hdev->adv_instance.timeout_exp,
+                                  msecs_to_jiffies(timeout * 1000));
+
        if (!hci_dev_test_and_set_flag(hdev, HCI_ADVERTISING_INSTANCE))
                advertising_added(sk, hdev, 1);
 
@@ -6682,6 +6740,9 @@ 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));
 
        advertising_removed(sk, hdev, 1);