Bluetooth: Add initialization tracking to HCI Three-wire driver

This patch adds tracking for the uninitialized, initialized and active
states for Three-wire UART. This is needed so we can handle periodic
sending of the Link Establishment messages before reaching active state
and so that we do not try to do any higher level HCI data transmission
before reaching active state.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
Signed-off-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk>
diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c
index d9d42f6..6fb8d4e 100644
--- a/drivers/bluetooth/hci_h5.c
+++ b/drivers/bluetooth/hci_h5.c
@@ -75,32 +75,17 @@
 	u8			tx_seq;		/* Next seq number to send */
 	u8			tx_ack;		/* Next ack number to send */
 
+	enum {
+		H5_UNINITIALIZED,
+		H5_INITIALIZED,
+		H5_ACTIVE,
+	} state;
+
 	bool			sleeping;
 };
 
 static void h5_reset_rx(struct h5 *h5);
 
-static void h5_timed_event(unsigned long arg)
-{
-	struct hci_uart *hu = (struct hci_uart *) arg;
-	struct h5 *h5 = hu->priv;
-	struct sk_buff *skb;
-	unsigned long flags;
-
-	BT_DBG("hu %p retransmitting %u pkts", hu, h5->unack.qlen);
-
-	spin_lock_irqsave_nested(&h5->unack.lock, flags, SINGLE_DEPTH_NESTING);
-
-	while ((skb = __skb_dequeue_tail(&h5->unack)) != NULL) {
-		h5->tx_seq = (h5->tx_seq - 1) & 0x07;
-		skb_queue_head(&h5->rel, skb);
-	}
-
-	spin_unlock_irqrestore(&h5->unack.lock, flags);
-
-	hci_uart_tx_wakeup(hu);
-}
-
 static void h5_link_control(struct hci_uart *hu, const void *data, size_t len)
 {
 	struct h5 *h5 = hu->priv;
@@ -117,6 +102,41 @@
 	skb_queue_tail(&h5->unrel, nskb);
 }
 
+static void h5_timed_event(unsigned long arg)
+{
+	const unsigned char sync_req[] = { 0x01, 0x7e };
+	const unsigned char conf_req[] = { 0x03, 0xfc, 0x01 };
+	struct hci_uart *hu = (struct hci_uart *) arg;
+	struct h5 *h5 = hu->priv;
+	struct sk_buff *skb;
+	unsigned long flags;
+
+	if (h5->state == H5_UNINITIALIZED)
+		h5_link_control(hu, sync_req, sizeof(sync_req));
+
+	if (h5->state == H5_INITIALIZED)
+		h5_link_control(hu, conf_req, sizeof(conf_req));
+
+	if (h5->state != H5_ACTIVE) {
+		mod_timer(&h5->timer, jiffies + H5_SYNC_TIMEOUT);
+		goto wakeup;
+	}
+
+	BT_DBG("hu %p retransmitting %u pkts", hu, h5->unack.qlen);
+
+	spin_lock_irqsave_nested(&h5->unack.lock, flags, SINGLE_DEPTH_NESTING);
+
+	while ((skb = __skb_dequeue_tail(&h5->unack)) != NULL) {
+		h5->tx_seq = (h5->tx_seq - 1) & 0x07;
+		skb_queue_head(&h5->rel, skb);
+	}
+
+	spin_unlock_irqrestore(&h5->unack.lock, flags);
+
+wakeup:
+	hci_uart_tx_wakeup(hu);
+}
+
 static int h5_open(struct hci_uart *hu)
 {
 	struct h5 *h5;
@@ -230,12 +250,14 @@
 	if (memcmp(data, sync_req, 2) == 0) {
 		h5_link_control(hu, sync_rsp, 2);
 	} else if (memcmp(data, sync_rsp, 2) == 0) {
+		h5->state = H5_INITIALIZED;
 		h5_link_control(hu, conf_req, 3);
 	} else if (memcmp(data, conf_req, 2) == 0) {
 		h5_link_control(hu, conf_rsp, 2);
 		h5_link_control(hu, conf_req, 3);
 	} else if (memcmp(data, conf_rsp, 2) == 0) {
 		BT_DBG("Three-wire init sequence complete");
+		h5->state = H5_ACTIVE;
 		hci_uart_init_ready(hu);
 		return;
 	} else if (memcmp(data, sleep_req, 2) == 0) {
@@ -340,6 +362,12 @@
 		return 0;
 	}
 
+	if (h5->state != H5_ACTIVE &&
+	    H5_HDR_PKT_TYPE(hdr) != HCI_3WIRE_LINK_PKT) {
+		BT_ERR("Non-link packet received in non-active state");
+		h5_reset_rx(h5);
+	}
+
 	h5->rx_func = h5_rx_payload;
 	h5->rx_pending = H5_HDR_LEN(hdr);
 
@@ -468,6 +496,12 @@
 		return 0;
 	}
 
+	if (h5->state != H5_ACTIVE) {
+		BT_ERR("Ignoring HCI data in non-active state");
+		kfree_skb(skb);
+		return 0;
+	}
+
 	switch (bt_cb(skb)->pkt_type) {
 	case HCI_ACLDATA_PKT:
 	case HCI_COMMAND_PKT: