]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
Bluetooth: btnxpuart: Correct the Independent Reset handling after FW dump
authorNeeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Mon, 14 Jul 2025 07:30:15 +0000 (13:00 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 23 Jul 2025 14:32:06 +0000 (10:32 -0400)
This adds proper handling for the independent reset command sent by the
driver after FW dump is complete.

In normal scenario, the independent reset vendor command gives a success
response before jumping to bootcode.

However, when FW goes in a bad state, and sends out FW dump packets, the
independent reset command does not get any response from the controller.

[  159.807732] Bluetooth: hci0: ==== Start FW dump ===
[  180.759060] Bluetooth: hci0: ==== FW dump complete ===
[  182.779208] Bluetooth: hci0: command 0xfcfc tx timeout
[  183.364974] Bluetooth: hci0: ChipID: 7601, Version: 0
[  183.368490] Bluetooth: hci0: Request Firmware: nxp/uartspi_n61x_v1.bin.se
[  184.679977] Bluetooth: hci0: FW Download Complete: 417064 bytes
[  187.963102] Bluetooth: hci0: Opcode 0x0c03 failed: -110

As a fix for such scenario, the independent reset vendor command is sent
using the __hci_cmd_send() API, which does not expect any response for
vendor commands.

__hci_cmd_send is non blocking, so before the tx_work is scheduled, it
sometimes gets canceled and 3F|FC command is never sent. Adding a small
delay after __hci_cmd_send allows the command to be sent to the
controller.

Signed-off-by: Neeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Tested-by: Jean-Yves Salaün <jean-yves.salaun@nxp.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
drivers/bluetooth/btnxpuart.c

index 52e5cc3eb451d5eceb2b042b5b87984500006efe..a2a0a3937d17896d150d7082208b765242afb3ab 100644 (file)
@@ -370,17 +370,26 @@ static u8 crc8_table[CRC8_TABLE_SIZE];
 
 static struct sk_buff *nxp_drv_send_cmd(struct hci_dev *hdev, u16 opcode,
                                        u32 plen,
-                                       void *param)
+                                       void *param,
+                                       bool resp)
 {
        struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
        struct ps_data *psdata = &nxpdev->psdata;
-       struct sk_buff *skb;
+       struct sk_buff *skb = NULL;
 
        /* set flag to prevent nxp_enqueue from parsing values from this command and
         * calling hci_cmd_sync_queue() again.
         */
        psdata->driver_sent_cmd = true;
-       skb = __hci_cmd_sync(hdev, opcode, plen, param, HCI_CMD_TIMEOUT);
+       if (resp) {
+               skb = __hci_cmd_sync(hdev, opcode, plen, param, HCI_CMD_TIMEOUT);
+       } else {
+               __hci_cmd_send(hdev, opcode, plen, param);
+               /* Allow command to be sent before tx_work is cancelled
+                * by btnxpuart_flush()
+                */
+               msleep(20);
+       }
        psdata->driver_sent_cmd = false;
 
        return skb;
@@ -600,7 +609,8 @@ static int send_ps_cmd(struct hci_dev *hdev, void *data)
                pcmd.ps_cmd = BT_PS_DISABLE;
        pcmd.c2h_ps_interval = __cpu_to_le16(psdata->c2h_ps_interval);
 
-       skb = nxp_drv_send_cmd(hdev, HCI_NXP_AUTO_SLEEP_MODE, sizeof(pcmd), &pcmd);
+       skb = nxp_drv_send_cmd(hdev, HCI_NXP_AUTO_SLEEP_MODE, sizeof(pcmd),
+                              &pcmd, true);
        if (IS_ERR(skb)) {
                bt_dev_err(hdev, "Setting Power Save mode failed (%ld)", PTR_ERR(skb));
                return PTR_ERR(skb);
@@ -649,7 +659,8 @@ static int send_wakeup_method_cmd(struct hci_dev *hdev, void *data)
                break;
        }
 
-       skb = nxp_drv_send_cmd(hdev, HCI_NXP_WAKEUP_METHOD, sizeof(pcmd), &pcmd);
+       skb = nxp_drv_send_cmd(hdev, HCI_NXP_WAKEUP_METHOD, sizeof(pcmd),
+                              &pcmd, true);
        if (IS_ERR(skb)) {
                bt_dev_err(hdev, "Setting wake-up method failed (%ld)", PTR_ERR(skb));
                return PTR_ERR(skb);
@@ -1275,7 +1286,8 @@ static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data)
        if (!psdata)
                return 0;
 
-       skb = nxp_drv_send_cmd(hdev, HCI_NXP_SET_OPER_SPEED, 4, (u8 *)&new_baudrate);
+       skb = nxp_drv_send_cmd(hdev, HCI_NXP_SET_OPER_SPEED, 4,
+                              (u8 *)&new_baudrate, true);
        if (IS_ERR(skb)) {
                bt_dev_err(hdev, "Setting baudrate failed (%ld)", PTR_ERR(skb));
                return PTR_ERR(skb);
@@ -1333,7 +1345,7 @@ static void nxp_coredump(struct hci_dev *hdev)
        struct sk_buff *skb;
        u8 pcmd = 2;
 
-       skb = nxp_drv_send_cmd(hdev, HCI_NXP_TRIGGER_DUMP, 1, &pcmd);
+       skb = nxp_drv_send_cmd(hdev, HCI_NXP_TRIGGER_DUMP, 1, &pcmd, true);
        if (IS_ERR(skb))
                bt_dev_err(hdev, "Failed to trigger FW Dump. (%ld)", PTR_ERR(skb));
        else
@@ -1375,7 +1387,6 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb)
 
        if (buf_len == 0) {
                bt_dev_warn(hdev, "==== FW dump complete ===");
-               clear_bit(BTNXPUART_FW_DUMP_IN_PROGRESS, &nxpdev->tx_state);
                hci_devcd_complete(hdev);
                nxp_set_ind_reset(hdev, NULL);
        }
@@ -1489,7 +1500,13 @@ static int nxp_shutdown(struct hci_dev *hdev)
        u8 pcmd = 0;
 
        if (ind_reset_in_progress(nxpdev)) {
-               skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1, &pcmd);
+               if (test_and_clear_bit(BTNXPUART_FW_DUMP_IN_PROGRESS,
+                                      &nxpdev->tx_state))
+                       skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1,
+                                              &pcmd, false);
+               else
+                       skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1,
+                                              &pcmd, true);
                serdev_device_set_flow_control(nxpdev->serdev, false);
                set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
                /* HCI_NXP_IND_RESET command may not returns any response */