[RFC 2/3] Bluetooth: Implement Intel specific device initialization

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

 



From: Tedd Ho-Jeong An <tedd.an@xxxxxxxxx>

This patch implements the Intel specific device initialization:
- Enable the device configuration mode
- Read the FW version of the device
- Open the patch file, if exists.
- Send the patch data via HCI command
- Once done, disable the device configuation mode

Signed-off-by: Tedd Ho-Jeong An <tedd.an@xxxxxxxxx>
---
 drivers/bluetooth/btusb_intel.c |  322 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 321 insertions(+), 1 deletion(-)

diff --git a/drivers/bluetooth/btusb_intel.c b/drivers/bluetooth/btusb_intel.c
index 51c019d..d45ddb9 100644
--- a/drivers/bluetooth/btusb_intel.c
+++ b/drivers/bluetooth/btusb_intel.c
@@ -21,12 +21,38 @@
  *
  */
 #include <linux/module.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
 #include <linux/errno.h>
+#include <linux/timer.h>
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 
 #include "btusb.h"
 
+/* Intel specific HCI cmd opcodes */
+#define INTEL_HCI_MFG_MODE		0xfc11
+#define INTEL_HCI_GET_VER		0xfc05
+
+/* Intel specific HCI cmd parameter for patch reset */
+#define INTEL_HCI_PATCH_SKIP		0x00
+#define INTEL_HCI_PATCH_DISABLE		0x01
+#define INTEL_HCI_PATCH_ENABLE		0x02
+
+/* Intel specific HCI event status - success */
+#define INTEL_EV_STATUS_SUCCESS		0x00
+
+/* Intel specific patch file location and file extension */
+#define INTEL_PATCH_DIR			"intel/"
+#define INTEL_PATCH_EXT			".bseq"
+
+/* Patch entry type flag */
+#define INTEL_PATCH_TYPE_CMD		0x01
+#define INTEL_PATCH_TYPE_EVT		0x02
+
+/* Maximum length of one patch entry */
+#define INTEL_PATCH_MAX_LEN		260
+
 /* patch state */
 enum intel_patch_state {
 	INTEL_PATCH_PRE,
@@ -42,11 +68,165 @@ struct intel_patch_data {
 	struct hci_dev		*hdev;
 
 	int			state;
+	u8			patch_reset;
+
+	struct completion	wait_patch_completion;
+
+	char			device_ver[32];
+	const struct firmware	*fw;
+	const u8		*patch_curr;
+	unsigned int		patch_read;
 };
 
+static int intel_send_mfg_cmd(struct hci_dev *hdev, u8 mode, u8 reset)
+{
+	u8 param[2];
+
+	param[0] = mode;
+	param[1] = reset;
+
+	BT_DBG("mfg mode: %02x reset: %02x", mode, reset);
+
+	return hci_send_cmd(hdev, INTEL_HCI_MFG_MODE, 2, param);
+}
+
+static int intel_send_patch_cmd(struct intel_patch_data *data)
+{
+	const u8 *ptr;
+	u8 param[INTEL_PATCH_MAX_LEN];
+	u16 opcode;
+	struct hci_command_hdr *hdr;
+
+	ptr = data->patch_curr;
+	if (*ptr != INTEL_PATCH_TYPE_CMD) {
+		BT_ERR("invalid patch cmd sequence: %02x", *ptr);
+		return -EILSEQ;
+	}
+	ptr++;
+
+	hdr = (void *)ptr;
+	opcode = le16_to_cpu(hdr->opcode);
+
+	ptr += sizeof(*hdr);
+
+	/* update the data before sending the command */
+	data->patch_curr = ptr + hdr->plen;
+	data->patch_read += hdr->plen + 4;
+
+	memcpy(param, ptr, hdr->plen);
+
+	if (hci_send_cmd(data->hdev, opcode, hdr->plen, param) < 0) {
+		BT_ERR("failed to send patch cmd: %04x", opcode);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int intel_verify_cc_evt(struct sk_buff *skb, u16 opcode)
+{
+	u8 status;
+	u16 opc;
+	struct hci_ev_cmd_complete *cc;
+	struct hci_event_hdr *hdr;
+
+	hdr = (void *)skb->data;
+	if (hdr->evt != HCI_EV_CMD_COMPLETE) {
+		BT_ERR("invalid event code: %02x", hdr->evt);
+		return -1;
+	}
+	skb_pull(skb, sizeof(*hdr));
+
+	cc = (void *)skb->data;
+	opc = le16_to_cpu(cc->opcode);
+	if (opc != opcode) {
+		BT_ERR("invalid opcode: %04x", opc);
+		return -1;
+	}
+	skb_pull(skb, sizeof(*cc));
+
+	status = *((u8 *) skb->data);
+	if (status != INTEL_EV_STATUS_SUCCESS) {
+		BT_ERR("event status failed: %02x", status);
+		return -1;
+	}
+	skb_pull(skb, 1);
+
+	return 0;
+}
+
+static int intel_verify_ver_cc_evt(struct sk_buff *skb,
+				   struct intel_patch_data *data)
+{
+	int i;
+	if (intel_verify_cc_evt(skb, INTEL_HCI_GET_VER) < 0)
+		return -1;
+
+	for (i = 0; i < skb->len; i++)
+		sprintf(&data->device_ver[i*2], "%02x", skb->data[i]);
+
+	return 0;
+}
+
+static int intel_verify_patch_evt(struct sk_buff *skb,
+				  struct intel_patch_data *data)
+{
+	const u8 *ptr;
+	struct hci_event_hdr *s_hdr;
+	struct hci_event_hdr *p_hdr;
+
+	ptr = data->patch_curr;
+	if (INTEL_PATCH_TYPE_EVT != *ptr) {
+		BT_ERR("invalid patch evt sequence: %02x", *ptr);
+		return -EILSEQ;
+	}
+	ptr++;
+
+	p_hdr = (void *)ptr;
+	s_hdr = (void *)skb->data;
+
+	if (p_hdr->evt != s_hdr->evt || p_hdr->plen != s_hdr->plen) {
+		BT_ERR("mismatch evt hdr: %02x %02x", s_hdr->evt, s_hdr->plen);
+		return -1;
+	}
+
+	ptr += sizeof(*p_hdr);
+	skb_pull(skb, sizeof(*s_hdr));
+
+	data->patch_curr = ptr + p_hdr->plen;
+	data->patch_read += p_hdr->plen + 3;
+
+	if (memcmp(ptr, skb->data, s_hdr->plen)) {
+		BT_ERR("mismatch evt data");
+	}
+
+	return 0;
+}
+
+static int intel_prepare_patch_file(struct intel_patch_data *data)
+{
+	char file[120];
+
+	snprintf(file, 120, "%s%s%s", INTEL_PATCH_DIR, data->device_ver,
+		 INTEL_PATCH_EXT);
+	BT_DBG("patch file: %s", file);
+
+	if (request_firmware(&data->fw, file, &data->hdev->dev) < 0) {
+		BT_ERR("failed to open patch file: %s", file);
+		return -1;
+	}
+
+	data->patch_read = 0;
+	data->patch_curr = data->fw->data;
+
+	return 0;
+}
+
 int btusb_intel_init(struct hci_dev *hdev)
 {
+	int ret;
 	struct intel_patch_data *data;
+	int cont = 1;
 
 	BT_INFO("Intel BT USB: device initialization - patching device");
 
@@ -61,9 +241,93 @@ int btusb_intel_init(struct hci_dev *hdev)
 	data->hdev = hdev;
 	data->state = INTEL_PATCH_PRE;
 
+	init_completion(&data->wait_patch_completion);
+
+	while (cont) {
+		BT_DBG("patch state: %d", data->state);
+		switch (data->state) {
+		case INTEL_PATCH_PRE:
+			/* send cmd to enable the device configuration mode */
+			ret = intel_send_mfg_cmd(hdev, 0x01, 0x00);
+			if (ret < 0) {
+				BT_ERR("failed to send cmd: enter mfg %d", ret);
+				goto exit_error;
+			}
+			break;
+
+		case INTEL_PATCH_VER:
+			/* send cmd to get the device's version */
+			ret = hci_send_cmd(hdev, INTEL_HCI_GET_VER, 0, NULL);
+			if (ret < 0) {
+				BT_ERR("failed to send cmd: get ver %d", ret);
+				goto exit_error;
+			}
+			break;
+
+		case INTEL_PATCH_PREP_PATCH:
+			/* open the patch file if it is available */
+			ret = intel_prepare_patch_file(data);
+			if (ret < 0) {
+				BT_ERR("failed to prepare patch file %d", ret);
+				data->state = INTEL_PATCH_POST;
+				data->patch_reset = INTEL_HCI_PATCH_SKIP;
+			} else {
+				BT_DBG("patch data is setup");
+				data->state = INTEL_PATCH_PATCHING;
+			}
+			/* this is the only state that doesn't expect any evt */
+			goto skip_wait;
+
+		case INTEL_PATCH_PATCHING:
+			/* send patch entry in patch data. one at a time */
+			ret = intel_send_patch_cmd(data);
+			if (ret < 0) {
+				BT_ERR("failed to send cmd: patch cmd %d", ret);
+				goto exit_error;
+			}
+			break;
+
+		case INTEL_PATCH_POST:
+			/* exit the device configuration mode */
+			ret = intel_send_mfg_cmd(hdev, 0x00, data->patch_reset);
+			if (ret < 0) {
+				BT_ERR("failed to send cmd: exit mfg: %d", ret);
+				goto exit_error;
+			}
+			break;
+
+		default:
+			BT_ERR("unknown patch state: %d", data->state);
+			ret = -EILSEQ;
+			goto exit_error;
+		}
+
+		/* waiting for event */
+		ret = wait_for_completion_interruptible(
+				&data->wait_patch_completion);
+		if (ret < 0) {
+			BT_ERR("patch completion error: %d", ret);
+			goto exit_error;
+		}
+
+skip_wait:
+		if (data->state == INTEL_PATCH_ERROR) {
+			BT_ERR("patch error");
+			ret = -EILSEQ;
+			goto exit_error;
+		}
+
+		if (data->state == INTEL_PATCH_COMPLETED) {
+			BT_INFO("patch completed");
+			cont = 0;
+		}
+	}
+
+exit_error:
+	release_firmware(data->fw);
 	kfree(data);
 
-	return 0;
+	return ret;
 }
 EXPORT_SYMBOL_GPL(btusb_intel_init);
 
@@ -73,9 +337,65 @@ void btusb_intel_event(struct hci_dev *hdev, struct sk_buff *skb)
 
 	BT_DBG("Intel BT USB: HCI event handler state=%d", data->state);
 
+	switch (data->state) {
+	case INTEL_PATCH_PRE:
+		if (intel_verify_cc_evt(skb, INTEL_HCI_MFG_MODE) < 0) {
+			BT_ERR("cmd failed: enter mfg mode");
+			data->state = INTEL_PATCH_ERROR;
+		} else {
+			BT_DBG("cmd success: enter mfg mode");
+			data->state = INTEL_PATCH_VER;
+		}
+		break;
+
+	case INTEL_PATCH_VER:
+		if (intel_verify_ver_cc_evt(skb, data) < 0) {
+			BT_ERR("cmd failed: get version");
+			data->patch_reset = INTEL_HCI_PATCH_SKIP;
+			data->state = INTEL_PATCH_POST;
+		} else {
+			BT_DBG("cmd success: get version");
+			data->state = INTEL_PATCH_PREP_PATCH;
+		}
+		break;
+
+	case INTEL_PATCH_PATCHING:
+		if (intel_verify_patch_evt(skb, data) < 0) {
+			BT_ERR("cmd failed: patch");
+			data->patch_reset = INTEL_HCI_PATCH_DISABLE;
+			data->state = INTEL_PATCH_POST;
+		} else {
+			BT_DBG("cmd success: patch");
+			if (data->patch_read == data->fw->size) {
+				BT_DBG("no more patch to send");
+				data->patch_reset = INTEL_HCI_PATCH_ENABLE;
+				data->state = INTEL_PATCH_POST;
+			} else {
+				BT_DBG("more patch to send");
+			}
+		}
+		break;
+
+	case INTEL_PATCH_POST:
+		if (intel_verify_cc_evt(skb, INTEL_HCI_MFG_MODE) < 0) {
+			BT_ERR("cmd failed: exit mfg mode");
+			data->state = INTEL_PATCH_ERROR;
+		} else {
+			BT_DBG("cmd success: exit mfg mode");
+			data->state = INTEL_PATCH_COMPLETED;
+		}
+		break;
+
+	default:
+		BT_ERR("unknown patch state: %d", data->state);
+		data->state = INTEL_PATCH_ERROR;
+		break;
+	}
+
 	del_timer(&hdev->cmd_timer);
 	atomic_set(&hdev->cmd_cnt, 1);
 	kfree_skb(skb);
+	complete(&data->wait_patch_completion);
 	return;
 }
 EXPORT_SYMBOL_GPL(btusb_intel_event);
-- 
1.7.9.5


--
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