From 289649af681f304d5aa727d828d379e098430808 Mon Sep 17 00:00:00 2001 From: Seungyoun Ju Date: Fri, 30 Jun 2017 11:46:53 +0900 Subject: [PATCH] Bluetooth: Fix enhance audio streaming chopping issue If BT controller's buffer is occupied by another profile such as OPP or SPP, and it is blocked due to RF condition, A2DP packets couldn't be sent properly. It causes the A2DP chopping issue. It is because HCI buffer is limited but another tx requests occupy it and audio sreaming packet delay is occurred. So this patch reserves some HCI buffer for A2DP to guarantee A2DP QoS better. Change-Id: I7f423f885ef6b7b0a56880d259fd7cd47ce60213 Signed-off-by: Seungyoun Ju --- include/net/bluetooth/hci_core.h | 13 +++++ include/net/bluetooth/mgmt_tizen.h | 7 +++ net/bluetooth/hci_conn.c | 47 +++++++++++++++++ net/bluetooth/hci_core.c | 81 ++++++++++++++++++++++++++++++ net/bluetooth/hci_event.c | 10 ++++ net/bluetooth/mgmt.c | 37 ++++++++++++++ 6 files changed, 195 insertions(+) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index f9d117c1bf14..e39ee3cfbf2d 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -31,6 +31,11 @@ /* HCI priority */ #define HCI_PRIO_MAX 7 +#ifdef TIZEN_BT +/* Reserved ACL slots for Streaming packets */ +#define STREAMING_RESERVED_SLOTS 2 +#endif + /* HCI Core structures */ struct inquiry_data { bdaddr_t bdaddr; @@ -290,6 +295,9 @@ struct hci_dev { unsigned int acl_cnt; unsigned int sco_cnt; unsigned int le_cnt; +#ifdef TIZEN_BT + unsigned int streaming_cnt; +#endif unsigned int acl_mtu; unsigned int sco_mtu; @@ -345,6 +353,9 @@ struct hci_dev { struct discovery_state le_discovery; #endif struct hci_conn_hash conn_hash; +#ifdef TIZEN_BT + struct hci_conn *streaming_conn; +#endif struct list_head mgmt_pending; struct list_head blacklist; @@ -466,6 +477,7 @@ struct hci_conn { unsigned int sent; #ifdef TIZEN_BT + unsigned int streaming_sent; __u16 tx_len; __u16 tx_time; __u16 rx_len; @@ -1572,6 +1584,7 @@ void mgmt_le_data_length_change_complete(struct hci_dev *hdev, bdaddr_t *bdaddr, u16 tx_octets, u16 tx_time, u16 rx_octets, u16 rx_time); int hci_le_set_data_length(struct hci_conn *conn, u16 tx_octets, u16 tx_time); +int hci_conn_streaming_mode(struct hci_conn *conn, bool streaming_mode); #endif u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, diff --git a/include/net/bluetooth/mgmt_tizen.h b/include/net/bluetooth/mgmt_tizen.h index b4e30aeca4fb..09fd5fe795a8 100644 --- a/include/net/bluetooth/mgmt_tizen.h +++ b/include/net/bluetooth/mgmt_tizen.h @@ -222,6 +222,13 @@ struct mgmt_rp_le_set_data_length { } __packed; #define MGMT_LE_SET_DATA_LENGTH_RSP_SIZE 3 +#define MGMT_OP_SET_STREAMING_MODE (TIZEN_OP_CODE_BASE + 0x1b) +struct mgmt_cp_set_streaming_mode { + uint8_t streaming_mode; + bdaddr_t bdaddr; +} __packed; +#define MGMT_SET_STREAMING_MODE_SIZE 7 + /* EVENTS */ /* For device name update changes */ diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 1848ff8e93da..0c96fd868e0c 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -549,6 +549,12 @@ int hci_conn_del(struct hci_conn *conn) /* Unacked frames */ hdev->acl_cnt += conn->sent; +#ifdef TIZEN_BT + if (hdev->streaming_conn == conn) { + hdev->streaming_conn = NULL; + hdev->streaming_cnt = 0; + } +#endif } else if (conn->type == LE_LINK) { cancel_delayed_work(&conn->le_conn_timeout); @@ -1483,3 +1489,44 @@ struct hci_chan *hci_chan_lookup_handle(struct hci_dev *hdev, __u16 handle) return hchan; } + +#ifdef TIZEN_BT +int hci_conn_streaming_mode(struct hci_conn *conn, bool streaming_mode) +{ + struct hci_dev *hdev = conn->hdev; + + if (streaming_mode && + conn->state != BT_CONNECTED && conn->state != BT_CONFIG) + return -ENOTCONN; + + if (conn->type != ACL_LINK) + return -EOPNOTSUPP; + + if (hdev->acl_pkts < STREAMING_RESERVED_SLOTS * 2) + return -EOPNOTSUPP; + + if (streaming_mode) { + if (hdev->streaming_conn) { + if (hdev->streaming_conn == conn) + return 0; + + BT_ERR("already set [%p], request conn [%p]", + hdev->streaming_conn, conn); + return -EOPNOTSUPP; + } + + hdev->streaming_cnt = STREAMING_RESERVED_SLOTS; + conn->streaming_sent = 0; + hdev->streaming_conn = conn; + } else { + if (hdev->streaming_conn != conn) + return -ENOENT; + + hdev->streaming_conn = NULL; + hdev->streaming_cnt = 0; + conn->streaming_sent = 0; + } + + return 0; +} +#endif diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 39f2026dc477..5f2f4da4712b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3758,8 +3758,15 @@ static struct hci_chan *hci_chan_sent(struct hci_dev *hdev, __u8 type, switch (chan->conn->type) { case ACL_LINK: +#ifdef TIZEN_BT + cnt = hdev->acl_cnt - hdev->streaming_cnt; + if (cnt <= 0) + return NULL; + break; +#else cnt = hdev->acl_cnt; break; +#endif case AMP_LINK: cnt = hdev->block_cnt; break; @@ -3848,6 +3855,69 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt) } } +#ifdef TIZEN_BT +static void hci_sched_streaming_conn(struct hci_dev *hdev) +{ + struct hci_conn *conn = hdev->streaming_conn; + + if (conn->state != BT_CONNECTED && conn->state != BT_CONFIG) + return; + + while (hdev->streaming_cnt && hdev->acl_cnt) { + struct hci_chan *tmp; + struct sk_buff *skb; + struct hci_chan *chan = NULL; + unsigned int priority = 0; + + rcu_read_lock(); + + list_for_each_entry_rcu(tmp, &conn->chan_list, list) { + if (skb_queue_empty(&tmp->data_q)) + continue; + + skb = skb_peek(&tmp->data_q); + if (skb->priority < priority) + continue; + + if (skb->priority > priority) { + chan = tmp; + priority = skb->priority; + } else if (!chan) { + chan = tmp; + priority = skb->priority; + } + } + + rcu_read_unlock(); + + if (!chan) + break; + + while (hdev->streaming_cnt && hdev->acl_cnt && + (skb = skb_peek(&chan->data_q))) { + /* Stop if priority has changed */ + if (skb->priority < priority) + break; + + skb = skb_dequeue(&chan->data_q); + + hci_conn_enter_active_mode(chan->conn, + bt_cb(skb)->force_active); + + hci_send_frame(hdev, skb); + hdev->acl_last_tx = jiffies; + + hdev->acl_cnt--; + hdev->streaming_cnt--; + chan->sent++; + + conn->streaming_sent++; + conn->sent++; + } + } +} +#endif + static void hci_sched_acl_pkt(struct hci_dev *hdev) { unsigned int cnt = hdev->acl_cnt; @@ -3857,8 +3927,13 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) __check_timeout(hdev, cnt); +#ifdef TIZEN_BT + while (hdev->acl_cnt > hdev->streaming_cnt && + (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { +#else while (hdev->acl_cnt && (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { +#endif u32 priority = (skb_peek(&chan->data_q))->priority; while (quote-- && (skb = skb_peek(&chan->data_q))) { BT_DBG("chan %p skb %p len %d priority %u", chan, skb, @@ -3878,10 +3953,16 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) hdev->acl_cnt--; chan->sent++; + chan->conn->sent++; } } +#ifdef TIZEN_BT + if (hdev->streaming_conn) + hci_sched_streaming_conn(hdev); +#endif + if (cnt != hdev->acl_cnt) hci_prio_recalculate(hdev, ACL_LINK); } diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index f49d81e5ba95..d29a3861920c 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -3480,6 +3480,16 @@ static void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb) continue; conn->sent -= count; +#ifdef TIZEN_BT + if (hdev->streaming_conn && conn->sent < conn->streaming_sent) { + hdev->streaming_cnt += + (conn->streaming_sent - conn->sent); + if (hdev->streaming_cnt > STREAMING_RESERVED_SLOTS) + hdev->streaming_cnt = STREAMING_RESERVED_SLOTS; + + conn->streaming_sent = conn->sent; + } +#endif switch (conn->type) { case ACL_LINK: diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index fc56baff2600..620d4f30a6f1 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -8903,6 +8903,42 @@ void mgmt_le_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, mgmt_event(MGMT_EV_LE_DEVICE_FOUND, hdev, ev, ev_size, NULL); } + +static int set_streaming_mode(struct sock *sk, struct hci_dev *hdev, + void *cp_data, u16 len) +{ + struct mgmt_cp_set_streaming_mode *cp = cp_data; + struct hci_conn *conn; + int err; + int status = MGMT_STATUS_SUCCESS; + + BT_DBG("request for %s", hdev->name); + + hci_dev_lock(hdev); + + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + if (!conn) { + err = -ENOTCONN; + goto unlock; + } + + err = hci_conn_streaming_mode(conn, cp->streaming_mode); + +unlock: + if (err == -ENOTCONN) + status = MGMT_STATUS_NOT_CONNECTED; + else if (err == -EOPNOTSUPP) + status = MGMT_STATUS_NOT_SUPPORTED; + else if (err == -ENOENT) + status = MGMT_STATUS_INVALID_PARAMS; + + err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_SET_STREAMING_MODE, + status, NULL, 0); + + hci_dev_unlock(hdev); + + return err; +} #endif static void read_local_oob_ext_data_complete(struct hci_dev *hdev, u8 status, @@ -9708,6 +9744,7 @@ static const struct hci_mgmt_handler tizen_mgmt_handlers[] = { MGMT_LE_READ_HOST_SUGGESTED_DATA_LENGTH_SIZE }, { set_le_data_length_params, MGMT_LE_SET_DATA_LENGTH_SIZE }, + { set_streaming_mode, MGMT_SET_STREAMING_MODE_SIZE }, }; #endif -- 2.34.1