记录一次USB虚拟网络问题排查

背景介绍

项目是用yocto构建的,在升级kernel及yocto后(见https://www.cnblogs.com/ma-yangbiao/p/19149251), 发现某些功能不能正常工作。

问题介绍

目标机是某个不常用的x86平台,该x86 盒子通过USB连接MDM9150, 在该x86盒子上跑某个应用程序过程中,发现没有像预期一样建立和初始化USB虚拟网口custom_usb0。

调试

由于qxdm log在该目标机上受限。加入一些文本打印,重新编译运行,进一步调试发现是因为我们一个管理网络的服务ioctl返回错误,这个ioctl是自己客制化的驱动代码里负责处理的,

于是把相关的驱动编译成模块然后手动加载进内核,其中现象如下

$ sudo insmod ./custom_usb.ko

insmod: ERROR: could not insert module custom_usb.ko: Unknown symbol in module

$ dmesg | tail

复制代码
[  183.497776] custom_usb: Unknown symbol usbnet_suspend (err -2)
[  183.497778] custom_usb: Unknown symbol usbnet_start_xmit (err -2)
[  183.497793] custom_usb: Unknown symbol usbnet_stop (err -2)
[  183.497797] custom_usb: Unknown symbol usbnet_disconnect (err -2)
[  183.497801] custom_usb: Unknown symbol usbnet_probe (err -2)
[  183.497804] custom_usb: Unknown symbol usbnet_resume (err -2)

这几个符号都来自linux kernel 驱动代码,具体在kernel/drivers/net/usb/usbnet.c,由CONFIG_USB_USBNET决定是否编译,但是在我的defconfig中CONFIG_USB_USBNET已经被显示设置位y了。

于是在我的defconfig把CONFIG_USB_USBNET=m,重新编译后待会手动加载看看是不是报错,

同时该文件中有几处打印用的netdev_dbg,为了方便调试有2种方法运行时看到打印信息,

  1. 把netdev_dgb改成netdev_warn
  2. 改CONFIG_DYNAMIC_DEBUG=y,这是为了打开编译时动态打印开关,运行时还需要打开运行时调试开关:echo 'file drivers/net/usb/usbnet.c +p' > /sys/kernel/debug/dynamic_debug/control,这样即使是netdev_dbg也能打印到dmesg

我选择方法1,

再次编译完执行insmod usbnet.ko发现没有错误,再手动insmod custom_usb.ko也没有错误.

说明代码没问题,回过头来在usbnet.c-> usbnet_init()里加一句打印 pr_err("usbnet_init called\n");

我的defconfig里再设置CONFIG_USB_USBNET=y,重新编译更新,再次重启发现没有这句打印, 有也就是说这个usbnet压根没有自动加载。

于是检查最终的defconfig检查一下,发现最终的.config里CONFIG_USB_USBNET=m

经过检查我的kernel recipe bbapend代码,有这么一段值得怀疑

do_preconfigure_prepend () {

cat {WORKDIR}/custom/defconfig \>\> {WORKDIR}/defconfig

}

如果我更新后,还有别的部分更新那就会覆盖我的配置。

于是尝试删除上述这三行,同时加入这2行,

SRC_URI += "file://custom/defconfig.cfg"

KERNEL_FEATURES += " custom/defconfig.cfg"

再次编译,查看.config.还是不起作用CONFIG_USB_USBNET=m.

阅读源码,Kconfig并没有提及CONFIG_USB_USBNET是否有依赖项,查看代码CONFIG_USB_USBNET实际是属于CONFIG_USB_NET_DRIVERS的一个子模块,所以尝试也配置

CONFIG_USB_NET_DRIVERS=y

CONFIG_DYNAMIC_DEBUG=y

再次编译,这次可以看到最终的.config里已经成功使能CONFIG_DYNAMIC_DEBUG=y了。说明kernel升级后这些配置项与旧版本里的配置不再一样。

再重新烧image,重新尝试,在解决了其它问题后功能终于正常。

总结

经学习 Yocto 的内核配置流程是:从 BSP 提供的 defconfig 开始,按顺序合并 KERNEL_FEATURES 和 SRC_URI 中的 .cfg 片段, 运行 merge_config.sh + olddefconfig 生成最终 .config。

usbnet的功能

最后分享一下,这次debug过程中加深了对usbnet的功能的认识,

usbnet.c 是一个通用的 USB 网络驱动核心,定义了一个结构体 struct usbnet,它包含struct net_device, struct usb_interface等指针,就像胶水一样把usb操作与网络的操作net_device联系在一起

它实现了一个"USB 网络设备"的抽象:负责处理 USB 传输、数据包队列,以及与内核的 net_device 的集成交互。通过usb虚拟网口收发数据等通用逻辑放在这里。

它被许多小型驱动程序共享:例如 cdc_ether、asix、r8152、rmnet_usb 等都依赖于 usbnet 作为基础,这些特定厂商的驱动只需重写/写少量部分即可。它将通用逻辑与硬件特性分离:核心部分负责收发(TX/RX)管理等通用逻辑,而其它特定的驱动则通过重写钩子函数来实现设备特定的行为。

它还导出了一些辅助函数,供其他驱动调用。

最终效果是:当你插入一个 USB 网卡时,内核可以将其识别为 usb0;而通过少量的驱动特定代码,也可以显示为例如 rmnet_usb0 等。

具体解读

复制代码
  模块初始化/退出
  usbnet_init():注册通用的 USB 驱动框架,准备好供子驱动调用。
  usbnet_exit():注销驱动,释放资源。

  设备探测与移除
  usbnet_probe():当匹配的 USB 设备插入时调用,分配并初始化 struct usbnet 和 struct net_device。
  usbnet_disconnect():设备拔出时清理资源。

  网络设备操作
  usbnet_open() / usbnet_stop():启动或停止接口,提交/取消接收 URB。
  usbnet_start_xmit():把上层网络栈传下来的数据包封装成 URB,提交给 USB core。
  usbnet_change_mtu()、usbnet_set_mac_address():常见的 net_device 操作。

  数据收发路径
  发送 (TX):start_xmit() → 构造 URB → usb_submit_urb() → 设备。
  接收 (RX):URB 完成回调 → rx_complete() → 调用 netif_rx() 把包交给内核协议栈。
  提供 rx_fixup() / tx_fixup() 钩子,供子驱动修正报文格式。

  电源管理与错误恢复
  处理 USB suspend/resume。提供 usbnet_tx_timeout() watchdog,避免传输卡死。