Every issue for EPIC E16 (nextbsd#473), written out in full: title, repo, labels, size, track, dependencies and the exact Markdown body. Companion to the Bluetooth plan. All 38 are filed as sub-issues of #473 on the roadmap (Epic = E16 Bluetooth), with native blocked-by links and a Dependencies footer on each.
area:bluetooth label is in all three.<script id="e16-data">).vkbd) and A1 (bake cuse) → merged into K1, so there’s one kernel config change.hcsecd and added SSP.| Repo | nextbsd/nextbsd-kernel | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | area:bluetooth area:kext enhancement | ||||
| Depends on | — | ||||
NextBSD has no Bluetooth at all (nextbsd/nextbsd#412): config/NEXTBSD carries no NETGRAPH* option, and neither amd64 nor arm64 GENERIC does either (grep -i netgraph sys/{amd64,arm64}/conf/GENERIC is empty). On stock FreeBSD that is fine because rc.d/bluetooth kldloads ng_bluetooth ng_hci ng_l2cap ng_btsocket (libexec/rc/rc.d/bluetooth:8) and ng_socket.c:281-303 kern_kldloads ng_<type> on any NGM_MKPEER for an unknown type. NextBSD deletes the whole .ko tree at image assembly (ci/assemble-image.sh:168), so every one of those paths fails — the wlan_xauth trap (#51, #52) again. Plugging in a dongle today gives a ugen with nothing to claim it.
Add to config/NEXTBSD (shared by amd64, arm64 and NEXTBSD-RPI5, which includes it), with a comment block in the file's house style explaining the no-.ko-tree reason:
options NETGRAPH # netgraph core (ng_base, ng_parse)
options NETGRAPH_SOCKET # ng_socket: PF_NETGRAPH — ngctl/libnetgraph/hccontrol find nodes through it
options NETGRAPH_BLUETOOTH # ng_bluetooth: net.bluetooth sysctls; MODULE_DEPEND of every BT node
options NETGRAPH_BLUETOOTH_HCI # ng_hci
options NETGRAPH_BLUETOOTH_L2CAP # ng_l2cap
options NETGRAPH_BLUETOOTH_SOCKET # ng_btsocket: PF_BLUETOOTH (HCI raw / L2CAP / RFCOMM / SCO); DOMAIN_SET, never autoloadable
options NETGRAPH_BLUETOOTH_UBT # ng_ubt + ng_ubt_intel + ng_ubt_rtl: USB HCI transport (class E0/01/01, Broadcom/Apple vendor-class)
Every token is stock and machine-independent: sys/conf/options:516-526, sys/conf/files:4299-4320 (ng_ubt*.c are optional netgraph_bluetooth_ubt usb), sys/conf/NOTES:813-818. All MODULE_DEPENDs resolve inside this set plus usb, which both GENERICs already bake (sys/arm64/conf/std.dev:101). No src-overlay, no patch.
Deliberately not added: NETGRAPH_BLUETOOTH_UBTBCMFW (2003-era BCM2033 firmware cdev whose firmware is unshippable, usr.sbin/bluetooth/bcmfw/bcmfw.8:54-56), and NETGRAPH_BLUETOOTH_H4/_BT3C (dead tokens — ng_h4 was removed in 2021, commit 79a100e28e).
What this does not do: nothing in the kernel builds the HCI/L2CAP graph. ng_ubt attaches a bare ubtN node; assembling ubtNhci/ubtNl2cap and wiring the btsock_* nodes is what rc.d/bluetooth did behind devd's bluetooth.conf, and on NextBSD it becomes bluetoothd's job (area 2). Until then it is done by hand with ngctl (K6).
config/NEXTBSD carries the seven options above with a comment in the file's style (cite assemble-image.sh:168, ng_socket.c:281-303, #52)config -x of the built kernel (amd64 and arm64 legs, and the NEXTBSD-RPI5 leg) lists all seven optionsngctl list shows btsock_hci_raw, btsock_l2c_raw, btsock_l2c; ngctl types lists hci, l2cap, ubt, socket; sysctl -n net.bluetooth.version prints a numberhccontrol read_bd_addr exits 7 with Could not find HCI nodes (usr.sbin/bluetooth/hccontrol/hccontrol.c:110-112) — i.e. PF_BLUETOOTH and PF_NETGRAPH exist; it must not say Could not create socket/usr/sbin/{hccontrol,ngctl,l2ping,sdpcontrol,sdpd,hcsecd,bthidd,iwmbtfw,rtlbtfw} (expected from the compat buildworld with MK_BLUETOOTH default yes, share/mk/src.opts.mk:70; verify, since the srclist plan once marked ngctl DEFER)size on kernel before/after; estimate is ~0.4–0.5 MB, unmeasured)em0/virtio DHCP path in the boot test is unchanged (purely additive change)ng_socket.c:281-303 is added to the #52 audit table as "covered by baking the BT types"Merged from E16 draft H1.
bthidd(8) opens /dev/vkbdctl unconditionally for every device whose HID descriptor has a keyboard page, and fails the session if it cannot (usr.sbin/bluetooth/bthidd/session.c:106-115, releng/15.1). This is independent of -u/uinput. vkbd(4) is a module on stock FreeBSD (sys/conf/files:3501 optional vkbd; not in sys/amd64/conf/GENERIC nor sys/arm64/conf/std.dev), and the stock rc.d/bthidd load_klds it (libexec/rc/rc.d/bthidd:34-38). NextBSD ships no .ko tree (ci/assemble-image.sh:168), so on today's config/NEXTBSD (which only includes GENERIC, line 1) no Bluetooth keyboard can ever attach, and the only symptom is a syslog line. Same class of bug as wlan_xauth (#51) and hms/hmt (#335), documented at config/NEXTBSD:153-159.
The rest of the bthidd sink path is already in GENERIC and inherited: options EVDEV_SUPPORT, device evdev, device uinput (GENERIC:381-383, std.dev:72-74) and device kbdmux (GENERIC:199, std.dev:49). kbd_register() auto-attaches every new keyboard, including each vkbd, to kbdmux (sys/dev/kbd/kbd.c:197-231). That inheritance is inferred, not yet observed on a shipped kernel; this ticket makes it verified.
In config/NEXTBSD, next to the HID block, add:
# Bluetooth HID (E16). bthidd(8) feeds every BT keyboard into a vkbd(4)
# instance (session.c:106-115), which kbd_register() auto-attaches to kbdmux.
# vkbd is module-only upstream (sys/conf/files: optional vkbd) and rc.d/bthidd
# kldloads it -- impossible here (no .ko tree), so it must be static.
# evdev/uinput/kbdmux come from GENERIC and are the other half of the path.
device vkbd
No overlay needed: vkbd is a stock config token. Valid on amd64 and arm64 (MI, sys/conf/files).
config/NEXTBSD contains device vkbd with the comment above; both matrix legs build green.config -x /boot/kernel/kernel | grep -E '^(device|options)[[:space:]]+(vkbd|uinput|evdev|kbdmux|EVDEV_SUPPORT)$' prints all five./dev/vkbdctl, /dev/uinput, /dev/kbdmux0, /dev/consolectl exist; sysctl kern.evdev.rcpt_mask exists./dev/vkbdctl, write scancodes) produces keystrokes on the console (dmesg/tty) — this is the manual precursor of H8.usr.sbin/bluetooth/bthidd/session.c, sys/dev/vkbd/vkbd.c:674, sys/dev/kbd/kbd.c:197-231Merged from E16 draft A1.
Bluetooth audio on FreeBSD 15 is virtual_oss(8), which is now in base (usr.sbin/virtual_oss, lib/libcuse) and exposes its mixed device through cuse(3) — a userland character-device framework. The cuse kernel module is optional in sys/conf/files:3603 (fs/cuse/cuse.c optional cuse), is not in amd64 GENERIC or arm64 std.dev, and on stock FreeBSD is kldloaded on demand (the port's rc script has required_modules="cuse"). NextBSD deletes the whole .ko tree at image assembly (ci/assemble-image.sh:168), so this is the wlan_xauth / snd_uaudio / snd_hda unbaked-leaf trap again: virtual_oss starts, finds no /dev/cuse, and exits. Same dependency for webcamd and the DriverKit-shape userland-driver plan (cuse is catalogued as org.nextbsd.kpi.CUSE in the kld-to-kext catalog), so the line pays for itself beyond Bluetooth.
In config/NEXTBSD, next to the snd_uaudio / snd_hda block (lines ~188-240), add with the usual rationale comment:
# Userland character devices (cuse(3)). virtual_oss(8) -- FreeBSD 15's base
# audio mixer and the Bluetooth A2DP path -- creates its /dev/dsp* nodes
# through cuse. Module-only on stock FreeBSD (sys/conf/files: optional cuse),
# and NextBSD ships no .ko tree, so it must be static. MI, no MODULE_DEPENDs.
device cuse
It reaches NEXTBSD-RPI5 through its include NEXTBSD.
device cuse present in config/NEXTBSD; amd64 and arm64 kernels build clean in CIls -l /dev/cuse exists after boot (add to tests/boot-test.sh alongside the /dev/sndstat check)virtual_oss -f /dev/dsp0 -d dsp.test -t dsp.test.ctl on the boot-test VM gets past cuse_init() (it will still die at kldload("cuse.ko") until A3 lands — record that in the test as expected-fail until then)wlan_xauth), and the snd_uaudio/snd_hda comments in config/NEXTBSD:188-240sys/conf/files:3603, sys/fs/cuse/cuse.c, lib/libcuse/cuse.3 (releng/15.1)e16/5-audio.md section 1| Repo | nextbsd/nextbsd | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth area:ci | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
The failure mode K1 fixes is silent: drop one NETGRAPH_* option and the kernel still boots; the symptom is hccontrol: Could not create socket: Address family not supported by protocol family or ngctl: send msg: No such file or directory months later on someone's laptop. #52 asked for exactly this kind of guard. There is no Bluetooth in qemu (the emulation was removed in 2019, qemu commits 1d4ffe8dc7 and 43d68d0a94), so CI cannot prove a radio works — but it can prove the stack is there and that the userland tools reach it.
Extend the qemu boot harness (tests/boot-test.sh / tests/img-boot-test.sh, both arches) with a Bluetooth block that runs after login and fails the job on any miss:
ngctl list | grep -q '^ *Name: btsock_hci_raw ' # ng_btsocket nodes exist at boot (ng_btsocket_hci_raw.c:197-207)
ngctl list | grep -q 'btsock_l2c_raw'
ngctl list | grep -q 'btsock_l2c '
ngctl types | grep -Eq '^ *Type name: (hci|l2cap|ubt|socket) ' # one line each; adjust to ngctl's exact format
sysctl -n net.bluetooth.version # ENOENT before K1
hccontrol read_bd_addr; test $? -eq 7 # "Could not find HCI nodes" — PF_BLUETOOTH + PF_NETGRAPH work
l2ping -a 00:00:00:00:00:01 2>&1 | grep -qv 'socket' # fails on connect, not on socket(2)
for b in hccontrol ngctl l2ping sdpcontrol sdpd hcsecd bthidd iwmbtfw rtlbtfw; do test -x /usr/sbin/$b; done
and, in the kernel repo's build job (or here, on the downloaded kernel), a config -x check that all seven options from K1 are present — the same idea as the existing KEXTD-LOAD / linuxulator probes.
Keep the strings exact and cite the source line each one comes from, so a future upstream change in hccontrol's wording fails loudly rather than passing vacuously.
ngctl list line (negative test done once, recorded in the PR)config -x option guard is wired (kernel repo build job or image assembly — whichever the maintainers prefer; say which)| Repo | nextbsd/nextbsd | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
Intel Wireless-AC/AX combo cards (VID 0x8087: 7260/7265, 8260/8265, 9260/9560, AX200-class — the twelve PIDs in sys/netgraph/bluetooth/drivers/ubt/ng_ubt_intel.c:89-105) and Realtek 87xx/88xx parts enumerate in bootloader/ROM mode. The in-tree drivers deliberately refuse to attach until operational firmware is loaded (ng_ubt_intel.c:33-37: attaching in bootloader mode "locks the adapter hardly so it requires power on/off cycle"; ng_ubt_rtl.c:172-181). The loaders are in base — iwmbtfw(8), rtlbtfw(8) — but they read the firmware from a directory (-f), and NextBSD ships none: /boot/firmware is deleted at assembly (nextbsd-kernel/ci/assemble-image.sh:169, and #392 removes a stray iwm*), and the kext firmware-bundle path (patch 0004, firmware(9)) does not apply because these blobs are consumed by userland tools, not the kernel. Every Intel laptop we already target for WLAN (IntelWiFi.kext) has one of these Bluetooth halves.
Do what build.sh:444-482 already does for the Pi DTBs and LICENCE.broadcom: fetch at image assembly, pinned, with the licence beside the blobs.
non-free-firmware .debs the FreeBSD ports use (comms/iwmbt-firmware: firmware-iwlwifi_<ver>_all.deb, extract usr/lib/firmware/intel/ibt-* + usr/share/doc/firmware-iwlwifi/copyright; comms/rtlbt-firmware: firmware-realtek_<ver>_all.deb, extract usr/lib/firmware/rtl_bt/* + its copyright). Pin the Debian version the way RPI_FIRMWARE_TAG is pinned. Licences are Intel's and Realtek's firmware licences; the ports declare LICENSE_PERMS= dist-mirror dist-sell pkg-mirror pkg-sell auto-accept, i.e. redistribution is allowed with the notice./usr/share/firmware/bluetooth/intel/ and /usr/share/firmware/bluetooth/rtl_bt/, with COPYRIGHT.intel / COPYRIGHT.realtek alongside. (A data-only org.nextbsd.firmware.bluetooth bundle under /System/Library/Extensions was considered; OSKext authentication and kextd gain nothing from an executable-less bundle, so a plain directory wins. Say so in the comment.)ibt-<hw_variant>-<hw_revision>* families that iwmbtfw's selection logic can actually pick for the twelve PIDs, and record the mapping.iwmbtfw -d ugenX.Y -f /usr/share/firmware/bluetooth/intel, rtlbtfw -d ugenX.Y -f /usr/share/firmware/bluetooth/rtl_bt — run by bluetoothd per K4.Out of scope: Broadcom .hcd patchram for USB parts (ROM firmware works; no loader in base), Atheros ath3k (no port, rare), the Pi 5 BCM4345C0.hcd (K8).
build.sh fetches both .debs at a pinned version, verifies a checksum, and stages the two directories plus licence files; the fetch is cached in $DIST like the DTBs/usr/share/firmware/bluetooth/{intel,rtl_bt} and both licence files; the size of each is recorded in the PRiwmbtfw -f /usr/share/firmware/bluetooth/intel -d <ugen> on a real Intel part (K6 hardware) results in the device re-enumerating and ubt0 attaching — recorded in the hardware matrix/boot/firmware (#392 stays fixed)comms/iwmbt-firmware, comms/rtlbt-firmware| Repo | nextbsd/nextbsd | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
FreeBSD 15 base ships virtual_oss(8) but builds only the null and oss backends (lib/virtual_oss/Makefile: SUBDIR+= null oss). The Bluetooth backend sources are in the tree (lib/virtual_oss/bt/: bt.c, avdtp.c, sbc_encode.c, SHLIB_NAME= voss_bt.so, LIBADD= bluetooth sdp) but are packaged by the ports tree as audio/virtual_oss_bluetooth (WRKSRC=${SRC_BASE}/lib/virtual_oss/bt, PLIST_FILES= lib/virtual_oss/voss_bt.so). virtual_oss dlopens it from a hard-coded /usr/local/lib/virtual_oss/voss_bt.so whenever a device string starts with /dev/bluetooth/ (usr.sbin/virtual_oss/virtual_oss/main.c:1650, 1671-1672, 1686-1687) and errx(1)s if it is missing. Without this file there is no A2DP on NextBSD at all.
SBC is built in; AAC is a compile option (HAVE_LIBAV) that drags in multimedia/gstreamer1-libav. Ship SBC-only.
Preferred, zero code: add virtual_oss_bluetooth to pkglist.txt so build.sh step 5b installs it from the FreeBSD:15 repo into the rootfs (it already lays down /usr/local packages that way). First verify the binary package exists (pkg -r $RF -o REPOS_DIR=$FBSD_REPOS search virtual_oss_bluetooth): the port is DISTVERSION=${OSVERSION} and IGNOREs without /usr/src, so it is plausible the cluster skips it.
Fallback if the package is absent: build lib/virtual_oss/bt in the base build (nextbsd-freebsd-compat: append SUBDIR.${MK_BLUETOOTH}+= bt to lib/virtual_oss/Makefile via a patch, and either install to /usr/local/lib/virtual_oss/ or patch main.c:1672/1687 to use prefix /usr), or as a tiny target in nextbsd-userland that compiles the three files from the FreeBSD source snapshot. Record which path was taken in PORTING.md.
/usr/local/lib/virtual_oss/voss_bt.so present in both .img and .iso rootfs; ldd resolves libbluetooth.so and libsdp.sonm -D exports voss_backend_bt_play and voss_backend_bt_recvirtual_oss -R /dev/null -P /dev/bluetooth/00:00:00:00:00:00 -d dsp.bt -t dsp.bt.ctl gets past dlopen and fails at the SDP/L2CAP step with a clear message (proves loading, not connectivity)PORTING.mdaudio/virtual_oss_bluetooth/Makefile and pkg-descr (freebsd-ports main); lib/virtual_oss/bt/Makefile (releng/15.1)e16/5-audio.md section 1.2| Repo | nextbsd/nextbsd-kernel | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | bug area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
virtual_oss self-heals on stock FreeBSD with
/* usr.sbin/virtual_oss/virtual_oss/main.c:2554 */
if (kldload("cuse.ko") < 0 && errno != EEXIST)
err(1, "Failed to load cuse kernel module");
It expects EEXIST when cuse is already in the kernel. That is not what the linker returns on NextBSD. sys/kern/kern_linker.c:1218-1224 treats any name containing . as a file name (kldname = file; modname = NULL); linker_load_module() (:2264-2282) then only does linker_search_kld(kldname) and returns ENOENT when no file exists — the modlist_lookup2() -> EEXIST branch is reached only for bare module names. Because NextBSD removes every .ko (ci/assemble-image.sh:168), a baked cuse still yields ENOENT, and virtual_oss exits before creating any device. webcamd and other cuse consumers carry the identical call. This is the userland face of the wlan_xauth class (#51): the kernel can no longer answer "is this module here?" for anything spelled foo.ko.
Add patches/00NN-kern_linker-static-module-answers-EEXIST-for-kldname.patch: in linker_load_module(), when modname == NULL and linker_search_kld() returns NULL, derive the module name from linker_basename(kldname) with a trailing .ko stripped and, if modlist_lookup2(name, NULL) != NULL, return EEXIST instead of ENOENT. This is what callers already handle, and it is semantically what stock FreeBSD returns when the file is found for an already-linked module ("Can't load more than one file with the same basename" -> EEXIST). No behaviour change when the file exists.
Also send the belt-and-braces fix upstream to freebsd/virtual_oss (main.c): if (modfind("cuse") < 0 && kldload("cuse.ko") < 0 && errno != EEXIST) — that also lets a non-root virtual_oss (which today gets EPERM from priv_check(PRIV_KLD_LOAD), kern_linker.c:1210) start when cuse is present. Link the PR from this issue; do not wait on it.
patches/ with the NextBSD prefix conventionkldload("cuse.ko")) returns -1/EEXIST on the VM; kldload("nonexistent.ko") still returns ENOENTvirtual_oss -f /dev/dsp0 -d dsp.test -t dsp.test.ctl runs, /dev/dsp.test appears, sndctl -f /dev/dsp.test reports from_user=1, and virtual_oss_cmd /dev/dsp.test.ctl prints system infofreebsd/virtual_oss opened and linkedsys/kern/kern_linker.c:1210-1224, 2255-2282; usr.sbin/virtual_oss/virtual_oss/main.c:2554-2558 (releng/15.1)e16/5-audio.md section 1.2| Repo | nextbsd/nextbsd | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | documentation area:bluetooth needs-hardware | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
Run the recipe below on (a) an amd64 laptop or desktop with a class-E0 dongle (CSR8510 0a12:0001 is the zero-firmware baseline), (b) the Pi 500+ with the same dongle in an RP1 xhci port (the onboard radio is parked, K8), and (c) once K3/K4 exist, an Intel 8265/9560/AX200 laptop and a Realtek 8761BU dongle. Record everything in a new "Bluetooth hardware matrix" section on the plans site (the WLAN plan's §9 table is the template), with kernel/userland SHAs.
usbconfig; dmesg | grep ubt; ngctl list # ubt0 on uhubN; node ubt0 (type ubt)
# graph, verbatim from libexec/rc/rc.d/bluetooth bluetooth_setup_stack()
ngctl mkpeer ubt0: hci hook drv; ngctl name ubt0:hook ubt0hci
ngctl mkpeer ubt0hci: l2cap acl hci; ngctl name ubt0hci:acl ubt0l2cap
ngctl connect ubt0hci: btsock_hci_raw: raw ubt0raw
ngctl connect ubt0l2cap: btsock_l2c_raw: ctl ubt0ctl
ngctl connect ubt0l2cap: btsock_l2c: l2c ubt0l2c
# controller up
hccontrol -n ubt0hci reset
hccontrol -n ubt0hci read_bd_addr # the real proof: a non-zero BD_ADDR
hccontrol -n ubt0hci read_local_version_information
hccontrol -n ubt0hci read_local_supported_features
hccontrol -n ubt0hci read_buffer_size
hccontrol -n ubt0hci write_scan_enable 3
hccontrol -n ubt0hci write_class_of_device 0x1c0104
hccontrol -n ubt0hci change_local_name nextbsd
hccontrol -n ubt0hci initialize; hccontrol -n ubt0hci read_node_state # "up"
# the world
hccontrol -n ubt0hci inquiry
hccontrol -n ubt0hci remote_name_request <addr> 0 0 0
l2ping -a <addr> # result=0
sdpcontrol -a <addr> browse # service records from a phone/headset
# teardown
ngctl shutdown ubt0hci:; ngctl shutdown ubt0l2cap:
Note what hccontrol read_local_version_information reports (HCI/LMP version) per dongle — it decides what BLE work is even possible later.
read_bd_addr, inquiry, l2ping, sdpcontrol browse outcomearea:bluetoothbluetoothd| Repo | nextbsd/nextbsd | Size | S | Track | Needs answer |
|---|---|---|---|---|---|
| Labels | question area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
The LE decision (B1) rests on the kernel's 2015-era LE code being usable: LE_Create_Connection via socket connect() with an LE address type, ATT on the CID-4 socket, SMP on CID-6, SO_L2CAP_ENCRYPTED, encryption-change propagation. It has been exercised by exactly one out-of-tree prototype, and the FreeBSD forum thread "The current state of BT-LE?" (2024-11 .. 2025-02) reports ATT timeouts against an MX Master 3S. Before B5/B6 are sized, run the prototype on the hardware NextBSD will bless and write down what actually works. This is the WLAN-plan "phase 1 hand-verification" for LE.
rhaberkorn/FreeBSD-BLE branch improvements (has hid-pair.sh, random-address support) on a NextBSD kernel with ng_bluetooth, ng_hci, ng_l2cap, ng_btsocket, ng_ubt baked in (K ticket). Note: no licence file - do not vendor, run only.hccontrol -n ubt0hci le_enable enable; le_read_buffer_size -v 2; le_read_local_supported_features on each controller: CSR 8510 dongle, Intel AX200/AX210 (iwmbtfw), Realtek 8852 (rtlbtfw). Record LE ACL buffer count vs BR/EDR count (feeds B5).le_enable scan; le_enable <addr> to dump attributes of: an MX Master 3S (BLE edition), a Keychron/Surface keyboard, a phone advertising, a heart-rate strap or Micro:bit if available.lepair + lesecd + lehid for the mouse: does the cursor move; does it survive the peripheral's connection-parameter-update request (kernel ACKs but never applies it, ng_l2cap_cmds.c:224-226); does it reconnect after power-cycling the mouse (peripheral-initiated, LTK request path via raw HCI).hcidump/btsnoop traces attached for each device; note any ng_l2cap panics or ng_hci "could not send" logs.| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | — | ||||
Parent: nextbsd/nextbsd#473 (E16). Seed: nextbsd/nextbsd#412. Area 3 notes: e16/3-bluetoothd-cli.md §1, §4.1, §7, §8.
Every later Bluetooth ticket (adapter lifecycle, scan, pairing, HID, audio) needs a daemon that already boots under launchd, holds org.nextbsd.bluetooth, and passes CI on a machine with no radio and, today, no netgraph in the kernel. wland showed that the skeleton is where the boot-order and /var/run races live (userland#48, #49), so land it first and alone. wland also shipped without any boot-test gate and is still UNPROBED in wedge-check.sh:153,156; bluetoothd must not repeat that.
Copy the wland layout one-for-one (src/WLAN/ → src/Bluetooth/):
src/Bluetooth/bluetoothd.c — main loop shaped like wland.c:472-655: signal handlers, ensure_run_dirs() (/var/run/bluetoothd, re-created on every attempt, wland.c:284-296), Mach service started before any hardware work (wland.c:499-505), SCDynamicStore opened lazily and NULL tolerated (wland.c:507-515), 1 s poll tick, 2 s adapter rescan (wland.c:93-99).src/Bluetooth/adapter.c — v0 only detects whether the stack exists: socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); EPROTONOSUPPORT → log BT-NOSTACK: ng_btsocket absent — idling and stop polling (the VAP_NO_NET80211 rule, vap.h:35-43, wland.c:526, :620-625); success with zero ubt nodes → BT-NOADAPTER and keep polling. Real adapter bring-up is D2.src/Bluetooth/mach_service.c/.h — verbatim copy of src/WLAN/mach_service.c with the service name org.nextbsd.bluetooth, marker BT-RPC-OK.src/Bluetooth/bluetooth.defs + bluetooth_mig_types.h — subsystem 31000; v0 carries bt_adapter_count, bt_adapter_info, bt_stack_state only, all fixed-size (wlan.defs:10-16 OOL rule). Full surface grows in D2–D8; the enums (bt_status_t etc.) are defined here in full so later tickets only append routines.src/Bluetooth/bluetoothd_mig.c — routine bodies under bluetoothd_lock().src/Bluetooth/bluetooth.c — the CLI, pure C, bootstrap_look_up, v0 verbs status and list (adapters only). Output: no Bluetooth adapters / stack: absent, exit 0.src/Bluetooth/Makefile — copy of src/WLAN/Makefile (bsd.prog.mk, MIGOUT=, WARNS=0, -fblocks, link line -llaunch -lsystem_kernel -lSystemConfiguration -lCoreFoundation -lsystem_dispatch -l:libsystem_blocks.so, BINDIR=/usr/sbin), MAN= bluetoothd.8 bluetooth.8.overlay/System/Library/LaunchDaemons/org.nextbsd.bluetoothd.plist — RunAtLoad, KeepAlive, MachServices {org.nextbsd.bluetooth}, stderr to /var/log/bluetoothd.stderr; header comment explains why org.nextbsd.* (same text as wland's plist) and why it always runs (no device-triggered launch exists on NextBSD).build-userland.sh — a comp "bluetoothd" block after the WLAN block (:740-778): host mig over bluetooth.defs, make ... MIGOUT=, then hand-link /usr/sbin/bluetooth from bluetooth.c + bluetoothUser.c with -llaunch -lsystem_kernel only.overlay/usr/tests/freebsd-launchd-mach/run.sh — after the ipconfig block: timeout 15 bluetooth status → BT-CLI-OK/BT-CLI-FAIL; cat /var/log/bluetoothd.stderr so BT-RPC-OK and BT-NOSTACK/BT-NOADAPTER reach the console.overlay/usr/tests/freebsd-launchd-mach/wedge-check.sh — probe "org.nextbsd.bluetooth" "bluetooth status" bluetooth status.nextbsd/tests/boot-test.sh — expect BT-RPC-OK and BT-CLI-OK (FAIL if absent); BT-NOSTACK/BT-NOADAPTER informational. (Separate PR in nextbsd/nextbsd, same ticket.)bluetoothd.8 / bluetooth.8 stubs with NAME/SYNOPSIS/DESCRIPTION/FILES; filled in by D6.libbluetooth, libnetgraph, libsdp, sdpd, bthidd, hccontrol (FreeBSD MK_BLUETOOTH/MK_NETGRAPH default yes; NextBSD's src.conf not yet inspected). Record the answer in the plan; if absent, file a follow-up before D2.hcsecd process exists at startup, log BT-SEC-CONFLICT (two link-key responders race; see D4).launchctl list on the CI image shows org.nextbsd.bluetoothd with a live PID; daemon-state.sh passes without edits/var/log/bluetoothd.stderr contains BT-RPC-OK and exactly one of BT-NOSTACK (pre-K kernel) or BT-NOADAPTER (post-K kernel)bluetooth status exits 0 within 15 s on the CI image and prints no Bluetooth adapters or stack: absent; run.sh emits BT-CLI-OK; boot-test.sh gates on BT-RPC-OK and BT-CLI-OKwedge-check.sh probes org.nextbsd.bluetooth; it is not in UNPROBEDem0 boot-test markers (IPCFG-*) are unchanged.m files, no CMake, no -ldispatch/-lBlocksRuntimelibbluetooth/libnetgraph/sdpd/bthidd is recorded in the PR descriptionhttps://pkgdemon.github.io/nextbsd-wlan-plan.html §7.1 (the daemon recipe) · src/WLAN/wland.c, src/WLAN/mach_service.c, src/WLAN/Makefile, overlay/System/Library/LaunchDaemons/org.nextbsd.wland.plist, build-userland.sh:740-778, overlay/usr/tests/freebsd-launchd-mach/wedge-check.sh:80-156, run.sh:1512-1520
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | — | ||||
bluetoothd's policy layer (adapter lifecycle, discovery list, pairing UX, SCPreferences persistence, SCDynamicStore publication, the MIG surface for bluetooth(8)) must not know whether a device is BR/EDR or LE. If the Classic engine is written first with Classic-only types (6-byte bdaddr_t, 16-byte link key, PIN-only prompts), adding LE later means touching every layer. WLAN avoided this by putting a narrow engine contract in src/WLAN/supplicant.h (opaque engine, event_fd, pump_events returning "state changed", caller-owned caches); do the same here, but for two engines.
New src/Bluetooth/engine.h (name to match area D's layout), contents per the research note §4.2:
struct bt_addr { uint8_t b[6]; enum { BREDR, LE_PUBLIC, LE_RANDOM } type; } - identity everywhere (CLI, store keys, dynamic-store paths). Maps 1:1 onto BDADDR_BREDR/BDADDR_LE_PUBLIC/BDADDR_LE_RANDOM (ng_bluetooth.h:228-229).struct bt_found - name, RSSI, class-of-device (Classic), appearance (LE), raw EIR/AD bytes (<=240, what the kernel neighbor cache already stores, ng_hci.h:632-634), and bredr/le seen-flags so a dual-mode device merges into one row.struct bt_pair_prompt { kind: PIN | PASSKEY_ENTRY | NUMERIC_CONFIRM | DISPLAY_PASSKEY; passkey } - one shape for legacy PIN, SSP and LE SMP; enum bt_pair_io for the local IO capability the UI can offer.struct bt_bond { addr; kind BREDR|LE; link_key[16]; ltk[16], irk[16], csrk[16], ediv, rand, key_size, authenticated, secure_conn } - the SCPreferences record for both engines.struct bt_engine_ops { attach, detach, event_fd, pump, discover, found_count/found_get, pair, pair_reply, connect, disconnect, bond_export, bond_import, profiles } plus an event queue (FOUND, PAIR_PROMPT, PAIRED, PAIR_FAILED, CONNECTED, DISCONNECTED, BOND_UPDATED).struct bt_le_ops reachable via bt_engine_le(), consumed by the HOGP bridge (area H), never by the policy layer.ng_btsocket_hci_raw), one SO_HCI_RAW_FILTER union mask, and a dispatcher keyed on event code; the LE engine adds three adapter bring-up commands (Write_LE_Host_Supported, LE_Set_Event_Mask without subevent 0x0a, Set_Event_Mask | LE_META) - what hccontrol le_enable does (le.c:334-361).engine.h committed with the types above and a header comment explaining the two-engine rule and the WLAN precedentbluetoothd.c/MIG codele_engine.c returning ENOTSUP) registers through the same table, proving the policy layer is engine-agnosticbt_bond with both key families; bond_import at startup restores Classic link keys and LE LTKs before the first connectionbluetooth(8) prints addresses as AA:BB:CC:DD:EE:FF for BR/EDR and AA:..:FF/le or /le-random for LEnextbsd-userland/src/WLAN/supplicant.h, wland.h, wlan.defsbluetoothd skeleton ticket for file placement| Repo | nextbsd/nextbsd-userland | Size | L | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D1 (nextbsd-userland#206), K1 (nextbsd-kernel#231), B2 (nextbsd-userland#204) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §2, §4, §7.
On FreeBSD a USB dongle is useless until /etc/rc.d/bluetooth (devd-triggered) has built the netgraph graph and run the HCI init sequence (libexec/rc/rc.d/bluetooth, bluetooth_setup_stack). ng_ubt does not do it itself (no mkpeer in ng_ubt.c). NextBSD has no rc.d and no devd (README.md:31), so bt_devenum() (lib/libbluetooth/hci.c:691-720) lists nothing forever. This is the VAP-clone problem again (vap.h:1-19, userland#46 "Nothing creates the VAP"), and it belongs to bluetoothd.
src/Bluetooth/adapter.c/.h, replacing D1's stub:
ubt via libnetgraph (NgMkSockNode + NgSendMsg(NGM_GENERIC_COOKIE, NGM_LISTNODES)). Idempotent adopt: if ubtNhci already exists (a bluetoothd restart), attach to it instead of rebuilding (vap_find_existing() rule, vap.h:79-86). Handle removal: a recvfrom error on the raw socket or the node vanishing marks the slot inactive and removes its store key.bluetooth_setup_stack: mkpeer ubtN: hci hook drv; name ubtN:hook ubtNhci; mkpeer ubtNhci: l2cap acl hci; name ubtNhci:acl ubtNl2cap; connect ubtNhci: btsock_hci_raw: raw ubtNraw; connect ubtNl2cap: btsock_l2c_raw: ctl ubtNctl; connect ubtNl2cap: btsock_l2c: l2c ubtNl2c.ubtNhci (bt_devopen, bt_devreq): Reset (retry ×3), Read_BD_ADDR, Read_Local_Supported_Features, Read_Buffer_Size, Write_Class_Of_Device 0x000104, Change_Local_Name (ComputerName from configd if published, else hostname), Write_Scan_Enable 2 (page scan only), Set_Event_Mask = default ∪ SSP bits (NG_HCI_EVMSK_IO_CAPABILITY_REQ|_RESP|USER_CONFIRMATION_REQ|USER_PASSKEY_REQ|SIMPLE_PAIRING_COMPL|USER_PASSKEY_NOTIFICATION, ng_hci.h:275-283), Write_Simple_Pairing_Mode 1 when NG_HCI_LMP_SECURE_SIMPLE_PAIRING (ng_hci.h:118) is set, then NGM_HCI_NODE_INIT. Log BT-ADAPTER-OK: ubt0hci <bdaddr> ssp=<y|n>.State:/Bluetooth/Adapter/<node> = {Address, Name, Powered, Discoverable, Connectable, Scanning} from the CF TU (btstore.c, D5's file; D2 adds the adapter half) and notify_post("org.nextbsd.bluetooth.adapter").bt_adapter_power(name, on) — off = disconnect all, Write_Scan_Enable 0, Powered:false, refuse scan/connect with bt_status_powered_off_e; on = re-enable scans. Persist under Adapters/<bdaddr>/Powered in org.nextbsd.bluetoothd prefs; honour it at bring-up. bt_adapter_discoverable(name, on, seconds) toggles inquiry scan with an auto-off timer.bt_adapter_count, bt_adapter_info(index → name, addr, powered, discoverable), bt_adapter_power, bt_adapter_discoverable in bluetooth.defs; CLI bluetooth status, bluetooth power on|off, bluetooth discoverable on|off [-t s].src/Bluetooth/hci_compat.h with the opcodes/events ng_hci.h lacks (Write_Inquiry_Mode 0x0045, events 0x22, 0x2f, 0x32, 0x34, 0x3b) until the K ticket upstreams them.bluetooth status prints the adapter node, bdaddr, powered: on within 5 s of plugging in; ngctl list shows ubt0hci, ubt0l2cap connected to btsock_*BT-ADAPTER-OK in the log; State:/Bluetooth/Adapter/ubt0hci present (verified through bluetooth status, which reads the same data over MIG — there is no scutil)hci node); unplugging the dongle removes the store key and bluetooth status returns to no Bluetooth adaptersbluetooth power off → controller stops answering pages (verified from a phone); power on restores; the setting survives a rebootBT-NOADAPTER, BT-CLI-OKssp=y|n)libexec/rc/rc.d/bluetooth (bluetooth_setup_stack), lib/libnetgraph/netgraph.h:48-60, lib/libbluetooth/hci.c:54-76,691-720, sys/netgraph/bluetooth/include/ng_hci.h:118,275-283,1015-1022,1423-1428, src/WLAN/vap.h, src/WLAN/wland.c:421-470, src/DiskArbitration/da_iokit_subscribe.c (event-driven alternative, follow-up)
Implements its part of the Classic engine behind the engine.h interface (B2).
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D2 (nextbsd-userland#207) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §5, §5.1.
Pairing starts with the user picking a device from a list. Scan results cannot cross MIG as a plist (OOL is broken, wlan.defs:10-16) nor sit in the dynamic store (CONFIG_DATA_MAX 8192, config_types.h:74), so bluetoothd keeps a per-adapter cache and hands it out one entry per RPC, exactly as wlan_scan_count/wlan_scan_entry do (wland_mig.c:110-156).
src/Bluetooth/hci.c/.h — the raw-socket event loop (poll fd per adapter, in the main poll() set like supplicant_event_fd, wland.c:544-582). hci_scan_start(adapter, seconds): Inquiry (GIAC 0x9e8b33, length = seconds/1.28, unlimited responses); collect Inquiry_Result (0x02) and, if Write_Inquiry_Mode 1 was accepted at init, Inquiry_Result_with_RSSI (0x22); on Inquiry_Complete mark scanning=0, then issue Remote_Name_Request sequentially for entries without a cached name (Remote_Name_Request_Complete 0x07). Cache: struct bt_scan_entry { bdaddr, name[249], cod, rssi, last_seen }, max 64 per adapter, entries older than 5 min dropped on the next scan. Names are cached across scans by bdaddr.bt_scan(name, seconds) returns as soon as the inquiry is requested (wlan.defs comment on wlan_scan); bt_scan_count(name → count, scanning); bt_scan_entry(name, i → addr, devname, cod, rssi, paired).notify_post("org.nextbsd.bluetooth.scan") when the cache changes.bluetooth scan [-t seconds] [adapter]: request, poll bt_scan_count.scanning until 0 (max seconds+5), then print ADDRESS NAME CLASS SIGNAL PAIRED with the wlan.c:110-122 signal bar; class rendered from CoD major/minor (keyboard, mouse, headset, phone, computer, …). No fixed sleep(3) — that is the wart wlan.8 BUGS records.bt_status_powered_off_e / bt_status_busy_e as appropriate.bluetooth scan on real hardware lists a phone in discoverable mode with its name within ~15 s; a second scan is faster (names cached)no devices found and exits 0bluetooth scan while powered off prints bluetooth: scan: adapter is powered off, exit 1bluetooth scan invocations: the second gets busy or joins the running inquiry; neither hangs the Mach service (wedge probe still answers during a scan)bluetooth scan with no adapter prints no Bluetooth adapters, exit 1, within 15 ssrc/WLAN/wlan.defs (wlan_scan*), src/WLAN/wland_mig.c:110-156, src/WLAN/supplicant.h:33-39, ng_hci.h:697,838,1866-1871,1915, lib/libbluetooth/bluetooth.h:157-186 (bt_devinquiry, bt_devremote_name as reference implementations)
Implements its part of the Classic engine behind the engine.h interface (B2).
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D1 (nextbsd-userland#206) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §6.
"Keep it paired across reboots" is the E16 goal statement. Link keys are 16-byte secrets, there is still no keychain (keychain plan §4.G "interim", Q1 "first consumer"), and WLAN already chose plaintext PSKs in a root-owned SCPreferences file as acknowledged debt (src/WLAN/airport.h:40-48, https://pkgdemon.github.io/nextbsd-wlan-plan.html Q2). Bluetooth repeats that decision, with one improvement: the secrets live in their own prefs file behind one function, so the keychain migration later moves one file and changes one function, and non-secret device metadata stays world-readable for tooling.
src/Bluetooth/btstore.c/.h — the single CoreFoundation translation unit (the airport.c:1-13 rule), holding both the SCDynamicStore publish helpers used by D2/D4/D8 and the SCPreferences code:
org.nextbsd.bluetoothd → /Local/Library/Preferences/SystemConfiguration/org.nextbsd.bluetoothd.plist:PairedDevices = { "<addr>" = { Name, Class (int), Adapter (local bdaddr), Paired (unix time), LastSeen, AutoConnect (bool), Trusted (bool), Profiles (array of strings, e.g. "HID") } }Adapters = { "<bdaddr>" = { Powered, Discoverable, Name } }org.nextbsd.bluetoothd.keys → ...org.nextbsd.bluetoothd.keys.plist:LinkKeys = { "<addr>" = { Key = <CFData 16 bytes>, Type = <int, HCI key_type> } }; the name LongTermKeys is reserved for the B area.bt_dev_save/find/forget/count/at, bt_adapter_pref_get/set, bt_keys_save/find/forget. Every save commits immediately (SCPreferencesSetValue + SCPreferencesCommitChanges, temp-file + rename as airport.c:276-293).umask(077) at daemon start and chmod(path, 0600) after every commit of the keys file (rename yields a new inode, and whether SCPreferences honours umask is unverified). The metadata file stays 0644.bt_paired_count, bt_paired_entry(i → addr, devname, cod, state, autoconnect); CLI bluetooth list (ADDRESS NAME CLASS STATE AUTO).bluetoothd.8 FILES section documents both paths and states plainly that link keys are stored in plaintext.bt_dev_save + bt_keys_save pair from a unit-style test binary (run in run.sh, marker BT-PREFS-OK) round-trips through a bluetoothd restart on the CI image with no adapter: bluetooth list shows the entry, the keys file is -rw------- rootbt_keys_forget removes only the key; bt_dev_forget removes only the metadata; each file is valid XML plist afterwards (parseable by plutil-equivalent or CFPropertyList round-trip in the test)grep -c of any 32-hex-char string in /var/log/bluetoothd.stderr after a pairing (D4) is 0src/WLAN/airport.c:230-430, src/WLAN/airport.h:32-48, usr.sbin/bluetooth/hcsecd/parser.y:300-390 (the hcsecd.keys format it replaces), https://pkgdemon.github.io/freebsd-keychain-port-plan.html
| Repo | nextbsd/nextbsd-userland | Size | L | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D2 (nextbsd-userland#207), D5 (nextbsd-userland#210) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §3.1, §3.3, §5.2, §5.3.
FreeBSD's hcsecd(8) cannot be the key owner under bluetoothd:
/etc/bluetooth/hcsecd.conf (hcsecd.c:392-396, get_key(.., exact_match=1)), so a device paired at runtime loses its key;/var/db/hcsecd.keys only on SIGHUP or exit (hcsecd.c:202,423);hcsecd.c:124-127,190-193) — no Secure Simple Pairing, which every phone and most keyboards made after 2009 require;PIN_Code_Request from a static file and cannot ask anyone;Link_Key_Request (ng_btsocket_hci_raw.c:500-517), so running hcsecd next to bluetoothd would make both reply.The reply code is ~120 lines (hcsecd.c:264-379); reimplementing it over the same socket with a real key store behind it is smaller than any supervision scheme. The kernel already delivers every HCI event to the raw hook before processing it (ng_hci_main.c:706-719, ng_hci_misc.c:63-78) and defines the SSP events/replies bluetoothd needs (ng_hci.h:865-907,2089-2103); what nothing in base does is set the controller event mask and Write_Simple_Pairing_Mode (D2 does).
src/Bluetooth/security.c/.h:
PIN_CODE_REQ, LINK_KEY_REQ, LINK_KEY_NOTIFICATION, AUTH_COMPL, IO_CAPABILITY_REQUEST (0x31), 0x32, USER_CONFIRMATION_REQUEST (0x33), 0x34, 0x36, 0x3b, plus the connection events (bt_devfilter_evt_set).Link_Key_Request → Link_Key_Request_Reply from bt_keys_find() else _Negative_Reply; Link_Key_Notification → bt_keys_save() immediately (D5); PIN_Code_Request → reply if a PIN was supplied to bt_pair or park in awaiting_user(method=pin); IO_Capability_Request → IO_Capability_Request_Reply(DisplayYesNo, no OOB, MITM required) if we initiated a pairing with this bdaddr, else IO_Capability_Request_Negative_Reply (incoming pairings rejected in v0); User_Confirmation_Request(numeric) → park in awaiting_user(method=numeric_comparison, passkey) (auto-accept only when the peer is NoInputNoOutput and we initiated); User_Passkey_Request → awaiting_user(method=passkey_entry); Simple_Pairing_Complete/Authentication_Complete → done|failed.bt_pair(addr, pin) → Create_Connection (page-scan params from the scan cache) → Authentication_Requested → the state machine above; 60 s awaiting_user timeout sends the negative reply and fails with pairing_timeout.awaiting_user, set State:/Bluetooth/Pairing/<addr> = {Method, Passkey, Expires} and notify_post("org.nextbsd.bluetooth.pairing"); bt_pair_status(addr → phase, method, passkey); bt_pair_confirm(addr, accept, passkey) sends User_Confirmation_Request_Reply/_Negative_Reply or User_Passkey_Request_Reply/PIN_Code_Request_Reply. Remove the Pairing/ key on done/failed.PairedDevices[addr] written (name, class, adapter, time, AutoConnect=true, Trusted=true), State:/Bluetooth/Device/<addr> published, notify_post("org.nextbsd.bluetooth.device").bt_unpair(addr): bt_keys_forget, Delete_Stored_Link_Key (ng_hci.h:1090), disconnect if connected, remove prefs entry and store key, notify./etc/bluetooth/hcsecd.conf is never read; if a hcsecd process exists, log BT-SEC-CONFLICT and refuse to answer Link_Key_Request until it is gone (prevents a double reply).hcsecd.c:249-253).bluetooth pair <addr> [pin] interactive prompt Confirm that 123456 is shown on <name> [y/N]; bluetooth confirm <addr> yes|no|<passkey>; bluetooth unpair <addr>.bluetooth pair shows the same 6 digits the phone shows; y completes; the phone lists the desktop as pairedDisplayYesNo on us) and legacy PIN with a 2000s-era device (PIN via argument) both completen, or not answering for 60 s, fails cleanly (pairing_rejected / pairing_timeout), leaves no Pairing/ key, and the adapter is usable immediately afterLink_Key_Request is answered from the store — verified by reconnecting without pairing againbluetooth unpair makes the next connection attempt from the device fail authentication (it must be re-paired)hcsecd process runs on the image; BT-SEC-CONFLICT fires if one is started by handusr.sbin/bluetooth/hcsecd/hcsecd.c, sys/netgraph/bluetooth/hci/ng_hci_main.c:706-719, ng_hci_misc.c:63-78, ng_hci_evnt.c:107-126, sys/netgraph/bluetooth/include/ng_btsocket.h:71-74, ng_btsocket_hci_raw.c:500-517,704-716, ng_hci.h:275-283,813,865-907,1090,2089-2103, src/hostnamed/hostnamed.c:20 (notify_post precedent)
This is the Classic engine behind engine.h (B2): inquiry/remote-name (D3) and legacy PIN + Secure Simple Pairing here share one raw HCI socket and event loop. B3's analysis follows.
FreeBSD's Classic pairing daemon hcsecd(8) answers exactly three events - PIN_Code_Request, Link_Key_Request, Link_Key_Notification (usr.sbin/bluetooth/hcsecd/hcsecd.c:123-126) - i.e. legacy PIN pairing only. Since FreeBSD commit 4ae0fa8a2f (2025-01-27, PR 265066) the kernel defines and passes through the Secure Simple Pairing events (IO_Capability_Request 0x31, User_Confirmation_Request 0x33, Simple_Pairing_Complete 0x36; ng_hci_evnt.c:123-124, ng_hci.h:865-898, 2089-2094) but no daemon in base uses them. Devices that require SSP (BT 2.1+ with no legacy fallback, "Secure Connections Only" mode) cannot pair on stock FreeBSD. Because hcsecd is ~500 lines with a yacc config, absorbing it into bluetoothd's Classic engine costs less than supervising it and bolting SSP on beside it. (Unlike wpa_supplicant, there is nothing to preserve.)
src/Bluetooth/classic_engine.c implementing bt_engine_ops (B2):
socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI), bind to the ubtNhci node, set SO_HCI_RAW_FILTER for: Inquiry_Result(+RSSI, Extended), Inquiry_Complete, Remote_Name_Request_Complete, Connection_Complete/Disconnection_Complete, PIN_Code_Request, Link_Key_Request, Link_Key_Notification, IO_Capability_Request/Response, User_Confirmation_Request, User_Passkey_Request/Notification, Simple_Pairing_Complete, Authentication_Complete, Encryption_Change.Write_Simple_Pairing_Mode(1), Write_Scan_Enable, Write_Class_of_Device, Write_Local_Name (from ComputerName in SCPreferences), Set_Event_Mask including the SSP bits (NG_HCI_EVMSK_IO_CAPABILITY_REQ .. _USER_PASSKEY_NOTIFICATION, ng_hci.h:275-283).discover(): Inquiry (GIAP, 10.24 s) then Remote_Name_Request per result without EIR name; fill bt_found (CoD, EIR bytes, RSSI).pair(): initiate Authentication_Requested on an ACL (or Create_Connection first); answer IO_Capability_Request with the caller's bt_pair_io; surface User_Confirmation_Request (numeric comparison) / User_Passkey_Request / PIN_Code_Request as one BT_EV_PAIR_PROMPT; pair_reply() sends the matching *_Reply / *_Negative_Reply (OCFs 0x2b-0x2d, 0x34 for SSP; 0x0d/0x0e for PIN).Link_Key_Notification -> bt_bond{kind=BREDR} -> BT_EV_BOND_UPDATED (policy layer persists); Link_Key_Request answered from the in-memory bond table populated by bond_import.profiles(): SDP ServiceSearchAttribute via libsdp for HID (0x1124), A2DP sink (0x110b), HFP (0x111e), AVRCP (0x110e) - enough for the CLI and for area H/A to decide what to start.hcsecd.conf compatibility for one release: bond_import can read /etc/bluetooth/hcsecd.conf keys if present (migration), then the file is retired.bluetooth scan lists a Magic Keyboard and a Classic headset with names and CoD within one inquiry cyclebluetooth pair --confirm), and SSP just-works pairing with a headsethcsecd is not started by launchd; no /var/run/hcsecd.pidhcidump-style frames for every filtered event| Repo | nextbsd/nextbsd-userland | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D2 (nextbsd-userland#207) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §2, §3.2.
sdpd(8) is the SDP server every profile daemon registers with over /var/run/sdp (lib/libsdp/sdp.h:550); without it a remote device cannot discover what the desktop offers, and bthidd/btpand fail to register. It stays stock — base FreeBSD, socket-driven, Mach-unaware — for the same reasons wpa_supplicant does (wland.c:41-55; userland#46). launchd cannot start it: it needs the L2CAP socket layer wired to an adapter, which only exists after D2, and there is no launchd job ordering. So bluetoothd spawns it, as wland spawns wpa_supplicant (wland.c:249-282).
src/Bluetooth/child.c/.h — a small generic supervisor: child_spawn(name, argv) (fork/exec, -d no-detach so we reap), child_reap() from the main loop's waitpid(-1, WNOHANG) sweep (wland.c:584-601), child_stop(name) (SIGTERM, wait), restart with 1 s/2 s/4 s back-off capped at 30 s, BT-CHILD-DIED: <name> log line. The same API is what the H (bthidd) and, if ever, PAN (btpand) tickets call./usr/sbin/sdpd -d when the first adapter reaches BT-ADAPTER-OK; stop it when the last adapter goes away; never run two.SIGPIPE ignored (wland.c:492-493); children inherit no Mach ports beyond bootstrap.wland.c:630-650 reasoning).sdpd is running as a child of bluetoothd within 1 s of BT-ADAPTER-OK; sdpcontrol -l browse from another FreeBSD/Linux box lists the desktop's records once a profile registerskill -9 $(pgrep sdpd) → restarted within 2 s, BT-CHILD-DIED: sdpd logged; ten kills in a row hit the back-off cap without spinningusr.sbin/bluetooth/sdpd/sdpd.8, sdpd/main.c:75-95 (-d, -c, -u, -g), src/WLAN/wland.c:249-282,584-601
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D4 (nextbsd-userland#209), D5 (nextbsd-userland#210) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §5, §7.
Pairing is worthless if the device does not come back after a reboot. On FreeBSD the ACL is either initiated by the peripheral paging us (page scan must be on and the link key answered — D2, D4) or by us (Create_Connection). bluetoothd owns the policy: reconnect AutoConnect devices when an adapter comes up, accept incoming ACLs from Trusted devices, and publish a compact per-device state that the pane, the H/A daemons and bluetooth list all read. This is the AirPort{Authenticated} equivalent for Bluetooth (airport.h:9-25).
src/Bluetooth/hci.c: Create_Connection/Disconnect (ng_hci.h:724,735), track Connection_Complete/Disconnection_Complete/Encryption_Change/Role_Change per bdaddr; Authentication_Requested + Set_Connection_Encryption on every new ACL to a paired device so profile traffic is always on an encrypted link.bt_connect(addr), bt_disconnect(addr), bt_device_status(addr → devname, cod, state, rssi); bt_connect returns when requested (the wlan_connect rule) and the caller watches bt_device_status/bluetooth list.State:/Bluetooth/Device/<addr> = {Name, Class, Adapter, Paired, Connected, Profiles}, updated on every transition; notify_post("org.nextbsd.bluetooth.device"). Only paired or connected devices get a key (never scan results).BT-ADAPTER-OK, for each PairedDevices[*].AutoConnect bound to this adapter, attempt Create_Connection once with a 10 s spacing; incoming Connection_Request from a Trusted bdaddr is accepted, from anyone else rejected (Reject_Connection_Request), logged.Read_RSSI on connected devices every 10 s for bt_device_status (cheap; optional if the controller rejects it).bluetooth connect|disconnect <addr> verbs.bluetooth list shows it connected within 30 s of BT-ADAPTER-OK with no user action (the HID data path itself is the H ticket; this ticket proves the ACL + encryption)bluetooth disconnect drops the ACL; the device reconnects when it pages us (keyboard keypress) because it is TrustedState:/Bluetooth/Device/<addr> transitions are visible through bluetooth list and bluetooth status (paired/connected counts) and each publish is under 1 KiBbluetooth connect aa:bb:cc:dd:ee:ff prints no Bluetooth adapters, exit 1ng_hci.h:724,735,813,819, src/WLAN/airport.c:129-212, src/WLAN/wlan.defs (wlan_connect returns-when-requested comment)
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D1 (nextbsd-userland#206), D3 (nextbsd-userland#208), D4 (nextbsd-userland#209), D5 (nextbsd-userland#210), D8 (nextbsd-userland#213) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §9, §10.
The CLI is two things, as wlan(8) is (src/WLAN/wlan.c:14-40): the only way to inspect Bluetooth state on a system with no scutil, and the desktop's bridge across the Mach line — no Gershwin process speaks Mach, so the preference pane will drive bluetooth via NSTask exactly as Network.prefPane drives its helper. D1–D5 and D8 each land their minimum verb; this ticket makes the surface complete, consistent, documented and scriptable.
src/Bluetooth/bluetooth.c (pure C, -llaunch -lsystem_kernel only) and bluetooth.8, bluetoothd.8:
bluetooth status [adapter]
bluetooth list
bluetooth scan [-t seconds] [adapter]
bluetooth pair <addr> [pin] [--no-prompt]
bluetooth confirm <addr> yes|no|<passkey>
bluetooth unpair <addr>
bluetooth connect <addr>
bluetooth disconnect <addr>
bluetooth power on|off [adapter]
bluetooth discoverable on|off [-t seconds] [adapter]
status is key/value lines like wlan status; list/scan are fixed-width tables like wlan scan, wlan.c:215-229).pair --no-prompt only, printing method=<numeric_comparison|passkey_entry|pin> passkey=<n> on stdout so the pane can show it and call confirm).pair interactive: polls bt_pair_status every 250 ms, prompts on the tty, calls bt_pair_confirm, prints the outcome; honours Ctrl-C by sending confirm no.aa:bb:cc:dd:ee:ff or a unique name prefix from the paired list.cannot reach org.nextbsd.bluetooth — is bluetoothd running? on lookup failure (wlan.c:360-366); status_str() table for every bt_status_t.bluetooth.8: COMMANDS, EXIT STATUS, FILES (/var/log/bluetoothd.stderr, both prefs paths, link keys are plaintext), EXAMPLES (scan → pair with numeric comparison → list → unpair, in the style of wlan.8 EXAMPLES), DIAGNOSTICS (no Bluetooth adapters → ngctl list; stack: absent → kernel lacks ng_btsocket; busy), SEE ALSO (bluetoothd(8), hccontrol(8), sdpd(8), bthidd(8)), BUGS.bluetoothd.8: ARCHITECTURE diagram (as wland.8), why hcsecd is replaced, which children it supervises, the State:/ keys and notify names, FILES.run.sh: extend BT-CLI-OK to run every verb once on the adapterless image and assert the documented exit codes (status/list → 0; scan/pair/connect → 1 with no Bluetooth adapters; bad verb → 2).bluetooth.8, and returns the documented exit code on the CI image (asserted by run.sh)bluetooth pair --no-prompt + bluetooth confirm complete an SSP pairing on hardware without any tty interactionman bluetooth and man bluetoothd render (mandoc lint clean, as wlan.8/wland.8)needed_check-style assertion in build-userland.sh)src/Bluetooth/ mirrors userland#47's WLAN sectionsrc/WLAN/wlan.c, src/WLAN/wlan.8, src/WLAN/wland.8, build-userland.sh:763-776, nextbsd/nextbsd#396 (the pane pattern this is the backend for), userland#47
| Repo | nextbsd/nextbsd | Size | S | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | D1 (nextbsd-userland#206) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §8.
There is no Bluetooth in qemu, so CI can only prove that bluetoothd boots, registers, answers a bounded RPC, persists, and does not disturb the wired boot. That is still the line between "wland-style silently unprobed" (wedge-check.sh:153,156; no WLAN expectation in tests/boot-test.sh) and a daemon whose liveness is asserted on every image. This ticket owns the nextbsd/nextbsd side of the gates that D1/D5/D6 emit from run.sh.
tests/boot-test.sh (expect blocks placed after IPCFG-IPCONFIG-OK, before LAUNCHD-MACH-RUN-DONE, in the style of :986-997):
BT-RPC-OK — FAIL if absent ("bluetoothd did not claim org.nextbsd.bluetooth")BT-CLI-OK — FAIL on BT-CLI-FAIL or timeout ("bluetooth status did not answer within 15s")BT-PREFS-OK — FAIL on BT-PREFS-FAIL (D5's round-trip test); WARN if absent on pre-D5 imagesBT-NOSTACK / BT-NOADAPTER — informational, but once nextbsd-kernel bakes ng_btsocket (K1) the expectation flips: BT-NOSTACK becomes FAIL ("kernel lost netgraph/Bluetooth — the wlan_xauth trap, see kernel#52")WEDGE-CHECK output must list org.nextbsd.bluetooth as OK, not UNPROBEDDAEMON-STATE-OK already covers the resident jobAlso: add bluetooth and bluetoothd to the pkglist/superseded audit if hcsecd is on the image (the image must not start it; D4). Document the markers in tests/README next to the IPCFG ones.
BT-RPC-OK (negative test done once by hand, recorded in the PR)ng_btsocket fails CI at BT-NOSTACKtests/boot-test.sh:858-997 (ipconfigd gates), ul/overlay/usr/tests/freebsd-launchd-mach/run.sh:1498-1520, wedge-check.sh, daemon-state.sh, https://pkgdemon.github.io/nextbsd-wlan-plan.html §1.1 (the unbaked-module trap)
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K3 (nextbsd#476), D2 (nextbsd-userland#207) | ||||
On stock FreeBSD two devd rule files fire the firmware loaders on USB attach: usr.sbin/bluetooth/iwmbtfw/iwmbtfw.conf (system USB / subsystem DEVICE / type ATTACH / vendor 0x8087 / product (0x07dc|0x0a2a|0x0aa7|0x0a2b|0x0aaa|0x0025|0x0026|0x0029|0x0032|0x0033|0x0035|0x0036) → /usr/sbin/iwmbtfw -d $cdev -f …) and rtlbtfw.conf (365 lines: generic vendor 0x0bda + interface 0 + intclass 0xe0/intsubclass 0x01/intprotocol 0x01, plus ~40 explicit VID/PIDs for 8821CE/8851BE/8852AE/BE/CE/8922AE/8723AE/BE/BU/DE/8761BUV/8821AE/8822BE/CE → /usr/sbin/rtlbtfw -d $cdev -f …). Then the stock sbin/devd/bluetooth.conf runs service bluetooth quietstart ubtN when the re-enumerated device attaches as ubt.
NextBSD has no devd (stripped in nextbsd-freebsd-compat), launchd's HardwareMatch is iter-1 parse-only and matches name/class/driver — never VID/PID (src/launchd/src/core.c:262-270, 6014-6047), and the in-kernel IOCatalogue matches PCI and FDT only (nextbsd-kernel src-overlay/sys/sys/iocatalogue.h:24-31). So nothing loads Bluetooth firmware, and an Intel laptop's Bluetooth never appears.
This ticket fixes the contract so the daemon ticket (area 2's bluetoothd v0) implements it; it is Sequence, not Ship, because it lands with the daemon.
In bluetoothd (or, if area 2 prefers, a tiny org.nextbsd.btfw helper it spawns), on USB device arrival (libIOKit IOKitNotify, the same registry subscription diskarbitrationd uses in src/DiskArbitration/da_iokit_subscribe.c):
0x8087 with one of the twelve PIDs → iwmbtfw; VID 0x0bda interface 0 class e0/01/01, or one of the rtlbtfw.conf VID/PIDs → rtlbtfw. Keep the id tables in one source file with a comment pointing at the two .conf files so they can be re-diffed on each FreeBSD import./usr/sbin/{iwmbtfw,rtlbtfw} -d ugenX.Y -f /usr/share/firmware/bluetooth/{intel,rtl_bt} (K3 paths) with a timeout; log stdout/stderr through ASL.ubtN arrival then goes through the normal graph-assembly path (bluetooth_setup_stack() equivalent). Do not attempt mkpeer on an Intel part before firmware is up — that is the hard-lock ng_ubt_intel.c:33-37 warns about; the kernel guards it, the daemon must not route around it.firmware-failed in the daemon's state (SCDynamicStore key per area 2) so the CLI can say why there is no adapter.Also handle the detach: on ubtN departure, ngctl shutdown ubtNhci: / ubtNl2cap: (what service bluetooth quietstop did).
bluetoothd with a re-diff comment; unit test that every PID from the two upstream .conf files is presentubt0 attaches → adapter shows up in bluetooth status (CLI from area 3) — recorded in the hardware matrix (K6)usr.sbin/bluetooth/{iwmbtfw,rtlbtfw}/*.conf, sbin/devd/bluetooth.conf, libexec/rc/rc.d/bluetooth| Repo | nextbsd/nextbsd | Size | S | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth needs-hardware | ||||
| Depends on | D2 (nextbsd-userland#207), D3 (nextbsd-userland#208), D4 (nextbsd-userland#209), D8 (nextbsd-userland#213) | ||||
Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §3.3, §11, §12.
Everything past BT-NOADAPTER is reasoned from source, never executed — the same honest state WLAN was in at userland#46 ("Everything past that needs real hardware"). Two things are genuinely unknown until a radio is plugged in: whether Secure Simple Pairing works through ng_hci at all (no FreeBSD base tool has ever sent Write_Simple_Pairing_Mode; hccontrol has no SSP command), and how the dongles we own report Read_Local_Supported_Features. This ticket is the first run and the record of what it found; it gates calling D2–D8 done.
No code by default. A checklist executed on real hardware, results pasted into this issue, and follow-up tickets filed for every deviation:
ng_ubt claims. Record usbconfig ids and Read_Local_Supported_Features bit 51 (SSP).BT-ADAPTER-OK within 5 s of plug-in; ngctl list graph matches D2's expectation; restart-adopts; unplug/replug cycle ×5.bluetooth scan finds a discoverable phone and a keyboard with names and (if Write_Inquiry_Mode 1 accepted) RSSI.bluetooth pair numeric comparison with a phone; passkey entry with a keyboard; legacy PIN with any old device on hand. Note the controller's own confirmation timeout (spec default ~30 s, unverified) versus bluetoothd's 60 s.bluetooth list → connected) with no user action; Link_Key_Request answered from the .keys file (log shows key: exists).bluetooth unpair; device must re-pair.sdpd child visible; sdpcontrol browse from a second machine.hcsecd by hand → BT-SEC-CONFLICT; unplug mid-pairing → clean failed, adapter usable after replug.userland#46 "Testing" section, e16/3-bluetoothd-cli.md §3.3 table, usr.sbin/bluetooth/hccontrol/*.c (absence of SSP commands), FreeBSD Handbook Bluetooth chapter (legacy PIN pairing walkthrough)
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231), D4 (nextbsd-userland#209), D5 (nextbsd-userland#210), D7 (nextbsd-userland#212) | ||||
FreeBSD's bthidd(8) is the Classic-HID engine we supervise (agreed shape: bluetoothd supervises stock daemons). It has no control socket and a static config: devices not listed in /etc/bluetooth/bthidd.conf are rejected when they connect (server.c:244-250), the file is read once, and SIGHUP is a shutdown, not a reload (bthidd.c:138-140,249-253; main loop bthidd.c:167). So bluetoothd must own the config file's contents and bthidd's lifecycle, and must have both bthidd and hcsecd listening before a user touches a paired keyboard at the login window.
Reconnect model to encode (client.c:63-118): after pairing the host initiates the first connection (device flagged new_device in /var/db/bthidd.hids); once a session has been established the flag is cleared (client.c:179, server.c:255-256) and, for devices advertising HIDReconnectInitiate, bthidd only listens on PSM 0x11/0x13 (server.c:96-138) for device-initiated reconnects. The device's reconnect triggers an HCI Link_Key_Request that hcsecd answers (hcsecd.8).
Add an HID driver to bluetoothd (src/Bluetooth/, beside the wland-shaped core):
0x1124), fetch the HID attributes the way bthidcontrol Query does (bthidcontrol/sdp.c:58-80: ProtocolDescriptorList, AdditionalProtocolDescriptorLists, 0x0205 ReconnectInitiate, 0x0206 HIDDescriptorList, 0x0209 BatteryPower, 0x020d NormallyConnectable, DeviceID vendor/product/version). Either exec /usr/sbin/bthidcontrol -a <bdaddr> Query and capture the stanza, or link the same logic. Store the result in the paired-device record (SCPreferences, area D) so bthidd.conf is derived state.bthidd.conf (pass with -c) from the paired-device records at start and on every change; keep /var/db/bthidd.hids (-H) as bthidd's own; on pair make sure the device is absent from the hids file so the first connect is host-initiated.bthidd -d -c <conf> -H <hids> -p <pid> -u -a <adapter bdaddr> as a child once the adapter's HCI node is up and hcsecd is running; restart it on config change (log that this drops other HID sessions until H3 lands); -t may be lowered from 10 s for snappier first connects.bthidcontrol Forget (or drop the hids line), restart bthidd; area D drops the link key.State:/Bluetooth/Device/<addr> Profiles=[HID] and Connected — bthidd exposes no status; watch its syslog ("Opening outbound session", "Rejecting", "Could not open /dev/vkbdctl") and the hids-file mtime as the v0 signal, and surface Could not open /dev/vkbdctl / /dev/uinput / /dev/consolectl as a bluetooth status error (they are otherwise silent failures).-u. Both sinks are always fed by bthidd (vkbd/consolectl unconditionally; uinput gated by kern.evdev.rcpt_mask, btuinput.c:474-484,511-519), so -u is safe under every mask value and is required for per-device evdev nodes (H4/H5).bluetooth status shows HID devices with keyboard/mouse flags; bluetooth pair prints a clear "LE-only HID (HOGP) is not supported yet" when the inquiry result carries no BR/EDR HID service (see H7).Not in scope: the X-side visibility of uinput nodes (H4/H5); trackpads (H6); LE (H7).
bluetooth pair <kbd> on real hardware produces a stanza equivalent to bthidcontrol -a <addr> Query, restarts bthidd, and the keyboard types in the text console within -t seconds (host-initiated first connect).rcpt_mask).bluetooth unpair <kbd> removes the stanza and hids entry; the device is rejected on its next connect attempt (bthidd log line) and bluetooth status no longer lists it.launchctl list shows org.nextbsd.bluetoothd running before the loginwindow job starts X; bthidd and hcsecd are children and are restarted by bluetoothd if they die.bthidd.conf is regenerated from SCPreferences at boot (delete it, reboot, it is back)./dev/vkbdctl (e.g. kernel without H1) is reported by bluetooth status, not just syslog.e16/4-hid.md §4-5usr.sbin/bluetooth/bthidd/{bthidd.c,client.c,server.c,session.c,btuinput.c}, bthidcontrol/sdp.c, libexec/rc/rc.d/bthiddorg.nextbsd.wland supervising wpa_supplicantNote (coordination): D4 replaces hcsecd; wherever this ticket says hcsecd answers Link_Key_Request, bluetoothd's security manager (D4) does. bthidd is supervised through D7's child-supervision hook.
| Repo | nextbsd/nextbsd-userland | Size | S | Track | Harden |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | H2 (nextbsd-userland#214) | ||||
With H2, every pair/unpair restarts bthidd, and a restart disconnects every connected Bluetooth keyboard and mouse for the restart plus reconnect window (users lose their keyboard while pairing a mouse). Root causes in releng/15.1:
bthidd.c:138-140 installs the same handler for SIGTERM/SIGHUP/SIGINT; the handler only increments done (bthidd.c:249-253) and the main loop for (done = 0; !done; ) (bthidd.c:167) exits. There is no reload path; read_config_file()/clean_config() exist (bthid_config.h:59-60) but are only called at start.client_rescan() inspects one configured device per -t interval (client.c:73, get_next_hid_device(d) advances a static cursor), so with N devices the first outbound connect for the last one can take N x 10 s./var/db/bthidd.hids writes.Carry a patch to usr.sbin/bluetooth/bthidd in the NextBSD userland build (and send it upstream):
reload flag; in the main loop, re-read the config into a new list, diff by bdaddr: keep sessions for devices still present (update descriptor/flags in place), close sessions for removed devices, mark added devices new_device so the host initiates. Keep SIGTERM/SIGINT as shutdown.client_rescan(): iterate the whole list per tick (respecting connect_in_progress), or at least start the cursor at the first device without a session.Connected without parsing syslog.bthidd -h mentions reload, or a version string).xinput/console keeps working); B connects within one rescan tick.usr.sbin/bluetooth/bthidd and is filed as a FreeBSD review/PR (link in this issue).e16/4-hid.md §4usr.sbin/bluetooth/bthidd/{bthidd.c,client.c,bthid_config.h,parser.y}| Repo | nextbsd/nextbsd | Size | M | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth area:desktop | ||||
| Depends on | H2 (nextbsd-userland#214), H4 (nextbsd#482) | ||||
Keyboards have a hotplug-free path into X (vkbd → kbdmux aggregate evdev, H4). Mice mostly do not: the aggregate "System mouse" route duplicates psm(4) events on laptops and can never carry multitouch, so the real path is bthidd -u's per-device uinput node ("Bluetooth Mouse", btuinput.c:339-370, UNIQ=bdaddr) — which appears at connect time and is invisible to a running XLibre until the input epic delivers a hotplug interface (H4 Q2).
xinput list shows "Bluetooth Mouse" with the bdaddr as Device Node/uniq), that removal on disconnect (session_close, session.c:223-230) removes it, and that reconnects do not accumulate stale devices.psm(4) laptop with the chosen mask, and record that BT trackpad multitouch is therefore out of reach (H6 parked harder).InputClass snippet shipped with the desktop overlay if libinput needs hints for bthidd's devices (e.g. MatchProduct "Bluetooth Mouse" for scroll method / natural scrolling defaults), and check that INPUT_PROP_POINTER (set at btuinput.c:357) classifies them as pointers, not touchscreens.xinput list afterwards (no stale entries).has_wheel/has_hwheel, btuinput.c:355-356).psm(4) laptop with the H4 mask: no duplicate pointer motion from the internal device.e16/4-hid.md §3, §5| Repo | nextbsd/nextbsd-userland | Size | L | Track | Ship |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231), A2 (nextbsd#485), A3 (nextbsd-kernel#236), D1 (nextbsd-userland#206), D8 (nextbsd-userland#213) | ||||
This is the profile users notice: music/video out of a Bluetooth headset or speaker. With A1-A3 the whole protocol path exists in base (virtual_oss + voss_bt.so: SDP lookup of the AVDTP PSM, two L2CAP SOCK_SEQPACKET sockets, AVDTP signalling, SBC encode — lib/virtual_oss/bt/bt.c, avdtp.c). What is missing is the orchestrator: something that knows a paired device just connected, knows from SDP that it is an AUDIO_SINK (0x110B), starts or retargets the mixer, tells the system where the new output is, and undoes all of it when the link drops. That is bluetoothd's job, exactly as wland owns wpa_supplicant (src/WLAN/wland.c:262-275 fork/exec, :356/:586 reaping).
General audio-device management (which output is default, volume, a CoreAudio-shaped layer) is out of scope — it is the future audio epic. This ticket defines the seam and ships an interim that works without it.
AUDIO_SINK / AUDIO_SOURCE records and store Profiles on the device object./dev/dsp.ctl existing and belonging to the system virtual_oss): issue the switch through the audio epic's API; interim shim = exec virtual_oss_cmd /dev/dsp.ctl -P /dev/bluetooth/<addr> (and -R if the device is also an AUDIO_SOURCE), remembering the previous -P target from VIRTUAL_OSS_GET_SYSTEM_INFO (ctl.c:586-592) for the fallback. main.c:2205-2216 re-parses these options live and calls voss_tx_backend_refresh(), so clients holding /dev/dsp keep playing.
- Standalone (no system mixer): fork/exec /usr/sbin/virtual_oss -S -C 2 -c 2 -r 48000 -b 16 -s 8ms -R /dev/null -P /dev/bluetooth/<addr> -d dsp.bt<N> -t dsp.bt<N>.ctl as a supervised child (SIGCHLD/waitpid like wland; restart with backoff while the ACL is up; kill on disconnect, unpair, or adapter loss). Not a per-device launchd job: the child's lifetime is the link's and launchd has no ordering to express that.State:/Bluetooth/Device/<addr>/Audio = { Node: "/dev/dsp.bt0" | "/dev/dsp", Role: source|sink, Codec: SBC, Active: bool } in SCDynamicStore; clear on disconnect. bluetooth status shows it; bluetooth audio <addr> [on|off] toggles it.AutoSwitchAudio, default true) so a user can keep BT audio manual.bluetoothd.8 describing the two modes and the SC keys — this is the contract the audio epic implements against./dev/dsp.bt0 appears within 3 s, cat music.wav > /dev/dsp.bt0 plays on the headset; walk out of range -> child reaped, SC key cleared, no zombie virtual_oss; walk back -> re-established without user actionvirtual_oss ... -d dsp -t dsp.ctl: connect switches playback to the headset without interrupting an aplay/mpv already holding /dev/dsp; disconnect restores the previous device/dev/bluetooth/<addr> fail at SDP; asserts the child is spawned, exits non-zero, is retried with backoff, and is torn down on disconnect with the SC key never left setbluetoothd.8 documents State:/Bluetooth/Device/<addr>/Audio and the AutoSwitchAudio preferenceusr.sbin/virtual_oss/virtual_oss/{main.c,ctl.c}, virtual_oss_cmd.8:67-97, lib/virtual_oss/bt/bt.c (releng/15.1)e16/5-audio.md sections 1.3, 4, 5, 6| Repo | nextbsd/nextbsd-userland | Size | M | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231), A3 (nextbsd-kernel#236), D1 (nextbsd-userland#206) | ||||
The reverse direction already exists in base: virtual_bt_speaker(8) (usr.sbin/virtual_oss/virtual_bt_speaker, built under MK_BLUETOOTH) registers an AUDIO_SINK SDP record through sdpd (bt_speaker.c:80-91, sdp_open_local + sdp_register_service), listens on the AVDTP PSM (:113-127), answers AVDTP as the acceptor (avdtp.c:191, 589), decodes SBC (sbc_encode.c:417, 629) and writes to an OSS device (-d /dev/dspX). It is a one-process profile; it only needs someone to start it after sdpd is up and to stop it when the adapter goes away. Lower priority than A4 (phones already have speakers) but nearly free.
Note: it does not need voss_bt.so (it links libbluetooth/libsdp directly, virtual_bt_speaker/Makefile), but it does need a running local SDP server — whichever of sdpd / bluetoothd-owned SDP the D track lands.
/usr/sbin/virtual_bt_speaker -d /dev/dsp (foreground, no -B) as a supervised child when an adapter is up and the A2DPSink preference is enabled (SCPreferences, default off — it advertises the machine as a speaker to everything nearby); passes -p <sdpd socket> if bluetoothd relocates the SDP control socket.State:/Bluetooth/Adapter/<name>/A2DPSink = { Running: bool, Peer: <addr>|absent }; bluetooth status shows it./dev/dsp today; when the audio epic lands this becomes its default-output value (interface: bluetoothd reads it, never picks).A2DPSink, pair a phone, play music on the phone -> audio out of the HDA/USB device; disconnect -> child stays listening; disable preference -> child gone and SDP record unregisteredRunning: falsebluetoothd.8 and bluetooth.8usr.sbin/virtual_oss/virtual_bt_speaker/Makefile, lib/virtual_oss/bt/bt_speaker.c (upstream backend_bt/bt_speaker.c), virtual_bt_speaker.8e16/5-audio.md section 1.3| Repo | nextbsd/nextbsd-userland | Size | L | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | A4 (nextbsd-userland#218), D1 (nextbsd-userland#206) | ||||
No AVRCP exists anywhere in FreeBSD (virtual_oss has none — grep -ri avrcp over freebsd/virtual_oss and lib/virtual_oss/bt is empty; ports: only wireshark's dissector list). So with A4 a headset's buttons do nothing and its volume rocker cannot reach the mixer. Every consumer headset expects the PC to be an AVRCP target (TG) with at least PASSTHROUGH and, on 1.4+, absolute volume. This is userland-only: AVCTP over L2CAP PSM 0x0017, no kernel change.
SOCK_SEQPACKET, like bt_speaker.c:113-127); on a connection from a device with an active A2DP link, run a minimal AVRCP TG: answer UNIT INFO/SUBUNIT INFO, decode PASSTHROUGH (PLAY, PAUSE, STOP, FORWARD, BACKWARD, VOLUME_UP, VOLUME_DOWN), and SetAbsoluteVolume (AVRCP 1.4, vendor-dependent PDU 0x50). Reject everything else politely (NOT IMPLEMENTED) so metadata-hungry headsets don't drop the link.AV_REMOTE_CONTROL_TARGET (0x110C): sdpd cannot register it (fixed profile list, usr.sbin/bluetooth/sdpd/profile.c:51-76) — add a profile to sdpd (small, copy audio_sink.c) or have bluetoothd's SDP server (D track) carry it. Pick whichever the D track chose.notify(3) names org.nextbsd.bluetooth.avrcp.<key> and publish State:/Bluetooth/Device/<addr>/Audio/Volume (0-127). Gershwin/X consumer (media-key injection) is a sibling ticket in the Gershwin repos, out of scope here. Absolute volume -> the audio epic's SetVolume on the device node (interim: mixer -f /dev/dsp.bt0.ctl-shaped no-op with a log line).notifypoke-style test tool in tests/); volume rocker updates the SC keytests/); malformed frames are rejected without crashing bluetoothdGetCapabilities, RegisterNotification) keep their A2DP stream up for 10 minsdpcontrol -a <us> browse lists AV Remote Control Target)lib/virtual_oss/bt/bt_speaker.c for the L2CAP listener shapee16/5-audio.md section 3| Repo | nextbsd/nextbsd-kernel | Size | M | Track | Harden |
|---|---|---|---|---|---|
| Labels | bug area:bluetooth | ||||
| Depends on | B4 (nextbsd#478), K1 (nextbsd-kernel#231) | ||||
NextBSD compiles the Bluetooth nodes into the kernel, so any LE-path defect is a boot-visible failure rather than a module you can unload. Reading releng/15.1, the LE code has four known soft spots, none of which block a HOGP mouse but all of which bite under load or with picky peripherals:
LE_Read_Buffer_Size is in the "no post processing" list (sys/netgraph/bluetooth/hci/ng_hci_cmds.c:824); the unit's ACL packet budget comes only from the BR/EDR Read_Buffer_Size reply (ng_hci_main.c NG_HCI_BUFF_ACL_TOTAL). Controllers with a separate, smaller LE pool (B4 measures this) can be over-driven, and Number_Of_Completed_Packets accounting then drifts.ng_l2cap_process_cmd_urq always replies ACCEPT (ng_l2cap_evnt.c:363) but the request is never turned into LE_Connection_Update (ng_l2cap_cmds.c:224-226, /*TBD.*/). Mice asking for a 7.5 ms interval stay at the connect-time interval; some peripherals disconnect when their accepted parameters never take effect.LE_Connection_Update_Complete body is /*TBD*/ (ng_hci_evnt.c:571-578); LE_Read_Remote_Features_Complete and LE_Long_Term_Key_Request fall through to free (:602-606). The LTK request is still mirrored to the raw hook so userland can answer, but the kernel connection descriptor never learns the encryption outcome on the peripheral-role path except via the generic Encryption_Change event - verify that path in B4.LE_Enhanced_Connection_Complete (0x0a) is defined but unhandled: if userland enables it in LE_Set_Event_Mask, LE connections never register in the kernel. Either handle it (map to the same code as 0x01, keeping local/peer RPAs) or document that bit as forbidden.Also worth a look while in there: ng_l2cap_con_wakeup panic()s on an unknown pending command code (ng_l2cap_cmds.c:228-232) - should be an error, not a panic, once LE commands are added.
ng_hci_cmds.c: post-process LE_Read_Buffer_Size (v1 and v2): if hc_total_num_le_data_packets != 0, keep a separate le_pkts/le_size budget in ng_hci_unit_buff_t (ng_hci_var.h:62-75) and have ng_hci_send_data / Number_Of_Completed_Packets charge LE connection handles against it; expose via NGM_HCI_NODE_GET_BUFFER.ng_l2cap: on Connection_Parameter_Update_Request, after ACCEPT, issue LP_*-style request to ng_hci that sends LE_Connection_Update (OCF 0x13, ng_hci.h:1719-1728) with the peer's parameters; handle LE_Connection_Update_Complete to update the connection descriptor.ng_hci_evnt.c: implement LE_Read_Remote_Features_Complete (store in con), LE_Enhanced_Connection_Complete (as 0x01), and leave LE_Long_Term_Key_Request to userland but comment it as such.panic() default in ng_l2cap_con_wakeup with NG_L2CAP_ERR + free.takawata@, hselasky@ as reviewers); carry as a NextBSD patch until merged.ng_hci "could not send" / stall and NGM_HCI_NODE_GET_BUFFER counters return to baseline when idleLE_Connection_Update_Complete with the requested interval (btsnoop attached)panic() reachable from ng_l2cap_con_wakeup with an unknown command codefbc48c2bfb, 3a601a2381, b32073c458| Repo | nextbsd/nextbsd-userland | Size | XL | Track | Sequence |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | B2 (nextbsd-userland#204), D4 (nextbsd-userland#209), B4 (nextbsd#478), B5 (nextbsd-kernel#234) | ||||
Every third-party keyboard and mouse a user is likely to bring (Logitech MX, Microsoft Surface, Keychron, most gaming mice) is LE-only HOGP; Classic (B3) covers headsets and Apple's Magic peripherals but none of those. The kernel already provides LE connections and the ATT/SMP fixed channels as sockets; the entire gap is this userland host. This ticket is the second engine behind the B2 interface - the policy layer, CLI, MIG surface and bond store do not change.
Deliberately not a port of NimBLE/Zephyr/BTstack: those need the raw ACL stream, which on netgraph means taking ng_hci's single acl hook away from ng_l2cap and losing Classic (see B7 for the priced alternative). This engine is the takawata/FreeBSD-BLE shape - clean-room, or derived from it if the licence request from B4 succeeds.
src/Bluetooth/le_engine.c (+ att.c, gatt.c, smp.c, le_gap.c) implementing bt_engine_ops and bt_le_ops:
Write_LE_Host_Supported(1,0), LE_Set_Event_Mask (all Core-4.x subevents except 0x0a until B5 lands), Set_Event_Mask | LE_META; discover() = LE_Set_Scan_Parameters (active, 60/30 ms) + LE_Set_Scan_Enable(1, dup-filter); parse LE_Advertising_Report from the raw socket (or the kernel neighbor cache) into bt_found with AD parsing for Flags, Complete Local Name, Appearance, 16-bit service UUIDs (0x1812 HID, 0x180F Battery). connect() = L2CAP socket connect() with l2cap_bdaddr_type = LE_PUBLIC|LE_RANDOM, l2cap_cid = ATT_CID (kernel issues LE_Create_Connection, ng_hci_ulpi.c:486-623).LE_Start_Encryption (OCF 0x19) over raw HCI with the stored LTK. Peripheral-initiated reconnect: answer LE_Long_Term_Key_Request (mirrored to raw, ng_hci_misc.c:63-78) with LE_Long_Term_Key_Request_Reply (OCF 0x1a). Surface prompts through BT_EV_PAIR_PROMPT exactly like Classic.bt_bond{kind=LE} with LTK/EDIV/Rand/IRK/CSRK/key-size/authenticated/sc; bond_import at boot so a paired LE keyboard works at the login window; IRK-based resolution of resolvable private addresses when present.SO_L2CAP_ENCRYPTED for bonded devices so HID reads never race the encryption change.uhid/evdev bridge) and consumes bt_le_ops; keep it out of this ticket.bluetooth scan shows LE and Classic devices in one list, LE rows with appearance/name from ADbluetooth pair <addr>/le completes Just-Works with a mouse and Passkey with a keyboard; LTK stored in SCPreferencesbluetooth gatt <addr> (debug subcommand)ng_btsocket.h:225-247, ng_btsocket_l2cap.c:2183-2192, :2366-2372 (SO_L2CAP_ENCRYPTED)lehid/att.c, lepair/lepair.c, lesecd/)| Repo | nextbsd/nextbsd-userland | Size | XL | Track | Parked |
|---|---|---|---|---|---|
| Labels | question area:bluetooth status:parked | ||||
| Depends on | B6 (nextbsd-userland#205) | ||||
Most current wireless peripherals (Logitech MX Master 3/3S, MX Keys, Pebble; many Microsoft, Keychron, etc. in Bluetooth mode) are BLE-only and use HID over GATT (HOGP). FreeBSD's netgraph stack is Classic-only: no SMP, no GATT (site/freebsd-bluetooth-management.html, line 79), so bthidd cannot talk to them at all. Whether NextBSD gets LE via a userland engine on raw HCI/L2CAP, a ported stack, or not at all is area B's decision; this ticket records what HID needs from it so the decision is made with HID in view.
hccontrol already exposes le_scan, le_connect, white-list and advertising commands (usr.sbin/bluetooth/hccontrol/le.c:76-1136), and ng_l2cap.h:78-80,359-361 defines ATT_CID 0x0004, SMP_CID 0x0006, L2CA_IDTYPE_ATT/SMP, referenced from ng_btsocket_l2cap.c — so raw ATT/SMP over L2CAP fixed channels is addressable from userland in 15.1 (depth unverified; area B to confirm).LE_Start_Encryption on reconnect; LTK persistence (same secret-store problem as link keys, area D).0x1812 (Report Map 0x2A4B, Report 0x2A4D + Report Reference descriptors, HID Information 0x2A4A, Protocol Mode 0x2A4E, Control Point), Battery Service 0x180F, CCCD writes to enable notifications.hid.c, kbd.c, btuinput.c are reusable almost verbatim) → vkbd/uinput. Output reports (LEDs) via write-without-response.e16/4-hid.md §6usr.sbin/bluetooth/hccontrol/le.c; sys/netgraph/bluetooth/include/ng_l2cap.h| Repo | nextbsd/nextbsd | Size | S | Track | Needs answer |
|---|---|---|---|---|---|
| Labels | question area:bluetooth area:desktop | ||||
| Depends on | — | ||||
XLibre on the Gershwin image gets input through libudev-devd (x11-servers/xlibre-server/Makefile:16-21, UDEV on by default; x11/xlibre-minimal pulls xlibre-xf86-input-libinput). libudev-devd enumerates /dev/input/event*, /dev/kbdmux*, /dev/sysmouse, ... once at X start (udev-utils.c:100-137) and gets hotplug only from devd's /var/run/devd.pipe (udev-monitor.c:50-51,188-213, retrying every second forever). NextBSD has no devd (README.md:31, PORTING.md:487-489) and nothing else speaks that socket, so X sees exactly the input nodes that existed when it started. Gershwin already works around this for USB (gershwin-system/Library/Scripts/LoginWindow.sh:91-93: "X enumerates input exactly once at startup and XLibre has no hotplug backend on FreeBSD"), and nextbsd-kernel#175 lists it as a candidate cause.
A Bluetooth keyboard or mouse connects when the user touches it, i.e. after X is up. bthidd -u creates its per-device /dev/input/eventN at that moment (btuinput.c:339-413), and X will never see it. This is the single biggest risk in E16 area 4 and it is not a Bluetooth problem — it is the input-management epic's interface — but E16 needs the answer to schedule H5.
e16/4-hid.md §3)kern.evdev.rcpt_mask (sys/dev/evdev/evdev.h:48-56, default 0xC = HW_MOUSE|HW_KBD, evdev.c:73-82, tunable) selects exactly one evdev route per class:
| per-device nodes (bits 2/3, default) | aggregate nodes (bits 0/1) | |
|---|---|---|
| keyboard | bthidd uinput "Bluetooth Keyboard" — needs hotplug | vkbd → kbdmux → "System keyboard multiplexer" evdev (kbdmux.c:455-473,707) — exists from boot, no hotplug needed, but all keyboards become one libinput device |
| mouse | bthidd uinput "Bluetooth Mouse" — needs hotplug; only route that can carry trackpad MT | consolectl → sysmouse → "System mouse" evdev (vt_consolectl.c:55-59, vt_sysmouse.c:97-125) — exists from boot, but psm(4) also feeds sysmouse (duplicates unless bit 2 is cleared, which may silence hms — unverified) |
XLibre's own pkg-message.in recommends kern.evdev.rcpt_mask=6 (KBDMUX|HW_MOUSE) on FreeBSD, which gives hotplug-free keyboards and per-device mice. Nothing in nextbsd, nextbsd-overlays, ul/overlay or gershwin-on-nextbsd sets the mask today, so the image runs 0xC.
hms/hmt/hpen, baked at config/NEXTBSD:165-174) honour EVDEV_RCPT_HW_MOUSE? (sys/dev/hid/hidmap.c, hms.c.) If they ignore it, 3 is a duplicate-free "everything aggregate" option; if they honour it, clearing bit 2 kills USB mice./var/run/devd.pipe fed from /dev/devctl (the kernel already emits DEVFS/CDEV/CREATE cdev=input/eventN for every node, sys/kern/kern_conf.c:530-554,832,993, evdev/cdev.c:781; /dev/devctl has no reader today, kextd.c:23, #225), (b) a NextBSD libudev-devd whose monitor subscribes to IOKit/hwregd notifications, or (c) none, with the aggregate policy as the design. E16 only needs the decision and the delivery ticket id.nextbsd-overlays/rootfs/boot/loader.conf.d/, or a launchd-run sysctl at boot)? E16 will not set it from bluetoothd.LoginWindow.sh's keyboard wait (devinfo | grep hkbd|ukbd|atkbd) count vkbd/kbdmux, so a BT-only-keyboard machine is not penalised? (Only matters on the virtio branch.)e16/4-hid.md §3FreeBSDDesktop/libudev-devd udev-monitor.c, udev-utils.c; ports x11-servers/xlibre-server/files/{patch-config_udev.c,pkg-message.in}| Repo | nextbsd/nextbsd-kernel | Size | XL | Track | Needs answer |
|---|---|---|---|---|---|
| Labels | question area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231), A4 (nextbsd-userland#218) | ||||
A2DP (A4) gives playback only. Using a headset's microphone needs HFP or HSP: an RFCOMM AT-command channel plus a SCO/eSCO voice link. Nothing in FreeBSD base or ports implements either profile (0 code-search hits for handsfree/HFP; sdpd has no Headset/Handsfree records — usr.sbin/bluetooth/sdpd/profile.c:51-76), and the kernel half is incomplete. Quantified against releng/15.1 sys/netgraph/bluetooth:
| piece | state | where |
|---|---|---|
| SCO over USB isochronous | works (4 isoc xfers, reassembly) | drivers/ubt/ng_ubt.c:281-282, 336-378, 1290-1366; sysctl net.bluetooth.usb_isoc_enable |
| isoc alt-setting selection | picks the largest wMaxPacketSize alt (:738-780) instead of the alt that matches SCO bandwidth (BT-USB spec: alt 1 = one CVSD link ...) — frame-size mismatch on many dongles |
ng_ubt.c |
| HCI SCO setup | legacy Add_SCO_Connection only, HV1/HV2/HV3 CVSD (ng_hci_ulpi.c:322-450); no Setup_Synchronous_Connection -> no eSCO, no mSBC wideband, no transparent air mode |
hci/ |
| HCI sync events | Synchronous_Connection_Complete (0x2C) / _Changed (0x2D) absent from the event switch (ng_hci_evnt.c:107-200); headset-initiated eSCO is an unhandled event |
hci/ |
| SCO socket | exists: PF_BLUETOOTH/SOCK_SEQPACKET/BLUETOOTH_PROTO_SCO, SO_SCO_MTU, listen/accept/connect |
socket/ng_btsocket_sco.c |
| stack wiring | stock rc.d/bluetooth:119-127 never connects hci: sco to btsock_sco:; routes only appear on NGM_HCI_NODE_UP over a connected hook (ng_btsocket_sco.c:821-856) |
bring-up script / bluetoothd |
| RFCOMM | exists | socket/ng_btsocket_rfcomm.c |
| HFP AG state machine, SCO<->OSS bridge, SDP records | do not exist anywhere | — |
Prior art: NetBSD sys/dev/bluetooth/btsco.c (Itronix, BSD-2-Clause) — a kernel audio device on a SCO PCB; design reference only, different stack API.
patches/): Setup_Synchronous_Connection + the two sync events in ng_hci; alt-setting selection by requested SCO packet size in ng_ubt; expose eSCO parameters through SO_SCO_* sockopts. Hardware-verified only — the netgraph BT stack has no tests.ngctl connect ${dev}hci: btsock_sco: sco ${dev}sco at stack bring-up so SCO sockets are routable at all (cheap; do it regardless, it unblocks experiments).AT+BRSF, +CIND, +CMER, AT+VGS/VGM, AT+BCC/+BCS codec select, ring/answer/hangup) over RFCOMM, SDP HANDSFREE_AUDIO_GATEWAY (0x111F) record (extend sdpd or bluetoothd-owned SDP), and an SCO<->cuse bridge (8 kHz mono CVSD, 48-byte frames / 3.75 ms) surfacing as /dev/dsp.hfp for virtual_oss.Result even then: telephone-grade narrowband until eSCO/mSBC lands. Estimate XL.
Is narrowband headset mic in scope for E16 at all, or does E16 ship A2DP-only and file HFP as its own epic (or park until LE Audio forces a BLE-side stack anyway)? Options:
hccontrol+SCO socket can carry CVSD both ways with one dongle before committing to the daemon.sco hook (ngctl list shows ubt0sco), and bluetooth status reports whether the adapter's SCO path is wiredsys/netgraph/bluetooth/{drivers/ubt/ng_ubt.c,hci/ng_hci_ulpi.c,hci/ng_hci_evnt.c,socket/ng_btsocket_sco.c}, libexec/rc/rc.d/bluetooth, usr.sbin/bluetooth/sdpd/profile.c (releng/15.1); NetBSD sys/dev/bluetooth/btsco.ce16/5-audio.md section 2| Repo | nextbsd/nextbsd-kernel | Size | S | Track | Needs answer |
|---|---|---|---|---|---|
| Labels | question area:bluetooth area:rpi5 needs-hardware | ||||
| Depends on | — | ||||
The Pi 5 / Pi 500 Bluetooth radio is UART-attached, and every layer between it and ng_hci is missing in FreeBSD 15.1 (K8 has the list). Before sizing that work, three facts have to be read off the board — they decide whether K8 is "a UART compat line and an H4 node" or "three new SoC drivers first":
shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH> (bcm2712-rpi-5-b.dts:377), on the SoC's brcm,brcmstb-gpio block at 0x107d508500 (bcm2712.dtsi:497-508 in rpi-6.6.y), for which FreeBSD has no driver. Linux hci_bcm toggles it itself. If the Pi firmware/EEPROM leaves it high, a GPIO driver can wait; if it leaves the chip in reset, K8 needs brcmstb-gpio first.uart0 (uarta)? pinctrl-0 = <&uarta_24_pins &bt_shutdown_pins> on brcm,bcm2712-pinctrl at 0x107d504100 (bcm2712.dtsi:402-…), also driverless. Linux applies it at probe.uarta (brcm,bcm7271-uart, 0x107d50c000, reg-shift=2, reg-io-width=4, clock-frequency=96000000) actually run at, and does skip-init mean the firmware already programmed it? Same class of question as the UART10 clock hunt in config/rpi5.env (44.2368 MHz measured vs 9.216 MHz claimed) — the number has to come from the hardware, not the DT.local-bd-address is zeros in the source dts; the Pi firmware patches it at boot (bdaddr override, bcm2712-rpi.dtsi:177). Dump /proc/device-tree/…/bluetooth/local-bd-address from Linux and the FreeBSD ofw view.On a Pi 500+ under Raspberry Pi OS: raspi-gpio get 24-29 (or pinctrl get 24-29), gpioinfo for gio line 29 state, cat /sys/kernel/debug/clk/clk_summary | grep -i uart, hcitool dev / btmgmt info for the working baud/bdaddr, and dmesg | grep -i 'hci_uart\|bcm' for the firmware file the kernel loaded (expect brcm/BCM4345C0.hcd or the raspberrypi,5-model-b variant). Then boot NEXTBSD-RPI5 and read the same registers/DT nodes from FreeBSD (ofwdump -a, devinfo -v, a kldstat-free peek via ddb or a tiny /dev/mem read if needed) to see what our boot path leaves.
| Repo | nextbsd/nextbsd-userland | Size | M | Track | Harden |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth area:ci | ||||
| Depends on | K1 (nextbsd-kernel#231), K2 (nextbsd#475) | ||||
QEMU has had no Bluetooth since 2019 (commits 1d4ffe8dc7, 43d68d0a94), GitHub runners have no dongle, and macOS/HVF USB passthrough is unreliable. So after K2 CI proves only that the nodes exist. Everything that matters — ng_hci command/event flow, ng_l2cap, the btsock_* sockets, and later bluetoothd's scan/pair/connect state machine — would be exercised for the first time on a developer's real dongle. The WLAN plan lived with that ("there is no 802.11 in qemu"); Bluetooth does not have to, because ng_hci does not care what sits on its transport hook.
A small C tool over libnetgraph (src/Bluetooth/tests/btemu.c or wherever area 2 puts the daemon's tests):
NgMkSockNode(); mkpeer . hci emu drv (ng_hci.h:62: "drv" is the driver-facing hook); name .:emu emu0hci; then exactly rc.d/bluetooth's bluetooth_setup_stack(): mkpeer emu0hci: l2cap acl hci, name … emu0l2cap, connect emu0hci: btsock_hci_raw: raw emu0raw, connect emu0l2cap: btsock_l2c_raw: ctl emu0ctl, connect emu0l2cap: btsock_l2c: l2c emu0l2c.0x01 command / 0x02 ACL as ng_ubt would deliver them), and answer with Command Complete / Command Status events: Reset, Read_BD_ADDR (a fixed 00:11:22:33:44:55), Read_Local_Version_Information, Read_Local_Supported_Features, Read_Buffer_Size, Write_Scan_Enable, Write_Class_of_Device, Change_Local_Name, Read_Local_Name, Host_Buffer_Size, Set_Event_Mask. Unknown opcodes → Command Complete with status Unknown HCI Command.l2ping -a <fake> gets its echo response back. That is enough for sdpcontrol browse against a fake SDP responder later.--script or env knob to inject failures (timeouts, status errors) so the daemon's error paths get covered.Then K2's boot-test block grows: start btemu, hccontrol -n emu0hci read_bd_addr prints the fixed address, hccontrol -n emu0hci initialize succeeds, read_node_state says up, and (step 3) l2ping -a <fake> reports result=0.
Linux has vhci/btvirt for this; FreeBSD never grew one. ~400 LOC.
btemu builds in nextbsd-userland (C, libnetgraph, no Mach dependency) and runs on a K1 kernel in qemu on both archeshccontrol -n emu0hci reset / read_bd_addr / read_local_supported_features / read_buffer_size / initialize / read_node_state all succeed against itl2ping -a <fake addr> gets result=0; hccontrol inquiry lists the fake device-h and a short section in the Bluetooth plan page so area 2 can point bluetoothd's tests at itbluetoothd v0 ticket (area 2) should list this as its CI fixture| Repo | nextbsd/nextbsd | Size | M | Track | Harden |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231) | ||||
qemu has no Bluetooth controller and FreeBSD has no virtual HCI, so pairing/L2CAP cannot run in CI (same as WLAN). But everything below bthidd's sinks can: bthidd feeds keyboards into vkbd(4) and mice into /dev/consolectl, and with -u into /dev/uinput (session.c:106-121, hid.c:541-573, btuinput.c:339-413). A synthetic device exercising those same kernel interfaces proves H1's baked drivers, kbdmux auto-attach, the rcpt_mask routing chosen in H4, and — the part that matters most — whether XLibre sees a node created after it started. Today that last case is expected to fail; the test documents the gap and becomes the acceptance test for the input-hotplug interface.
Add tests/input-path-test.sh (nextbsd) plus a small C helper nextbsd-userland/src/Bluetooth/tests/synth-hid.c (reusing btuinput.c verbatim for the uinput half):
/dev/vkbdctl, write a make/break scancode pair for a the way bthidd/kbd.c:kbd_write does; assert (a) /dev/vkbdctl opened (H1), (b) with kern.evdev.rcpt_mask bit 1 set the key appears on kbdmux's evdev node (find it by EVIOCGNAME == "System keyboard multiplexer"), (c) with bit 1 clear it does not (no duplicates), (d) the character reaches a getty on /dev/ttyv0 (optional).uinput_open_keyboard/mouse from btuinput.c with a fake bdaddr; assert /dev/input/eventN appears with that name and EVIOCGUNIQ == the bdaddr; inject KEY_A and REL_X; read them back with a tiny evdev reader; destroy and assert the node is gone.CONS_MOUSECTL MOUSE_ACTION on /dev/consolectl; with bit 0 set assert motion on the "System mouse" evdev node; assert /dev/sysmouse MSC bytes regardless.gershwin-on-nextbsd/tests/): start XLibre (scfb or xlibre-xf86-video-dummy), then (i) create the uinput keyboard before X → xinput list shows it (expected pass), (ii) create it after X → xinput list (expected fail until the hotplug interface from H4 lands; mark xfail and flip to a hard assert then).vkbd fails step 1 with a clear message./dev/input/event*.e16/4-hid.md §7usr.sbin/bluetooth/bthidd/btuinput.c (reusable), sys/dev/vkbd/vkbd.c, sys/dev/kbdmux/kbdmux.c:455-473,705-716, sys/dev/vt/vt_consolectl.c, vt_sysmouse.cgershwin-on-nextbsd/tests/loginwindow-test.sh (existing desktop test harness)| Repo | nextbsd/nextbsd | Size | S | Track | Harden |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth | ||||
| Depends on | K1 (nextbsd-kernel#231), A2 (nextbsd#485), A3 (nextbsd-kernel#236) | ||||
qemu has no Bluetooth, so the air interface can only be tested on hardware. Everything below the radio can be gated in CI, and it is exactly the part NextBSD keeps breaking by construction (unbaked modules, deleted .ko trees, hard-coded /usr/local paths). The WLAN plan set the precedent: CI proves the daemon runs under launchd and the wired path is untouched; the radio run is separate.
Extend tests/boot-test.sh (and the arm64 lane) with a bt-audio block:
/dev/cuse exists (A1)./usr/local/lib/virtual_oss/voss_bt.so exists, ldd clean, exports voss_backend_bt_play/_rec (A2).virtual_oss -S -C 2 -c 2 -r 48000 -b 16 -s 8ms -f /dev/dsp0 -d dsp.ci -t dsp.ci.ctl against the emulated HDA (snd_hda is baked; cat /dev/sndstat shows pcm0) -> /dev/dsp.ci appears within 2 s, sndctl -f /dev/dsp.ci prints from_user=1, virtual_oss_cmd /dev/dsp.ci.ctl prints system info, and dd if=/dev/zero of=/dev/dsp.ci bs=4k count=50 completes (A3: the kldload trap is what this line catches).virtual_oss_cmd /dev/dsp.ci.ctl -P /dev/bluetooth/00:00:00:00:00:00 returns and the daemon survives (backend loads, SDP fails cleanly) — asserts the dlopen path and that a bad BT target cannot kill a running mixer.launchctl list shows org.nextbsd.bluetoothd, and bluetooth status reports "no adapter" without crashing; the audio state-machine unit test from A4 runs here.cat /dev/sndstat still lists pcm0 as default after the test (no hw.snd.default_unit drift).Record in docs/ (or the plan page) the hardware checklist CI cannot cover: pair, A2DP connect/disconnect/reconnect, out-of-range, reboot with stored link key, and (if A6 goes ahead) SCO.
tests/boot-test.sh runs the six checks on amd64 and arm64 lanes and fails the build on any of themvoss_bt.so removed) fails at step 2 with a message naming the fileconfig/NEXTBSD:208-240 (snd_hda rationale, qemu HDA)e16/5-audio.md TL;DR item 6| Repo | nextbsd/nextbsd | Size | XS | Track | Ship |
|---|---|---|---|---|---|
| Labels | documentation area:bluetooth | ||||
| Depends on | — | ||||
The epic's "read first" page states (§ TL;DR, §4, §5, §6) that FreeBSD's netgraph stack "supports Classic Bluetooth only - no BLE, no GATT, no SMP", that "the gap is in the kernel", and prices LE as "a large kernel+userland effort" (option A) or "a from-scratch BLE stack" (option C). Against releng/15.1 that is wrong on the kernel half:
sys/netgraph/bluetooth/hci/ng_hci_ulpi.c:486-623, ng_hci_evnt.c:376-620include/ng_btsocket.h:225-247, socket/ng_btsocket_l2cap.c:2183-2192, include/ng_l2cap.h:75-87,359-361l2cap/ng_l2cap_evnt.c:278-372hccontrol le_*: usr.sbin/bluetooth/hccontrol/le.c:1240-1365takawata/FreeBSD-BLEAnyone cutting tickets from the page as written will plan kernel work that already exists. The FreeBSD forum quote the page leans on ("14.1 supports only 1.0/1.1/2.0") describes the userland tooling, not the kernel.
hcsecd as "legacy PIN only, no SSP" (hcsecd.c:123-126).acl hook, raw hook command-only) that rules out full hosts, with the BTstack port as evidence.freebsd-bluetooth-management.htmle16/2-engine-ble.md §1 (full inventory)| Repo | nextbsd/nextbsd-kernel | Size | XL | Track | Parked |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth area:rpi5 needs-hardware status:parked | ||||
| Depends on | K1 (nextbsd-kernel#231), K7 (nextbsd-kernel#232) | ||||
The Pi 5 / Pi 500 have a Bluetooth 5.0 radio in the CYW43455 combo chip, attached over a SoC UART, not USB. FreeBSD 15.1 supports none of the path, so E16 on these boards means a USB dongle (K6). Filed so the scope is on record and nobody promises the onboard radio.
| Layer | 15.1 state |
|---|---|
UART uarta: serial@7d50c000 { compatible = "brcm,bcm7271-uart"; reg-shift=<2>; reg-io-width=<4>; } (rpi-6.6.y bcm2712.dtsi:510-519), 96 MHz, auto-flow-control, max-speed = 3000000 |
uart_dev_ns8250.c's FDT table is ns16550/ns16550a only (:527-528) and the node has no such fallback; no uart_dev_bcm7271.c exists. reg-shift/reg-io-width are honoured generically (uart_bus_fdt.c:87,96), so a compat entry + rclk may give basic 8250 TX/RX; the bcm7271 auto-flow and 3 Mbaud behaviour is unknown |
BT_ON shutdown-gpios = <&gio 29> on brcm,brcmstb-gpio @7d508500 |
No brcmstb GPIO driver in sys/dev/gpio/ or files.arm64; the only Broadcom GPIO drivers are 2837/2838-gated and NEXTBSD-RPI5 turns those SoC options off (config/NEXTBSD-RPI5:63-64). RP1's GPIO (patch 0025/0048) is a different block |
Pinmux uarta_24_pins (gpio24 RTS, gpio25 CTS, …) + bt_shutdown_pins on brcm,bcm2712-pinctrl @7d504100 |
No driver |
| H4 transport | ng_h4 removed 2021-09-05 (commit 79a100e28e: "disconnected 13 years ago … completely fails to compile … Bluetooth has largely moved on from UART transport"); hcseriald removed (66d6299848). The 13.5 source (sys/netgraph/bluetooth/drivers/h4/ng_h4.c, 1019 lines) is a pre-2008 linesw line discipline (:98-107); a rewrite would follow ng_tty(4)'s ttyhook model (sys/netgraph/ng_tty.c:47,116-121), which is alive in 15.1. The NETGRAPH_BLUETOOTH_H4 option token still exists (sys/conf/options:521) but gates nothing |
| Patchram | Linux btbcm downloads BCM4345C0.hcd (63,806 bytes; RPi-Distro/bluez-firmware debian/firmware/broadcom/) over HCI vendor commands (download-minidriver 0xFC2E, the .hcd records, baud update 0xFC18) before the stack starts. FreeBSD has never had a Broadcom UART patchram tool. Licence: Cypress "WIRELESS CONNECTIVITY DEVICES DRIVER END USER LICENSE AGREEMENT", Debian non-free-firmware; Raspberry Pi states it is released "on the expectation that we will release them to our users" (debian/copyright:19-131) — shippable with the notice, like LICENCE.broadcom already is (nextbsd/build.sh:480-482) |
| BD address | DT local-bd-address is zeros; the Pi firmware patches it in and Linux applies it with vendor cmd 0xFC01. Without it every board shares the ROM default |
| Ownership | hcseriald's job (open the tty, set 3 Mbaud + CRTSCTS, push the line discipline/hook, hand the node to the stack) has no owner; it would be bluetoothd's, per-board, on arm64 only |
uart_dev_ns8250.c: add {"brcm,bcm7271-uart", …} with the measured rclk (or a uart_dev_bcm7271.c if auto-flow needs its registers). Baked into NEXTBSD-RPI5; a kext cannot deliver it (no bus to match it from, and the DT child bluetooth node is never a newbus device).brcmstb-gpio and bcm2712-pinctrl drivers (gpiobus/fdt_pinctrl), following the RP1 patches' shape.ng_h4 rewritten on ttyhook (≈1k LOC; the 13.5 file is the spec for the framing/state machine).bcmpatchram in base-style C (not the GPL brcm_patchram_plus) that speaks the H4 framing over the tty before the node is attached, plus the .hcd shipped under /usr/share/firmware/bluetooth/brcm/ with the Cypress licence (K3's directory).bluetoothd learns a "uart adapter" kind: open /dev/cuau<N>, patchram, set bdaddr, attach H4, then the same bluetooth_setup_stack() as USB.hccontrol -n <node> read_bd_addr on a Pi 500+ returns the board's own address over the onboard radiol2ping and sdpcontrol browse against a phone succeed; results in the hardware matrix (K6)config/NEXTBSD-RPI5 or the patch series with the usual measured-on-the-board comments/usr/share/firmware/bluetooth/brcm/releng/13.5 sys/netgraph/bluetooth/drivers/h4/| Repo | nextbsd/nextbsd-kernel | Size | M | Track | Parked |
|---|---|---|---|---|---|
| Labels | question area:bluetooth status:parked | ||||
| Depends on | B4 (nextbsd#478) | ||||
B1 chose a thin userland ATT/GATT/SMP host over the kernel's LE sockets. The alternative - a complete, actively maintained, Apache-2.0 LE host (Apache NimBLE: Bluetooth 5.4, porting/npl/linux is a ~20 KB pthread OSAL that would compile on FreeBSD unchanged) - is blocked by one fact: NimBLE wants the raw H4 stream including ACL data, and on netgraph ng_hci has a single acl hook (sys/netgraph/bluetooth/hci/ng_hci_main.c:205-206) while the raw hook accepts commands only (ng_hci_main.c:1025-1102). BTstack's FreeBSD port takes the acl hook with an ng_socket and documents "The OS Bluetooth functionality will be interrupted" - i.e. no Classic while it runs.
If the thin host (B6) stalls on GATT breadth (EATT, LE CoC - which the kernel also lacks, ng_btsocket_l2cap.c:2188-2190 - LE Audio/ISO, privacy), the way to get a full host and keep Classic is a small kernel node that splits the ACL stream by link type. This spike prices it; it is parked unless B6 hits that wall.
Design and prototype ng_btle_tee (working name):
ubtNhci:acl and ubtNl2cap:hci; extra hook le connected to a userland ng_socket.Connection_Complete / LE_Connection_Complete / Disconnection_Complete (needs a copy of events: either a third hook from ng_hci's raw mirror or a control message from ng_hci on connection state).le hook, BR/EDR -> ng_l2cap; outbound from either side -> ng_hci; ACL flow control still lives in ng_hci (which then needs B5's LE buffer accounting).ble_hci_socket.c-style transport that reads commands/events from the raw HCI socket and ACL from the ng_socket. Determine whether NimBLE's host L2CAP would fight ng_l2cap for the LE signalling channel (both would answer CID 5) - probably requires ng_l2cap to stop handling LE entirely when the tee is present.ng_l2cap LE code that would be bypassedl2ping on BR/EDR while a userland process receives LE ACL for one connection (proof of split)porting/npl/linux) linking against the prototype transport; blecent-style discovery of one GATT serverporting/npl/linux, nimble/transport/socket/src/ble_hci_socket.c)| Repo | nextbsd/nextbsd-userland | Size | L | Track | Parked |
|---|---|---|---|---|---|
| Labels | enhancement area:bluetooth status:parked | ||||
| Depends on | H2 (nextbsd-userland#214), H5 (nextbsd#483) | ||||
FreeBSD's bthidd already special-cases the Magic Mouse 1 (hid.c:73 MAGIC_MOUSE() matches 05ac:030d; hid.c:434-512 parses the undeclared report 0x29: finger blocks, two-finger scroll → wheel, firm centre touch → middle click; battery reports 0x30/0x47). It does not match the Magic Mouse 2 (05ac:0269), which therefore degrades to a two-button mouse with no scrolling, and it has no digitizer/multitouch handling at all (parser.y:442-463, hid.c:227-430 handle only generic-desktop/keyboard/button/consumer/microsoft pages). Magic Trackpad 1 and 2 over Bluetooth send Apple vendor multitouch reports, not Windows Precision Touchpad reports, so hmt(4) cannot help, and wsp(4)/atp(4) are USB-only internal-trackpad drivers. Result today: Magic Trackpad = nothing; Magic Mouse 2 = no scroll.
MAGIC_MOUSE() to 0x0269 and handle its report id (Linux hid-magicmouse uses a different id for MM2 — verify against the device; unverified here). Keep the MM1 path intact.hid-magicmouse into a bthidd apple_trackpad module that emits evdev MT type-B events (ABS_MT_SLOT, ABS_MT_TRACKING_ID, ABS_MT_POSITION_X/Y, ABS_MT_TOUCH_MAJOR/MINOR, ABS_MT_PRESSURE, BTN_TOUCH, BTN_TOOL_FINGER/DOUBLETAP/TRIPLETAP, INPUT_PROP_POINTER|BUTTONPAD) through a uinput device set up with UI_SET_ABSBIT/UI_ABS_SETUP — verify FreeBSD uinput(4) MT support (sys/dev/evdev/uinput.c, evdev_mt.c; unverified). libinput then provides gestures/two-finger scroll.session_run).BTN_LEFT; MT2 pressure to ABS_MT_PRESSURE.usr.sbin/bluetooth/bthidd, upstream when stable.This requires the per-device uinput route into X (H5); the sysmouse aggregate cannot carry MT.
libinput debug-events shows GESTURE_*/POINTER_SCROLL_FINGER; two-finger scroll and tap-to-click work in Gershwin.evtest/libinput record capture attached for each device.e16/4-hid.md §6usr.sbin/bluetooth/bthidd/hid.c:59-74,434-512; Linux drivers/hid/hid-magicmouse.c (reference only, GPL — reimplement, do not copy)