+ linux-pci@xxxxxxxxxxxxxxx, bhelgaas@xxxxxxxxxx >-----Original Message----- >From: K, Kiran <kiran.k@xxxxxxxxx> >Sent: Sunday, May 25, 2025 3:46 PM >To: linux-bluetooth@xxxxxxxxxxxxxxx >Cc: Srivatsa, Ravishankar <ravishankar.srivatsa@xxxxxxxxx>; Tumkur Narayan, >Chethan <chethan.tumkur.narayan@xxxxxxxxx>; Devegowda, Chandrashekar ><chandrashekar.devegowda@xxxxxxxxx>; Satija, Vijay <vijay.satija@xxxxxxxxx>; >K, Kiran <kiran.k@xxxxxxxxx> >Subject: [PATCH v2 2/2] Bluetooth: btintel_pcie: Support Function level reset > >From: Chandrashekar Devegowda <chandrashekar.devegowda@xxxxxxxxx> > >Support function level reset (flr) on hardware exception to recover controller. >Driver also implements the back-off time of 5 seconds and the maximum >number of retries are limited to 5 before giving up. > >Signed-off-by: Chandrashekar Devegowda ><chandrashekar.devegowda@xxxxxxxxx> >Signed-off-by: Kiran K <kiran.k@xxxxxxxxx> >--- > drivers/bluetooth/btintel_pcie.c | 234 ++++++++++++++++++++++++++++++- > drivers/bluetooth/btintel_pcie.h | 4 +- > 2 files changed, 235 insertions(+), 3 deletions(-) > >diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c >index 74a957784525..6779a2cfa75d 100644 >--- a/drivers/bluetooth/btintel_pcie.c >+++ b/drivers/bluetooth/btintel_pcie.c >@@ -41,6 +41,13 @@ static const struct pci_device_id btintel_pcie_table[] = { >}; MODULE_DEVICE_TABLE(pci, btintel_pcie_table); > >+struct btintel_pcie_dev_restart_data { >+ struct list_head list; >+ u8 restart_count; >+ time64_t last_error; >+ char name[]; >+}; >+ > /* Intel PCIe uses 4 bytes of HCI type instead of 1 byte BT SIG HCI type */ > #define BTINTEL_PCIE_HCI_TYPE_LEN 4 > #define BTINTEL_PCIE_HCI_CMD_PKT 0x00000001 >@@ -62,6 +69,9 @@ MODULE_DEVICE_TABLE(pci, btintel_pcie_table); > #define BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER 0x17A2 > #define BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT 0x1E61 > >+#define BTINTEL_PCIE_RESET_OK_TIME_SECS 5 >+#define BTINTEL_PCIE_FLR_RESET_MAX_RETRY 5 >+ > /* Alive interrupt context */ > enum { > BTINTEL_PCIE_ROM, >@@ -99,6 +109,14 @@ struct btintel_pcie_dbgc_ctxt { > struct btintel_pcie_dbgc_ctxt_buf >bufs[BTINTEL_PCIE_DBGC_BUFFER_COUNT]; > }; > >+struct btintel_pcie_removal { >+ struct pci_dev *pdev; >+ struct work_struct work; >+}; >+ >+static LIST_HEAD(btintel_pcie_restart_data_list); >+static DEFINE_SPINLOCK(btintel_pcie_restart_data_lock); >+ > /* This function initializes the memory for DBGC buffers and formats the > * DBGC fragment which consists header info and DBGC buffer's LSB, MSB and > * size as the payload >@@ -1927,6 +1945,9 @@ static int btintel_pcie_send_frame(struct hci_dev >*hdev, > u32 type; > u32 old_ctxt; > >+ if (test_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags)) >+ return -ENODEV; >+ > /* Due to the fw limitation, the type header of the packet should be > * 4 bytes unlike 1 byte for UART. In UART, the firmware can read > * the first byte to get the packet type and redirect the rest of data @@ >-2187,9 +2208,204 @@ static int btintel_pcie_setup(struct hci_dev *hdev) > } > btintel_pcie_start_rx(data); > } >+ >+ if (!err) >+ set_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags); > return err; > } > >+static struct btintel_pcie_dev_restart_data >*btintel_pcie_get_restart_data(struct pci_dev *pdev, >+ struct >device *dev) >+{ >+ struct btintel_pcie_dev_restart_data *tmp, *data = NULL; >+ const char *name = pci_name(pdev); >+ struct hci_dev *hdev = to_hci_dev(dev); >+ >+ spin_lock(&btintel_pcie_restart_data_lock); >+ list_for_each_entry(tmp, &btintel_pcie_restart_data_list, list) { >+ if (strcmp(tmp->name, name)) >+ continue; >+ data = tmp; >+ break; >+ } >+ spin_unlock(&btintel_pcie_restart_data_lock); >+ >+ if (data) { >+ bt_dev_dbg(hdev, "Found restart data for BDF:%s", data- >>name); >+ return data; >+ } >+ >+ /* First time allocate */ >+ data = kzalloc(struct_size(data, name, strlen(name) + 1), GFP_ATOMIC); >+ if (!data) >+ return NULL; >+ >+ strscpy_pad(data->name, name, strlen(name) + 1); >+ spin_lock(&btintel_pcie_restart_data_lock); >+ list_add_tail(&data->list, &btintel_pcie_restart_data_list); >+ spin_unlock(&btintel_pcie_restart_data_lock); >+ >+ return data; >+} >+ >+static void btintel_pcie_free_restart_list(void) >+{ >+ struct btintel_pcie_dev_restart_data *tmp; >+ >+ while ((tmp = list_first_entry_or_null(&btintel_pcie_restart_data_list, >+ typeof(*tmp), list))) { >+ list_del(&tmp->list); >+ kfree(tmp); >+ } >+} >+ >+static void btintel_pcie_inc_restart_count(struct pci_dev *pdev, >+ struct device *dev) >+{ >+ struct btintel_pcie_dev_restart_data *data; >+ struct hci_dev *hdev = to_hci_dev(dev); >+ time64_t retry_window; >+ >+ data = btintel_pcie_get_restart_data(pdev, dev); >+ if (!data) >+ return; >+ >+ retry_window = ktime_get_boottime_seconds() - data->last_error; >+ if (data->restart_count == 0) { >+ /* First iteration initialise the time and counter */ >+ data->last_error = ktime_get_boottime_seconds(); >+ data->restart_count++; >+ bt_dev_dbg(hdev, "First iteration initialise. last_error:%lld >seconds restart_count:%d", >+ data->last_error, data->restart_count); >+ } else if (retry_window < BTINTEL_PCIE_RESET_OK_TIME_SECS && >+ data->restart_count <= >BTINTEL_PCIE_FLR_RESET_MAX_RETRY) { >+ /* FLR triggered still within the Max retry time so >+ * increment the counter >+ */ >+ data->restart_count++; >+ bt_dev_dbg(hdev, "flr triggered still within the max retry time >so increment the restart_count:%d", >+ data->restart_count); >+ } else if (retry_window > BTINTEL_PCIE_RESET_OK_TIME_SECS) { >+ /* FLR triggered out of the retry window so reset */ >+ bt_dev_dbg(hdev, "flr triggered out of retry window. >last_error:%lld seconds restart_count:%d", >+ data->last_error, data->restart_count); >+ data->last_error = 0; >+ data->restart_count = 0; >+ } >+} >+ >+static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data); >+ >+static void btintel_pcie_removal_work(struct work_struct *wk) { >+ struct btintel_pcie_removal *removal = >+ container_of(wk, struct btintel_pcie_removal, work); >+ struct pci_dev *pdev = removal->pdev; >+ struct btintel_pcie_data *data; >+ int err; >+ >+ pci_lock_rescan_remove(); >+ >+ if (!pdev->bus) >+ goto error; >+ >+ data = pci_get_drvdata(pdev); >+ >+ btintel_pcie_disable_interrupts(data); >+ btintel_pcie_synchronize_irqs(data); >+ flush_workqueue(data->workqueue); >+ >+ bt_dev_dbg(data->hdev, "Release bluetooth interface"); >+ btintel_pcie_release_hdev(data); >+ >+ err = pci_reset_function(pdev); >+ if (err) { >+ BT_ERR("Failed resetting the pcie device (%d)", err); >+ goto error; >+ } >+ >+ btintel_pcie_enable_interrupts(data); >+ btintel_pcie_config_msix(data); >+ >+ err = btintel_pcie_enable_bt(data); >+ if (err) { >+ BT_ERR("Failed to enable bluetooth hardware after reset >(%d)", >+ err); >+ goto error; >+ } >+ >+ btintel_pcie_reset_ia(data); >+ btintel_pcie_start_rx(data); >+ data->flags = 0; >+ >+ err = btintel_pcie_setup_hdev(data); >+ if (err) { >+ BT_ERR("Failed registering hdev (%d)", err); >+ goto error; >+ } >+error: >+ pci_dev_put(pdev); >+ pci_unlock_rescan_remove(); >+ kfree(removal); >+} >+ >+static void btintel_pcie_reset(struct hci_dev *hdev) { >+ struct btintel_pcie_removal *removal; >+ struct btintel_pcie_data *data; >+ >+ data = hci_get_drvdata(hdev); >+ >+ if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) >+ return; >+ >+ removal = kzalloc(sizeof(*removal), GFP_ATOMIC); >+ if (!removal) >+ return; >+ >+ flush_work(&data->rx_work); >+ flush_work(&hdev->dump.dump_rx); >+ >+ removal->pdev = data->pdev; >+ INIT_WORK(&removal->work, btintel_pcie_removal_work); >+ pci_dev_get(removal->pdev); >+ schedule_work(&removal->work); >+} >+ >+static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) { >+ struct btintel_pcie_dev_restart_data *data; >+ struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); >+ struct pci_dev *pdev = dev_data->pdev; >+ time64_t retry_window; >+ >+ if (code == 0x13) { >+ bt_dev_err(hdev, "Encountered top exception"); >+ return; >+ } >+ >+ data = btintel_pcie_get_restart_data(pdev, &hdev->dev); >+ if (!data) >+ return; >+ >+ retry_window = ktime_get_boottime_seconds() - data->last_error; >+ >+ /* If within 5 seconds max 5 attempts have already been made >+ * then stop any more retry and indicate to user for cold boot >+ */ >+ if (retry_window < BTINTEL_PCIE_RESET_OK_TIME_SECS && >+ data->restart_count >= BTINTEL_PCIE_FLR_RESET_MAX_RETRY) { >+ bt_dev_err(hdev, "Max recovery retries(%d) exhausted.", >+ BTINTEL_PCIE_FLR_RESET_MAX_RETRY); >+ bt_dev_dbg(hdev, "Boot time:%lld seconds first_flr at:%lld >seconds restart_count:%d", >+ ktime_get_boottime_seconds(), data->last_error, >+ data->restart_count); >+ return; >+ } >+ btintel_pcie_inc_restart_count(pdev, &hdev->dev); >+ btintel_pcie_reset(hdev); >+} >+ > static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) { > int err; >@@ -2211,9 +2427,10 @@ static int btintel_pcie_setup_hdev(struct >btintel_pcie_data *data) > hdev->send = btintel_pcie_send_frame; > hdev->setup = btintel_pcie_setup; > hdev->shutdown = btintel_shutdown_combined; >- hdev->hw_error = btintel_hw_error; >+ hdev->hw_error = btintel_pcie_hw_error; > hdev->set_diag = btintel_set_diag; > hdev->set_bdaddr = btintel_set_bdaddr; >+ hdev->reset = btintel_pcie_reset; > > err = hci_register_dev(hdev); > if (err < 0) { >@@ -2361,7 +2578,20 @@ static struct pci_driver btintel_pcie_driver = { > .driver.coredump = btintel_pcie_coredump #endif }; - >module_pci_driver(btintel_pcie_driver); >+ >+static int __init btintel_pcie_init(void) { >+ return pci_register_driver(&btintel_pcie_driver); >+} >+ >+static void __exit btintel_pcie_exit(void) { >+ pci_unregister_driver(&btintel_pcie_driver); >+ btintel_pcie_free_restart_list(); >+} >+ >+module_init(btintel_pcie_init); >+module_exit(btintel_pcie_exit); > > MODULE_AUTHOR("Tedd Ho-Jeong An <tedd.an@xxxxxxxxx>"); >MODULE_DESCRIPTION("Intel Bluetooth PCIe transport driver ver " VERSION); >diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h >index 21b964b15c1c..62b6bcdaf10f 100644 >--- a/drivers/bluetooth/btintel_pcie.h >+++ b/drivers/bluetooth/btintel_pcie.h >@@ -117,7 +117,9 @@ enum { > enum { > BTINTEL_PCIE_CORE_HALTED, > BTINTEL_PCIE_HWEXP_INPROGRESS, >- BTINTEL_PCIE_COREDUMP_INPROGRESS >+ BTINTEL_PCIE_COREDUMP_INPROGRESS, >+ BTINTEL_PCIE_RECOVERY_IN_PROGRESS, >+ BTINTEL_PCIE_SETUP_DONE > }; > > enum btintel_pcie_tlv_type { >-- >2.43.0