我一直在用树莓派连接有线网络,然后令树莓派放出 WiFi 供台式电脑连接,这个流程是完全可以工作的,但美中不足的是树莓派放出的 WiFi 只有大概 50 Mbit/s 的速度,而树莓派所连接的有线网络是 100 Mbits/s,相当于是白白给自己的网络体验打了个对折。
思虑良久(约一年),终于下定决心买下了一款 USB 转 RJ45 接口的 USB 网卡,我所购买的是裕合联的 USB3.0 有线网卡千兆铝合金,型号 YHL-U5503BK。
把网线、网卡、树莓派、台式电脑都接上,🤔,基本上是工作的……具体情况大概是:
- 在树莓派开机时,把 USB 网卡插上,能正确识别到新的网卡,也可以进行 IP 等的配置。
- 在树莓派关机时,把 USB 网卡插上,再开机,网卡被识别为一个大容量存储设备,网络配置自然也不能进行。
在一系列的尝试过后,发现在网卡被识别为大容量存储设备时,使用 usbreset 0bda:8151
,可以使其重新被识别为网卡,此时在 journalctl 的系统日志中也可以发现 USB 设备断开连接并且同一个接口中又新添加了一个设备,同时要注意到 Vendor ID 和 Product ID 也发生了改变。
$ lsusb -tv
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
ID 1d6b:0003 Linux Foundation 3.0 root hub
|__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
ID 152d:1561 JMicron Technology Corp. / JMicron USA Technology Corp. JMS561U two ports SATA 6Gb/s bridge
|__ Port 2: Dev 3, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
ID 0bda:8151 Realtek Semiconductor Corp. RTL8151 Adapteon Business Mobile Networks BV
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
ID 1d6b:0002 Linux Foundation 2.0 root hub
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
ID 2109:3431 VIA Labs, Inc. Hub
$ sudo usbreset 0bda:8151
Resetting USB 10/100/1000 LAN ... failed [No such device]
$ lsusb -tv
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
ID 1d6b:0003 Linux Foundation 3.0 root hub
|__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
ID 152d:1561 JMicron Technology Corp. / JMicron USA Technology Corp. JMS561U two ports SATA 6Gb/s bridge
|__ Port 2: Dev 4, If 0, Class=Vendor Specific Class, Driver=r8152, 5000M
ID 0bda:8153 Realtek Semiconductor Corp. RTL8153 Gigabit Ethernet Adapter
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
ID 1d6b:0002 Linux Foundation 2.0 root hub
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
ID 2109:3431 VIA Labs, Inc. Hub
在发现 usbreset 可以使其正常工作以后,便在 /etc/udev/rules.d/90-my-usbrj45.rules
中使用下面这一行配置,在 0bda:8151
添加时,直接对其 usbreset,然后就能够正常使用了,
ACTION=="add", ATTR{idVendor}=="0bda", ATTR{idProduct}=="8151" RUN+="/usr/bin/usbreset 0bda:8151"
不过虽然解决了,依然感觉不是很优雅,询问群友得知,在连接时先被识别为大容量存储设备、然后可以切换为其他工作模式而被识别为另一种设备的行为,是所谓的免驱硬件常见的工作方式,一般在 Windows 下,设备被识别为大容量存储设备后,系统可以从此设备中拿到驱动安装文件,安装以后再切换到实际工作的模式。在 Linux 下 usb_modeswitch 可以触发硬件工作模式的切换。
Several new USB devices have their proprietary Windows drivers onboard, most of them WAN dongles. When plugged in for the first time, they act like a flash storage and start installing the Windows driver from there. If the driver is already installed, it makes the storage device disappear and a new device, mainly composite with modem ports, shows up.
但是在直接使用 usb_modeswitch 时,一条“没有给定切换方法”的警告有点令人奇怪,并且重新 lsusb 查看发现并没有什么效果,大容量存储设备还是大容量存储设备。
$ sudo usb_modeswitch -v 0bda -p 8151
Look for default devices ...
Found devices in default mode (1)
Access device 004 on bus 002
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 255
Warning: no switching method given. See documentation
-> Run lsusb to note any changes. Bye!
不过既然裕合联是个小品牌,但是它可能用的东西是通用模块或者是其他大厂的下游也说不定,印象里面 VID:PID 是唯一的所以以之作为关键字应该可以找到有用的信息。
果不其然以 usb_modeswitch 0bda 8151
为关键字找到了 这个 和 这个,其中得到的下面的命令就能够切换工作模式。
sudo usb_modeswitch -v 0bda -p 8151 -V 0bda -P 8152 -M 555342430860d9a9c0000000800006e0000000000000000000000000000000
此外为了得到一个较有意义的网卡名称在 udev 中添加了一条配置将网卡名称改为 usbrj45,同时在符合期望 VID 和 PID 的情况下执行上面的切换运行状态的命令,于是最后的结果就是创建了一个 /etc/udev/rules.d/90-my-usbrj45.rules
,文件内容如下,其中 MAC 地址被抹去:
# persistent link nane of usbrj45
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="xx:xx:xx:xx:xx:xx", NAME="usbrj45"
ACTION=="add", ATTR{idVendor}=="0bda", ATTR{idProduct}=="8151" RUN+="/sbin/usb_modeswitch -v 0bda -p 8151 -M 555342430860d9a9c0000000800006e0000000000000000000000000000000"
收获 🔗
Linux 选择驱动的方式 🔗
经过这一大圈的调查,了解到 VID (Vendor ID)是 USB 厂商识别码,每个厂商都会有一个 ID,其唯一性由 USB-IF 负责,而 PID (Product ID) 就是厂商自己这一款产品的 ID,其唯一性由厂商自己负责。
所以可以认为对于一个 USB 设备,其 VID:PID 的组合是唯一的,于是 Linux 通过既定格式组合这两个属性并追加其他属性,得到一个长长的字符串,随后到 /lib/modules/$(uname -r)/modules.alias
中寻找能够匹配这个字符串的模块,匹配的模块会被加载来操控硬件设备。
在我的网卡的例子中,把 /etc/udev/udev.conf
中 udev_log
设置为 debug
后可以从 journalctl 中看到被组合得到的字符串,像这样:
2-2:1.0: hwdb modalias key: "usb:v0BDAp8151d3100dc00dsc00dp00ic08isc06ip50in00"
2-2:2.0: hwdb modalias key: "usb:v0BDAp8153d3100dc00dsc00dp00ic02isc06ip00in00"
2-2:2.1: hwdb modalias key: "usb:v0BDAp8153d3100dc00dsc00dp00ic0Aisc00ip00in01"
然后这个字符串能够匹配到 modules.alias 中的这两个模块,于是模块被加载并用来操控硬件:
$ grep -e v0BDAp8151 -e v0BDAp8153 /lib/modules/$(uname -r)/modules.alias
alias usb:v0BDAp8153d*dc*dsc*dp*ic02isc06ip00in* cdc_ether
alias usb:v0BDAp8153d*dc*dsc*dp*ic02isc06ip00in* r8153_ecm
字符串拼接的细节和 USB 以外的其他设备的描述可以见 这里
关于 udev 🔗
udev 能够在每次添加或者移除设备时收到信号,并可以进行如在 /dev 目录下创建额外的符号、更改设备名称等操作。
udev 有规则文件,可以在满足条件时修改设备的属性或运行特定的程序,规则文件细节可以看 udev(7) 。
在 raspbian 中,/usr/lib/udev/rules.d/40-usb_modeswitch.rules
中有大量的自动切换工作模式的规则,如果我所使用的 USB 网卡在这里也有写好的规则的话,这篇文章就不会出现了。
参考 🔗
- switch codes for Realtek USB ethernet adapter - USB_ModeSwitch
- [SOLVED] [RTL8151] Ethernet not working via multiport adapter - Support / Network - Manjaro Linux Forum
- ubuntu - How does Linux know how and what drivers to install in a new installation - Unix & Linux Stack Exchange
- usb_modeswitch(1) - Linux man page
- networking - How to automate usb_modeswitch? - Ask Ubuntu
- udev(7) - Linux manual page