Skip to content

mdev: load the tethering drivers for a phone, and bring usb0 up when it appears - #2652

Merged
widgetii merged 2 commits into
masterfrom
mdev-usb-tethering
Oct 21, 2026
Merged

widgetii merged 2 commits into
masterfrom
mdev-usb-tethering

Conversation

@widgetii

@widgetii widgetii commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Problem

With an Android phone in USB tethering mode, an OpenIPC board never got usb0 (#1112, reported on hi3536dv100). The kernel builds rndis_host, cdc_ether and cdc_ncm as modules, and the image ships them. But nothing loads a module when a USB device enumerates: mdev.conf has no MODALIAS rule, so the phone's RNDIS interfaces stayed with no driver bound.

Loading the driver by hand was not enough either. usb0 came up bare whenever it appeared after S40network had run. Android switches tethering off each time the cable is pulled, so that is the usual case. On the second plug, ifup also refused with interface usb0 already configured, because ifupdown still recorded the vanished interface.

The fix:

  • mdev.conf loads the driver for the RNDIS, ECM and NCM interface classes only. It is not a blanket MODALIAS rule, because that would load usbserial for a modem before rndis_host, which /etc/wireless/modem exists to prevent. When the usb0 network interface appears or goes, it runs /lib/mdev/usbnet.sh.
  • /lib/mdev/usbnet.sh runs ifup on add and ifdown -f on remove, serialized under one flock, so a pull while DHCP is still running cannot leave stale ifupdown state. 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 give it the camera's ipaddr.
  • interfaces.d/usb0 sets metric 10, so a tethered phone never takes the default route or DNS from the cable.
  • S38mdev replays the USB interfaces' add events after mdev -s. A phone already tethering at power-on was announced before mdev became the hotplug helper, and mdev -s walks only devices with a device node.

Hardware tested on

hi3536dv100 (lab NVR board, hi3536dv100_lite, kernel 4.9.37). The phone was a Samsung Galaxy A02 (SM-A022G, Android 11) in USB tethering mode, plugged into the board's USB port. I installed the three changed files over a local build of hi3536dv100_lite in the overlay, then removed them afterwards.

"Replug" below means echo 1-1 > /sys/bus/usb/drivers/usb/unbind followed by bind. This re-enumerates the phone without anyone touching it. A physical replug and toggling tethering on the phone were not tested.

Not tested: a phone that tethers over NCM, and the ECM class.

Evidence

Before (stock image, phone tethering):

/sys/bus/usb/devices/1-1:1.0 e0/01/03 RNDIS Communications Control drv=
/sys/bus/usb/devices/1-1:1.1 0a/00/00 RNDIS Ethernet Data drv=
lo:
eth0:

Before, with modprobe rndis_host and ifup usb0 by hand, then a replug:

4: usb0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop qlen 1000
(no address, no udhcpc)
ifup: interface usb0 already configured

After, a cold boot with the phone already tethering. Nothing was run by hand:

usb 1-1: new high-speed USB device number 2 using ehci-platform
rndis_host 1-1:1.0 usb0: register 'rndis_host' at usb-11010000.ehci-1, RNDIS device, a6:6b:89:c7:59:41
/sys/bus/usb/devices/1-1:1.0 e0/01/03 drv=rndis_host
/sys/bus/usb/devices/1-1:1.1 0a/00/00 drv=rndis_host
    inet 192.168.120.251/24 brd 192.168.120.255 scope global usb0
default via 10.216.128.1 dev eth0
default via 192.168.120.211 dev usb0
  783 root      0:00 udhcpc ... -p /var/run/udhcpc.usb0.pid -i usb0

After, three replugs:

-- unplugged: usb0=0 ifstate=0 dhcp=0
== replug 1: addr=192.168.120.181/24 dhcp=1 ifstate=1
-- unplugged: usb0=0 ifstate=0 dhcp=0
== replug 2: addr=192.168.120.138/24 dhcp=1 ifstate=1
-- unplugged: usb0=0 ifstate=0 dhcp=0
== replug 3: addr=192.168.120.102/24 dhcp=1 ifstate=1
ping -I usb0 1.1.1.1: round-trip min/avg/max = 12.408/12.853/13.299 ms

After the review round: the route order on a cold boot with the phone tethering, and pulls during DHCP (USB unbind at 0 to 3.5 s after the plug, 10 trials, all clean). The full output is in the review threads.

default via 10.216.128.1 dev eth0
default via 192.168.120.211 dev usb0  metric 10
1.1.1.1 via 10.216.128.1 dev eth0  src 10.216.128.99
800000: after_pull ifstate=0 udhcpc=0 | next_plug=192.168.120.101/24 udhcpc=1

The guard leaves anything that is not a tether alone:

# ACTION=add MDEV=eth0 sh -x /lib/mdev/usbnet.sh
+ readlink /sys/class/net/eth0/device/driver      (../bus/platform/drivers/hisi-femac)
+ exit 0

/dev/bus/usb/001/* nodes are still created. STRICT=1 test_shell_parse.sh and test_strip_shell_comments.sh pass.

Local cross build of hi3536dv100_lite from this branch, at 8c9a939:

- uImage: [1708KB/2048KB]
- rootfs.squashfs: [4820KB/5120KB]

Scope

  • No kernel patches under general/package/all-patches/linux/ (those go to OpenIPC/linux)
  • No files specific to a single retail camera model (those go to OpenIPC/builder)
  • No probing or bring-up tooling (that goes to OpenIPC/ipctool)
  • Nothing under general/overlay/ or in a shared load_<vendor> script hardcodes a value specific to my board
  • Package sources come from an OpenIPC repository, and any version bump keeps at least the specificity of the pin it replaces (a new package should pin a full 40-character SHA)
  • No LD_PRELOAD, and no binaries that cannot be rebuilt from source
  • New code is selected by a defconfig, so CI actually builds it

Closes #1112

…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.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Load USB tethering drivers and manage usbN hotplug

🐞 Bug fix ✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Load RNDIS, ECM, and NCM drivers when matching USB tethering interfaces appear.
• Bring tethered usbN interfaces up on arrival and clear network state on removal.
• Replay USB interface events at boot so already-connected phones receive a driver.
Diagram

graph TD
  Boot["S38mdev replay"] --> USB["USB interface"] --> Mdev["mdev rules"] --> Driver["Tether driver"] --> Net["usbN interface"] --> Helper["usbnet helper"] --> Network["ifup / ifdown"]
Loading
High-Level Assessment

The targeted mdev rules fit the existing hotplug setup. A blanket MODALIAS rule was considered but would also load usbserial for modems; relying on boot-time network setup would miss later arrivals and replugs.

Files changed (3) +41 / -0

Enhancement (1) +8 / -0
mdev.confMatch tethering drivers and usbN hotplug events +8/-0

Match tethering drivers and usbN hotplug events

• Adds targeted modalias matching for RNDIS, ECM, and NCM interfaces rather than loading modules for every USB alias. Routes usbN events to the new network helper without creating a device node.

general/overlay/etc/mdev.conf

Bug fix (2) +33 / -0
S38mdevReplay USB interface events during mdev startup +7/-0

Replay USB interface events during mdev startup

• After installing mdev and scanning devices, re-emits add events for existing USB interfaces. This lets a phone tethering at power-on reach the new modalias rule.

general/overlay/etc/init.d/S38mdev

usbnet.shManage tethered network interfaces on add and remove +26/-0

Manage tethered network interfaces on add and remove

• Starts ifup asynchronously for usbN interfaces bound to the three tethering drivers, leaving unrelated interfaces alone. Runs ifdown -f on removal so a subsequent arrival can be configured again.

general/overlay/lib/mdev/usbnet.sh

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. A tethered phone can displace wired traffic ✓ Resolved
Description
usbnet.sh brings up the phone with DHCP, but the DHCP script assigns its default route the same
metric as the wired connection. When a phone is already tethering at boot, the USB setup starts
before the wired-network watcher; both connections can then install competing default routes instead
of preserving the wired connection's preference.
Code

general/overlay/lib/mdev/usbnet.sh[21]

+		ifup "$MDEV" </dev/null >/dev/null 2>&1 &
Evidence
The new hook runs ifup for a tether, while the usb0 stanza requests DHCP. The DHCP script gives
metric 0 to every interface except selected wireless interfaces and adds metric-0 default routes;
S38's replay precedes S40's wired-network watcher.

general/overlay/lib/mdev/usbnet.sh[15-21]
general/overlay/etc/network/interfaces.d/usb0[1-1]
general/overlay/usr/share/udhcpc/default.script[27-36]
general/overlay/usr/share/udhcpc/default.script[147-164]
general/overlay/etc/init.d/S38mdev[5-14]
general/overlay/etc/init.d/S40network[224-230]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Automatic USB tether DHCP introduces a default route at the same metric as the wired connection, including when tethering starts first at boot.
## Fix Focus Areas
- general/overlay/lib/mdev/usbnet.sh[19-21]
- general/overlay/etc/network/interfaces.d/usb0[1-1]
- general/overlay/usr/share/udhcpc/default.script[27-36]
## Recommended Fix
Assign tethering a higher default-route metric than the wired connection through the existing interface-stanza metric mechanism, and verify the boot-time route order.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. A second tether gets no network setup ✓ Resolved
Description
mdev.conf dispatches every usbN interface to usbnet.sh, but ifup "$MDEV" has a DHCP stanza
only for usb0. If another USB network interface already holds that name and the tether appears as
usb1, the hook's ifup fails without configuring an address and discards the diagnostic.
Code

general/overlay/etc/mdev.conf[28]

+$INTERFACE=usb[0-9]+ root:root 660 ! *(/lib/mdev/usbnet.sh)
Evidence
The event regex explicitly includes usb1 and higher, but the sourced network configuration has only
a usb0 stanza for USB interfaces. The hook passes the matched name directly to ifup and suppresses
its output.

general/overlay/etc/mdev.conf[28-28]
general/overlay/lib/mdev/usbnet.sh[15-21]
general/overlay/etc/network/interfaces[1-3]
general/overlay/etc/network/interfaces.d/usb0[1-1]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The event rule accepts usb1 and later interfaces, but only usb0 has a network stanza.
## Fix Focus Areas
- general/overlay/etc/mdev.conf[28-28]
- general/overlay/lib/mdev/usbnet.sh[19-21]
- general/overlay/etc/network/interfaces.d/usb0[1-1]
## Recommended Fix
Provide DHCP configuration for every USB interface name the hook accepts, or narrow the event rule to names that have configuration and surface unsupported names rather than silently attempting ifup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A quick unplug can race tether cleanup ✓ Resolved
Description
usbnet.sh backgrounds ifup on add but runs ifdown -f on remove without waiting for the earlier
operation. If the cable is removed during DHCP's foreground retry period, cleanup can finish before
setup does, leaving the interface's DHCP client or configuration state after removal and interfering
with the next plug.
Code

general/overlay/lib/mdev/usbnet.sh[21]

+		ifup "$MDEV" </dev/null >/dev/null 2>&1 &
Evidence
The new add and remove paths have no ordering mechanism. The shipped udhcpc configuration retries
before backgrounding, and the hook itself documents stale ifupdown state as the cause of failed
subsequent plugs. The exact state left by a particular timing depends on BusyBox's setup progress.

general/overlay/lib/mdev/usbnet.sh[3-8]
general/overlay/lib/mdev/usbnet.sh[19-25]
general/package/busybox/busybox.config[1031-1031]
general/package/busybox/busybox.config[910-910]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An add starts a background ifup, so a subsequent remove can run ifdown before setup has finished.
## Fix Focus Areas
- general/overlay/lib/mdev/usbnet.sh[19-25]
## Recommended Fix
Serialize add and remove for each interface, ensuring removal waits for any in-progress ifup and then tears down the final DHCP and ifupdown state. Test unplugging during the initial DHCP retries.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread general/overlay/lib/mdev/usbnet.sh Outdated
Comment thread general/overlay/etc/mdev.conf Outdated
Comment thread general/overlay/lib/mdev/usbnet.sh Outdated
…ring 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.
@widgetii
widgetii merged commit 141a940 into master Oct 21, 2026
141 checks passed
@widgetii
widgetii deleted the mdev-usb-tethering branch October 21, 2026 12:23
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.

HI3535dv100 cannot connect to the USB shared network

1 participant