Bluetooth: btnxpuart: Add support for HCI coredump feature

This adds support for Bluetooth Coredump feature to BTNXPUART driver to
collect FW dumps on demand, or in case FW goes in a bad state.

To trigger manual FW dump, following command can be used:
echo 1 > /sys/class/bluetooth/hci0/device/coredump

Once FW dump is complete, it can be written to a file:
cat /sys/class/bluetooth/hci0/devcoredump/data > fw_dump

While FW dump is in progress, any HCI command will return -EBUSY.

After FW dump is complete, driver will give HCI_NXP_IND_RESET command
which soft-resets the chip, allowing FW re-download.

Signed-off-by: Neeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Neeraj Sanjay Kale 2025-02-27 23:56:20 +05:30 committed by Luiz Augusto von Dentz
parent 6fca6781d1
commit 998e447f44
1 changed files with 132 additions and 15 deletions

View File

@ -31,6 +31,7 @@
#define BTNXPUART_SERDEV_OPEN 4
#define BTNXPUART_IR_IN_PROGRESS 5
#define BTNXPUART_FW_DOWNLOAD_ABORT 6
#define BTNXPUART_FW_DUMP_IN_PROGRESS 7
/* NXP HW err codes */
#define BTNXPUART_IR_HW_ERR 0xb0
@ -106,6 +107,8 @@
#define HCI_NXP_SET_OPER_SPEED 0xfc09
/* Bluetooth vendor command: Independent Reset */
#define HCI_NXP_IND_RESET 0xfcfc
/* Bluetooth vendor command: Trigger FW dump */
#define HCI_NXP_TRIGGER_DUMP 0xfe91
/* Bluetooth Power State : Vendor cmd params */
#define BT_PS_ENABLE 0x02
@ -310,6 +313,16 @@ union nxp_v3_rx_timeout_nak_u {
u8 buf[6];
};
/* FW dump */
#define NXP_FW_DUMP_SIZE (1024 * 1000)
struct nxp_fw_dump_hdr {
__le16 seq_num;
__le16 reserved;
__le16 buf_type;
__le16 buf_len;
};
static u8 crc8_table[CRC8_TABLE_SIZE];
/* Default configurations */
@ -774,6 +787,16 @@ static bool is_fw_downloading(struct btnxpuart_dev *nxpdev)
return test_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
}
static bool ind_reset_in_progress(struct btnxpuart_dev *nxpdev)
{
return test_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state);
}
static bool fw_dump_in_progress(struct btnxpuart_dev *nxpdev)
{
return test_bit(BTNXPUART_FW_DUMP_IN_PROGRESS, &nxpdev->tx_state);
}
static bool process_boot_signature(struct btnxpuart_dev *nxpdev)
{
if (test_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state)) {
@ -1175,7 +1198,7 @@ static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data)
static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev)
{
serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE);
if (test_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state))
if (ind_reset_in_progress(nxpdev))
serdev_device_set_flow_control(nxpdev->serdev, false);
else
serdev_device_set_flow_control(nxpdev->serdev, true);
@ -1204,6 +1227,73 @@ static int nxp_set_ind_reset(struct hci_dev *hdev, void *data)
return hci_recv_frame(hdev, skb);
}
/* Firmware dump */
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);
if (!IS_ERR(skb))
kfree_skb(skb);
}
static void nxp_coredump_hdr(struct hci_dev *hdev, struct sk_buff *skb)
{
/* Nothing to be added in FW dump header */
}
static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb)
{
struct hci_acl_hdr *acl_hdr = (struct hci_acl_hdr *)skb_pull_data(skb,
sizeof(*acl_hdr));
struct nxp_fw_dump_hdr *fw_dump_hdr = (struct nxp_fw_dump_hdr *)skb->data;
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
__u16 seq_num = __le16_to_cpu(fw_dump_hdr->seq_num);
__u16 buf_len = __le16_to_cpu(fw_dump_hdr->buf_len);
int err;
if (seq_num == 0x0001) {
if (test_and_set_bit(BTNXPUART_FW_DUMP_IN_PROGRESS, &nxpdev->tx_state)) {
bt_dev_err(hdev, "FW dump already in progress");
goto free_skb;
}
bt_dev_warn(hdev, "==== Start FW dump ===");
err = hci_devcd_init(hdev, NXP_FW_DUMP_SIZE);
if (err < 0)
goto free_skb;
schedule_delayed_work(&hdev->dump.dump_timeout,
msecs_to_jiffies(20000));
}
err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC));
if (err < 0)
goto free_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);
}
free_skb:
kfree_skb(skb);
return 0;
}
static int nxp_recv_acl_pkt(struct hci_dev *hdev, struct sk_buff *skb)
{
__u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle);
/* FW dump chunks are ACL packets with conn handle 0xfff */
if ((handle & 0x0FFF) == 0xFFF)
return nxp_process_fw_dump(hdev, skb);
else
return hci_recv_frame(hdev, skb);
}
/* NXP protocol */
static int nxp_setup(struct hci_dev *hdev)
{
@ -1265,20 +1355,15 @@ static int nxp_shutdown(struct hci_dev *hdev)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
struct sk_buff *skb;
u8 *status;
u8 pcmd = 0;
if (test_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state)) {
if (ind_reset_in_progress(nxpdev)) {
skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1, &pcmd);
if (IS_ERR(skb))
return PTR_ERR(skb);
status = skb_pull_data(skb, 1);
if (status) {
serdev_device_set_flow_control(nxpdev->serdev, false);
set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
}
kfree_skb(skb);
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 */
if (!IS_ERR(skb))
kfree_skb(skb);
} else if (nxpdev->current_baudrate != nxpdev->fw_init_baudrate) {
nxpdev->new_baudrate = nxpdev->fw_init_baudrate;
nxp_set_baudrate_cmd(hdev, NULL);
@ -1298,6 +1383,16 @@ static bool nxp_wakeup(struct hci_dev *hdev)
return false;
}
static void nxp_reset(struct hci_dev *hdev)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
if (!ind_reset_in_progress(nxpdev) && !fw_dump_in_progress(nxpdev)) {
bt_dev_dbg(hdev, "CMD Timeout detected. Resetting.");
nxp_set_ind_reset(hdev, NULL);
}
}
static int btnxpuart_queue_skb(struct hci_dev *hdev, struct sk_buff *skb)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
@ -1318,6 +1413,9 @@ static int nxp_enqueue(struct hci_dev *hdev, struct sk_buff *skb)
struct wakeup_cmd_payload wakeup_parm;
__le32 baudrate_parm;
if (fw_dump_in_progress(nxpdev))
return -EBUSY;
/* 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.
* In case the payload for any command does not match expected payload
@ -1486,7 +1584,7 @@ static int btnxpuart_flush(struct hci_dev *hdev)
}
static const struct h4_recv_pkt nxp_recv_pkts[] = {
{ H4_RECV_ACL, .recv = hci_recv_frame },
{ H4_RECV_ACL, .recv = nxp_recv_acl_pkt },
{ H4_RECV_SCO, .recv = hci_recv_frame },
{ H4_RECV_EVENT, .recv = hci_recv_frame },
{ H4_RECV_ISO, .recv = hci_recv_frame },
@ -1508,11 +1606,13 @@ static size_t btnxpuart_receive_buf(struct serdev_device *serdev,
if (IS_ERR(nxpdev->rx_skb)) {
int err = PTR_ERR(nxpdev->rx_skb);
/* Safe to ignore out-of-sync bootloader signatures */
if (!is_fw_downloading(nxpdev))
if (!is_fw_downloading(nxpdev) &&
!ind_reset_in_progress(nxpdev))
bt_dev_err(nxpdev->hdev, "Frame reassembly failed (%d)", err);
return count;
}
if (!is_fw_downloading(nxpdev))
if (!is_fw_downloading(nxpdev) &&
!ind_reset_in_progress(nxpdev))
nxpdev->hdev->stat.byte_rx += count;
return count;
}
@ -1580,6 +1680,7 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
hdev->hw_error = nxp_hw_err;
hdev->shutdown = nxp_shutdown;
hdev->wakeup = nxp_wakeup;
hdev->reset = nxp_reset;
SET_HCIDEV_DEV(hdev, &serdev->dev);
if (hci_register_dev(hdev) < 0) {
@ -1590,6 +1691,8 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
if (ps_setup(hdev))
goto probe_fail;
hci_devcd_register(hdev, nxp_coredump, nxp_coredump_hdr, NULL);
return 0;
probe_fail:
@ -1641,6 +1744,17 @@ static int nxp_serdev_resume(struct device *dev)
}
#endif
#ifdef CONFIG_DEV_COREDUMP
static void nxp_serdev_coredump(struct device *dev)
{
struct btnxpuart_dev *nxpdev = dev_get_drvdata(dev);
struct hci_dev *hdev = nxpdev->hdev;
if (hdev->dump.coredump)
hdev->dump.coredump(hdev);
}
#endif
static struct btnxpuart_data w8987_data __maybe_unused = {
.helper_fw_name = NULL,
.fw_name = FIRMWARE_W8987,
@ -1671,6 +1785,9 @@ static struct serdev_device_driver nxp_serdev_driver = {
.name = "btnxpuart",
.of_match_table = of_match_ptr(nxpuart_of_match_table),
.pm = &nxp_pm_ops,
#ifdef CONFIG_DEV_COREDUMP
.coredump = nxp_serdev_coredump,
#endif
},
};