Bluetooth: hci_h5: implement CRC data integrity

The UART-based H5 protocol supports CRC data integrity checks for
reliable packets. The host sets bit 5 in the configuration field of the
CONFIG link control message to indicate that CRC is supported. The
controller sets the same bit in the CONFIG RESPONSE message to indicate
that CRC may be used from then on.

Tested on a MangoPi MQ-Pro with a Realtek RTL8723DS Bluetooth controller
using the tip of the bluetooth-next tree.

Signed-off-by: Javier Nieto <jgnieto@cs.stanford.edu>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Javier Nieto 2025-09-29 15:59:21 -07:00 committed by Luiz Augusto von Dentz
parent 01622e9a53
commit 97fdb2ea06
2 changed files with 39 additions and 4 deletions

View File

@ -188,6 +188,7 @@ config BT_HCIUART_3WIRE
bool "Three-wire UART (H5) protocol support" bool "Three-wire UART (H5) protocol support"
depends on BT_HCIUART depends on BT_HCIUART
depends on BT_HCIUART_SERDEV depends on BT_HCIUART_SERDEV
select CRC_CCITT
help help
The HCI Three-wire UART Transport Layer makes it possible to The HCI Three-wire UART Transport Layer makes it possible to
user the Bluetooth HCI over a serial port interface. The HCI user the Bluetooth HCI over a serial port interface. The HCI

View File

@ -7,6 +7,8 @@
*/ */
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/bitrev.h>
#include <linux/crc-ccitt.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
#include <linux/kernel.h> #include <linux/kernel.h>
@ -58,6 +60,7 @@ enum {
H5_TX_ACK_REQ, /* Pending ack to send */ H5_TX_ACK_REQ, /* Pending ack to send */
H5_WAKEUP_DISABLE, /* Device cannot wake host */ H5_WAKEUP_DISABLE, /* Device cannot wake host */
H5_HW_FLOW_CONTROL, /* Use HW flow control */ H5_HW_FLOW_CONTROL, /* Use HW flow control */
H5_CRC, /* Use CRC */
}; };
struct h5 { struct h5 {
@ -141,8 +144,8 @@ static void h5_link_control(struct hci_uart *hu, const void *data, size_t len)
static u8 h5_cfg_field(struct h5 *h5) static u8 h5_cfg_field(struct h5 *h5)
{ {
/* Sliding window size (first 3 bits) */ /* Sliding window size (first 3 bits) and CRC request (fifth bit). */
return h5->tx_win & 0x07; return (h5->tx_win & 0x07) | 0x10;
} }
static void h5_timed_event(struct timer_list *t) static void h5_timed_event(struct timer_list *t)
@ -361,8 +364,10 @@ static void h5_handle_internal_rx(struct hci_uart *hu)
h5_link_control(hu, conf_rsp, 2); h5_link_control(hu, conf_rsp, 2);
h5_link_control(hu, conf_req, 3); h5_link_control(hu, conf_req, 3);
} else if (memcmp(data, conf_rsp, 2) == 0) { } else if (memcmp(data, conf_rsp, 2) == 0) {
if (H5_HDR_LEN(hdr) > 2) if (H5_HDR_LEN(hdr) > 2) {
h5->tx_win = (data[2] & 0x07); h5->tx_win = (data[2] & 0x07);
assign_bit(H5_CRC, &h5->flags, data[2] & 0x10);
}
BT_DBG("Three-wire init complete. tx_win %u", h5->tx_win); BT_DBG("Three-wire init complete. tx_win %u", h5->tx_win);
h5->state = H5_ACTIVE; h5->state = H5_ACTIVE;
hci_uart_init_ready(hu); hci_uart_init_ready(hu);
@ -426,7 +431,24 @@ static void h5_complete_rx_pkt(struct hci_uart *hu)
static int h5_rx_crc(struct hci_uart *hu, unsigned char c) static int h5_rx_crc(struct hci_uart *hu, unsigned char c)
{ {
struct h5 *h5 = hu->priv;
const unsigned char *hdr = h5->rx_skb->data;
u16 crc;
__be16 crc_be;
crc = crc_ccitt(0xffff, hdr, 4 + H5_HDR_LEN(hdr));
crc = bitrev16(crc);
crc_be = cpu_to_be16(crc);
if (memcmp(&crc_be, hdr + 4 + H5_HDR_LEN(hdr), 2) != 0) {
bt_dev_err(hu->hdev, "Received packet with invalid CRC");
h5_reset_rx(h5);
} else {
/* Remove CRC bytes */
skb_trim(h5->rx_skb, 4 + H5_HDR_LEN(hdr));
h5_complete_rx_pkt(hu); h5_complete_rx_pkt(hu);
}
return 0; return 0;
} }
@ -557,6 +579,7 @@ static void h5_reset_rx(struct h5 *h5)
h5->rx_func = h5_rx_delimiter; h5->rx_func = h5_rx_delimiter;
h5->rx_pending = 0; h5->rx_pending = 0;
clear_bit(H5_RX_ESC, &h5->flags); clear_bit(H5_RX_ESC, &h5->flags);
clear_bit(H5_CRC, &h5->flags);
} }
static int h5_recv(struct hci_uart *hu, const void *data, int count) static int h5_recv(struct hci_uart *hu, const void *data, int count)
@ -687,6 +710,7 @@ static struct sk_buff *h5_prepare_pkt(struct hci_uart *hu, u8 pkt_type,
struct h5 *h5 = hu->priv; struct h5 *h5 = hu->priv;
struct sk_buff *nskb; struct sk_buff *nskb;
u8 hdr[4]; u8 hdr[4];
u16 crc;
int i; int i;
if (!valid_packet_type(pkt_type)) { if (!valid_packet_type(pkt_type)) {
@ -714,6 +738,7 @@ static struct sk_buff *h5_prepare_pkt(struct hci_uart *hu, u8 pkt_type,
/* Reliable packet? */ /* Reliable packet? */
if (pkt_type == HCI_ACLDATA_PKT || pkt_type == HCI_COMMAND_PKT) { if (pkt_type == HCI_ACLDATA_PKT || pkt_type == HCI_COMMAND_PKT) {
hdr[0] |= 1 << 7; hdr[0] |= 1 << 7;
hdr[0] |= (test_bit(H5_CRC, &h5->flags) && 1) << 6;
hdr[0] |= h5->tx_seq; hdr[0] |= h5->tx_seq;
h5->tx_seq = (h5->tx_seq + 1) % 8; h5->tx_seq = (h5->tx_seq + 1) % 8;
} }
@ -733,6 +758,15 @@ static struct sk_buff *h5_prepare_pkt(struct hci_uart *hu, u8 pkt_type,
for (i = 0; i < len; i++) for (i = 0; i < len; i++)
h5_slip_one_byte(nskb, data[i]); h5_slip_one_byte(nskb, data[i]);
if (H5_HDR_CRC(hdr)) {
crc = crc_ccitt(0xffff, hdr, 4);
crc = crc_ccitt(crc, data, len);
crc = bitrev16(crc);
h5_slip_one_byte(nskb, (crc >> 8) & 0xff);
h5_slip_one_byte(nskb, crc & 0xff);
}
h5_slip_delim(nskb); h5_slip_delim(nskb);
return nskb; return nskb;