☰
联发科usb_hcd驱动解析:从HCD原理到内核编译与常见坑
2026/9/28 5:15:49 网站建设 项目流程

简介:面向联发科(MediaTek)平台的USB主机控制器驱动(USB HCD)源代码,主要服务于嵌入式驱动开发工程师、系统底层研究者以及需要为联发科设备定制USB功能的开发者。压缩包为rar格式,共6个文件,全部为C语言源码,整体仅55KB,代码量虽然精简,但围绕USB驱动核心功能组织,覆盖主机控制器驱动、USB Mass Storage类设备、日志调试等模块,便于快速定位具体实现。目前已有228人学习下载,对相关从业者具有一定参考价值。仔细阅读源码,可以掌握联发科平台在USB枚举过程、配置与接口选择、端点管理、中断处理、批量/中断/控制/同步传输、电源管理以及故障恢复等环节的设计思路;并可在此基础上进行驱动调试、性能瓶颈分析、新USB设备兼容性增强,或扩展OTG等高级功能,为实际项目提供稳定高效的驱动支持。

1. 联发科 usb_driver 里的 usb_hcd 到底是什么

拿到一份命名为usb_driver.rar_usb_hcd_联发科的压缩包,先别急着解压,它大概率不是某个小工具,而是一整套 SoC 平台上的 USB 主机控制器驱动源码。usb_hcd 全称是 Host Controller Driver,Linux USB 子系统里负责直接操作 USB 控制器硬件的层次,USB Core 只是通用框架,HCD 才是各芯片平台真正要做的部分。联发科平台把这份 HCD 单独打包,常见动机是官方 BSP 里对时钟、PHY、OTG 切换做了私有定制,主线内核自带的通用驱动覆盖不了。

这套东西解决的是三类具体诉求:板子起系统后 U 盘/网卡插上没反应、枚举报错刷屏、Type-C 口在 device 和 host 角色之间切换失败。读者对象基本是 BSP 工程师、驱动开发者和做公板转产品方案的硬件工程师。新手可以照后面的步骤把驱动编进内核,熟手能从踩坑记录里对照自己遇到的现象。标题里的 rar 只是分发形式,核心价值在那份 HCD 代码到底怎么写、怎么挂进内核、怎么调物理层参数。

2. 为什么联发科的 usb_hcd 不能直接用内核主线自带驱动

2.1 Linux USB 子系统的 HCD 层到底做了什么

Linux USB 驱动从上到下分四层:应用层、USB 设备驱动(class driver / function driver)、USB Core、HCD。HCD 是最贴近硬件的一环,它把 USB Core 下发的 URB(USB Request Block)翻译成控制器硬件能执行的寄存器操作、描述符读取、中断处理和 DMA 事务。USB Core 只关心「发一个 bulk 传输到端点 3」,至于这个传输在硬件上是通过 EHCI 队列、xHCI 命令 Ring 还是私有 DMA 通道发,全由 HCD 决定。

内核里每个 HCD 的核心是struct usb_hcd和配套的struct hc_driver。usb_hcd承载控制器状态、镜像根 hub、带宽与端点管理等通用数据,hc_driver才是厂商填回调函数的那个关键结构。usb_add_hcd()把两者绑定并注册到 USB Core,之后 USB 子系统才能通过bus->op调用到厂商写的底层函数。

hc_driver里最关键的几组回调:

回调函数职责
.irq处理控制器中断,把完成传输通知到 URB 层次
.start/.stop控制器上电/下电,初始化内部状态
.urb_enqueue/.urb_dequeue提交/取消一个 URB,例如 bulk 读、control 传输
.hub_status_data/.hub_control查询并响应根 hub 的端口状态变化事件

主线内核里的 ehci-hcd、xhci-hcd 也实现同一套接口,但设备树和 PHY 初始化流程每家 IP 完全不同。联发科平台的 usb_hcd 包通常同时包含hc_driver实现、PHY 驱动和 OTG role switch 联动代码,三块都齐了板子才能正常枚举。

2.2 联发科平台的差异点:时钟、复位和 PHY 联动

很多工程师第一次拿到 vendor HCD 会问同一个问题:IP 不是 DWC 的么,直接把dwc2或者dwc3使能不就行了?对,DWC 是通用 IP,但 SoC 集成时真正干的活是时序配置,这才是平台差异的根源。

联发科平台 USB host 跑起来至少依赖三样私有逻辑。第一是参考时钟,USB 2.0 的 48MHz 或 USB 3.0 需要的 SerDes 参考时钟未必是常开的,通常从 SoC 的 PLL 分出来,由时钟框架单独管理。第二是 PHY 初始化顺序,usb_phy_power_on()之前必须完成 pinctrl 把 DP/DM 引脚切到 USB 模式,否则 PHY 的差分信号根本出不来。第三是复位释放时机,控制器 IP 的 reset 信号往往与 PMIC 时序绑定,too early 或 too late 都会造成寄存器读回异常。

主线dwc2/dwc3驱动只负责 IP 内部数据通路,SoC 私有时钟、复位、PHY 上电由驱动自己拼凑,经常拼不齐。常见翻车现场是设备树里开了一个 USB 控制器,dmesg里也能看到usb 1-1: new high-speed USB device number 2,但设备永远枚举不出来,因为 PHY 根本没被正确初始化。vendor HCD 的价值就是把这三段私有逻辑固定在 probe 流程里。

2.3 vendor 代码和主线内核的分工

我一般会把联发科 HCD 源码当参考实现而不是替代品。正确策略是先确认目标内核版本,再看 vendor 包是基于哪个内核 tag 剪出来的。如果目标 SoC 有自己的主线支持,优先走主线驱动路线,vendor 代码用来对照参数;如果主线只支持到通用 DWC 层次,那就老老实实把 vendor HCD 作为主方案。

vendor HCD 包相比主线驱动多了不少「惯例性代码」,例如suspend/resume时对 PHY 的断电时序、睡眠唤醒时 USB 信号恢复、公有 pinctrl 复用等。这些代码在主线仓库里通常没有对应实现,直接删掉容易踩 PM 的坑。保留 vendor 代码的代价是维护压力:每次内核升级都要跟 HCD API 的变动做适配,特别是usb_phy相关的接口,内核版本一跳就经常编译不过。

3. 把 usb_hcd 编译进内核:从 rar 到可加载模块

3.1 先解包,确认这份源码对应的内核版本

拿到仓库代码第一件事不是改代码,而是先确认它面向哪个内核版本。解压后看drivers/usb/host/下面是否有对应的 vendor 目录,比如mtk_hcd/或mtxxxx_hcd/。这个目录里的Kconfig和Makefile会说明它挂在哪一层,MODULE_LICENSE和文件顶部的 Copyright 时间也能判断代码是否太老。

# 解压 rar,rar 在 Linux 下通常用 unrar 或 unar unrar x usb_driver.rar_usb_hcd_联发科.rar # 解开后确认内核源码树的根位置 cd usb_driver/linux/kernel/drivers/usb/host/ ls -l mtk_hcd/ # 看 Kconfig 和 Makefile 的配置名 grep -r "USB_MTK_HCD" mtk_hcd/Kconfig mtk_hcd/Makefile

注意这个包名里带「联发科」不一定代表代码只适配联发科芯片,很多第三方方案商也把公版代码标注成平台名。你需要比对compatible = "mediatek,..."和设备树里的实际 SoC 型号,确认匹配再继续。如果grep出来的配置项和设备树节点都对得上,说明源码包完整可编。

3.2 Kconfig 与 Makefile 匹配:以 CONFIG_USB_MTK_HCD 为例

vendor HCD 源码放进去之后必须让内核构建系统认识它。一套标准做法是在drivers/usb/host/Kconfig末尾追加一个tristate配置项:tristate表示既可以编进内核,也可以编成模块。驱动开发阶段用m更灵活,不用每次改代码都重烧 kernel。

config USB_MTK_HCD tristate "MediaTek USB Host Controller Driver" depends on USB && ARCH_MTK default n help MediaTek SoC USB host controller driver. Say Y here to support USB host functionality on MediaTek platforms.

ARCH_MTK这个依赖是关键。如果你在通用 x86 开发机上编这个驱动,ARCH_MTK不成立,配置项会被自动隐藏,make menuconfig里根本找不到。这时候可以在主 Makefile 或者板级 defconfig 中强制打开,也可以把depends on放宽成depends on USB临时验证编译。正式产品里我建议保留ARCH_MTK依赖,避免模块被错误装载到无关平台。

对应的 Makefile 片段一般放在drivers/usb/host/Makefile末尾:

obj-$(CONFIG_USB_MTK_HCD) += mtk_hcd.o mtk_hcd-objs := mtk_hcd_core.o mtk_hcd_phy.o mtk_hcd_otg.o

mtk_hcd-objs里写的是组成最终模块的所有.o文件,顺序不用管。只要 Kconfig 和 Makefile 里配置名一致,内核构建系统会维护依赖关系。改完这两文件先执行make menuconfig,在 Device Drivers → USB support → Host Controller Drivers 下找到MediaTek USB Host Controller Driver,按 M 编成模块。

3.3 给 hcd 配设备树节点:dr_mode 与 phys 是重点

设备树描述的是硬件连接关系,HCD 驱动通过platform_get_resource()拿地址和中断,通过devm_of_phy_get()拿 PHY 句柄。联发科 USB host 节点常见结构包含compatible、reg、interrupts、clocks、phys与dr_mode几个关键字段。

usb_host: usb@11280000 { compatible = "mediatek,mtk-hcd"; reg = <0x0 0x11280000 0x0 0x1000>; interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_LOW>; clocks = <&topckgen CLK_TOP_USB0>, <&apmixedsys CLK_APMIXED_USBPLL>; clock-names = "sys_ck", "ref_ck"; phys = <&u2phy0>; phy-names = "usb2-phy"; dr_mode = "host"; status = "okay"; };

dr_mode决定控制器工作在 host、peripheral 还是 otg。做 U 盘、4G 模块这类固定主机场景直接写"host"最省心;做 Type-C 双角色设备就得写"otg",同时要求驱动实现 role switch 回调。clocks一般要配两个,一个是控制器自身时钟,一个是 PHY 的参考时钟,漏掉参考时钟会导致枚举随机失败。

phys引用 PHY 节点,phy-names与驱动里的phy_get调用对应。如果驱动代码里调用的是devm_of_phy_get_by_index(),则不需要phy-names。建议打开时看一遍驱动 probe 函数里具体拿 PHY 的方式,实测经常出现名字不匹配导致EPROBE_DEFER。

3.4 编译与开机验证:从 dmesg 到 /sys/bus/usb

编译这一步要注意构建环境的内核版本必须与目标板 kernel source 对应。单独编一个 HCD 模块可以用:

# 假设内核源码目录是 ~/src/kernel export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make O=~/build/kernel mtk_hcd.ko # 拷贝模块到目标板 scp ~/build/kernel/drivers/usb/host/mtk_hcd.ko root@board:/lib/modules/5.4/

模块编好后进入目标板,先insmod mtk_hcd.ko,再看dmesg尾部输出。正常注册的 HCD 会打印mtk-hcd mtk-hcd.0: new USB bus registered与总线编号usb1。如果没出现这行,大概率是 probe 没执行或者设备树节点没匹配。

验证链路再走一步:插上 U 盘,看/sys/bus/usb/devices/下是否出现1-1这样的目录项。有目录但枚举失败,问题多半在物理层;目录都没有,说明 HCD 的根 hub 没起来,回头查中断和时钟。这一步能快速把问题归类到驱动还是硬件。

4. 联发科 usb_hcd 落地的典型坑与排查方法

4.1 现象:插上 U 盘 dmesg 完全没有任何反应

现象:dmesg尾部只有mtk-hcd mtk-hcd.0: new USB bus registered,插入 U 盘后连new full-speed USB device都不打印。lsusb也看不到任何设备。

原因:root hub 虽然注册成功,但 HCD 的hub_status_data没有检测到端口连接事件。通常是三个位置出问题:PHY 没有真正上电、控制器中断没有触发、或者 GPIO 上拉没有生效。联发科平台最常见的坑是 PHY 的power_on依赖clocks里的 ref_ck,设备树漏写ref_ck后 PHY 内部 PLL 停振,DP/DM 上无法形成正确的线路状态。

解决:插上设备后立刻查/proc/interrupts看 USB 控制器的中断计数是不是真的在涨。如果不涨,查/sys/kernel/debug/clk/clk_summary里usb_ref相关时钟是否enabled。我一般会直接在驱动hub_status_data入口加一条dev_info打印,排掉中断和 PHY 哪个先断掉。确认是 ref_ck 问题,在设备树里把CLK_APMIXED_USBPLL常开,或者改驱动在 probe 时显式clk_prepare_enable()。

这个坑的血泪教训是:vendor 包解出来之后不要着急编,先对照设备树把所有 clock 节点列出来,一次补全。断断续续补容易陷入「改了 dts 重烧,结果另一个时钟又没开」的循环。

4.2 现象:枚举到一半刷屏 device descriptor read error -71

现象:插上 U 盘后日志反复出现usb 1-1: new high-speed USB device number 4,紧接着usb 1-1: device descriptor read/8, error -71,如此往复循环,最后报告unable to enumerate USB device。

原因:-71 是EPROTO,表示协议层错误。host 发了 8 字节的 GET_DESCRIPTOR 请求,但 device 没有正确应答。问题可能在 host PHY 的发送信号质量,也可能在 device 端的应答时序。常见板级原因包括 USB 差分线走得太长、串了磁珠、PHY 驱动强度偏弱、供电瞬间跌落等。驱动层面还可能是maximum-speed配错,把高速设备当成全速设备来握手。

解决:先抓包确认错误发生在哪个 stage。usbmon能看到 URB 完成状态,如果是-71出现在ctrl-in端点,基本锁定是 device 没回 ACK,重点查 PHY。联发科 PHY 驱动里通常有phy_tune相关接口,调整输出信号幅值或者 pre-emphasis,多试几组参数。另外检查 VBUS 供电是否稳定,插上设备瞬间电压跌落超过 5%,枚举失败概率大增。

这类问题比较玄学,软件能做的就是先把 PHY 参数调成官方推荐的数值,然后把 dts 里maximum-speed = "high-speed"改成"full-speed"做对照实验,看是否能枚举出全速设备。能枚举出全速说明物理层还有救,调 PHY 参数解决;全速也不行,基本可以判断是走线或器件问题,需要硬件介入。

4.3 现象:OTG 口插 Type-C 设备 host 角色起不来

现象:Type-C 口接入 U 盘后,系统没有切换为 host,dmesg完全没有 USB 相关打印,/sys/class/usb_role/下的角色还是device。

原因:联发科的 OTG 实现一般分三层:Type-C/PD 协议检测、extcon/typec 通知、role switch 驱动。任何一层断链,HCD 都不会进入 host 模式。常见问题是设备树里只开了dr_mode = "otg",但没配置usb-role-switch属性,或者 role switch 节点里没有usb_con连接上挂的端到端关系。

解决:先手动强制切换角色,把协议栈的问题排除掉。查/sys/class/usb_role/下当前端口名,然后直接写入:

ls /sys/class/usb_role/ # 假设端口名为 usb0-role-switch echo host > /sys/class/usb_role/usb0-role-switch/role

手动切到 host 后如果 U 盘能识别,说明 HCD 本身没问题,问题在 Type-C 驱动没有把插拔事件传给 role switch。顺着typec到usb_role_switch的连接关系查一遍设备树里endpoint是否连接完整。手动切换也失败,就得回头查 PHY 的状态切换函数,看usb_phy_set_mode是否在 host 模式时正确把 DP/DM 置于设备直通状态。

4.4 现象:把别家驱动代码复制进来编译过不了

现象:从 vendor 包里抽出的mtk_hcd_core.c在目标内核上编译,报出一堆类似struct usb_hcdhas no member named...的编译错误。包里的代码看起来功能完整,但就是编不过。

原因:HCD 接口在内核不同版本之间变动频繁。比如老的usb_hcd结构成员、usb_phy的调用方式、usb_add_hcd的参数,几乎每个大版本都有调整。vendor 包通常基于某个老内核裁剪,直接搬到新内核必然翻车。

解决:先确认源码包注释里的内核版本,再对比目标内核的include/linux/usb.h。我的做法是保留 vendor HCD 作为参考实现,按目标内核 API 重写hc_driver的回调注册部分,私有 PHY 和时钟管理代码原样保留。别硬改老代码去适配新内核,结构体成员变动牵一发动全身。重写过程中反复用git diff维护一个补丁文件,后续内核再升级时复用这套迁改逻辑。

5. 用 usbmon 验证 HCD 行为:把问题钉死在 host 还是 device

HCD 出问题时最容易陷入「改一行参数、重编、烧录、插拔一次」的死循环。其实在动手调 PHY 之前,我应该先回答一个问题:失败到底是 host 侧没把 URB 送出去,还是 device 侧收到了但没应答。usbmon 是解开这个问题最快的手段。

# 加载 usbmon modprobe usbmon # 查看当前有哪些总线 cat /sys/kernel/debug/usb/devices | grep -E "^T:|^B:" # 对总线 1 进行抓包,输出到文件 tcpdump -i usbmon1 -w /tmp/usb_wait.pcap

抓包结束后用tcpdump -r或者 Wireshark 打开。枚举阶段能看到GET_DESCRIPTOR包和对应的应答,如果 URB 显示-EPROTO而 device 端并没有任何响应,说明 PHY 物理信号异常;如果 device 端 ACK 正常但后续的 SET_CONFIGURATION 失败,问题就在驱动的端点配置逻辑上。

usbmon 拿到的信息已经能覆盖大多数分支判断。新手最容易忽略的一点是 usbmon 属于 USB core 层,如果 HCD 自己就没把 URB 送进硬件,usbmon 是看不到对应包的,这时候-71刷屏就不存在,反而是中断没触发的问题。所以先用 usbmon 排除硬件链路,再用perf或者 trace 看 HCD 的urb_enqueue是否被调用,两条路走完就能把问题圈定在驱动逻辑还是 PHY 参数。

处理类似这份 usb_driver 包时,我的习惯是先把 usbmon 的抓包能力验证好,再去做 PHY 参数调整。每一条-71背后可能有十种以上成因,抓包能先排掉一半,剩下的一半交给示波器量 DP/DM。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询