From: Neeraj Sanjay Kale Date: Mon, 14 Jul 2025 07:30:15 +0000 (+0530) Subject: Bluetooth: btnxpuart: Correct the Independent Reset handling after FW dump X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=634fd53a63be4798da356dbc7251046b713d992a;p=thirdparty%2Flinux.git Bluetooth: btnxpuart: Correct the Independent Reset handling after FW dump 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 Tested-by: Jean-Yves Salaün Signed-off-by: Luiz Augusto von Dentz --- diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 52e5cc3eb451d..a2a0a3937d178 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -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 */