Skip to content

pi5-cellular-modems: Add reference for USB cellular modems on a Pi 5 - #38

Open
jetm wants to merge 1 commit into
mainfrom
pi5-cellular-modems
Open

jetm wants to merge 1 commit into
mainfrom
pi5-cellular-modems

Conversation

@jetm

@jetm jetm commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Problem

The Pi 5 image builds the USB cellular modem drivers but leaves them out of the rootfs. A modem link named usb0 or wwan0 also matches the stock 80-wired.network, which gives it Ethernet's route metric of 10, so a device with Ethernet and a modem picks between them by accident.

Change

Adds the pi5-cellular-modems reference:

  • A cellular-modems extension with the network drivers (usbnet, cdc_ether, cdc_ncm, cdc_mbim, cdc_wdm, qmi_wwan, rndis_host), the serial and PPP ones (cdc_acm, option, usb_wwan, usbserial, qcserial, sierra, sierra_net, ppp_*, slhc) and tun, plus lsusb, ethtool and iproute2.
  • 30-cellular.network for usb0 and wwan0: DHCP at route metric 300, behind Ethernet (10) and Wi-Fi (200). The number sorts it ahead of 80-wired.network.
  • cellular-modems-networkd.service, a oneshot that runs networkctl reload. networkd starts before the extension merge, so without it the profile is not read on a cold boot.
  • getting_started.md with install, build, deploy and three checks, and a list of what the reference does not do (no USB mode switching, no ModemManager or APN handling).

It builds on the same extension-merge ordering problem as #37, and the two do not depend on each other.

Testing

On a Pi 5 running the 2026 next feed (kernel 6.12.87, avocado-cli 1.0.0-rc.4), built and deployed with avocado build and avocado deploy, with no modem attached:

  • All 19 modules resolve with modprobe -n and load with modprobe.
  • After a cold reboot with no manual reload, a tap link named usb0 and one named wwan0 both report Network File: /usr/lib/systemd/network/30-cellular.network. Before the reload unit existed, the same usb0 matched 80-wired.network until networkctl reload was run by hand.
  • The verify commands in getting_started.md were run exactly as written.

Not tested: a real modem in any USB mode, DHCP over a modem link, and the route metric on a live modem link. The README and guide say so.

avocado.lock is in the reference's .gitignore, as no other reference tracks it.

The Pi 5 image builds the USB modem drivers but leaves them out of the
rootfs, and a modem link named usb0 or wwan0 matches the stock
80-wired.network, which gives it Ethernet's route metric. A device with
Ethernet and a modem would then pick between them by accident.

The reference carries the network and serial drivers a modem needs
whichever USB mode it presents, a networkd profile that sorts ahead of
80-wired and sets the modem's route metric to 300, and a unit that
reloads networkd after the extension merge. networkd starts before the
merge, so without the reload the profile is not read until someone runs
networkctl reload by hand, which a cold boot never does.

Tested on a Pi 5 on the 2026 next feed without a modem attached: all 19
modules resolve and load, and after a cold reboot a link named usb0 or
wwan0 matches the new profile with no manual reload. Behaviour with a
real modem is not covered.

Signed-off-by: Javier Tia <javier@peridio.com>
@github-actions

Copy link
Copy Markdown

Avocado References — Build Check

Reference Target install build Result
pi5-cellular-modems raspberrypi5 ✅ ✅ PASS

Totals: 1 ✅ passed · 0 ❌ failed

Per-reference logs: workflow run

Comment on lines +61 to +62
enable_services:
- cellular-modems-networkd.service

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cellular-modems-networkd.service does not run after avocado deploy. It runs only after a reboot.

The guide's prerequisite is a Pi 5 that already runs 2026 next. On that device, avocado deploy calls avocadoctl runtime add. avocadoctl sees no OS change and does a hot refresh: merge, daemon-reload, then the on_merge commands. enable_services only adds the multi-user.target.wants link. Nothing starts the unit, so networkd keeps its old configuration.

Result: a user who follows Deploy and then Verify step 2 sees 80-wired.network, not 30-cellular.network. Also, on a later deploy that changes 30-cellular.network, RemainAfterExit=yes keeps the unit active, so networkd does not reload until the next reboot. The PR testing ran the checks after a cold reboot, so this did not show up.

Suggested fix:

  • Add a reload to the extension's merge hooks. rubicon/avocado.yaml already does this. In avocadoctl, a failed on_merge command only logs a warning, so it cannot break the merge.
        on_merge:
          - networkctl reload
  • If you keep the current unit instead, tell the user in getting_started.md to reboot after avocado deploy.

Comment on lines +3 to +9
# networkd starts before the extension merge, so on a cold boot it has not read
# this extension's 30-cellular.network. Until it does, a modem link named usb0
# matches the stock 80-wired.network and gets Ethernet's route metric. This unit
# exists only after the merge, which makes it the first point that can ask
# networkd to re-read its configuration.
After=systemd-networkd.service
Wants=systemd-networkd.service

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The boot race is in the platform, so each reference has to work around it.

avocado-extension.service is ordered Before=sysinit.target, but systemd-networkd.service has DefaultDependencies=no and no ordering against the merge. The two can start in parallel, so networkd can read its configuration before the extensions are merged.

As a result, every reference that ships a .network file must include its own fix. #37 adds wifi-station.service for the same problem, and rubicon uses on_merge: networkctl reload. That gives three patterns for one problem, and customers copy references.

Suggested fix:

  • In meta-avocado, add a drop-in at systemd-networkd.service.d/avocado.conf:
    [Unit]
    After=avocado-extension.service
    This is the same pattern as sshdgenkeys-after-extensions.conf in meta-avocado-distro/recipes-avocado/avocadoctl/avocadoctl/.
  • In this PR, keep the unit as a stopgap. Add a comment that links to the meta-avocado ticket, so that the unit is removed when the platform fix ships.

# which matches every Ethernet-type link and would otherwise give a modem the
# same low route metric as Ethernet.
[Match]
Name=usb0 wwan0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name=usb0 wwan0 matches only the first interface of each type.

Predictable interface labels are off in Avocado (the NamePolicy= sed in systemd_258.1.bb), so the kernel labels apply. A modem link misses this match when:

  • A second modem is attached (usb1, wwan1).
  • The modem exposes two data interfaces, for example multi-PDN QMI or a composite configuration.
  • The modem resets its firmware and comes back as usb1 before usb0 is removed.
  • cdc_ether or rndis_host gets a MAC that is not locally administered. usbnet then gives the link eth%d.

All of these links are Type=ether, so they match 80-wired.network and get DHCP at metric 10. The modem then becomes the default route ahead of Ethernet, which is the problem this PR fixes. On a metered SIM, that costs money. Verify step 2 does not find this, because the tap test sets the label to usb0.

Suggested fix:

  • Minimum: Name=usb* wwan*.
  • Better: match on the driver, so the kernel label does not matter:
    [Match]
    Driver=cdc_ether cdc_ncm cdc_mbim qmi_wwan rndis_host sierra_net
    Some USB Ethernet dongles run in ECM mode under cdc_ether and would also get metric 300. Add a comment about that.

ssh root@<device-ip> 'lsusb; dmesg | tail -20; ip -br link; networkctl status usb0 wwan0 --no-pager'
```

An ECM, NCM or RNDIS modem shows up as `usb0`; a QMI or MBIM modem as `wwan0`. Once it has an address, `ip route` lists a default route through it at metric 300, behind `eth0` at 10 and `wlan0` at 200.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QMI and MBIM modems do not get an address with this reference.

The extension has no qmicli, mbimcli or ModemManager. On a QMI or MBIM modem, DHCP on wwan0 gets no reply until a tool starts the data session. Common Pi modems such as the Quectel EC25/EG25 and the Sierra EM7455 start in QMI or MBIM mode.

A user with a SIM runs step 3, sees wwan0 with no address and no route, and has no tool on the device to fix it. Line 77 states this limit, but this line still promises a default route at metric 300, and the prerequisites ask for an active SIM and data plan.

Suggested fix:

  • Say that the route check in step 3 applies only to ECM, NCM and RNDIS modems.
  • Add the libqmi and libmbim tools if the feed has them. Or give the AT command that switches a common modem to ECM, for example Quectel AT+QCFG="usbnet",1.

Comment on lines +80 to +83
kernel-module-ppp-generic: "*"
kernel-module-ppp-async: "*"
kernel-module-ppp-deflate: "*"
kernel-module-slhc: "*"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PPP kernel modules do nothing without pppd.

The extension adds ppp_generic, ppp_async, ppp_deflate and slhc, but no userspace dialer. The README says that the reference covers serial AT ports. A user with a serial-only or PPP modem gets /dev/ttyUSB* but cannot dial, and must find the missing package by themselves.

Suggested fix:

  • Add the ppp package if the feed has it.
  • If the feed does not have it, remove these four modules and add PPP to "What this reference does not do" in getting_started.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants