|
| 1 | +# WiFi Configuration in EdgeOS |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +EdgeOS includes WiFi support via ConnMan and wpa_supplicant. WiFi is disabled by default for power management and must be explicitly enabled by the user. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +- **ConnMan**: Network manager that provides the WiFi API |
| 10 | +- **wpa_supplicant**: Handles WiFi authentication and connection |
| 11 | +- **wireless-regdb**: Provides regulatory domain database |
| 12 | +- **Udev**: Automatically brings up WiFi interfaces when detected |
| 13 | + |
| 14 | +## Regulatory Domain (Country Code) |
| 15 | + |
| 16 | +### Why It's Required |
| 17 | + |
| 18 | +The Broadcom WiFi driver (brcmfmac) on Raspberry Pi **keeps WiFi soft-blocked** until a valid regulatory domain (country code) is set. This is a regulatory compliance requirement to ensure the device only transmits on frequencies legal in the user's country. |
| 19 | + |
| 20 | +The kernel parameter `ieee80211_regdom` sets the **global default**, but the WiFi PHY device won't actually adopt it until **userspace sends a regulatory hint**. Until then, the PHY stays in the "world domain" (country 99) and remains soft-blocked. |
| 21 | + |
| 22 | +### Default Configuration |
| 23 | + |
| 24 | +EdgeOS sets the regulatory domain via a **udev rule** that runs when the WiFi PHY device appears: |
| 25 | + |
| 26 | +**Udev Rule**: `/etc/udev/rules.d/85-wifi-regdomain.rules` |
| 27 | +``` |
| 28 | +ACTION=="add", SUBSYSTEM=="ieee80211", KERNEL=="phy[0-9]*", \ |
| 29 | + IMPORT{program}="/bin/sh -c 'c=$(cat /etc/regdomain 2>/dev/null || echo US); printf \"COUNTRY=%%s\\n\" \"$c\"'", \ |
| 30 | + RUN+="/usr/sbin/iw reg set $env{COUNTRY}" |
| 31 | +``` |
| 32 | + |
| 33 | +**Note**: |
| 34 | +- Uses `printf` instead of `echo` to output KEY=VALUE format that udev expects |
| 35 | +- `%%s` escapes the `%` for udev (udev uses `%` for its own substitutions) so it passes literal `%s` to the shell |
| 36 | +- You may see a harmless warning about "invalid substitution type" in logs - udev complains about shell syntax it doesn't understand, but still executes correctly |
| 37 | + |
| 38 | +**Country Code File**: `/etc/regdomain` |
| 39 | +``` |
| 40 | +US |
| 41 | +``` |
| 42 | + |
| 43 | +**How it works:** |
| 44 | +- Triggers when WiFi PHY device (phy0) appears during boot |
| 45 | +- Reads country code from `/etc/regdomain` (defaults to US if file missing) |
| 46 | +- Runs `iw reg set <COUNTRY>` to send regulatory hint to kernel |
| 47 | +- ConnMan manages rfkill state (WiFi is disabled by default, enabled by the user) |
| 48 | +- Simple and reliable - no systemd service complexity |
| 49 | + |
| 50 | +**Fallback**: `/etc/modprobe.d/cfg80211.conf` |
| 51 | +``` |
| 52 | +options cfg80211 ieee80211_regdom=US |
| 53 | +``` |
| 54 | + |
| 55 | +This sets the global default, but is not sufficient by itself - the PHY-specific regulatory hint via `iw reg set` is required. |
| 56 | + |
| 57 | +### Provisioning Integration |
| 58 | + |
| 59 | +**The provisioning flow MUST set the correct country code** for the user's location. To do this: |
| 60 | + |
| 61 | +1. **During WiFi setup**, prompt the user for their country code (or derive from timezone/location) |
| 62 | +2. **Update the regulatory domain file**: |
| 63 | + |
| 64 | +```bash |
| 65 | +# Set the country code (e.g., GB, DE, JP, etc.) |
| 66 | +echo GB > /etc/regdomain |
| 67 | +``` |
| 68 | + |
| 69 | +3. **Apply immediately without reboot**: |
| 70 | + |
| 71 | +```bash |
| 72 | +# Set regulatory domain immediately |
| 73 | +iw reg set GB |
| 74 | + |
| 75 | +# Enable WiFi (ConnMan will unblock rfkill automatically) |
| 76 | +connmanctl enable wifi |
| 77 | +``` |
| 78 | + |
| 79 | +**Alternative: Trigger udev to re-apply**: |
| 80 | +```bash |
| 81 | +# Trigger udev to re-read /etc/regdomain and apply |
| 82 | +udevadm trigger -s ieee80211 |
| 83 | + |
| 84 | +# Verify it was applied |
| 85 | +iw reg get |
| 86 | +``` |
| 87 | + |
| 88 | +**Why this is safe:** |
| 89 | +- Writes to a data file (`/etc/regdomain`), not editing udev rule syntax |
| 90 | +- Can't break the udev rule with a typo |
| 91 | +- Udev rule reads this file on every boot and applies it automatically |
| 92 | + |
| 93 | +**Note**: `iw reg set` does NOT persist across reboots - it only sets the regulatory domain at runtime in kernel memory. The udev rule re-reads `/etc/regdomain` and reapplies it on every boot when the WiFi PHY appears. |
| 94 | + |
| 95 | +### Valid Country Codes |
| 96 | + |
| 97 | +Country codes follow ISO 3166-1 alpha-2 standard: |
| 98 | +- `US` - United States |
| 99 | +- `GB` - United Kingdom |
| 100 | +- `DE` - Germany |
| 101 | +- `JP` - Japan |
| 102 | +- `CN` - China |
| 103 | +- `AU` - Australia |
| 104 | +- etc. |
| 105 | + |
| 106 | +See `/lib/firmware/regulatory.db` or wireless-regdb documentation for complete list. |
| 107 | + |
| 108 | +## WiFi Power Management |
| 109 | + |
| 110 | +### Default State and Persistence |
| 111 | + |
| 112 | +WiFi is **disabled by default** on first boot for power savings. This is controlled by `/etc/connman/main.conf` which excludes WiFi from `PreferredTechnologies`. |
| 113 | + |
| 114 | +**After first boot:** |
| 115 | +- User enables WiFi → ConnMan saves `Enable=true` to `/var/lib/connman/settings` |
| 116 | +- WiFi stays enabled across reboots (persisted state) |
| 117 | +- User disables WiFi → ConnMan saves `Enable=false` |
| 118 | +- WiFi stays disabled across reboots (persisted state) |
| 119 | + |
| 120 | +ConnMan naturally persists the user's WiFi preference. |
| 121 | + |
| 122 | +### User Control |
| 123 | + |
| 124 | +Users manage WiFi via ConnMan commands: |
| 125 | + |
| 126 | +```bash |
| 127 | +# Enable WiFi (required before scanning/connecting) |
| 128 | +connmanctl enable wifi |
| 129 | + |
| 130 | +# Disable WiFi (power saving) |
| 131 | +connmanctl disable wifi |
| 132 | + |
| 133 | +# Scan for networks |
| 134 | +connmanctl scan wifi |
| 135 | + |
| 136 | +# List available networks |
| 137 | +connmanctl services |
| 138 | +``` |
| 139 | + |
| 140 | +## Technical Details |
| 141 | + |
| 142 | +### Why `ip link set wlan0 up` is Required |
| 143 | + |
| 144 | +wpa_supplicant only registers interfaces that are **UP** with D-Bus/ConnMan. A udev rule automatically brings up WiFi interfaces when detected: |
| 145 | + |
| 146 | +**File**: `/etc/udev/rules.d/80-wireless.rules` |
| 147 | +``` |
| 148 | +SUBSYSTEM=="net", ACTION=="add", KERNEL=="wlan*", RUN+="/sbin/ip link set %k up" |
| 149 | +``` |
| 150 | + |
| 151 | +### How WiFi Initialization Works |
| 152 | + |
| 153 | +#### Boot Sequence |
| 154 | +1. **Module Load**: cfg80211 module loads with `ieee80211_regdom=US` parameter (sets global default) |
| 155 | +2. **Driver Init**: brcmfmac driver initializes - WiFi PHY appears as phy0 in "world domain" (country 99), registers with rfkill (default: unblocked) |
| 156 | +3. **Udev Event**: ieee80211 subsystem fires ADD event for phy0 |
| 157 | +4. **Udev Rule Triggers**: `85-wifi-regdomain.rules` matches the phy0 device |
| 158 | +5. **Regulatory Hint**: Rule runs `/usr/sbin/iw reg set US` which sends nl80211 regulatory hint to kernel |
| 159 | +6. **Domain Applied**: Kernel cfg80211 applies US regulatory domain to phy0 |
| 160 | +7. **wlan0 appears**: Interface appears, rfkill device registered |
| 161 | +8. **systemd-rfkill restores state**: Reads `/var/lib/systemd/rfkill/platform-*:wlan` and restores saved rfkill block/unblock state |
| 162 | +9. **Migration service** (first boot only): Reads ConnMan settings, sets initial rfkill state |
| 163 | +10. **Udev brings up wlan0**: `80-wireless.rules` runs `ip link set wlan0 up` so wpa_supplicant can register it |
| 164 | +11. **ConnMan Starts**: Reads hardware rfkill state, synchronizes with persisted settings |
| 165 | +12. **WiFi Nudge** (if WiFi enabled): Toggles WiFi to ensure wpa_supplicant registers wlan0 with ConnMan |
| 166 | +13. **WiFi Ready**: Enabled or disabled based on persisted state, fully operational |
| 167 | + |
| 168 | +#### When User Enables/Disables WiFi |
| 169 | +1. **User Command**: `connmanctl enable wifi` or `connmanctl disable wifi` |
| 170 | +2. **ConnMan Action**: Automatically manages rfkill (unblock/block) |
| 171 | +3. **systemd-rfkill saves state**: Monitors `/dev/rfkill` and persists block/unblock state to `/var/lib/systemd/rfkill/platform-*:wlan` |
| 172 | +4. **ConnMan synchronizes**: Updates `Enable=true/false` in `/var/lib/connman/settings` to match rfkill state |
| 173 | +5. **Next Boot**: systemd-rfkill restores rfkill state → ConnMan reads it → WiFi state persisted |
| 174 | + |
| 175 | +### WiFi State Persistence |
| 176 | + |
| 177 | +**systemd-rfkill**: Persists WiFi enable/disable state across reboots |
| 178 | + |
| 179 | +EdgeOS uses the standard `systemd-rfkill` service to persist WiFi radio state: |
| 180 | +- When user enables WiFi → ConnMan unblocks rfkill → systemd-rfkill saves state to `/var/lib/systemd/rfkill/platform-*:wlan` |
| 181 | +- When user disables WiFi → ConnMan blocks rfkill → systemd-rfkill saves state |
| 182 | +- On boot → systemd-rfkill restores the saved rfkill state before ConnMan starts |
| 183 | +- ConnMan reads the hardware rfkill state and synchronizes its settings |
| 184 | + |
| 185 | +**Migration Service**: `wifi-rfkill-migrate.service` |
| 186 | + |
| 187 | +One-time service that runs on first boot to migrate existing ConnMan WiFi policy into rfkill state: |
| 188 | +- Reads `[WiFi] Enable=true/false` from `/var/lib/connman/settings` |
| 189 | +- Sets initial rfkill state accordingly |
| 190 | +- Creates marker file `/var/lib/systemd/wifi-rfkill-migrated` to prevent re-running |
| 191 | +- Runs before ConnMan to ensure correct initial state |
| 192 | + |
| 193 | +**WiFi Registration Nudge**: `wifi-nudge.service` |
| 194 | + |
| 195 | +Ensures wpa_supplicant properly registers wlan0 with ConnMan on boot when WiFi is enabled: |
| 196 | +- Runs after ConnMan starts |
| 197 | +- If WiFi is enabled, performs a disable/enable cycle to ensure wpa_supplicant registration |
| 198 | +- Harmless if WiFi is disabled (skips the toggle) |
| 199 | +- Fixes race where wpa_supplicant doesn't register wlan0 before ConnMan checks |
| 200 | + |
| 201 | +### Key Insights |
| 202 | + |
| 203 | +✅ **PHY-aware timing**: udev rule runs when PHY actually exists, not "early" before it appears |
| 204 | + |
| 205 | +✅ **Standard persistence**: Uses systemd-rfkill (standard Linux component) instead of custom solution |
| 206 | + |
| 207 | +✅ **Clean separation**: udev owns regulatory domain, systemd-rfkill owns radio state persistence, ConnMan owns network management |
| 208 | + |
| 209 | +✅ **No USB gadget interference**: ConnMan starts immediately for USB gadget networking; WiFi services run in parallel |
| 210 | + |
| 211 | +✅ **Robust state handling**: rfkill is the single source of truth; ConnMan synchronizes to match hardware state |
| 212 | + |
| 213 | +### Previous Failed Approaches |
| 214 | + |
| 215 | +❌ **Kernel parameter `rfkill.default_state=1`**: Doesn't work - brcmfmac driver ignores it |
| 216 | + |
| 217 | +❌ **Relying solely on `ieee80211_regdom` modprobe parameter**: Sets global default but WiFi PHY won't adopt it without userspace regulatory hint |
| 218 | + |
| 219 | +❌ **Running `iw reg set` too early**: PHY doesn't exist yet, hint goes nowhere |
| 220 | + |
| 221 | +❌ **wpa_supplicant country= with ConnMan**: wpa_supplicant runs in `-u` mode (D-Bus), won't read config file until ConnMan tells it to manage an interface (but WiFi is blocked so it can't) |
| 222 | + |
| 223 | +❌ **Complex systemd services**: Early services can interfere with USB gadget and other boot processes |
| 224 | + |
| 225 | +## Edge Cases and Considerations |
| 226 | + |
| 227 | +### Multiple WiFi PHYs (USB Dongles) |
| 228 | + |
| 229 | +The udev rule runs **per-PHY** automatically. If you plug in a USB WiFi dongle, it will get its own phy1 device and the regulatory domain will be set for it as well. This is the correct behavior. |
| 230 | + |
| 231 | +### Country Code Provisioning |
| 232 | + |
| 233 | +The recommended approach for setting the country code during provisioning: |
| 234 | + |
| 235 | +```bash |
| 236 | +# Update the country code file |
| 237 | +echo GB > /etc/regdomain |
| 238 | + |
| 239 | +# Trigger udev to re-apply immediately |
| 240 | +udevadm trigger -s ieee80211 |
| 241 | + |
| 242 | +# Verify it was applied |
| 243 | +iw reg get |
| 244 | +``` |
| 245 | + |
| 246 | +This avoids manual `iw reg set` commands and ensures the setting persists across reboots. |
| 247 | + |
| 248 | +### systemd-rfkill State Files |
| 249 | + |
| 250 | +EdgeOS uses systemd-rfkill to persist WiFi radio state across reboots. State files are stored in `/var/lib/systemd/rfkill/`: |
| 251 | +- `platform-*:wlan` - WiFi rfkill state (0 = unblocked, 1 = blocked) |
| 252 | +- `platform-*:bluetooth` - Bluetooth rfkill state |
| 253 | + |
| 254 | +**Do not manually edit these files.** They are managed automatically by systemd-rfkill when ConnMan toggles the radio state. |
| 255 | + |
| 256 | +### Alternative Network Managers |
| 257 | + |
| 258 | +If you ever switch from ConnMan to standalone wpa_supplicant (with `wpa_supplicant@wlan0.service`), you'll need to: |
| 259 | + |
| 260 | +1. Add `country=XX` to `/etc/wpa_supplicant/wpa_supplicant-wlan0.conf` |
| 261 | +2. Disable ConnMan's WiFi management |
| 262 | + |
| 263 | +With ConnMan's `-u` D-Bus mode, the wpa_supplicant config file is ignored. |
| 264 | + |
| 265 | +### Broadcom Firmware Requirements (Raspberry Pi) |
| 266 | + |
| 267 | +Broadcom WiFi (brcmfmac) requires matching firmware files: |
| 268 | + |
| 269 | +- Main firmware: `/lib/firmware/brcm/brcmfmac43455-sdio.bin` (or similar for your chip) |
| 270 | +- CLM blob: `/lib/firmware/brcm/brcmfmac43455-sdio.clm_blob` (contains per-country channel/power limits) |
| 271 | + |
| 272 | +If the CLM blob is missing, WiFi may fail to initialize or have limited channel access. The `wireless-regdb` package provides the general regulatory database, but Broadcom needs its own CLM blob. |
| 273 | + |
| 274 | +## Troubleshooting |
| 275 | + |
| 276 | +### WiFi is soft-blocked (expected behavior) |
| 277 | + |
| 278 | +WiFi is **soft-blocked by default** for power saving. This is normal! To enable WiFi: |
| 279 | + |
| 280 | +```bash |
| 281 | +connmanctl enable wifi |
| 282 | +``` |
| 283 | + |
| 284 | +Check the regulatory domain: |
| 285 | +```bash |
| 286 | +iw reg get |
| 287 | +``` |
| 288 | + |
| 289 | +Should show: |
| 290 | +``` |
| 291 | +global |
| 292 | +country US: DFS-FCC |
| 293 | +``` |
| 294 | + |
| 295 | +If it shows `country 00` or `country 99`, the regulatory domain is not set. Check if the udev rule executed: |
| 296 | + |
| 297 | +```bash |
| 298 | +# Test the udev rule manually |
| 299 | +udevadm test --action=add /sys/class/ieee80211/phy0 |
| 300 | + |
| 301 | +# Check boot logs for udev rule execution |
| 302 | +journalctl -b | grep -E 'ieee80211|iw reg' |
| 303 | +``` |
| 304 | + |
| 305 | +### WiFi scan returns "Not implemented" |
| 306 | + |
| 307 | +ConnMan is not built with WiFi support. Check: |
| 308 | +```bash |
| 309 | +connmanctl technologies |
| 310 | +``` |
| 311 | + |
| 312 | +Should list `/net/connman/technology/wifi` - if missing, `wifi` is not in DISTRO_FEATURES. |
| 313 | + |
| 314 | +### wlan0 interface doesn't appear |
| 315 | + |
| 316 | +Check if wireless firmware is loaded: |
| 317 | +```bash |
| 318 | +dmesg | grep brcmfmac |
| 319 | +``` |
| 320 | + |
| 321 | +Should see firmware loading successfully. |
| 322 | + |
| 323 | +### WiFi scanning works but can't connect |
| 324 | + |
| 325 | +Check wpa_supplicant is running with D-Bus support: |
| 326 | +```bash |
| 327 | +ps aux | grep wpa_supplicant |
| 328 | +``` |
| 329 | + |
| 330 | +Should see `-u` flag (D-Bus mode). |
| 331 | + |
| 332 | +### Missing regulatory database or firmware |
| 333 | + |
| 334 | +For Raspberry Pi Broadcom WiFi: |
| 335 | + |
| 336 | +```bash |
| 337 | +# Check regulatory database |
| 338 | +ls -la /lib/firmware/regulatory.db |
| 339 | + |
| 340 | +# Check Broadcom firmware and CLM blob |
| 341 | +ls -la /lib/firmware/brcm/ | grep -E 'brcmfmac.*\.(bin|txt|clm_blob)' |
| 342 | + |
| 343 | +# Check kernel messages for firmware loading |
| 344 | +dmesg | grep -i firmware |
| 345 | +``` |
| 346 | + |
| 347 | +Required packages: `wireless-regdb`, `linux-firmware-bcm43455` (or equivalent for your hardware). |
| 348 | + |
| 349 | +## Build Configuration |
| 350 | + |
| 351 | +WiFi support is controlled by these Yocto variables: |
| 352 | + |
| 353 | +**In `conf/distro/edgeos.conf`**: |
| 354 | +```bitbake |
| 355 | +DISTRO_FEATURES:append = " wifi" |
| 356 | +``` |
| 357 | + |
| 358 | +**In `recipes-connectivity/connman/connman_%.bbappend`**: |
| 359 | +```bitbake |
| 360 | +PACKAGECONFIG:append = " wifi" |
| 361 | +``` |
| 362 | + |
| 363 | +## Recipe Files |
| 364 | + |
| 365 | +- `meta-edgeos/recipes-connectivity/connman/connman_%.bbappend` - ConnMan WiFi configuration, udev rules, and regulatory domain setup |
| 366 | +- `meta-edgeos/recipes-core/packagegroups/packagegroup-edgeos-wifi.bb` - WiFi package group |
| 367 | + |
| 368 | +## References |
| 369 | + |
| 370 | +- [ConnMan Documentation](https://git.kernel.org/pub/scm/network/connman/connman.git/about/) |
| 371 | +- [wpa_supplicant D-Bus API](https://w1.fi/wpa_supplicant/devel/dbus.html) |
| 372 | +- [Linux Wireless Regulatory Documentation](https://wireless.wiki.kernel.org/en/developers/regulatory) |
| 373 | +- [systemd-rfkill](https://www.freedesktop.org/software/systemd/man/systemd-rfkill.service.html) |
0 commit comments