]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
Bluetooth: btintel_pcie: Support Function level reset
authorChandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Tue, 10 Jun 2025 14:00:38 +0000 (19:30 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 23 Jul 2025 14:23:57 +0000 (10:23 -0400)
The driver supports Function Level Reset (FLR) to recover the controller
upon hardware exceptions or hci command timeouts. FLR is triggered only
when no prior reset has occurred within the retry window, with a maximum
of one FLR allowed within this window.

This patch is tested by,
echo 1 >  /sys/class/bluetooth/hciX/reset

Signed-off-by: Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
Signed-off-by: Kiran K <kiran.k@intel.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
drivers/bluetooth/btintel_pcie.c
drivers/bluetooth/btintel_pcie.h

index f4e3fb54fe766d208a74bd3adfe6c04d113e7913..628b3ea7f6f67c14672c58cf33b9a6e186575bba 100644 (file)
@@ -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_WINDOW_SECS         5
+#define BTINTEL_PCIE_FLR_MAX_RETRY     1
+
 /* 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
@@ -1932,6 +1950,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
@@ -2192,9 +2213,196 @@ 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;
+       }
+
+       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) {
+               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_WINDOW_SECS &&
+                  data->restart_count <= BTINTEL_PCIE_FLR_MAX_RETRY) {
+               data->restart_count++;
+               bt_dev_dbg(hdev, "Flr triggered within the max retry time so increment the restart_count: %d",
+                          data->restart_count);
+       } else if (retry_window > BTINTEL_PCIE_RESET_WINDOW_SECS) {
+               data->last_error = 0;
+               data->restart_count = 0;
+               bt_dev_dbg(hdev, "Flr triggered out of the retry window, so reset counters");
+       }
+}
+
+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_work(&data->rx_work);
+       flush_work(&data->hdev->dump.dump_rx);
+
+       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;
+
+       if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags))
+               return;
+
+       removal = kzalloc(sizeof(*removal), GFP_ATOMIC);
+       if (!removal)
+               return;
+
+       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 (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS &&
+           data->restart_count >= BTINTEL_PCIE_FLR_MAX_RETRY) {
+               bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d",
+                          BTINTEL_PCIE_FLR_MAX_RETRY, data->restart_count);
+               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;
@@ -2216,9 +2424,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) {
@@ -2366,7 +2575,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@intel.com>");
 MODULE_DESCRIPTION("Intel Bluetooth PCIe transport driver ver " VERSION);
index 7dad4523236c9fcc1fdb0d057ce902c16a5d8029..0fa876c5b954a0bfa165185846f66024dd21f658 100644 (file)
@@ -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 {