From 91fa4b2267b3d33b5d8798b00bf7d1893236468a Mon Sep 17 00:00:00 2001 From: Lingao Meng Date: Thu, 23 Jul 2026 14:44:40 +0800 Subject: [PATCH 1/3] arch/sim: Add BabbleSim discrete time support Add support for using a BabbleSim PHY as the monotonic time source for the Linux sim target. When CONFIG_SIM_BSIM_TIME is enabled, the sim host build links a small host-side time helper against the BabbleSim PhyCom and Util libraries. The helper joins the BabbleSim PHY wait protocol and advances NuttX monotonic time through PB_MSG_WAIT requests instead of Linux wall-time sleeps. A SIM binary built with CONFIG_SIM_BSIM_TIME enabled joins BabbleSim time at startup. Runtime options allow the test runner to select the BabbleSim simulation id, PHY id, and device number: --sim-bsim-sid= --sim-bsim-pid= --sim-bsim-dev= Keep the integration inside the sim host time path rather than exposing a new application API. RTC/realtime reads still use the host realtime clock; the BabbleSim source is used only for monotonic time after the sim has joined the PHY. The Kconfig option depends on the sleep based walltime mode and is disabled for SMP and non-Linux hosts. The build requires BSIM_COMPONENTS_PATH for headers and either BSIM_OUT_PATH or BSIM_LIBS_DIR for shared libraries. The path checks are skipped for clean, distclean, clean_context, and context targets so a tree with CONFIG_SIM_BSIM_TIME enabled can still be cleaned without exporting the BabbleSim environment first. Document the configuration, build environment, runtime options, and the requirement that the BabbleSim PHY process is started separately by the test runner. Testing: Host: Ubuntu 22.04 x86_64 Board/config: sim:nsh Style check: git diff --check Default sim build and smoke test: ./tools/configure.sh -l -a ../nuttx-apps sim:nsh make -j16 printf 'help\npoweroff\n' | timeout 20s ./nuttx BabbleSim-enabled build: kconfig-tweak --file .config \ -e SIM_WALLTIME_SLEEP \ -d SIM_WALLTIME_SIGNAL \ -e SIM_BSIM_TIME make olddefconfig BSIM_OUT_PATH=/tmp/bsworld/build/babblesim/bsim \ BSIM_COMPONENTS_PATH=/tmp/bsworld/build/babblesim/bsim/components \ make -j16 Verified actual BabbleSim PHY time integration without a controller by starting bs_2G4_phy_v1 and running NSH usleep through the PHY wait barrier: bs_2G4_phy_v1 -s= -D=1 -defmodem=BLE_simple -nodump printf 'usleep 1000000\npoweroff\n' | \ ./nuttx --sim-bsim-sid= \ --sim-bsim-pid=2G4 \ --sim-bsim-dev=0 The same 1 second simulated sleep completed in 19 ms wall time when no handbrake device was present. With handbrake registered as device 1: bs_2G4_phy_v1 -s= -D=2 -defmodem=BLE_simple -nodump bs_device_handbrake -s= -p=2G4 -d=1 -pp=50000 -r=1 the same NuttX usleep test completed in 985 ms wall time. A shorter 200 ms check showed the same behavior: 27 ms without handbrake and 172 ms with handbrake. This verifies that NuttX sim time advances through the BabbleSim PHY and that the handbrake affects the NuttX sim device. Also verified make distclean succeeds after CONFIG_SIM_BSIM_TIME was enabled and without exporting BSIM_COMPONENTS_PATH. BSWorld out-of-tree native BLE examples: ./tools/configure.sh -l /path/to/bsim-auto-test/tests/nuttx/native_ble/source/advertiser/config make -j16 exodus --tarball -o /path/to/bsim-auto-test/tests/nuttx/native_ble/source/advertiser/prebuilt/nuttx.tgz nuttx ./tools/configure.sh -l /path/to/bsim-auto-test/tests/nuttx/native_ble/source/scanner/config make -j16 exodus --tarball -o /path/to/bsim-auto-test/tests/nuttx/native_ble/source/scanner/prebuilt/nuttx.tgz nuttx pytest tests/nuttx/native_ble -q --no-ellisys Assisted-by: OpenAI Codex Signed-off-by: Lingao Meng --- .../platforms/sim/sim/boards/sim/index.rst | 37 +++++ arch/sim/Kconfig | 42 ++++++ arch/sim/src/Makefile | 25 ++++ arch/sim/src/sim/posix/sim_bsimtime.c | 130 ++++++++++++++++++ arch/sim/src/sim/posix/sim_hosttime.c | 23 ++++ arch/sim/src/sim/sim_head.c | 29 ++++ arch/sim/src/sim/sim_internal.h | 9 ++ 7 files changed, 295 insertions(+) create mode 100644 arch/sim/src/sim/posix/sim_bsimtime.c diff --git a/Documentation/platforms/sim/sim/boards/sim/index.rst b/Documentation/platforms/sim/sim/boards/sim/index.rst index 59b19ba81ca8f..4326dac031896 100644 --- a/Documentation/platforms/sim/sim/boards/sim/index.rst +++ b/Documentation/platforms/sim/sim/boards/sim/index.rst @@ -41,6 +41,43 @@ Timing Fidelity Another option is to use ``CONFIG_SIM_WALLTIME_SLEEP`` which will enable the tick events to be delayed from the Idle task by using a host sleep call. +BabbleSim Discrete Time +----------------------- + +The Linux ``sim`` target can optionally use a BabbleSim PHY process as the +monotonic time source. This is useful for tests where a NuttX host-side stack +and other BabbleSim devices need to advance in the same discrete-time domain. + +Enable ``CONFIG_SIM_BSIM_TIME`` together with ``CONFIG_SIM_WALLTIME_SLEEP``. +This mode is available only on Linux hosts and is not supported with SMP or +``CONFIG_SIM_WALLTIME_SIGNAL``. + +When ``CONFIG_SIM_BSIM_TIME`` is enabled, the build needs the BabbleSim +component headers and libraries: + +.. code:: console + + $ export BSIM_OUT_PATH=/path/to/bsim + $ export BSIM_COMPONENTS_PATH=${BSIM_OUT_PATH}/components + +``BSIM_LIBS_DIR`` may be used instead of ``BSIM_OUT_PATH`` when the BabbleSim +shared libraries are installed in a non-default directory. + +When ``CONFIG_SIM_BSIM_TIME`` is enabled, the simulator connects to the +BabbleSim PHY at startup: + +.. code:: console + + $ ./nuttx --sim-bsim-sid=default \ + --sim-bsim-pid=2G4 \ + --sim-bsim-dev=0 + +The BabbleSim PHY process must be started separately by the test runner before +launching the NuttX simulator. + +The runtime options override ``CONFIG_SIM_BSIM_SIM_ID``, +``CONFIG_SIM_BSIM_PHY_ID``, and ``CONFIG_SIM_BSIM_DEVICE_NBR``. + Debugging ========= diff --git a/arch/sim/Kconfig b/arch/sim/Kconfig index 70b1f3c88ab93..29675843803b5 100644 --- a/arch/sim/Kconfig +++ b/arch/sim/Kconfig @@ -180,6 +180,48 @@ config SIM_WALLTIME_RATIO twice as fast as real time. 50 means half speed. This can also be overridden at runtime with --sim-rt-ratio=. +config SIM_BSIM_TIME + bool "Enable BabbleSim PHY time support" + default n + depends on HOST_LINUX + depends on SIM_WALLTIME_SLEEP + depends on !SMP + ---help--- + Build support for using the BabbleSim PHY wait protocol as the SIM + monotonic time source. When this option is enabled, the SIM target + participates in the PHY discrete-time barrier as a device, and + host_sleepuntil() advances by sending PB_MSG_WAIT requests instead + of sleeping on Linux wall time. + + This is intended for tests where the host stack and controller need + to share the same simulated time domain. The signal-driven walltime + timer mode is intentionally not supported. + + The build must be given the BabbleSim paths through the environment: + BSIM_COMPONENTS_PATH for headers and either BSIM_OUT_PATH or + BSIM_LIBS_DIR for libraries. + +config SIM_BSIM_SIM_ID + string "BabbleSim simulation ID" + default "default" + depends on SIM_BSIM_TIME + ---help--- + The BabbleSim simulation ID passed to the PHY with -s. + +config SIM_BSIM_PHY_ID + string "BabbleSim PHY ID" + default "2G4" + depends on SIM_BSIM_TIME + ---help--- + The BabbleSim PHY ID. This must match the PHY process ID. + +config SIM_BSIM_DEVICE_NBR + int "BabbleSim device number for this SIM host" + default 0 + depends on SIM_BSIM_TIME + ---help--- + The BabbleSim device number reserved for this SIM host. + config SIM_LOOP_INTERVAL int "loop interval in ms" default 10 diff --git a/arch/sim/src/Makefile b/arch/sim/src/Makefile index fbd900d8e826d..1883aee10b392 100644 --- a/arch/sim/src/Makefile +++ b/arch/sim/src/Makefile @@ -134,6 +134,31 @@ endif HOSTSRCS = sim_hostirq.c sim_hostmemory.c sim_hostmisc.c sim_hosttime.c sim_hostuart.c HOSTSRCS += sim_hostfs.c sim_errno.c +ifeq ($(CONFIG_SIM_BSIM_TIME),y) + HOSTSRCS += sim_bsimtime.c + ifeq ($(BSIM_COMPONENTS_PATH),) + $(error CONFIG_SIM_BSIM_TIME requires BSIM_COMPONENTS_PATH to point to \ + the BabbleSim components directory) + endif + ifeq ($(BSIM_LIBS_DIR),) + ifeq ($(BSIM_OUT_PATH),) + $(error CONFIG_SIM_BSIM_TIME requires BSIM_LIBS_DIR or BSIM_OUT_PATH \ + to locate BabbleSim libraries) + endif + BSIM_LIBS_DIR := $(BSIM_OUT_PATH)/lib + endif + HOSTCFLAGS += ${INCDIR_PREFIX}$(BSIM_COMPONENTS_PATH)/libPhyComv1/src + HOSTCFLAGS += ${INCDIR_PREFIX}$(BSIM_COMPONENTS_PATH)/libUtilv1/src + STDLIBS += -Wl,-rpath,$(BSIM_LIBS_DIR) + ifeq ($(CONFIG_SIM_M32),y) + STDLIBS += $(BSIM_LIBS_DIR)/libPhyComv1.32.so + STDLIBS += $(BSIM_LIBS_DIR)/libUtilv1.32.so + else + STDLIBS += $(BSIM_LIBS_DIR)/libPhyComv1.so + STDLIBS += $(BSIM_LIBS_DIR)/libUtilv1.so + endif +endif + hostfs.h: $(TOPDIR)/include/nuttx/fs/hostfs.h @echo "CP: $<" $(Q) cp $< $@ diff --git a/arch/sim/src/sim/posix/sim_bsimtime.c b/arch/sim/src/sim/posix/sim_bsimtime.c new file mode 100644 index 0000000000000..2e1d019d7188c --- /dev/null +++ b/arch/sim/src/sim/posix/sim_bsimtime.c @@ -0,0 +1,130 @@ +/**************************************************************************** + * arch/sim/src/sim/posix/sim_bsimtime.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include "bs_pc_base.h" +#include "sim_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define NSEC_PER_BSIM_USEC 1000ull + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static pb_dev_state_t g_bsim_dev; +static uint64_t g_now_nsec; +static bool g_connected; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void bsimtime_disconnect_atexit(void) +{ + host_bsimtime_disconnect(); +} + +static void bsimtime_fail(void) +{ + host_abort(1); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int host_bsimtime_init(const char *sim_id, const char *phy_id, + unsigned int dev_nbr) +{ + int ret; + + if (sim_id == NULL || phy_id == NULL) + { + return -EINVAL; + } + + ret = pb_dev_init_com(&g_bsim_dev, dev_nbr, sim_id, phy_id); + if (ret != 0) + { + return ret; + } + + g_connected = true; + atexit(bsimtime_disconnect_atexit); + + return 0; +} + +uint64_t host_bsimtime_gettime(void) +{ + return g_now_nsec; +} + +bool host_bsimtime_is_enabled(void) +{ + return g_connected; +} + +void host_bsimtime_sleepuntil(uint64_t nsec) +{ + pb_wait_t wait; + + if (nsec <= g_now_nsec) + { + return; + } + + if (!g_connected) + { + bsimtime_fail(); + } + + wait.end = (nsec + NSEC_PER_BSIM_USEC - 1) / NSEC_PER_BSIM_USEC; + + if (pb_dev_request_wait_block(&g_bsim_dev, &wait) < 0) + { + bsimtime_fail(); + } + + g_now_nsec = wait.end * NSEC_PER_BSIM_USEC; +} + +void host_bsimtime_disconnect(void) +{ + if (!g_connected) + { + return; + } + + pb_dev_disconnect(&g_bsim_dev); + g_connected = false; +} diff --git a/arch/sim/src/sim/posix/sim_hosttime.c b/arch/sim/src/sim/posix/sim_hosttime.c index 0eaefa4158a16..ec527ed082993 100644 --- a/arch/sim/src/sim/posix/sim_hosttime.c +++ b/arch/sim/src/sim/posix/sim_hosttime.c @@ -109,6 +109,13 @@ uint64_t host_gettime(bool rtc) struct timespec tp; uint64_t current; +#ifdef CONFIG_SIM_BSIM_TIME + if (!rtc && host_bsimtime_is_enabled()) + { + return host_bsimtime_gettime(); + } +#endif + clock_gettime(rtc ? CLOCK_REALTIME : CLOCK_MONOTONIC, &tp); current = 1000000000ull * tp.tv_sec + tp.tv_nsec; @@ -128,6 +135,14 @@ uint64_t host_gettime(bool rtc) void host_sleep(uint64_t nsec) { +#ifdef CONFIG_SIM_BSIM_TIME + if (host_bsimtime_is_enabled()) + { + host_sleepuntil(host_gettime(false) + nsec); + return; + } +#endif + usleep((nsec + 999) / 1000); } @@ -139,6 +154,14 @@ void host_sleepuntil(uint64_t nsec) { uint64_t now; +#ifdef CONFIG_SIM_BSIM_TIME + if (host_bsimtime_is_enabled()) + { + host_bsimtime_sleepuntil(nsec); + return; + } +#endif + now = host_gettime(false); if (nsec > now + 1000) { diff --git a/arch/sim/src/sim/sim_head.c b/arch/sim/src/sim/sim_head.c index dc625cd925c16..1e6a2176b199b 100644 --- a/arch/sim/src/sim/sim_head.c +++ b/arch/sim/src/sim/sim_head.c @@ -165,6 +165,11 @@ noprofile_function const char *__ubsan_default_options(void) int main(int argc, char **argv, char **envp) { +#ifdef CONFIG_SIM_BSIM_TIME + const char *bsim_sim_id = CONFIG_SIM_BSIM_SIM_ID; + const char *bsim_phy_id = CONFIG_SIM_BSIM_PHY_ID; + unsigned int bsim_dev_nbr = CONFIG_SIM_BSIM_DEVICE_NBR; +#endif int i; g_argc = argc; @@ -173,6 +178,9 @@ int main(int argc, char **argv, char **envp) /* Parse simulator-specific options before handing control to NuttX. * --sim-rt-ratio= Set simulated-to-real time ratio in percent * (default 100). Values > 100 speed up simulated time; < 100 slow down. + * --sim-bsim-sid= Override the BabbleSim simulation ID. + * --sim-bsim-pid= Override the BabbleSim PHY ID. + * --sim-bsim-dev= Override the BabbleSim device number. */ for (i = 1; i < argc; i++) @@ -181,7 +189,28 @@ int main(int argc, char **argv, char **envp) { host_set_timeratio(atoi(argv[i] + 15)); } +#ifdef CONFIG_SIM_BSIM_TIME + else if (strncmp(argv[i], "--sim-bsim-sid=", 15) == 0) + { + bsim_sim_id = argv[i] + 15; + } + else if (strncmp(argv[i], "--sim-bsim-pid=", 15) == 0) + { + bsim_phy_id = argv[i] + 15; + } + else if (strncmp(argv[i], "--sim-bsim-dev=", 15) == 0) + { + bsim_dev_nbr = atoi(argv[i] + 15); + } +#endif + } + +#ifdef CONFIG_SIM_BSIM_TIME + if (host_bsimtime_init(bsim_sim_id, bsim_phy_id, bsim_dev_nbr) < 0) + { + return EXIT_FAILURE; } +#endif #ifdef CONFIG_ALLSYMS allsyms_relocate(); diff --git a/arch/sim/src/sim/sim_internal.h b/arch/sim/src/sim/sim_internal.h index 9f256838cd6a1..82ace1ecdd468 100644 --- a/arch/sim/src/sim/sim_internal.h +++ b/arch/sim/src/sim/sim_internal.h @@ -249,6 +249,15 @@ void host_set_timeratio(int ratio); int host_timerirq(void); int host_settimer(uint64_t nsec); +#ifdef CONFIG_SIM_BSIM_TIME +int host_bsimtime_init(const char *sim_id, const char *phy_id, + unsigned int dev_nbr); +uint64_t host_bsimtime_gettime(void); +bool host_bsimtime_is_enabled(void); +void host_bsimtime_sleepuntil(uint64_t nsec); +void host_bsimtime_disconnect(void); +#endif + /* sim_sigdeliver.c *********************************************************/ void sim_sigdeliver(void); From bb4c2d41b7707204fcc808fb211913d93dc4ac73 Mon Sep 17 00:00:00 2001 From: Lingao Meng Date: Tue, 8 Sep 2026 17:18:25 +0800 Subject: [PATCH 2/3] arch/sim: Fix HCI socket watchdog lifetime Start the simulated HCI socket receive watchdog only after the host HCI socket has been opened successfully. The previous code armed the watchdog immediately after driver registration, before the Bluetooth stack opened the driver and before the device had a valid host fd. Cancel the watchdog on close/free and close any opened host fd during allocation-failure cleanup. This keeps the polling path tied to the actual socket lifetime and prevents the watchdog from polling an invalid host fd. Testing: Host: Ubuntu 22.04 x86_64 Board/config: sim:bthcisock Style checks: git diff --check HEAD~2..HEAD PATH=/home/mi/bsim-auto-test/.venv/bin:$PATH \ ./tools/checkpatch.sh -c -u -m -g HEAD~2..HEAD Clean build: make distclean ./tools/configure.sh -l -a ../../nuttx-apps sim:bthcisock kconfig-tweak --file .config --set-val STACK_USAGE_WARNING 0 make olddefconfig make -j16 Default startup smoke test: printf 'poweroff\n' | timeout 10s ./nuttx Verified the sim still reaches NSH and powers off cleanly. When no host HCI controller is available through the default BlueZ target, the board reports sim_bthcisock_register() failure and continues booting; no invalid-fd watchdog crash occurs. Assisted-by: OpenAI Codex Signed-off-by: Lingao Meng --- arch/sim/src/sim/sim_hcisocket.c | 35 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/arch/sim/src/sim/sim_hcisocket.c b/arch/sim/src/sim/sim_hcisocket.c index 462bc182a333e..387c2f1cca522 100644 --- a/arch/sim/src/sim/sim_hcisocket.c +++ b/arch/sim/src/sim/sim_hcisocket.c @@ -88,6 +88,7 @@ static int bthcisock_send(struct bt_driver_s *drv, static int bthcisock_open(struct bt_driver_s *drv); static void bthcisock_close(struct bt_driver_s *drv); static int bthcisock_receive(struct bt_driver_s *drv); +static void sim_bthcisock_interrupt(wdparm_t arg); /**************************************************************************** * Private Functions @@ -127,6 +128,7 @@ static void bthcisock_close(struct bt_driver_s *drv) { struct bthcisock_s *dev = (struct bthcisock_s *)drv; + wd_cancel(&dev->wdog); host_bthcisock_close(dev->fd); dev->fd = -1; } @@ -153,10 +155,9 @@ static int bthcisock_receive(struct bt_driver_s *drv) hdr = (union bt_hdr_u *)&dev->rxbuf[H4_HEADER_SIZE]; switch (dev->rxbuf[0]) { - case H4_EVT: - { + case H4_EVT: if (dev->rxlen < H4_HEADER_SIZE - + sizeof (struct bt_hci_evt_hdr_s)) + + sizeof(struct bt_hci_evt_hdr_s)) { return ret; } @@ -164,10 +165,8 @@ static int bthcisock_receive(struct bt_driver_s *drv) type = BT_EVT; pktlen = H4_HEADER_SIZE + sizeof(struct bt_hci_evt_hdr_s) + hdr->evt.len; - } - break; - case H4_ACL: - { + break; + case H4_ACL: if (dev->rxlen < H4_HEADER_SIZE + sizeof(struct bt_hci_acl_hdr_s)) { @@ -177,10 +176,8 @@ static int bthcisock_receive(struct bt_driver_s *drv) type = BT_ACL_IN; pktlen = H4_HEADER_SIZE + sizeof(struct bt_hci_acl_hdr_s) + hdr->acl.len; - } - break; - case H4_ISO: - { + break; + case H4_ISO: if (dev->rxlen < H4_HEADER_SIZE + sizeof(struct bt_hci_iso_hdr_s)) { @@ -190,10 +187,9 @@ static int bthcisock_receive(struct bt_driver_s *drv) type = BT_ISO_IN; pktlen = H4_HEADER_SIZE + sizeof(struct bt_hci_iso_hdr_s) + hdr->iso.len; - } - break; - default: - return -EINVAL; + break; + default: + return -EINVAL; } if (dev->rxlen < pktlen) @@ -223,6 +219,7 @@ static int bthcisock_open(struct bt_driver_s *drv) } dev->fd = fd; + wd_start(&dev->wdog, 0, sim_bthcisock_interrupt, (wdparm_t)dev); return OK; } @@ -253,6 +250,12 @@ static struct bthcisock_s *bthcisock_alloc(int dev_id) static void bthcisock_free(struct bthcisock_s *dev) { + wd_cancel(&dev->wdog); + if (dev->fd >= 0) + { + host_bthcisock_close(dev->fd); + } + kmm_free(dev); } @@ -315,7 +318,5 @@ int sim_bthcisock_register(int dev_id) return ret; } - wd_start(&dev->wdog, 0, sim_bthcisock_interrupt, (wdparm_t)dev); - return 0; } From 7f8c521b84f78de9aaac0d633c34ab590f77be47 Mon Sep 17 00:00:00 2001 From: Lingao Meng Date: Tue, 8 Sep 2026 17:18:25 +0800 Subject: [PATCH 3/3] arch/sim: Add runtime HCI socket target option Allow sim HCI socket users to select the host-side HCI target at runtime with --bt-dev. Passing --bt-dev=hciN overrides CONFIG_SIM_HCISOCKET_DEVID for the BlueZ HCI user channel, while omitting the option keeps the existing configured default behavior. Also allow --bt-dev=/path/to/socket to connect to an H:4 stream exposed through a Unix-domain socket. This lets sim applications use a controller provided by another host process or by a UART-to-Unix-socket bridge without requiring BlueZ raw HCI privileges for the NuttX process. Use host-side output for early --bt-dev parse errors, since NuttX stdio is not initialized before nx_start(). Document the BlueZ and Unix socket modes, including the capability requirements for BlueZ and the socat bridge example for Unix socket mode. Testing: Host: Ubuntu 22.04 x86_64 Board/config: sim:bthcisock Style checks: git diff --check HEAD~2..HEAD PATH=/home/mi/bsim-auto-test/.venv/bin:$PATH \ ./tools/checkpatch.sh -c -u -m -g HEAD~2..HEAD Clean build: make distclean ./tools/configure.sh -l -a ../../nuttx-apps sim:bthcisock kconfig-tweak --file .config --set-val STACK_USAGE_WARNING 0 make olddefconfig make -j16 Invalid runtime argument smoke test: ./nuttx --bt-dev=invalid Verified the command exits with status 1 and reports the invalid target without crashing before nx_start(). Unix socket HCI smoke test: socat -d -d UNIX-LISTEN:/tmp/hci.sock,fork,reuseaddr \ /dev/ttyACM2,b1000000,raw,echo=0,crtscts=1 printf 'ifconfig\nbt bnep0 info\npoweroff\n' | \ timeout 20s ./nuttx --bt-dev=/tmp/hci.sock Verified the sim registers the Bluetooth network device as bnep0 and bt bnep0 info reads the controller state through the Unix-socket HCI path, including BDAddr aa:bb:cc:dd:ee:ff from the attached controller. Assisted-by: OpenAI Codex Signed-off-by: Lingao Meng --- .../platforms/sim/sim/boards/sim/index.rst | 35 ++++- arch/sim/Kconfig | 5 +- arch/sim/src/sim/posix/sim_hosthcisocket.c | 146 +++++++++++++++++- arch/sim/src/sim/sim_head.c | 13 ++ arch/sim/src/sim/sim_hosthcisocket.h | 1 + 5 files changed, 191 insertions(+), 9 deletions(-) diff --git a/Documentation/platforms/sim/sim/boards/sim/index.rst b/Documentation/platforms/sim/sim/boards/sim/index.rst index 4326dac031896..c11abb6f7c73a 100644 --- a/Documentation/platforms/sim/sim/boards/sim/index.rst +++ b/Documentation/platforms/sim/sim/boards/sim/index.rst @@ -651,18 +651,49 @@ the NULL Bluetooth device at ``drivers/wireless/bluetooth/bt_null.c``. There is also support on a Linux Host for attaching the bluetooth hardware from the host to the NuttX bluetooth stack via the HCI Socket interface over the User Channel. This is enabled in the bthcisock configuration. In order to use this -you must give the ``nuttx`` ELF additional capabilities: +with the configured default HCI device, you must give the ``nuttx`` ELF +additional capabilities: .. code:: console $ sudo setcap 'cap_net_raw,cap_net_admin=eip' ./nuttx -You can then monitor the HCI traffic on the host with WireShark or ``btmon``: +The default HCI device is selected by ``CONFIG_SIM_HCISOCKET_DEVID``. It may be +overridden at runtime with ``--bt-dev=hciN``: + +.. code:: console + + $ ./nuttx --bt-dev=hci1 + +You can then monitor the BlueZ HCI traffic on the host with WireShark or +``btmon``: .. code:: console $ sudo btmon +The sim target can also connect to an HCI H:4 stream exposed through a Unix +domain socket by passing an absolute socket path: + +.. code:: console + + $ ./nuttx --bt-dev=/tmp/hci0.sock + +This Unix socket mode does not require a BlueZ HCI device. Since ``nuttx`` only +connects to a normal Unix domain socket, the ``nuttx`` process does not need to +run as root and does not need the ``setcap`` command above. This is useful when +the controller is provided by another host process, or when a UART controller is +bridged into a Unix socket with a tool such as ``socat``: + +.. code:: console + + $ socat UNIX-LISTEN:/tmp/hci0.sock,fork,reuseaddr \ + /dev/ttyACM0,b1000000,raw,echo=0,crtscts=1 + +The process listening on the socket must be started before NuttX. If that +process opens a real UART device, it still needs permission to access that UART +device. + configdata ---------- diff --git a/arch/sim/Kconfig b/arch/sim/Kconfig index 29675843803b5..7aaedd23700ef 100644 --- a/arch/sim/Kconfig +++ b/arch/sim/Kconfig @@ -672,6 +672,9 @@ config SIM_HCISOCKET target via HCI_CHANNEL_USER. This gives NuttX full control of the device, but is abstracted from the physical interface which is still handled by Linux. + The default BlueZ device can be overridden at runtime + with --bt-dev=hciN, or replaced by a Unix-domain HCI + socket with --bt-dev=/path/to/socket. config SIM_HCISOCKET_DEVID int "Bluetooth Device ID" @@ -679,7 +682,7 @@ config SIM_HCISOCKET_DEVID depends on SIM_HCISOCKET ---help--- Attached the local bluetooth device use specific - Bluetooth HCI number id. + Bluetooth HCI number id when --bt-dev is not passed. config SIM_I2CBUS bool "Simulated I2C Bus" diff --git a/arch/sim/src/sim/posix/sim_hosthcisocket.c b/arch/sim/src/sim/posix/sim_hosthcisocket.c index e2bde0aede217..d4235e13712eb 100644 --- a/arch/sim/src/sim/posix/sim_hosthcisocket.c +++ b/arch/sim/src/sim/posix/sim_hosthcisocket.c @@ -28,9 +28,12 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -60,10 +63,126 @@ struct sockaddr_hci unsigned short hci_channel; }; +enum bthcisock_target_e +{ + BTHCISOCK_TARGET_DEFAULT = 0, + BTHCISOCK_TARGET_BLUEZ, + BTHCISOCK_TARGET_UNIX +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static enum bthcisock_target_e g_bthcisock_target; +static int g_bthcisock_devid; +static char g_bthcisock_path[sizeof(((struct sockaddr_un *)0)->sun_path)]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int host_bthcisock_parse_devid(const char *target, int *devid) +{ + if (strncmp(target, "hci", 3) != 0 || target[3] == '\0') + { + return -EINVAL; + } + + target += 3; + *devid = atoi(target); + return 0; +} + +static int host_bthcisock_open_unix(const char *path) +{ + struct sockaddr_un addr; + size_t len; + int ret; + int fd; + + len = strlen(path); + if (len == 0) + { + return -EINVAL; + } + + if (len >= sizeof(addr.sun_path)) + { + return -ENAMETOOLONG; + } + + fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (fd < 0) + { + return -errno; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, path, len + 1); + + ret = connect(fd, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + len + 1); + if (ret < 0) + { + ret = -errno; + close(fd); + return ret; + } + + return fd; +} + /**************************************************************************** * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: host_bthcisock_configure + * + * Description: + * Override the default HCI target. Accepted values: + * - "hci" for BlueZ HCI user channel + * - "/path/to.sock" for Unix-domain HCI socket + * + ****************************************************************************/ + +int host_bthcisock_configure(const char *target) +{ + size_t len; + int ret; + int devid; + + if (target == NULL || target[0] == '\0') + { + return -EINVAL; + } + + if (target[0] == '/') + { + len = strlen(target); + if (len >= sizeof(g_bthcisock_path)) + { + return -ENAMETOOLONG; + } + + memcpy(g_bthcisock_path, target, len + 1); + g_bthcisock_target = BTHCISOCK_TARGET_UNIX; + return 0; + } + + ret = host_bthcisock_parse_devid(target, &devid); + if (ret < 0) + { + return ret; + } + + g_bthcisock_devid = devid; + g_bthcisock_target = BTHCISOCK_TARGET_BLUEZ; + return 0; +} + /**************************************************************************** * Name: host_bthcisock_avail * @@ -127,7 +246,7 @@ int host_bthcisock_send(int fd, const void *data, size_t len) continue; } - return -1; + return -errno; } return 0; @@ -160,7 +279,7 @@ int host_bthcisock_receive(int fd, void *data, size_t len) { /* Both an empty read and an error are "error" conditions */ - return -1; + return err < 0 ? -errno : -ECONNRESET; } /* Return the number of bytes written to data */ @@ -189,11 +308,23 @@ int host_bthcisock_open(int dev_idx) { int err; struct sockaddr_hci addr; - int fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, - BTPROTO_HCI); + int fd; + + if (g_bthcisock_target == BTHCISOCK_TARGET_UNIX) + { + return host_bthcisock_open_unix(g_bthcisock_path); + } + + if (g_bthcisock_target == BTHCISOCK_TARGET_BLUEZ) + { + dev_idx = g_bthcisock_devid; + } + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + BTPROTO_HCI); if (fd < 0) { - return fd; + return -errno; } /* We must bring the device down before binding to user channel */ @@ -201,6 +332,8 @@ int host_bthcisock_open(int dev_idx) err = ioctl(fd, HCIDEVDOWN, dev_idx); if (err < 0) { + err = -errno; + close(fd); return err; } @@ -212,6 +345,7 @@ int host_bthcisock_open(int dev_idx) err = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0) { + err = -errno; close(fd); return err; } @@ -236,5 +370,5 @@ int host_bthcisock_open(int dev_idx) int host_bthcisock_close(int fd) { - return close(fd); + return close(fd) < 0 ? -errno : 0; } diff --git a/arch/sim/src/sim/sim_head.c b/arch/sim/src/sim/sim_head.c index 1e6a2176b199b..58b4ff1801031 100644 --- a/arch/sim/src/sim/sim_head.c +++ b/arch/sim/src/sim/sim_head.c @@ -41,6 +41,9 @@ #include #include "sim_internal.h" +#ifdef CONFIG_SIM_HCISOCKET +# include "sim_hosthcisocket.h" +#endif /**************************************************************************** * Public Data @@ -189,6 +192,16 @@ int main(int argc, char **argv, char **envp) { host_set_timeratio(atoi(argv[i] + 15)); } +#ifdef CONFIG_SIM_HCISOCKET + else if (strncmp(argv[i], "--bt-dev=", 9) == 0) + { + if (host_bthcisock_configure(argv[i] + 9) < 0) + { + host_printf("invalid --bt-dev target: %s\n", argv[i] + 9); + return EXIT_FAILURE; + } + } +#endif #ifdef CONFIG_SIM_BSIM_TIME else if (strncmp(argv[i], "--sim-bsim-sid=", 15) == 0) { diff --git a/arch/sim/src/sim/sim_hosthcisocket.h b/arch/sim/src/sim/sim_hosthcisocket.h index b5e015fda5e3a..4d3574733ec21 100644 --- a/arch/sim/src/sim/sim_hosthcisocket.h +++ b/arch/sim/src/sim/sim_hosthcisocket.h @@ -35,6 +35,7 @@ ****************************************************************************/ int host_bthcisock_open(int dev_idx); +int host_bthcisock_configure(const char *target); int host_bthcisock_send(int fd, const void *data, size_t len); int host_bthcisock_receive(int fd, void *data, size_t len); int host_bthcisock_avail(int fd);