diff --git a/drivers/bluetooth/hci/Kconfig b/drivers/bluetooth/hci/Kconfig index aea18f471e7..4e32b4d04b6 100644 --- a/drivers/bluetooth/hci/Kconfig +++ b/drivers/bluetooth/hci/Kconfig @@ -33,6 +33,7 @@ config BLUETOOTH_H4 bool "H:4 UART" select UART_INTERRUPT_DRIVEN select BLUETOOTH_UART + select BLUETOOTH_RECV_IS_RX_THREAD depends on SERIAL help Bluetooth H:4 UART driver. Requires hardware flow control diff --git a/drivers/bluetooth/hci/h4.c b/drivers/bluetooth/hci/h4.c index 71b49b049fe..d43df46661f 100644 --- a/drivers/bluetooth/hci/h4.c +++ b/drivers/bluetooth/hci/h4.c @@ -44,37 +44,143 @@ #include "../nrf51_pm.h" #endif -#define H4_CMD 0x01 -#define H4_ACL 0x02 -#define H4_SCO 0x03 -#define H4_EVT 0x04 +#define H4_NONE 0x00 +#define H4_CMD 0x01 +#define H4_ACL 0x02 +#define H4_SCO 0x03 +#define H4_EVT 0x04 + +static BT_STACK_NOINIT(rx_thread_stack, CONFIG_BLUETOOTH_RX_STACK_SIZE); + +static struct { + struct net_buf *buf; + struct k_sem sem; + + uint16_t remaining; + uint16_t discard; + + bool have_hdr; + + uint8_t type; + union { + struct bt_hci_evt_hdr evt; + struct bt_hci_acl_hdr acl; + }; +} rx = { + .sem = K_SEM_INITIALIZER(rx.sem, 0, 1), +}; static struct device *h4_dev; -static int h4_read(struct device *uart, uint8_t *buf, - size_t len, size_t min) +static inline void h4_get_type(void) { - int total = 0; - - while (len) { - int rx; - - rx = uart_fifo_read(uart, buf, len); - if (rx == 0) { - BT_DBG("Got zero bytes from UART"); - if (total < min) { - continue; - } - break; - } - - BT_DBG("read %d remaining %zu", rx, len - rx); - len -= rx; - total += rx; - buf += rx; + /* Get packet type */ + if (uart_fifo_read(h4_dev, &rx.type, 1) != 1) { + BT_WARN("Unable to read H:4 packet type"); + rx.type = H4_NONE; + return; } - return total; + switch (rx.type) { + case H4_EVT: + rx.remaining = sizeof(rx.evt); + break; + case H4_ACL: + rx.remaining = sizeof(rx.acl); + break; + default: + BT_ERR("Unknown H:4 type 0x%02x", rx.type); + rx.type = H4_NONE; + } +} + +static inline void get_acl_hdr(void) +{ + struct bt_hci_acl_hdr *hdr = &rx.acl; + int to_read = sizeof(*hdr) - rx.remaining; + + rx.remaining -= uart_fifo_read(h4_dev, (uint8_t *)hdr + to_read, + rx.remaining); + if (!rx.remaining) { + BT_DBG("Got ACL header"); + rx.remaining = sys_le16_to_cpu(hdr->len); + rx.have_hdr = true; + } +} + +static inline void get_evt_hdr(void) +{ + struct bt_hci_evt_hdr *hdr = &rx.evt; + int to_read = sizeof(*hdr) - rx.remaining; + + rx.remaining -= uart_fifo_read(h4_dev, (uint8_t *)hdr + to_read, + rx.remaining); + if (!rx.remaining) { + BT_DBG("Got event header"); + rx.remaining = hdr->len; + rx.have_hdr = true; + } +} + + +static inline void buf_set_type(struct net_buf *buf) +{ + if (rx.type == H4_EVT) { + bt_buf_set_type(buf, BT_BUF_EVT); + } else { + bt_buf_set_type(buf, BT_BUF_ACL_IN); + } +} + +static inline void copy_hdr(struct net_buf *buf) +{ + if (rx.type == H4_EVT) { + net_buf_add_mem(buf, &rx.evt, sizeof(rx.evt)); + } else { + net_buf_add_mem(buf, &rx.acl, sizeof(rx.acl)); + } +} + +static void rx_thread(void *p1, void *p2, void *p3) +{ + struct net_buf *buf; + + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + BT_DBG("started"); + + while (1) { + BT_DBG("waiting for semaphore"); + k_sem_take(&rx.sem, K_FOREVER); + BT_DBG("got semaphore, rx.buf %p have_hdr %u", + rx.buf, rx.have_hdr); + + if (rx.buf) { + buf = rx.buf; + buf_set_type(buf); + rx.buf = NULL; + rx.have_hdr = false; + rx.type = H4_NONE; + + uart_irq_rx_enable(h4_dev); + + BT_DBG("Calling bt_recv()"); + bt_recv(buf); + } else { + rx.buf = bt_buf_get_rx(K_FOREVER); + if (rx.remaining > net_buf_tailroom(rx.buf)) { + BT_ERR("Not enough space in buffer"); + rx.discard = rx.remaining; + rx.remaining = 0; + rx.have_hdr = 0; + } else { + copy_hdr(rx.buf); + } + uart_irq_rx_enable(h4_dev); + } + } } static size_t h4_discard(struct device *uart, size_t len) @@ -84,60 +190,84 @@ static size_t h4_discard(struct device *uart, size_t len) return uart_fifo_read(uart, buf, min(len, sizeof(buf))); } -static struct net_buf *h4_evt_recv(int *remaining) +/* Returns false if RX IRQ was disabled and control given to the RX thread */ +static inline bool read_payload(void) { - struct bt_hci_evt_hdr hdr; - struct net_buf *buf; + int read; - /* We can ignore the return value since we pass len == min */ - h4_read(h4_dev, (void *)&hdr, sizeof(hdr), sizeof(hdr)); + if (!rx.buf) { + rx.buf = bt_buf_get_rx(K_NO_WAIT); + if (!rx.buf) { + uart_irq_rx_disable(h4_dev); + BT_DBG("giving semaphore"); + k_sem_give(&rx.sem); + return false; + } - *remaining = hdr.len; - - buf = bt_buf_get_evt(hdr.evt, K_NO_WAIT); - if (buf) { - net_buf_add_mem(buf, &hdr, sizeof(hdr)); - } else { - BT_ERR("No available event buffers!"); + copy_hdr(rx.buf); } - BT_DBG("len %u", hdr.len); + read = uart_fifo_read(h4_dev, net_buf_tail(rx.buf), rx.remaining); + net_buf_add(rx.buf, read); + rx.remaining -= read; - return buf; + BT_DBG("got %d bytes, remaining %u", read, rx.remaining); + BT_DBG("Payload (len %u): %s", rx.buf->len, + bt_hex(rx.buf->data, rx.buf->len)); + + if (rx.remaining) { + return true; + } + + if (rx.type == H4_EVT && bt_hci_evt_is_prio(rx.evt.evt)) { + buf_set_type(rx.buf); + BT_DBG("Calling bt_recv()"); + bt_recv(rx.buf); + rx.buf = NULL; + rx.have_hdr = false; + rx.type = H4_NONE; + return true; + } else { + uart_irq_rx_disable(h4_dev); + k_sem_give(&rx.sem); + return false; + } } -static struct net_buf *h4_acl_recv(int *remaining) +static inline void read_header(void) { - struct bt_hci_acl_hdr hdr; - struct net_buf *buf; - - /* We can ignore the return value since we pass len == min */ - h4_read(h4_dev, (void *)&hdr, sizeof(hdr), sizeof(hdr)); - - buf = bt_buf_get_acl(K_NO_WAIT); - if (buf) { - net_buf_add_mem(buf, &hdr, sizeof(hdr)); - } else { - BT_ERR("No available ACL buffers!"); + switch (rx.type) { + case H4_NONE: + h4_get_type(); + return; + case H4_EVT: + get_evt_hdr(); + break; + case H4_ACL: + get_acl_hdr(); + break; + default: + CODE_UNREACHABLE; + return; } - *remaining = sys_le16_to_cpu(hdr.len); - - BT_DBG("len %u", *remaining); - - return buf; + if (rx.have_hdr && rx.buf) { + if (rx.remaining > net_buf_tailroom(rx.buf)) { + BT_ERR("Not enough space in buffer"); + rx.discard = rx.remaining; + rx.remaining = 0; + rx.have_hdr = 0; + } else { + copy_hdr(rx.buf); + } + } } static void bt_uart_isr(struct device *unused) { - static struct net_buf *buf; - static int remaining; - ARG_UNUSED(unused); while (uart_irq_update(h4_dev) && uart_irq_is_pending(h4_dev)) { - int read; - if (!uart_irq_rx_ready(h4_dev)) { if (uart_irq_tx_ready(h4_dev)) { BT_DBG("transmit ready"); @@ -148,58 +278,22 @@ static void bt_uart_isr(struct device *unused) break; } - /* Beginning of a new packet */ - if (!remaining) { - uint8_t type; + BT_DBG("remaining %u discard %u have_hdr %u buf %p len %u", + rx.remaining, rx.discard, rx.have_hdr, rx.buf, + rx.buf->len); - /* Get packet type */ - read = h4_read(h4_dev, &type, sizeof(type), 0); - if (read != sizeof(type)) { - BT_WARN("Unable to read H4 packet type"); - continue; - } - - switch (type) { - case H4_EVT: - buf = h4_evt_recv(&remaining); - break; - case H4_ACL: - buf = h4_acl_recv(&remaining); - break; - default: - BT_ERR("Unknown H4 type %u", type); - return; - } - - BT_DBG("need to get %u bytes", remaining); - - if (buf && remaining > net_buf_tailroom(buf)) { - BT_ERR("Not enough space in buffer"); - net_buf_unref(buf); - buf = NULL; - } - } - - if (!buf) { - read = h4_discard(h4_dev, remaining); - BT_WARN("Discarded %d bytes", read); - remaining -= read; + if (rx.discard) { + rx.discard -= h4_discard(h4_dev, rx.discard); continue; } - read = h4_read(h4_dev, net_buf_tail(buf), remaining, 0); + if (!rx.have_hdr) { + read_header(); + continue; + } - buf->len += read; - remaining -= read; - - BT_DBG("received %d bytes", read); - - if (!remaining) { - BT_DBG("full packet received"); - - /* Pass buffer to the stack */ - bt_recv(buf); - buf = NULL; + if (!read_payload()) { + return; } } } @@ -240,13 +334,16 @@ static int h4_open(void) return -EIO; } #else - bt_uart_drain(h4_dev); + h4_discard(h4_dev, 32); #endif uart_irq_callback_set(h4_dev, bt_uart_isr); uart_irq_rx_enable(h4_dev); + k_thread_spawn(rx_thread_stack, sizeof(rx_thread_stack), rx_thread, + NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + return 0; }