Re: [PATCH v5] Bluetooth: hci_uart: Support firmware download for Marvell

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

 



Hi Amitkumar,

From: Ganapathi Bhat <gbhat@xxxxxxxxxxx>

This patch implement firmware download feature for
Marvell Bluetooth devices. If firmware is already
downloaded, it will skip downloading.

Signed-off-by: Ganapathi Bhat <gbhat@xxxxxxxxxxx>
Signed-off-by: Amitkumar Karwar <akarwar@xxxxxxxxxxx>
---
v2: Fixed compilation warning reported by kbuild test robot
v3: Addressed review comments from Marcel Holtmann
     a) Removed vendor specific code from hci_ldisc.c
     b) Get rid of static forward declaration
     c) Removed unnecessary heavy nesting
     d) Git rid of module parameter and global variables
     e) Add logic to pick right firmware image
v4: Addresses review comments from Alan
     a) Use existing kernel helper APIs instead of writing own.
     b) Replace mdelay() with msleep()
v5: Addresses review comments from Loic Poulain
     a) Use bt_dev_err/warn/dbg helpers insted of BT_ERR/WARN/DBG
     b) Used static functions where required and removed forward delcarations
     c) Edited comments for the function hci_uart_recv_data
     d) Made HCI_UART_DNLD_FW flag a part of driver private data
---
  drivers/bluetooth/Kconfig     |  10 +
  drivers/bluetooth/Makefile    |   1 +
  drivers/bluetooth/btmrvl.h    |  41 +++
  drivers/bluetooth/hci_ldisc.c |   6 +
  drivers/bluetooth/hci_mrvl.c  | 592 ++++++++++++++++++++++++++++++++++++++++++
  drivers/bluetooth/hci_uart.h  |   8 +-
  6 files changed, 657 insertions(+), 1 deletion(-)
  create mode 100644 drivers/bluetooth/btmrvl.h
  create mode 100644 drivers/bluetooth/hci_mrvl.c


+
+/* This function receives data from the uart device during firmware download.
+ * Driver expects 5 bytes of data as per the protocal in the below format:
+ * <HEADER><BYTE_1><BYTE_2><BYTE_3><BYTE_4>
+ * BYTE_3 and BYTE_4 are compliment of BYTE_1 an BYTE_2. Data can come in chunks
+ * of any length. If length received is < 5, accumulate the data in an array,
+ * until we have a sequence of 5 bytes, starting with the expected HEADER. If
+ * the length received is > 5  bytes, then get the first 5 bytes, starting with
+ * the HEADER and process the same, ignoring the rest of the bytes as per the
+ * protocal.
+ */
+static void hci_uart_recv_data(struct hci_uart *hu, u8 *buf, int len)
+{
+	struct mrvl_data *mrvl = hu->priv;
+	struct fw_data *fw_data = mrvl->fwdata;
+	int i = 0;
+
+	if (len < 5) {

You want to accumulate your data in a buf/skb, once the size of your
buffer matches your packet expected size, call a mrvl_pkt_complete(hu, skb). This is the len of your current buffer you want to test, not
the current len. Keep it simple.

+		if ((!fw_data->next_index) &&
+		    (buf[0] != fw_data->expected_ack)) {
+			return;
+		}
+	}
+
+	if (len == 5) {
+		if (buf[0] != fw_data->expected_ack)
+			return;
+
+		fw_data->next_index = 0;
+		mrvl_validate_hdr_and_len(hu, buf);
+		return;
+	}
+
+	if (len > 5) {
+		i = 0;
+
+		while ((i < len) && (buf[i] != fw_data->expected_ack))
+			i++;
+
+		if (i == len)
+			return;
+
+		if ((len - i) >= 5) {
+			fw_data->next_index = 0;
+			mrvl_validate_hdr_and_len(hu, buf + i);
+			return;
+		}
+
+		hci_uart_recv_data(hu, buf + i, len - i);
+		return;
+	}
+
+	for (i = 0; i < len; i++) {
+		fw_data->five_bytes[fw_data->next_index] = buf[i];
+		if (++fw_data->next_index == 5) {
+			fw_data->next_index = 0;
+			mrvl_validate_hdr_and_len(hu, fw_data->five_bytes);
+			return;
+		}
+	}
+}
+

+
+/* Send bytes to device */
+static int mrvl_send_data(struct hci_uart *hu, struct sk_buff *skb)
+{
+	struct tty_struct *tty = hu->tty;
+	int retry = 0;
+	int skb_len;
+
+	skb_len = skb->len;
+	while (retry < MRVL_MAX_RETRY_SEND) {
+		tty->ops->write(tty, skb->data, skb->len);

You should test write returned value (look at hci_uart_write_work).
I don't understand why you don't enqueue your packet in your existing
tx queue to let hci_ldisc taking care writing to the tty layer.
skb_queue_head(&mrvl->txq, skb);
hci_uart_tx_wakeup(hu);

+		if (mrvl_wait_for_hdr(hu, MRVL_HDR_REQ_FW) == -1) {
+			retry++;
+			continue;
+		} else {
+			skb_pull(skb, skb_len);
+			break;
+		}
+	}
+	if (retry == MRVL_MAX_RETRY_SEND)
+		return -1;
+
+	return 0;
+}
+
+/* Download firmware to the device */
+static int mrvl_dnld_fw(struct hci_uart *hu, const char *file_name)
+{
+	struct hci_dev *hdev = hu->hdev;
+	const struct firmware *fw;
+	struct sk_buff *skb = NULL;
+	int offset = 0;
+	int ret, tx_len;
+	struct mrvl_data *mrvl = hu->priv;
+	struct fw_data *fw_data = mrvl->fwdata;
+
+	ret = request_firmware(&fw, file_name, &hdev->dev);
+	if (ret < 0) {
+		bt_dev_err(hu->hdev, "request_firmware() failed");
+		ret = -1;
+		goto done;

Why you don't just return here, nothing to do in done.

+	}
+	if (fw) {

You already tested request_firmware output, don't need to test fw.

+		bt_dev_info(hu->hdev, "Downloading FW (%d bytes)",
+			    (u16)fw->size);
+	} else {
+		bt_dev_err(hu->hdev, "No FW image found");
+		ret = -1;
+		goto done;
+	}
+
+	skb = bt_skb_alloc(MRVL_MAX_FW_BLOCK_SIZE, GFP_ATOMIC);

Why GFP_ATOMIC, use GFP_KERNEL, you are allowed to sleep here.

+	if (!skb) {
+		bt_dev_err(hu->hdev, "cannot allocate memory for skb");
+		ret = -1;
+		goto done;
+	}
+
+	skb->dev = (void *)hdev;
+	fw_data->last_ack = 0;
+
+	do {
+		if ((offset >= fw->size) || (fw_data->last_ack))
+			break;
+		tx_len = fw_data->next_len;
+		if ((fw->size - offset) < tx_len)
+			tx_len = fw->size - offset;
+
+		memcpy(skb->data, &fw->data[offset], tx_len);
+		skb_put(skb, tx_len);
+		if (mrvl_send_data(hu, skb) != 0) {
+			bt_dev_err(hu->hdev, "fail to download firmware");
+			ret = -1;
+			goto done;
+		}
+		skb_push(skb, tx_len);
+		skb_trim(skb, 0);
+		offset += tx_len;
+	} while (1);
+
+	bt_dev_info(hu->hdev, "downloaded %d byte firmware", offset);
+done:
+	if (fw)

I think fw can be unassigned if you come from request_firmware error.
Just return on request firmware issue and release firmware
unconditionally here.

+		release_firmware(fw);
+
+	kfree(skb);
+	return ret;
+}
+
+/* Set the baud rate */
+static int mrvl_set_dev_baud(struct hci_uart *hu)
+{
+	struct hci_dev *hdev = hu->hdev;
+	struct sk_buff *skb;
+	static const u8 baud_param[] = { 0xc0, 0xc6, 0x2d, 0x00};
Missing space before closing bracket.

+	int err;
+
+	skb = __hci_cmd_sync(hdev, MRVL_HCI_OP_SET_BAUD, sizeof(baud_param),
+			     baud_param, HCI_INIT_TIMEOUT);
+	if (IS_ERR(skb)) {
+		err = PTR_ERR(skb);
+		bt_dev_err(hu->hdev, "Set device baudrate failed (%d)", err);
+		return err;
+	}
+	kfree_skb(skb);
+
+	return 0;
+}
+
+
+/* Download helper and firmare to device */
+static int hci_uart_dnld_fw(struct hci_uart *hu)
+{
+	struct tty_struct *tty = hu->tty;
+	struct ktermios new_termios;
+	struct ktermios old_termios;
+	struct mrvl_data *mrvl = hu->priv;
+	char fw_name[128];
+	int ret;
+
+	old_termios = tty->termios;
+
+	if (!tty) {

Seems to be useless, tty should not be null here.

+		bt_dev_err(hu->hdev, "tty is null");
+		clear_bit(HCI_UART_DNLD_FW, &mrvl->flags);
+		ret = -1;
+		goto fail;
+	}
+
+	if (get_cts(hu)) {
+		bt_dev_info(hu->hdev, "fw is running");
+		clear_bit(HCI_UART_DNLD_FW, &mrvl->flags);
+		goto set_baud;
+	}
+
+	hci_uart_set_baudrate(hu, 115200);
+	hci_uart_set_flow_control(hu, true);
+
+	ret = mrvl_wait_for_hdr(hu, MRVL_HDR_REQ_FW);
+	if (ret)
+		goto fail;
+
+	ret = mrvl_dnld_fw(hu, MRVL_HELPER_NAME);
+	if (ret)
+		goto fail;
+
+	msleep(MRVL_DNLD_DELAY);
+
+	hci_uart_set_baudrate(hu, 3000000);
+	hci_uart_set_flow_control(hu, false);
+
+	ret = mrvl_get_fw_name(hu, fw_name);
+	if (ret)
+		goto fail;
+
+	ret = mrvl_wait_for_hdr(hu, MRVL_HDR_REQ_FW);
+	if (ret)
+		goto fail;
+
+	ret = mrvl_dnld_fw(hu, fw_name);
+	if (ret)
+		goto fail;
+
+	msleep(MRVL_DNLD_DELAY);
+	/* restore uart settings */
+	new_termios = tty->termios;
+	tty->termios.c_cflag = old_termios.c_cflag;
+	tty_set_termios(tty, &new_termios);
+	clear_bit(HCI_UART_DNLD_FW, &mrvl->flags);
+
+set_baud:
+	ret = mrvl_set_baud(hu);
+	if (ret)
+		goto fail;
+
+	msleep(MRVL_DNLD_DELAY);
+
+	return ret;
+fail:
+	/* restore uart settings */
+	new_termios = tty->termios;
+	tty->termios.c_cflag = old_termios.c_cflag;
+	tty_set_termios(tty, &new_termios);
+	clear_bit(HCI_UART_DNLD_FW, &mrvl->flags);
+
+	return ret;
+}
+

Regards,
Loic
--
Intel Open Source Technology Center
http://oss.intel.com/
--
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