4、内核配置与编译:配置OTG支持、编译内核、模块化加载

好,到了动手环节了。内核源码拿到了,驱动代码也看了个大概,现在咱们得把它真正跑起来。

配置和编译,看起来就是敲几条命令的事,但其实坑不少。我刚开始做嵌入式那会儿,就因为漏了一个配置项,板子上的OTG死活枚举不出设备,折腾了两天。所以这一章,咱们稳扎稳打,把每一步都讲透。

配置内核OTG支持

我个人的习惯,配置内核从来不用 make oldconfig,太容易漏东西了。我会用 make menuconfig,图形界面,一目了然。

要让Linux内核支持OTG,核心就是把这几个配置项打开。说白了,你需要在菜单里找到它们,然后按Y键选中。

配置路径小抄: 下面这些路径是你必须记住的。我不建议你背,但得知道去哪儿找。
Device Drivers  --->
  [*] USB support  --->
    <*>   Support for Host-side USB
    <*>   USB Gadget Support  --->
      <*>   USB Gadget Drivers (OTG support)
      [*]   OTG support
    <M>   USB Dual Role Switch support  --->
      <M>   Generic USB Role Switch Driver
    *** USB Physical Layer drivers ***
      <*>   NOP USB Transceiver Driver / PHY
      <M>   PHY drivers for your specific SoC (e.g. PHY_EXYNOS_USB2)

为什么要这么配?我简单解释一下:

  • Host-side USB:这是主机模式的基础,必须编进去。
  • USB Gadget Drivers:设备模式(Device/Gadget)的核心。选了它,你的板子才能假装成一个U盘或者鼠标。
  • OTG support:这就是开关。不选它,驱动里那套HNP和SRP的代码就不会被编译。
  • USB Dual Role Switch:负责角色切换。我遇到过的问题是,只配了OTG支持但没配这个,插拔线缆后角色死活切不过来。
  • PHY drivers:物理层。没有它,你的USB口就是废的。

核心技术点:

OTG的核心在于动态切换。内核配置里必须同时打开 CONFIG_USB_OTG 和 CONFIG_USB_DUAL_ROLE 这两个宏。前者是协议支持,后者是硬件控制器支持。缺一不可。

关键配置项一览

记不住全路径没关系,我整理了一个表格,你对着这个找,快很多。

配置宏 功能描述 我的建议
CONFIG_USB_OTG 启用OTG协议层支持(HNP/SRP) 必须选中(*)
CONFIG_USB_DUAL_ROLE 启用双角色控制器框架 必须选中(*)或模块(M)
CONFIG_USB_GADGET 启用设备模式(Gadget功能) 必须选中(*)
CONFIG_USB_OTG_FSM 启用OTG有限状态机 建议选中(*),方便调试
CONFIG_USB_PHY 启用USB PHY层抽象 必须选中(*)
CONFIG_EXTCON 外部连接器(用于检测ID引脚) 必须选中(*)
注意: 别急着全编成模块。我建议 CONFIG_USB_OTG 和 CONFIG_USB_DUAL_ROLE 直接编进内核(*),不然根文件系统挂载前你的USB口可能是废的。

编译内核与模块

配置好了,下一步就是编译。这个过程没什么花哨的,但有几行命令得记牢。

# 1. 编译内核镜像和所有选为 * 的驱动
make -j$(nproc) Image   # 或者 zImage, 取决于你的架构

# 2. 编译所有选为 M 的模块
make -j$(nproc) modules

# 3. 安装模块到目标目录
make modules_install INSTALL_MOD_PATH=/path/to/your/rootfs

这里有个细节,我提一句:make modules_install 会把编译好的.ko文件拷贝到 lib/modules/$(uname -r)/ 目录下。如果你是用NFS挂载根文件系统调试,这一步特别重要。

我自己在项目上吃过一个亏:编译完内核,匆匆忙忙烧进去,结果发现 dwc3.ko 没拷进去,系统启动后直接内核报错,找不到USB控制器。所以,我建议你养成习惯,每次编译完,都检查一下目标目录下有没有生成 dwc3.ko 或者 musb.ko 这类文件。

模块化加载 vs 内置

配置的时候,每个驱动都有三个选项:[*]、[M] 和 [ ]。怎么选?我的经验是这样:

  • 核心框架(如双角色框架、OTG状态机):用 [*],必须内置。因为这些是在系统初始化早期就需要的东西。
  • 控制器驱动(如dwc3、musb):用 [M],模块化。方便热插拔,也方便调试时单独替换。我在个人项目里,90% 的情况都会编成模块。
  • PHY驱动(如你的SoC特定PHY):如果系统只有一种USB PHY,我建议内置。如果有多种(比如同时有USB2和USB3),就用模块。

避坑小贴士:

我曾经把一个PHY驱动编成模块,但忘了把它加到 modules.builtin 或 modules.dep 里。结果系统启动时,控制器驱动先加载了,发现PHY没准备好,直接报错退出了。嗯,这问题的排查过程,得花掉你一个下午。

SVG:配置与编译流程全景图

理论说了这么多,不如一张图来得直观。我把整个从配置到生成驱动模块的流程画了出来,你看看,是不是瞬间就清晰了?

OTG内核配置与编译流程 配置与编译整体流程 1. 获取内核源码 2. make menuconfig 3. 配置OTG支持 4a. make Image 4b. make modules 5. 安装内核镜像 5. 安装模块到rootfs 最终产出:支持OTG的内核 + dwc3/musb.ko等模块 提示:模块依赖关系 dwc3.ko ← phy.ko ← extcon.ko

调试小技巧:

第一次编译,我建议你先把所有USB相关驱动都编成模块(M)。这样即使某个PHY驱动加载时出错,你只需要 rmmod 它,重新insmod正确的就行,不用反复重新编译整个内核。这个习惯能帮你节省大量时间。