← NextBSD Research

E16 ticket drafts — Bluetooth All filed

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.

TL;DR

Merged drafts

Tickets

Stage 0 — kernel & image

K1 · config: bake netgraph + the Bluetooth stack, vkbd and cuse into NEXTBSD — no .ko tree, so nothing can autoload (#412 phase 0) Filed nextbsd-kernel#231

Reponextbsd/nextbsd-kernelSizeSTrackShip
Labelsarea:bluetooth area:kext enhancement
Depends on

Problem

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.

Change

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).

Acceptance

Related


Also in this change: vkbd (Bluetooth HID keyboards)

Merged from E16 draft H1.

Why

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.

Change

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).

Acceptance

Links


Also in this change: cuse (virtual_oss / Bluetooth audio)

Merged from E16 draft A1.

Why

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.

Exact change

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.

Acceptance criteria

Links

K2 · boot-test: assert the Bluetooth stack is present on both arches (ngctl list/types, net.bluetooth, hccontrol exits 7) and guard the kernel options Filed nextbsd#475

Reponextbsd/nextbsdSizeSTrackShip
Labelsenhancement area:bluetooth area:ci
Depends onK1 (nextbsd-kernel#231)

Problem

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.

Change

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.

Acceptance

Related

K3 · Ship Intel (ibt-*) and Realtek (rtl_bt) Bluetooth firmware in the image, with licences — fetched at assembly like the Pi DTBs Filed nextbsd#476

Reponextbsd/nextbsdSizeSTrackShip
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231)

Problem

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.

Change

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.

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).

Acceptance

Related

A2 · Ship virtual_oss's Bluetooth backend (`/usr/local/lib/virtual_oss/voss_bt.so`) on the image Filed nextbsd#485

Reponextbsd/nextbsdSizeSTrackShip
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231)

Why

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.

Exact change

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.

Acceptance criteria

Links

A3 · kldload(2) of a statically linked module returns ENOENT on NextBSD — virtual_oss dies at `kldload("cuse.ko")` even with cuse baked Filed nextbsd-kernel#236

Reponextbsd/nextbsd-kernelSizeSTrackShip
Labelsbug area:bluetooth
Depends onK1 (nextbsd-kernel#231)

Why

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.

Exact change

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.

Acceptance criteria

Links

Stage 1 — hand verification

K6 · Phase 1: hand-verify the baked stack on real USB dongles (amd64 + Pi 500+ via RP1 xhci) and start the Bluetooth hardware matrix Filed nextbsd#477

Reponextbsd/nextbsdSizeSTrackShip
Labelsdocumentation area:bluetooth needs-hardware
Depends onK1 (nextbsd-kernel#231)

Problem

412 phase 1: "hccontrol, l2ping, sdpcontrol by hand. Prove the stack works before wrapping it." After K1 everything above USB enumeration is reasoned from source, never executed — the WLAN plan's honesty clause applies verbatim. No daemon should be written against an unproven kernel path.

Change

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.

Acceptance

Related

B4 · Spike: hand-verify FreeBSD 15.1's LE path on NextBSD hardware with FreeBSD-BLE (scan, connect, SMP pair, HOGP mouse) and record the controller matrix Filed nextbsd#478

Reponextbsd/nextbsdSizeSTrackNeeds answer
Labelsquestion area:bluetooth
Depends onK1 (nextbsd-kernel#231)

Why

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.

Exact steps

  1. Build 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.
  2. 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).
  3. 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.
  4. 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).
  5. hcidump/btsnoop traces attached for each device; note any ng_l2cap panics or ng_hci "could not send" logs.

Acceptance criteria

Links

Stage 2 — bluetoothd core

D1 · bluetoothd v0 skeleton: launchd job, Mach service, no-stack/no-adapter idle, `bluetooth status`, CI markers Filed nextbsd-userland#206

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement 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.

Why

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.

Exact change

Copy the wland layout one-for-one (src/WLAN/src/Bluetooth/):

Acceptance

Links

https://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

B2 · bluetoothd: engine interface (engine.h) that Classic and LE both implement - typed addresses, one pairing-prompt shape, engine-neutral bond record Filed nextbsd-userland#204

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends on

Why

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.

Exact change

New src/Bluetooth/engine.h (name to match area D's layout), contents per the research note §4.2:

Acceptance criteria

Links

D2 · bluetoothd: adapter lifecycle — detect ubtN, build the netgraph graph, initialise the controller, publish State:/Bluetooth/Adapter, power on/off Filed nextbsd-userland#207

Reponextbsd/nextbsd-userlandSizeLTrackShip
Labelsenhancement area:bluetooth
Depends onD1 (nextbsd-userland#206), K1 (nextbsd-kernel#231), B2 (nextbsd-userland#204)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §2, §4, §7.

Why

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.

Exact change

src/Bluetooth/adapter.c/.h, replacing D1's stub:

Acceptance

Links

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).

D3 · bluetoothd: Classic inquiry scan with cached results and remote names; `bluetooth scan` Filed nextbsd-userland#208

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends onD2 (nextbsd-userland#207)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §5, §5.1.

Why

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).

Exact change

Acceptance

Links

src/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).

D5 · bluetoothd: SCPreferences persistence — PairedDevices metadata plus a separate 0600 link-key file; `bluetooth list` Filed nextbsd-userland#210

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends onD1 (nextbsd-userland#206)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §6.

Why

"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.

Exact change

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:

Acceptance

Links

src/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

D4 · bluetoothd: security manager replacing hcsecd — link-key/PIN replies, SSP (numeric comparison, passkey), pair/unpair RPCs, pull-model user confirmation Filed nextbsd-userland#209

Reponextbsd/nextbsd-userlandSizeLTrackShip
Labelsenhancement area:bluetooth
Depends onD2 (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.

Why

FreeBSD's hcsecd(8) cannot be the key owner under bluetoothd:

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).

Exact change

src/Bluetooth/security.c/.h:

Acceptance

Links

usr.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)


Engine notes (merged from E16 draft B3)

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.

Why

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.)

Exact change

src/Bluetooth/classic_engine.c implementing bt_engine_ops (B2):

Acceptance criteria

Links

D7 · bluetoothd: supervise stock sdpd (and the child-supervision hook bthidd/btpand will use) Filed nextbsd-userland#212

Reponextbsd/nextbsd-userlandSizeSTrackShip
Labelsenhancement area:bluetooth
Depends onD2 (nextbsd-userland#207)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §2, §3.2.

Why

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).

Exact change

Acceptance

Links

usr.sbin/bluetooth/sdpd/sdpd.8, sdpd/main.c:75-95 (-d, -c, -u, -g), src/WLAN/wland.c:249-282,584-601

D8 · bluetoothd: connect/disconnect, per-device state publishing (State:/Bluetooth/Device), auto-reconnect of paired devices at boot Filed nextbsd-userland#213

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends onD4 (nextbsd-userland#209), D5 (nextbsd-userland#210)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §5, §7.

Why

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).

Exact change

Acceptance

Links

ng_hci.h:724,735,813,819, src/WLAN/airport.c:129-212, src/WLAN/wlan.defs (wlan_connect returns-when-requested comment)

D6 · bluetooth(8): full CLI — status|list|scan|pair|confirm|unpair|connect|disconnect|power|discoverable, `--no-prompt` exit codes, man pages Filed nextbsd-userland#211

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends onD1 (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.

Why

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.

Exact change

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]

Acceptance

Links

src/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

D9 · CI: Bluetooth boot-test gates (BT-RPC-OK, BT-CLI-OK, BT-PREFS-OK, BT-NOSTACK→BT-NOADAPTER flip when the kernel bakes netgraph) Filed nextbsd#480

Reponextbsd/nextbsdSizeSTrackShip
Labelsenhancement area:bluetooth
Depends onD1 (nextbsd-userland#206)

Parent: nextbsd/nextbsd#473. Notes: e16/3-bluetoothd-cli.md §8.

Why

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.

Exact change

tests/boot-test.sh (expect blocks placed after IPCFG-IPCONFIG-OK, before LAUNCHD-MACH-RUN-DONE, in the style of :986-997):

Also: 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.

Acceptance

Links

tests/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)

K4 · Contract: bluetoothd runs iwmbtfw/rtlbtfw on USB arrival for controllers that boot in bootloader/ROM mode (devd's iwmbtfw.conf/rtlbtfw.conf, without devd) Filed nextbsd-userland#202

Reponextbsd/nextbsd-userlandSizeMTrackSequence
Labelsenhancement area:bluetooth
Depends onK3 (nextbsd#476), D2 (nextbsd-userland#207)

Problem

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.

Change

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):

  1. Match: VID 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.
  2. Run /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.
  3. Expect the device to detach and re-attach; the 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.
  4. If the loader fails, publish the adapter as 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).

Acceptance

Related

D10 · Bluetooth hardware bring-up run: first execution of bluetoothd scan/pair/reconnect on a real USB dongle (test matrix + findings) Filed nextbsd#481

Reponextbsd/nextbsdSizeSTrackSequence
Labelsenhancement area:bluetooth needs-hardware
Depends onD2 (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.

Why

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.

Exact change

No code by default. A checklist executed on real hardware, results pasted into this issue, and follow-up tickets filed for every deviation:

  1. Dongle inventory: at least one CSR8510-class (BT 4.0, SSP) and, if available, one Intel/Realtek combo card that ng_ubt claims. Record usbconfig ids and Read_Local_Supported_Features bit 51 (SSP).
  2. BT-ADAPTER-OK within 5 s of plug-in; ngctl list graph matches D2's expectation; restart-adopts; unplug/replug cycle ×5.
  3. bluetooth scan finds a discoverable phone and a keyboard with names and (if Write_Inquiry_Mode 1 accepted) RSSI.
  4. 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.
  5. Reboot; keyboard reconnects (bluetooth list → connected) with no user action; Link_Key_Request answered from the .keys file (log shows key: exists).
  6. bluetooth unpair; device must re-pair.
  7. sdpd child visible; sdpcontrol browse from a second machine.
  8. Negative: start hcsecd by hand → BT-SEC-CONFLICT; unplug mid-pairing → clean failed, adapter usable after replug.

Acceptance

Links

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)

Stage 3 — keyboards, mice & trackpads (HID)

H2 · bluetoothd: HID profile driver — generate bthidd.conf on pair, supervise bthidd -u, keyboards usable pre-login Filed nextbsd-userland#214

Reponextbsd/nextbsd-userlandSizeMTrackShip
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231), D4 (nextbsd-userland#209), D5 (nextbsd-userland#210), D7 (nextbsd-userland#212)

Why

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).

Change

Add an HID driver to bluetoothd (src/Bluetooth/, beside the wland-shaped core):

  1. Pair hook. When area D's pairing completes for a device whose SDP record has the HID service (UUID 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.
  2. Config generation. Regenerate a bluetoothd-owned 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.
  3. Supervision. Run 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.
  4. Unpair. Remove the record, regenerate conf, bthidcontrol Forget (or drop the hids line), restart bthidd; area D drops the link key.
  5. State. Publish 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).
  6. Always -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).
  7. CLI. 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).

Acceptance

Links

Note (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.

H3 · bthidd: reload config on SIGHUP without dropping sessions; rescan all devices per tick (NextBSD patch, upstream candidate) Filed nextbsd-userland#215

Reponextbsd/nextbsd-userlandSizeSTrackHarden
Labelsenhancement area:bluetooth
Depends onH2 (nextbsd-userland#214)

Why

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:

Change

Carry a patch to usr.sbin/bluetooth/bthidd in the NextBSD userland build (and send it upstream):

  1. SIGHUP → set a 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.
  2. 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.
  3. Optional: dump session state (bdaddr, keyboard/mouse, uinput node names) to a file or on SIGUSR1 so bluetoothd can publish Connected without parsing syslog.
  4. bluetoothd (H2) then sends SIGHUP instead of restarting when the patched bthidd is detected (e.g. bthidd -h mentions reload, or a version string).

Acceptance

Links

H5 · Bluetooth mice and trackpads in X via per-device uinput nodes (blocked on input hotplug) Filed nextbsd#483

Reponextbsd/nextbsdSizeMTrackSequence
Labelsenhancement area:bluetooth area:desktop
Depends onH2 (nextbsd-userland#214), H4 (nextbsd#482)

Why

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).

Change (once H4 is answered)

  1. If a devd-protocol relay or IOKit-backed libudev-devd exists: verify that creating the uinput node after X is up results in libinput adding the device (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.
  2. If the decision is "aggregate policy": document that BT mice work through "System mouse" (rcpt_mask bit 0), verify no duplicate motion on a psm(4) laptop with the chosen mask, and record that BT trackpad multitouch is therefore out of reach (H6 parked harder).
  3. Either way: add an 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.
  4. Interim, if hotplug is far off: evaluate having bluetoothd trigger an X-side rescan; note there is no "rescan inputs" request in the X server — this is why the interface must be at the libudev-devd level.

Acceptance

Links

Stage 4 — audio

A4 · bluetoothd: A2DP source — on connect of an AudioSink device, run/retarget virtual_oss at `/dev/bluetooth/<addr>`, publish the node, fall back on disconnect Filed nextbsd-userland#218

Reponextbsd/nextbsd-userlandSizeLTrackShip
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231), A2 (nextbsd#485), A3 (nextbsd-kernel#236), D1 (nextbsd-userland#206), D8 (nextbsd-userland#213)

Why

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.

Exact change

  1. Profile detection: after ACL up + SDP browse (D track), classify AUDIO_SINK / AUDIO_SOURCE records and store Profiles on the device object.
  2. Two modes, chosen at runtime: - Cooperative (audio epic present, detected by /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.
  3. Publish 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.
  4. Policy knob in SCPreferences (AutoSwitchAudio, default true) so a user can keep BT audio manual.
  5. Man page section in bluetoothd.8 describing the two modes and the SC keys — this is the contract the audio epic implements against.

Acceptance criteria

Links

A5 · bluetoothd: A2DP sink — supervise virtual_bt_speaker so a phone can play through the machine's speakers Filed nextbsd-userland#219

Reponextbsd/nextbsd-userlandSizeMTrackSequence
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231), A3 (nextbsd-kernel#236), D1 (nextbsd-userland#206)

Why

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.

Exact change

Acceptance criteria

Links

A7 · bluetoothd: AVRCP target — accept the headset's play/pause/next/volume keys and deliver them to the session Filed nextbsd-userland#220

Reponextbsd/nextbsd-userlandSizeLTrackSequence
Labelsenhancement area:bluetooth
Depends onA4 (nextbsd-userland#218), D1 (nextbsd-userland#206)

Why

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.

Exact change

  1. bluetoothd listens on L2CAP PSM 0x0017 (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.
  2. Local SDP record 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.
  3. Delivery: post 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).

Acceptance criteria

Links

Stage 5 — Bluetooth LE

B5 · ng_hci/ng_l2cap LE hardening: honour LE_Read_Buffer_Size for LE ACL flow control, forward LE connection-parameter updates to the controller, implement the stub LE meta handlers Filed nextbsd-kernel#234

Reponextbsd/nextbsd-kernelSizeMTrackHarden
Labelsbug area:bluetooth
Depends onB4 (nextbsd#478), K1 (nextbsd-kernel#231)

Why

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:

  1. LE buffer accounting. 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.
  2. LE signalling parameter update. 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.
  3. Stub LE meta handlers. 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.
  4. 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.

Exact change

Acceptance criteria

Links

B6 · bluetoothd LE engine: GAP scan/connect, ATT/GATT client, SMP pairing agent and LTK responder over the kernel's ATT/SMP sockets Filed nextbsd-userland#205

Reponextbsd/nextbsd-userlandSizeXLTrackSequence
Labelsenhancement area:bluetooth
Depends onB2 (nextbsd-userland#204), D4 (nextbsd-userland#209), B4 (nextbsd#478), B5 (nextbsd-kernel#234)

Why

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.

Exact change

src/Bluetooth/le_engine.c (+ att.c, gatt.c, smp.c, le_gap.c) implementing bt_engine_ops and bt_le_ops:

Acceptance criteria

Links

H7 · Scope HOGP (HID over GATT) for LE-only keyboards/mice — blocked on the Bluetooth engine decision Filed nextbsd-userland#217

Reponextbsd/nextbsd-userlandSizeXLTrackParked
Labelsquestion area:bluetooth status:parked
Depends onB6 (nextbsd-userland#205)

Why

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.

What HOGP needs (scoping, no implementation)

  1. LE link management: 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).
  2. SMP pairing in userland: Just Works / Passkey, LTK + IRK exchange, LE_Start_Encryption on reconnect; LTK persistence (same secret-store problem as link keys, area D).
  3. GATT client: primary service discovery, HID Service 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.
  4. Report delivery: notified input reports → the same parsers/sinks bthidd uses (hid.c, kbd.c, btuinput.c are reusable almost verbatim) → vkbd/uinput. Output reports (LEDs) via write-without-response.
  5. Reconnect: LE peripherals reconnect by directed advertising; the host must keep auto-connect/white-list logic running for bonded devices (no bthidd equivalent); pre-login again means bluetoothd must do this without a session.
  6. Dual-mode devices (Apple Magic devices advertise both): prefer Classic while HOGP is absent; pairing UI must not offer LE-only devices as pairable until this lands (H2 item 7).

Acceptance (for this scoping ticket)

Links

Needs an answer

H4 · Decide how Bluetooth HID input reaches X: kern.evdev.rcpt_mask policy and the input-hotplug interface Filed nextbsd#482

Reponextbsd/nextbsdSizeSTrackNeeds answer
Labelsquestion area:bluetooth area:desktop
Depends on

The problem

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.

The routes (details: 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.

Questions to answer

  1. Mask policy for the desktop image (owner: desktop/input epic): keep 0xC, adopt XLibre's 6, or 7/3? Prerequisite fact: do hidmap-based drivers (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.
  2. Hotplug interface (owner: input epic): which of (a) a small relay speaking devd's notice protocol on /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.
  3. Where is the mask set on NextBSD (loader tunable in nextbsd-overlays/rootfs/boot/loader.conf.d/, or a launchd-run sysctl at boot)? E16 will not set it from bluetoothd.
  4. Should 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.)

Acceptance

Links

A6 · Decide: HFP/HSP headset microphone — needs eSCO in ng_hci, alt-setting fix in ng_ubt, sco hook wiring, and a from-scratch HFP daemon (XL) Filed nextbsd-kernel#237

Reponextbsd/nextbsd-kernelSizeXLTrackNeeds answer
Labelsquestion area:bluetooth
Depends onK1 (nextbsd-kernel#231), A4 (nextbsd-userland#218)

Why

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.

What shipping it would take

  1. Kernel (this repo, as 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.
  2. bluetoothd (K/D tracks): 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).
  3. New userland: an HFP AG (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.

The question

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:

Acceptance criteria

Links

K7 · rpi5: measure what the firmware leaves for the onboard CYW43455 Bluetooth (BT_ON gio 29, uarta pinmux, 96 MHz clock, bdaddr) before any driver work Filed nextbsd-kernel#232

Reponextbsd/nextbsd-kernelSizeSTrackNeeds answer
Labelsquestion area:bluetooth area:rpi5 needs-hardware
Depends on

Question

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":

  1. Is BT_ON already high? The radio's reset is 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.
  2. Are gpio24–27 muxed to 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.
  3. What does 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.
  4. What BD address does the firmware put in 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.

How

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.

Acceptance

Related

Harden — CI without a radio

K5 · btemu: a userland HCI controller on ng_hci's drv hook so CI can exercise hci/l2cap/btsocket and bluetoothd without a radio Filed nextbsd-userland#203

Reponextbsd/nextbsd-userlandSizeMTrackHarden
Labelsenhancement area:bluetooth area:ci
Depends onK1 (nextbsd-kernel#231), K2 (nextbsd#475)

Problem

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.

Change

A small C tool over libnetgraph (src/Bluetooth/tests/btemu.c or wherever area 2 puts the daemon's tests):

  1. 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.
  2. Read HCI packets from the data socket (type byte 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.
  3. Optional, second step: one fake remote — Inquiry → Inquiry Result + Inquiry Complete; Create_Connection → Connection Complete; ACL data echoed so l2ping -a <fake> gets its echo response back. That is enough for sdpcontrol browse against a fake SDP responder later.
  4. A --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.

Acceptance

Related

H8 · CI: synthetic input-path test in qemu (vkbd → kbdmux/evdev, uinput → /dev/input → X) without a radio Filed nextbsd#484

Reponextbsd/nextbsdSizeMTrackHarden
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231)

Why

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.

Change

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):

  1. vkbd: open /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).
  2. uinput keyboard/mouse: create "Bluetooth Keyboard"/"Bluetooth Mouse" via 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.
  3. consolectl/sysmouse: 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.
  4. X visibility (Gershwin image job, 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).
  5. Run 1-3 in the existing boot-test qemu job on both arches; run 4 in the desktop image job.

Acceptance

Links

A8 · Boot-test: prove the Bluetooth-audio plumbing without a radio (cuse, virtual_oss under launchd, backend load, sndstat/sndctl visibility) Filed nextbsd#486

Reponextbsd/nextbsdSizeSTrackHarden
Labelsenhancement area:bluetooth
Depends onK1 (nextbsd-kernel#231), A2 (nextbsd#485), A3 (nextbsd-kernel#236)

Why

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.

Exact change

Extend tests/boot-test.sh (and the arm64 lane) with a bt-audio block:

  1. /dev/cuse exists (A1).
  2. /usr/local/lib/virtual_oss/voss_bt.so exists, ldd clean, exports voss_backend_bt_play/_rec (A2).
  3. Start 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).
  4. 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.
  5. Once A4 lands: launchctl list shows org.nextbsd.bluetoothd, and bluetooth status reports "no adapter" without crashing; the audio state-machine unit test from A4 runs here.
  6. Assert the wired sound path is unchanged: 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.

Acceptance criteria

Links

Docs

B8 · Docs: correct freebsd-bluetooth-management.html - FreeBSD's kernel already has LE HCI/L2CAP fixed channels and SMP sockets; the gap is userland Filed nextbsd#479

Reponextbsd/nextbsdSizeXSTrackShip
Labelsdocumentation area:bluetooth
Depends on

Why

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:

Anyone 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.

Exact change

Acceptance criteria

Links

Parked

K8 · rpi5: onboard Bluetooth over uarta — bcm7271 UART compat, brcmstb-gpio, bcm2712-pinctrl, an H4 transport on ttyhook, BCM4345C0.hcd patchram (parked) Filed nextbsd-kernel#233

Reponextbsd/nextbsd-kernelSizeXLTrackParked
Labelsenhancement area:bluetooth area:rpi5 needs-hardware status:parked
Depends onK1 (nextbsd-kernel#231), K7 (nextbsd-kernel#232)

Problem

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.

What is missing (all of it, with evidence)

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

The route (if/when it is picked up)

  1. K7's measurements first.
  2. 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).
  3. If K7 says so: brcmstb-gpio and bcm2712-pinctrl drivers (gpiobus/fdt_pinctrl), following the RP1 patches' shape.
  4. ng_h4 rewritten on ttyhook (≈1k LOC; the 13.5 file is the spec for the framing/state machine).
  5. A ≈300-line 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).
  6. bluetoothd learns a "uart adapter" kind: open /dev/cuau<N>, patchram, set bdaddr, attach H4, then the same bluetooth_setup_stack() as USB.

Acceptance (for the eventual implementation)

Related

B7 · Spike: price a netgraph LE tee node (route LE connection-handle ACL to a userland ng_socket) that would let a full host like NimBLE run beside kernel Classic Filed nextbsd-kernel#235

Reponextbsd/nextbsd-kernelSizeMTrackParked
Labelsquestion area:bluetooth status:parked
Depends onB4 (nextbsd#478)

Why

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.

Exact question

Design and prototype ng_btle_tee (working name):

Acceptance criteria

Links

H6 · bthidd: Apple Magic Mouse 2 and Magic Trackpad 1/2 vendor reports (scroll, multitouch via uinput) Filed nextbsd-userland#216

Reponextbsd/nextbsd-userlandSizeLTrackParked
Labelsenhancement area:bluetooth status:parked
Depends onH2 (nextbsd-userland#214), H5 (nextbsd#483)

Why

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.

Change (parked until H2/H5 land and there is hardware)

  1. Magic Mouse 2 (S): extend 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.
  2. Magic Trackpad 1 (05ac:030e) / 2 (05ac:0265) (L): port the finger-block parsing from Linux 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.
  3. The Magic Trackpad 2 requires a vendor feature report to switch into multitouch mode (as Linux does); send it over the control channel on session open (session_run).
  4. Force-touch/click: map to BTN_LEFT; MT2 pressure to ABS_MT_PRESSURE.
  5. Carry as NextBSD patches to usr.sbin/bluetooth/bthidd, upstream when stable.

This requires the per-device uinput route into X (H5); the sysmouse aggregate cannot carry MT.

Acceptance

Links