Skip to content

Commit

Permalink
Bluetooth: btnxpuart: Add support for HCI coredump feature
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Neeraj Sanjay Kale authored and Luiz Augusto von Dentz committed Mar 25, 2025
1 parent 6fca678 commit 998e447
Showing 1 changed file with 132 additions and 15 deletions.
147 changes: 132 additions & 15 deletions drivers/bluetooth/btnxpuart.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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 },
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
},
};

Expand Down

0 comments on commit 998e447

Please sign in to comment.