Re: [PATCH v6 3/3] Bluetooth: NXP: Add protocol support for NXP Bluetooth chipsets
From: Ilpo Järvinen
Date: Thu Mar 02 2023 - 10:06:35 EST
On Wed, 1 Mar 2023, Neeraj Sanjay Kale wrote:
> This adds a driver based on serdev driver for the NXP BT serial protocol
> based on running H:4, which can enable the built-in Bluetooth device
> inside an NXP BT chip.
>
> This driver has Power Save feature that will put the chip into sleep state
> whenever there is no activity for 2000ms, and will be woken up when any
> activity is to be initiated over UART.
>
> This driver enables the power save feature by default by sending the vendor
> specific commands to the chip during setup.
>
> During setup, the driver checks if a FW is already running on the chip
> by waiting for the bootloader signature, and downloads device specific FW
> file into the chip over UART if bootloader signature is received..
>
> Signed-off-by: Neeraj Sanjay Kale <neeraj.sanjaykale@xxxxxxx>
> ---
> v2: Removed conf file support and added static data for each chip based
> on compatibility devices mentioned in DT bindings. Handled potential
> memory leaks and null pointer dereference issues, simplified FW download
> feature, handled byte-order and few cosmetic changes. (Ilpo Järvinen,
> Alok Tiwari, Hillf Danton)
> v3: Added conf file support necessary to support different vendor modules,
> moved .h file contents to .c, cosmetic changes. (Luiz Augusto von Dentz,
> Rob Herring, Leon Romanovsky)
> v4: Removed conf file support, optimized driver data, add logic to select
> FW name based on chip signature (Greg KH, Ilpo Jarvinen, Sherry Sun)
> v5: Replaced bt_dev_info() with bt_dev_dbg(), handled user-space cmd
> parsing in nxp_enqueue() in a better way. (Greg KH, Luiz Augusto von Dentz)
> v6: Add support for fw-init-baudrate parameter from device tree,
> modified logic to detect FW download is needed or FW is running. (Greg
> KH, Sherry Sun)
> ---
> MAINTAINERS | 1 +
> drivers/bluetooth/Kconfig | 11 +
> drivers/bluetooth/Makefile | 1 +
> drivers/bluetooth/btnxpuart.c | 1317 +++++++++++++++++++++++++++++++++
> 4 files changed, 1330 insertions(+)
> create mode 100644 drivers/bluetooth/btnxpuart.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 030ec6fe89df..fdb9b0788c89 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22840,6 +22840,7 @@ M: Amitkumar Karwar <amitkumar.karwar@xxxxxxx>
> M: Neeraj Kale <neeraj.sanjaykale@xxxxxxx>
> S: Maintained
> F: Documentation/devicetree/bindings/net/bluetooth/nxp,88w8987-bt.yaml
> +F: drivers/bluetooth/btnxpuart.c
>
> THE REST
> M: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index 5a1a7bec3c42..359a4833e31f 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -465,4 +465,15 @@ config BT_VIRTIO
> Say Y here to compile support for HCI over Virtio into the
> kernel or say M to compile as a module.
>
> +config BT_NXPUART
> + tristate "NXP protocol support"
> + depends on SERIAL_DEV_BUS
> + help
> + NXP is serial driver required for NXP Bluetooth
> + devices with UART interface.
> +
> + Say Y here to compile support for NXP Bluetooth UART device into
> + the kernel, or say M here to compile as a module (btnxpuart).
> +
> +
> endmenu
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index e0b261f24fc9..7a5967e9ac48 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -29,6 +29,7 @@ obj-$(CONFIG_BT_QCA) += btqca.o
> obj-$(CONFIG_BT_MTK) += btmtk.o
>
> obj-$(CONFIG_BT_VIRTIO) += virtio_bt.o
> +obj-$(CONFIG_BT_NXPUART) += btnxpuart.o
>
> obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o
>
> diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c
> new file mode 100644
> index 000000000000..f581e05ddecb
> --- /dev/null
> +++ b/drivers/bluetooth/btnxpuart.c
> @@ -0,0 +1,1317 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * NXP Bluetooth driver
> + * Copyright 2018-2023 NXP
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +
> +#include <linux/serdev.h>
> +#include <linux/of.h>
> +#include <linux/skbuff.h>
> +#include <asm/unaligned.h>
> +#include <linux/firmware.h>
> +#include <linux/string.h>
> +#include <linux/crc8.h>
> +#include <linux/crc32.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +
> +#include "h4_recv.h"
> +
> +#define MANUFACTURER_NXP 37
> +
> +#define BTNXPUART_TX_STATE_ACTIVE 1
> +#define BTNXPUART_FW_DOWNLOADING 2
> +#define BTNXPUART_CHECK_BOOT_SIGNATURE 3
> +
> +#define FIRMWARE_W8987 "nxp/uartuart8987_bt.bin"
> +#define FIRMWARE_W8997 "nxp/uartuart8997_bt_v4.bin"
> +#define FIRMWARE_W9098 "nxp/uartuart9098_bt_v1.bin"
> +#define FIRMWARE_IW416 "nxp/uartiw416_bt_v0.bin"
> +#define FIRMWARE_IW612 "nxp/uartspi_n61x_v1.bin.se"
> +
> +#define CHIP_ID_W9098 0x5c03
> +#define CHIP_ID_IW416 0x7201
> +#define CHIP_ID_IW612 0x7601
> +
> +#define HCI_NXP_PRI_BAUDRATE 115200
> +#define HCI_NXP_SEC_BAUDRATE 3000000
> +
> +#define MAX_FW_FILE_NAME_LEN 50
> +
> +/* Default ps timeout period in milli-second */
> +#define PS_DEFAULT_TIMEOUT_PERIOD 2000
> +
> +/* wakeup methods */
> +#define WAKEUP_METHOD_DTR 0
> +#define WAKEUP_METHOD_BREAK 1
> +#define WAKEUP_METHOD_EXT_BREAK 2
> +#define WAKEUP_METHOD_RTS 3
> +#define WAKEUP_METHOD_INVALID 0xff
> +
> +/* power save mode status */
> +#define PS_MODE_DISABLE 0
> +#define PS_MODE_ENABLE 1
> +
> +/* Power Save Commands to ps_work_func */
> +#define PS_CMD_EXIT_PS 1
> +#define PS_CMD_ENTER_PS 2
> +
> +/* power save state */
> +#define PS_STATE_AWAKE 0
> +#define PS_STATE_SLEEP 1
> +
> +/* Bluetooth vendor command : Sleep mode */
> +#define HCI_NXP_AUTO_SLEEP_MODE 0xfc23
> +/* Bluetooth vendor command : Wakeup method */
> +#define HCI_NXP_WAKEUP_METHOD 0xfc53
> +/* Bluetooth vendor command : Set operational baudrate */
> +#define HCI_NXP_SET_OPER_SPEED 0xfc09
> +/* Bluetooth vendor command: Independent Reset */
> +#define HCI_NXP_IND_RESET 0xfcfc
> +
> +/* Bluetooth Power State : Vendor cmd params */
> +#define BT_PS_ENABLE 0x02
> +#define BT_PS_DISABLE 0x03
> +
> +/* Bluetooth Host Wakeup Methods */
> +#define BT_HOST_WAKEUP_METHOD_NONE 0x00
> +#define BT_HOST_WAKEUP_METHOD_DTR 0x01
> +#define BT_HOST_WAKEUP_METHOD_BREAK 0x02
> +#define BT_HOST_WAKEUP_METHOD_GPIO 0x03
> +
> +/* Bluetooth Chip Wakeup Methods */
> +#define BT_CTRL_WAKEUP_METHOD_DSR 0x00
> +#define BT_CTRL_WAKEUP_METHOD_BREAK 0x01
> +#define BT_CTRL_WAKEUP_METHOD_GPIO 0x02
> +#define BT_CTRL_WAKEUP_METHOD_EXT_BREAK 0x04
> +#define BT_CTRL_WAKEUP_METHOD_RTS 0x05
> +
> +#define MAX_USER_PARAMS 10
> +
> +struct ps_data {
> + u8 ps_mode;
> + u8 cur_psmode;
> + u8 ps_state;
I tried to follow the code around these variables but its turned out too
hard for me... Should ps_mode perhaps be renamed to target_psmode?
Should nxp_enqueue() check if ps_mode was changed or not before calling
hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL); ?
It would be useful to explain somewhere how cur_psmode and ps_state
actually differ because they kinda sound the same.
> + u8 ps_cmd;
> + u8 h2c_wakeupmode;
> + u8 cur_h2c_wakeupmode;
> + u8 c2h_wakeupmode;
> + u8 c2h_wakeup_gpio;
> + bool driver_sent_cmd;
> + bool timer_on;
Look unnecessary, only used to guard del_timer_sync() but it already check
for timer_pending().
> + u32 interval;
> + struct hci_dev *hdev;
> + struct work_struct work;
> + struct timer_list ps_timer;
> +};
> + case WAKEUP_METHOD_BREAK:
> + default:
> + if (ps_state == PS_STATE_AWAKE)
> + status = serdev_device_break_ctl(nxpdev->serdev, 0);
> + else
> + status = serdev_device_break_ctl(nxpdev->serdev, -1);
> + bt_dev_dbg(hdev, "Set UART break: %s, status=%d",
> + ps_state == PS_STATE_AWAKE ? "off" : "on", status);
str_on_off() from linux/string_helpers.h
> +static int send_wakeup_method_cmd(struct hci_dev *hdev, void *data)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct ps_data *psdata = nxpdev->psdata;
> + u8 pcmd[4];
> + struct sk_buff *skb;
> + u8 *status;
> +
> + pcmd[0] = psdata->c2h_wakeupmode;
> + pcmd[1] = psdata->c2h_wakeup_gpio;
> + switch (psdata->h2c_wakeupmode) {
> + case WAKEUP_METHOD_DTR:
> + pcmd[2] = BT_CTRL_WAKEUP_METHOD_DSR;
> + break;
> + case WAKEUP_METHOD_BREAK:
> + default:
> + pcmd[2] = BT_CTRL_WAKEUP_METHOD_BREAK;
> + break;
> + }
> + pcmd[3] = 0xff;
Looks like an unnamed structure.
> +static void ps_init(struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct ps_data *psdata = nxpdev->psdata;
> +
> + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_RTS);
> + usleep_range(5000, 10000);
> + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_RTS, 0);
> + usleep_range(5000, 10000);
> +
> + switch (psdata->h2c_wakeupmode) {
> + case WAKEUP_METHOD_DTR:
> + serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR);
> + serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0);
> + break;
> + case WAKEUP_METHOD_BREAK:
> + default:
> + serdev_device_break_ctl(nxpdev->serdev, -1);
> + usleep_range(5000, 10000);
> + serdev_device_break_ctl(nxpdev->serdev, 0);
> + usleep_range(5000, 10000);
> + break;
> + }
> + if (!test_bit(HCI_RUNNING, &hdev->flags)) {
> + bt_dev_dbg(hdev, "HCI_RUNNING is not set");
> + return;
Is this an error that should be returned through the callchain rather than
concealed?
> + }
> + if (psdata->cur_h2c_wakeupmode != psdata->h2c_wakeupmode)
> + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL);
> + if (psdata->cur_psmode != psdata->ps_mode)
> + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL);
> +}
> +
> +/* NXP Firmware Download Feature */
> +static int nxp_download_firmware(struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + int err = 0;
> +
> + nxpdev->fw_dnld_v1_offset = 0;
> + nxpdev->fw_v1_sent_bytes = 0;
> + nxpdev->fw_v1_expected_len = HDR_LEN;
> + nxpdev->fw_v3_offset_correction = 0;
> + nxpdev->baudrate_changed = false;
> + nxpdev->timeout_changed = false;
> +
> + serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE);
> + serdev_device_set_flow_control(nxpdev->serdev, 0);
> + nxpdev->current_baudrate = HCI_NXP_PRI_BAUDRATE;
> +
> + /* Wait till FW is downloaded and CTS becomes low */
> + err = wait_event_interruptible_timeout(nxpdev->fw_dnld_done_wait_q,
> + !test_bit(BTNXPUART_FW_DOWNLOADING,
> + &nxpdev->tx_state),
> + msecs_to_jiffies(60000));
> + if (err == 0) {
> + bt_dev_err(hdev, "FW Download Timeout.");
> + return -ETIMEDOUT;
> + }
> +
> + serdev_device_set_flow_control(nxpdev->serdev, 1);
> + err = serdev_device_wait_for_cts(nxpdev->serdev, 1, 60000);
> + if (err < 0) {
> + bt_dev_err(hdev, "CTS is still high. FW Download failed.");
> + return err;
> + }
> + release_firmware(nxpdev->fw);
> + memset(nxpdev->fw_name, 0, MAX_FW_FILE_NAME_LEN);
Use sizeof() instead.
> +
> + /* Allow the downloaded FW to initialize */
> + usleep_range(800000, 1000000);
There would be USEC_PER_* constants to make these easier to read.
> + return 0;
> +}
> +
> +static void nxp_send_ack(u8 ack, struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + u8 ack_nak[2];
> +
> + if (ack == NXP_ACK_V1 || ack == NXP_NAK_V1) {
> + ack_nak[0] = ack;
> + serdev_device_write_buf(nxpdev->serdev, ack_nak, 1);
> + } else if (ack == NXP_ACK_V3) {
> + ack_nak[0] = ack;
> + ack_nak[1] = crc8(crc8_table, ack_nak, 1, 0xff);
> + serdev_device_write_buf(nxpdev->serdev, ack_nak, 2);
> + }
This could be done as:
int len = 1;
ack_nak[0] = ack;
if (ack == NXP_ACK_V3) {
len = 2;
ack_nak[1] = crc8(crc8_table, ack_nak, 1, 0xff);
}
serdev_device_write_buf(nxpdev->serdev, ack_nak, len);
or calculate the len with NXP_ACK_V3 ? 2 : 1 on the func call line.
> +}
> +
> +static bool nxp_fw_change_baudrate(struct hci_dev *hdev, u16 req_len)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct nxp_bootloader_cmd nxp_cmd5;
> + struct uart_config uart_config;
> +
> + if (req_len == sizeof(nxp_cmd5)) {
> + nxp_cmd5.header = __cpu_to_le32(5);
> + nxp_cmd5.arg = 0;
> + nxp_cmd5.payload_len = __cpu_to_le32(sizeof(uart_config));
> + nxp_cmd5.crc = swab32(crc32_be(0UL, (char *)&nxp_cmd5,
> + sizeof(nxp_cmd5) - 4));
swab32(crc32_be(...)) seems and odd construct instead of __cpu_to_le32().
> + serdev_device_write_buf(nxpdev->serdev, (u8 *)&nxp_cmd5, req_len);
> + nxpdev->fw_v3_offset_correction += req_len;
> + } else if (req_len == sizeof(uart_config)) {
> + uart_config.clkdiv.address = __cpu_to_le32(CLKDIVADDR);
> + uart_config.clkdiv.value = __cpu_to_le32(0x00c00000);
> + uart_config.uartdiv.address = __cpu_to_le32(UARTDIVADDR);
> + uart_config.uartdiv.value = __cpu_to_le32(1);
> + uart_config.mcr.address = __cpu_to_le32(UARTMCRADDR);
> + uart_config.mcr.value = __cpu_to_le32(MCR);
> + uart_config.re_init.address = __cpu_to_le32(UARTREINITADDR);
> + uart_config.re_init.value = __cpu_to_le32(INIT);
> + uart_config.icr.address = __cpu_to_le32(UARTICRADDR);
> + uart_config.icr.value = __cpu_to_le32(ICR);
> + uart_config.fcr.address = __cpu_to_le32(UARTFCRADDR);
> + uart_config.fcr.value = __cpu_to_le32(FCR);
> + uart_config.crc = swab32(crc32_be(0UL, (char *)&uart_config,
> + sizeof(uart_config) - 4));
Ditto.
> + serdev_device_write_buf(nxpdev->serdev, (u8 *)&uart_config, req_len);
> + serdev_device_wait_until_sent(nxpdev->serdev, 0);
> + nxpdev->fw_v3_offset_correction += req_len;
> + return true;
> + }
> + return false;
> +}
> +
> +static bool nxp_fw_change_timeout(struct hci_dev *hdev, u16 req_len)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct nxp_bootloader_cmd nxp_cmd7;
> +
> + if (req_len != sizeof(nxp_cmd7))
> + return false;
> +
> + nxp_cmd7.header = __cpu_to_le32(7);
> + nxp_cmd7.arg = __cpu_to_le32(0x70);
> + nxp_cmd7.payload_len = 0;
> + nxp_cmd7.crc = swab32(crc32_be(0UL, (char *)&nxp_cmd7,
> + sizeof(nxp_cmd7) - 4));
Ditto.
> + serdev_device_write_buf(nxpdev->serdev, (u8 *)&nxp_cmd7, req_len);
Is it safe to assume req_len is small enough to not leak stack content?
> + serdev_device_wait_until_sent(nxpdev->serdev, 0);
> + nxpdev->fw_v3_offset_correction += req_len;
> + return true;
> +}
> +
> +static u32 nxp_get_data_len(const u8 *buf)
> +{
> + struct nxp_bootloader_cmd *hdr = (struct nxp_bootloader_cmd *)buf;
> +
> + return __le32_to_cpu(hdr->payload_len);
> +}
> +
> +/* for legacy chipsets with V1 bootloader */
> +static int nxp_recv_fw_req_v1(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct btnxpuart_data *nxp_data = nxpdev->nxp_data;
> + struct v1_data_req *req;
> + u32 requested_len;
> + int err;
> +
> + if (test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state)) {
> + clear_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state);
> + wake_up_interruptible(&nxpdev->check_boot_sign_wait_q);
> + goto ret;
> + }
> +
> + if (!test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state))
> + goto ret;
This appears in both v1 and v3 (x2) functions, add a common helper for it.
> + req = (struct v1_data_req *)skb_pull_data(skb, sizeof(struct v1_data_req));
> + if (!req)
> + goto ret;
> +
> + if ((req->len ^ req->len_comp) != 0xffff) {
> + bt_dev_dbg(hdev, "ERR: Send NAK");
> + nxp_send_ack(NXP_NAK_V1, hdev);
> + goto ret;
> + }
> + nxp_send_ack(NXP_ACK_V1, hdev);
> +
> + if (nxp_data->fw_dnld_use_high_baudrate) {
> + if (!nxpdev->timeout_changed) {
> + nxpdev->timeout_changed = nxp_fw_change_timeout(hdev, req->len);
> + goto ret;
> + }
> + if (!nxpdev->baudrate_changed) {
> + nxpdev->baudrate_changed = nxp_fw_change_baudrate(hdev, req->len);
> + if (nxpdev->baudrate_changed) {
> + serdev_device_set_baudrate(nxpdev->serdev,
> + HCI_NXP_SEC_BAUDRATE);
> + serdev_device_set_flow_control(nxpdev->serdev, 1);
> + nxpdev->current_baudrate = HCI_NXP_SEC_BAUDRATE;
> + }
> + goto ret;
> + }
> + }
> +
> + if (!strlen(nxpdev->fw_name)) {
> + snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "%s",
> + nxp_data->fw_name);
> + bt_dev_info(hdev, "Request Firmware: %s", nxpdev->fw_name);
Don't print at all when there's no error here.
> + err = request_firmware(&nxpdev->fw, nxpdev->fw_name, &hdev->dev);
> + if (err < 0) {
> + bt_dev_err(hdev, "Firmware file %s not found", nxpdev->fw_name);
> + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + return err;
> + }
> + }
v1 and v3 duplicates this block except for how the fw_name is acquired,
create a helper.
> + requested_len = req->len;
> + if (requested_len == 0) {
> + bt_dev_info(hdev, "FW Downloaded Successfully: %zu bytes", nxpdev->fw->size);
> + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q);
> + goto ret;
> + }
> + if (requested_len & 0x01) {
> + /* The CRC did not match at the other end.
> + * Simply send the same bytes again.
> + */
> + requested_len = nxpdev->fw_v1_sent_bytes;
> + bt_dev_dbg(hdev, "CRC error. Resend %d bytes of FW.", requested_len);
> + } else {
> + nxpdev->fw_dnld_v1_offset += nxpdev->fw_v1_sent_bytes;
> +
> + /* The FW bin file is made up of many blocks of
> + * 16 byte header and payload data chunks. If the
> + * FW has requested a header, read the payload length
> + * info from the header, before sending the header.
> + * In the next iteration, the FW should request the
> + * payload data chunk, which should be equal to the
> + * payload length read from header. If there is a
> + * mismatch, clearly the driver and FW are out of sync,
> + * and we need to re-send the previous header again.
> + */
> + if (requested_len == nxpdev->fw_v1_expected_len) {
> + if (requested_len == HDR_LEN)
> + nxpdev->fw_v1_expected_len = nxp_get_data_len(nxpdev->fw->data +
> + nxpdev->fw_dnld_v1_offset);
> + else
> + nxpdev->fw_v1_expected_len = HDR_LEN;
> + } else {
> + if (requested_len == HDR_LEN) {
else if
> + /* FW download out of sync. Send previous chunk again */
> + nxpdev->fw_dnld_v1_offset -= nxpdev->fw_v1_sent_bytes;
> + nxpdev->fw_v1_expected_len = HDR_LEN;
> + }
> + }
> + }
> +
> + if (nxpdev->fw_dnld_v1_offset + requested_len <= nxpdev->fw->size)
> + serdev_device_write_buf(nxpdev->serdev,
> + nxpdev->fw->data + nxpdev->fw_dnld_v1_offset,
> + requested_len);
> + nxpdev->fw_v1_sent_bytes = requested_len;
> +
> +ret:
> + kfree_skb(skb);
> + return 0;
> +}
> +
> +static u8 *nxp_get_fw_name_from_chipid(struct hci_dev *hdev, u16 chipid)
> +{
> + u8 *fw_name = NULL;
> +
> + switch (chipid) {
> + case CHIP_ID_W9098:
> + fw_name = FIRMWARE_W9098;
> + break;
> + case CHIP_ID_IW416:
> + fw_name = FIRMWARE_IW416;
> + break;
> + case CHIP_ID_IW612:
> + fw_name = FIRMWARE_IW612;
> + break;
> + default:
> + bt_dev_err(hdev, "Unknown chip signature %04X", chipid);
%04x ?
> + break;
> + }
> + return fw_name;
> +}
> +
> +static int nxp_recv_chip_ver_v3(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct v3_start_ind *req = skb_pull_data(skb, sizeof(struct v3_start_ind));
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + int err;
> +
> + if (test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state)) {
> + clear_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state);
> + wake_up_interruptible(&nxpdev->check_boot_sign_wait_q);
> + goto ret;
> + }
> +
> + if (!test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state))
> + goto ret;
> +
> + if (!strlen(nxpdev->fw_name)) {
> + snprintf(nxpdev->fw_name, MAX_FW_FILE_NAME_LEN, "%s",
> + nxp_get_fw_name_from_chipid(hdev, req->chip_id));
> +
> + bt_dev_info(hdev, "Request Firmware: %s", nxpdev->fw_name);
> + err = request_firmware(&nxpdev->fw, nxpdev->fw_name, &hdev->dev);
> + if (err < 0) {
> + bt_dev_err(hdev, "Firmware file %s not found", nxpdev->fw_name);
> + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + goto ret;
> + }
> + }
> + nxp_send_ack(NXP_ACK_V3, hdev);
> +ret:
> + kfree_skb(skb);
> + return 0;
> +}
> +
> +static int nxp_recv_fw_req_v3(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct v3_data_req *req;
> +
> + if (test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state)) {
> + clear_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state);
> + wake_up_interruptible(&nxpdev->check_boot_sign_wait_q);
> + goto ret;
> + }
> +
> + if (!test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state))
> + goto ret;
> +
> + req = (struct v3_data_req *)skb_pull_data(skb, sizeof(struct v3_data_req));
> + if (!req || !nxpdev || !nxpdev->fw)
> + goto ret;
Can nxpdev really be NULL here?
> + nxp_send_ack(NXP_ACK_V3, hdev);
> +
> + if (!nxpdev->timeout_changed) {
> + nxpdev->timeout_changed = nxp_fw_change_timeout(hdev, req->len);
> + goto ret;
> + }
> +
> + if (!nxpdev->baudrate_changed) {
> + nxpdev->baudrate_changed = nxp_fw_change_baudrate(hdev, req->len);
> + if (nxpdev->baudrate_changed) {
> + serdev_device_set_baudrate(nxpdev->serdev,
> + HCI_NXP_SEC_BAUDRATE);
> + serdev_device_set_flow_control(nxpdev->serdev, 1);
> + nxpdev->current_baudrate = HCI_NXP_SEC_BAUDRATE;
> + }
> + goto ret;
> + }
> +
> + if (req->len == 0) {
> + bt_dev_info(hdev, "FW Downloaded Successfully: %zu bytes", nxpdev->fw->size);
> + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + wake_up_interruptible(&nxpdev->fw_dnld_done_wait_q);
> + goto ret;
> + }
> + if (req->error)
> + bt_dev_dbg(hdev, "FW Download received err 0x%02x from chip. Resending FW chunk.",
> + req->error);
It feels something might be off here, because regardless of req->error,
the same serdev_device_write_buf() will occur so the "Resending" part
of the comment doesn't make much of a sense.
> + if (req->offset < nxpdev->fw_v3_offset_correction) {
> + /* This scenario should ideally never occur.
> + * But if it ever does, FW is out of sync and
> + * needs a power cycle.
Use 80 columns.
> + */
> + bt_dev_err(hdev, "Something went wrong during FW download. Please power cycle and try again");
> + goto ret;
> + }
> +
> + serdev_device_write_buf(nxpdev->serdev,
> + nxpdev->fw->data + req->offset - nxpdev->fw_v3_offset_correction,
> + req->len);
> +
> +ret:
> + kfree_skb(skb);
> + return 0;
> +}
> +
> +static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + u32 new_baudrate = __cpu_to_le32(nxpdev->new_baudrate);
__le32 new_baudrate
> +static bool nxp_check_boot_sign(struct btnxpuart_dev *nxpdev)
> +{
> + int ret;
> +
> + serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE);
> + serdev_device_set_flow_control(nxpdev->serdev, 0);
> + set_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state);
> +
> + ret = wait_event_interruptible_timeout(nxpdev->check_boot_sign_wait_q,
> + !test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE,
> + &nxpdev->tx_state),
> + msecs_to_jiffies(1000));
> + if (ret == 0)
> + return false;
> + else
> + return true;
How does does this handle -ERESTARTSYS? But this runs in nxp_setup() so is
that even relevant (I don't know).
You can also rely on int -> bool conversion here instead of open-coding
with if () ...
> +}
> +
> +static int nxp_setup(struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + int err = 0;
> +
> + if (!nxpdev)
> + return 0;
How can this happen??
> + set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + init_waitqueue_head(&nxpdev->fw_dnld_done_wait_q);
> + init_waitqueue_head(&nxpdev->check_boot_sign_wait_q);
> +
> + if (nxp_check_boot_sign(nxpdev)) {
> + bt_dev_dbg(hdev, "Need FW Download.");
> + err = nxp_download_firmware(hdev);
> + if (err < 0)
> + return err;
> + } else {
> + bt_dev_dbg(hdev, "FW already running.");
> + clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
> + }
> +
> + serdev_device_set_flow_control(nxpdev->serdev, 1);
> + device_property_read_u32(&nxpdev->serdev->dev, "fw-init-baudrate",
> + &nxpdev->fw_init_baudrate);
> + if (!nxpdev->fw_init_baudrate)
> + nxpdev->fw_init_baudrate = FW_INIT_BAUDRATE;
> + serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate);
> + nxpdev->current_baudrate = nxpdev->fw_init_baudrate;
> +
> + if (nxpdev->current_baudrate != HCI_NXP_SEC_BAUDRATE) {
> + nxpdev->new_baudrate = HCI_NXP_SEC_BAUDRATE;
> + hci_cmd_sync_queue(hdev, nxp_set_baudrate_cmd, NULL, NULL);
> + }
> +
> + ps_init(hdev);
> +
> + return 0;
> +}
> +
> +static int nxp_enqueue(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> + struct ps_data *psdata = nxpdev->psdata;
> + struct hci_command_hdr *hdr;
> + u8 param[MAX_USER_PARAMS];
> +
> + if (!nxpdev || !psdata)
> + goto free_skb;
> +
> + /* if vendor commands are received from user space (e.g. hcitool), update
> + * driver flags accordingly and ask driver to re-send the command to FW.
> + */
> + if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT && !psdata->driver_sent_cmd) {
Should this !psdata->driver_sent_cmd do something else than end up into a
place labelled send_skb. Maybe return early (or free skb + return)?
There's a comment elsewhere stating: "set flag to prevent re-sending
command in nxp_enqueue."
> + if (!skb->len || skb->len > (MAX_USER_PARAMS + HCI_COMMAND_HDR_SIZE))
> + goto send_skb;
> +
> + hdr = (struct hci_command_hdr *)skb->data;
> + if (hdr->plen != (skb->len - HCI_COMMAND_HDR_SIZE))
> + goto send_skb;
Are these two gotos happily sending invalid skbs?
> + memcpy(param, skb->data + HCI_COMMAND_HDR_SIZE, hdr->plen);
> + switch (__le16_to_cpu(hdr->opcode)) {
> + case HCI_NXP_AUTO_SLEEP_MODE:
> + if (hdr->plen >= 1) {
> + if (param[0] == BT_PS_ENABLE)
> + psdata->ps_mode = PS_MODE_ENABLE;
> + else if (param[0] == BT_PS_DISABLE)
> + psdata->ps_mode = PS_MODE_DISABLE;
> + hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL);
> + goto free_skb;
> + }
> + break;
> + case HCI_NXP_WAKEUP_METHOD:
> + if (hdr->plen >= 4) {
> + psdata->c2h_wakeupmode = param[0];
> + psdata->c2h_wakeup_gpio = param[1];
> + switch (param[2]) {
> + case BT_CTRL_WAKEUP_METHOD_DSR:
> + psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR;
> + break;
> + case BT_CTRL_WAKEUP_METHOD_BREAK:
> + default:
> + psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK;
> + break;
> + }
> + hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL);
> + goto free_skb;
> + }
> + break;
> + case HCI_NXP_SET_OPER_SPEED:
> + if (hdr->plen == 4) {
> + nxpdev->new_baudrate = *((u32 *)param);
> + hci_cmd_sync_queue(hdev, nxp_set_baudrate_cmd, NULL, NULL);
> + goto free_skb;
> + }
> + break;
> + case HCI_NXP_IND_RESET:
> + if (hdr->plen == 1) {
> + hci_cmd_sync_queue(hdev, nxp_set_ind_reset, NULL, NULL);
> + goto free_skb;
> + }
> + break;
> + default:
> + break;
Okay to end up into place labelled send_skb?
> + }
> + }
> +
> +send_skb:
> + /* Prepend skb with frame type */
> + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1);
> + skb_queue_tail(&nxpdev->txq, skb);
> +
> + btnxpuart_tx_wakeup(nxpdev);
> +ret:
> + return 0;
> +
> +free_skb:
> + kfree_skb(skb);
> + goto ret;
> +}
> +
> +static struct sk_buff *nxp_dequeue(void *data)
> +{
> + struct btnxpuart_dev *nxpdev = (struct btnxpuart_dev *)data;
> +
> + ps_wakeup(nxpdev);
> + ps_start_timer(nxpdev);
> + return skb_dequeue(&nxpdev->txq);
> +}
> +static int btnxpuart_close(struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> +
> + if (!nxpdev)
> + return 0;
Can it be NULL here?
> + serdev_device_close(nxpdev->serdev);
> +
> + return 0;
> +}
> +
> +static int btnxpuart_flush(struct hci_dev *hdev)
> +{
> + struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
> +
> + if (!nxpdev)
> + return 0;
Ditto.
> + /* Flush any pending characters */
> + serdev_device_write_flush(nxpdev->serdev);
> + skb_queue_purge(&nxpdev->txq);
> +
> + cancel_work_sync(&nxpdev->tx_work);
> +
> + kfree_skb(nxpdev->rx_skb);
> + nxpdev->rx_skb = NULL;
> +
> + return 0;
> +}
> +
> +static const struct h4_recv_pkt nxp_recv_pkts[] = {
> + { H4_RECV_ACL, .recv = hci_recv_frame },
> + { H4_RECV_SCO, .recv = hci_recv_frame },
> + { H4_RECV_EVENT, .recv = hci_recv_frame },
> + { NXP_RECV_FW_REQ_V1, .recv = nxp_recv_fw_req_v1 },
> + { NXP_RECV_CHIP_VER_V3, .recv = nxp_recv_chip_ver_v3 },
> + { NXP_RECV_FW_REQ_V3, .recv = nxp_recv_fw_req_v3 },
> +};
> +
> +static bool is_valid_bootloader_signature(const u8 *data, size_t count)
> +{
> + if ((*data == NXP_V1_FW_REQ_PKT && count == sizeof(struct v1_data_req) + 1) ||
> + (*data == NXP_V3_FW_REQ_PKT && count == sizeof(struct v3_data_req) + 1) ||
> + (*data == NXP_V3_CHIP_VER_PKT && count == sizeof(struct v3_start_ind) + 1))
> + return true;
> + else
> + return false;
> +}
> +
> +static int btnxpuart_receive_buf(struct serdev_device *serdev, const u8 *data,
> + size_t count)
> +{
> + struct btnxpuart_dev *nxpdev = serdev_device_get_drvdata(serdev);
> +
> + if (test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state)) {
> + if (!is_valid_bootloader_signature(data, count)) {
Use && instead of cascading.
> + /* Unknown bootloader signature, skip without returning error */
> + return count;
> + }
> + }
> +
> + ps_start_timer(nxpdev);
> +
> + nxpdev->rx_skb = h4_recv_buf(nxpdev->hdev, nxpdev->rx_skb, data, count,
> + nxp_recv_pkts, ARRAY_SIZE(nxp_recv_pkts));
> + if (IS_ERR(nxpdev->rx_skb)) {
> + int err = PTR_ERR(nxpdev->rx_skb);
> +
> + bt_dev_err(nxpdev->hdev, "Frame reassembly failed (%d)", err);
> + nxpdev->rx_skb = NULL;
> + return err;
> + }
> + nxpdev->hdev->stat.byte_rx += count;
> + return count;
> +}
> +
> +static void btnxpuart_write_wakeup(struct serdev_device *serdev)
> +{
> + serdev_device_write_wakeup(serdev);
> +}
> +
> +static const struct serdev_device_ops btnxpuart_client_ops = {
> + .receive_buf = btnxpuart_receive_buf,
> + .write_wakeup = btnxpuart_write_wakeup,
> +};
> +
> +static int nxp_serdev_probe(struct serdev_device *serdev)
> +{
> + struct hci_dev *hdev;
> + struct btnxpuart_dev *nxpdev;
> +
> + nxpdev = devm_kzalloc(&serdev->dev, sizeof(*nxpdev), GFP_KERNEL);
> + if (!nxpdev)
> + return -ENOMEM;
> +
> + nxpdev->nxp_data = (struct btnxpuart_data *)device_get_match_data(&serdev->dev);
> +
> + nxpdev->serdev = serdev;
> + serdev_device_set_drvdata(serdev, nxpdev);
> +
> + serdev_device_set_client_ops(serdev, &btnxpuart_client_ops);
> +
> + INIT_WORK(&nxpdev->tx_work, btnxpuart_tx_work);
> + skb_queue_head_init(&nxpdev->txq);
> +
> + /* Initialize and register HCI device */
> + hdev = hci_alloc_dev();
> + if (!hdev) {
> + dev_err(&serdev->dev, "Can't allocate HCI device\n");
> + return -ENOMEM;
> + }
> +
> + nxpdev->hdev = hdev;
> +
> + hdev->bus = HCI_UART;
> + hci_set_drvdata(hdev, nxpdev);
> +
> + hdev->manufacturer = MANUFACTURER_NXP;
> + hdev->open = btnxpuart_open;
> + hdev->close = btnxpuart_close;
> + hdev->flush = btnxpuart_flush;
> + hdev->setup = nxp_setup;
> + hdev->send = nxp_enqueue;
> + SET_HCIDEV_DEV(hdev, &serdev->dev);
> +
> + if (hci_register_dev(hdev) < 0) {
> + dev_err(&serdev->dev, "Can't register HCI device\n");
> + hci_free_dev(hdev);
> + return -ENODEV;
> + }
> +
> + if (!ps_init_work(hdev))
> + ps_init_timer(hdev);
Eh, shouldn't this fail the probe on error?
But then, why is psdata not embedded into btnxpuart_dev but separately
allocated?
> + crc8_populate_msb(crc8_table, POLYNOMIAL8);
Is it okay to populate after HCI device is registered?
--
i.