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:配置与编译流程全景图
理论说了这么多,不如一张图来得直观。我把整个从配置到生成驱动模块的流程画了出来,你看看,是不是瞬间就清晰了?
调试小技巧:
第一次编译,我建议你先把所有USB相关驱动都编成模块(M)。这样即使某个PHY驱动加载时出错,你只需要 rmmod 它,重新insmod正确的就行,不用反复重新编译整个内核。这个习惯能帮你节省大量时间。