* [PATCH v1 0/1] emulator: add generic HW rfkill test tool
@ 2026-09-30 7:42 Sai Teja Aluvala
2026-09-30 7:42 ` [PATCH v1 1/1] " Sai Teja Aluvala
0 siblings, 1 reply; 7+ messages in thread
From: Sai Teja Aluvala @ 2026-09-30 7:42 UTC (permalink / raw)
To: linux-bluetooth; +Cc: kiran.k, chethan.tumkur.narayan, Sai Teja Aluvala
This patch adds emulator/hci_rfkill, a testing utility for the generic
Bluetooth hardware-rfkill path.
The tool operates on an hciN device and:
- writes Y/N to the kernel force_hw_rfkill debugfs entry;
- reads back the debugfs hardware-rfkill state;
- verifies the corresponding /sys/class/rfkill hardware state;
- supports enable-only, disable-only, and toggle modes;
- optionally restores the adapter's original up/down state.
The tool is built only under TESTING and is not installed as a production
utility.
This tests the generic net/bluetooth rfkill notification and state
handling. It does not test vendor-specific hardware interrupt, doorbell,
or register-read logic. For example, the Intel PCIe interrupt/doorbell
path must still be tested with the corresponding kernel driver and
hardware or transport-level emulation.
Dependency:
This tool requires the separate Bluetooth kernel series:
[PATCH v1 0/4] Bluetooth: add hardware rfkill handling and test hook
In particular, that series adds the Bluetooth kernel debugfs interface:
/sys/kernel/debug/bluetooth/hciN/force_hw_rfkill
The Bluetooth kernel series is submitted to bluetooth-next and itself
depends on the separate RFKILL helper submitted to wireless-next.
Its cover-thread lore link is:
https://lore.kernel.org/linux-bluetooth/cover.1790752799.git.aluvala.sai.teja@intel.com/
Patch:
1. emulator: add generic HW rfkill test tool
Changes in v1:
- Initial version.
The patch has been prepared against bluez/master.
Sai Teja Aluvala (1):
emulator: add generic HW rfkill test tool
Makefile.tools | 3 +
emulator/hci_rfkill.c | 478 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 481 insertions(+)
create mode 100644 emulator/hci_rfkill.c
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v1 1/1] emulator: add generic HW rfkill test tool
2026-09-30 7:42 [PATCH v1 0/1] emulator: add generic HW rfkill test tool Sai Teja Aluvala
@ 2026-09-30 7:42 ` Sai Teja Aluvala
2026-09-30 12:53 ` Luiz Augusto von Dentz
2026-09-30 15:12 ` bluez.test.bot
0 siblings, 2 replies; 7+ messages in thread
From: Sai Teja Aluvala @ 2026-09-30 7:42 UTC (permalink / raw)
To: linux-bluetooth; +Cc: kiran.k, chethan.tumkur.narayan, Sai Teja Aluvala
Add emulator/hci_rfkill, a standalone tool that drives the
force_hw_rfkill debugfs hook for an hciN device and verifies the
reported hardware-block state against /sys/class/rfkill. The hook
exercises the generic net/bluetooth rfkill path for any HCI transport;
it does not test the driver's hardware interrupt or register handling.
Support assert-only, deassert-only, and toggle modes. Validate the
hciN argument before using it in filesystem paths. In toggle mode,
if asserting the block was written successfully but subsequent
verification fails, still attempt a verified unblock while preserving
the failure result. Do not attempt an unblock when the assert write
itself fails.
The optional -r/--restore mode snapshots the adapter's up/down state
via HCIGETDEVINFO and restores it via HCIDEVUP/HCIDEVDOWN, following
the raw HCI control-socket approach used by tools/hciconfig.c.
Usage: hci_rfkill [-e|-d|-t] [-r] hciN
Signed-off-by: Sai Teja Aluvala <aluvala.sai.teja@intel.com>
---
Makefile.tools | 3 +
emulator/hci_rfkill.c | 478 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 481 insertions(+)
create mode 100644 emulator/hci_rfkill.c
diff --git a/Makefile.tools b/Makefile.tools
index 2914bf1c0..4e5d0d976 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -85,6 +85,7 @@ endif
if TESTING
noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \
+ emulator/hci_rfkill \
peripheral/btsensor tools/3dsp \
tools/mgmt-tester tools/gap-tester \
tools/l2cap-tester tools/sco-tester \
@@ -111,6 +112,8 @@ emulator_b1ee_LDADD = src/libshared-mainloop.la
emulator_hfp_SOURCES = emulator/hfp.c
emulator_hfp_LDADD = src/libshared-mainloop.la
+emulator_hci_rfkill_SOURCES = emulator/hci_rfkill.c
+
peripheral_btsensor_SOURCES = peripheral/main.c \
peripheral/efivars.h peripheral/efivars.c \
peripheral/attach.h peripheral/attach.c \
diff --git a/emulator/hci_rfkill.c b/emulator/hci_rfkill.c
new file mode 100644
index 000000000..f5ef5e3bb
--- /dev/null
+++ b/emulator/hci_rfkill.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ */
+
+/*
+ * hci_rfkill - generic HW rfkill stack test driver.
+ *
+ * Applies to any hci_dev with rfkill registered, regardless of vendor or
+ * transport (USB, UART, PCIe, virtual, etc.). Many transport drivers (e.g.
+ * btintel_pcie) are bound to real hardware and have no userspace-attachable
+ * transport to emulate the way emulator/vhci.c does for /dev/vhci. Instead,
+ * net/bluetooth's hci_debugfs.c exposes a generic debugfs test hook
+ * ("force_hw_rfkill" under /sys/kernel/debug/bluetooth/hciN/) that calls
+ * hci_rfkill_set_hw_state() directly. That is the exact same entry point
+ * any driver's real HW rfkill interrupt path (e.g. btintel_pcie's
+ * MSIX/doorbell flow, or another vendor's equivalent) calls into, so
+ * writing this hook exercises the full net/bluetooth rfkill core
+ * notification chain (hci_rfkill_set_block() -> HCI_RFKILLED flag ->
+ * device power on/off) without needing the driver to assert its real HW
+ * rfkill line. This tool writes that hook to simulate the HW rfkill line
+ * toggling and reads back both the debugfs state and the corresponding
+ * /sys/class/rfkill hard-block state to confirm the hci_dev (and rfkill
+ * core) picked up the change.
+ *
+ * Note: this only covers the net/bluetooth stack side of the flow. The
+ * driver-side interrupt/doorbell/register-read logic that decides when to
+ * call hci_rfkill_set_hw_state() in the first place is vendor/transport
+ * specific and is not exercised by this tool; that still requires real
+ * hardware or a transport-level (e.g. PCIe) emulation to test.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hci.h"
+
+#define DEBUGFS_PATH "/sys/kernel/debug/bluetooth"
+#define RFKILL_CLASS_PATH "/sys/class/rfkill"
+
+static bool verbose;
+
+static void print_verbose(const char *format, ...)
+{
+ va_list ap;
+
+ if (!verbose)
+ return;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+
+static int debugfs_hw_rfkill_write(const char *hci_name, bool enable)
+{
+ char path[128];
+ int fd, err;
+ char val = enable ? 'Y' : 'N';
+
+ snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
+ hci_name);
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0) {
+ err = -errno;
+ fprintf(stderr, "Failed to open %s: %s\n", path,
+ strerror(errno));
+ return err;
+ }
+
+ if (write(fd, &val, sizeof(val)) != sizeof(val)) {
+ err = -errno;
+ fprintf(stderr, "Failed to write %s: %s\n", path,
+ strerror(errno));
+ close(fd);
+ return err;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+static int debugfs_hw_rfkill_read(const char *hci_name, bool *enable)
+{
+ char path[128];
+ char buf[8];
+ int fd, err;
+ ssize_t len;
+
+ snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
+ hci_name);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ err = -errno;
+ fprintf(stderr, "Failed to open %s: %s\n", path,
+ strerror(errno));
+ return err;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ len = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+
+ if (len < 1)
+ return -EIO;
+
+ *enable = (buf[0] == 'Y');
+
+ return 0;
+}
+
+/* Locate the /sys/class/rfkill/rfkillN entry whose "name" matches the given
+ * hci device name (e.g. "hci0") and read back its hard-block state.
+ */
+static int rfkill_class_hard_read(const char *hci_name, bool *blocked)
+{
+ DIR *dir;
+ struct dirent *entry;
+ int ret = -ENOENT;
+ int val;
+
+ dir = opendir(RFKILL_CLASS_PATH);
+ if (!dir)
+ return -errno;
+
+ while ((entry = readdir(dir)) != NULL) {
+ char path[PATH_MAX];
+ char name[64];
+ FILE *f;
+
+ if (strncmp(entry->d_name, "rfkill", 6))
+ continue;
+
+ snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/name",
+ entry->d_name);
+
+ f = fopen(path, "r");
+ if (!f)
+ continue;
+
+ memset(name, 0, sizeof(name));
+ if (!fgets(name, sizeof(name), f)) {
+ fclose(f);
+ continue;
+ }
+ fclose(f);
+
+ name[strcspn(name, "\n")] = '\0';
+
+ if (strcmp(name, hci_name))
+ continue;
+
+ snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/hard",
+ entry->d_name);
+
+ f = fopen(path, "r");
+ if (!f) {
+ ret = -errno;
+ break;
+ }
+
+ if (fscanf(f, "%d", &val) == 1) {
+ *blocked = (val != 0);
+ ret = 0;
+ } else {
+ ret = -EIO;
+ }
+ fclose(f);
+ break;
+ }
+
+ closedir(dir);
+
+ return ret;
+}
+
+/* Parse the numeric device id out of an "hciN" name without requiring the
+ * device to be up (unlike lib/bluetooth's hci_devid(), which internally
+ * calls hci_devba() and fails with ENETDOWN on a down/blocked device --
+ * exactly the state this tool may have just put the adapter into).
+ */
+static int hci_name_to_id(const char *hci_name)
+{
+ const char *p;
+ char *end;
+ unsigned long id;
+
+ if (strncmp(hci_name, "hci", 3))
+ return -1;
+
+ p = hci_name + 3;
+ if (!*p)
+ return -1;
+
+ while (*p) {
+ if (!isdigit((unsigned char)*p))
+ return -1;
+ p++;
+ }
+
+ errno = 0;
+ id = strtoul(hci_name + 3, &end, 10);
+ if (errno == ERANGE || *end || id > USHRT_MAX)
+ return -1;
+
+ return id;
+}
+
+/* Query whether the given hci device currently has HCI_UP set, using the
+ * same raw HCI control socket + ioctl approach as tools/hciconfig.c.
+ */
+static int hci_dev_is_up(int hdev_id, bool *up)
+{
+ struct hci_dev_info di;
+ int ctl, err;
+
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ if (ctl < 0)
+ return -errno;
+
+ memset(&di, 0, sizeof(di));
+ di.dev_id = hdev_id;
+
+ if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0) {
+ err = -errno;
+ close(ctl);
+ return err;
+ }
+
+ close(ctl);
+
+ *up = !!(di.flags & (1U << HCI_UP));
+
+ return 0;
+}
+
+/* Bring the given hci device up or down via HCIDEVUP/HCIDEVDOWN, mirroring
+ * tools/hciconfig.c's cmd_up()/cmd_down(). Used to restore the adapter to
+ * its pre-test state, since unblocking HW rfkill does not automatically
+ * reopen the device (that is normally done by bluetoothd reacting to the
+ * rfkill unblock event via mgmt).
+ */
+static int hci_dev_set_up(int hdev_id, bool up)
+{
+ int ctl, err;
+
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ if (ctl < 0)
+ return -errno;
+
+ if (ioctl(ctl, up ? HCIDEVUP : HCIDEVDOWN, hdev_id) < 0) {
+ err = -errno;
+ close(ctl);
+ /* HCIDEVUP returns EALREADY if the device is already up
+ * (mirrors tools/hciconfig.c's cmd_up()); HCIDEVDOWN has
+ * no such case.
+ */
+ return (up && err == -EALREADY) ? 0 : err;
+ }
+
+ close(ctl);
+
+ return 0;
+}
+
+static int run_test(const char *hci_name, bool enable, bool *write_succeeded)
+{
+ bool val;
+ int err;
+
+ *write_succeeded = false;
+ printf("Setting HW rfkill %s on %s\n", enable ? "ON" : "OFF",
+ hci_name);
+
+ err = debugfs_hw_rfkill_write(hci_name, enable);
+ if (err)
+ return err;
+ *write_succeeded = true;
+
+ err = debugfs_hw_rfkill_read(hci_name, &val);
+ if (err) {
+ fprintf(stderr, "Failed to read back debugfs state: %s\n",
+ strerror(-err));
+ return err;
+ }
+
+ if (val != enable) {
+ fprintf(stderr, "debugfs force_hw_rfkill mismatch: wrote %d, "
+ "read %d\n", enable, val);
+ return -EIO;
+ }
+
+ print_verbose("debugfs force_hw_rfkill now reports: %s\n",
+ val ? "Y" : "N");
+
+ err = rfkill_class_hard_read(hci_name, &val);
+ if (err) {
+ fprintf(stderr, "Failed to read rfkill class state for %s: "
+ "%s\n", hci_name, strerror(-err));
+ return err;
+ }
+
+ print_verbose("rfkill class hard-block state: %s\n",
+ val ? "blocked" : "unblocked");
+
+ if (val != enable) {
+ fprintf(stderr, "rfkill class state mismatch: expected "
+ "%s, got %s\n",
+ enable ? "blocked" : "unblocked",
+ val ? "blocked" : "unblocked");
+ return -EIO;
+ }
+
+ printf("OK: hci_dev and rfkill class agree HW rfkill is %s\n",
+ enable ? "ON" : "OFF");
+
+ return 0;
+}
+
+static void usage(void)
+{
+ printf("hci_rfkill - generic HW rfkill stack test driver\n"
+ "Usage:\n");
+ printf("\thci_rfkill [options] <hciN>\n");
+ printf("options:\n"
+ "\t-t, --toggle Toggle HW rfkill on then off "
+ "(default)\n"
+ "\t-e, --enable Only assert HW rfkill (block)\n"
+ "\t-d, --disable Only de-assert HW rfkill (unblock)\n"
+ "\t-r, --restore Restore the adapter to its pre-test\n"
+ "\t up/down state afterwards (unblocking\n"
+ "\t HW rfkill does not reopen the device\n"
+ "\t on its own; that is normally done by\n"
+ "\t bluetoothd reacting to the rfkill\n"
+ "\t event via mgmt). Note: combined with\n"
+ "\t -e, restoring an adapter that was up\n"
+ "\t will fail with ERFKILL, since it is\n"
+ "\t left HW-blocked on purpose\n"
+ "\t-v, --verbose Show verbose output\n"
+ "\t-h, --help Show help options\n");
+}
+
+static const struct option main_options[] = {
+ { "toggle", no_argument, NULL, 't' },
+ { "enable", no_argument, NULL, 'e' },
+ { "disable", no_argument, NULL, 'd' },
+ { "restore", no_argument, NULL, 'r' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "help", no_argument, NULL, 'h' },
+ { }
+};
+
+int main(int argc, char *argv[])
+{
+ const char *hci_name;
+ int mode = 't';
+ bool restore = false;
+ int opt;
+ int hdev_id = -1;
+ bool was_up = false;
+ bool have_pre_state = false;
+ bool write_succeeded;
+ int ret = EXIT_SUCCESS;
+ int err;
+
+ while ((opt = getopt_long(argc, argv, "tedrvh",
+ main_options, NULL)) != -1) {
+ switch (opt) {
+ case 't':
+ case 'e':
+ case 'd':
+ mode = opt;
+ break;
+ case 'r':
+ restore = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'h':
+ usage();
+ return EXIT_SUCCESS;
+ default:
+ usage();
+ return EXIT_FAILURE;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ return EXIT_FAILURE;
+ }
+
+ hci_name = argv[0];
+
+ hdev_id = hci_name_to_id(hci_name);
+ if (hdev_id < 0) {
+ fprintf(stderr, "Invalid hci device name %s\n", hci_name);
+ return EXIT_FAILURE;
+ }
+
+ if (restore) {
+ err = hci_dev_is_up(hdev_id, &was_up);
+ if (err) {
+ fprintf(stderr, "Failed to query %s up state: %s\n",
+ hci_name, strerror(-err));
+ return EXIT_FAILURE;
+ }
+
+ have_pre_state = true;
+ print_verbose("%s pre-test state: %s\n", hci_name,
+ was_up ? "up" : "down");
+ }
+
+ switch (mode) {
+ case 'e':
+ if (run_test(hci_name, true, &write_succeeded))
+ ret = EXIT_FAILURE;
+ break;
+ case 'd':
+ if (run_test(hci_name, false, &write_succeeded))
+ ret = EXIT_FAILURE;
+ break;
+ case 't':
+ default:
+ err = run_test(hci_name, true, &write_succeeded);
+ if (err)
+ ret = EXIT_FAILURE;
+ if (write_succeeded) {
+ if (run_test(hci_name, false, &write_succeeded))
+ ret = EXIT_FAILURE;
+ }
+ break;
+ }
+
+ if (restore && have_pre_state) {
+ err = hci_dev_set_up(hdev_id, was_up);
+ if (err) {
+ fprintf(stderr, "Failed to restore %s to its "
+ "pre-test %s state: %s\n", hci_name,
+ was_up ? "up" : "down",
+ strerror(-err));
+ ret = EXIT_FAILURE;
+ } else {
+ print_verbose("%s restored to pre-test state: %s\n",
+ hci_name, was_up ? "up" : "down");
+ }
+ }
+
+ return ret;
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v1 1/1] emulator: add generic HW rfkill test tool
2026-09-30 7:42 ` [PATCH v1 1/1] " Sai Teja Aluvala
@ 2026-09-30 12:53 ` Luiz Augusto von Dentz
2026-09-30 14:41 ` Aluvala Sai Teja
2026-09-30 15:12 ` bluez.test.bot
1 sibling, 1 reply; 7+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-30 12:53 UTC (permalink / raw)
To: Sai Teja Aluvala; +Cc: linux-bluetooth, kiran.k, chethan.tumkur.narayan
Hi Sai,
On Wed, Sep 30, 2026 at 3:53 AM Sai Teja Aluvala
<aluvala.sai.teja@intel.com> wrote:
>
> Add emulator/hci_rfkill, a standalone tool that drives the
> force_hw_rfkill debugfs hook for an hciN device and verifies the
> reported hardware-block state against /sys/class/rfkill. The hook
> exercises the generic net/bluetooth rfkill path for any HCI transport;
> it does not test the driver's hardware interrupt or register handling.
>
> Support assert-only, deassert-only, and toggle modes. Validate the
> hciN argument before using it in filesystem paths. In toggle mode,
> if asserting the block was written successfully but subsequent
> verification fails, still attempt a verified unblock while preserving
> the failure result. Do not attempt an unblock when the assert write
> itself fails.
>
> The optional -r/--restore mode snapshots the adapter's up/down state
> via HCIGETDEVINFO and restores it via HCIDEVUP/HCIDEVDOWN, following
> the raw HCI control-socket approach used by tools/hciconfig.c.
>
> Usage: hci_rfkill [-e|-d|-t] [-r] hciN
I have a hard time understanding why we can't just use the `rfkill`
tool for this purpose, or does it not support hardware-block? It seems
it does:
rfkill --help
Usage:
rfkill [options] command [identifier ...]
Tool for enabling and disabling wireless devices.
Options:
-J, --json use JSON output format
-n, --noheadings don't print headings
-o, --output <list> define which output columns to use
--output-all output all columns
-r, --raw use the raw output format
-h, --help display this help
-V, --version display version
Available output columns:
DEVICE kernel device name
ID device identifier value
TYPE device type name that can be used as identifier
TYPE-DESC device type description
SOFT status of software block
-> HARD status of hardware block
>
> Signed-off-by: Sai Teja Aluvala <aluvala.sai.teja@intel.com>
> ---
> Makefile.tools | 3 +
> emulator/hci_rfkill.c | 478 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 481 insertions(+)
> create mode 100644 emulator/hci_rfkill.c
>
> diff --git a/Makefile.tools b/Makefile.tools
> index 2914bf1c0..4e5d0d976 100644
> --- a/Makefile.tools
> +++ b/Makefile.tools
> @@ -85,6 +85,7 @@ endif
>
> if TESTING
> noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \
> + emulator/hci_rfkill \
> peripheral/btsensor tools/3dsp \
> tools/mgmt-tester tools/gap-tester \
> tools/l2cap-tester tools/sco-tester \
> @@ -111,6 +112,8 @@ emulator_b1ee_LDADD = src/libshared-mainloop.la
> emulator_hfp_SOURCES = emulator/hfp.c
> emulator_hfp_LDADD = src/libshared-mainloop.la
>
> +emulator_hci_rfkill_SOURCES = emulator/hci_rfkill.c
> +
> peripheral_btsensor_SOURCES = peripheral/main.c \
> peripheral/efivars.h peripheral/efivars.c \
> peripheral/attach.h peripheral/attach.c \
> diff --git a/emulator/hci_rfkill.c b/emulator/hci_rfkill.c
> new file mode 100644
> index 000000000..f5ef5e3bb
> --- /dev/null
> +++ b/emulator/hci_rfkill.c
> @@ -0,0 +1,478 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2026 Intel Corporation
> + *
> + */
> +
> +/*
> + * hci_rfkill - generic HW rfkill stack test driver.
> + *
> + * Applies to any hci_dev with rfkill registered, regardless of vendor or
> + * transport (USB, UART, PCIe, virtual, etc.). Many transport drivers (e.g.
> + * btintel_pcie) are bound to real hardware and have no userspace-attachable
> + * transport to emulate the way emulator/vhci.c does for /dev/vhci. Instead,
> + * net/bluetooth's hci_debugfs.c exposes a generic debugfs test hook
> + * ("force_hw_rfkill" under /sys/kernel/debug/bluetooth/hciN/) that calls
> + * hci_rfkill_set_hw_state() directly. That is the exact same entry point
> + * any driver's real HW rfkill interrupt path (e.g. btintel_pcie's
> + * MSIX/doorbell flow, or another vendor's equivalent) calls into, so
> + * writing this hook exercises the full net/bluetooth rfkill core
> + * notification chain (hci_rfkill_set_block() -> HCI_RFKILLED flag ->
> + * device power on/off) without needing the driver to assert its real HW
> + * rfkill line. This tool writes that hook to simulate the HW rfkill line
> + * toggling and reads back both the debugfs state and the corresponding
> + * /sys/class/rfkill hard-block state to confirm the hci_dev (and rfkill
> + * core) picked up the change.
> + *
> + * Note: this only covers the net/bluetooth stack side of the flow. The
> + * driver-side interrupt/doorbell/register-read logic that decides when to
> + * call hci_rfkill_set_hw_state() in the first place is vendor/transport
> + * specific and is not exercised by this tool; that still requires real
> + * hardware or a transport-level (e.g. PCIe) emulation to test.
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdio.h>
> +#include <stdbool.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <ctype.h>
> +#include <getopt.h>
> +#include <dirent.h>
> +#include <limits.h>
> +#include <stdarg.h>
> +#include <sys/socket.h>
> +#include <sys/ioctl.h>
> +
> +#include "bluetooth/bluetooth.h"
> +#include "bluetooth/hci.h"
> +
> +#define DEBUGFS_PATH "/sys/kernel/debug/bluetooth"
> +#define RFKILL_CLASS_PATH "/sys/class/rfkill"
> +
> +static bool verbose;
> +
> +static void print_verbose(const char *format, ...)
> +{
> + va_list ap;
> +
> + if (!verbose)
> + return;
> +
> + va_start(ap, format);
> + vprintf(format, ap);
> + va_end(ap);
> +}
> +
> +static int debugfs_hw_rfkill_write(const char *hci_name, bool enable)
> +{
> + char path[128];
> + int fd, err;
> + char val = enable ? 'Y' : 'N';
> +
> + snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
> + hci_name);
> +
> + fd = open(path, O_WRONLY);
> + if (fd < 0) {
> + err = -errno;
> + fprintf(stderr, "Failed to open %s: %s\n", path,
> + strerror(errno));
> + return err;
> + }
> +
> + if (write(fd, &val, sizeof(val)) != sizeof(val)) {
> + err = -errno;
> + fprintf(stderr, "Failed to write %s: %s\n", path,
> + strerror(errno));
> + close(fd);
> + return err;
> + }
> +
> + close(fd);
> +
> + return 0;
> +}
> +
> +static int debugfs_hw_rfkill_read(const char *hci_name, bool *enable)
> +{
> + char path[128];
> + char buf[8];
> + int fd, err;
> + ssize_t len;
> +
> + snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
> + hci_name);
> +
> + fd = open(path, O_RDONLY);
> + if (fd < 0) {
> + err = -errno;
> + fprintf(stderr, "Failed to open %s: %s\n", path,
> + strerror(errno));
> + return err;
> + }
> +
> + memset(buf, 0, sizeof(buf));
> + len = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> +
> + if (len < 1)
> + return -EIO;
> +
> + *enable = (buf[0] == 'Y');
> +
> + return 0;
> +}
> +
> +/* Locate the /sys/class/rfkill/rfkillN entry whose "name" matches the given
> + * hci device name (e.g. "hci0") and read back its hard-block state.
> + */
> +static int rfkill_class_hard_read(const char *hci_name, bool *blocked)
> +{
> + DIR *dir;
> + struct dirent *entry;
> + int ret = -ENOENT;
> + int val;
> +
> + dir = opendir(RFKILL_CLASS_PATH);
> + if (!dir)
> + return -errno;
> +
> + while ((entry = readdir(dir)) != NULL) {
> + char path[PATH_MAX];
> + char name[64];
> + FILE *f;
> +
> + if (strncmp(entry->d_name, "rfkill", 6))
> + continue;
> +
> + snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/name",
> + entry->d_name);
> +
> + f = fopen(path, "r");
> + if (!f)
> + continue;
> +
> + memset(name, 0, sizeof(name));
> + if (!fgets(name, sizeof(name), f)) {
> + fclose(f);
> + continue;
> + }
> + fclose(f);
> +
> + name[strcspn(name, "\n")] = '\0';
> +
> + if (strcmp(name, hci_name))
> + continue;
> +
> + snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/hard",
> + entry->d_name);
> +
> + f = fopen(path, "r");
> + if (!f) {
> + ret = -errno;
> + break;
> + }
> +
> + if (fscanf(f, "%d", &val) == 1) {
> + *blocked = (val != 0);
> + ret = 0;
> + } else {
> + ret = -EIO;
> + }
> + fclose(f);
> + break;
> + }
> +
> + closedir(dir);
> +
> + return ret;
> +}
> +
> +/* Parse the numeric device id out of an "hciN" name without requiring the
> + * device to be up (unlike lib/bluetooth's hci_devid(), which internally
> + * calls hci_devba() and fails with ENETDOWN on a down/blocked device --
> + * exactly the state this tool may have just put the adapter into).
> + */
> +static int hci_name_to_id(const char *hci_name)
> +{
> + const char *p;
> + char *end;
> + unsigned long id;
> +
> + if (strncmp(hci_name, "hci", 3))
> + return -1;
> +
> + p = hci_name + 3;
> + if (!*p)
> + return -1;
> +
> + while (*p) {
> + if (!isdigit((unsigned char)*p))
> + return -1;
> + p++;
> + }
> +
> + errno = 0;
> + id = strtoul(hci_name + 3, &end, 10);
> + if (errno == ERANGE || *end || id > USHRT_MAX)
> + return -1;
> +
> + return id;
> +}
> +
> +/* Query whether the given hci device currently has HCI_UP set, using the
> + * same raw HCI control socket + ioctl approach as tools/hciconfig.c.
> + */
> +static int hci_dev_is_up(int hdev_id, bool *up)
> +{
> + struct hci_dev_info di;
> + int ctl, err;
> +
> + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> + if (ctl < 0)
> + return -errno;
> +
> + memset(&di, 0, sizeof(di));
> + di.dev_id = hdev_id;
> +
> + if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0) {
> + err = -errno;
> + close(ctl);
> + return err;
> + }
> +
> + close(ctl);
> +
> + *up = !!(di.flags & (1U << HCI_UP));
> +
> + return 0;
> +}
> +
> +/* Bring the given hci device up or down via HCIDEVUP/HCIDEVDOWN, mirroring
> + * tools/hciconfig.c's cmd_up()/cmd_down(). Used to restore the adapter to
> + * its pre-test state, since unblocking HW rfkill does not automatically
> + * reopen the device (that is normally done by bluetoothd reacting to the
> + * rfkill unblock event via mgmt).
> + */
> +static int hci_dev_set_up(int hdev_id, bool up)
> +{
> + int ctl, err;
> +
> + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> + if (ctl < 0)
> + return -errno;
> +
> + if (ioctl(ctl, up ? HCIDEVUP : HCIDEVDOWN, hdev_id) < 0) {
> + err = -errno;
> + close(ctl);
> + /* HCIDEVUP returns EALREADY if the device is already up
> + * (mirrors tools/hciconfig.c's cmd_up()); HCIDEVDOWN has
> + * no such case.
> + */
> + return (up && err == -EALREADY) ? 0 : err;
> + }
> +
> + close(ctl);
> +
> + return 0;
> +}
> +
> +static int run_test(const char *hci_name, bool enable, bool *write_succeeded)
> +{
> + bool val;
> + int err;
> +
> + *write_succeeded = false;
> + printf("Setting HW rfkill %s on %s\n", enable ? "ON" : "OFF",
> + hci_name);
> +
> + err = debugfs_hw_rfkill_write(hci_name, enable);
> + if (err)
> + return err;
> + *write_succeeded = true;
> +
> + err = debugfs_hw_rfkill_read(hci_name, &val);
> + if (err) {
> + fprintf(stderr, "Failed to read back debugfs state: %s\n",
> + strerror(-err));
> + return err;
> + }
> +
> + if (val != enable) {
> + fprintf(stderr, "debugfs force_hw_rfkill mismatch: wrote %d, "
> + "read %d\n", enable, val);
> + return -EIO;
> + }
> +
> + print_verbose("debugfs force_hw_rfkill now reports: %s\n",
> + val ? "Y" : "N");
> +
> + err = rfkill_class_hard_read(hci_name, &val);
> + if (err) {
> + fprintf(stderr, "Failed to read rfkill class state for %s: "
> + "%s\n", hci_name, strerror(-err));
> + return err;
> + }
> +
> + print_verbose("rfkill class hard-block state: %s\n",
> + val ? "blocked" : "unblocked");
> +
> + if (val != enable) {
> + fprintf(stderr, "rfkill class state mismatch: expected "
> + "%s, got %s\n",
> + enable ? "blocked" : "unblocked",
> + val ? "blocked" : "unblocked");
> + return -EIO;
> + }
> +
> + printf("OK: hci_dev and rfkill class agree HW rfkill is %s\n",
> + enable ? "ON" : "OFF");
> +
> + return 0;
> +}
> +
> +static void usage(void)
> +{
> + printf("hci_rfkill - generic HW rfkill stack test driver\n"
> + "Usage:\n");
> + printf("\thci_rfkill [options] <hciN>\n");
> + printf("options:\n"
> + "\t-t, --toggle Toggle HW rfkill on then off "
> + "(default)\n"
> + "\t-e, --enable Only assert HW rfkill (block)\n"
> + "\t-d, --disable Only de-assert HW rfkill (unblock)\n"
> + "\t-r, --restore Restore the adapter to its pre-test\n"
> + "\t up/down state afterwards (unblocking\n"
> + "\t HW rfkill does not reopen the device\n"
> + "\t on its own; that is normally done by\n"
> + "\t bluetoothd reacting to the rfkill\n"
> + "\t event via mgmt). Note: combined with\n"
> + "\t -e, restoring an adapter that was up\n"
> + "\t will fail with ERFKILL, since it is\n"
> + "\t left HW-blocked on purpose\n"
> + "\t-v, --verbose Show verbose output\n"
> + "\t-h, --help Show help options\n");
> +}
> +
> +static const struct option main_options[] = {
> + { "toggle", no_argument, NULL, 't' },
> + { "enable", no_argument, NULL, 'e' },
> + { "disable", no_argument, NULL, 'd' },
> + { "restore", no_argument, NULL, 'r' },
> + { "verbose", no_argument, NULL, 'v' },
> + { "help", no_argument, NULL, 'h' },
> + { }
> +};
> +
> +int main(int argc, char *argv[])
> +{
> + const char *hci_name;
> + int mode = 't';
> + bool restore = false;
> + int opt;
> + int hdev_id = -1;
> + bool was_up = false;
> + bool have_pre_state = false;
> + bool write_succeeded;
> + int ret = EXIT_SUCCESS;
> + int err;
> +
> + while ((opt = getopt_long(argc, argv, "tedrvh",
> + main_options, NULL)) != -1) {
> + switch (opt) {
> + case 't':
> + case 'e':
> + case 'd':
> + mode = opt;
> + break;
> + case 'r':
> + restore = true;
> + break;
> + case 'v':
> + verbose = true;
> + break;
> + case 'h':
> + usage();
> + return EXIT_SUCCESS;
> + default:
> + usage();
> + return EXIT_FAILURE;
> + }
> + }
> +
> + argc -= optind;
> + argv += optind;
> +
> + if (argc < 1) {
> + usage();
> + return EXIT_FAILURE;
> + }
> +
> + hci_name = argv[0];
> +
> + hdev_id = hci_name_to_id(hci_name);
> + if (hdev_id < 0) {
> + fprintf(stderr, "Invalid hci device name %s\n", hci_name);
> + return EXIT_FAILURE;
> + }
> +
> + if (restore) {
> + err = hci_dev_is_up(hdev_id, &was_up);
> + if (err) {
> + fprintf(stderr, "Failed to query %s up state: %s\n",
> + hci_name, strerror(-err));
> + return EXIT_FAILURE;
> + }
> +
> + have_pre_state = true;
> + print_verbose("%s pre-test state: %s\n", hci_name,
> + was_up ? "up" : "down");
> + }
> +
> + switch (mode) {
> + case 'e':
> + if (run_test(hci_name, true, &write_succeeded))
> + ret = EXIT_FAILURE;
> + break;
> + case 'd':
> + if (run_test(hci_name, false, &write_succeeded))
> + ret = EXIT_FAILURE;
> + break;
> + case 't':
> + default:
> + err = run_test(hci_name, true, &write_succeeded);
> + if (err)
> + ret = EXIT_FAILURE;
> + if (write_succeeded) {
> + if (run_test(hci_name, false, &write_succeeded))
> + ret = EXIT_FAILURE;
> + }
> + break;
> + }
> +
> + if (restore && have_pre_state) {
> + err = hci_dev_set_up(hdev_id, was_up);
> + if (err) {
> + fprintf(stderr, "Failed to restore %s to its "
> + "pre-test %s state: %s\n", hci_name,
> + was_up ? "up" : "down",
> + strerror(-err));
> + ret = EXIT_FAILURE;
> + } else {
> + print_verbose("%s restored to pre-test state: %s\n",
> + hci_name, was_up ? "up" : "down");
> + }
> + }
> +
> + return ret;
> +}
> --
> 2.53.0
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH v1 1/1] emulator: add generic HW rfkill test tool
2026-09-30 12:53 ` Luiz Augusto von Dentz
@ 2026-09-30 14:41 ` Aluvala Sai Teja
2026-09-30 14:59 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 7+ messages in thread
From: Aluvala Sai Teja @ 2026-09-30 14:41 UTC (permalink / raw)
To: Luiz Augusto von Dentz
Cc: linux-bluetooth@vger.kernel.org, K, Kiran,
Tumkur Narayan, Chethan
Hi Sai,
On Wed, Sep 30, 2026 at 3:53 AM Sai Teja Aluvala <aluvala.sai.teja@intel.com> wrote:
>
> Add emulator/hci_rfkill, a standalone tool that drives the
> force_hw_rfkill debugfs hook for an hciN device and verifies the
> reported hardware-block state against /sys/class/rfkill. The hook
> exercises the generic net/bluetooth rfkill path for any HCI transport;
> it does not test the driver's hardware interrupt or register handling.
>
> Support assert-only, deassert-only, and toggle modes. Validate the
> hciN argument before using it in filesystem paths. In toggle mode, if
> asserting the block was written successfully but subsequent
> verification fails, still attempt a verified unblock while preserving
> the failure result. Do not attempt an unblock when the assert write
> itself fails.
>
> The optional -r/--restore mode snapshots the adapter's up/down state
> via HCIGETDEVINFO and restores it via HCIDEVUP/HCIDEVDOWN, following
> the raw HCI control-socket approach used by tools/hciconfig.c.
>
> Usage: hci_rfkill [-e|-d|-t] [-r] hciN
I have a hard time understanding why we can't just use the `rfkill` tool for this purpose, or does it not support hardware-block? It seems it does:
rfkill --help
Usage:
rfkill [options] command [identifier ...]
Tool for enabling and disabling wireless devices.
Options:
-J, --json use JSON output format
-n, --noheadings don't print headings
-o, --output <list> define which output columns to use
--output-all output all columns
-r, --raw use the raw output format
-h, --help display this help
-V, --version display version
Available output columns:
DEVICE kernel device name
ID device identifier value
TYPE device type name that can be used as identifier
TYPE-DESC device type description
SOFT status of software block
-> HARD status of hardware block
[Sai]: The rfkill tool can list RFKILL devices and control the software
block with `rfkill block` and `rfkill unblock`, but it cannot synthesize a
hardware rfkill block. The hardware state is normally reported by the transport
driver and is read-only from userspace.
To simulate a hardware block for testing, we still need a kernel-side
injection point, such as a debugfs entry, that invokes the same HCI
hardware-state notification path used by the driver.
however, The test tool does not need a separate RFKILL-core API for validation. After
writing the simulated state through debugfs, userspace can read the existing
`/sys/class/rfkill/rfkillN/hard` entry. Therefore, the debugfs entry would
only provide event injection; the sysfs interface would provide validation.
The debugfs read operation and the separate `rfkill_hard_blocked()` helper
are not required. we can drop them
The reason I considered the emulator tool is that the existing `rfkill`
injection path, changes in `net/bluetooth` such as the HCI RFKILL
notification, `HCI_RFKILLED` handling, and the resulting controller
power-transition work are not directly validated for hw rfkill.they are
validated for sw rfkill path
my understanding is that the existing `rfkill` command is useful for
testing the software block/unblock path, while
`/sys/class/rfkill/rfkillN/hard` allows userspace to inspect the hardware
block bit. Neither interface injects a hardware event or exercises the
complete Bluetooth stack path.
Do you think it is worthwhile to keep a small `hci_rfkill` emulator tool to
inject the hardware state and validate the HCI RFKILL notification,
`HCI_RFKILLED` handling, and controller power-transition path in stack? If only
SW RFKILL state validation is needed, then the emulator can be dropped; if the
goal is complete Bluetooth stack validation for hw rfkill, I think keeping it is
worthwhile. I would appreciate your suggestion on which scope is preferred.
>
> Signed-off-by: Sai Teja Aluvala <aluvala.sai.teja@intel.com>
> ---
> Makefile.tools | 3 +
> emulator/hci_rfkill.c | 478
> ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 481 insertions(+)
> create mode 100644 emulator/hci_rfkill.c
>
> diff --git a/Makefile.tools b/Makefile.tools index
> 2914bf1c0..4e5d0d976 100644
> --- a/Makefile.tools
> +++ b/Makefile.tools
> @@ -85,6 +85,7 @@ endif
>
> if TESTING
> noinst_PROGRAMS += emulator/btvirt emulator/b1ee emulator/hfp \
> + emulator/hci_rfkill \
> peripheral/btsensor tools/3dsp \
> tools/mgmt-tester tools/gap-tester \
> tools/l2cap-tester
> tools/sco-tester \ @@ -111,6 +112,8 @@ emulator_b1ee_LDADD =
> src/libshared-mainloop.la emulator_hfp_SOURCES = emulator/hfp.c
> emulator_hfp_LDADD = src/libshared-mainloop.la
>
> +emulator_hci_rfkill_SOURCES = emulator/hci_rfkill.c
> +
> peripheral_btsensor_SOURCES = peripheral/main.c \
> peripheral/efivars.h peripheral/efivars.c \
> peripheral/attach.h
> peripheral/attach.c \ diff --git a/emulator/hci_rfkill.c
> b/emulator/hci_rfkill.c new file mode 100644 index
> 000000000..f5ef5e3bb
> --- /dev/null
> +++ b/emulator/hci_rfkill.c
> @@ -0,0 +1,478 @@
> +// SPDX-License-Identifier: LGPL-2.1-or-later
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2026 Intel Corporation
> + *
> + */
> +
> +/*
> + * hci_rfkill - generic HW rfkill stack test driver.
> + *
> + * Applies to any hci_dev with rfkill registered, regardless of
> +vendor or
> + * transport (USB, UART, PCIe, virtual, etc.). Many transport drivers (e.g.
> + * btintel_pcie) are bound to real hardware and have no
> +userspace-attachable
> + * transport to emulate the way emulator/vhci.c does for /dev/vhci.
> +Instead,
> + * net/bluetooth's hci_debugfs.c exposes a generic debugfs test hook
> + * ("force_hw_rfkill" under /sys/kernel/debug/bluetooth/hciN/) that
> +calls
> + * hci_rfkill_set_hw_state() directly. That is the exact same entry
> +point
> + * any driver's real HW rfkill interrupt path (e.g. btintel_pcie's
> + * MSIX/doorbell flow, or another vendor's equivalent) calls into, so
> + * writing this hook exercises the full net/bluetooth rfkill core
> + * notification chain (hci_rfkill_set_block() -> HCI_RFKILLED flag ->
> + * device power on/off) without needing the driver to assert its real
> +HW
> + * rfkill line. This tool writes that hook to simulate the HW rfkill
> +line
> + * toggling and reads back both the debugfs state and the
> +corresponding
> + * /sys/class/rfkill hard-block state to confirm the hci_dev (and
> +rfkill
> + * core) picked up the change.
> + *
> + * Note: this only covers the net/bluetooth stack side of the flow.
> +The
> + * driver-side interrupt/doorbell/register-read logic that decides
> +when to
> + * call hci_rfkill_set_hw_state() in the first place is
> +vendor/transport
> + * specific and is not exercised by this tool; that still requires
> +real
> + * hardware or a transport-level (e.g. PCIe) emulation to test.
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#include <stdio.h>
> +#include <stdbool.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <ctype.h>
> +#include <getopt.h>
> +#include <dirent.h>
> +#include <limits.h>
> +#include <stdarg.h>
> +#include <sys/socket.h>
> +#include <sys/ioctl.h>
> +
> +#include "bluetooth/bluetooth.h"
> +#include "bluetooth/hci.h"
> +
> +#define DEBUGFS_PATH "/sys/kernel/debug/bluetooth"
> +#define RFKILL_CLASS_PATH "/sys/class/rfkill"
> +
> +static bool verbose;
> +
> +static void print_verbose(const char *format, ...) {
> + va_list ap;
> +
> + if (!verbose)
> + return;
> +
> + va_start(ap, format);
> + vprintf(format, ap);
> + va_end(ap);
> +}
> +
> +static int debugfs_hw_rfkill_write(const char *hci_name, bool enable)
> +{
> + char path[128];
> + int fd, err;
> + char val = enable ? 'Y' : 'N';
> +
> + snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
> + hci_name);
> +
> + fd = open(path, O_WRONLY);
> + if (fd < 0) {
> + err = -errno;
> + fprintf(stderr, "Failed to open %s: %s\n", path,
> + strerror(errno));
> + return err;
> + }
> +
> + if (write(fd, &val, sizeof(val)) != sizeof(val)) {
> + err = -errno;
> + fprintf(stderr, "Failed to write %s: %s\n", path,
> + strerror(errno));
> + close(fd);
> + return err;
> + }
> +
> + close(fd);
> +
> + return 0;
> +}
> +
> +static int debugfs_hw_rfkill_read(const char *hci_name, bool *enable)
> +{
> + char path[128];
> + char buf[8];
> + int fd, err;
> + ssize_t len;
> +
> + snprintf(path, sizeof(path), DEBUGFS_PATH "/%s/force_hw_rfkill",
> + hci_name);
> +
> + fd = open(path, O_RDONLY);
> + if (fd < 0) {
> + err = -errno;
> + fprintf(stderr, "Failed to open %s: %s\n", path,
> + strerror(errno));
> + return err;
> + }
> +
> + memset(buf, 0, sizeof(buf));
> + len = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> +
> + if (len < 1)
> + return -EIO;
> +
> + *enable = (buf[0] == 'Y');
> +
> + return 0;
> +}
> +
> +/* Locate the /sys/class/rfkill/rfkillN entry whose "name" matches
> +the given
> + * hci device name (e.g. "hci0") and read back its hard-block state.
> + */
> +static int rfkill_class_hard_read(const char *hci_name, bool
> +*blocked) {
> + DIR *dir;
> + struct dirent *entry;
> + int ret = -ENOENT;
> + int val;
> +
> + dir = opendir(RFKILL_CLASS_PATH);
> + if (!dir)
> + return -errno;
> +
> + while ((entry = readdir(dir)) != NULL) {
> + char path[PATH_MAX];
> + char name[64];
> + FILE *f;
> +
> + if (strncmp(entry->d_name, "rfkill", 6))
> + continue;
> +
> + snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/name",
> +
> + entry->d_name);
> +
> + f = fopen(path, "r");
> + if (!f)
> + continue;
> +
> + memset(name, 0, sizeof(name));
> + if (!fgets(name, sizeof(name), f)) {
> + fclose(f);
> + continue;
> + }
> + fclose(f);
> +
> + name[strcspn(name, "\n")] = '\0';
> +
> + if (strcmp(name, hci_name))
> + continue;
> +
> + snprintf(path, sizeof(path), RFKILL_CLASS_PATH "/%s/hard",
> +
> + entry->d_name);
> +
> + f = fopen(path, "r");
> + if (!f) {
> + ret = -errno;
> + break;
> + }
> +
> + if (fscanf(f, "%d", &val) == 1) {
> + *blocked = (val != 0);
> + ret = 0;
> + } else {
> + ret = -EIO;
> + }
> + fclose(f);
> + break;
> + }
> +
> + closedir(dir);
> +
> + return ret;
> +}
> +
> +/* Parse the numeric device id out of an "hciN" name without
> +requiring the
> + * device to be up (unlike lib/bluetooth's hci_devid(), which
> +internally
> + * calls hci_devba() and fails with ENETDOWN on a down/blocked device
> +--
> + * exactly the state this tool may have just put the adapter into).
> + */
> +static int hci_name_to_id(const char *hci_name) {
> + const char *p;
> + char *end;
> + unsigned long id;
> +
> + if (strncmp(hci_name, "hci", 3))
> + return -1;
> +
> + p = hci_name + 3;
> + if (!*p)
> + return -1;
> +
> + while (*p) {
> + if (!isdigit((unsigned char)*p))
> + return -1;
> + p++;
> + }
> +
> + errno = 0;
> + id = strtoul(hci_name + 3, &end, 10);
> + if (errno == ERANGE || *end || id > USHRT_MAX)
> + return -1;
> +
> + return id;
> +}
> +
> +/* Query whether the given hci device currently has HCI_UP set, using
> +the
> + * same raw HCI control socket + ioctl approach as tools/hciconfig.c.
> + */
> +static int hci_dev_is_up(int hdev_id, bool *up) {
> + struct hci_dev_info di;
> + int ctl, err;
> +
> + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> + if (ctl < 0)
> + return -errno;
> +
> + memset(&di, 0, sizeof(di));
> + di.dev_id = hdev_id;
> +
> + if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0) {
> + err = -errno;
> + close(ctl);
> + return err;
> + }
> +
> + close(ctl);
> +
> + *up = !!(di.flags & (1U << HCI_UP));
> +
> + return 0;
> +}
> +
> +/* Bring the given hci device up or down via HCIDEVUP/HCIDEVDOWN,
> +mirroring
> + * tools/hciconfig.c's cmd_up()/cmd_down(). Used to restore the
> +adapter to
> + * its pre-test state, since unblocking HW rfkill does not
> +automatically
> + * reopen the device (that is normally done by bluetoothd reacting to
> +the
> + * rfkill unblock event via mgmt).
> + */
> +static int hci_dev_set_up(int hdev_id, bool up) {
> + int ctl, err;
> +
> + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> + if (ctl < 0)
> + return -errno;
> +
> + if (ioctl(ctl, up ? HCIDEVUP : HCIDEVDOWN, hdev_id) < 0) {
> + err = -errno;
> + close(ctl);
> + /* HCIDEVUP returns EALREADY if the device is already up
> + * (mirrors tools/hciconfig.c's cmd_up()); HCIDEVDOWN has
> + * no such case.
> + */
> + return (up && err == -EALREADY) ? 0 : err;
> + }
> +
> + close(ctl);
> +
> + return 0;
> +}
> +
> +static int run_test(const char *hci_name, bool enable, bool
> +*write_succeeded) {
> + bool val;
> + int err;
> +
> + *write_succeeded = false;
> + printf("Setting HW rfkill %s on %s\n", enable ? "ON" : "OFF",
> +
> + hci_name);
> +
> + err = debugfs_hw_rfkill_write(hci_name, enable);
> + if (err)
> + return err;
> + *write_succeeded = true;
> +
> + err = debugfs_hw_rfkill_read(hci_name, &val);
> + if (err) {
> + fprintf(stderr, "Failed to read back debugfs state: %s\n",
> + strerror(-err));
> + return err;
> + }
> +
> + if (val != enable) {
> + fprintf(stderr, "debugfs force_hw_rfkill mismatch: wrote %d, "
> + "read %d\n", enable, val);
> + return -EIO;
> + }
> +
> + print_verbose("debugfs force_hw_rfkill now reports: %s\n",
> + val ? "Y" : "N");
> +
> + err = rfkill_class_hard_read(hci_name, &val);
> + if (err) {
> + fprintf(stderr, "Failed to read rfkill class state for %s: "
> + "%s\n", hci_name, strerror(-err));
> + return err;
> + }
> +
> + print_verbose("rfkill class hard-block state: %s\n",
> + val ? "blocked" :
> + "unblocked");
> +
> + if (val != enable) {
> + fprintf(stderr, "rfkill class state mismatch: expected "
> + "%s, got %s\n",
> + enable ? "blocked" : "unblocked",
> + val ? "blocked" : "unblocked");
> + return -EIO;
> + }
> +
> + printf("OK: hci_dev and rfkill class agree HW rfkill is %s\n",
> + enable ? "ON" :
> + "OFF");
> +
> + return 0;
> +}
> +
> +static void usage(void)
> +{
> + printf("hci_rfkill - generic HW rfkill stack test driver\n"
> + "Usage:\n");
> + printf("\thci_rfkill [options] <hciN>\n");
> + printf("options:\n"
> + "\t-t, --toggle Toggle HW rfkill on then off "
> + "(default)\n"
> + "\t-e, --enable Only assert HW rfkill (block)\n"
> + "\t-d, --disable Only de-assert HW rfkill (unblock)\n"
> + "\t-r, --restore Restore the adapter to its pre-test\n"
> + "\t up/down state afterwards (unblocking\n"
> + "\t HW rfkill does not reopen the device\n"
> + "\t on its own; that is normally done by\n"
> + "\t bluetoothd reacting to the rfkill\n"
> + "\t event via mgmt). Note: combined with\n"
> + "\t -e, restoring an adapter that was up\n"
> + "\t will fail with ERFKILL, since it is\n"
> + "\t left HW-blocked on purpose\n"
> + "\t-v, --verbose Show verbose output\n"
> + "\t-h, --help Show help options\n");
> +}
> +
> +static const struct option main_options[] = {
> + { "toggle", no_argument, NULL, 't' },
> + { "enable", no_argument, NULL, 'e' },
> + { "disable", no_argument, NULL, 'd' },
> + { "restore", no_argument, NULL, 'r' },
> + { "verbose", no_argument, NULL, 'v' },
> + { "help", no_argument, NULL, 'h' },
> + { }
> +};
> +
> +int main(int argc, char *argv[])
> +{
> + const char *hci_name;
> + int mode = 't';
> + bool restore = false;
> + int opt;
> + int hdev_id = -1;
> + bool was_up = false;
> + bool have_pre_state = false;
> + bool write_succeeded;
> + int ret = EXIT_SUCCESS;
> + int err;
> +
> + while ((opt = getopt_long(argc, argv, "tedrvh",
> + main_options, NULL)) != -1) {
> + switch (opt) {
> + case 't':
> + case 'e':
> + case 'd':
> + mode = opt;
> + break;
> + case 'r':
> + restore = true;
> + break;
> + case 'v':
> + verbose = true;
> + break;
> + case 'h':
> + usage();
> + return EXIT_SUCCESS;
> + default:
> + usage();
> + return EXIT_FAILURE;
> + }
> + }
> +
> + argc -= optind;
> + argv += optind;
> +
> + if (argc < 1) {
> + usage();
> + return EXIT_FAILURE;
> + }
> +
> + hci_name = argv[0];
> +
> + hdev_id = hci_name_to_id(hci_name);
> + if (hdev_id < 0) {
> + fprintf(stderr, "Invalid hci device name %s\n", hci_name);
> + return EXIT_FAILURE;
> + }
> +
> + if (restore) {
> + err = hci_dev_is_up(hdev_id, &was_up);
> + if (err) {
> + fprintf(stderr, "Failed to query %s up state: %s\n",
> + hci_name, strerror(-err));
> + return EXIT_FAILURE;
> + }
> +
> + have_pre_state = true;
> + print_verbose("%s pre-test state: %s\n", hci_name,
> + was_up ? "up" : "down");
> + }
> +
> + switch (mode) {
> + case 'e':
> + if (run_test(hci_name, true, &write_succeeded))
> + ret = EXIT_FAILURE;
> + break;
> + case 'd':
> + if (run_test(hci_name, false, &write_succeeded))
> + ret = EXIT_FAILURE;
> + break;
> + case 't':
> + default:
> + err = run_test(hci_name, true, &write_succeeded);
> + if (err)
> + ret = EXIT_FAILURE;
> + if (write_succeeded) {
> + if (run_test(hci_name, false, &write_succeeded))
> + ret = EXIT_FAILURE;
> + }
> + break;
> + }
> +
> + if (restore && have_pre_state) {
> + err = hci_dev_set_up(hdev_id, was_up);
> + if (err) {
> + fprintf(stderr, "Failed to restore %s to its "
> + "pre-test %s state: %s\n", hci_name,
> + was_up ? "up" : "down",
> + strerror(-err));
> + ret = EXIT_FAILURE;
> + } else {
> + print_verbose("%s restored to pre-test state: %s\n",
> + hci_name, was_up ? "up" : "down");
> + }
> + }
> +
> + return ret;
> +}
> --
> 2.53.0
>
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v1 1/1] emulator: add generic HW rfkill test tool
2026-09-30 14:41 ` Aluvala Sai Teja
@ 2026-09-30 14:59 ` Luiz Augusto von Dentz
2026-10-01 1:56 ` Aluvala Sai Teja
0 siblings, 1 reply; 7+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-30 14:59 UTC (permalink / raw)
To: Aluvala Sai Teja
Cc: linux-bluetooth@vger.kernel.org, K, Kiran,
Tumkur Narayan, Chethan
Hi Sai,
On Wed, Sep 30, 2026 at 10:42 AM Aluvala Sai Teja
<aluvala.sai.teja@intel.com> wrote:
>
> Hi Sai,
>
> On Wed, Sep 30, 2026 at 3:53 AM Sai Teja Aluvala <aluvala.sai.teja@intel.com> wrote:
> >
> > Add emulator/hci_rfkill, a standalone tool that drives the
> > force_hw_rfkill debugfs hook for an hciN device and verifies the
> > reported hardware-block state against /sys/class/rfkill. The hook
> > exercises the generic net/bluetooth rfkill path for any HCI transport;
> > it does not test the driver's hardware interrupt or register handling.
> >
> > Support assert-only, deassert-only, and toggle modes. Validate the
> > hciN argument before using it in filesystem paths. In toggle mode, if
> > asserting the block was written successfully but subsequent
> > verification fails, still attempt a verified unblock while preserving
> > the failure result. Do not attempt an unblock when the assert write
> > itself fails.
> >
> > The optional -r/--restore mode snapshots the adapter's up/down state
> > via HCIGETDEVINFO and restores it via HCIDEVUP/HCIDEVDOWN, following
> > the raw HCI control-socket approach used by tools/hciconfig.c.
> >
> > Usage: hci_rfkill [-e|-d|-t] [-r] hciN
>
> I have a hard time understanding why we can't just use the `rfkill` tool for this purpose, or does it not support hardware-block? It seems it does:
>
> rfkill --help
>
> Usage:
> rfkill [options] command [identifier ...]
>
> Tool for enabling and disabling wireless devices.
>
> Options:
> -J, --json use JSON output format
> -n, --noheadings don't print headings
> -o, --output <list> define which output columns to use
> --output-all output all columns
> -r, --raw use the raw output format
>
> -h, --help display this help
> -V, --version display version
>
> Available output columns:
> DEVICE kernel device name
> ID device identifier value
> TYPE device type name that can be used as identifier
> TYPE-DESC device type description
> SOFT status of software block
> -> HARD status of hardware block
>
> [Sai]: The rfkill tool can list RFKILL devices and control the software
> block with `rfkill block` and `rfkill unblock`, but it cannot synthesize a
> hardware rfkill block. The hardware state is normally reported by the transport
> driver and is read-only from userspace.
>
> To simulate a hardware block for testing, we still need a kernel-side
> injection point, such as a debugfs entry, that invokes the same HCI
> hardware-state notification path used by the driver.
>
> however, The test tool does not need a separate RFKILL-core API for validation. After
> writing the simulated state through debugfs, userspace can read the existing
> `/sys/class/rfkill/rfkillN/hard` entry. Therefore, the debugfs entry would
> only provide event injection; the sysfs interface would provide validation.
>
> The debugfs read operation and the separate `rfkill_hard_blocked()` helper
> are not required. we can drop them
>
> The reason I considered the emulator tool is that the existing `rfkill`
> injection path, changes in `net/bluetooth` such as the HCI RFKILL
> notification, `HCI_RFKILLED` handling, and the resulting controller
> power-transition work are not directly validated for hw rfkill.they are
> validated for sw rfkill path
>
> my understanding is that the existing `rfkill` command is useful for
> testing the software block/unblock path, while
> `/sys/class/rfkill/rfkillN/hard` allows userspace to inspect the hardware
> block bit. Neither interface injects a hardware event or exercises the
> complete Bluetooth stack path.
>
> Do you think it is worthwhile to keep a small `hci_rfkill` emulator tool to
> inject the hardware state and validate the HCI RFKILL notification,
> `HCI_RFKILLED` handling, and controller power-transition path in stack? If only
> SW RFKILL state validation is needed, then the emulator can be dropped; if the
> goal is complete Bluetooth stack validation for hw rfkill, I think keeping it is
> worthwhile. I would appreciate your suggestion on which scope is preferred.
My assumption is that if this functionality were needed, it should be
part of `rfkill` directly. Alternatively, if you really want to
exercise this code, Id probably recommend doing it via platform layer
which implements the actual hardware line, thus exercising the code
path exactly as real hardware would, rather than through a
Bluetooth-specific debugfs entry.
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: emulator: add generic HW rfkill test tool
2026-09-30 7:42 ` [PATCH v1 1/1] " Sai Teja Aluvala
2026-09-30 12:53 ` Luiz Augusto von Dentz
@ 2026-09-30 15:12 ` bluez.test.bot
1 sibling, 0 replies; 7+ messages in thread
From: bluez.test.bot @ 2026-09-30 15:12 UTC (permalink / raw)
To: linux-bluetooth, aluvala.sai.teja
[-- Attachment #1: Type: text/plain, Size: 1022 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/1176664/
---Test result---
Test Summary:
CheckPatch PASS 0.77 seconds
GitLint PASS 0.31 seconds
BuildEll PASS 19.69 seconds
BluezMake PASS 354.50 seconds
MakeCheck PASS 14.61 seconds
MakeDistcheck PASS 143.54 seconds
CheckValgrind PASS 233.08 seconds
CheckSmatch PASS 280.96 seconds
bluezmakeextell PASS 91.13 seconds
TestFunctional PASS 1078.06 seconds
IncrementalBuild PASS 374.27 seconds
ScanBuild PASS 1043.58 seconds
https://github.com/bluez/bluez/pull/2612
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH v1 1/1] emulator: add generic HW rfkill test tool
2026-09-30 14:59 ` Luiz Augusto von Dentz
@ 2026-10-01 1:56 ` Aluvala Sai Teja
0 siblings, 0 replies; 7+ messages in thread
From: Aluvala Sai Teja @ 2026-10-01 1:56 UTC (permalink / raw)
To: Luiz Augusto von Dentz
Cc: linux-bluetooth@vger.kernel.org, K, Kiran,
Tumkur Narayan, Chethan
Hi Sai,
On Wed, Sep 30, 2026 at 10:42 AM Aluvala Sai Teja <aluvala.sai.teja@intel.com> wrote:
>
> Hi Sai,
>
> On Wed, Sep 30, 2026 at 3:53 AM Sai Teja Aluvala <aluvala.sai.teja@intel.com> wrote:
> >
> > Add emulator/hci_rfkill, a standalone tool that drives the
> > force_hw_rfkill debugfs hook for an hciN device and verifies the
> > reported hardware-block state against /sys/class/rfkill. The hook
> > exercises the generic net/bluetooth rfkill path for any HCI
> > transport; it does not test the driver's hardware interrupt or register handling.
> >
> > Support assert-only, deassert-only, and toggle modes. Validate the
> > hciN argument before using it in filesystem paths. In toggle mode,
> > if asserting the block was written successfully but subsequent
> > verification fails, still attempt a verified unblock while
> > preserving the failure result. Do not attempt an unblock when the
> > assert write itself fails.
> >
> > The optional -r/--restore mode snapshots the adapter's up/down state
> > via HCIGETDEVINFO and restores it via HCIDEVUP/HCIDEVDOWN, following
> > the raw HCI control-socket approach used by tools/hciconfig.c.
> >
> > Usage: hci_rfkill [-e|-d|-t] [-r] hciN
>
> I have a hard time understanding why we can't just use the `rfkill` tool for this purpose, or does it not support hardware-block? It seems it does:
>
> rfkill --help
>
> Usage:
> rfkill [options] command [identifier ...]
>
> Tool for enabling and disabling wireless devices.
>
> Options:
> -J, --json use JSON output format
> -n, --noheadings don't print headings
> -o, --output <list> define which output columns to use
> --output-all output all columns
> -r, --raw use the raw output format
>
> -h, --help display this help
> -V, --version display version
>
> Available output columns:
> DEVICE kernel device name
> ID device identifier value
> TYPE device type name that can be used as identifier
> TYPE-DESC device type description
> SOFT status of software block
> -> HARD status of hardware block
>
> [Sai]: The rfkill tool can list RFKILL devices and control the
> software block with `rfkill block` and `rfkill unblock`, but it cannot
> synthesize a hardware rfkill block. The hardware state is normally
> reported by the transport driver and is read-only from userspace.
>
> To simulate a hardware block for testing, we still need a kernel-side
> injection point, such as a debugfs entry, that invokes the same HCI
> hardware-state notification path used by the driver.
>
> however, The test tool does not need a separate RFKILL-core API for
> validation. After writing the simulated state through debugfs,
> userspace can read the existing `/sys/class/rfkill/rfkillN/hard`
> entry. Therefore, the debugfs entry would only provide event injection; the sysfs interface would provide validation.
>
> The debugfs read operation and the separate `rfkill_hard_blocked()`
> helper are not required. we can drop them
>
> The reason I considered the emulator tool is that the existing
> `rfkill` injection path, changes in `net/bluetooth` such as the HCI
> RFKILL notification, `HCI_RFKILLED` handling, and the resulting
> controller power-transition work are not directly validated for hw
> rfkill.they are validated for sw rfkill path
>
> my understanding is that the existing `rfkill` command is useful for
> testing the software block/unblock path, while
> `/sys/class/rfkill/rfkillN/hard` allows userspace to inspect the
> hardware block bit. Neither interface injects a hardware event or
> exercises the complete Bluetooth stack path.
>
> Do you think it is worthwhile to keep a small `hci_rfkill` emulator
> tool to inject the hardware state and validate the HCI RFKILL
> notification, `HCI_RFKILLED` handling, and controller power-transition
> path in stack? If only SW RFKILL state validation is needed, then the
> emulator can be dropped; if the goal is complete Bluetooth stack
> validation for hw rfkill, I think keeping it is worthwhile. I would appreciate your suggestion on which scope is preferred.
My assumption is that if this functionality were needed, it should be part of `rfkill` directly. Alternatively, if you really want to exercise this code, Id probably recommend doing it via platform layer which implements the actual hardware line, thus exercising the code path exactly as real hardware would, rather than through a Bluetooth-specific debugfs entry.
[Sai]: Agreed. For this series, `btintel_pcie` is the implementation that
owns and detects the actual hardware rfkill event. Testing through its
interrupt/doorbell path exercises the flow from hardware line:
btintel_pcie -> hci_rfkill_set_hw_state()
-> RFKILL state
-> HCI_RFKILLED handling
-> controller power transition
Based on your suggestion, I will drop the Bluetooth-specific
debugfs/emulator patches and retain the HCI notification changes and the
`btintel_pcie` implementation. If a generic software injection mechanism
is needed later, it should be designed as part of the RFKILL subsystem
rather than added specifically to Bluetooth.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-01 1:56 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-30 7:42 [PATCH v1 0/1] emulator: add generic HW rfkill test tool Sai Teja Aluvala
2026-09-30 7:42 ` [PATCH v1 1/1] " Sai Teja Aluvala
2026-09-30 12:53 ` Luiz Augusto von Dentz
2026-09-30 14:41 ` Aluvala Sai Teja
2026-09-30 14:59 ` Luiz Augusto von Dentz
2026-10-01 1:56 ` Aluvala Sai Teja
2026-09-30 15:12 ` 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.