[PATCH v2 1/2] hci_bcsp: Add LE protocol on kernel side

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add the link establishment protocol handling in the kernel:
- le_state enum to handle state machine
- bcsp_timed_event to send LE messages
- bcsp_handle_le_pkt to answer endpoint LE messages
- various tests to muzzle BCSP link before GARULOUS

Signed-off-by: Tristan Lelong <tristan@xxxxxxxxxx>
---
 drivers/bluetooth/hci_bcsp.c | 210 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 172 insertions(+), 38 deletions(-)

diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c
index 21cc45b..2685ab9 100644
--- a/drivers/bluetooth/hci_bcsp.c
+++ b/drivers/bluetooth/hci_bcsp.c
@@ -47,13 +47,24 @@
 
 #include "hci_uart.h"
 
-#define VERSION "0.3"
+#define VERSION "0.4"
+
+#define LE_POLLING	(HZ/10)
+#define TX_POLLING	(HZ/4)
 
 static bool txcrc = 1;
 static bool hciextn = 1;
 
+static const u8 conf_pkt[4]     = { 0xad, 0xef, 0xac, 0xed };
+static const u8 conf_rsp_pkt[4] = { 0xde, 0xad, 0xd0, 0xd0 };
+static const u8 sync_pkt[4]     = { 0xda, 0xdc, 0xed, 0xed };
+static const u8 sync_rsp_pkt[4] = { 0xac, 0xaf, 0xef, 0xee };
+
 #define BCSP_TXWINSIZE	4
 
+#define BCSP_PKT_LEN(data)	((data[1] >> 4) + (data[2]))
+#define BCSP_LE_PKT_LEN	0x04
+
 #define BCSP_ACK_PKT	0x05
 #define BCSP_LE_PKT	0x06
 
@@ -69,6 +80,12 @@ struct bcsp_struct {
 	struct	timer_list tbcsp;
 
 	enum {
+		BCSP_SHY = 0,
+		BCSP_CURIOUS,
+		BCSP_GARULOUS
+	} le_state;
+
+	enum {
 		BCSP_W4_PKT_DELIMITER,
 		BCSP_W4_PKT_START,
 		BCSP_W4_BCSP_HDR,
@@ -184,6 +201,9 @@ static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data,
 	u16 BCSP_CRC_INIT(bcsp_txmsg_crc);
 	int rel, i;
 
+	if (bcsp->le_state != BCSP_GARULOUS && pkt_type != BCSP_LE_PKT)
+		return NULL;
+
 	switch (pkt_type) {
 	case HCI_ACLDATA_PKT:
 		chan = 6;	/* BCSP ACL channel */
@@ -299,10 +319,16 @@ static struct sk_buff *bcsp_dequeue(struct hci_uart *hu)
 			return nskb;
 		} else {
 			skb_queue_head(&bcsp->unrel, skb);
+			if (bcsp->le_state == BCSP_GARULOUS)
+				return NULL;
 			BT_ERR("Could not dequeue pkt because alloc_skb failed");
 		}
 	}
 
+	/* If BCSP is not connected yet, there is no need to go further */
+	if (bcsp->le_state != BCSP_GARULOUS)
+		return NULL;
+
 	/* Now, try to send a reliable pkt. We can only send a
 	   reliable packet if the number of packets sent but not yet ack'ed
 	   is < than the winsize */
@@ -316,11 +342,18 @@ static struct sk_buff *bcsp_dequeue(struct hci_uart *hu)
 								bt_cb(skb)->pkt_type);
 			if (nskb) {
 				__skb_queue_tail(&bcsp->unack, skb);
-				mod_timer(&bcsp->tbcsp, jiffies + HZ / 4);
+
+				if (bcsp->le_state == BCSP_GARULOUS)
+					/* only re-arm retransmit timer
+					 * if the BCSP link is connected */
+					mod_timer(&bcsp->tbcsp,
+						  jiffies + HZ / 4);
 				spin_unlock_irqrestore(&bcsp->unack.lock, flags);
 				return nskb;
 			} else {
 				skb_queue_head(&bcsp->rel, skb);
+				if (bcsp->le_state != BCSP_GARULOUS)
+					return NULL;
 				BT_ERR("Could not dequeue pkt because alloc_skb failed");
 			}
 		}
@@ -350,8 +383,9 @@ static int bcsp_flush(struct hci_uart *hu)
 }
 
 /* Remove ack'ed packets */
-static void bcsp_pkt_cull(struct bcsp_struct *bcsp)
+static void bcsp_pkt_cull(struct hci_uart *hu)
 {
+	struct bcsp_struct *bcsp = hu->priv;
 	struct sk_buff *skb, *tmp;
 	unsigned long flags;
 	int i, pkts_to_be_removed;
@@ -386,43 +420,111 @@ static void bcsp_pkt_cull(struct bcsp_struct *bcsp)
 		kfree_skb(skb);
 	}
 
-	if (skb_queue_empty(&bcsp->unack))
+	if (skb_queue_empty(&bcsp->unack) && bcsp->le_state == BCSP_GARULOUS)
 		del_timer(&bcsp->tbcsp);
 
 	spin_unlock_irqrestore(&bcsp->unack.lock, flags);
 
+	/* Some reliable packets might be queued in ack queue,
+	 * but not sent since the tx window was full.
+	 * try to process them now. */
+	if (bcsp->unack.qlen < BCSP_TXWINSIZE)
+		if (skb_queue_len(&bcsp->rel))
+			hci_uart_tx_wakeup(hu);
+
 	if (i != pkts_to_be_removed)
 		BT_ERR("Removed only %u out of %u pkts", i, pkts_to_be_removed);
 }
 
-/* Handle BCSP link-establishment packets. When we
-   detect a "sync" packet, symptom that the BT module has reset,
-   we do nothing :) (yet) */
+static void bcsp_internal_reset(struct hci_uart *hu)
+{
+	struct bcsp_struct *bcsp = hu->priv;
+
+	/* reset the bcsp stack counters */
+	bcsp->rxseq_txack = 0;
+	bcsp->rxack = 0;
+	bcsp->message_crc = 0;
+	bcsp->txack_req = 0;
+	bcsp->msgq_txseq = 0;
+
+	/* reset state machine */
+	bcsp->le_state = BCSP_SHY;
+	bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
+	bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC;
+
+	/* empty all the queues */
+	skb_queue_purge(&bcsp->unack);
+	skb_queue_purge(&bcsp->rel);
+	skb_queue_purge(&bcsp->unrel);
+
+	/* tell upper layer about the reset */
+	hci_reset_dev(hu->hdev);
+
+	bcsp->tbcsp.expires  = jiffies + LE_POLLING;
+	add_timer(&bcsp->tbcsp);
+}
+
+/* Handle BCSP link-establishment packets.
+ */
 static void bcsp_handle_le_pkt(struct hci_uart *hu)
 {
+	struct sk_buff *nskb;
 	struct bcsp_struct *bcsp = hu->priv;
-	u8 conf_pkt[4]     = { 0xad, 0xef, 0xac, 0xed };
-	u8 conf_rsp_pkt[4] = { 0xde, 0xad, 0xd0, 0xd0 };
-	u8 sync_pkt[4]     = { 0xda, 0xdc, 0xed, 0xed };
-
-	/* spot "conf" pkts and reply with a "conf rsp" pkt */
-	if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 &&
-			!memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) {
-		struct sk_buff *nskb = alloc_skb(4, GFP_ATOMIC);
-
-		BT_DBG("Found a LE conf pkt");
-		if (!nskb)
-			return;
-		memcpy(skb_put(nskb, 4), conf_rsp_pkt, 4);
-		bt_cb(nskb)->pkt_type = BCSP_LE_PKT;
-
-		skb_queue_head(&bcsp->unrel, nskb);
-		hci_uart_tx_wakeup(hu);
-	}
-	/* Spot "sync" pkts. If we find one...disaster! */
-	else if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 &&
-			!memcmp(&bcsp->rx_skb->data[4], sync_pkt, 4)) {
-		BT_ERR("Found a LE sync pkt, card has reset");
+
+	if (BCSP_PKT_LEN(bcsp->rx_skb->data) == BCSP_LE_PKT_LEN) {
+		if (!memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) {
+			/* spot "conf" pkts and reply with a "conf rsp" pkt */
+			if (bcsp->le_state == BCSP_SHY)
+				return;
+
+			BT_DBG("Found a LE conf pkt");
+			nskb = alloc_skb(4, GFP_ATOMIC);
+			if (!nskb)
+				return;
+			memcpy(skb_put(nskb, 4), conf_rsp_pkt, 4);
+			bt_cb(nskb)->pkt_type = BCSP_LE_PKT;
+
+			skb_queue_head(&bcsp->unrel, nskb);
+			hci_uart_tx_wakeup(hu);
+		} else if (!memcmp(&bcsp->rx_skb->data[4], sync_pkt, 4)) {
+			/* Spot "sync" pkts and reply with a "sync rsp" pkt */
+			if (bcsp->le_state == BCSP_GARULOUS)
+				/* we force the connection state to reset */
+				bcsp_internal_reset(hu);
+
+			BT_INFO("BCSP: hci%d LE sync pkt, performing reset",
+				hu->hdev->id);
+
+			nskb = alloc_skb(4, GFP_ATOMIC);
+			if (!nskb)
+				return;
+			memcpy(skb_put(nskb, 4), sync_rsp_pkt, 4);
+			bt_cb(nskb)->pkt_type = BCSP_LE_PKT;
+
+			skb_queue_head(&bcsp->unrel, nskb);
+			hci_uart_tx_wakeup(hu);
+		} else if (!memcmp(&bcsp->rx_skb->data[4], conf_rsp_pkt, 4)) {
+			/* Spot "conf rsp" pkts and update state machine */
+			if (bcsp->le_state != BCSP_CURIOUS) {
+				BT_DBG("BCSP: hci%d ingoring conf_resp packet",
+				       hu->hdev->id);
+				return;
+			}
+
+			BT_INFO("BCSP: hci%d connected", hu->hdev->id);
+			bcsp->le_state = BCSP_GARULOUS;
+		} else if (!memcmp(&bcsp->rx_skb->data[4], sync_rsp_pkt, 4)) {
+			/* Spot "sync rsp" pkts and update state machine */
+			if (bcsp->le_state != BCSP_SHY) {
+				BT_DBG("BCSP: hci%d ignoring sync_resp packet",
+				       hu->hdev->id);
+				return;
+			}
+
+			BT_DBG("Found a LE sync rsp pkt");
+			bcsp->le_state = BCSP_CURIOUS;
+		}
+
 	}
 }
 
@@ -493,7 +595,7 @@ static void bcsp_complete_rx_pkt(struct hci_uart *hu)
 	bcsp->rxack = (bcsp->rx_skb->data[0] >> 3) & 0x07;
 	BT_DBG("Request for pkt %u from card", bcsp->rxack);
 
-	bcsp_pkt_cull(bcsp);
+	bcsp_pkt_cull(hu);
 	if ((bcsp->rx_skb->data[1] & 0x0f) == 6 &&
 			bcsp->rx_skb->data[0] & 0x80) {
 		bt_cb(bcsp->rx_skb)->pkt_type = HCI_ACLDATA_PKT;
@@ -537,11 +639,13 @@ static void bcsp_complete_rx_pkt(struct hci_uart *hu)
 			}
 		} else
 			kfree_skb(bcsp->rx_skb);
-	} else {
+	} else if (bcsp->le_state == BCSP_GARULOUS) {
 		/* Pull out BCSP hdr */
 		skb_pull(bcsp->rx_skb, 4);
 
 		hci_recv_frame(hu->hdev, bcsp->rx_skb);
+	} else {
+		kfree_skb(bcsp->rx_skb);
 	}
 
 	bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
@@ -676,16 +780,42 @@ static void bcsp_timed_event(unsigned long arg)
 	struct sk_buff *skb;
 	unsigned long flags;
 
-	BT_DBG("hu %p retransmitting %u pkts", hu, bcsp->unack.qlen);
+	switch (bcsp->le_state) {
+	case BCSP_GARULOUS:
+		BT_DBG("hu %p retransmitting %u pkts", hu, bcsp->unack.qlen);
 
-	spin_lock_irqsave_nested(&bcsp->unack.lock, flags, SINGLE_DEPTH_NESTING);
+		spin_lock_irqsave_nested(&bcsp->unack.lock, flags,
+					 SINGLE_DEPTH_NESTING);
 
-	while ((skb = __skb_dequeue_tail(&bcsp->unack)) != NULL) {
-		bcsp->msgq_txseq = (bcsp->msgq_txseq - 1) & 0x07;
-		skb_queue_head(&bcsp->rel, skb);
-	}
+		while ((skb = __skb_dequeue_tail(&bcsp->unack)) != NULL) {
+			bcsp->msgq_txseq = (bcsp->msgq_txseq - 1) & 0x07;
+			skb_queue_head(&bcsp->rel, skb);
+		}
 
-	spin_unlock_irqrestore(&bcsp->unack.lock, flags);
+		spin_unlock_irqrestore(&bcsp->unack.lock, flags);
+		break;
+	case BCSP_CURIOUS:
+		skb = alloc_skb(4, GFP_ATOMIC);
+		memcpy(skb_put(skb, 4), conf_pkt, 4);
+		bt_cb(skb)->pkt_type = BCSP_LE_PKT;
+		skb_queue_head(&bcsp->unrel, skb);
+
+		bcsp->tbcsp.expires += LE_POLLING;
+		add_timer(&bcsp->tbcsp);
+		break;
+	default:
+		BT_INFO("Internal state unknown. Assuming not connected.");
+		bcsp->le_state = BCSP_SHY;
+	case BCSP_SHY:
+		skb = alloc_skb(4, GFP_ATOMIC);
+		memcpy(skb_put(skb, 4), sync_pkt, 4);
+		bt_cb(skb)->pkt_type = BCSP_LE_PKT;
+		skb_queue_head(&bcsp->unrel, skb);
+
+		bcsp->tbcsp.expires += LE_POLLING;
+		add_timer(&bcsp->tbcsp);
+		break;
+	}
 
 	hci_uart_tx_wakeup(hu);
 }
@@ -708,12 +838,16 @@ static int bcsp_open(struct hci_uart *hu)
 	init_timer(&bcsp->tbcsp);
 	bcsp->tbcsp.function = bcsp_timed_event;
 	bcsp->tbcsp.data     = (u_long) hu;
+	bcsp->tbcsp.expires  = jiffies + LE_POLLING;
 
 	bcsp->rx_state = BCSP_W4_PKT_DELIMITER;
+	bcsp->le_state = BCSP_SHY;
 
 	if (txcrc)
 		bcsp->use_crc = 1;
 
+	add_timer(&bcsp->tbcsp);
+
 	return 0;
 }
 
-- 
2.1.3

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux