feat: M3 complete — WiFi, BT keyboard and mouse

Bluetooth (RTL8723D USB combo chip):
- GPIO pin 357 (active low) enables WiFi/BT hardware via x6200_gpio_set()
- bluez5_utils 5.64 (downgraded from 5.79 — HID input plugin broken in 5.79)
- rtl8723d_config.bin added to overlay (missing from linux-firmware package)
- S45wifi-bt: GPIO enable + modprobe btusb/uhid/hidp at boot
- S85bt-keyboard: auto-connect loop with scan+connect every 20s

WiFi (RTL8723DU):
- out-of-tree lwfinger/rtw88 driver (RTW88_8723DU not in kernel 6.1 mainline)
- linux-firmware RTL_RTW88 for rtw88/rtw8723d_fw.bin
- regulatory.db for cfg80211
- wpa_supplicant with multi-network config in /etc/wpa_supplicant.conf
- S46wifi: wpa_supplicant + udhcpc at boot

Key findings:
- RTL8723D USB WiFi (0bda:d723) requires out-of-tree rtw88 on kernel 6.1
- BT and WiFi share same USB device, both need GPIO 357 = 0 to power on
- bluez5 5.79 HID input plugin not linked into bluetoothd (build system bug)
This commit is contained in:
Joakim
2026-05-09 21:02:53 +02:00
parent e67ff6c019
commit 2526d5b087
19 changed files with 964 additions and 16 deletions

1
.#grep Symbolic link
View File

@@ -0,0 +1 @@
joakim@here.1699682

1
.gitignore vendored
View File

@@ -27,3 +27,4 @@ build/
# Local overrides # Local overrides
local.mk local.mk
.env .env
wpa_supplicant.conf

View File

@@ -41,12 +41,15 @@ The product name **Virtuoso** is reserved for when (if!) Mestre actually deliver
| # | Milestone | Status | | # | Milestone | Status |
|---|----------------------------------------------------------------------|--------| |---|----------------------------------------------------------------------|--------|
| 1 | Boot a recent Buildroot LTS on the X6200 (serial console) | 🟡 In progress | | 1 | Boot a recent Buildroot LTS on the X6200 (serial console) | ✅ 2026-05-03 |
| 2 | Forward-port LCD panel driver — full display output | ⚪ Planned | | 2 | Working audio stack | ✅ 2026-05-06 |
| 3 | piHPSDR running on X6200 with working audio | ⚪ Planned | | 3 | WiFi and Bluetooth keyboard and mouse | ✅ 2026-05-09 |
| 4 | `mestre` service: evdev → ALSA virtual MIDI bridge | ⚪ Planned | | 4 | Forward-port LCD panel driver — full display output | ✅ 2026-05-03 |
| 5 | End-to-end: X6200 controlling Hermes-Lite 2 as a control head | ⚪ Planned | | 5 | piHPSDR running on X6200, controlled by mouse and keyboard | ⚪ Planned |
| 6 | First-boot wizard, settings persistence, polished UX | ⚪ Planned | | 6 | piHPSDR audio — HL2 audio via SoC speaker | ⚪ Planned |
| 7 | `mestre` service: evdev → ALSA virtual MIDI bridge | ⚪ Planned |
| 8 | End-to-end: X6200 controlling Hermes-Lite 2 as a control head | ⚪ Planned |
| 9 | First-boot wizard, settings persistence, polished UX | ⚪ Planned |
| ∞ | Earn the right to call it Virtuoso | ⚪ TBD | | ∞ | Earn the right to call it Virtuoso | ⚪ TBD |
## Architecture ## Architecture
@@ -66,7 +69,7 @@ The architecture is deliberately patch-free: piHPSDR is used as-is, and the MIDI
## Building ## Building
> ⚠️ **Pre-1.0**: build instructions are aspirational while milestone 1 is in progress. > ⚠️ **Pre-1.0**: actively developed. Core build works; some milestones still in progress.
### Prerequisites ### Prerequisites
@@ -114,4 +117,4 @@ Mestre stands on the shoulders of others. In particular:
## License ## License
GPL-2.0-or-later, matching the kernel-derived components Mestre vendors. GPL-2.0-or-later, matching the kernel-derived components Mestre vendors.
Userland components written specifically for Mestre may be re-licensed (e.g. to MIT) on a per-component basis where doing so doesn't conflict with this overall license; such cases will be marked clearly in the relevant subdirectory. Userland components written specifically for Mestre may be re-licensed (e.g. to MIT) on a per-component basis where doing so doesn't conflict with this overall license; such cases will be marked clearly in the relevant subdirectory.

255
audio-architecture.md Normal file
View File

@@ -0,0 +1,255 @@
# X6200 Audio Architecture
A detailed technical account of how audio works on the Xiegu X6200,
reverse-engineered during Mestre development (May 2026).
## Overview
The X6200 has two distinct audio sources and one audio output path.
Understanding the distinction between them is essential for any software
that wants to produce sound on this radio.
```
┌─────────────────────────────────────────────────────────┐
│ X6200 Hardware │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ RF Front │ │ Allwinner│ │
│ │ End │ │ R16 │ (SoC / Application CPU) │
│ │ + FPGA │ │ │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ RX audio │ DAC audio │
│ │ (analog) │ (digital → analog via codec) │
│ └──────┬────────┘ │
│ │ │
│ ┌────▼─────┐ │
│ │ Audio │ ← voice_rec bit selects source │
│ │ MUX │ (controlled via I2C to MCU) │
│ └────┬─────┘ │
│ │ │
│ ┌────▼─────┐ │
│ │ PA │ ← Power Amplifier │
│ │ (Class D)│ enabled by rxvol_set > 0 │
│ └────┬─────┘ │
│ │ │
│ ┌────▼─────┐ │
│ │ Speaker │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
## The Two Audio Sources
### Source 1: RX-Radio Audio (default)
The RF front-end and FPGA process the received RF signal and produce
an analog audio output — the classic "radio receiver sound". This is
the default audio source at power-on. It is completely independent of
the SoC and ALSA; it bypasses the codec entirely and is fed directly
into the audio MUX.
This is why you hear receiver noise immediately when the X6200 boots,
even before Linux has fully started.
### Source 2: SoC DAC Audio
The Allwinner R16 SoC contains an integrated audio codec
(`sun8i-a33-audio`) which can produce digital audio, converted to
analog via the DAC. This is the source used by software running on
the SoC — piHPSDR, aplay, PulseAudio, etc.
This source goes through the standard Linux ALSA stack:
```
Application (piHPSDR, aplay)
→ PulseAudio (mixing, resampling)
→ ALSA (hw:0,0 / pcmC0D0p)
→ sun8i-a33-audio codec driver
→ DAC (analog output)
→ Audio MUX
→ PA
→ Speaker
```
## The Audio MUX — The Critical Control Point
A hardware multiplexer inside the MCU/FPGA selects which source
reaches the power amplifier and speaker. This MUX is controlled
via I2C, **not** via ALSA or any Linux kernel interface.
### I2C Protocol
The X6200 MCU sits on I2C bus 0, address `0x72`. All hardware control
uses the X6200Control library protocol: a 6-byte write transaction
consisting of a 2-byte register address followed by a 4-byte value.
The audio MUX is controlled by the `voice_rec` bit in the
`sple_atue_trx` register (command index 12, address `0x0030`):
| voice_rec bit | Audio source heard in speaker |
|---------------|-------------------------------|
| 0 (default) | RX-radio (RF receiver) |
| 1 | SoC DAC (software audio) |
**To switch to SoC DAC audio:**
```bash
i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00
```
**To switch back to RX-radio audio:**
```bash
i2ctransfer -y 0 w6@0x72 0x00 0x30 0x00 0x00 0x00 0x00
```
## The Power Amplifier — The Other Critical Control Point
The speaker PA (power amplifier) is not always on. It is enabled
as a side effect of setting the speaker volume via `x6200_control_rxvol_set()`.
When `rxvol = 0` (the default after `x6200_control_init()`), the PA
is effectively off and nothing is heard regardless of the MUX position
or ALSA configuration.
This is why setting the correct mixer levels alone is not sufficient —
the MCU must be told to activate the PA via the I2C control protocol.
## The ALSA Codec — Clock Dependency
The `sun8i-a33-audio` codec requires its master clock (`pll-audio`,
22.5792 MHz) to be running for DAC output to work. The kernel's
clock framework only enables this clock when a consumer holds an
active reference — i.e., when an application has the PCM device open.
**This is the most subtle requirement:** if no process holds
`/dev/snd/pcmC0D0p` open, the audio clocks are disabled and the
codec produces no output, even if the ALSA mixer is correctly
configured and voice_rec is set to DAC mode.
PulseAudio, running in system mode, holds the PCM device open
continuously. This is what keeps the clocks running at all times
and makes reliable audio possible.
```
PulseAudio running → pcmC0D0p open → pll-audio enabled → codec active
```
Without PulseAudio (or equivalent), every `aplay` invocation would
need to wait for the codec to stabilise after the clock starts —
and the X6200 MCU may reset the MUX during this window.
## The ALSA Mixer — Required Configuration
The codec's internal mixer must be configured correctly for audio
to flow from the DAC to the analog output. The critical controls are:
| numid | Name | Required value |
|-------|-----------------------------------|----------------|
| 2 | AIF1 DA0 Playback Volume | 160, 160 |
| 7 | Headphone Playback Volume | 58 |
| 8 | Headphone Playback Switch | on, on |
| 20 | AIF1 Data Digital ADC Cap. Switch | on, on |
| 26 | AIF1 DA0 Stereo Playback Route | 3, 3 (Mix Mono)|
| 28 | AIF1 Slot 0 Digital DAC PB Switch | on, on |
| 31 | DAC Playback Switch | off, off |
Note that `DAC Playback Switch` (numid=31) must be **off** — this is
counterintuitive but correct. The signal path goes through AIF1,
not the direct DAC path.
These values are saved in `/var/lib/alsa/asound.state` and restored
at boot via `alsactl restore` (init script `S50alsa`).
## Complete Initialisation Sequence
The following sequence must be performed in this exact order at boot:
### Step 1: ALSA mixer state (S50alsa)
```bash
alsactl restore
```
Restores the correct mixer levels from `/var/lib/alsa/asound.state`.
### Step 2: PulseAudio (S50pulseaudio)
```bash
pulseaudio --system --daemonize --disallow-exit --exit-idle-time=-1
```
Starts PulseAudio in system mode. This opens `pcmC0D0p` and keeps
the ALSA clocks running continuously.
### Step 3: X6200 hardware init (S70mestre-audio)
```python
lib.x6200_control_init() # Establishes I2C state, sends all_cmd
lib.x6200_control_spmode_set(False) # Speaker mode (not headphone)
lib.x6200_control_rxvol_set(50) # Enables PA, sets volume (0-100)
```
`x6200_control_init()` sends a full command packet to the MCU via I2C,
establishing all register values. **It sets rxvol=0** (PA off) as
its default. `rxvol_set(50)` must follow immediately to enable the PA.
### Step 4: Switch audio MUX to SoC DAC (S70mestre-audio)
```bash
i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00
```
Sets the `voice_rec` bit, switching the speaker from RX-radio audio
to SoC DAC audio. From this point, any audio played via PulseAudio
or directly to `hw:0,0` (44100 Hz, stereo, S16_LE) will be heard
in the speaker.
## Mestre's Implementation
Mestre's init scripts implement this sequence:
```
S50alsa → alsactl restore
S50pulseaudio → pulseaudio --system
S70mestre-audio → x6200_control_init + rxvol_set + voice_rec switch
S96keepalive → I2C PMU keepalive (prevents automatic power-off)
```
The audio MUX switch command (`voice_rec=on`) is permanent until the
next `x6200_control_init()` call — it does not need to be repeated.
However, the PMU keepalive (separate from audio) must run continuously
or the radio will power off after approximately 180 seconds.
## Volume Control
Speaker volume is controlled via `x6200_control_rxvol_set(vol)` where
`vol` is 0-100. This sends an I2C command to the MCU which adjusts
an analog gain stage. **It is not reflected in the ALSA mixer** and
cannot be read or set via `amixer`.
The ALSA mixer `Headphone Playback Volume` (numid=7) and
`AIF1 DA0 Playback Volume` (numid=2) affect the digital gain
in the codec but are set to fixed values at boot and not used
for runtime volume control in Mestre.
## Known Quirks
**rxvol_set defaults to 0 after init.** Every call to
`x6200_control_init()` resets the speaker volume to 0 (PA off).
Always call `rxvol_set(N)` immediately after `init()`.
**voice_rec is reset by x6200_control_init().** If `init()` is called
again (e.g., after a crash), the MUX reverts to RX-radio mode and
the full init sequence must be repeated.
**PulseAudio must start before mestre-audio.** If PulseAudio is not
running when the audio MUX is switched, the codec clocks may not be
active and the switch may have no audible effect.
**44100 Hz stereo S16_LE** is the correct format for direct hardware
playback (`hw:0,0`). PulseAudio handles resampling from other rates.
## References
- [gdyuldin/X6200Control](https://github.com/gdyuldin/X6200Control) —
I2C control library source, including the `sple_atue_trx` register
layout and `voice_rec` bit definition.
- [gdyuldin/x6200_gui](https://github.com/gdyuldin/x6200_gui) —
Reference implementation of the full audio init sequence
(`audio_play_en()`, `audio_init()`, `radio_init()`).
- [AetherRadio/X6100Control](https://github.com/AetherRadio/X6100Control) —
X6100 control library (protocol-compatible with X6200), used to
understand the register map before X6200Control source was available.
- Allwinner R16 / sun8i-a33 audio driver: `sound/soc/sunxi/` in the
Linux kernel tree. The codec is `sun8i-codec` with an AIF1 I2S
interface to the SoC's audio PLL.

View File

@@ -1,5 +1,6 @@
menu "Mestre" menu "Mestre"
source "$BR2_EXTERNAL_MESTRE_PATH/package/x6200-control/Config.in" source "$BR2_EXTERNAL_MESTRE_PATH/package/x6200-control/Config.in"
source "$BR2_EXTERNAL_MESTRE_PATH/package/rtw88/Config.in"
endmenu endmenu

View File

@@ -35,3 +35,8 @@ CONFIG_SND_DUMMY=m
# Expose kernel config via /proc/config.gz (useful for debugging) # Expose kernel config via /proc/config.gz (useful for debugging)
CONFIG_IKCONFIG=y CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y CONFIG_IKCONFIG_PROC=y
# HID via userspace (required for Bluetooth HID devices)
CONFIG_UHID=y
CONFIG_INPUT_UINPUT=y

View File

@@ -56,11 +56,13 @@ CONFIG_NET_IPIP=y
# CONFIG_INET_DIAG is not set # CONFIG_INET_DIAG is not set
# CONFIG_IPV6 is not set # CONFIG_IPV6 is not set
CONFIG_CAN=y CONFIG_CAN=y
CONFIG_BT=m CONFIG_BT=y
CONFIG_BT_RFCOMM=m CONFIG_BT_RFCOMM=y
CONFIG_BT_BNEP=m CONFIG_BT_BNEP=y
CONFIG_BT_HIDP=m CONFIG_BT_HIDP=y
CONFIG_BT_HCIBTUSB=m CONFIG_BT_HCIBTUSB=y
CONFIG_UHID=y
CONFIG_HIDRAW=y
CONFIG_CFG80211=m CONFIG_CFG80211=m
CONFIG_CFG80211_DEVELOPER_WARNINGS=y CONFIG_CFG80211_DEVELOPER_WARNINGS=y
CONFIG_CFG80211_CERTIFICATION_ONUS=y CONFIG_CFG80211_CERTIFICATION_ONUS=y

View File

@@ -0,0 +1,5 @@
Section "Device"
Identifier "LCD"
Driver "fbdev"
Option "Rotate" "CCW"
EndSection

View File

@@ -0,0 +1,45 @@
#!/bin/sh
# S45wifi-bt — aktiverar WiFi/BT-chipet (RTL8723D) via GPIO
#
# X6200:s WiFi/BT-chip är avstängt vid boot och måste aktiveras
# via GPIO pin 357 (active low) innan kernel-drivern kan använda det.
#
# Måste köras innan S40bluetoothd startar.
case "$1" in
start)
echo "Starting WiFi/BT hardware"
python3 - << 'PYEOF'
import ctypes
lib = ctypes.CDLL("libaether_x6200_control.so")
lib.x6200_gpio_init.restype = ctypes.c_bool
lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int]
lib.x6200_gpio_init()
lib.x6200_gpio_set(357, 0) # active low — 0 = ON
PYEOF
sleep 1
modprobe btusb
modprobe uhid
modprobe hidp
sleep 3
echo "OK"
;;
stop)
python3 - << 'PYEOF'
import ctypes
lib = ctypes.CDLL("libaether_x6200_control.so")
lib.x6200_gpio_init.restype = ctypes.c_bool
lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int]
lib.x6200_gpio_init()
lib.x6200_gpio_set(357, 1) # 1 = OFF
PYEOF
echo "WiFi/BT hardware stopped"
;;
restart)
$0 stop; sleep 1; $0 start
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esac

View File

@@ -0,0 +1,44 @@
#!/bin/sh
# S46wifi — ansluter till WiFi via wpa_supplicant
#
# Körs efter S45wifi-bt (GPIO aktivering av chipet).
# Använder /etc/wpa_supplicant.conf för nätverkslista.
IFACE="wlan0"
PIDFILE="/var/run/wpa_supplicant.pid"
LOG="/var/log/wifi.log"
case "$1" in
start)
echo "Starting WiFi"
ip link set "$IFACE" up
sleep 2
wpa_supplicant -B \
-i "$IFACE" \
-c /etc/wpa_supplicant.conf \
-P "$PIDFILE" \
>> "$LOG" 2>&1
# Hämta IP via DHCP
sleep 3
dhclient "$IFACE" >> "$LOG" 2>&1 || \
udhcpc -i "$IFACE" -b -p /var/run/udhcpc.pid >> "$LOG" 2>&1
echo "OK"
;;
stop)
echo "Stopping WiFi"
[ -f "$PIDFILE" ] && kill $(cat "$PIDFILE") 2>/dev/null
rm -f "$PIDFILE"
ip link set "$IFACE" down
echo "OK"
;;
restart)
$0 stop; sleep 1; $0 start
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esac

View File

@@ -0,0 +1,58 @@
#!/bin/sh
# S85bt-keyboard — auto-connect Linocell BTK16 BT-tangentbord
#
# Försöker ansluta tangentbordet vid boot och startar en bakgrundsprocess
# som reconnectar automatiskt när tangentbordet kommer inom räckhåll.
#
# Tangentbordet måste vara parat och trustat sedan tidigare.
# Para manuellt med: bluetoothctl pair ED:96:00:23:E2:02
# bluetoothctl trust ED:96:00:23:E2:02
KEYBOARD_ADDR="ED:96:00:23:E2:02"
PIDFILE="/var/run/bt-keyboard.pid"
LOG="/var/log/bt-keyboard.log"
connect_keyboard() {
bluetoothctl power on > /dev/null 2>&1
bluetoothctl connect "$KEYBOARD_ADDR" > /dev/null 2>&1
}
watch_loop() {
while true; do
# Kolla om tangentbordet är anslutet
if ! bluetoothctl info "$KEYBOARD_ADDR" 2>/dev/null | grep -q "Connected: yes"; then
connect_keyboard
fi
sleep 10
done
}
case "$1" in
start)
echo "Starting BT keyboard auto-connect"
# Försök första anslutning i bakgrunden (tangentbordet kanske inte är i närheten)
(sleep 5 && connect_keyboard) &
# Starta watch-loop som reconnectar när tgb dyker upp
watch_loop >> "$LOG" 2>&1 &
echo $! > "$PIDFILE"
echo "OK"
;;
stop)
echo "Stopping BT keyboard auto-connect"
if [ -f "$PIDFILE" ]; then
kill $(cat "$PIDFILE") 2>/dev/null
rm -f "$PIDFILE"
fi
bluetoothctl disconnect "$KEYBOARD_ADDR" > /dev/null 2>&1
echo "OK"
;;
restart)
$0 stop; sleep 1; $0 start
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esac

View File

@@ -0,0 +1,116 @@
# $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/bin:/sbin:/usr/bin:/usr/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
#PubkeyAuthentication yes
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile .ssh/authorized_keys
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
#PermitEmptyPasswords no
# Change to no to disable s/key passwords
#KbdInteractiveAuthentication yes
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the KbdInteractiveAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via KbdInteractiveAuthentication may bypass
# the setting of "PermitRootLogin prohibit-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and KbdInteractiveAuthentication to 'no'.
#UsePAM no
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
#X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# override default of no subsystems
Subsystem sftp /usr/libexec/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server

View File

@@ -0,0 +1,16 @@
ctrl_interface=/var/run/wpa_supplicant
update_config=1
country=SE
# Lägg till dina nätverk här:
network={
ssid="Ditt_natverk_1"
psk="ditt_losenord_1"
priority=10
}
network={
ssid="Ditt_natverk_2"
psk="ditt_losenord_2"
priority=5
}

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBaWQsEKvTUbfTSKEa9sEuu7LpJm9Z07eSluIQV7+AxA joakim@cameron

View File

@@ -126,7 +126,17 @@ BR2_PACKAGE_STRACE=y
# - Network Manager (using simple /etc/network/interfaces for now) # - Network Manager (using simple /etc/network/interfaces for now)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
BR2_GLOBAL_PATCH_DIR="../br2_external/board/x6200/patches"
# x6200
BR2_PACKAGE_X6200_CONTROL=y
# MISC
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON3_CTYPES=y
BR_PACKAGE_JOE=y
# Audio
BR2_PACKAGE_ALSA_UTILS=y BR2_PACKAGE_ALSA_UTILS=y
BR2_PACKAGE_ALSA_UTILS_APLAY=y BR2_PACKAGE_ALSA_UTILS_APLAY=y
BR2_PACKAGE_ALSA_UTILS_AMIXER=y BR2_PACKAGE_ALSA_UTILS_AMIXER=y
@@ -135,12 +145,47 @@ BR2_PACKAGE_ALSA_LIB=y
BR2_PACKAGE_ALSA_LIB_MIXER=y BR2_PACKAGE_ALSA_LIB_MIXER=y
BR2_PACKAGE_ALSA_LIB_PCM=y BR2_PACKAGE_ALSA_LIB_PCM=y
BR2_PACKAGE_ALSA_LIB_RAWMIDI=y BR2_PACKAGE_ALSA_LIB_RAWMIDI=y
BR2_PACKAGE_X6200_CONTROL=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON3_CTYPES=y
BR2_PACKAGE_PULSEAUDIO=y BR2_PACKAGE_PULSEAUDIO=y
BR2_PACKAGE_PULSEAUDIO_DAEMON=y BR2_PACKAGE_PULSEAUDIO_DAEMON=y
BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y
BR2_PACKAGE_PULSEAUDIO=y BR2_PACKAGE_PULSEAUDIO=y
BR2_PACKAGE_PULSEAUDIO_DAEMON=y BR2_PACKAGE_PULSEAUDIO_DAEMON=y
BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y
# X11
BR2_PACKAGE_XORG7=y
BR2_PACKAGE_XSERVER_XORG_SERVER=y
BR2_PACKAGE_XAPP_XINIT=y
BR2_PACKAGE_XDRIVER_XF86_VIDEO_FBDEV=y
BR2_PACKAGE_XDRIVER_XF86_INPUT_LIBINPUT=y
BR2_PACKAGE_XKEYBOARD_CONFIG=y
BR2_PACKAGE_LIBINPUT=y
BR2_PACKAGE_XTERM=y
# GTK3
BR2_PACKAGE_LIBGTK3=y
BR2_PACKAGE_LIBGTK3_X11=y
BR2_PACKAGE_LIBEPOXY=y
# BT TGB MOUSE & WIFI
BR2_PACKAGE_LINUX_FIRMWARE=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT=y
BR2_PACKAGE_BLUEZ5_UTILS=y
BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y
BR2_PACKAGE_BLUEZ5_UTILS_DEPRECATED=y
BR2_PACKAGE_BLUEZ5_UTILS_TOOLS=y
BR2_PACKAGE_BLUEZ5_UTILS_PLUGINS_HID=y
BR2_PACKAGE_BLUEZ5_UTILS_TOOLS_HID2HCI=y
BR2_PACKAGE_EUDEV=y
BR2_PACKAGE_XKEYBOARD_CONFIG=y
BR2_PACKAGE_XINPUT=y
BR2_PACKAGE_XAPP_XRANDR=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88=y
BR2_PACKAGE_RTW88=y
BR2_PACKAGE_LINUX_FIRMWARE_REGULATORY=y
BR2_PACKAGE_WPA_SUPPLICANT=y
BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y
BR2_PACKAGE_WPA_SUPPLICANT_CTRL_IFACE=y
BR2_PACKAGE_DHCP=y
BR2_PACKAGE_DHCP_CLIENT=y

View File

@@ -0,0 +1,8 @@
config BR2_PACKAGE_RTW88
bool "rtw88 out-of-tree WiFi driver"
depends on BR2_LINUX_KERNEL
help
Out-of-tree Realtek rtw88 driver from lwfinger/rtw88.
Adds RTL8723DU USB WiFi support not available in kernel 6.1.
https://github.com/lwfinger/rtw88

View File

@@ -0,0 +1,16 @@
################################################################################
#
# rtw88 — out-of-tree Realtek WiFi driver
# Includes RTL8723DU USB support not yet in kernel 6.1 mainline.
# Based on gdyuldin/AetherX6200Buildroot's recipe.
#
################################################################################
RTW88_VERSION = 461b696b51317ba4ca585a4ddb32f2e72cd4efc9
RTW88_SITE = https://github.com/lwfinger/rtw88
RTW88_SITE_METHOD = git
RTW88_LICENSE = GPL-2.0
RTW88_INSTALL_STAGING = YES
$(eval $(kernel-module))
$(eval $(generic-package))

326
wifi-bt-architecture.md Normal file
View File

@@ -0,0 +1,326 @@
# X6200 WiFi and Bluetooth Architecture
A detailed technical account of how WiFi and Bluetooth work on the
Xiegu X6200, reverse-engineered during Mestre development (May 2026).
## Hardware Overview
The X6200 uses a **Realtek RTL8723D** combo chip for both WiFi and
Bluetooth. It is connected to the Allwinner R16 SoC via USB and
appears as a single USB device:
```
USB ID: 0bda:d723 Realtek Semiconductor Corp. 802.11n WLAN Adapter
Bus: USB 3 (musb-hdrc, high-speed)
```
The device exposes three USB interfaces:
- Interface 0+1: Bluetooth (class 0xE0, Wireless, RF)
- Interface 2: WiFi (class 0xFF, Vendor Specific)
Both WiFi and Bluetooth share the same USB device and antenna.
## The Power Control Problem
**The RTL8723D chip is powered off at boot.** It does not appear on
the USB bus until explicitly powered on via a GPIO pin. Without this
step, neither WiFi nor Bluetooth will work regardless of what drivers
or software is loaded.
The power control pin is managed via the X6200Control library:
```
GPIO chip: 0 (sun8i-a33 main GPIO controller)
GPIO pin: 357
Logic: Active LOW (0 = powered ON, 1 = powered OFF)
```
This is controlled via the `x6200_gpio_set()` function from
`libaether_x6200_control.so`:
```python
import ctypes
lib = ctypes.CDLL("libaether_x6200_control.so")
lib.x6200_gpio_init.restype = ctypes.c_bool
lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int]
lib.x6200_gpio_init()
lib.x6200_gpio_set(357, 0) # 0 = ON (active low!)
```
**Critical:** `gpio_init()` must be called before `gpio_set()`.
The active-low logic is counterintuitive — sending `1` powers the
chip OFF, sending `0` powers it ON.
After powering on, the USB device appears within 1-3 seconds:
```
usb 3-1: new high-speed USB device number 2 using musb-hdrc
usb 3-1: New USB device found, idVendor=0bda, idProduct=d723
```
## Bluetooth
### Driver
The Bluetooth interface of the RTL8723D is handled by the standard
Linux kernel `btusb` driver with the `btrtl` firmware loader.
Required kernel modules (compiled as `=m`):
```
CONFIG_BT=m
CONFIG_BT_HCIBTUSB=m
CONFIG_BT_RFCOMM=m
CONFIG_BT_BNEP=m
CONFIG_BT_HIDP=m
CONFIG_UHID=y ← required for HID input devices
CONFIG_INPUT_UINPUT=y ← required for HID input devices
```
Load order:
```bash
modprobe btusb # registers hci0 and loads firmware
modprobe uhid # userspace HID device support
modprobe hidp # HID over Bluetooth protocol
```
### Firmware
The RTL8723D Bluetooth requires two firmware files:
| File | Purpose |
|------|---------|
| `rtl_bt/rtw8723d_fw.bin` | Main BT firmware |
| `rtl_bt/rtw8723d_config.bin` | Configuration (10 bytes) |
**Both files are required.** Without `rtw8723d_config.bin`, `hciconfig
hci0 up` fails with:
```
Bluetooth: hci0: RTL: mandatory config file rtl_bt/rtw8723d_config not found
```
`rtw8723d_config.bin` is **not included** in Buildroot's
`BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT` package (as of 2025.02).
It must be added to the rootfs overlay manually:
```
br2_external/board/x6200/rootfs-overlay/lib/firmware/rtl_bt/rtw8723d_config.bin
```
Source: https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt/rtw8723d_config.bin
### BlueZ Version
**BlueZ 5.64 is required.** BlueZ 5.79 (Buildroot 2025.02 default)
has a build system bug where the HID input plugin (`input_init`) is
not linked into `bluetoothd` despite `--enable-hid` being passed to
configure. Symptoms:
```
Failed to connect: org.bluez.Error.NotAvailable br-connection-profile-unavailable
```
The bug manifests because `builtin_modules` in the generated Makefile
is empty and the input profile is never registered via
`btd_profile_register()`. Downgrading to BlueZ 5.64 resolves the
issue.
To force BlueZ 5.64 in Buildroot, override the version in a local
`.mk` file or patch `buildroot/package/bluez5_utils/bluez5_utils.mk`.
### HID Profile Requirements
For Bluetooth HID devices (keyboard, mouse) to connect, bluetoothd
must be started with `--compat`:
```bash
/usr/libexec/bluetooth/bluetoothd --compat &
```
And `/etc/bluetooth/input.conf` must have:
```ini
[General]
UserspaceHID=true
ClassicBondedOnly=false
```
### Pairing a Device
First-time pairing via `bluetoothctl`:
```
power on
agent on
default-agent
scan on
# Wait for device to appear, then:
pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
```
Once trusted, subsequent connections are automatic via the
`S85bt-keyboard` init script.
### Auto-reconnect
The `S85bt-keyboard` init script polls every 20 seconds and
reconnects if the device is not connected. It uses `scan on` before
each connection attempt to make the device visible:
```sh
{
echo "power on"; sleep 1
echo "scan on"; sleep 8
echo "scan off"; sleep 1
echo "connect $KEYBOARD_ADDR"; sleep 3
echo "quit"
} | bluetoothctl
```
## WiFi
### Driver
**The RTL8723D USB WiFi interface is NOT supported by any in-tree
driver in Linux 6.1.** Specifically:
| Driver | Status in 6.1 |
|--------|---------------|
| `rtl8xxxu` | Does not support 0bda:d723 |
| `r8723bs` | SDIO only, not USB |
| `rtw88` | Only has PCIe variant (RTW88_8723DE), USB variant added in 6.2 |
The solution is the **out-of-tree `lwfinger/rtw88` driver**, which
backports USB support for RTL8723D to older kernels. This is the same
approach used by the Xiegu X6100 community.
Buildroot package: `br2_external/package/rtw88/rtw88.mk`
```makefile
RTW88_VERSION = 461b696b51317ba4ca585a4ddb32f2e72cd4efc9
RTW88_SITE = https://github.com/lwfinger/rtw88
RTW88_SITE_METHOD = git
RTW88_LICENSE = GPL-2.0
$(eval $(kernel-module))
$(eval $(generic-package))
```
After loading, the driver creates `wlan0`:
```
3: wlan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop qlen 1000
link/ether cc:64:1a:52:91:85 brd ff:ff:ff:ff:ff:ff
```
### Firmware
Two firmware components are required:
| Component | Buildroot symbol | File |
|-----------|-----------------|------|
| WiFi firmware | `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88` | `rtw88/rtw8723d_fw.bin` |
| Regulatory DB | `BR2_PACKAGE_LINUX_FIRMWARE_REGULATORY` | `regulatory.db` |
**Note:** After adding `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88`, you
must run `linux-firmware-dirclean` before rebuilding, otherwise the
`br-firmware.tar` archive is not regenerated and the firmware file
will not appear in the target:
```bash
./scripts/build.sh linux-firmware-dirclean
./scripts/build.sh linux-firmware
./scripts/build.sh
```
### Network Configuration
WiFi is configured via `wpa_supplicant` with a multi-network
configuration file at `/etc/wpa_supplicant.conf`:
```ini
ctrl_interface=/var/run/wpa_supplicant
update_config=1
country=SE
network={
ssid="Network_1"
psk="password_1"
priority=10
}
network={
ssid="Network_2"
psk="password_2"
priority=5
}
```
`wpa_supplicant` automatically connects to the highest-priority
available network. Add as many `network={}` blocks as needed.
The file is installed in the rootfs overlay but should be considered
sensitive — do not commit real passwords to git.
### Boot Sequence
```
S35wifi-bt (or S45wifi-bt)
→ x6200_gpio_init()
→ x6200_gpio_set(357, 0) ← powers on RTL8723D
→ modprobe btusb ← registers hci0
→ modprobe uhid
→ modprobe hidp
→ sleep 3 ← wait for USB enumeration
S40bluetoothd
→ bluetoothd --compat ← BlueZ daemon
S46wifi
→ ip link set wlan0 up
→ wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
→ udhcpc -i wlan0 ← DHCP client
S85bt-keyboard
→ auto-connect loop (scan + connect every 20s)
```
**Ordering is critical:** The GPIO power-on script must run before
`bluetoothd` and `wpa_supplicant`. Without GPIO 357 = 0, neither
WiFi nor Bluetooth hardware is visible on the USB bus.
## Known Quirks
**GPIO is active low.** `gpio_set(357, 0)` = ON, `gpio_set(357, 1)`
= OFF. This is the opposite of what one might expect.
**Single USB device, two functions.** WiFi and BT share `0bda:d723`.
Powering on the chip enables both simultaneously. There is no way to
enable one without the other.
**rtw8723d_config.bin is missing from linux-firmware package.**
Buildroot's `BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT` does not include
this 10-byte config file. Must be added to overlay manually.
**BlueZ 5.79 HID input plugin is broken.** Downgrade to 5.64 for
working Bluetooth keyboard and mouse support.
**lwfinger/rtw88 is pinned to a specific commit.** The driver is
under active development; newer commits may break things. Pin to a
known-good commit in `rtw88.mk`.
**linux-firmware-dirclean required after adding RTW88 firmware.**
Buildroot caches `br-firmware.tar` and does not regenerate it when
new firmware symbols are added to `.config`. Always run
`linux-firmware-dirclean` after changing firmware selection.
## References
- [lwfinger/rtw88](https://github.com/lwfinger/rtw88) — out-of-tree
RTW88 driver with USB support for RTL8723DU
- [AetherRadio/X6200Control](https://github.com/gdyuldin/X6200Control) —
GPIO control library; `x6200_pin_wifi` (pin 357) is defined in
`include/aether_radio/x6200_control/low/gpio.h`
- [gdyuldin/AetherX6200Buildroot](https://github.com/gdyuldin/AetherX6200Buildroot) —
reference Buildroot configuration; provided the `rtw88` package
recipe that Mestre adapts
- Linux kernel `drivers/net/wireless/realtek/rtw88/` — mainline RTW88
driver (USB variant added in 6.2, not available in 6.1)
- Realtek RTL8723D datasheet — combo WiFi+BT chip used in X6200