summaryrefslogtreecommitdiff
path: root/net/bluetooth/hci_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/bluetooth/hci_core.c')
-rw-r--r--net/bluetooth/hci_core.c569
1 files changed, 381 insertions, 188 deletions
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index a0f99baafd35..b3a5a3cc9372 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -29,6 +29,7 @@
#include <linux/rfkill.h>
#include <linux/debugfs.h>
#include <linux/crypto.h>
+#include <linux/kcov.h>
#include <linux/property.h>
#include <linux/suspend.h>
#include <linux/wait.h>
@@ -594,6 +595,11 @@ static int hci_dev_do_reset(struct hci_dev *hdev)
skb_queue_purge(&hdev->rx_q);
skb_queue_purge(&hdev->cmd_q);
+ /* Cancel these to avoid queueing non-chained pending work */
+ hci_dev_set_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE);
+ cancel_delayed_work(&hdev->cmd_timer);
+ cancel_delayed_work(&hdev->ncmd_timer);
+
/* Avoid potential lockdep warnings from the *_flush() calls by
* ensuring the workqueue is empty up front.
*/
@@ -607,8 +613,13 @@ static int hci_dev_do_reset(struct hci_dev *hdev)
if (hdev->flush)
hdev->flush(hdev);
+ hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE);
+
atomic_set(&hdev->cmd_cnt, 1);
- hdev->acl_cnt = 0; hdev->sco_cnt = 0; hdev->le_cnt = 0;
+ hdev->acl_cnt = 0;
+ hdev->sco_cnt = 0;
+ hdev->le_cnt = 0;
+ hdev->iso_cnt = 0;
ret = hci_reset_sync(hdev);
@@ -1691,63 +1702,77 @@ static void adv_instance_rpa_expired(struct work_struct *work)
}
/* This function requires the caller holds hdev->lock */
-int hci_add_adv_instance(struct hci_dev *hdev, u8 instance, u32 flags,
- u16 adv_data_len, u8 *adv_data,
- u16 scan_rsp_len, u8 *scan_rsp_data,
- u16 timeout, u16 duration, s8 tx_power,
- u32 min_interval, u32 max_interval)
+struct adv_info *hci_add_adv_instance(struct hci_dev *hdev, u8 instance,
+ u32 flags, u16 adv_data_len, u8 *adv_data,
+ u16 scan_rsp_len, u8 *scan_rsp_data,
+ u16 timeout, u16 duration, s8 tx_power,
+ u32 min_interval, u32 max_interval)
{
- struct adv_info *adv_instance;
+ struct adv_info *adv;
- adv_instance = hci_find_adv_instance(hdev, instance);
- if (adv_instance) {
- memset(adv_instance->adv_data, 0,
- sizeof(adv_instance->adv_data));
- memset(adv_instance->scan_rsp_data, 0,
- sizeof(adv_instance->scan_rsp_data));
+ adv = hci_find_adv_instance(hdev, instance);
+ if (adv) {
+ memset(adv->adv_data, 0, sizeof(adv->adv_data));
+ memset(adv->scan_rsp_data, 0, sizeof(adv->scan_rsp_data));
+ memset(adv->per_adv_data, 0, sizeof(adv->per_adv_data));
} else {
if (hdev->adv_instance_cnt >= hdev->le_num_of_adv_sets ||
instance < 1 || instance > hdev->le_num_of_adv_sets)
- return -EOVERFLOW;
+ return ERR_PTR(-EOVERFLOW);
- adv_instance = kzalloc(sizeof(*adv_instance), GFP_KERNEL);
- if (!adv_instance)
- return -ENOMEM;
+ adv = kzalloc(sizeof(*adv), GFP_KERNEL);
+ if (!adv)
+ return ERR_PTR(-ENOMEM);
- adv_instance->pending = true;
- adv_instance->instance = instance;
- list_add(&adv_instance->list, &hdev->adv_instances);
+ adv->pending = true;
+ adv->instance = instance;
+ list_add(&adv->list, &hdev->adv_instances);
hdev->adv_instance_cnt++;
}
- adv_instance->flags = flags;
- adv_instance->adv_data_len = adv_data_len;
- adv_instance->scan_rsp_len = scan_rsp_len;
- adv_instance->min_interval = min_interval;
- adv_instance->max_interval = max_interval;
- adv_instance->tx_power = tx_power;
+ adv->flags = flags;
+ adv->min_interval = min_interval;
+ adv->max_interval = max_interval;
+ adv->tx_power = tx_power;
- if (adv_data_len)
- memcpy(adv_instance->adv_data, adv_data, adv_data_len);
+ hci_set_adv_instance_data(hdev, instance, adv_data_len, adv_data,
+ scan_rsp_len, scan_rsp_data);
- if (scan_rsp_len)
- memcpy(adv_instance->scan_rsp_data,
- scan_rsp_data, scan_rsp_len);
-
- adv_instance->timeout = timeout;
- adv_instance->remaining_time = timeout;
+ adv->timeout = timeout;
+ adv->remaining_time = timeout;
if (duration == 0)
- adv_instance->duration = hdev->def_multi_adv_rotation_duration;
+ adv->duration = hdev->def_multi_adv_rotation_duration;
else
- adv_instance->duration = duration;
+ adv->duration = duration;
- INIT_DELAYED_WORK(&adv_instance->rpa_expired_cb,
- adv_instance_rpa_expired);
+ INIT_DELAYED_WORK(&adv->rpa_expired_cb, adv_instance_rpa_expired);
BT_DBG("%s for %dMR", hdev->name, instance);
- return 0;
+ return adv;
+}
+
+/* This function requires the caller holds hdev->lock */
+struct adv_info *hci_add_per_instance(struct hci_dev *hdev, u8 instance,
+ u32 flags, u8 data_len, u8 *data,
+ u32 min_interval, u32 max_interval)
+{
+ struct adv_info *adv;
+
+ adv = hci_add_adv_instance(hdev, instance, flags, 0, NULL, 0, NULL,
+ 0, 0, HCI_ADV_TX_POWER_NO_PREFERENCE,
+ min_interval, max_interval);
+ if (IS_ERR(adv))
+ return adv;
+
+ adv->periodic = true;
+ adv->per_adv_data_len = data_len;
+
+ if (data)
+ memcpy(adv->per_adv_data, data, data_len);
+
+ return adv;
}
/* This function requires the caller holds hdev->lock */
@@ -1755,29 +1780,33 @@ int hci_set_adv_instance_data(struct hci_dev *hdev, u8 instance,
u16 adv_data_len, u8 *adv_data,
u16 scan_rsp_len, u8 *scan_rsp_data)
{
- struct adv_info *adv_instance;
+ struct adv_info *adv;
- adv_instance = hci_find_adv_instance(hdev, instance);
+ adv = hci_find_adv_instance(hdev, instance);
/* If advertisement doesn't exist, we can't modify its data */
- if (!adv_instance)
+ if (!adv)
return -ENOENT;
- if (adv_data_len) {
- memset(adv_instance->adv_data, 0,
- sizeof(adv_instance->adv_data));
- memcpy(adv_instance->adv_data, adv_data, adv_data_len);
- adv_instance->adv_data_len = adv_data_len;
+ if (adv_data_len && ADV_DATA_CMP(adv, adv_data, adv_data_len)) {
+ memset(adv->adv_data, 0, sizeof(adv->adv_data));
+ memcpy(adv->adv_data, adv_data, adv_data_len);
+ adv->adv_data_len = adv_data_len;
+ adv->adv_data_changed = true;
}
- if (scan_rsp_len) {
- memset(adv_instance->scan_rsp_data, 0,
- sizeof(adv_instance->scan_rsp_data));
- memcpy(adv_instance->scan_rsp_data,
- scan_rsp_data, scan_rsp_len);
- adv_instance->scan_rsp_len = scan_rsp_len;
+ if (scan_rsp_len && SCAN_RSP_CMP(adv, scan_rsp_data, scan_rsp_len)) {
+ memset(adv->scan_rsp_data, 0, sizeof(adv->scan_rsp_data));
+ memcpy(adv->scan_rsp_data, scan_rsp_data, scan_rsp_len);
+ adv->scan_rsp_len = scan_rsp_len;
+ adv->scan_rsp_changed = true;
}
+ /* Mark as changed if there are flags which would affect it */
+ if (((adv->flags & MGMT_ADV_FLAG_APPEARANCE) && hdev->appearance) ||
+ adv->flags & MGMT_ADV_FLAG_LOCAL_NAME)
+ adv->scan_rsp_changed = true;
+
return 0;
}
@@ -1874,151 +1903,120 @@ void hci_free_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor)
kfree(monitor);
}
-int hci_add_adv_patterns_monitor_complete(struct hci_dev *hdev, u8 status)
-{
- return mgmt_add_adv_patterns_monitor_complete(hdev, status);
-}
-
-int hci_remove_adv_monitor_complete(struct hci_dev *hdev, u8 status)
-{
- return mgmt_remove_adv_monitor_complete(hdev, status);
-}
-
/* Assigns handle to a monitor, and if offloading is supported and power is on,
* also attempts to forward the request to the controller.
- * Returns true if request is forwarded (result is pending), false otherwise.
- * This function requires the caller holds hdev->lock.
+ * This function requires the caller holds hci_req_sync_lock.
*/
-bool hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor,
- int *err)
+int hci_add_adv_monitor(struct hci_dev *hdev, struct adv_monitor *monitor)
{
int min, max, handle;
+ int status = 0;
- *err = 0;
+ if (!monitor)
+ return -EINVAL;
- if (!monitor) {
- *err = -EINVAL;
- return false;
- }
+ hci_dev_lock(hdev);
min = HCI_MIN_ADV_MONITOR_HANDLE;
max = HCI_MIN_ADV_MONITOR_HANDLE + HCI_MAX_ADV_MONITOR_NUM_HANDLES;
handle = idr_alloc(&hdev->adv_monitors_idr, monitor, min, max,
GFP_KERNEL);
- if (handle < 0) {
- *err = handle;
- return false;
- }
+
+ hci_dev_unlock(hdev);
+
+ if (handle < 0)
+ return handle;
monitor->handle = handle;
if (!hdev_is_powered(hdev))
- return false;
+ return status;
switch (hci_get_adv_monitor_offload_ext(hdev)) {
case HCI_ADV_MONITOR_EXT_NONE:
- hci_update_passive_scan(hdev);
- bt_dev_dbg(hdev, "%s add monitor status %d", hdev->name, *err);
+ bt_dev_dbg(hdev, "%s add monitor %d status %d", hdev->name,
+ monitor->handle, status);
/* Message was not forwarded to controller - not an error */
- return false;
+ break;
+
case HCI_ADV_MONITOR_EXT_MSFT:
- *err = msft_add_monitor_pattern(hdev, monitor);
- bt_dev_dbg(hdev, "%s add monitor msft status %d", hdev->name,
- *err);
+ status = msft_add_monitor_pattern(hdev, monitor);
+ bt_dev_dbg(hdev, "%s add monitor %d msft status %d", hdev->name,
+ monitor->handle, status);
break;
}
- return (*err == 0);
+ return status;
}
/* Attempts to tell the controller and free the monitor. If somehow the
* controller doesn't have a corresponding handle, remove anyway.
- * Returns true if request is forwarded (result is pending), false otherwise.
- * This function requires the caller holds hdev->lock.
+ * This function requires the caller holds hci_req_sync_lock.
*/
-static bool hci_remove_adv_monitor(struct hci_dev *hdev,
- struct adv_monitor *monitor,
- u16 handle, int *err)
+static int hci_remove_adv_monitor(struct hci_dev *hdev,
+ struct adv_monitor *monitor)
{
- *err = 0;
+ int status = 0;
switch (hci_get_adv_monitor_offload_ext(hdev)) {
case HCI_ADV_MONITOR_EXT_NONE: /* also goes here when powered off */
+ bt_dev_dbg(hdev, "%s remove monitor %d status %d", hdev->name,
+ monitor->handle, status);
goto free_monitor;
+
case HCI_ADV_MONITOR_EXT_MSFT:
- *err = msft_remove_monitor(hdev, monitor, handle);
+ status = msft_remove_monitor(hdev, monitor);
+ bt_dev_dbg(hdev, "%s remove monitor %d msft status %d",
+ hdev->name, monitor->handle, status);
break;
}
/* In case no matching handle registered, just free the monitor */
- if (*err == -ENOENT)
+ if (status == -ENOENT)
goto free_monitor;
- return (*err == 0);
+ return status;
free_monitor:
- if (*err == -ENOENT)
+ if (status == -ENOENT)
bt_dev_warn(hdev, "Removing monitor with no matching handle %d",
monitor->handle);
hci_free_adv_monitor(hdev, monitor);
- *err = 0;
- return false;
+ return status;
}
-/* Returns true if request is forwarded (result is pending), false otherwise.
- * This function requires the caller holds hdev->lock.
- */
-bool hci_remove_single_adv_monitor(struct hci_dev *hdev, u16 handle, int *err)
+/* This function requires the caller holds hci_req_sync_lock */
+int hci_remove_single_adv_monitor(struct hci_dev *hdev, u16 handle)
{
struct adv_monitor *monitor = idr_find(&hdev->adv_monitors_idr, handle);
- bool pending;
-
- if (!monitor) {
- *err = -EINVAL;
- return false;
- }
-
- pending = hci_remove_adv_monitor(hdev, monitor, handle, err);
- if (!*err && !pending)
- hci_update_passive_scan(hdev);
- bt_dev_dbg(hdev, "%s remove monitor handle %d, status %d, %spending",
- hdev->name, handle, *err, pending ? "" : "not ");
+ if (!monitor)
+ return -EINVAL;
- return pending;
+ return hci_remove_adv_monitor(hdev, monitor);
}
-/* Returns true if request is forwarded (result is pending), false otherwise.
- * This function requires the caller holds hdev->lock.
- */
-bool hci_remove_all_adv_monitor(struct hci_dev *hdev, int *err)
+/* This function requires the caller holds hci_req_sync_lock */
+int hci_remove_all_adv_monitor(struct hci_dev *hdev)
{
struct adv_monitor *monitor;
int idr_next_id = 0;
- bool pending = false;
- bool update = false;
-
- *err = 0;
+ int status = 0;
- while (!*err && !pending) {
+ while (1) {
monitor = idr_get_next(&hdev->adv_monitors_idr, &idr_next_id);
if (!monitor)
break;
- pending = hci_remove_adv_monitor(hdev, monitor, 0, err);
+ status = hci_remove_adv_monitor(hdev, monitor);
+ if (status)
+ return status;
- if (!*err && !pending)
- update = true;
+ idr_next_id++;
}
- if (update)
- hci_update_passive_scan(hdev);
-
- bt_dev_dbg(hdev, "%s remove all monitors status %d, %spending",
- hdev->name, *err, pending ? "" : "not ");
-
- return pending;
+ return status;
}
/* This function requires the caller holds hdev->lock */
@@ -2640,12 +2638,9 @@ int hci_register_dev(struct hci_dev *hdev)
hci_sock_dev_event(hdev, HCI_DEV_REG);
hci_dev_hold(hdev);
- if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks)) {
- hdev->suspend_notifier.notifier_call = hci_suspend_notifier;
- error = register_pm_notifier(&hdev->suspend_notifier);
- if (error)
- goto err_wqueue;
- }
+ error = hci_register_suspend_notifier(hdev);
+ if (error)
+ goto err_wqueue;
queue_work(hdev->req_workqueue, &hdev->power_on);
@@ -2680,8 +2675,7 @@ void hci_unregister_dev(struct hci_dev *hdev)
hci_cmd_sync_clear(hdev);
- if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks))
- unregister_pm_notifier(&hdev->suspend_notifier);
+ hci_unregister_suspend_notifier(hdev);
msft_unregister(hdev);
@@ -2741,10 +2735,33 @@ void hci_release_dev(struct hci_dev *hdev)
ida_simple_remove(&hci_index_ida, hdev->id);
kfree_skb(hdev->sent_cmd);
+ kfree_skb(hdev->recv_event);
kfree(hdev);
}
EXPORT_SYMBOL(hci_release_dev);
+int hci_register_suspend_notifier(struct hci_dev *hdev)
+{
+ int ret = 0;
+
+ if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks)) {
+ hdev->suspend_notifier.notifier_call = hci_suspend_notifier;
+ ret = register_pm_notifier(&hdev->suspend_notifier);
+ }
+
+ return ret;
+}
+
+int hci_unregister_suspend_notifier(struct hci_dev *hdev)
+{
+ int ret = 0;
+
+ if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks))
+ ret = unregister_pm_notifier(&hdev->suspend_notifier);
+
+ return ret;
+}
+
/* Suspend HCI device */
int hci_suspend_dev(struct hci_dev *hdev)
{
@@ -3025,6 +3042,37 @@ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode)
return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE;
}
+/* Get data from last received event */
+void *hci_recv_event_data(struct hci_dev *hdev, __u8 event)
+{
+ struct hci_event_hdr *hdr;
+ int offset;
+
+ if (!hdev->recv_event)
+ return NULL;
+
+ hdr = (void *)hdev->recv_event->data;
+ offset = sizeof(*hdr);
+
+ if (hdr->evt != event) {
+ /* In case of LE metaevent check the subevent match */
+ if (hdr->evt == HCI_EV_LE_META) {
+ struct hci_ev_le_meta *ev;
+
+ ev = (void *)hdev->recv_event->data + offset;
+ offset += sizeof(*ev);
+ if (ev->subevent == event)
+ goto found;
+ }
+ return NULL;
+ }
+
+found:
+ bt_dev_dbg(hdev, "event 0x%2.2x", event);
+
+ return hdev->recv_event->data + offset;
+}
+
/* Send ACL data */
static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags)
{
@@ -3132,9 +3180,117 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb)
queue_work(hdev->workqueue, &hdev->tx_work);
}
+/* Send ISO data */
+static void hci_add_iso_hdr(struct sk_buff *skb, __u16 handle, __u8 flags)
+{
+ struct hci_iso_hdr *hdr;
+ int len = skb->len;
+
+ skb_push(skb, HCI_ISO_HDR_SIZE);
+ skb_reset_transport_header(skb);
+ hdr = (struct hci_iso_hdr *)skb_transport_header(skb);
+ hdr->handle = cpu_to_le16(hci_handle_pack(handle, flags));
+ hdr->dlen = cpu_to_le16(len);
+}
+
+static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue,
+ struct sk_buff *skb)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct sk_buff *list;
+ __u16 flags;
+
+ skb->len = skb_headlen(skb);
+ skb->data_len = 0;
+
+ hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
+
+ list = skb_shinfo(skb)->frag_list;
+
+ flags = hci_iso_flags_pack(list ? ISO_START : ISO_SINGLE, 0x00);
+ hci_add_iso_hdr(skb, conn->handle, flags);
+
+ if (!list) {
+ /* Non fragmented */
+ BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len);
+
+ skb_queue_tail(queue, skb);
+ } else {
+ /* Fragmented */
+ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len);
+
+ skb_shinfo(skb)->frag_list = NULL;
+
+ __skb_queue_tail(queue, skb);
+
+ do {
+ skb = list; list = list->next;
+
+ hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
+ flags = hci_iso_flags_pack(list ? ISO_CONT : ISO_END,
+ 0x00);
+ hci_add_iso_hdr(skb, conn->handle, flags);
+
+ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len);
+
+ __skb_queue_tail(queue, skb);
+ } while (list);
+ }
+}
+
+void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb)
+{
+ struct hci_dev *hdev = conn->hdev;
+
+ BT_DBG("%s len %d", hdev->name, skb->len);
+
+ hci_queue_iso(conn, &conn->data_q, skb);
+
+ queue_work(hdev->workqueue, &hdev->tx_work);
+}
+
/* ---- HCI TX task (outgoing data) ---- */
/* HCI Connection scheduler */
+static inline void hci_quote_sent(struct hci_conn *conn, int num, int *quote)
+{
+ struct hci_dev *hdev;
+ int cnt, q;
+
+ if (!conn) {
+ *quote = 0;
+ return;
+ }
+
+ hdev = conn->hdev;
+
+ switch (conn->type) {
+ case ACL_LINK:
+ cnt = hdev->acl_cnt;
+ break;
+ case AMP_LINK:
+ cnt = hdev->block_cnt;
+ break;
+ case SCO_LINK:
+ case ESCO_LINK:
+ cnt = hdev->sco_cnt;
+ break;
+ case LE_LINK:
+ cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+ case ISO_LINK:
+ cnt = hdev->iso_mtu ? hdev->iso_cnt :
+ hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
+ break;
+ default:
+ cnt = 0;
+ bt_dev_err(hdev, "unknown link type %d", conn->type);
+ }
+
+ q = cnt / num;
+ *quote = q ? q : 1;
+}
+
static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type,
int *quote)
{
@@ -3167,29 +3323,7 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type,
rcu_read_unlock();
- if (conn) {
- int cnt, q;
-
- switch (conn->type) {
- case ACL_LINK:
- cnt = hdev->acl_cnt;
- break;
- case SCO_LINK:
- case ESCO_LINK:
- cnt = hdev->sco_cnt;
- break;
- case LE_LINK:
- cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
- break;
- default:
- cnt = 0;
- bt_dev_err(hdev, "unknown link type %d", conn->type);
- }
-
- q = cnt / num;
- *quote = q ? q : 1;
- } else
- *quote = 0;
+ hci_quote_sent(conn, num, quote);
BT_DBG("conn %p quote %d", conn, *quote);
return conn;
@@ -3223,7 +3357,7 @@ static struct hci_chan *hci_chan_sent(struct hci_dev *hdev, __u8 type,
struct hci_chan *chan = NULL;
unsigned int num = 0, min = ~0, cur_prio = 0;
struct hci_conn *conn;
- int cnt, q, conn_num = 0;
+ int conn_num = 0;
BT_DBG("%s", hdev->name);
@@ -3273,27 +3407,8 @@ static struct hci_chan *hci_chan_sent(struct hci_dev *hdev, __u8 type,
if (!chan)
return NULL;
- switch (chan->conn->type) {
- case ACL_LINK:
- cnt = hdev->acl_cnt;
- break;
- case AMP_LINK:
- cnt = hdev->block_cnt;
- break;
- case SCO_LINK:
- case ESCO_LINK:
- cnt = hdev->sco_cnt;
- break;
- case LE_LINK:
- cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt;
- break;
- default:
- cnt = 0;
- bt_dev_err(hdev, "unknown link type %d", chan->conn->type);
- }
+ hci_quote_sent(chan->conn, num, quote);
- q = cnt / num;
- *quote = q ? q : 1;
BT_DBG("chan %p quote %d", chan, *quote);
return chan;
}
@@ -3582,18 +3697,46 @@ static void hci_sched_le(struct hci_dev *hdev)
hci_prio_recalculate(hdev, LE_LINK);
}
+/* Schedule CIS */
+static void hci_sched_iso(struct hci_dev *hdev)
+{
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+ int quote, *cnt;
+
+ BT_DBG("%s", hdev->name);
+
+ if (!hci_conn_num(hdev, ISO_LINK))
+ return;
+
+ cnt = hdev->iso_pkts ? &hdev->iso_cnt :
+ hdev->le_pkts ? &hdev->le_cnt : &hdev->acl_cnt;
+ while (*cnt && (conn = hci_low_sent(hdev, ISO_LINK, &quote))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(hdev, skb);
+
+ conn->sent++;
+ if (conn->sent == ~0)
+ conn->sent = 0;
+ (*cnt)--;
+ }
+ }
+}
+
static void hci_tx_work(struct work_struct *work)
{
struct hci_dev *hdev = container_of(work, struct hci_dev, tx_work);
struct sk_buff *skb;
- BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt,
- hdev->sco_cnt, hdev->le_cnt);
+ BT_DBG("%s acl %d sco %d le %d iso %d", hdev->name, hdev->acl_cnt,
+ hdev->sco_cnt, hdev->le_cnt, hdev->iso_cnt);
if (!hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) {
/* Schedule queues and send stuff to HCI driver */
hci_sched_sco(hdev);
hci_sched_esco(hdev);
+ hci_sched_iso(hdev);
hci_sched_acl(hdev);
hci_sched_le(hdev);
}
@@ -3676,6 +3819,43 @@ static void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb)
kfree_skb(skb);
}
+static void hci_isodata_packet(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct hci_iso_hdr *hdr;
+ struct hci_conn *conn;
+ __u16 handle, flags;
+
+ hdr = skb_pull_data(skb, sizeof(*hdr));
+ if (!hdr) {
+ bt_dev_err(hdev, "ISO packet too small");
+ goto drop;
+ }
+
+ handle = __le16_to_cpu(hdr->handle);
+ flags = hci_flags(handle);
+ handle = hci_handle(handle);
+
+ bt_dev_dbg(hdev, "len %d handle 0x%4.4x flags 0x%4.4x", skb->len,
+ handle, flags);
+
+ hci_dev_lock(hdev);
+ conn = hci_conn_hash_lookup_handle(hdev, handle);
+ hci_dev_unlock(hdev);
+
+ if (!conn) {
+ bt_dev_err(hdev, "ISO packet for unknown connection handle %d",
+ handle);
+ goto drop;
+ }
+
+ /* Send to upper protocol */
+ iso_recv(conn, skb, flags);
+ return;
+
+drop:
+ kfree_skb(skb);
+}
+
static bool hci_req_is_complete(struct hci_dev *hdev)
{
struct sk_buff *skb;
@@ -3781,7 +3961,14 @@ static void hci_rx_work(struct work_struct *work)
BT_DBG("%s", hdev->name);
- while ((skb = skb_dequeue(&hdev->rx_q))) {
+ /* The kcov_remote functions used for collecting packet parsing
+ * coverage information from this background thread and associate
+ * the coverage with the syscall's thread which originally injected
+ * the packet. This helps fuzzing the kernel.
+ */
+ for (; (skb = skb_dequeue(&hdev->rx_q)); kcov_remote_stop()) {
+ kcov_remote_start_common(skb_get_kcov_handle(skb));
+
/* Send copy to monitor */
hci_send_to_monitor(hdev, skb);
@@ -3830,6 +4017,11 @@ static void hci_rx_work(struct work_struct *work)
hci_scodata_packet(hdev, skb);
break;
+ case HCI_ISODATA_PKT:
+ BT_DBG("%s ISO data packet", hdev->name);
+ hci_isodata_packet(hdev, skb);
+ break;
+
default:
kfree_skb(skb);
break;
@@ -3864,7 +4056,8 @@ static void hci_cmd_work(struct work_struct *work)
if (res < 0)
__hci_cmd_sync_cancel(hdev, -res);
- if (test_bit(HCI_RESET, &hdev->flags))
+ if (test_bit(HCI_RESET, &hdev->flags) ||
+ hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE))
cancel_delayed_work(&hdev->cmd_timer);
else
schedule_delayed_work(&hdev->cmd_timer,