From 1c674a6c1be3e12bdfc6b0efd7218ec8fc8c0466 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 2 May 2026 12:59:37 -0700 Subject: [PATCH] Revert "Remove ModemManager & friends (#561)" (#571) * Revert "Remove ModemManager & friends (#561)" This reverts commit 4c5609a9cf71d494f110f2a8fc8a77cbd4b22889. * shouldn't need this anymore --- Dockerfile.agnos | 28 ++++++- TESTING.md | 1 + scripts/setup-on-device.sh | 2 +- userspace/base_setup.sh | 1 + userspace/compile-libqmi.sh | 31 +++++++ userspace/compile-modemmanager.sh | 72 +++++++++++++++++ userspace/files/ModemManager.service | 22 +++++ .../etc/polkit-1/rules.d/comma-polkit.rules | 5 +- userspace/root/etc/profile | 5 ++ .../root/etc/udev/rules.d/78-modem.rules | 24 ++++++ userspace/root/usr/comma/tests/test_agnos.py | 20 +++++ userspace/root/usr/comma/tests/test_modem.py | 81 +++++++++++++++++++ .../system-connections/lte.nmconnection | 28 +++++++ 13 files changed, 316 insertions(+), 4 deletions(-) create mode 100755 userspace/compile-libqmi.sh create mode 100755 userspace/compile-modemmanager.sh create mode 100644 userspace/files/ModemManager.service create mode 100644 userspace/root/etc/udev/rules.d/78-modem.rules create mode 100755 userspace/root/usr/comma/tests/test_modem.py create mode 100644 userspace/root/usr/lib/NetworkManager/system-connections/lte.nmconnection diff --git a/Dockerfile.agnos b/Dockerfile.agnos index cca948c..2e55d1c 100644 --- a/Dockerfile.agnos +++ b/Dockerfile.agnos @@ -21,6 +21,18 @@ RUN apt-get update && apt-get install -yq --no-install-recommends \ # Enable ccache ENV PATH="/usr/lib/ccache:$PATH" +# libqmi +FROM agnos-compiler AS agnos-compiler-libqmi +COPY ./userspace/compile-libqmi.sh /tmp/agnos/ +RUN --mount=type=cache,target=/root/.ccache,id=libqmi,sharing=shared \ + /tmp/agnos/compile-libqmi.sh + +# ModemManager +FROM agnos-compiler-libqmi AS agnos-compiler-modemmanager +COPY ./userspace/compile-modemmanager.sh /tmp/agnos/ +RUN --mount=type=cache,target=/root/.ccache,id=modemmanager,sharing=shared \ + /tmp/agnos/compile-modemmanager.sh + # power_burn_max FROM agnos-compiler AS agnos-compiler-power-burn RUN apt-get update && apt-get install -yq --no-install-recommends \ @@ -74,12 +86,17 @@ COPY ./userspace/root/system/ /system/ COPY ./userspace/root/usr/ /usr/ RUN mkdir -p /dsp /firmware /persist /data /cache /rwtmp +COPY --from=agnos-compiler-libqmi /tmp/libqmi.deb /tmp/ +COPY --from=agnos-compiler-modemmanager /tmp/modemmanager.deb /tmp/ + RUN cd /tmp && \ apt-get update && \ apt-get install -yq --no-install-recommends \ python3 \ python3-dev \ + gir1.2-qmi-1.0 \ libglib2.0-dev \ + libqmi-glib5 \ libc6 \ libglib2.0-0t64 \ libgudev-1.0-0 \ @@ -88,7 +105,10 @@ RUN cd /tmp && \ libsystemd0 \ libwayland-client0 \ libwayland-server0 \ - polkitd + polkitd \ + mobile-broadband-provider-info && \ + apt-get -o Dpkg::Options::="--force-overwrite" install -yq ./libqmi.deb && \ + apt-get -o Dpkg::Options::="--force-overwrite" install -yq ./modemmanager.deb ARG XDG_DATA_HOME="/usr/local" @@ -125,9 +145,15 @@ RUN /tmp/agnos/services.sh RUN rm -r /etc/update-motd.d/* COPY --chown=root:root ./userspace/root/etc/update-motd.d/* /etc/update-motd.d/ +RUN chmod 600 /usr/lib/NetworkManager/system-connections/*.nmconnection + # Prefer ipv4 over ipv6 RUN echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf +# Run ModemManager in debug mode to allow AT commands +COPY ./userspace/files/ModemManager.service /lib/systemd/system/ +RUN systemctl enable ModemManager + # Setup hostname resolution for our custom hostname RUN sed -i 's/hosts: files dns myhostname/hosts: files myhostname dns/g' /etc/nsswitch.conf diff --git a/TESTING.md b/TESTING.md index e49b940..fe3b708 100644 --- a/TESTING.md +++ b/TESTING.md @@ -4,6 +4,7 @@ - [ ] [`test_onroad`](https://github.com/commaai/openpilot/blob/master/selfdrive/test/test_onroad.py) passes - [ ] Wi-Fi: lists networks and connects +- [ ] Modem: connects to cell network - [ ] Image sizes haven't increased - [ ] Sounds work: `pkill -f manager ; /data/openpilot/scripts/disable-powersave.py && aplay /data/openpilot/selfdrive/assets/sounds/engage.wav` - [ ] Clean openpilot build: `scons -c && scons -j8` diff --git a/scripts/setup-on-device.sh b/scripts/setup-on-device.sh index a2ca733..c69e561 100755 --- a/scripts/setup-on-device.sh +++ b/scripts/setup-on-device.sh @@ -17,7 +17,7 @@ sudo rm -rf /usr/comma sudo ln -snf $ROOT/userspace/root/usr/comma/ /usr/comma echo "cp systemd services" -for s in "$ROOT"/userspace/root/lib/systemd/system/*.{service,path,timer,mount}; do +for s in "$ROOT"/userspace/root/lib/systemd/system/*.{service,path,timer,mount} "$ROOT"/userspace/files/ModemManager.service; do [ -e "$s" ] || continue service=$(basename $s) echo "- $service" diff --git a/userspace/base_setup.sh b/userspace/base_setup.sh index 8a5da38..b6c1e03 100755 --- a/userspace/base_setup.sh +++ b/userspace/base_setup.sh @@ -56,6 +56,7 @@ apt-fast install --no-install-recommends -yq \ libi2c-dev \ libncursesw5-dev \ libnss-myhostname \ + libqmi-utils \ libssl-dev \ locales \ llvm \ diff --git a/userspace/compile-libqmi.sh b/userspace/compile-libqmi.sh new file mode 100755 index 0000000..acc034a --- /dev/null +++ b/userspace/compile-libqmi.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +LIBQMI_VERSION="1.36.0" + +cd /tmp + +# meson support for checkinstall +git clone https://github.com/keithbowes/meson-install.git + +apt-get update && apt-get install -yq --no-install-recommends \ + bash-completion \ + gobject-introspection \ + gtk-doc-tools \ + help2man \ + libgirepository1.0-dev \ + libglib2.0-dev \ + libgudev-1.0-dev \ + meson \ + ninja-build \ + +git clone -b $LIBQMI_VERSION --depth 1 https://gitlab.freedesktop.org/mobile-broadband/libqmi.git +cd libqmi +meson setup build --prefix=/usr --libdir=/usr/lib/aarch64-linux-gnu -Dmbim_qmux=false -Dqrtr=false +ninja -C build + +cd build +checkinstall -yD --install=no --fstrans=no --pkgname=libqmi /tmp/meson-install/meson-install +mv libqmi*.deb /tmp/libqmi.deb + +apt-get -o Dpkg::Options::="--force-overwrite" install -yq /tmp/libqmi.deb diff --git a/userspace/compile-modemmanager.sh b/userspace/compile-modemmanager.sh new file mode 100755 index 0000000..d09e9ff --- /dev/null +++ b/userspace/compile-modemmanager.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +MM_VERSION="1.22.0" + +cd /tmp + +git clone -b $MM_VERSION --depth 1 https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git + +apt-get install -y --no-install-recommends \ + cmake \ + gettext \ + libdbus-1-dev \ + libpolkit-gobject-1-dev \ + libsystemd-dev \ + udev + +cd ModemManager +meson setup build \ + --prefix=/usr \ + --libdir=/usr/lib/aarch64-linux-gnu \ + --sysconfdir=/etc \ + --buildtype=release \ + -Dqmi=true \ + -Dmbim=false \ + -Dqrtr=false \ + -Dplugin_generic=enabled \ + -Dplugin_altair_lte=disabled \ + -Dplugin_anydata=disabled \ + -Dplugin_broadmobi=disabled \ + -Dplugin_cinterion=disabled \ + -Dplugin_dell=disabled \ + -Dplugin_dlink=disabled \ + -Dplugin_fibocom=disabled \ + -Dplugin_foxconn=disabled \ + -Dplugin_gosuncn=disabled \ + -Dplugin_haier=disabled \ + -Dplugin_huawei=disabled \ + -Dplugin_intel=disabled \ + -Dplugin_iridium=disabled \ + -Dplugin_linktop=disabled \ + -Dplugin_longcheer=disabled \ + -Dplugin_mbm=disabled \ + -Dplugin_motorola=disabled \ + -Dplugin_mtk=disabled \ + -Dplugin_nokia=disabled \ + -Dplugin_nokia_icera=disabled \ + -Dplugin_novatel=disabled \ + -Dplugin_novatel_lte=disabled \ + -Dplugin_option=disabled \ + -Dplugin_option_hso=disabled \ + -Dplugin_pantech=disabled \ + -Dplugin_qcom_soc=disabled \ + -Dplugin_quectel=enabled \ + -Dplugin_samsung=disabled \ + -Dplugin_sierra_legacy=disabled \ + -Dplugin_sierra=disabled \ + -Dplugin_simtech=disabled \ + -Dplugin_telit=disabled \ + -Dplugin_thuraya=disabled \ + -Dplugin_tplink=disabled \ + -Dplugin_ublox=disabled \ + -Dplugin_via=disabled \ + -Dplugin_wavecom=disabled \ + -Dplugin_x22x=disabled \ + -Dplugin_zte=disabled + +ninja -C build + +cd build +checkinstall -yD --install=no --fstrans=no --pkgname=modemmanager /tmp/meson-install/meson-install +mv modemmanager*.deb /tmp/modemmanager.deb diff --git a/userspace/files/ModemManager.service b/userspace/files/ModemManager.service new file mode 100644 index 0000000..8884248 --- /dev/null +++ b/userspace/files/ModemManager.service @@ -0,0 +1,22 @@ +[Unit] +Description=Modem Manager +After=polkit.service +Requires=polkit.service + +[Service] +Type=dbus +BusName=org.freedesktop.ModemManager1 +ExecStart=/usr/sbin/ModemManager --filter-policy=strict --debug +StandardError=null +Restart=on-abort +CapabilityBoundingSet=CAP_SYS_ADMIN +ProtectSystem=true +ProtectHome=true +PrivateTmp=true +RestrictAddressFamilies=AF_NETLINK AF_UNIX +NoNewPrivileges=true +User=root + +[Install] +WantedBy=multi-user.target +Alias=dbus-org.freedesktop.ModemManager1.service diff --git a/userspace/root/etc/polkit-1/rules.d/comma-polkit.rules b/userspace/root/etc/polkit-1/rules.d/comma-polkit.rules index 0774939..e5b65b2 100644 --- a/userspace/root/etc/polkit-1/rules.d/comma-polkit.rules +++ b/userspace/root/etc/polkit-1/rules.d/comma-polkit.rules @@ -1,6 +1,7 @@ -// Allow sudo users to control network +// Allow sudo users to control network & modem polkit.addRule(function (action, subject) { - if (action.id.startsWith("org.freedesktop.NetworkManager.") && + if ((action.id.startsWith("org.freedesktop.NetworkManager.") || + action.id.startsWith("org.freedesktop.ModemManager1.")) && subject.isInGroup("sudo")) { return polkit.Result.YES; } diff --git a/userspace/root/etc/profile b/userspace/root/etc/profile index 8df2bf6..9363f2a 100644 --- a/userspace/root/etc/profile +++ b/userspace/root/etc/profile @@ -39,3 +39,8 @@ source /usr/local/venv/bin/activate # custom shims export PATH="/usr/comma/shims:${PATH}" + +# nice AT command helper +atc() { + mmcli -m any --command="${@}" +} diff --git a/userspace/root/etc/udev/rules.d/78-modem.rules b/userspace/root/etc/udev/rules.d/78-modem.rules new file mode 100644 index 0000000..29d8457 --- /dev/null +++ b/userspace/root/etc/udev/rules.d/78-modem.rules @@ -0,0 +1,24 @@ +# EG25 +# Interface 0 - GNSS +# Interface 2 - AT +# Interface 3 - AT +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="0125",ENV{ID_USB_INTERFACE_NUM}=="00",ENV{ID_MM_DEVICE_IGNORE}="1" +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="0125",ENV{ID_USB_INTERFACE_NUM}=="02",SYMLINK+="modem_at0",ENV{ID_MM_PORT_IGNORE}="1" +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="0125",ENV{ID_USB_INTERFACE_NUM}=="03",SYMLINK+="modem_at1",ENV{ID_MM_PORT_IGNORE}="1" + +# EG91 +# Interface 0 - debug +# Interface 1 - GPS +# Interface 2 - AT +# Interface 3 - AT +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="6007",ENV{ID_USB_INTERFACE_NUM}=="00",ENV{ID_MM_DEVICE_IGNORE}="1" +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="6007",ENV{ID_USB_INTERFACE_NUM}=="01",ENV{ID_MM_DEVICE_IGNORE}="1" +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="6007",ENV{ID_USB_INTERFACE_NUM}=="02",SYMLINK+="modem_at0",ENV{ID_MM_PORT_IGNORE}="1" +SUBSYSTEM=="tty",ATTRS{idVendor}=="2c7c",ATTRS{idProduct}=="6007",ENV{ID_USB_INTERFACE_NUM}=="03",SYMLINK+="modem_at1",ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +ACTION=="add",SUBSYSTEM=="usb",ATTR{idVendor}=="2c7c",ATTR{idProduct}=="6007",RUN+="/bin/sh -c 'echo -n $kernel:1.4 > /sys/bus/usb/drivers/cdc_ether/unbind'" + +# C16 +KERNEL=="ttyACM1",ENV{ID_MM_DEVICE_IGNORE}="1" +KERNEL=="ttyACM0",ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1" +KERNEL=="ttyACM2",ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1" +SUBSYSTEM=="net", ACTION=="add", ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9330", NAME="usb%n" diff --git a/userspace/root/usr/comma/tests/test_agnos.py b/userspace/root/usr/comma/tests/test_agnos.py index 138d680..8ca6647 100755 --- a/userspace/root/usr/comma/tests/test_agnos.py +++ b/userspace/root/usr/comma/tests/test_agnos.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import json import time import subprocess @@ -19,6 +20,25 @@ def test_reset(): assert proc.poll() is None proc.terminate() +def test_modem(): + out = run("mmcli -m 0 --output-json") + mm = json.loads(out) + from pprint import pprint + pprint(mm) + + # modem is up + g = mm['modem']['generic'] + assert g['manufacturer'] == 'QUALCOMM INCORPORATED' + assert g['model'] == 'QUECTEL Mobile Broadband Module' + assert g['revision'] == 'EG25GGBR07A08M2G' + + # sim is present + assert g['sim'] == '/org/freedesktop/ModemManager1/SIM/0' + + # blue prime is active + out = run("nmcli con show --active") + assert "blue-prime" in out + def test_wifi(): out = run("nmcli dev wifi") networks = out.strip().splitlines()[1:] diff --git a/userspace/root/usr/comma/tests/test_modem.py b/userspace/root/usr/comma/tests/test_modem.py new file mode 100755 index 0000000..8eb6e27 --- /dev/null +++ b/userspace/root/usr/comma/tests/test_modem.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import os +import json +import subprocess +import time + +def mmcli(cmd): + try: + out = subprocess.check_output(["mmcli", "-J", *cmd]) + return json.loads(out) + except Exception as e: + raise Exception(f"modem command failed: {cmd}") from e + +def test_modem(): + # wait for the modem to come back up after flashing + n = 0 + cnt = 0 + while n == 0: + n = len(mmcli(["-L"])['modem-list']) + if n > 1: + raise Exception(f"Wrong number of modems ({n})") + + cnt += 1 + if cnt > 100: + raise Exception("Modem never came up") + + time.sleep(1) + + cnt = 0 + while True: + modem = mmcli(["-m", "any"]) + if modem['modem']['generic']['sim'] != '--': + break + + cnt += 1 + if cnt > 100: + raise Exception("SIM missing") + + time.sleep(1) + + # force to LTE + #os.system("mmcli -m any --set-allowed-modes='4g'") + + # set initial eps bearer apn + #sim_id = mmcli(['-i', '0'])['sim']['properties']['iccid'] + #if sim_id.startswith('8901410'): + # os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn=Broadband"') + + print("waiting for cell connection") + time.sleep(7) + + expected = [ + # key, expected value, error msg + (['generic', 'manufacturer'], "QUALCOMM INCORPORATED", "Modem: wrong manufacturer"), + (['generic', 'model'], "QUECTEL Mobile Broadband Module", "Modem: wrong model"), + (['generic', 'revision'], "EG25GGBR07A08M2G", "Modem: wrong revision"), + (['generic', 'carrier-configuration'], ["VoLTE-ATT", "Commercial-TMO_VoLTE"], "Modem: wrong carrier configuration"), + (['generic', 'carrier-configuration-revision'], ["0501033C", "05010505"], "Modem: wrong carrier configuration revision"), + + (['generic', 'supported-capabilities'], [['gsm-umts, lte'], ], "Modem: wrong capabilities"), + (['3gpp', 'operator-name'], ["--", "AT&T", "T-Mobile"], "Modem: wrong operator"), + ] + for key, val, err_msg in expected: + v = modem['modem'] + for k in key: + v = v.get(k) + if isinstance(val, list): + assert v in val, f"{err_msg} ({v})" + else: + assert v == val, f"{err_msg} ({v})" + + os.system("date >> /data/tmp/modem_log") + os.system("sudo su -c 'tail /data/tmp/modem_log > /dev/console'") + os.system("sudo su -c 'wc -l /data/tmp/modem_log > /dev/console'") + os.sync() + + time.sleep(2) + os.system("sudo reboot") + +if __name__ == "__main__": + test_modem() diff --git a/userspace/root/usr/lib/NetworkManager/system-connections/lte.nmconnection b/userspace/root/usr/lib/NetworkManager/system-connections/lte.nmconnection new file mode 100644 index 0000000..c896c3f --- /dev/null +++ b/userspace/root/usr/lib/NetworkManager/system-connections/lte.nmconnection @@ -0,0 +1,28 @@ +[connection] +id=lte +uuid=6586fb1f-4a66-46d4-9164-0100b73750ee +type=gsm +permissions= +autoconnect=true +autoconnect-retries=100 +metered=1 + +[gsm] +apn= +home-only=false +auto-config=true + +[ipv4] +route-metric=1000 +dns-priority=1000 +dns-search= +method=auto + +[ipv6] +ddr-gen-mode=stable-privacy +dns-search= +route-metric=1000 +dns-priority=1000 +method=auto + +[proxy]