User Data Partition¶
This directory contains the writable user partition for the gateway.
Overview¶
Unlike the root filesystem (read-only SquashFS), the userdata partition uses JFFS2 — a writable, wear-leveling filesystem designed for flash memory.
This partition is essential because:
- The rootfs
/etcsymlinks most configuration files here - All init scripts are stored here (the rootfs only contains a bootstrap)
- User applications and customizations live here
This design allows modifying configuration and adding services without rebuilding the read-only rootfs.
Boot Integration¶
The rootfs bootstrap (/etc/init.d/rcS) mounts this partition at /userdata, then executes all scripts matching /userdata/etc/init.d/S??* in alphanumeric order.
Rootfs bootstrap
↓
Mount /userdata (JFFS2)
↓
Execute init scripts:
S05syslog → Start system logging
S10network → Configure network (DHCP or static)
S15hostname → Set hostname, /etc/hosts
S20time → Set timezone, start the ntpd daemon
S30dropbear → Start SSH server
S50uart_bridge → Arm the in-kernel UART↔TCP bridge (skipped if radio mode = otbr)
S70otbr → Start Thread border router (if radio mode = otbr)
S90checkpasswd → Warn if default password
Partition Structure¶
/userdata/
├── etc/
│ ├── passwd # User accounts (with password hashes)
│ ├── group # User groups
│ ├── hostname # Gateway hostname
│ ├── profile # Shell profile (TERM, TERMINFO, resize)
│ ├── motd # Message of the day
│ ├── TZ # Timezone
│ ├── ntp.conf # NTP server configuration
│ ├── eth0.bak # Static IP template (for reference)
│ ├── radio.conf # Radio mode: MODE=otbr for Thread (absent = Zigbee)
│ ├── dropbear/ # Dropbear SSH host keys (generated on first boot)
│ └── init.d/ # Init scripts (executed by rootfs bootstrap)
│ ├── S05syslog
│ ├── S10network
│ ├── S15hostname
│ ├── S20time
│ ├── S30dropbear
│ ├── S50uart_bridge
│ ├── S70otbr
│ └── S90checkpasswd
├── ssh/
│ └── authorized_keys # SSH public keys for passwordless access
├── thread/ # Thread network credentials (created by otbr-agent)
└── usr/
├── bin/ # User applications (nano, otbr-agent, ot-ctl, boothold)
└── share/
└── terminfo/ # Terminal definitions (linux, vt100, vt102, xterm)
Network Configuration¶
flash_userdata.sh asks for network configuration and radio mode at flash time.
The settings are baked into userdata.bin before flashing.
To change the IP after flashing, edit /userdata/etc/eth0.conf on the running gateway:
Format:
Then restart the network: /userdata/etc/init.d/S10network restart
/userdata/etc/eth0.bak is provided as a reference template.
Radio Mode¶
The gateway supports two radio modes, selected at flash time. Mode is
controlled by /userdata/etc/radio.conf (managed automatically by
flash_efr32.sh since v3.1):
| Mode | radio.conf keys |
Init script that wakes up | EFR32 firmware | Use case |
|---|---|---|---|---|
| Zigbee (default) | FIRMWARE_BAUD=<baud> (no MODE) |
S50uart_bridge |
NCP, RCP, or OT-RCP for ZoH/OTBR-host | Zigbee2MQTT, ZHA, OTBR-on-host |
| Thread | FIRMWARE_BAUD=<baud> + MODE=otbr |
S70otbr |
OT-RCP | Matter, Home Assistant Thread (OTBR-on-gateway) |
When MODE=otbr is present, S50uart_bridge is skipped and S70otbr
starts otbr-agent instead. When absent (Zigbee mode), S50uart_bridge
arms the in-kernel UART bridge at FIRMWARE_BAUD.
See ot-br-posix/README.md for Thread-specific documentation.
radio.conf keys (full reference)¶
| Key | Values | Default | Written by | Read by |
|---|---|---|---|---|
FIRMWARE |
ncp, rcp, otrcp, router |
(absent) | flash_efr32.sh |
docs / diagnostics |
FIRMWARE_VERSION |
e.g. 7.5.1 (NCP, Router only) |
(absent) | flash_efr32.sh |
docs / diagnostics |
FIRMWARE_BAUD |
115200, 230400, 460800, 691200, 892857 |
460800 |
flash_efr32.sh |
S50uart_bridge, S70otbr |
BOOTLOADER_VERSION |
e.g. 2.4.2 |
(absent) | flash_efr32.sh — every flash |
docs / diagnostics |
MODE |
otbr (or absent) |
(absent = Zigbee) | flash_efr32.sh |
S50uart_bridge, S70otbr |
BRIDGE_BIND |
0.0.0.0, 127.0.0.1 |
0.0.0.0 |
(manual) | S50uart_bridge |
Migrated from v3.0.x? Older releases wrote two redundant host-side keys,
BRIDGE_BAUD(Zigbee) andOTBR_BAUD(OTBR), in addition toFIRMWARE_BAUD. v3.2+ collapses them to the singleFIRMWARE_BAUDtruth. Both init scripts still fall back to the legacy keys whenFIRMWARE_BAUDis absent, and the nextflash_efr32.shrun strips them automatically — no user action needed.
flash_efr32.sh writes the right MODE and FIRMWARE_BAUD based on
the firmware you flash — manual editing is only needed for advanced
cases like OT-RCP in ZoH or OTBR-on-host modes (see
2-Zigbee-Radio-Silabs-EFR32/26-OT-RCP/docker/README.md).
FIRMWARE / FIRMWARE_VERSION / FIRMWARE_BAUD / BOOTLOADER_VERSION (v3.2+)¶
Four informational keys describing what's actually on the EFR32 — both
the application slot AND the Stage-2 Gecko Bootloader. They let an
offline reader (or a future migration script) tell exactly what's
running without probing the chip via universal-silabs-flasher.
FIRMWARE— name of the app firmware in the EFR32's application slot:ncp|rcp|otrcp|router. Neverbootloader— the Gecko Bootloader is a runtime mode, not an application. A bootloader- only flash leaves this key untouched (the existing app is still the one in the slot).FIRMWARE_VERSION— when the GBL filename embeds it (currently NCP and Router carry the EmberZNet version). Absent for RCP and OT-RCP — for those, the meaningful version lives host-side (zigbeedfor RCP,ot-br-posixfor OT-RCP).FIRMWARE_BAUD— the chip's UART baud as configured at last flash. Single source of truth: bothS50uart_bridge(Zigbee) andS70otbr(OTBR) read this same key, since a working UART link forces both ends to the same baud. If you ever set it to something the chip isn't actually running at, the host-side daemons can't reach the chip — fix it by re-runningflash_efr32.shor by editingradio.confto match.BOOTLOADER_VERSION— Gecko Bootloader Stage-2 version (e.g.2.4.2) as reported byuniversal-silabs-flasherduring the last flash. Both bootloader-only and app flashes refresh this — USF transits the bootloader to upload either kind of GBL, and logs its version on the way through.
If the chip happens to be sitting in the Gecko Bootloader (empty or
corrupt application slot), FIRMWARE may be stale — the actual
runtime state is detected by flash_efr32.sh's pre-flight probe.
Switching Radio Mode¶
To switch between modes on a running gateway:
Thread → Zigbee:
# Reflash EFR32 with NCP firmware (from your workstation)
./flash_efr32.sh -y ncp # default IP 192.168.1.88
# or: ./flash_efr32.sh -y -g 10.0.0.5 ncp # custom IP
# That's it. The script stops otbr-agent if running, flashes the new
# firmware, writes FIRMWARE_BAUD=<baud> to /userdata/etc/radio.conf
# (no MODE= line → S50uart_bridge takes over instead of S70otbr),
# then reboots.
Zigbee → Thread:
# Reflash EFR32 with OT-RCP firmware
./flash_efr32.sh -y otrcp
# That's it. The script stops the bridge daemons, flashes OT-RCP,
# writes MODE=otbr + FIRMWARE_BAUD=460800 to radio.conf so S70otbr
# launches otbr-agent on next boot, then reboots.
Pre-v3.1 manual approach (still works, no longer needed): edit
radio.confby hand before flashing. Since v3.1,flash_efr32.shhandles both the chip flash AND the gateway-sideradio.confrewrite in one shot.
Alternatively, reflash the entire userdata partition with
flash_userdata.sh — its prompt sets the radio mode at flash time
(useful for a fresh install).
SSH Passwordless Access¶
The /userdata/ssh/authorized_keys file allows SSH access without a password. Add your public key to this file:
# On your workstation, copy your public key
cat ~/.ssh/id_rsa.pub
# Add it to authorized_keys on the gateway
echo "ssh-rsa AAAA... user@host" >> /userdata/ssh/authorized_keys
Dropbear is configured to read this file, enabling secure key-based authentication.
Contents¶
| Directory/File | Description |
|---|---|
skeleton/ |
Base structure for the user partition |
nano/ |
GNU nano text editor build |
ot-br-posix/ |
OpenThread Border Router build |
build_userdata.sh |
Script to assemble and package the partition |
The Zigbee UART↔TCP bridge is now in-kernel (rtl8196e-uart-bridge, part of
the 6.18 kernel tree — see ../32-Kernel/files-6.18/drivers/net/rtl8196e-uart-bridge/).
No userspace component to build here.
Building¶
# Build nano (optional)
cd nano && ./build_nano.sh && cd ..
# Build otbr-agent (optional, for Thread mode)
cd ot-br-posix && ./build_otbr.sh && cd ..
# Assemble and package userdata
./build_userdata.sh
Output¶
userdata.bin— Flashable JFFS2 image with Realtek header (~12 MB)
Included Applications¶
nano¶
Lightweight text editor for editing configuration files directly on the gateway.
Terminal support: nano requires terminal capability definitions (terminfo) to display correctly. The profile sets up TERMINFO to point to /userdata/usr/share/terminfo/ which includes definitions for common terminal types:
| Terminal | Use case |
|---|---|
linux |
Direct console access |
vt100 |
Basic serial terminal |
vt102 |
Minicom, PuTTY (VT102 mode) |
xterm |
SSH from modern terminals |
If your terminal emulator uses a different type, you can add the corresponding terminfo file to /userdata/usr/share/terminfo/<first-letter>/<name>.
Terminal size: The profile automatically runs resize at login to detect the terminal dimensions. This ensures nano and other curses applications display at the correct size.
In-kernel UART↔TCP bridge (rtl8196e-uart-bridge)¶
An in-kernel driver (6.18 kernel, CONFIG_RTL8196E_UART_BRIDGE=y)
that exposes the Zigbee UART (/dev/ttyS1) over the network on
TCP:8888. Replaces the former userspace serialgateway daemon from
v3.0. Allows Zigbee2MQTT, Home Assistant ZHA, or other Zigbee
coordinators to communicate with the Silabs EFR32 radio remotely.
Default configuration (writable via sysfs, armed at boot by
S50uart_bridge):
| sysfs param | Default | Description |
|---|---|---|
tty |
/dev/ttyS1 |
TTY device path (root only) |
baud |
460800 via FIRMWARE_BAUD in /userdata/etc/radio.conf |
UART baud rate |
port |
8888 |
TCP listen port (root only) |
bind_addr |
0.0.0.0 |
TCP bind address (root only) |
flow_control |
1 |
0/none, 1/hw (RTS/CTS), 2/sw (XON/XOFF) — set 0 for EFR32 flash; readback is numeric |
enable |
0 → 1 by S50uart_bridge |
1=armed, 0=disarmed |
armed |
read-only | Actual bridge state |
stats |
read-only | Live rx/tx/drop counters |
All under /sys/module/rtl8196e_uart_bridge/parameters/. The table above
is the day-to-day subset; the full parameter list (nrst_pulse,
nrst_gpio, …), the device-tree seeding and the stats reference are in the
bridge README.
Example:
# Change baud rate at runtime (no disarm needed)
echo 230400 > /sys/module/rtl8196e_uart_bridge/parameters/baud
# Read live counters
cat /sys/module/rtl8196e_uart_bridge/parameters/stats
Usage with Zigbee2MQTT:
Usage with Home Assistant ZHA:
Source: ../32-Kernel/files-6.18/drivers/net/rtl8196e-uart-bridge/.
Adding Custom terminfo¶
To add support for additional terminal types:
-
On a Linux system with the desired terminfo, locate the file:
-
Copy it to the gateway:
-
Set the terminal type:
Adding Your Own Applications¶
Cross-compiling applications for the gateway is straightforward. Use the existing build_*.sh scripts as templates:
-
Cross-compile your application using the toolchain:
-
Strip the binary to reduce size:
-
Transfer to the gateway via SSH:
The application is immediately available — no reboot required.
Adding Custom Init Scripts¶
To add a new service that starts at boot:
- Create a script in
/userdata/etc/init.d/with a name likeS70myservice - Make it executable:
chmod +x S70myservice - The script should accept
start,stop, and optionallyrestartarguments
Example:
#!/bin/sh
case "$1" in
start)
echo "Starting myservice..."
/userdata/usr/bin/myservice &
;;
stop)
echo "Stopping myservice..."
killall myservice
;;
restart)
$0 stop
$0 start
;;
esac
The number prefix (S70) determines execution order — lower numbers run first.
Mount Point¶
The userdata partition is mounted at /userdata by the rootfs bootstrap. The rootfs /etc symlinks point here, making this partition the source of truth for all configuration.