From 0ab728f92dddb32f16a05f8d8102931477820861 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:46:21 +0300 Subject: [PATCH 1/2] mdev: load the tethering drivers for a phone, and bring usb0 up when it appears A phone in USB tethering mode gave no usb0 (#1112). rndis_host, cdc_ether and cdc_ncm are built as modules on the boards that have them, and nothing loads a module when a USB device enumerates: mdev.conf has no MODALIAS rule, so the phone's RNDIS interfaces sat there with no driver bound. mdev now loads a driver for the RNDIS, ECM and NCM interface classes only. It is not a blanket MODALIAS rule, because that would also load usbserial for a modem before rndis_host, which /etc/wireless/modem exists to prevent. With the driver loaded by hand, usb0 still came up bare whenever it appeared after S40network: Android switches tethering off each time the cable is pulled, so that is the usual case. /lib/mdev/usbnet.sh runs ifup when usb0 appears and ifdown -f when it goes. Without the ifdown, the next plug got "ifup: interface usb0 already configured" and no address. It acts only on interfaces bound by those three drivers: a USB gadget's usb0 has no DHCP server across the cable, and udhcpc's leasefail would hand it the camera's ipaddr. A phone tethering at power-on was announced before mdev became the hotplug helper, and mdev -s walks only devices with a node, so S38mdev replays the USB interfaces' add events after it. --- general/overlay/etc/init.d/S38mdev | 7 +++++++ general/overlay/etc/mdev.conf | 8 ++++++++ general/overlay/lib/mdev/usbnet.sh | 26 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100755 general/overlay/lib/mdev/usbnet.sh diff --git a/general/overlay/etc/init.d/S38mdev b/general/overlay/etc/init.d/S38mdev index 47da55b519..7b92f0c55c 100755 --- a/general/overlay/etc/init.d/S38mdev +++ b/general/overlay/etc/init.d/S38mdev @@ -5,6 +5,13 @@ case "$1" in echo "Starting mdev..." echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s + # Whatever was plugged in at power-on was announced before mdev became + # the hotplug helper, and mdev -s walks only devices with a node, not + # the USB interfaces a driver binds to. Announce them again, so a phone + # already tethering at boot gets its driver like one plugged in later. + for uevent in /sys/bus/usb/devices/*:*/uevent; do + [ -w "$uevent" ] && echo add > "$uevent" + done ;; stop) diff --git a/general/overlay/etc/mdev.conf b/general/overlay/etc/mdev.conf index 3b119e307d..a1144dbdc5 100644 --- a/general/overlay/etc/mdev.conf +++ b/general/overlay/etc/mdev.conf @@ -18,3 +18,11 @@ mmcblk[0-9]p[0-9] root:disk 660 *(/lib/mdev/automount.sh) sd[a-z] root:disk 660 *(/lib/mdev/automount.sh) sd[a-z][0-9] root:disk 660 *(/lib/mdev/automount.sh) + +# USB tethering: a phone, or a modem in RNDIS, ECM or NCM mode. The kernel +# builds these drivers as modules and nothing else loads them, so a phone that +# tethered gave no usb0 at all (#1112). The classes are the drivers' own; a +# blanket MODALIAS rule would also load usbserial for a modem before +# rndis_host, which /etc/wireless/modem exists to prevent. +$MODALIAS=usb:.*ic(E0isc01ip03|EFisc01ip01|02isc02ipFF|02isc06ip00|02isc0Dip00)in.* root:root 660 @modprobe -q "$MODALIAS" +$INTERFACE=usb[0-9]+ root:root 660 ! *(/lib/mdev/usbnet.sh) diff --git a/general/overlay/lib/mdev/usbnet.sh b/general/overlay/lib/mdev/usbnet.sh new file mode 100755 index 0000000000..a2fd9457bc --- /dev/null +++ b/general/overlay/lib/mdev/usbnet.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# usb0 comes and goes with the cable, long after S40network has run: Android +# switches tethering off whenever the cable is pulled, so the phone is +# re-plugged and re-enabled at any time. Bring the interface up when it appears +# and forget it when it goes. Without the ifdown, ifupdown still records the +# vanished interface as configured, and the next plug got "ifup: interface usb0 +# already configured" and no address. + +case "$ACTION" in + add) + # Only the host side of a tether. A USB gadget's usb0 -- the camera + # being the device -- has no DHCP server across the cable, and + # udhcpc's leasefail would hand it the camera's ipaddr. + case "$(readlink "/sys/class/net/$MDEV/device/driver")" in + */rndis_host | */cdc_ether | */cdc_ncm) ;; + *) exit 0 ;; + esac + # mdev waits for its helper, and udhcpc tries for seconds before + # it backgrounds. + ifup "$MDEV" /dev/null 2>&1 & + ;; + remove) + ifdown -f "$MDEV" >/dev/null 2>&1 + ;; +esac From 8c9a9397c930f77d5f13b56af9cae68f9bd92b09 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:19:04 +0300 Subject: [PATCH 2/2] mdev: keep the cable's route ahead of a tether, and survive a pull during DHCP Review of the tethering hotplug found three gaps. A phone tethering at boot got its default route at the same metric as eth0, and could come up first. usb0's stanza now sets metric 10, which default.script already honours, so the cable keeps its default route and its DNS ahead of the phone's. The rule took any usbN, but only usb0 has a stanza. It now takes usb0 alone, and an ifup that fails says so in syslog. A pull while udhcpc was still in its foreground retries could leave usb0 recorded as configured, and the next plug got no address. Add and remove now share a lock. The add looks for the interface again once it holds the lock, and clears whatever ifupdown kept for the name before it brings the new interface up. Two traps on the way: - udhcpc daemonizes, and would inherit the lock and hold it for good. - ash execs a subshell's last command in place, so `ifup ... 9>&-` as the last command dropped the lock for the whole ifup. Every pull during DHCP left the state behind; with tracing added after it, none did. --- general/overlay/etc/mdev.conf | 2 +- general/overlay/etc/network/interfaces.d/usb0 | 1 + general/overlay/lib/mdev/usbnet.sh | 29 ++++++++++++++++--- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/general/overlay/etc/mdev.conf b/general/overlay/etc/mdev.conf index a1144dbdc5..1eec32b70e 100644 --- a/general/overlay/etc/mdev.conf +++ b/general/overlay/etc/mdev.conf @@ -25,4 +25,4 @@ sd[a-z][0-9] root:disk 660 *(/lib/mdev/automount.sh) # blanket MODALIAS rule would also load usbserial for a modem before # rndis_host, which /etc/wireless/modem exists to prevent. $MODALIAS=usb:.*ic(E0isc01ip03|EFisc01ip01|02isc02ipFF|02isc06ip00|02isc0Dip00)in.* root:root 660 @modprobe -q "$MODALIAS" -$INTERFACE=usb[0-9]+ root:root 660 ! *(/lib/mdev/usbnet.sh) +$INTERFACE=usb0 root:root 660 ! *(/lib/mdev/usbnet.sh) diff --git a/general/overlay/etc/network/interfaces.d/usb0 b/general/overlay/etc/network/interfaces.d/usb0 index a84312310c..b4beba0359 100644 --- a/general/overlay/etc/network/interfaces.d/usb0 +++ b/general/overlay/etc/network/interfaces.d/usb0 @@ -1 +1,2 @@ iface usb0 inet dhcp + metric 10 diff --git a/general/overlay/lib/mdev/usbnet.sh b/general/overlay/lib/mdev/usbnet.sh index a2fd9457bc..f06528aff1 100755 --- a/general/overlay/lib/mdev/usbnet.sh +++ b/general/overlay/lib/mdev/usbnet.sh @@ -7,6 +7,13 @@ # vanished interface as configured, and the next plug got "ifup: interface usb0 # already configured" and no address. +# Both halves under one lock: the cable can be pulled while udhcpc is still in +# its foreground retries, and an ifdown that finished first would leave the +# late ifup's state behind for the next plug to trip over. The lock orders +# nothing, though -- a pull within a second of the plug took it first -- so +# the add looks again once it holds it. +lock=/var/run/usbnet.$MDEV.lock + case "$ACTION" in add) # Only the host side of a tether. A USB gadget's usb0 -- the camera @@ -16,11 +23,25 @@ case "$ACTION" in */rndis_host | */cdc_ether | */cdc_ncm) ;; *) exit 0 ;; esac - # mdev waits for its helper, and udhcpc tries for seconds before - # it backgrounds. - ifup "$MDEV" /dev/null 2>&1 & + # In the background: mdev waits for its helper, and udhcpc tries + # for seconds before it daemonizes. The daemon must not take the + # lock with it, hence the 9>&-. And ifup must not be the last + # command here: ash execs that one in place of the subshell, the + # 9>&- then drops the lock for the whole ifup, and a pull during + # DHCP left usb0 recorded as configured every time. + ( + flock 9 + [ -e "/sys/class/net/$MDEV" ] || exit 0 + # A new interface: whatever ifupdown holds for the name + # is left over from the last one. + ifdown -f "$MDEV" + ifup "$MDEV" 9>&- || logger -t network "$MDEV: ifup failed" + ) 9>"$lock" /dev/null 2>&1 & ;; remove) - ifdown -f "$MDEV" >/dev/null 2>&1 + ( + flock 9 + ifdown -f "$MDEV" + ) 9>"$lock" >/dev/null 2>&1 ;; esac