linux-next: manual merge of the bluetooth tree with the origin tree
From: Mark Brown
Date: Fri Jul 17 2026 - 10:41:42 EST
Hi all,
Today's linux-next merge of the bluetooth tree got a conflict in:
drivers/bluetooth/btintel_pcie.c
between commit:
9c36951474d8e ("Bluetooth: btintel_pcie: Refactor FLR to use device_reprobe()")
from the origin tree and commit:
603b91aeb20a2 ("Bluetooth: btintel_pcie: Refactor FLR to use device_reprobe()")
ca75417ab1793 ("Bluetooth: btintel_pcie: split coredump worker into per-trigger works")
from the bluetooth tree.
I fixed it up (see below) and can carry the fix as necessary. This
is now fixed as far as linux-next is concerned, but any non trivial
conflicts should be mentioned to your upstream maintainer when your tree
is submitted for merging. You may also want to consider cooperating
with the maintainer of the conflicting tree to minimise any particularly
complex conflicts.
diff --combined drivers/bluetooth/btintel_pcie.c
index 2b7231be5973d,2e28847263ab0..0000000000000
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@@ -1446,72 -1446,134 +1446,134 @@@ exit_on_error
return err;
}
+ /* Queue a coredump dump_traces() pass.
+ *
+ * Returns true if a new coredump was queued, false if one was already
+ * in-flight (the BTINTEL_PCIE_COREDUMP_INPROGRESS bit serves as the
+ * single-writer guard for the @coredump_work item) or the workqueue is
+ * disabled (reset / remove in progress).
+ *
+ * Always queue this AFTER any companion event-reader work (hwexp /
+ * fwtrigger) so that, on the ordered @dump_workqueue, the event reader
+ * runs first and populates dmp_hdr.event_type / event_id before
+ * dump_traces consumes them.
+ */
+ static bool btintel_pcie_queue_coredump(struct btintel_pcie_data *data,
+ u16 trigger_reason)
+ {
+ if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
+ return false;
+
+ data->dmp_hdr.trigger_reason = trigger_reason;
+
+ if (queue_work(data->dump_workqueue, &data->coredump_work))
+ return true;
+
+ /* Workqueue is disabled (reset/remove drained it). Release the
+ * guard so a later trigger, after re-probe, can succeed.
+ */
+ clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags);
+ return false;
+ }
+
static void btintel_pcie_msix_fw_trigger_handler(struct btintel_pcie_data *data)
{
bt_dev_dbg(data->hdev, "Received firmware smart trigger cause");
- if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags))
+ /* Per-work guard: deduplicate concurrent FW-trigger interrupts.
+ * Cleared at the tail of btintel_pcie_fwtrigger_worker().
+ */
+ if (test_and_set_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS,
+ &data->flags))
return;
- /* Trigger device core dump when there is FW assert */
- if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
- data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
+ if (!queue_work(data->dump_workqueue, &data->fwtrigger_work)) {
+ clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags);
+ return;
+ }
- queue_work(data->coredump_workqueue, &data->coredump_work);
+ /* Queue coredump after the fwtrigger event reader so dmp_hdr.event_*
+ * is populated before dump_traces consumes it.
+ */
+ btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT);
}
static void btintel_pcie_msix_hw_exp_handler(struct btintel_pcie_data *data)
{
bt_dev_err(data->hdev, "Received hw exception interrupt");
+ /* CORE_HALTED is the single-writer guard for this handler. It is
+ * set once on first HW exception and cleared only by re-probe
+ * (data is reallocated), so it also serializes hwexp_work
+ * scheduling without needing a separate bit.
+ */
if (test_and_set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags))
return;
- if (test_and_set_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags))
- return;
+ /* Queue companion coredump first so it is appended after hwexp_work
+ * on the ordered @dump_workqueue (preserves the original
+ * coredump-then-hwexp ordering).
+ */
+ btintel_pcie_queue_coredump(data, BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT);
- /* Trigger device core dump when there is HW exception */
- if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
- data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
-
- queue_work(data->coredump_workqueue, &data->coredump_work);
+ queue_work(data->dump_workqueue, &data->hwexp_work);
}
static void btintel_pcie_coredump_worker(struct work_struct *work)
{
struct btintel_pcie_data *data = container_of(work,
struct btintel_pcie_data, coredump_work);
- int err;
/* hdev is NULL until setup_hdev() succeeds, and is cleared on
* teardown after disable_work_sync() drains us; bail in that case.
*/
+ if (!data->hdev)
+ goto out;
+
+ btintel_pcie_dump_traces(data->hdev);
+ out:
+ /* Release guard last so a new trigger can run only after this
+ * pass has fully completed (including dev_coredumpv()).
+ */
+ clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags);
+ }
+
+ static void btintel_pcie_hwexp_worker(struct work_struct *work)
+ {
+ struct btintel_pcie_data *data = container_of(work,
+ struct btintel_pcie_data, hwexp_work);
+
if (!data->hdev)
return;
- if (test_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags)) {
- err = btintel_pcie_dump_fwtrigger_event(data);
- if (err)
- bt_dev_warn(data->hdev, "failed to log fwtrigger event");
- clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags);
- }
+ /* Unlike usb products, controller will not send hardware exception
+ * event on exception. Instead controller writes the hardware event
+ * to device memory along with optional debug events, raises MSIX
+ * and halts. Driver shall read the exception event from device
+ * memory and passes it to the stack for further processing.
+ *
+ * Re-entry is gated by BTINTEL_PCIE_CORE_HALTED in the IRQ
+ * handler, which is only cleared by re-probe; no per-work bit
+ * is needed here.
+ */
+ btintel_pcie_read_hwexp(data);
+ }
- if (test_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags)) {
- btintel_pcie_dump_traces(data->hdev);
- clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags);
- }
+ static void btintel_pcie_fwtrigger_worker(struct work_struct *work)
+ {
+ struct btintel_pcie_data *data = container_of(work,
+ struct btintel_pcie_data, fwtrigger_work);
+ int err;
- if (test_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags)) {
- /* Unlike usb products, controller will not send hardware
- * exception event on exception. Instead controller writes the
- * hardware event to device memory along with optional debug
- * events, raises MSIX and halts. Driver shall read the
- * exception event from device memory and passes it stack for
- * further processing.
- */
- btintel_pcie_read_hwexp(data);
- clear_bit(BTINTEL_PCIE_HWEXP_INPROGRESS, &data->flags);
- }
+ if (!data->hdev)
+ goto out;
+
+ err = btintel_pcie_dump_fwtrigger_event(data);
+ if (err)
+ bt_dev_warn(data->hdev, "failed to log fwtrigger event");
+ out:
+ /* Release guard last; matches set in fw_trigger handler. */
+ clear_bit(BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS, &data->flags);
}
static void btintel_pcie_rx_work(struct work_struct *work)
@@@ -2488,8 -2550,6 +2550,6 @@@ static void btintel_pcie_inc_recovery_c
}
}
- static void btintel_pcie_reset(struct hci_dev *hdev);
-
static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data)
{
union acpi_object *obj, argv4;
@@@ -2650,20 -2710,22 +2710,22 @@@ static void btintel_pcie_reset_work(str
btintel_pcie_synchronize_irqs(data);
flush_work(&data->rx_work);
- /* Drain any in-flight coredump and block new ones across reset.
- * Safe from self-deadlock: coredump_work runs on a separate wq.
+ /* Drain any in-flight dump workers and block new ones across reset.
+ * Safe from self-deadlock: they all run on a separate wq.
*/
disable_work_sync(&data->coredump_work);
+ disable_work_sync(&data->hwexp_work);
+ disable_work_sync(&data->fwtrigger_work);
bt_dev_dbg(data->hdev, "Release bluetooth interface");
/* Both reset paths follow the same contract: on success they
* destroy 'data' via device_reprobe() (a fresh probe re-INIT_WORKs
- * the coredump_work with disable count 0), so enable_work() must
+ * the dump workers with disable count 0), so enable_work() must
* NOT be called on the success path. Only the FLR path can fail
* with 'data' still alive, in which case we balance the
- * disable_work_sync() above so a later successful reset is not
- * permanently blocked.
+ * disable_work_sync() calls above so a later successful reset is
+ * not permanently blocked.
*
* pci_lock_rescan_remove() (held above) serializes against PCI
* device addition/removal (hotplug), so no device can be added to
@@@ -2674,64 -2736,97 +2736,97 @@@
goto out;
}
- if (btintel_pcie_perform_flr(data))
+ if (btintel_pcie_perform_flr(data)) {
enable_work(&data->coredump_work);
+ enable_work(&data->hwexp_work);
+ enable_work(&data->fwtrigger_work);
+ }
out:
pci_dev_put(pdev);
pci_unlock_rescan_remove();
}
- static void btintel_pcie_reset(struct hci_dev *hdev)
+ /* Schedule a device reset of the requested type.
+ *
+ * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters
+ * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error
+ * path, etc.) so that:
+ *
+ * - dev_data->reset_type is written by exactly one caller (the
+ * thread that wins test_and_set_bit), eliminating the race where
+ * a second hw_error could clobber an already-scheduled reset's
+ * type;
+ * - the write happens AFTER the bit is set, so reset_work observes
+ * it through schedule_work()'s memory ordering;
+ * - losers return without touching reset_type or scheduling the
+ * work, so concurrent triggers are silently coalesced into the
+ * in-flight one (whose recovery will reinitialize the device
+ * regardless of the dropped trigger's variant).
+ *
+ * The bit is cleared only by .remove() / re-probe via fresh devm
+ * allocation, which is the intended one-shot semantics: a reset
+ * tears down and re-probes 'data', so there is no "in-flight"
+ * reset to follow up after device_reprobe() succeeds.
+ */
+ static void btintel_pcie_request_reset(struct btintel_pcie_data *data,
+ enum btintel_pcie_reset_type type)
{
- 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;
+ data->reset_type = type;
+
pci_dev_get(data->pdev);
schedule_work(&data->reset_work);
}
+ static void btintel_pcie_hci_reset(struct hci_dev *hdev)
+ {
+ struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+
+ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR);
+ }
+
static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code)
{
- struct btintel_pcie_dev_recovery *data;
+ struct btintel_pcie_dev_recovery *rec;
struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev);
struct pci_dev *pdev = dev_data->pdev;
+ enum btintel_pcie_reset_type type;
time64_t retry_window;
+ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags))
+ return;
+
btintel_pcie_dump_debug_registers(hdev);
- data = btintel_pcie_get_recovery(pdev, &hdev->dev);
- if (!data)
+ rec = btintel_pcie_get_recovery(pdev, &hdev->dev);
+ if (!rec)
return;
- if (code == 0x13)
- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR;
- else
- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
+ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR
+ : BTINTEL_PCIE_IOSF_PRR_FLR;
bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code,
- dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR");
- retry_window = ktime_get_boottime_seconds() - data->last_error;
+ type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR");
+ retry_window = ktime_get_boottime_seconds() - rec->last_error;
if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS &&
- data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) {
+ rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) {
bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d",
- BTINTEL_PCIE_FLR_MAX_RETRY, data->count);
+ BTINTEL_PCIE_FLR_MAX_RETRY, rec->count);
bt_dev_dbg(hdev, "Boot time: %lld seconds",
ktime_get_boottime_seconds());
bt_dev_dbg(hdev, "last error at: %lld seconds",
- data->last_error);
+ rec->last_error);
return;
}
btintel_pcie_inc_recovery_count(pdev, &hdev->dev);
- btintel_pcie_reset(hdev);
+ btintel_pcie_request_reset(dev_data, type);
}
static bool btintel_pcie_wakeup(struct hci_dev *hdev)
@@@ -2821,7 -2916,7 +2916,7 @@@ static int btintel_pcie_setup_hdev(stru
hdev->hw_error = btintel_pcie_hw_error;
hdev->set_diag = btintel_set_diag;
hdev->set_bdaddr = btintel_set_bdaddr;
- hdev->reset = btintel_pcie_reset;
+ hdev->reset = btintel_pcie_hci_reset;
hdev->wakeup = btintel_pcie_wakeup;
hdev->hci_drv = &btintel_pcie_hci_drv;
@@@ -2869,8 -2964,8 +2964,8 @@@ static int btintel_pcie_probe(struct pc
if (!data->workqueue)
return -ENOMEM;
- data->coredump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0);
- if (!data->coredump_workqueue) {
+ data->dump_workqueue = alloc_ordered_workqueue(KBUILD_MODNAME "_cd", 0);
+ if (!data->dump_workqueue) {
destroy_workqueue(data->workqueue);
return -ENOMEM;
}
@@@ -2879,6 -2974,8 +2974,8 @@@
INIT_WORK(&data->rx_work, btintel_pcie_rx_work);
INIT_WORK(&data->reset_work, btintel_pcie_reset_work);
INIT_WORK(&data->coredump_work, btintel_pcie_coredump_worker);
+ INIT_WORK(&data->hwexp_work, btintel_pcie_hwexp_worker);
+ INIT_WORK(&data->fwtrigger_work, btintel_pcie_fwtrigger_worker);
data->boot_stage_cache = 0x00;
data->img_resp_cache = 0x00;
@@@ -2921,7 -3018,7 +3018,7 @@@ exit_error
/* reset device before exit */
btintel_pcie_reset_bt(data);
- destroy_workqueue(data->coredump_workqueue);
+ destroy_workqueue(data->dump_workqueue);
pci_clear_master(pdev);
@@@ -2940,12 -3037,14 +3037,14 @@@ static void btintel_pcie_remove(struct
return;
}
- /* Permanently block coredump triggers and drain the worker before
- * tearing down. Must run before cancel_work_sync(&reset_work) so
- * the disable counter stays >= 1 even after reset_work()'s
+ /* Permanently block all dump triggers and drain the workers before
+ * tearing down. Must run before disable_work_sync(&reset_work) so
+ * the disable counters stay >= 1 even after reset_work()'s
* balanced enable_work() (counter 2 -> 1, never reaching 0).
*/
disable_work_sync(&data->coredump_work);
+ disable_work_sync(&data->hwexp_work);
+ disable_work_sync(&data->fwtrigger_work);
/* Cancel pending reset work. Skip only when remove() is called from
* within the reset work itself (PLDR device_reprobe path) to avoid
@@@ -2973,7 -3072,7 +3072,7 @@@
btintel_pcie_release_hdev(data);
- destroy_workqueue(data->coredump_workqueue);
+ destroy_workqueue(data->dump_workqueue);
destroy_workqueue(data->workqueue);
btintel_pcie_free(data);
@@@ -2992,16 -3091,8 +3091,8 @@@ static void btintel_pcie_coredump(struc
if (!data)
return;
- if (test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags))
- return;
-
- data->dmp_hdr.trigger_reason = BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER;
- /* queue_work() returns false if the work is disabled (reset or
- * remove in progress); clear the in-progress bit so a later
- * trigger can succeed once the work is re-enabled.
- */
- if (!queue_work(data->coredump_workqueue, &data->coredump_work))
- clear_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS, &data->flags);
+ btintel_pcie_queue_coredump(data,
+ BTINTEL_PCIE_TRIGGER_REASON_USER_TRIGGER);
}
#endif
@@@ -3113,8 -3204,7 +3204,7 @@@ static int btintel_pcie_resume(struct d
if (data->pm_sx_event == PM_EVENT_FREEZE ||
data->pm_sx_event == PM_EVENT_HIBERNATE) {
set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
- data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR;
- btintel_pcie_reset(data->hdev);
+ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR);
return 0;
}
@@@ -3138,14 -3228,10 +3228,10 @@@
if (btintel_pcie_in_error(data) ||
btintel_pcie_in_device_halt(data)) {
bt_dev_err(data->hdev, "Controller in error state for D0 entry");
- if (!test_and_set_bit(BTINTEL_PCIE_COREDUMP_INPROGRESS,
- &data->flags)) {
- data->dmp_hdr.trigger_reason =
- BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT;
- queue_work(data->coredump_workqueue, &data->coredump_work);
- }
+ btintel_pcie_queue_coredump(data,
+ BTINTEL_PCIE_TRIGGER_REASON_FW_ASSERT);
set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags);
- btintel_pcie_reset(data->hdev);
+ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR);
}
return err;
}
Attachment:
signature.asc
Description: PGP signature