--- type: report date: 2026-10-07 --- # SIM7600G-H-M.2 (LTE + GNSS) Setup on Jetson Orin Nano ## Result | Item | State | |------|-------| | LTE data via ModemManager + NetworkManager (QMI, `wwan0`) | Working: IPv4 + IPv6, ~9 Mbit/s DL observed | | Route priority: Ethernet (100) > WiFi (600) > LTE (1000) | Working | | GNSS engine + gpsd on `/dev/sim7600-gps` | **Working**: first 3D fix 2026-10-09 in the car (indoors: satellites seen, no fix) | ## Hardware - **Board:** Jetson Orin Nano, hostname `mikkeli-orin-nano`, kernel `5.15.148-tegra` (L4T 36.4.4) - **Modem:** Waveshare SIM7600G-H-M.2 on the SIM7600G-H-M2 4G HAT, connected over USB - USB ID `1e0e:9001` (default composition, `AT+CUSBPIDSWITCH?` → `9001`) - Firmware `LE20B04SIM7600G-H-M2` / `SIM7600G_V2.0.2` - **SIM:** povo (KDDI/au MVNO), APN `povo.jp`, no user/password. Registers as `KDDI KDDI` (44051), LTE band 18. ### USB interfaces (PID 9001) | Iface | Port (typical) | Driver | Role | |-------|------|--------|------| | 0 | ttyUSB0 | option | diagnostics (ignored by ModemManager) | | 1 | ttyUSB1 | option | **NMEA GPS** → `/dev/sim7600-gps` (gpsd) | | 2 | ttyUSB2 | option | AT, used by ModemManager | | 3 | ttyUSB3 | option | AT, **reserved for us** → `/dev/sim7600-at`, ModemManager told to ignore it | | 4 | ttyUSB4 | option | audio | | 5 | `cdc-wdm0` + `wwan0` | **qmi_wwan** (self-built) | QMI control + network | ⚠ The `ttyUSB` numbers are **not** stable: after a modem reset they shifted (e.g. NMEA on ttyUSB2, AT on ttyUSB3/4) because the old nodes were still held open. Use the `/dev/sim7600-*` symlinks or `/dev/serial/by-id/*-ifNN-port0`, never a bare `ttyUSBn`. ## Problems hit and fixes ### 1. `sim-missing`: SIM inserted after the modem powered up ModemManager reported `failed reason: sim-missing`, but `AT+CPIN?` returned `READY`. SIM hot-swap is off on this module (`AT+UIMHOTSWAPON: 0`), so a SIM seated after power-on isn't noticed until the modem re-initializes. Restarting ModemManager (or re-plugging the modem) fixes it. Hot-swap was deliberately left off. Turning it on needs the right `AT+UIMHOTSWAPLEVEL` for the HAT's SIM-detect wiring, and getting that wrong makes the modem report "no SIM" permanently. **Insert the SIM before powering the modem.** ### 2. Tegra kernel has no `qmi_wwan` / `cdc-wdm` `CONFIG_USB_NET_QMI_WWAN` and `CONFIG_USB_WDM` are unset in the stock L4T kernel, so interface 5 had no driver, leaving PPP over a ttyUSB port as the only data path. Fixed by building both modules out of tree from upstream stable `v5.15.148` (see [rebuild](#rebuilding-qmi_wwan-after-a-kernel-update)). ### 3. PPP dial causes `NW DETACH` on KDDI With PPP, every `ATD*99***#` ended about 32 s later with `NO CARRIER` + `+CGEV: NW DETACH`, `+CEER: No service available`. Two causes: - PDP context 1 (the LTE **initial-attach** APN) was preset to `au.au-net.ne.jp`, which a povo SIM can't use, and ModemManager put `povo.jp` on a new context 5. Fixed by setting context 1 to `povo.jp` (see below). After that the attach itself brings up a default bearer with an address. - Even with that fixed, a PPP dial on context 1 still triggered a network detach: PPP tries to activate a context that is already the active default bearer. **QMI does not have this problem**: it uses the existing default bearer. Do not use PPP with this SIM. Setting the attach APN (with ModemManager stopped; the setting persists in modem NVRAM): ``` AT+CFUN=4 # radio off, SIM stays powered (CFUN=0 powers the SIM down → CGDCONT returns ERROR) AT+CGDCONT=1,"IPV4V6","povo.jp" AT+CGDCONT=5 # remove stray contexts ModemManager had created AT+CFUN=1 AT+CGPADDR=1 # should now show an IPv4 + IPv6 address ``` ## Configuration applied ### Kernel modules Source + Makefile live in `~/infra/qmi_wwan/` (per [GUIDELINES §1](../../GUIDELINES.md)): ``` ~/infra/qmi_wwan/ ├── Makefile # obj-m += cdc-wdm.o qmi_wwan.o ; `make install` → /lib/modules/$(uname -r)/misc/ + depmod ├── cdc-wdm.c # linux-stable v5.15.148 drivers/usb/class/cdc-wdm.c └── qmi_wwan.c # linux-stable v5.15.148 drivers/net/usb/qmi_wwan.c (has QMI_QUIRK_SET_DTR(0x1e0e, 0x9001, 5)) ``` Installed to `/lib/modules/5.15.148-tegra/misc/{cdc-wdm,qmi_wwan}.ko`. They auto-load through the USB modalias after `depmod`, so no `/etc/modules` entry is needed. ### NetworkManager connection `povo` ``` sudo nmcli con add type gsm ifname '*' con-name povo gsm.apn povo.jp \ connection.autoconnect yes connection.autoconnect-retries 0 \ ipv4.route-metric 1000 ipv6.route-metric 1000 ipv6.method auto ``` - `ifname '*'`: binds to whichever modem ModemManager exposes (now `cdc-wdm0`). - `connection.metered yes` (added 2026-10-08): marks LTE as metered so bulk transfers can refuse it. Project Yukart's map-tile sync uses this as its second guard (LAN-only rsync). - `autoconnect-retries 0` (forever): the default of 4 retries left the link permanently down after early failures. In a car, the modem may come up after NetworkManager. - Metric 1000 vs NM defaults Ethernet 100 / WiFi 600 → LTE only carries traffic when both are gone. Right after connecting the route can briefly show metric 21000 (NM's +20000 penalty until its connectivity check passes); it settles to 1000. Check: ``` mmcli -m any # state: connected, primary port: cdc-wdm0 ip route show default # wlP1p1s0 metric 600, wwan0 metric 1000 curl --interface wwan0 https://ifconfig.me ``` ### Failover test (2026-10-08) Ran `/usr/local/sbin/lte-failover-test.sh` as a detached unit (`systemd-run --unit=lte-failover-test`). It turns the WiFi radio off for 180 s, samples every 10 s, then turns it back on, logging to `/var/log/lte-failover-test.log`. A `wifi-failsafe` timer (`systemd-run --on-active=10min nmcli radio wifi on`) was armed first, so a broken LTE path couldn't lock out a remote session. | Phase | Default route | Public IPv4 | Ping 1.1.1.1 | Tailscale → halogen | |-------|---------------|-------------|--------------|---------------------| | WiFi on | `wlP1p1s0` (600) | home ISP | ~12 ms | direct, LAN (3–4 ms) | | WiFi off | `wwan0` (1000), within 10 s | KDDI `106.128.x.x` | 25–55 ms | **direct over IPv6** to halogen's home IPv6 (40–85 ms), no DERP relay | | WiFi back | `wlP1p1s0`; IPv4 within 15 s, IPv6 within ~45 s (NM connectivity check) | home ISP | ~12 ms | direct, LAN | Zero failed samples (IPv4, DNS, Tailscale ping). An SSH session over Tailscale stayed up across both switches. Carrier CGNAT on IPv4 doesn't matter because povo gives a global IPv6 address, which Tailscale uses for a direct path. Note: `wwan0` also gets a kernel-installed IPv6 RA default route at metric 1024. That still ranks below WiFi (600) and only wins during the brief NM connectivity-check penalty (`+20000`) after WiFi returns. ### GNSS - GNSS engine was already running (`AT+CGPS?` → `1,1`). Set to start on boot: `AT+CGPSAUTO=1` (persists in modem). - ModemManager location is **not** used. gpsd owns the NMEA port; ModemManager only labels it `(gps)` and leaves it unopened. - Stable device names, `/etc/udev/rules.d/99-sim7600-gps.rules`: ``` # if01: NMEA. gpsd.service follows this device (drop-in BindsTo), so it restarts with a fresh handle after a modem reset. SUBSYSTEM=="tty", ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{ID_USB_INTERFACE_NUM}=="01", SYMLINK+="sim7600-gps", TAG+="systemd", ENV{SYSTEMD_WANTS}+="gpsd.service" # if03: spare AT port, reserved for us (ModemManager keeps if02). Powers the active GNSS antenna on every modem start. SUBSYSTEM=="tty", ATTRS{idVendor}=="1e0e", ATTRS{idProduct}=="9001", ENV{ID_USB_INTERFACE_NUM}=="03", SYMLINK+="sim7600-at", ENV{ID_MM_PORT_IGNORE}="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sim7600-gnss-antenna.service" ``` - `apt install gpsd gpsd-clients`, `/etc/default/gpsd`: ``` DEVICES="/dev/sim7600-gps" GPSD_OPTIONS="-n" USBAUTO="false" # hotplug would let gpsd probe the modem's AT ports and conflict with ModemManager ``` - `systemctl enable --now gpsd.socket gpsd` - `/etc/systemd/system/gpsd.service.d/10-sim7600.conf`: `BindsTo=` / `After=dev-sim7600\x2dgps.device`. gpsd stops when the NMEA port vanishes (modem reset, unplug) and the udev rule starts it again when the port returns. An earlier `RUN+="gpsdctl add …"` hook was dropped: at boot it fired before `gpsd.service`, so `gpsdctl` spawned its own gpsd **without `-n`**, which then held port 2947 and kept the real service from ever starting. Check: `cgps` or `gpspipe -w -n 10`. On 2026-10-07, tested indoors, gpsd saw `nSat: 10, uSat: 0`, `mode: 1` (no fix). Next: test with the GNSS antenna (IPEX, labeled GNSS on the HAT) with a clear view of the sky. A cold start can take a few minutes. **2026-10-09, in the car:** first fix, `mode: 3` (3D) with 4 of 9 satellites used, GPS time valid. Passive GNSS antenna on the dashboard, facing the sky. No setup changes were needed; the indoor tests simply lacked sky view. #### Active antenna (2026-10-09) Swapped to an active GNSS antenna (spec 2.7–5.0 V) on the car roof. At first it **received nothing** (8 of 9 satellites at 0 dB-Hz): the modem's antenna supply was off. `AT+CVAUXS?` → `0`, `AT+CVAUXV?` → `1700` (mV, below the antenna's minimum). ``` AT+CVAUXV=3050 # modem range 1700–3050 mV; 3.05 V is inside the antenna's 2.7–5.0 V AT+CVAUXS=1 # switch the supply on → fix back within 1 s ``` | | Passive, dashboard | Active, roof (powered) | |---|---|---| | Satellites used | 6.3 avg (max 7) | 6.5 avg (max 7) | | Signal of used satellites | 31.3 dB-Hz | **36.8 dB-Hz** (peaks 43–47) | Satellites used barely change; that count looks limited by which constellations are enabled (see below). The gain is ~5.5 dB of margin, which is what matters under trees and between buildings. **Both settings are volatile.** They reset to off/1.7 V on every modem restart (`AT+CRESET` tested), and `AT&W` does not save them. So: - `/etc/default/sim7600-gnss-antenna`: `ANTENNA=active|passive`, `CVAUXV_MV=3050`. - `/usr/local/sbin/sim7600-gnss-antenna` reads the modem's state over `/dev/sim7600-at` and writes only if it differs from the config (`passive` keeps the supply **off**). It logs only actual changes. - `sim7600-gnss-antenna.service` (oneshot, `LogLevelMax=notice`) runs it when interface 03 appears (udev), **and** `sim7600-gnss-antenna.timer` re-runs it every 30 s. The timer is needed: one modem reset showed the modem clearing the supply again **after** the service had set and verified it (most likely ModemManager bringing the radio up). With the timer, any reset is corrected within ~30 s (tested: supply switched off by hand at 19:13:59, restored 19:14:34). Full recovery after `mmcli -m any --reset` (tested twice): LTE back in ~30–50 s, antenna supply back on, gpsd restarted with the port. ⚠ **Passive antenna: set `ANTENNA=passive` before connecting it**, then `sudo systemctl start sim7600-gnss-antenna`. Many passive GNSS antennas are a DC short to ground (inductive or shorted-patch feeds), and `CVAUX` is a general-purpose modem output, not a dedicated antenna supply with documented short-circuit protection. With nothing connected the supply is harmless. #### Constellations (2026-10-09) `AT+CGNSSMODE=<0-15>,` sets the satellite systems on top of GPS. The default `3` is GPS+GLONASS+BeiDou (per SIMCom's GNSS application note), which fits bit 0 = GLONASS, bit 1 = BeiDou. The note lists Galileo and QZSS next, so bits 2–3 are most likely those, but it doesn't define the bits explicitly. ``` AT+CGPS=0 AT+CGNSSMODE=15,1 # all four: GLONASS, BeiDou, Galileo, QZSS (Japan's regional system, "Michibiki") AT+CGPSNMEA=262143 # all NMEA sentence types; the modem keeps only those it supports: reads back 198143 AT+CGPS=1 ``` Both settings **persist across a modem reset** (checked), unlike the antenna supply. **Not yet verified on sky:** whether Galileo/QZSS satellites actually appear (look for Galileo/QZSS PRNs in `gpspipe -w` SKY, or `GA`/`GQ` NMEA talkers). Indoors with no antenna nothing is tracked. No AT command for SBAS/MSAS (Japan's augmentation service) appears in the application note. ## Rebuilding qmi_wwan after a kernel update Out-of-tree modules do not survive a kernel update. Signs: ModemManager shows `primary port: ttyUSB2` and there is no `/dev/cdc-wdm0`. ``` cd ~/infra/qmi_wwan # if the kernel's upstream base version changed, refresh the sources: # V=v5.15.xxx; for f in drivers/net/usb/qmi_wwan.c drivers/usb/class/cdc-wdm.c; do # curl -fL -o $(basename $f) "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/$f?h=$V"; done make clean && make && sudo make install sudo modprobe qmi_wwan && sudo systemctl restart ModemManager ``` ## Useful one-offs - Talk to the modem directly: use **`/dev/sim7600-at`**. ModemManager ignores it, so there's no need to stop anything. One-shot: `sudo sim7600-at 'AT+CGPS?' 'AT+CVAUXS?'` (`/usr/local/sbin/sim7600-at`; filters NMEA noise). Interactive: `picocom -b 115200 /dev/sim7600-at`. Don't send commands that change the data connection behind ModemManager's back. - Verbose ModemManager log incl. AT traffic: `sudo mmcli -G debug` then `journalctl -u ModemManager -f` (resets on restart).