板子上能用的原生网口只有一个,串口占了调试通道,剩下的 USB 口还空着——这是我接手这个 RK3399 项目时看到的第一眼硬件布局。需求很朴素:同一台设备要同时接一路内网采集、一路外网回传,还得给调试电脑留个口,一个千兆原生口显然不够。翻了一圈方案,加交换机、换带多网口的核心板、上 USB 以太网适配器,三条路里最省钱、改动最小、也最容易回退的就是最后一条:插一个 USB 以太网网卡,把内核里 usbnet 那一套驱动跑起来。听起来像插上就能用,实际动手才发现,RK3399 这种嵌入式平台上"USB 以太网编译和安装"这件事,卡人的地方从来不是插拔,而是内核配置没开、模块版本对不上、固件没放对位置、开机不自动加载这几步。这篇就把我从零到跑通、再到稳定压测的完整过程摊开写,包括选型时踩的坑、只编单个模块的省时做法、以及在板子上做双网口路由的那点经验,适合正在折腾 RK3399 或类似 ARM64 平台、需要扩展网络接口的同行参考。
1. 先把需求捋清楚:为什么非得给板子挂个 USB 以太网
1.1 板载原生网口不够用的三个真实场景
很多教程一上来就讲编译,跳过了"为什么",结果读者照着敲完命令,驱动加载了,却不知道自己要拿它干什么,后面配 IP、配路由全抓瞎。我先说说我这边的三个场景,基本能覆盖大部分人的需求。
第一种是多网段隔离。设备一头接现场的内网采集设备,网段是 192.168.1.x,另一头要把数据回传到上层服务器,网段是 10.10.x.x,两个网段物理隔离、不能互通。只有一个网口的话,要么加一台交换机再做 VLAN,要么就得来回改配置,前者增加成本,后者根本没法同时在线。
第二种是调试口独立。开发阶段我习惯用 SSH 连板子,但一旦我在原生网口上改 IP、改路由、重启网络服务,SSH 必断,人就被踢出去了,只能跑去接串口。多一个 USB 网口专门留给调试链路,改另一个口的配置时心里踏实得多,这个习惯我保持了很多年,救过不少次现场。
第三种是功能验证与备用链路。有些项目要求主链路故障时能自动切到备用通道,USB 网卡插上去就是现成的第二张网卡,配个路由 metric 就能做主备切换,验证成本极低。而且 USB 网卡坏了大不了换一个,几十块钱的事,比返修主板划算太多。
把这三个场景想明白,后面所有的配置选择才有依据——你要的是"能通",还是"能扛住长时间跑",决定了你是随手 insmod 一下,还是老老实实做开机自动加载和稳定性测试。
1.2 芯片选型:从 AX88772 到 RTL8153 的取舍
USB 以太网适配器市面上五花八门,外壳长得都差不多,里面的主控芯片差别很大。买之前一定看商品详情页有没有写芯片型号,没写的直接跳过,那种大概率是便宜货,内核里驱动支持得零零碎碎。我手上这几类都用过,整理成一张表:
| 芯片方案 | 接口速率 | 内核配置项 | 是否需固件 | 实际体验 |
|---|---|---|---|---|
| AX88772 系列 | 百兆 | CONFIG_USB_NET_AX8817X | 否 | 老而稳,几乎不需要折腾,适合只要通网的场景 |
| AX88179 系列 | 千兆 | CONFIG_USB_NET_AX88179_178A | 否 | 千兆里兼容性最好的之一,USB3.0 下能跑起来 |
| RTL8152/8153 系列 | 百兆/千兆 | CONFIG_USB_RTL8152 | 部分型号需要 | 性能不错,但固件缺失时会加载失败,坑最多 |
| CDC ECM / NCM 类 | 视设备而定 | CONFIG_USB_NET_CDCETHER / CDC_NCM | 否 | 免驱类,通用性好,性能一般 |
选型上我的建议很直接:如果只是想给板子多加一个网口,优先选 AX88179 或 AX88772,内核里配置项单一、不依赖外部固件、加载即用,省心。如果你追求千兆吞吐并且能接受折腾,RTL8153 系列性能确实更好,但要提前把固件文件准备好,否则 modprobe 之后 dmesg 里会出现固件加载失败的报错,网卡根本出不来。
还有一个容易忽略的点:USB 口的速度等级决定上限。RK3399 平台上通常有 USB2.0 和 USB3.0 接口,千兆网卡插在 USB2.0 口上,理论带宽 480Mbps,实际跑个 280~330Mbps 就到顶了,网卡再好也没用。插之前先确认你插的是哪个口,别回头测出来只有两百多兆就怀疑驱动有问题。
1.3 "内核支持"和"能用"之间隔着好几道坎
新手最容易有的一个误解是:内核源码里明明有 r8152.c 这个文件,为什么板子上插了网卡没反应。原因很现实——源码里有不等于编译进去了。厂商给的默认 defconfig 为了控制镜像体积,通常只保留了最基础的一批驱动,USB 网络这一块经常是关的,或者是编成模块但从来没往文件系统里装过。
所以完整链路其实是四步:内核配置里把对应选项打开、编译出 .ko 文件、把 .ko 放到板子文件系统的正确目录、让系统在启动时自动加载它。任何一步断了,表现都是"插上网卡没反应",而这四种"没反应"的排查方向完全不同。后面我会按这四步分开讲,每一步都给出确认方法,这样你卡住的时候知道自己在哪一段。
顺便提一句,有些厂商的 BSP 内核会把 usbnet 编进内核(built-in),但具体某个网卡驱动编成模块。这种情况下你只要补那一个 .ko 就行,不用重编整个内核,能省下大量时间。所以在动手之前,先摸清楚现状,比闷头编译重要得多。
2. 编译前的准备:把内核源码和工具链理顺
2.1 先确认板子上跑的内核和你手上的源码是不是同一份
这一步我吃过亏,必须放在最前面说。模块和内核之间有一条硬约束:vermagic 必须完全一致,包括内核版本号、SMP 是否开启、抢占模式、模块版本校验这些信息。版本号差一个字符,insmod 就会甩给你一句 "invalid module format",然后你盯着源码看半天也看不出问题。
确认方法很简单,板子上执行:
uname -r cat /proc/version把输出记下来,比如4.4.189或者5.10.110,然后到源码根目录看 Makefile 前几行的VERSION、PATCHLEVEL、SUBLEVEL是不是对得上。对不上就说明你手上的源码不是板子上跑的那份,要么去问厂商要对应版本的 BSP 包,要么就接受"编出来的模块装不上"这个结果。
还有一种情况更隐蔽:版本号一样,但配置文件不一样。比如板子上跑的内核开了CONFIG_PREEMPT,你手上这份源码的 .config 没开,编出来的模块 vermagic 里会多一个preempt或者少一个,照样装不上。所以最稳妥的做法是直接从板子上把当前配置抠出来:
# 板子上执行,前提是内核开了 IKCONFIG zcat /proc/config.gz > board_defconfig如果/proc/config.gz不存在,那就只能找厂商要编译这份内核时用的 defconfig 文件。拿到之后放到源码的arch/arm64/configs/目录下,后面配置内核时直接用它做基线,能最大程度保证一致性。
2.2 交叉编译工具链的选择与验证
RK3399 是 ARM64 架构,编译要在 x86 主机上做,需要 aarch64 的交叉工具链。工具链的选择上不用太纠结,主流的两条路:一是用厂商 BSP 包里自带的预编译工具链,路径一般在prebuilts/gcc/linux-x86/aarch64/下面;二是用系统包管理器装一个,比如 Ubuntu 上的gcc-aarch64-linux-gnu。
我个人的偏好是优先用厂商自带的,理由很实际:内核里有些汇编代码对编译器版本比较敏感,换个版本可能出现奇怪的编译错误或者运行时行为差异,厂商既然验证过,就别给自己找麻烦。当然如果你只是编个 USB 网卡模块,用系统工具链一般也没问题。
装好之后先验证一下:
aarch64-linux-gnu-gcc --version能打印出版本信息就说明 PATH 里能找到。如果用的是厂商工具链,通常需要手动加路径:
export PATH=$PWD/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin:$PATH export CROSS_COMPILE=aarch64-linux-gnu- export ARCH=arm64这三个环境变量建议写进一个envsetup.sh脚本里,每次开新终端 source 一下,省得反复敲。ARCH=arm64这一条千万别漏,漏了的话 make 会按主机架构去编译,报一堆莫名其妙的错误。
2.3 一次干净的基线编译,别省这一步
在改配置之前,我强烈建议先做一次原样编译,确认工具链、源码、配置这套组合本身是能跑通的。这步看着多余,其实是在排除变量:如果基线都编不过,你改完配置编不过,就分不清是配置改错了还是环境本来就有问题。
命令大致是这样,具体目录名按你的源码结构来:
cd kernel make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- your_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image dtbs第一次编译时间会比较长,四核八线程的机器上大概二三十分钟,看源码规模和机器性能。编完检查一下arch/arm64/boot/Image有没有生成,有就说明基线没问题。
如果这一步就报错,常见原因是缺依赖包。Ubuntu 上一般需要装bison、flex、libssl-dev、libncurses5-dev、bc这几个,缺哪个报错信息里都会写,照着装就行。还有个小坑:有些老版本内核配新版 gcc 会报-Werror相关的警告直接中断编译,这种可以在 make 后面加KCFLAGS="-Wno-error"临时绕过,但更推荐换个匹配的工具链版本。
3. 内核配置:把 USB 以太网驱动真正打开
3.1 menuconfig 里的关键路径与选项含义
配置入口用make menuconfig,图形界面里按这个路径找:Device Drivers→Network device support→USB Network Adapters。进去之后你会看到一长串选项,第一次看容易懵,我把几个关键的说清楚:
| 配置项 | 含义 | 是否必选 |
|---|---|---|
| CONFIG_USB_USBNET | usbnet 框架本体,所有 USB 网卡驱动的底座 | 必选 |
| CONFIG_MII | 媒体独立接口库,usbnet 依赖它 | 必选(通常默认已开) |
| CONFIG_USB_NET_AX8817X | AX88772 系列百兆方案 | 按需 |
| CONFIG_USB_NET_AX88179_178A | AX88179 系列千兆方案 | 按需 |
| CONFIG_USB_RTL8152 | RTL8152/8153 系列 | 按需 |
| CONFIG_USB_NET_CDCETHER | CDC ECM 免驱方案 | 按需 |
| CONFIG_USB_NET_CDC_NCM | CDC NCM 免驱方案 | 按需 |
| CONFIG_USB_NET_SMSC75XX | SMSC 75xx 系列 | 按需 |
CONFIG_USB_USBNET是底座,没有它上面那些具体驱动一个都编不出来,这个依赖关系在新手眼里经常被忽略——明明勾了 AX88179,编出来没有 .ko,就是因为它爹没开。
实际选的时候不用全开,按你手上的网卡芯片勾对应的就行,多开了只会让镜像变大、编译变慢。我一般会顺手把CONFIG_USB_NET_CDCETHER和CONFIG_USB_NET_CDC_NCM也打开编成模块,理由很朴素:免驱类设备太常见了,同事随手拿个网卡过来测试、或者临时要接个带网口的扩展坞,有这两个模块在手能省很多事。
3.2 编成模块还是直接编进内核
这个问题每次都会被问到,我的答案取决于你的使用场景:
编成模块(=m),好处是不用重新烧写内核镜像,把 .ko 拷到板子上 modprobe 就能用,出问题也好回退。缺点是文件系统里得留位置放 .ko,而且开机自动加载要多配一步。开发调试阶段我几乎全部选这个。
编进内核(=y),好处是开机就有,不用管模块加载,适合量产固件。缺点是每次改动都要重新烧内核,调试周期长,而且会让内核镜像变大几十到上百 KB。
还有一个折中做法值得推荐:usbnet 框架和 MII 编进内核(=y),具体网卡驱动编成模块(=m)。这样底座常驻,插不同网卡只需要加载对应的那一个 .ko,既灵活又不至于每次都动内核。这个组合我在好几个项目里都用了,很顺手。
配置改完记得保存,menuconfig 里选Save,默认写到.config。保险起见再确认一下:
grep -E "USB_USBNET|USB_NET_AX88179|USB_RTL8152|CONFIG_MII" .config输出里能看到=y或=m就说明写进去了。有时候图形界面里勾了但保存失败,就是权限或者磁盘问题,用 grep 确认一遍最踏实。
3.3 只编模块的省时办法
改完配置如果只编模块,完全不用重新编整个内核,这一步能省下大量时间。正确姿势是先做一次 prepare,然后指定目录编译:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_prepare make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/net/usb modulesmodules_prepare的作用是准备好编译模块所需的头文件和脚本,只需要跑一次。M=指定只编drivers/net/usb这个子目录,几十秒就能出结果。
有个坑必须提前说:用modules_prepare编出来的模块,有可能因为缺少Module.symvers而在加载时报 "unknown symbol"。原因是modules_prepare不会生成完整的内核符号表。解决办法是老老实实跑一次完整的内核编译(不用烧写),把Module.symvers生成出来,然后再编模块。这也是为什么我前面强调先做一次基线编译——它顺手就把符号表准备好了。
编译完成后,产物在drivers/net/usb/目录下:
ls -lh drivers/net/usb/*.ko正常情况下能看到usbnet.ko、mii.ko(如果 MII 编成模块的话)、ax88179_178a.ko或者r8152.ko。用modinfo检查一下版本信息对不对:
modinfo drivers/net/usb/ax88179_178a.ko | head -20重点看vermagic那一行,和你板子上uname -r的输出比对一下,一致才能往下走。
3.4 RTL8153 的固件准备,这是最容易被忽略的一步
如果你选的是 RTL8152/8153 系列,还有一个额外的活要干:准备固件文件。部分型号在初始化时需要从/lib/firmware/加载固件,缺了就会加载失败。
固件文件通常是这几个名字:rtl_nic/rtl8153a-2.fw、rtl8153a-3.fw、rtl8153a-4.fw、rtl8153b-2.fw等等,具体需要哪个取决于芯片的具体版本。这些文件一般在源码树的firmware/目录或者厂商的 BSP 包里能找到,也可以从上游的 linux-firmware 仓库获取。
放的位置是:
# 板子上的目标路径 /lib/firmware/rtl_nic/目录不存在就自己建。放好之后加载驱动,用 dmesg 确认固件加载成功。如果看到类似 "Direct firmware load failed" 的报错,八成就是文件名或者路径不对,仔细核对一下内核源码里r8152.c中写死的固件名,别猜。
4. 安装到板子:从拷贝到开机自动加载
4.1 传输通道怎么选,以及为什么别用 U 盘
把 .ko 传到板子上,常用三条路:网络(scp)、串口(通过终端软件的传输功能)、U 盘。我的建议是能用网络就别用别的,原因很实际:U 盘插拔容易碰到其他设备,而且有些文件系统挂载之后权限位会乱;串口传输速度慢还容易丢包。
前提当然是你板子现在的网络是通的。如果原生网口已经配好了,scp 一条命令搞定:
scp drivers/net/usb/*.ko root@192.168.1.100:/tmp/如果板子还没网,那就先接串口或者用 TF 卡拷过去,这个阶段忍一忍就行。另外提醒一句,拷贝之前先在板子上建个临时目录,别一股脑往/root里扔,后面清理的时候容易漏。
4.2 目录放置与 depmod,模块依赖怎么处理
模块的正确落点是:
/lib/modules/$(uname -r)/kernel/drivers/net/usb/注意版本号是从板子上uname -r动态取的,别手敲。放好之后跑一次depmod:
depmod -a $(uname -r)这一步的作用是扫描所有模块,把依赖关系写进modules.dep和modules.alias。为什么要做这一步?因为ax88179_178a.ko依赖usbnet.ko,usbnet.ko又依赖mii.ko,你直接 insmod 具体驱动会报 "unknown symbol" 之类的错。跑完 depmod 之后用modprobe加载,它会自动把依赖链按顺序拉起来,省事。
验证依赖关系有没有写进去:
modinfo -F depends ax88179_178a正常应该输出usbnet,mii之类的内容。如果是空的,说明 depmod 没扫到,检查一下文件是不是放对目录了、文件名后缀对不对。
注意:
depmod跑完之后,如果你又拷了新模块进去,必须重新跑一次,否则新模块的依赖关系和别名不会更新。这个坑我见过不止一次,表现为"模块明明在目录里,modprobe 却说找不到"。
4.3 加载、卸载与开机自动加载
手动加载先验证一遍:
modprobe ax88179_178a然后dmesg | tail -30看输出。成功的标志是能看到类似usb 1-1.2: new high-speed USB device、ax88179_178a 1-1.2:1.0 eth1: register 'ax88179_178a'这样的日志,并且ip link里能看到新接口。
卸载用modprobe -r ax88179_178a,注意如果接口还处于 up 状态,卸载会失败,先ip link set eth1 down。
开机自动加载有两种方式,我推荐第一种:
# 方式一:modules-load.d,干净利落 echo "ax88179_178a" > /etc/modules-load.d/usbnet.conf # 方式二:写入 /etc/modules(部分老系统用这个) echo "ax88179_178a" >> /etc/modulesmodules-load.d是 systemd 体系下的标准做法,配置文件里一行一个模块名,开机由 systemd-modules-load 服务统一处理,不依赖其他脚本的执行顺序。比往rc.local里塞 insmod 靠谱得多——rc.local 在有些发行版里默认是关的,你改完以为生效了,重启一看白干。
还有一种情况要考虑:热插拔。如果你的使用方式是"板子先开机,网卡后插",那就不能只靠开机加载。要么让驱动常驻(编进内核或者开机就 modprobe),要么配 udev 规则触发加载。实际上 usbnet 驱动加载之后是常驻的,检测到新设备会自动绑定,所以只要驱动在内存里,热插拔没问题。我一般就是开机直接 modprobe,插拔随意。
4.4 接口命名不叫 eth1 怎么办
现在不少发行版启用了 predictable network interface names,新插的网卡可能不叫eth1,而是一串类似enx00e04c123456的名字,中间那段是 MAC 地址。这不是出错,是命名规则变了。
如果脚本里写死了eth1,就会一路报错。两个选择:一是改脚本用新名字,二是把命名规则关掉:
# 关闭 predictable names,用传统的 ethX 命名 ln -s /dev/null /etc/systemd/network/99-default.link或者在/etc/udev/rules.d/下加一条规则,按 MAC 地址固定命名:
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:e0:4c:12:34:56", NAME="eth1"我偏向第二种,因为固定了名字之后,不管系统怎么升级、怎么改命名策略,这块网卡永远是eth1,脚本不用动。缺点是换一块网卡就得改规则,但生产环境里一般不会随便换。
5. 验证、配置与性能实测
5.1 dmesg 和 ethtool 要看哪几个关键信息
驱动加载完,第一件事是确认它真的被识别成了什么样的设备:
dmesg | grep -i -E "usb|eth" ethtool -i eth1ethtool -i的输出里重点看三个字段:driver是驱动名,得和你加载的模块对得上;version是驱动版本;bus-info是挂在哪个 USB 总线上,形如1-1.2:1.0。这个 bus-info 后面调电源管理的时候要用。
再看链路状态:
ethtool eth1输出里Speed显示 1000Mb/s 还是 100Mb/s、Duplex是全双工还是半双工、Link detected是不是 yes,一目了然。如果显示 100Mb/s,先别急着怀疑驱动,八成是网线的问题——老的五类线跑千兆会协商降速,换根超五类或者六类线再试。
还有一种情况是Link detected: no,但网卡确实插着,对端交换机也是好的。这种多半是 USB 供电不足,尤其在小体积的 HUB 上。表现是网卡枚举出来了,但 PHY 初始化不完整。换个带独立供电的 HUB 试试,或者直接插板子上的 USB3.0 口。
5.2 IP 配置与双网口路由
接口通了之后配 IP。先用 iproute2 那套命令,比老的 ifconfig 更清晰:
ip addr add 192.168.10.2/24 dev eth1 ip link set eth1 up ip route add 192.168.10.0/24 dev eth1如果要配成 DHCP 客户端:
udhcpc -i eth1 # 或者 dhclient eth1,看系统里装的是哪个双网口的核心问题是默认路由走谁。两个网口都配了网关的话,系统里会出现两条 default 路由,谁先加谁的 metric 小,流量就走谁,很容易出现"内网能通、外网不通"或者反过来。正确做法是只给一个口配默认网关,另一个口只加网段路由:
# 主链路走默认网关 ip route add default via 192.168.1.1 dev eth0 metric 100 # 副链路只走自己的网段,不抢默认路由 ip route add 192.168.10.0/24 dev eth1 metric 200如果你需要主备切换(主链路断开自动切副链路),可以在同一条 default 上配不同 metric:
ip route add default via 192.168.1.1 dev eth0 metric 100 ip route add default via 192.168.10.1 dev eth1 metric 200metric 小的优先,主链路 down 掉之后内核会自动切到 metric 大的那条。这套机制很简单但很可靠,我用了很多次,比装一堆路由协议软件轻量得多。
5.3 带宽实测与长跑稳定性
性能这块不测心里没底。用 iperf3 最方便,PC 端起服务端,板子上跑客户端:
# PC 端 iperf3 -s # 板子端,-t 60 表示跑 60 秒 iperf3 -c 192.168.1.50 -t 60参考数据:AX88179 插在 RK3399 的 USB3.0 口上,千兆链路,实测单向大约 850~940Mbps,双向同时跑会掉到各自四五百兆。同样的网卡插在 USB2.0 口上,顶天 300Mbps 出头。RTL8153 在 USB3.0 下能跑到 900Mbps 上下,差距不大,但小包性能通常略好一点。
长跑测试我一般跑 30 分钟以上,观察两个指标:一是吞吐有没有随时间下降,二是dmesg里有没有出现NETDEV WATCHDOG或者 USB reset 相关的日志。出现 USB reset 基本可以确定是供电或者线材问题,驱动本身很少是这个原因。
再补充一个容易被忽略的参数:关闭 USB 自动挂起。默认情况下内核为了省电,会让空闲的 USB 设备进入低功耗状态,网卡频繁进出这个状态时可能丢包或者断连。关闭方法:
echo -1 > /sys/bus/usb/devices/1-1.2/power/autosuspend路径里的1-1.2就是前面ethtool -i看到的 bus-info。要持久化的话,写成 udev 规则或者开机脚本,别指望手动 echo 一次就完事。
6. 踩坑实录与问题速查
6.1 常见问题速查表
下面这些是我这几年攒下来的,基本覆盖了 USB 以太网在 RK3399 上出问题的绝大多数情况:
| 现象 | 大概率原因 | 排查动作 |
|---|---|---|
| 插上网卡毫无反应,dmesg 无任何日志 | USB 口没供电或设备没枚举 | 换口、换线、看lsusb有无新设备 |
| 有 USB 枚举日志,但没有 net 接口注册 | 对应网卡驱动没加载 | lsmod看驱动在不在,modprobe手动加载 |
| modprobe 报 "module not found" | .ko 没放到/lib/modules/$(uname -r)/或没跑 depmod | 检查目录、重跑depmod -a |
| insmod 报 "invalid module format" | vermagic 不匹配 | 对比modinfo的 vermagic 和uname -r |
| modprobe 报 "unknown symbol" | 依赖模块没加载,或 Module.symvers 缺失 | 先加载 usbnet/mii,重编模块 |
| 驱动加载了但接口没有 IP,也拿不到 DHCP | 链路没起来或对端没开 DHCP | ethtool看 Link detected,ip link看状态 |
| RTL8153 加载失败,日志提示固件 | 固件文件缺失或路径不对 | 放到/lib/firmware/rtl_nic/ |
| 千兆网卡只协商到 100Mbps | 网线质量或对端口速率 | 换六类线,检查交换机端口配置 |
| 跑一段时间后断连,dmesg 有 USB reset | 供电不足或 USB 自动挂起 | 换带供电 HUB,关闭 autosuspend |
| 重启后网卡不见了 | 没配开机自动加载 | 写/etc/modules-load.d/配置 |
6.2 几条文档里不会写的经验
第一条:先lsusb,再谈驱动。很多人插上网卡第一步就去 modprobe,加载失败就开始怀疑内核配置。正确的顺序是先lsusb,看设备有没有被 USB 子系统枚举出来,能不能看到厂商 ID 和产品 ID。看不到的话,问题在硬件层——供电、线材、接口,跟驱动一点关系都没有。这一步能省掉大量无效排查。
第二条:别迷信"免驱"。商家标榜的"免驱"通常是指 Windows 下免驱,Linux 下能不能用完全看内核有没有对应的驱动。买之前查一下芯片型号在内核源码drivers/net/usb/目录下有没有对应的 .c 文件,比看商品详情页靠谱一百倍。
第三条:模块文件别只拷一个。依赖链上的usbnet.ko、mii.ko都要一起拷,少一个就加载失败。我见过有人只拷了ax88179_178a.ko,然后对着 "unknown symbol usbnet_generic_cdc_bind" 这种报错查了一下午。
第四条:改完内核配置一定要 grep 确认。menuconfig 图形界面偶尔会出现勾了没保存的情况,尤其是用 SSH 连过去跑的时候网络一抖。保存之后grep一下.config,几秒钟的事,能避免一次白编译。
第五条:量产固件里把驱动编进内核更省心。调试阶段用模块方便,量产时建议把 usbnet 和相关驱动都改成=y,省掉模块加载和文件系统依赖,减少一个可能出问题的环节。这个切换点我一般放在功能验证完成、开始做稳定性测试之前。
6.3 关于固件体积的一点取舍
最后聊个实际项目中会遇到的权衡。RK3399 平台的固件通常放在 eMMC 上,空间不算紧张,但把整个drivers/net/usb目录下所有驱动全编进去,加起来也就几百 KB,对固件体积影响很小。所以我个人的做法是:量产固件里把常见的几类 USB 网卡驱动全编进内核,包括 AX88772、AX88179、RTL8152、CDC ECM、CDC NCM。
理由是现场环境不可控,客户或者施工方随手拿来的网卡什么芯片都有可能,驱动不全就得远程指导人家换网卡,沟通成本远高于几百 KB 的存储开销。这个决定我在两个项目里做过,事后都被证明是对的——现场救急的时候,多一个驱动就少一次返工。
反过来,如果你的固件要刷到几万台设备上、升级带宽有限,那就精简到只留项目指定的那一款芯片,其余全关。这时候驱动选择就得跟着采购清单走,BOM 里写的是什么芯片,内核里就只留什么驱动,别留余地,避免现场插错网卡反而引发一堆兼容性问题的排查。
这套流程从选型到编译、从部署到压测,我在三块不同批次的板子上都走过一遍,每次卡住的地方都不太一样,但排查路径是通用的。真正让我少走弯路的不是记住了多少条命令,而是养成了"先确认现状、再动手改"的习惯——先uname -r看版本,先lsusb看设备,先lsmod看模块,把现状摸清楚,剩下的基本就是按图索骥。