Commit 13218453 authored by Luiz Augusto von Dentz's avatar Luiz Augusto von Dentz
Browse files

Bluetooth: hci_core: Enable buffer flow control for SCO/eSCO



This enables buffer flow control for SCO/eSCO
(see: Bluetooth Core 6.0 spec: 6.22. Synchronous Flow Control Enable),
recently this has caused the following problem and is actually a nice
addition for the likes of Socket TX complete:

< HCI Command: Read Buffer Size (0x04|0x0005) plen 0
> HCI Event: Command Complete (0x0e) plen 11
      Read Buffer Size (0x04|0x0005) ncmd 1
        Status: Success (0x00)
        ACL MTU: 1021 ACL max packet: 5
        SCO MTU: 240  SCO max packet: 8
...
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
< SCO Data TX: Handle 257 flags 0x00 dlen 120
> HCI Event: Hardware Error (0x10) plen 1
        Code: 0x0a

To fix the code will now attempt to enable buffer flow control when
HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED is set by the driver:

< HCI Command: Write Sync Fl.. (0x03|0x002f) plen 1
        Flow control: Enabled (0x01)
> HCI Event: Command Complete (0x0e) plen 4
      Write Sync Flow Control Enable (0x03|0x002f) ncmd 1
        Status: Success (0x00)

On success then HCI_SCO_FLOWCTL would be set which indicates sco_cnt
shall be used for flow contro.

Fixes: 7fedd3bb ("Bluetooth: Prioritize SCO traffic")
Signed-off-by: default avatarLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Tested-by: default avatarPauli Virtanen <pav@iki.fi>
parent 42c6c7a0
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -208,6 +208,13 @@ enum {
	 */
	HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED,

	/* When this quirk is set consider Sync Flow Control as supported by
	 * the driver.
	 *
	 * This quirk must be set before hci_register_dev is called.
	 */
	HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED,

	/* When this quirk is set, the LE states reported through the
	 * HCI_LE_READ_SUPPORTED_STATES are invalid/broken.
	 *
@@ -448,6 +455,7 @@ enum {
	HCI_WIDEBAND_SPEECH_ENABLED,
	HCI_EVENT_FILTER_CONFIGURED,
	HCI_PA_SYNC,
	HCI_SCO_FLOWCTL,

	HCI_DUT_MODE,
	HCI_VENDOR_DIAG,
@@ -1544,6 +1552,11 @@ struct hci_rp_read_tx_power {
	__s8     tx_power;
} __packed;

#define HCI_OP_WRITE_SYNC_FLOWCTL	0x0c2f
struct hci_cp_write_sync_flowctl {
	__u8     enable;
} __packed;

#define HCI_OP_READ_PAGE_SCAN_TYPE	0x0c46
struct hci_rp_read_page_scan_type {
	__u8     status;
+1 −0
Original line number Diff line number Diff line
@@ -1858,6 +1858,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
#define lmp_hold_capable(dev)      ((dev)->features[0][0] & LMP_HOLD)
#define lmp_sniff_capable(dev)     ((dev)->features[0][0] & LMP_SNIFF)
#define lmp_park_capable(dev)      ((dev)->features[0][1] & LMP_PARK)
#define lmp_sco_capable(dev)       ((dev)->features[0][1] & LMP_SCO)
#define lmp_inq_rssi_capable(dev)  ((dev)->features[0][3] & LMP_RSSI_INQ)
#define lmp_esco_capable(dev)      ((dev)->features[0][3] & LMP_ESCO)
#define lmp_bredr_capable(dev)     (!((dev)->features[0][4] & LMP_NO_BREDR))
+28 −34
Original line number Diff line number Diff line
@@ -3552,42 +3552,27 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type)
}

/* Schedule SCO */
static void hci_sched_sco(struct hci_dev *hdev)
static void hci_sched_sco(struct hci_dev *hdev, __u8 type)
{
	struct hci_conn *conn;
	struct sk_buff *skb;
	int quote;
	int quote, *cnt;
	unsigned int pkts = hdev->sco_pkts;

	BT_DBG("%s", hdev->name);
	bt_dev_dbg(hdev, "type %u", type);

	if (!hci_conn_num(hdev, SCO_LINK))
	if (!hci_conn_num(hdev, type) || !pkts)
		return;

	while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_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;
		}
	}
}

static void hci_sched_esco(struct hci_dev *hdev)
{
	struct hci_conn *conn;
	struct sk_buff *skb;
	int quote;

	BT_DBG("%s", hdev->name);

	if (!hci_conn_num(hdev, ESCO_LINK))
		return;
	/* Use sco_pkts if flow control has not been enabled which will limit
	 * the amount of buffer sent in a row.
	 */
	if (!hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL))
		cnt = &pkts;
	else
		cnt = &hdev->sco_cnt;

	while (hdev->sco_cnt && (conn = hci_low_sent(hdev, ESCO_LINK,
						     &quote))) {
	while (*cnt && (conn = hci_low_sent(hdev, type, &quote))) {
		while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
			BT_DBG("skb %p len %d", skb, skb->len);
			hci_send_frame(hdev, skb);
@@ -3595,8 +3580,17 @@ static void hci_sched_esco(struct hci_dev *hdev)
			conn->sent++;
			if (conn->sent == ~0)
				conn->sent = 0;
			(*cnt)--;
		}
	}

	/* Rescheduled if all packets were sent and flow control is not enabled
	 * as there could be more packets queued that could not be sent and
	 * since no HCI_EV_NUM_COMP_PKTS event will be generated the reschedule
	 * needs to be forced.
	 */
	if (!pkts && !hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL))
		queue_work(hdev->workqueue, &hdev->tx_work);
}

static void hci_sched_acl_pkt(struct hci_dev *hdev)
@@ -3632,8 +3626,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev)
			chan->conn->sent++;

			/* Send pending SCO packets right away */
			hci_sched_sco(hdev);
			hci_sched_esco(hdev);
			hci_sched_sco(hdev, SCO_LINK);
			hci_sched_sco(hdev, ESCO_LINK);
		}
	}

@@ -3688,8 +3682,8 @@ static void hci_sched_le(struct hci_dev *hdev)
			chan->conn->sent++;

			/* Send pending SCO packets right away */
			hci_sched_sco(hdev);
			hci_sched_esco(hdev);
			hci_sched_sco(hdev, SCO_LINK);
			hci_sched_sco(hdev, ESCO_LINK);
		}
	}

@@ -3734,8 +3728,8 @@ static void hci_tx_work(struct work_struct *work)

	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_sco(hdev, SCO_LINK);
		hci_sched_sco(hdev, ESCO_LINK);
		hci_sched_iso(hdev);
		hci_sched_acl(hdev);
		hci_sched_le(hdev);
+2 −0
Original line number Diff line number Diff line
@@ -4445,9 +4445,11 @@ static void hci_num_comp_pkts_evt(struct hci_dev *hdev, void *data,
			break;

		case SCO_LINK:
		case ESCO_LINK:
			hdev->sco_cnt += count;
			if (hdev->sco_cnt > hdev->sco_pkts)
				hdev->sco_cnt = hdev->sco_pkts;

			break;

		case ISO_LINK:
+24 −0
Original line number Diff line number Diff line
@@ -3769,6 +3769,28 @@ static int hci_write_ca_timeout_sync(struct hci_dev *hdev)
				     sizeof(param), &param, HCI_CMD_TIMEOUT);
}

/* Enable SCO flow control if supported */
static int hci_write_sync_flowctl_sync(struct hci_dev *hdev)
{
	struct hci_cp_write_sync_flowctl cp;
	int err;

	/* Check if the controller supports SCO and HCI_OP_WRITE_SYNC_FLOWCTL */
	if (!lmp_sco_capable(hdev) || !(hdev->commands[10] & BIT(4)) ||
	    !test_bit(HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, &hdev->quirks))
		return 0;

	memset(&cp, 0, sizeof(cp));
	cp.enable = 0x01;

	err = __hci_cmd_sync_status(hdev, HCI_OP_WRITE_SYNC_FLOWCTL,
				    sizeof(cp), &cp, HCI_CMD_TIMEOUT);
	if (!err)
		hci_dev_set_flag(hdev, HCI_SCO_FLOWCTL);

	return err;
}

/* BR Controller init stage 2 command sequence */
static const struct hci_init_stage br_init2[] = {
	/* HCI_OP_READ_BUFFER_SIZE */
@@ -3787,6 +3809,8 @@ static const struct hci_init_stage br_init2[] = {
	HCI_INIT(hci_clear_event_filter_sync),
	/* HCI_OP_WRITE_CA_TIMEOUT */
	HCI_INIT(hci_write_ca_timeout_sync),
	/* HCI_OP_WRITE_SYNC_FLOWCTL */
	HCI_INIT(hci_write_sync_flowctl_sync),
	{}
};