* RE: Bluetooth: add hardware rfkill handling
2026-10-02 1:43 [PATCH v2 1/2] Bluetooth: Add interface to allow stack to communicate hw rfkill events Sai Teja Aluvala
@ 2026-10-02 3:14 ` bluez.test.bot
0 siblings, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2026-10-02 3:14 UTC (permalink / raw)
To: linux-bluetooth, aluvala.sai.teja
[-- Attachment #1: Type: text/plain, Size: 2225 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1178028/
---Test result---
Test Summary:
CheckPatch PASS 1.50 seconds
VerifyFixes PASS 0.86 seconds
VerifySignedoff PASS 0.74 seconds
GitLint PASS 0.56 seconds
SubjectPrefix PASS 0.17 seconds
BuildKernel PASS 22.65 seconds
CheckAllWarning PASS 26.26 seconds
CheckSparse PASS 27.89 seconds
BuildKernel32 PASS 22.88 seconds
CheckKernelLLVM PASS 25.29 seconds
TestRunnerSetup PASS 566.67 seconds
TestRunner_l2cap-tester PASS 13.50 seconds
TestRunner_iso-tester PASS 9.48 seconds
TestRunner_bnep-tester PASS 2.74 seconds
TestRunner_mgmt-tester FAIL 56.99 seconds
TestRunner_rfcomm-tester PASS 4.01 seconds
TestRunner_sco-tester PASS 5.04 seconds
TestRunner_ioctl-tester PASS 4.14 seconds
TestRunner_mesh-tester FAIL 7.64 seconds
TestRunner_smp-tester PASS 3.91 seconds
TestRunner_userchan-tester PASS 7.88 seconds
TestRunner_6lowpan-tester PASS 5.01 seconds
IncrementalBuild PASS 23.03 seconds
Details
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 503, Passed: 502 (99.8%), Failed: 1, Not Run: 0
Failed Test Cases
Pairing Acceptor - LE Security Level Changed Failed 0.020 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0
Failed Test Cases
Mesh - Send cancel - 1 Timed out 2.668 seconds
Mesh - Send cancel - 2 Timed out 1.998 seconds
https://github.com/bluez/bluetooth-next/pull/849
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 6+ messages in thread
* RE: Bluetooth: add hardware rfkill handling
2026-10-14 6:19 [PATCH v3 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
@ 2026-10-14 7:09 ` bluez.test.bot
0 siblings, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2026-10-14 7:09 UTC (permalink / raw)
To: linux-bluetooth, aluvala.sai.teja
[-- Attachment #1: Type: text/plain, Size: 1605 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1185286/
---Test result---
Test Summary:
CheckPatch PASS 2.13 seconds
VerifyFixes PASS 0.12 seconds
VerifySignedoff PASS 0.12 seconds
GitLint PASS 0.65 seconds
SubjectPrefix PASS 0.24 seconds
BuildKernel PASS 29.57 seconds
CheckAllWarning PASS 33.50 seconds
CheckSparse PASS 36.49 seconds
BuildKernel32 PASS 29.60 seconds
CheckKernelLLVM PASS 33.23 seconds
TestRunnerSetup PASS 714.03 seconds
TestRunner_l2cap-tester PASS 12.59 seconds
TestRunner_iso-tester PASS 16.83 seconds
TestRunner_bnep-tester PASS 3.14 seconds
TestRunner_mgmt-tester PASS 87.37 seconds
TestRunner_rfcomm-tester PASS 4.46 seconds
TestRunner_sco-tester PASS 5.61 seconds
TestRunner_ioctl-tester PASS 7.76 seconds
TestRunner_mesh-tester PASS 5.85 seconds
TestRunner_smp-tester PASS 4.35 seconds
TestRunner_userchan-tester PASS 3.18 seconds
TestRunner_6lowpan-tester PASS 4.42 seconds
IncrementalBuild PASS 29.63 seconds
https://github.com/bluez/bluetooth-next/pull/929
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 0/2] Bluetooth: add hardware rfkill handling
@ 2026-10-14 8:26 Sai Teja Aluvala
2026-10-14 8:26 ` [PATCH v4 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
2026-10-14 8:26 ` [PATCH v4 2/2] Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow Sai Teja Aluvala
0 siblings, 2 replies; 6+ messages in thread
From: Sai Teja Aluvala @ 2026-10-14 8:26 UTC (permalink / raw)
To: Luiz Augusto von Dentz, linux-bluetooth
Cc: kiran.k, chethan.tumkur.narayan, Sai Teja Aluvala
This series adds hardware rfkill handling for the Intel PCIe transport
and the Bluetooth-side notification path.
The resulting flow is:
transport hardware event
-> hci_rfkill_set_hw_state()
-> rfkill hardware state
-> hdev->rfkill_block work
-> HCI_RFKILLED state and device power handling
For Intel PCIe controllers, the series adds GP2 and HW_RFKILL MSI-X
handling, IPC_CONTROL doorbell synchronization, delayed state checking,
reset/shutdown/remove/suspend lifecycle handling, and notification of
the generic HCI rfkill layer.
Patches:
1. Bluetooth: Add HCI interface for HW rfkill events
2. Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow
Changes since v3:
Sashiko's automated review of v3 is available at:
https://sashiko.dev/#/patchset/20261014061947.1238279-1-aluvala.sai.teja%40intel.com
Of the concerns raised, one was a genuine bug and is fixed in v4:
- A masked HW_RFKILL MSI-X cause still latches, so an unrelated
interrupt during remove could requeue the HW rfkill worker after
it was canceled, leading to a use-after-free. The handler now only
queues the worker while HW_RFKILL is unmasked, and remove and the
probe failure path disable the worker instead of canceling it.
The remaining concerns were reviewed; no further changes were made.
Changes since v2:
Sashiko's automated review of v2 is available at:
https://sashiko.dev/#/patchset/cover.1790905146.git.aluvala.sai.teja%40intel.com
The following genuine concerns it raised against this series have been
addressed:
- hci_rfkill_set_hw_state() could race with hci_unregister_dev() and
let hdev->rfkill_block run after hdev->rfkill was destroyed; the
work is now scheduled while still holding hdev->unregister_lock.
- The asynchronous hardware rfkill work item and the rfkill core's
synchronous set_block callback could race on HCI_RFKILLED and
controller power state; both paths are now serialized with a new
hdev->rfkill_lock.
- The HW rfkill worker could dereference data->hdev before probe had
published it; an early NULL check was added.
- The worker and HW_RFKILL MSI-X cause could still run or fire during
shutdown, reset, probe failure, remove, and suspend, racing with
those lifecycle paths; the cause is now masked and the worker
drained before each of them, and unmasked again after setup/resume.
- A hardware rfkill release during suspend was not reported after
resume because the initial state check unconditionally suppressed
the first "off" result; it is now only suppressed when rfkill was
not previously asserted.
- GP2 completion could be falsely triggered by a stale interrupt
that happened to match the current register state; the firmware
acknowledgment is now checked against the specific requested
transition.
Not reported by Sashiko, but found during this review and also fixed:
- hci_rfkill_set_hw_state() now flushes hdev->rfkill_block before
returning, closing a race where a userspace power-on could see a
stale HCI_RFKILLED flag.
- btintel_pcie_shutdown() keeps HW_RFKILL monitoring enabled when
shutdown is itself caused by an active assertion, so a later
de-assertion can still be detected.
The rfkill registration-failure path was also reviewed and confirmed
safe (hdev->rfkill is only published after hci_register_dev()
succeeds).
Pre-existing issues flagged by Sashiko that are unrelated to this
series (a workqueue leak on an existing probe failure path, and an
existing hci_unregister_dev() resource-ordering concern) were left
unchanged, as requested.
v2 also dropped the Bluetooth-specific debugfs test hook, the redundant
hdev->rfkill pointer-clear patch, and the separate RFKILL-core helper
dependency that were present in v1.
The series has been prepared against bluetooth-next/master.
Sai Teja Aluvala (2):
Bluetooth: Add HCI interface for HW rfkill events
Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow
drivers/bluetooth/btintel_pcie.c | 202 +++++++++++++++++++++++++++++--
drivers/bluetooth/btintel_pcie.h | 30 +++++
include/net/bluetooth/hci_core.h | 4 +
net/bluetooth/hci_core.c | 51 +++++++-
4 files changed, 277 insertions(+), 10 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 1/2] Bluetooth: Add HCI interface for HW rfkill events
2026-10-14 8:26 [PATCH v4 0/2] Bluetooth: add hardware rfkill handling Sai Teja Aluvala
@ 2026-10-14 8:26 ` Sai Teja Aluvala
2026-10-14 12:52 ` Bluetooth: add hardware rfkill handling bluez.test.bot
2026-10-14 8:26 ` [PATCH v4 2/2] Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow Sai Teja Aluvala
1 sibling, 1 reply; 6+ messages in thread
From: Sai Teja Aluvala @ 2026-10-14 8:26 UTC (permalink / raw)
To: Luiz Augusto von Dentz, linux-bluetooth
Cc: kiran.k, chethan.tumkur.narayan, Sai Teja Aluvala
Add hci_rfkill_set_hw_state() so that transport drivers can report
hardware rfkill switch changes to the HCI stack through the same
path used for software rfkill.
hci_rfkill_set_hw_state() validates HCI_UNREGISTER and hdev->rfkill
under hdev->unregister_lock, applies the new state via a new
hdev->rfkill_block work item while still holding that lock, and
flushes it before returning. This avoids hci_unregister_dev()
destroying hdev->rfkill before the work runs, and avoids a racing
userspace power-on (triggered by rfkill core's own async uevent)
observing a stale HCI_RFKILLED flag before the work applies the new
state.
A new hdev->rfkill_lock serializes hdev->rfkill_block against the
rfkill core's synchronous set_block callback, so a hardware rfkill
event and a concurrent userspace soft block/unblock cannot race on
HCI_RFKILLED and the controller's power state.
Signed-off-by: Sai Teja Aluvala <aluvala.sai.teja@intel.com>
---
include/net/bluetooth/hci_core.h | 4 +++
net/bluetooth/hci_core.c | 51 ++++++++++++++++++++++++++++++--
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index aa04dec45153..8f32c860d492 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -503,6 +503,7 @@ struct hci_dev {
struct list_head cmd_sync_work_list;
struct mutex cmd_sync_work_lock;
struct mutex unregister_lock;
+ struct mutex rfkill_lock;
struct work_struct cmd_sync_cancel_work;
struct work_struct reenable_adv_work;
@@ -586,6 +587,7 @@ struct hci_dev {
struct device dev;
struct rfkill *rfkill;
+ struct work_struct rfkill_block;
DECLARE_BITMAP(dev_flags, __HCI_NUM_FLAGS);
hci_conn_flags_t conn_flags;
@@ -1822,6 +1824,8 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src, u8 src_type);
struct hci_dev *hci_alloc_dev_priv(int sizeof_priv);
+void hci_rfkill_set_hw_state(struct hci_dev *hdev, bool blocked);
+
static inline struct hci_dev *hci_alloc_dev(void)
{
return hci_alloc_dev_priv(0);
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 209244fd9391..7598a1044c5c 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -868,13 +868,19 @@ static int hci_rfkill_set_block(void *data, bool blocked)
struct hci_dev *hdev = data;
int err;
+ mutex_lock(&hdev->rfkill_lock);
+
BT_DBG("%p name %s blocked %d", hdev, hdev->name, blocked);
- if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
+ if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) {
+ mutex_unlock(&hdev->rfkill_lock);
return -EBUSY;
+ }
- if (blocked == hci_dev_test_flag(hdev, HCI_RFKILLED))
+ if (blocked == hci_dev_test_flag(hdev, HCI_RFKILLED)) {
+ mutex_unlock(&hdev->rfkill_lock);
return 0;
+ }
if (blocked) {
hci_dev_set_flag(hdev, HCI_RFKILLED);
@@ -897,9 +903,45 @@ static int hci_rfkill_set_block(void *data, bool blocked)
hci_dev_clear_flag(hdev, HCI_RFKILLED);
}
+ mutex_unlock(&hdev->rfkill_lock);
+
return 0;
}
+void hci_rfkill_set_hw_state(struct hci_dev *hdev, bool blocked)
+{
+ mutex_lock(&hdev->unregister_lock);
+ if (hci_dev_test_flag(hdev, HCI_UNREGISTER) ||
+ IS_ERR_OR_NULL(hdev->rfkill)) {
+ mutex_unlock(&hdev->unregister_lock);
+ return;
+ }
+
+ rfkill_set_hw_state(hdev->rfkill, blocked);
+
+ /* Flush so HCI_RFKILLED is updated before rfkill core's async
+ * uevent reaches userspace, otherwise a racing power-on could see
+ * a stale flag and immediately close the device again.
+ */
+ schedule_work(&hdev->rfkill_block);
+ flush_work(&hdev->rfkill_block);
+ mutex_unlock(&hdev->unregister_lock);
+}
+EXPORT_SYMBOL(hci_rfkill_set_hw_state);
+
+static void hci_rfkill_block_work(struct work_struct *work)
+{
+ struct hci_dev *hdev = container_of(work, struct hci_dev, rfkill_block);
+
+ if (!hdev->rfkill)
+ return;
+
+ if (rfkill_blocked(hdev->rfkill))
+ hci_rfkill_set_block(hdev, true);
+ else
+ hci_rfkill_set_block(hdev, false);
+}
+
static const struct rfkill_ops hci_rfkill_ops = {
.set_block = hci_rfkill_set_block,
};
@@ -2495,6 +2537,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
mutex_init(&hdev->req_lock);
mutex_init(&hdev->mgmt_pending_lock);
mutex_init(&hdev->remote_oob_lock);
+ mutex_init(&hdev->rfkill_lock);
ida_init(&hdev->unset_handle_ida);
@@ -2518,6 +2561,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
INIT_LIST_HEAD(&hdev->monitored_devices);
INIT_LIST_HEAD(&hdev->local_codecs);
+ INIT_WORK(&hdev->rfkill_block, hci_rfkill_block_work);
INIT_WORK(&hdev->rx_work, hci_rx_work);
INIT_WORK(&hdev->cmd_work, hci_cmd_work);
INIT_WORK(&hdev->tx_work, hci_tx_work);
@@ -2683,6 +2727,7 @@ void hci_unregister_dev(struct hci_dev *hdev)
disable_work_sync(&hdev->tx_work);
disable_work_sync(&hdev->power_on);
disable_work_sync(&hdev->error_reset);
+ disable_work_sync(&hdev->rfkill_block);
disable_delayed_work_sync(&hdev->cmd_timer);
disable_delayed_work_sync(&hdev->ncmd_timer);
hci_devcd_shutdown(hdev);
@@ -2707,10 +2752,12 @@ void hci_unregister_dev(struct hci_dev *hdev)
hci_sock_dev_event(hdev, HCI_DEV_UNREG);
+ mutex_lock(&hdev->unregister_lock);
if (hdev->rfkill) {
rfkill_unregister(hdev->rfkill);
rfkill_destroy(hdev->rfkill);
}
+ mutex_unlock(&hdev->unregister_lock);
device_del(&hdev->dev);
/* Actual cleanup is deferred until hci_release_dev(). */
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 2/2] Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow
2026-10-14 8:26 [PATCH v4 0/2] Bluetooth: add hardware rfkill handling Sai Teja Aluvala
2026-10-14 8:26 ` [PATCH v4 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
@ 2026-10-14 8:26 ` Sai Teja Aluvala
1 sibling, 0 replies; 6+ messages in thread
From: Sai Teja Aluvala @ 2026-10-14 8:26 UTC (permalink / raw)
To: Luiz Augusto von Dentz, linux-bluetooth
Cc: kiran.k, chethan.tumkur.narayan, Sai Teja Aluvala
Add host-driven HW rfkill handling for Intel PCIe Bluetooth
controllers and report state changes to the Bluetooth stack through
hci_rfkill_set_hw_state(). The GP2 and HW_RFKILL MSI-X hardware
causes are enabled so that the host can detect a HW rfkill assertion
and hand its acknowledgment back to firmware.
On a HW_RFKILL interrupt, a delayed worker reads the FUNC_CTRL and
IPC_CONTROL registers to determine whether the switch is asserted or
released. On assertion, the worker marks HW rfkill as active and
reports a blocked state directly. On release, it clears the
IPC_CONTROL HW rfkill doorbell bit, waits for the firmware's GP2
acknowledgment that matches the requested transition, and reports
an unblocked state to the stack only once that acknowledgment
arrives. IPC_CONTROL register read-modify-write updates are
serialized with a new ipc_lock, and the wait state/done flags use
READ_ONCE()/WRITE_ONCE() since they are shared between the worker
and the MSI-X handler.
After a successful setup or resume, an initial state check is
scheduled. That check reports an already-asserted HW rfkill state,
and also reports a release if rfkill was asserted before suspend and
cleared while the controller was asleep; otherwise it is a no-op so
it cannot suppress a later genuine transition.
The HW_RFKILL MSI-X cause is masked before synchronizing interrupts
and canceling or draining the worker across setup retry, reset,
shutdown, probe failure, remove, and suspend, and is unmasked again
after setup and resume succeed. GP2 is left enabled throughout so
that the shutdown path can still wait for the firmware's doorbell
acknowledgment when re-asserting rfkill before the controller is
powered down. If the shutdown is itself caused by an active HW
rfkill assertion, monitoring is left enabled instead, so a later
de-assertion can still be detected.
Since a masked cause still latches, the MSI-X handler only queues the
worker while HW_RFKILL is unmasked, and remove and probe failure
disable the worker so it cannot be requeued after data is freed.
Signed-off-by: Sai Teja Aluvala <aluvala.sai.teja@intel.com>
---
drivers/bluetooth/btintel_pcie.c | 202 +++++++++++++++++++++++++++++--
drivers/bluetooth/btintel_pcie.h | 30 +++++
2 files changed, 224 insertions(+), 8 deletions(-)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 91b1ee9d6010..6c5665a9a10d 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -2710,15 +2710,126 @@ static inline bool btintel_pcie_is_txackq_empty(struct btintel_pcie_data *data)
return data->ia.cr_tia[BTINTEL_PCIE_TXQ_NUM] == data->ia.cr_hia[BTINTEL_PCIE_TXQ_NUM];
}
+static bool btintel_pcie_hw_rfkill_doorbell(struct btintel_pcie_data *data,
+ bool state)
+{
+ unsigned long flags;
+ u32 reg;
+ int err;
+
+ WRITE_ONCE(data->rfkill_wait_done, false);
+ WRITE_ONCE(data->rfkill_wait_state, state);
+
+ spin_lock_irqsave(&data->ipc_lock, flags);
+ reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_IPC_CONTROL_REG);
+ if (state)
+ reg |= BTINTEL_PCIE_CSR_IPC_CONTROL_HW_RFKILL;
+ else
+ reg &= ~BTINTEL_PCIE_CSR_IPC_CONTROL_HW_RFKILL;
+ btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_IPC_CONTROL_REG, reg);
+ spin_unlock_irqrestore(&data->ipc_lock, flags);
+
+ err = wait_event_timeout(data->rfkill_wait_q,
+ READ_ONCE(data->rfkill_wait_done),
+ msecs_to_jiffies(BTINTEL_PCIE_HW_RFKILL_INTERRUPT_TIMEOUT));
+ if (!err) {
+ bt_dev_err(data->hdev, "Timeout (%u ms) on alive interrupt for RFKILL",
+ BTINTEL_PCIE_HW_RFKILL_INTERRUPT_TIMEOUT);
+ return false;
+ }
+
+ return true;
+}
+
+static void btintel_pcie_hw_rfkill_delay_work(struct work_struct *wk)
+{
+ struct btintel_pcie_data *data =
+ container_of(wk, struct btintel_pcie_data, hw_rfkill_work.work);
+ u32 func_ctrl, ipc_ctrl;
+ bool set_rfkill, first_check;
+
+ if (!data->hdev)
+ return;
+
+ /* Do not report an initial clear when rfkill was never asserted.
+ * After resume, however, a previously asserted rfkill must be
+ * cleared and reported to the HCI stack.
+ */
+ first_check = test_and_clear_bit(BTINTEL_PCIE_HW_RFKILL_STATE_CHECK,
+ &data->flags);
+
+ func_ctrl = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG);
+ ipc_ctrl = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_IPC_CONTROL_REG);
+
+ set_rfkill = !(func_ctrl & BTINTEL_PCIE_CSR_FUNC_CTRL_HW_RFKILL) &&
+ !(ipc_ctrl & BTINTEL_PCIE_CSR_IPC_CONTROL_HW_RFKILL);
+ if (set_rfkill) {
+ bt_dev_dbg(data->hdev, "hw rfkill on");
+ set_bit(BTINTEL_PCIE_HWRFKILL_ON, &data->flags);
+ hci_rfkill_set_hw_state(data->hdev, true);
+ } else {
+ bt_dev_dbg(data->hdev, "hw rfkill off");
+ /* Nothing changed at the initial post-setup check, so there
+ * is no transition to report.
+ */
+ if (first_check &&
+ !test_bit(BTINTEL_PCIE_HWRFKILL_ON, &data->flags))
+ return;
+
+ if (btintel_pcie_hw_rfkill_doorbell(data, false)) {
+ clear_bit(BTINTEL_PCIE_HWRFKILL_ON, &data->flags);
+ hci_rfkill_set_hw_state(data->hdev, false);
+ }
+ }
+}
+
+static void btintel_delayed_hw_rfkill(struct btintel_pcie_data *data,
+ bool delay)
+{
+ mod_delayed_work(system_wq, &data->hw_rfkill_work,
+ delay ? msecs_to_jiffies(BTINTEL_PCIE_HW_RFKILL_DELAY) : 0);
+}
+
+static void btintel_pcie_msix_gp2_handler(struct btintel_pcie_data *data)
+{
+ u32 reg, boot_stage_reg;
+ bool state;
+
+ boot_stage_reg = btintel_pcie_rd_reg32(data,
+ BTINTEL_PCIE_CSR_BOOT_STAGE_REG);
+ reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_IPC_STATUS_REG);
+ state = READ_ONCE(data->rfkill_wait_state);
+
+ if (state &&
+ (reg & BTINTEL_PCIE_CSR_IPC_STATUS_HW_RFKILL) &&
+ (boot_stage_reg & BTINTEL_PCIE_CSR_BOOT_STAGE_HW_RFKILL_STATE)) {
+ bt_dev_dbg(data->hdev,
+ "firmware done handling HW RF-KILL SET state update");
+ WRITE_ONCE(data->rfkill_wait_done, true);
+ }
+
+ if (!state &&
+ !(reg & BTINTEL_PCIE_CSR_IPC_STATUS_HW_RFKILL) &&
+ !(boot_stage_reg & BTINTEL_PCIE_CSR_BOOT_STAGE_HW_RFKILL_STATE)) {
+ bt_dev_dbg(data->hdev,
+ "firmware done handling HW RF-KILL CLEAR state update");
+ WRITE_ONCE(data->rfkill_wait_done, true);
+ }
+
+ if (READ_ONCE(data->rfkill_wait_done))
+ wake_up(&data->rfkill_wait_q);
+}
+
static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id)
{
struct msix_entry *entry = dev_id;
struct btintel_pcie_data *data = btintel_pcie_get_data(entry);
- u32 intr_fh, intr_hw;
+ u32 intr_fh, intr_hw, hw_mask;
spin_lock(&data->irq_lock);
intr_fh = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MSIX_FH_INT_CAUSES);
intr_hw = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES);
+ hw_mask = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK);
/* Clear causes registers to avoid being handling the same cause */
btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_MSIX_FH_INT_CAUSES, intr_fh);
@@ -2740,6 +2851,8 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id)
if (intr_hw & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP1)
btintel_pcie_msix_gp1_handler(data);
+ if (intr_hw & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP2)
+ btintel_pcie_msix_gp2_handler(data);
/* For TX */
if (intr_fh & BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0) {
@@ -2758,6 +2871,11 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id)
if (intr_hw & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG)
btintel_pcie_msix_fw_trigger_handler(data);
+ /* A masked cause still latches; ignore it once masked. */
+ if ((intr_hw & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL) &&
+ !(hw_mask & BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL))
+ btintel_delayed_hw_rfkill(data, false);
+
/* This interrupt is triggered by the firmware after updating
* boot_stage register and image_response register
*/
@@ -2835,8 +2953,10 @@ static struct btintel_pcie_causes_list causes_list[] = {
{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_0, BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK, 0x00 },
{ BTINTEL_PCIE_MSIX_FH_INT_CAUSES_1, BTINTEL_PCIE_CSR_MSIX_FH_INT_MASK, 0x01 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x20 },
+ { BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP2, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x22 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x23 },
{ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x25 },
+ { BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK, 0x27 },
};
/* This function configures the interrupt masks for both HW_INT_CAUSES and
@@ -3353,6 +3473,22 @@ static void btintel_pcie_disable_interrupts(struct btintel_pcie_data *data)
spin_unlock(&data->irq_lock);
}
+static void btintel_pcie_mask_hw_rfkill(struct btintel_pcie_data *data)
+{
+ spin_lock(&data->irq_lock);
+ btintel_pcie_set_reg_bits(data, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL);
+ spin_unlock(&data->irq_lock);
+}
+
+static void btintel_pcie_unmask_hw_rfkill(struct btintel_pcie_data *data)
+{
+ spin_lock(&data->irq_lock);
+ btintel_pcie_clr_reg_bits(data, BTINTEL_PCIE_CSR_MSIX_HW_INT_MASK,
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL);
+ spin_unlock(&data->irq_lock);
+}
+
static void btintel_pcie_enable_interrupts(struct btintel_pcie_data *data)
{
spin_lock(&data->irq_lock);
@@ -3535,8 +3671,10 @@ static int btintel_pcie_setup(struct hci_dev *hdev)
bt_dev_err(hdev, "Firmware download retry count: %d",
fw_dl_retry);
btintel_pcie_dump_debug_registers(hdev);
- btintel_pcie_disable_interrupts(data);
+ btintel_pcie_mask_hw_rfkill(data);
btintel_pcie_synchronize_irqs(data);
+ cancel_delayed_work_sync(&data->hw_rfkill_work);
+ btintel_pcie_disable_interrupts(data);
err = btintel_pcie_reset_bt(data);
if (err) {
bt_dev_err(hdev, "Failed to do shr reset: %d", err);
@@ -3554,8 +3692,12 @@ static int btintel_pcie_setup(struct hci_dev *hdev)
btintel_pcie_start_rx(data);
}
- if (!err)
+ if (!err) {
set_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags);
+ set_bit(BTINTEL_PCIE_HW_RFKILL_STATE_CHECK, &data->flags);
+ btintel_pcie_unmask_hw_rfkill(data);
+ btintel_delayed_hw_rfkill(data, true);
+ }
return err;
}
@@ -3783,18 +3925,20 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
if (!data)
goto out;
+ btintel_pcie_mask_hw_rfkill(data);
+ btintel_pcie_synchronize_irqs(data);
+ disable_delayed_work_sync(&data->hw_rfkill_work);
btintel_pcie_disable_interrupts(data);
btintel_pcie_synchronize_irqs(data);
flush_work(&data->rx_work);
- /* Drain any in-flight dump workers and block new ones across reset.
- * Safe from self-deadlock: they all run on a separate wq.
+ /* Drain any in-flight dump/rfkill 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);
disable_work_sync(&data->mbox_work);
-
bt_dev_dbg(data->hdev, "Release bluetooth interface");
/* Both reset paths follow the same contract: on success they
@@ -3819,6 +3963,7 @@ static void btintel_pcie_reset_work(struct work_struct *wk)
enable_work(&data->hwexp_work);
enable_work(&data->fwtrigger_work);
enable_work(&data->mbox_work);
+ enable_delayed_work(&data->hw_rfkill_work);
}
out:
@@ -4009,6 +4154,24 @@ static struct hci_drv btintel_pcie_hci_drv = {
.specific_handlers = btintel_pcie_hci_drv_specific_handlers,
};
+static int btintel_pcie_shutdown(struct hci_dev *hdev)
+{
+ struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+
+ /* Keep monitoring if shutting down due to an active HW rfkill
+ * assertion, so a later de-assertion can still be detected.
+ */
+ if (test_bit(BTINTEL_PCIE_HWRFKILL_ON, &data->flags)) {
+ btintel_pcie_hw_rfkill_doorbell(data, true);
+ } else {
+ btintel_pcie_mask_hw_rfkill(data);
+ btintel_pcie_synchronize_irqs(data);
+ cancel_delayed_work_sync(&data->hw_rfkill_work);
+ }
+
+ return btintel_shutdown_combined(hdev);
+}
+
static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
{
int err;
@@ -4028,7 +4191,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
hdev->close = btintel_pcie_close;
hdev->send = btintel_pcie_send_frame;
hdev->setup = btintel_pcie_setup;
- hdev->shutdown = btintel_shutdown_combined;
+ hdev->shutdown = btintel_pcie_shutdown;
hdev->hw_error = btintel_pcie_hw_error;
hdev->set_diag = btintel_set_diag;
hdev->set_bdaddr = btintel_set_bdaddr;
@@ -4069,6 +4232,7 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
spin_lock_init(&data->irq_lock);
spin_lock_init(&data->hci_rx_lock);
+ spin_lock_init(&data->ipc_lock);
init_waitqueue_head(&data->gp0_wait_q);
data->gp0_received = false;
@@ -4076,6 +4240,9 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
init_waitqueue_head(&data->tx_wait_q);
data->tx_wait_done = false;
+ init_waitqueue_head(&data->rfkill_wait_q);
+ data->rfkill_wait_done = false;
+
init_waitqueue_head(&data->mbox_parse_wait_q);
data->workqueue = alloc_ordered_workqueue(KBUILD_MODNAME, WQ_HIGHPRI);
@@ -4090,6 +4257,8 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
skb_queue_head_init(&data->rx_skb_q);
INIT_WORK(&data->rx_work, btintel_pcie_rx_work);
+ INIT_DELAYED_WORK(&data->hw_rfkill_work,
+ btintel_pcie_hw_rfkill_delay_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);
@@ -4135,6 +4304,10 @@ static int btintel_pcie_probe(struct pci_dev *pdev,
return 0;
exit_error:
+ btintel_pcie_mask_hw_rfkill(data);
+ btintel_pcie_synchronize_irqs(data);
+ disable_delayed_work_sync(&data->hw_rfkill_work);
+
/* reset device before exit */
btintel_pcie_reset_bt(data);
@@ -4175,8 +4348,11 @@ static void btintel_pcie_remove(struct pci_dev *pdev)
if (current_work() != &data->reset_work)
disable_work_sync(&data->reset_work);
- btintel_pcie_disable_interrupts(data);
+ btintel_pcie_mask_hw_rfkill(data);
+ btintel_pcie_synchronize_irqs(data);
+ disable_delayed_work_sync(&data->hw_rfkill_work);
+ btintel_pcie_disable_interrupts(data);
btintel_pcie_synchronize_irqs(data);
flush_work(&data->rx_work);
@@ -4317,6 +4493,10 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
data = pci_get_drvdata(pdev);
+ btintel_pcie_mask_hw_rfkill(data);
+ btintel_pcie_synchronize_irqs(data);
+ cancel_delayed_work_sync(&data->hw_rfkill_work);
+
/* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */
if (mesg.event == PM_EVENT_SUSPEND &&
pm_suspend_target_state == PM_SUSPEND_TO_IDLE)
@@ -4330,6 +4510,9 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg)
err = btintel_pcie_set_dxstate(data, dxstate);
if (err) {
+ btintel_pcie_unmask_hw_rfkill(data);
+ set_bit(BTINTEL_PCIE_HW_RFKILL_STATE_CHECK, &data->flags);
+ btintel_delayed_hw_rfkill(data, true);
bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)",
dxstate, err);
return err;
@@ -4384,6 +4567,9 @@ static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg)
if (err) {
bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err);
} else {
+ btintel_pcie_unmask_hw_rfkill(data);
+ set_bit(BTINTEL_PCIE_HW_RFKILL_STATE_CHECK, &data->flags);
+ btintel_delayed_hw_rfkill(data, true);
bt_dev_dbg(data->hdev,
"device entered into d0 state from d3 in %lld us",
ktime_to_us(ktime_get() - start));
diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h
index 3030b4e8b750..dda711f7755d 100644
--- a/drivers/bluetooth/btintel_pcie.h
+++ b/drivers/bluetooth/btintel_pcie.h
@@ -33,6 +33,7 @@
#define BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_INIT (BIT(6))
#define BTINTEL_PCIE_CSR_FUNC_CTRL_FUNC_INIT (BIT(7))
#define BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_STS (BIT(20))
+#define BTINTEL_PCIE_CSR_FUNC_CTRL_HW_RFKILL (BIT(27))
#define BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ (BIT(21))
@@ -40,6 +41,12 @@
#define BTINTEL_PCIE_CSR_FUNC_CTRL_BUS_MASTER_DISCON (BIT(29))
#define BTINTEL_PCIE_CSR_FUNC_CTRL_SW_RESET (BIT(31))
+/* BTINTEL PCIE CSR IPC Control Register */
+#define BTINTEL_PCIE_CSR_IPC_CONTROL_HW_RFKILL (BIT(7))
+
+/* BTINTEL PCIE CSR IPC Status Register */
+#define BTINTEL_PCIE_CSR_IPC_STATUS_HW_RFKILL (BIT(7))
+
/* Value for BTINTEL_PCIE_CSR_BOOT_STAGE register */
#define BTINTEL_PCIE_CSR_BOOT_STAGE_ROM (BIT(0))
#define BTINTEL_PCIE_CSR_BOOT_STAGE_IML (BIT(1))
@@ -53,6 +60,7 @@
#define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23))
/* Reflects live D-state. Updated by hardware on every D-state transition. */
#define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24))
+#define BTINTEL_PCIE_CSR_BOOT_STAGE_HW_RFKILL_STATE (BIT(25))
#define BTINTEL_PCIE_CSR_DOORBELL_MBOX_READ_CONFIRM (BIT(4))
@@ -135,8 +143,10 @@ enum msix_fh_int_causes {
enum msix_hw_int_causes {
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0 = BIT(0), /* cause 32 */
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP1 = BIT(1), /* cause 33 */
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP2 = BIT(2), /* cause 34 */
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HWEXP = BIT(3), /* cause 35 */
BTINTEL_PCIE_MSIX_HW_INT_CAUSES_FWTRIG = BIT(5), /* cause 37 */
+ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_HW_RFKILL = BIT(7), /* cause 39 */
};
/* PCIe device states
@@ -155,6 +165,8 @@ enum {
BTINTEL_PCIE_COREDUMP_INPROGRESS,
BTINTEL_PCIE_FWTRIGGER_DUMP_INPROGRESS,
BTINTEL_PCIE_RECOVERY_IN_PROGRESS,
+ BTINTEL_PCIE_HWRFKILL_ON,
+ BTINTEL_PCIE_HW_RFKILL_STATE_CHECK,
BTINTEL_PCIE_SETUP_DONE,
BTINTEL_PCIE_MAIL_BOX_INTR,
BTINTEL_PCIE_MBOX_PARSE_PENDING,
@@ -215,6 +227,12 @@ enum btintel_pcie_mbox_msg {
#define BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES 3
+/* HW rfkill interrupt timeout */
+#define BTINTEL_PCIE_HW_RFKILL_INTERRUPT_TIMEOUT 200
+
+/* HW rfkill check timeout */
+#define BTINTEL_PCIE_HW_RFKILL_DELAY 1000
+
/* The number of descriptors in TX queues */
#define BTINTEL_PCIE_TX_DESCS_COUNT 32
@@ -680,6 +698,7 @@ struct btintel_pcie_ini_dump_info {
* @flags: driver state
* @irq_lock: spinlock for MSI-X
* @hci_rx_lock: spinlock for HCI RX flow
+ * @ipc_lock: spinlock for IPC control register read-modify-write
* @base_addr: pci base address (from BAR)
* @msix_entries: array of MSI-X entries
* @msix_enabled: true if MSI-X is enabled;
@@ -696,6 +715,10 @@ struct btintel_pcie_ini_dump_info {
* @gp0_wait_q: wait_q for gp0 interrupt
* @tx_wait_done: condition for tx interrupt
* @tx_wait_q: wait_q for tx interrupt
+ * @rfkill_wait_done: condition for HW rfkill GP2 interrupt
+ * @rfkill_wait_state: requested HW rfkill transition (set or clear)
+ * being waited on, used to validate the GP2 acknowledgment
+ * @rfkill_wait_q: wait_q for HW rfkill GP2 interrupt
* @workqueue: workqueue for RX work
* @rx_skb_q: SKB queue for RX packet
* @rx_work: RX work struct to process the RX packet in @rx_skb_q
@@ -723,6 +746,8 @@ struct btintel_pcie_data {
spinlock_t irq_lock;
/* lock to serialize rx events */
spinlock_t hci_rx_lock;
+ /* lock to serialize IPC control register updates */
+ spinlock_t ipc_lock;
void __iomem *base_addr;
@@ -747,9 +772,14 @@ struct btintel_pcie_data {
bool tx_wait_done;
wait_queue_head_t tx_wait_q;
+ bool rfkill_wait_done;
+ bool rfkill_wait_state;
+ wait_queue_head_t rfkill_wait_q;
+
struct workqueue_struct *workqueue;
struct sk_buff_head rx_skb_q;
struct work_struct rx_work;
+ struct delayed_work hw_rfkill_work;
struct work_struct reset_work;
struct workqueue_struct *dump_workqueue;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* RE: Bluetooth: add hardware rfkill handling
2026-10-14 8:26 ` [PATCH v4 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
@ 2026-10-14 12:52 ` bluez.test.bot
0 siblings, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2026-10-14 12:52 UTC (permalink / raw)
To: linux-bluetooth, aluvala.sai.teja
[-- Attachment #1: Type: text/plain, Size: 1605 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1185391/
---Test result---
Test Summary:
CheckPatch PASS 1.56 seconds
VerifyFixes PASS 0.08 seconds
VerifySignedoff PASS 0.09 seconds
GitLint PASS 0.46 seconds
SubjectPrefix PASS 0.27 seconds
BuildKernel PASS 22.51 seconds
CheckAllWarning PASS 25.95 seconds
CheckSparse PASS 27.42 seconds
BuildKernel32 PASS 23.15 seconds
CheckKernelLLVM PASS 24.83 seconds
TestRunnerSetup PASS 512.02 seconds
TestRunner_l2cap-tester PASS 13.40 seconds
TestRunner_iso-tester PASS 9.38 seconds
TestRunner_bnep-tester PASS 2.78 seconds
TestRunner_mgmt-tester PASS 82.92 seconds
TestRunner_rfcomm-tester PASS 4.05 seconds
TestRunner_sco-tester PASS 5.05 seconds
TestRunner_ioctl-tester PASS 10.17 seconds
TestRunner_mesh-tester PASS 5.09 seconds
TestRunner_smp-tester PASS 3.97 seconds
TestRunner_userchan-tester PASS 2.82 seconds
TestRunner_6lowpan-tester PASS 4.01 seconds
IncrementalBuild PASS 23.08 seconds
https://github.com/bluez/bluetooth-next/pull/931
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-14 12:52 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 8:26 [PATCH v4 0/2] Bluetooth: add hardware rfkill handling Sai Teja Aluvala
2026-10-14 8:26 ` [PATCH v4 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
2026-10-14 12:52 ` Bluetooth: add hardware rfkill handling bluez.test.bot
2026-10-14 8:26 ` [PATCH v4 2/2] Bluetooth: btintel_pcie: add HW rfkill interrupt/doorbell flow Sai Teja Aluvala
-- strict thread matches above, loose matches on Subject: below --
2026-10-14 6:19 [PATCH v3 1/2] Bluetooth: Add HCI interface for HW rfkill events Sai Teja Aluvala
2026-10-14 7:09 ` Bluetooth: add hardware rfkill handling bluez.test.bot
2026-10-02 1:43 [PATCH v2 1/2] Bluetooth: Add interface to allow stack to communicate hw rfkill events Sai Teja Aluvala
2026-10-02 3:14 ` Bluetooth: add hardware rfkill handling bluez.test.bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.