Skip to content

Commit

Permalink
usb: bluetooth: fix OUT buffer handling
Browse files Browse the repository at this point in the history
acl_read_cb does not handle incoming (ACL) data
if BT_CTLR_TX_BUFFER_SIZE is greater than
USB_MAX_FS_BULK_MPS - BT_HCI_ACL_HDR_SIZE.

Since the host adjusts the data according to
the BT_CTLR_TX_BUFFER_SIZE and does not use ZLP
we cannot start usb_transfer over the possible length of
the whole packet, with or without USB_TRANS_NO_ZLP flag.
But we can read the packet length from the header and
call net_buf_put() when the whole packet is received.

Fixes: #31922

Reported-by: Matias Karhumaa <matias.karhumaa@gmail.com>
Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
  • Loading branch information
jfischer-no authored and nashif committed Feb 17, 2021
1 parent 46d95a2 commit af67c16
Showing 1 changed file with 78 additions and 11 deletions.
89 changes: 78 additions & 11 deletions subsys/usb/class/bluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ static struct k_thread tx_thread_data;
static bool configured;
static bool suspended;

static uint8_t ep_out_buf[USB_MAX_FS_BULK_MPS];

struct usb_bluetooth_config {
struct usb_if_descriptor if0;
struct usb_ep_descriptor if0_int_ep;
Expand Down Expand Up @@ -167,32 +169,97 @@ static void hci_rx_thread(void)
}
}

static uint16_t hci_pkt_get_len(struct net_buf *buf,
const uint8_t *data, size_t size)
{
uint16_t len = 0;
size_t hdr_len = 0;

switch (bt_buf_get_type(buf)) {
case BT_BUF_CMD: {
struct bt_hci_cmd_hdr *cmd_hdr;

hdr_len = sizeof(*cmd_hdr);
cmd_hdr = (struct bt_hci_cmd_hdr *)data;
len = cmd_hdr->param_len + hdr_len;
break;
}
case BT_BUF_ACL_OUT: {
struct bt_hci_acl_hdr *acl_hdr;

hdr_len = sizeof(*acl_hdr);
acl_hdr = (struct bt_hci_acl_hdr *)data;
len = sys_le16_to_cpu(acl_hdr->len) + hdr_len;
break;
}
case BT_BUF_ISO_OUT: {
struct bt_hci_iso_data_hdr *iso_hdr;

hdr_len = sizeof(*iso_hdr);
iso_hdr = (struct bt_hci_iso_data_hdr *)data;
len = sys_le16_to_cpu(iso_hdr->slen) + hdr_len;
break;
}
default:
LOG_ERR("Unknown bt buffer type");
return 0;
}

return (size < hdr_len) ? 0 : len;
}

static void acl_read_cb(uint8_t ep, int size, void *priv)
{
static uint8_t data[USB_MAX_FS_BULK_MPS];
static struct net_buf *buf;
static uint16_t pkt_len;
uint8_t *data = ep_out_buf;

if (size > 0) {
struct net_buf *buf;
if (size == 0) {
goto restart_out_transfer;
}

if (buf == NULL) {
/*
* Obtain the first chunk and determine the length
* of the HCI packet.
*/
if (IS_ENABLED(CONFIG_USB_DEVICE_BLUETOOTH_VS_H4) &&
bt_hci_raw_get_mode() == BT_HCI_RAW_MODE_H4) {
buf = bt_buf_get_tx(BT_BUF_H4, K_FOREVER, data, size);
pkt_len = hci_pkt_get_len(buf, &data[1], size - 1);
LOG_DBG("pkt_len %u, chunk %u", pkt_len, size);
} else {
buf = bt_buf_get_tx(BT_BUF_ACL_OUT, K_FOREVER, data,
size);
buf = bt_buf_get_tx(BT_BUF_ACL_OUT, K_FOREVER,
data, size);
pkt_len = hci_pkt_get_len(buf, data, size);
LOG_DBG("pkt_len %u, chunk %u", pkt_len, size);
}

if (!buf) {
LOG_ERR("Cannot get free TX buffer\n");
return;
if (pkt_len == 0) {
LOG_ERR("Failed to get packet length");
net_buf_unref(buf);
buf = NULL;
}
} else {
/*
* Take over the next chunk if HCI packet is
* larger than USB_MAX_FS_BULK_MPS.
*/
net_buf_add_mem(buf, data, size);
LOG_DBG("len %u, chunk %u", buf->len, size);
}

if (buf != NULL && pkt_len == buf->len) {
net_buf_put(&rx_queue, buf);
LOG_DBG("put");
buf = NULL;
pkt_len = 0;
}

/* Start a new read transfer */
usb_transfer(bluetooth_ep_data[HCI_OUT_EP_IDX].ep_addr, data,
BT_BUF_ACL_SIZE, USB_TRANS_READ, acl_read_cb, NULL);
restart_out_transfer:
usb_transfer(bluetooth_ep_data[HCI_OUT_EP_IDX].ep_addr, ep_out_buf,
sizeof(ep_out_buf), USB_TRANS_READ | USB_TRANS_NO_ZLP,
acl_read_cb, NULL);
}

static void bluetooth_status_cb(struct usb_cfg_data *cfg,
Expand Down

0 comments on commit af67c16

Please sign in to comment.