From 7003f21e811e409df3f7ab630b19970bd89cfbd9 Mon Sep 17 00:00:00 2001 From: Justin Pfister <119864532+JJRPF@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:43:09 -0400 Subject: [PATCH] Bluetooth: hci_bcm4377: Add hardware reset recovery and post-init restoration When Bluetooth is stopped during heavy traffic (such as active LE scanning or discovery), transfer ring destruction commands to the Broadcom PCIe controller can time out and fail. Subsequent opens fail because rings cannot be created, leaving the device wedged. Implement hardware reset recovery using PCI Function-Level Reset (FLR) to re-initialize the PCIe controller and restart the firmware. Introduce a synchronous hdev->post_init hook to reload calibration data, regulatory PTB tables, and restore the device BD_ADDR after hardware reset, ensuring the controller is fully restored to operational state. Assisted-by: Antigravity:gemini-3.8-flash Signed-off-by: Justin Pfister <119864532+JJRPF@users.noreply.github.com> --- drivers/bluetooth/hci_bcm4377.c | 259 ++++++++++++++++++++++++++++---- 1 file changed, 229 insertions(+), 30 deletions(-) diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 0eb137a99add7c..5fe775766e7813 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include @@ -583,6 +585,10 @@ struct bcm4377_data { char vendor[BCM4377_OTP_MAX_PARAM_LEN]; const char *board_type; + bool needs_reset; + bool cal_needed; + bdaddr_t bdaddr; + struct completion event; struct bcm4377_context *ctx; @@ -1304,9 +1310,86 @@ static int bcm4378_send_ptb(struct bcm4377_data *bcm4377, return 0; } -static int bcm4377_hci_open(struct hci_dev *hdev) +static void bcm4377_disable_aspm(struct bcm4377_data *bcm4377); +static int bcm4377_init_cfg(struct bcm4377_data *bcm4377); +static int bcm4377_boot(struct bcm4377_data *bcm4377); +static int bcm4377_setup_rti(struct bcm4377_data *bcm4377); + +static int bcm4377_pci_reset(struct bcm4377_data *bcm4377) +{ + int ret; + int irq; + + dev_info(&bcm4377->pdev->dev, "performing hardware reset recovery\n"); + + irq = pci_irq_vector(bcm4377->pdev, 0); + if (irq > 0) + disable_irq(irq); + + if (bcm4377->hw->disable_aspm) + bcm4377_disable_aspm(bcm4377); + + ret = pci_reset_function(bcm4377->pdev); + if (ret) + dev_warn(&bcm4377->pdev->dev, + "function level reset failed with %d; trying to continue anyway\n", + ret); + + msleep(100); + + pci_set_master(bcm4377->pdev); + + ret = bcm4377_init_cfg(bcm4377); + if (ret) { + dev_err(&bcm4377->pdev->dev, + "failed to reinitialize PCI config: %d\n", ret); + goto out_irq; + } + + bcm4377->bootstage = 0; + bcm4377->rti_status = 0; + reinit_completion(&bcm4377->event); + + memset(bcm4377->ring_state, 0, sizeof(*bcm4377->ring_state)); + bcm4377->control_h2d_ring.generation = 0; + bcm4377->control_ack_ring.enabled = false; + bitmap_zero(bcm4377->control_h2d_ring.msgids, + bcm4377->control_h2d_ring.n_entries); + if (bcm4377->control_h2d_ring.events) + memset(bcm4377->control_h2d_ring.events, 0, + bcm4377->control_h2d_ring.n_entries * + sizeof(*bcm4377->control_h2d_ring.events)); + + if (irq > 0) + enable_irq(irq); + + ret = bcm4377_boot(bcm4377); + if (ret) { + dev_err(&bcm4377->pdev->dev, "failed to reboot firmware: %d\n", + ret); + return ret; + } + + ret = bcm4377_setup_rti(bcm4377); + if (ret) { + dev_err(&bcm4377->pdev->dev, "failed to re-setup RTI: %d\n", + ret); + return ret; + } + + bcm4377->needs_reset = false; + dev_info(&bcm4377->pdev->dev, + "hardware reset recovery completed successfully\n"); + return 0; + +out_irq: + if (irq > 0) + enable_irq(irq); + return ret; +} + +static int bcm4377_hci_open_rings(struct bcm4377_data *bcm4377) { - struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); int ret; dev_dbg(&bcm4377->pdev->dev, "creating rings\n"); @@ -1374,23 +1457,92 @@ static int bcm4377_hci_open(struct hci_dev *hdev) return ret; } +static int bcm4377_hci_open(struct hci_dev *hdev) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + int ret; + + if (bcm4377->needs_reset) { + dev_info(&bcm4377->pdev->dev, + "device marked for reset, reinitializing hardware before open\n"); + ret = bcm4377_pci_reset(bcm4377); + if (ret) + return ret; + bcm4377->cal_needed = true; + } + + ret = bcm4377_hci_open_rings(bcm4377); + if (ret && !bcm4377->needs_reset) { + dev_warn(&bcm4377->pdev->dev, + "creating rings failed (%d); attempting hardware reset recovery\n", + ret); + ret = bcm4377_pci_reset(bcm4377); + if (ret) { + bcm4377->needs_reset = true; + return ret; + } + bcm4377->cal_needed = true; + ret = bcm4377_hci_open_rings(bcm4377); + if (ret) + bcm4377->needs_reset = true; + } + + return ret; +} + static int bcm4377_hci_close(struct hci_dev *hdev) { struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); dev_dbg(&bcm4377->pdev->dev, "destroying rings in hci_close\n"); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_d2h_ring); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring); - bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring); + if (!bcm4377->needs_reset) { + if (bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_d2h_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->acl_h2d_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_d2h_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->sco_h2d_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_d2h_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_transfer_ring(bcm4377, &bcm4377->hci_h2d_ring)) + bcm4377->needs_reset = true; + + if (!bcm4377->needs_reset && + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_event_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_ack_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_event_ring)) + bcm4377->needs_reset = true; + if (!bcm4377->needs_reset && + bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_ack_ring)) + bcm4377->needs_reset = true; + } - bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_event_ring); - bcm4377_destroy_completion_ring(bcm4377, &bcm4377->sco_ack_ring); - bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_event_ring); - bcm4377_destroy_completion_ring(bcm4377, &bcm4377->hci_acl_ack_ring); + if (bcm4377->needs_reset) { + dev_warn(&bcm4377->pdev->dev, + "ring teardown failed; marking device for reset on next open\n"); + bcm4377->acl_d2h_ring.enabled = false; + bcm4377->acl_h2d_ring.enabled = false; + bcm4377->sco_d2h_ring.enabled = false; + bcm4377->sco_h2d_ring.enabled = false; + bcm4377->hci_d2h_ring.enabled = false; + bcm4377->hci_h2d_ring.enabled = false; + bcm4377->sco_event_ring.enabled = false; + bcm4377->sco_ack_ring.enabled = false; + bcm4377->hci_acl_event_ring.enabled = false; + bcm4377->hci_acl_ack_ring.enabled = false; + } return 0; } @@ -1467,6 +1619,67 @@ static int bcm4377_hci_setup(struct hci_dev *hdev) return bcm4377_check_bdaddr(bcm4377); } +static int bcm4377_hci_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + struct sk_buff *skb; + int err; + + skb = __hci_cmd_sync(hdev, 0xfc01, 6, bdaddr, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + dev_err(&bcm4377->pdev->dev, + "Change address command failed (%d)", err); + return err; + } + kfree_skb(skb); + + bacpy(&bcm4377->bdaddr, bdaddr); + return 0; +} + +static int bcm4377_hci_post_init(struct hci_dev *hdev) +{ + struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); + const bdaddr_t *bda = NULL; + int ret; + + if (!bcm4377->cal_needed) + return 0; + + dev_info(&bcm4377->pdev->dev, + "reloading calibration and PTB after hardware reset\n"); + + ret = bcm4377_hci_setup(hdev); + if (ret) { + dev_err(&bcm4377->pdev->dev, + "failed to reload calibration/PTB: %d\n", ret); + return ret; + } + + if (bacmp(&bcm4377->bdaddr, BDADDR_ANY)) + bda = &bcm4377->bdaddr; + else if (bacmp(&hdev->setup_addr, BDADDR_ANY)) + bda = &hdev->setup_addr; + else if (bacmp(&hdev->public_addr, BDADDR_ANY)) + bda = &hdev->public_addr; + + if (bda) { + dev_info(&bcm4377->pdev->dev, + "restoring BD_ADDR %pMR after hardware reset\n", bda); + ret = bcm4377_hci_set_bdaddr(hdev, bda); + if (ret) { + dev_err(&bcm4377->pdev->dev, + "failed to restore BD_ADDR: %d\n", ret); + return ret; + } + bacpy(&hdev->bdaddr, bda); + } + + bcm4377->cal_needed = false; + return 0; +} + static int bcm4377_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb) { struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); @@ -1504,24 +1717,6 @@ static int bcm4377_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb) return ret; } -static int bcm4377_hci_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr) -{ - struct bcm4377_data *bcm4377 = hci_get_drvdata(hdev); - struct sk_buff *skb; - int err; - - skb = __hci_cmd_sync(hdev, 0xfc01, 6, bdaddr, HCI_INIT_TIMEOUT); - if (IS_ERR(skb)) { - err = PTR_ERR(skb); - dev_err(&bcm4377->pdev->dev, - "Change address command failed (%d)", err); - return err; - } - kfree_skb(skb); - - return 0; -} - static int bcm4377_alloc_transfer_ring(struct bcm4377_data *bcm4377, struct bcm4377_transfer_ring *ring) { @@ -2241,6 +2436,9 @@ static int bcm4377_probe_of(struct bcm4377_data *bcm4377) return -ENOENT; } + device_property_read_u8_array(&bcm4377->pdev->dev, "local-bd-address", + (u8 *)&bcm4377->bdaddr, 6); + return 0; } @@ -2387,6 +2585,7 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) hdev->send = bcm4377_hci_send_frame; hdev->set_bdaddr = bcm4377_hci_set_bdaddr; hdev->setup = bcm4377_hci_setup; + hdev->post_init = bcm4377_hci_post_init; if (bcm4377->hw->broken_mws_transport_config) hci_set_quirk(hdev, HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG);