241 lines
9.7 KiB
YAML
241 lines
9.7 KiB
YAML
---
|
|
# Configures the embedded BroadMobi BM806C LTE modem for QMI use.
|
|
#
|
|
# Two workarounds are applied here for documented bugs in this modem's firmware
|
|
# (M1.2.0_E1.0.1_A1.1.8, no public updates available):
|
|
#
|
|
# 1. raw-ip framing. The modem advertises 802.3 support but the 802.3 firmware
|
|
# path is buggy — downlink frames don't reach the host. raw-ip works.
|
|
# See bmork's kernel commit "net: qmi_wwan: support 'raw IP' mode":
|
|
# 'newer generations of QMI hardware and firmware have moved towards
|
|
# defaulting to raw IP mode instead, followed by an increasing number of
|
|
# bugs in the already buggy 802.3 firmware implementation'.
|
|
# qmi.sh hardcodes 802.3; we patch it in-place to use raw-ip.
|
|
#
|
|
# 2. --profile instead of --apn on --start-network. With --apn, the modem
|
|
# establishes a phantom bearer that has no working downlink data path.
|
|
# With --profile referencing a pre-configured NVRAM profile, data flows.
|
|
# See https://forum.openwrt.org/t/problem-with-bm806u-e1-dwr-921-c3/130094
|
|
# and https://github.com/openwrt/openwrt/issues/6295 (FS#1363).
|
|
# qmi.sh already supports the 'profile' UCI option; we set it in the
|
|
# wwan interface config (profile=1 for IPv4, v6profile=2 for IPv6).
|
|
# qmi.sh's --modify-profile call writes profile 1; profile 2 is bootstrapped
|
|
# here for the IPv6-specific APN.
|
|
|
|
# Patch qmi.sh to request raw-ip framing instead of 802.3. Two distinct uqmi
|
|
# calls in the upstream proto handler request the data format — both must be
|
|
# patched for the readback to return raw-ip and trigger the kernel driver's
|
|
# sysfs raw_ip=Y flip. Idempotent: lineinfile is a no-op once the patterns
|
|
# no longer match.
|
|
# Diff output is suppressed for these tasks: the full qmi.sh file (~13 KB) is
|
|
# included in both before/after diff payloads, and ansible's SSH stdout parser
|
|
# truncates the resulting JSON, causing 'Module result deserialization failed:
|
|
# No end of json char found'. The change is self-evident from the task name.
|
|
- name: Patch qmi.sh — set-data-format to raw-ip
|
|
community.openwrt.lineinfile:
|
|
path: /lib/netifd/proto/qmi.sh
|
|
regex: '^(\s*uqmi .* --set-data-format) 802\.3(.*)$'
|
|
line: '\1 raw-ip\2'
|
|
backrefs: true
|
|
diff: false
|
|
|
|
- name: Patch qmi.sh — wda-set-data-format to raw-ip
|
|
community.openwrt.lineinfile:
|
|
path: /lib/netifd/proto/qmi.sh
|
|
regex: '^(\s*uqmi .* --wda-set-data-format) 802\.3(.*)$'
|
|
line: '\1 raw-ip\2'
|
|
backrefs: true
|
|
diff: false
|
|
|
|
# The kernel rejects writes to /sys/class/net/wwan0/qmi/raw_ip while the netdev
|
|
# is up: 'Cannot change a running device' (-EBUSY). qmi.sh tries to flip it
|
|
# while the interface is up — this works in 802.3 mode (no-op when already N),
|
|
# but with our raw-ip patch above, the flip is mandatory and must succeed.
|
|
# We bracket the sysfs write with ip link down/up.
|
|
- name: Patch qmi.sh — bracket raw_ip sysfs write with ip link down/up
|
|
community.openwrt.lineinfile:
|
|
path: /lib/netifd/proto/qmi.sh
|
|
regex: '^(\s*)echo "Y" > /sys/class/net/\$ifname/qmi/raw_ip$'
|
|
line: '\1ip link set $ifname down; echo "Y" > /sys/class/net/$ifname/qmi/raw_ip; ip link set $ifname up'
|
|
backrefs: true
|
|
diff: false
|
|
|
|
# Profile 2 in modem NVRAM holds the IPv6 APN. qmi.sh only manages profile 1
|
|
# (the v4 APN via --modify-profile, line 314); profile 2 is our responsibility.
|
|
# These steps are skipped if the modem isn't enumerated yet (fresh boot before
|
|
# usb-modeswitch completes, or modem in a fault state).
|
|
- name: Check if QMI device is available
|
|
community.openwrt.stat:
|
|
path: /dev/cdc-wdm0
|
|
register: wwan_cdc_wdm
|
|
|
|
- name: Query QMI profile list
|
|
community.openwrt.command:
|
|
cmd: uqmi -t 3000 -d /dev/cdc-wdm0 --get-profile-list 3gpp
|
|
register: wwan_profile_list
|
|
changed_when: false
|
|
failed_when: false
|
|
when: wwan_cdc_wdm.stat.exists | default(false)
|
|
|
|
- name: Configure IPv6 APN profile 2
|
|
when:
|
|
- wwan_cdc_wdm.stat.exists | default(false)
|
|
- wwan_profile_list.rc | default(1) == 0
|
|
- wwan_profile_list.stdout | default('') | trim | length > 0
|
|
- wwan_profile_list.stdout | default('') | trim is match('^\\{')
|
|
block:
|
|
- name: Parse profile indexes
|
|
ansible.builtin.set_fact:
|
|
wwan_profile_indexes: >-
|
|
{{ (wwan_profile_list.stdout | from_json).profiles
|
|
| default([]) | map(attribute='index') | list }}
|
|
|
|
- name: Create profile 2 for IPv6 APN if missing
|
|
community.openwrt.command:
|
|
cmd: uqmi -t 3000 -d /dev/cdc-wdm0 --create-profile 3gpp --apn internetipv6 --pdp-type ipv6
|
|
when: 2 not in wwan_profile_indexes
|
|
|
|
# --modify-profile is idempotent at the modem level. We can't detect
|
|
# whether values changed (uqmi doesn't return diff info), so we always
|
|
# report 'ok' (changed_when: false) to keep play output clean. The cost
|
|
# of always calling this is one QMI roundtrip.
|
|
- name: Ensure profile 2 settings are current
|
|
community.openwrt.command:
|
|
cmd: uqmi -t 3000 -d /dev/cdc-wdm0 --modify-profile 3gpp,2 --apn internetipv6 --pdp-type ipv6
|
|
changed_when: false
|
|
|
|
# On cold boot the BM806C's UIM (SIM) QMI service comes up permanently
|
|
# broken: --uim-get-sim-state returns {}, --get-imsi returns
|
|
# "UIM uninitialized", AT+CPIN? returns +CME ERROR: SIM busy, and the
|
|
# modem never converges (verified at uptime 21 min with no intervention).
|
|
# CTL/NAS/WDS do come up after ~5 min of warmup, but UIM does not.
|
|
#
|
|
# A single USB re-enumeration of the device (authorized=0 / authorized=1)
|
|
# forces the modem to redo its internal QMI service init from scratch.
|
|
# After this, UIM comes up within ~1 s and ifup wwan succeeds normally.
|
|
#
|
|
# We use authorized=0/1 rather than usb/unbind+bind because the former
|
|
# keeps qmi_wwan in the bound-drivers list and the kernel re-runs its
|
|
# bind machinery for us; the latter detaches and re-attaches drivers
|
|
# explicitly. Both work; authorized is cleaner.
|
|
#
|
|
# Full investigation, ruled-out hypotheses, and reproduction steps:
|
|
# /root/wwan-diag/boot-wedge-investigation.md on the router.
|
|
- name: Install wwan-bringup worker script
|
|
community.openwrt.copy:
|
|
dest: /usr/libexec/wwan-bringup
|
|
mode: '0755'
|
|
owner: root
|
|
group: root
|
|
content: |
|
|
#!/bin/sh
|
|
# Force-clean BM806C cold-boot UIM wedge by re-enumerating the USB
|
|
# device once, then bring up wwan. Called by /etc/init.d/wwan-bringup
|
|
# as a procd service.
|
|
|
|
DEV=/dev/cdc-wdm0
|
|
IFACE=wwan
|
|
USB_PORT=1-1
|
|
|
|
log() {
|
|
logger -t wwan-bringup "$1"
|
|
}
|
|
|
|
# Wait for cold-boot enumeration of cdc-wdm0 (<=60s).
|
|
waited=0
|
|
while [ ! -e "$DEV" ]; do
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
[ $waited -ge 60 ] && break
|
|
done
|
|
if [ ! -e "$DEV" ]; then
|
|
log "$DEV never appeared within 60s; giving up"
|
|
exit 1
|
|
fi
|
|
|
|
# Force-clean re-enumeration. The BM806C's UIM QMI service never
|
|
# comes up on cold boot without this.
|
|
log "BM806C cold-boot UIM workaround: re-authorizing $USB_PORT"
|
|
echo 0 > /sys/bus/usb/devices/$USB_PORT/authorized
|
|
sleep 3
|
|
echo 1 > /sys/bus/usb/devices/$USB_PORT/authorized
|
|
|
|
# Wait for cdc-wdm0 to return after re-enumeration (<=30s).
|
|
waited=0
|
|
while [ ! -e "$DEV" ]; do
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
[ $waited -ge 30 ] && break
|
|
done
|
|
if [ ! -e "$DEV" ]; then
|
|
log "$DEV did not return after re-auth; giving up"
|
|
exit 1
|
|
fi
|
|
|
|
# qmi.sh's own SIM-init and network-registration loops handle the
|
|
# small remaining warmup (~5-30s) gracefully now that UIM is healthy.
|
|
log "bringing up $IFACE"
|
|
ifup "$IFACE"
|
|
|
|
# qmi.sh installs an IPv6 default route with a source-specific prefix
|
|
# constraint (`default from 2a00:f44:.../64 ...`). This means only
|
|
# traffic sourced from the wwan IPv6 prefix uses it — forwarded traffic
|
|
# from internal subnets fails routing lookup with "net unreachable"
|
|
# before masquerade can rewrite the source. Add a non-source-specific
|
|
# default at a higher metric so forwarded traffic has a valid route,
|
|
# gets routed out wwan0, then masqueraded by fw4.
|
|
#
|
|
# Wait up to 90s for qmi.sh to install its source-specific default,
|
|
# then derive the gateway and add a regular default route.
|
|
waited=0
|
|
while [ $waited -lt 90 ]; do
|
|
gw6=$(ip -6 route show default dev wwan0 2>/dev/null | awk '/^default from/ {print $5; exit}')
|
|
if [ -n "$gw6" ]; then
|
|
if ip -6 route show default dev wwan0 | grep -qE "^default via "; then
|
|
log "non-source-specific IPv6 default already present"
|
|
else
|
|
log "adding non-source-specific IPv6 default via $gw6"
|
|
ip -6 route add default via "$gw6" dev wwan0 metric 1024
|
|
fi
|
|
break
|
|
fi
|
|
sleep 3
|
|
waited=$((waited + 3))
|
|
done
|
|
[ -z "$gw6" ] && log "warning: wwan IPv6 gateway never appeared, skipping default route"
|
|
|
|
- name: Install wwan-bringup init script
|
|
community.openwrt.copy:
|
|
dest: /etc/init.d/wwan-bringup
|
|
mode: '0755'
|
|
owner: root
|
|
group: root
|
|
content: |
|
|
#!/bin/sh /etc/rc.common
|
|
# Starts the wwan-bringup worker which re-enumerates the BM806C USB
|
|
# device once to clear the cold-boot UIM wedge, then triggers
|
|
# `ifup wwan`. See /usr/libexec/wwan-bringup.
|
|
|
|
START=99
|
|
USE_PROCD=1
|
|
|
|
# One-shot script: launch the worker directly without procd_open_instance
|
|
# so procd does not respawn it after successful exit.
|
|
PIDFILE=/var/run/wwan-bringup.pid
|
|
|
|
start_service() {
|
|
/usr/libexec/wwan-bringup &
|
|
echo $! > $PIDFILE
|
|
}
|
|
|
|
stop_service() {
|
|
[ -f $PIDFILE ] && kill "$(cat $PIDFILE)" 2>/dev/null
|
|
rm -f $PIDFILE
|
|
}
|
|
|
|
- name: Enable and start wwan-bringup service
|
|
community.openwrt.service:
|
|
name: wwan-bringup
|
|
enabled: true
|
|
state: started
|