Time: chrony replaces timesyncd, with NICT/JP-pool NTP primary and gpsd SHM as an offline-only refclock; makestep any time and fake-hwclock cover the battery-less RTC. Antenna: the modem cleared the supply again after the service set it, so the service is now idempotent, configurable (ANTENNA=active|passive, passive keeps it off) and re-checked by a 30 s timer. gpsd: a gpsdctl udev hook spawned a stray gpsd without -n at boot that blocked the real service; gpsd.service now binds to the NMEA device. GNSS: CGNSSMODE=15 (GLONASS, BeiDou, Galileo, QZSS) and all supported NMEA sentences; both persist across a modem reset. Adds sim7600-at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 KiB
type, date
| type | date |
|---|---|
| report | 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, kernel5.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
- USB ID
- SIM: povo (KDDI/au MVNO), APN
povo.jp, no user/password. Registers asKDDI 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).
3. PPP dial causes NW DETACH on KDDI
With PPP, every ATD*99***<cid># 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 putpovo.jpon a new context 5. Fixed by setting context 1 topovo.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):
~/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 (nowcdc-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 ModemManagersystemctl 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 earlierRUN+="gpsdctl add …"hook was dropped: at boot it fired beforegpsd.service, sogpsdctlspawned 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-antennareads the modem's state over/dev/sim7600-atand writes only if it differs from the config (passivekeeps the supply off). It logs only actual changes.sim7600-gnss-antenna.service(oneshot,LogLevelMax=notice) runs it when interface 03 appears (udev), andsim7600-gnss-antenna.timerre-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>,<dpo> 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 debugthenjournalctl -u ModemManager -f(resets on restart).