Resetting USB / Ethernet Adapters from Linux

I bought a cheap UGREEN USB 3.0 Gigabit Ethernet Adapter, which is based on the ASIX AX88179 chipset. I found that it would periodically (usually while under load) stop transmitting/receiving packets, and shortly after that, its LEDs would turn off. Apparently AX88179 adapters are often flakey like this.

Cycling the Linux network interface running on top of the adapter using ip link set dev eth0 down and ip link set dev eth0 up didn’t get it working again. This is unfortunate since I run PPP over this ethernet link to connect to the internet. It seemed like the only fix was to reboot the machine.

However, I did find a solution to soft-reset the adapter by using the USBDEVFS_RESET ioctl system call.

To do this once-off, first find the USB bus/device IDs for the adapter:

% lsusb |grep AX88179
Bus 002 Device 001: ID 0b95:1790 ASIX Electronics Corp. AX88179 Gigabit Ethernet

The next trick is to send the ioctl. Unfortunately the GNU coreutils package doesn’t provide a utility for this, but if you have Python installed you can make a short script:

#!/usr/bin/python3
# in ioctl.py
import fcntl, sys

# defined in linux/usbdevice_fs.h
USBDEVFS_RESET = 21780

with open(sys.argv[1], "wb") as fd:
  fcntl.ioctl(fd, USBDEVFS_RESET, 0)

Then call the script with the appropriate device:

% sudo python3 ioctl.py /dev/bus/usb/002/001

Automating the reset

I don’t want to ssh into this machine and reset the adapter manually every time it breaks. Instead, I want it to detect when the adapter is dead and reset it accordingly.

The first thing to do is get a stable device name for the adapter, by adding a udev rule:

# /etc/udev/rules.d/20-ax88179.rules
SUBSYSTEM=="usb", ATTR{product}=="AX88179", ATTR{idVendor}=="0b95", SYMLINK+="ax88179"

This will get picked up on the next reboot. Until then, run:

sudo udevadm control --reload-rules && sudo udevadm trigger

Instead of using the Python script from above, I built and installed the ioctl utility at /usr/local/bin/ioctl. This is a more general purpose C program for making ioctl syscalls.

Linux doesn’t realize the ethernet link or adapter is dead, but pppd does terminate the PPP session. So I modified my systemd ppp service config to unwind the ethernet stack and reset the adapter whenever pppd exits. In some cases this will be overkill, but there doesn’t seem to be a good way to distinguish between “the USB adapter is wedged” and “my RSP peer is down”.

# in /etc/systemd/system/pppd@ppp0.service
# ...

[Service]
ExecStartPre=-/bin/ip link set eth1 up
ExecStartPre=-/bin/ip link add link eth1 eth1.2 type vlan id 2
ExecStart=/usr/sbin/pppd call %I linkname %i updetach maxfail 2
ExecStop=/bin/kill $MAINPID
ExecStopPost=-/bin/ip link set eth1 down
ExecStopPost=-/usr/local/bin/ioctl /dev/ax88179 USBDEVFS_RESET
ExecReload=/bin/kill -HUP $MAINPID
# ...

Note that the kernel documentation on USBDEVFS_RESET has a warning that it should be avoided due to bugs in usbcore. That warning has been around since before 2005 (the start of the kernel’s git history), and in practice I haven’t had any problems.

Leave a Reply

Your email address will not be published.