Linux之MHI(常用于PCIe接口的5G模组)

1. MHI 是什么

MHI = Modem Host Interface,最初是 Qualcomm 定义的一套主机与 Modem/加速器之间的通信协议。

它常见承载在 PCIe 上,用于主机和 PCIe Modem 之间高速通信,例如:

  • Qualcomm SDX55 / SDX62 / SDX65 / SDX75 等 5G Modem;
  • 一些车载/工业网关里的蜂窝模组;
  • 通过 PCIe 接入的 AI/加速器设备,也可能复用 MHI 框架。

可以把 MHI 理解为:

在 PCIe 之上的一套基于 Ring/DMA/中断的通信协议,向操作系统暴露多个逻辑通道,用于承载 QMI、MBIM、AT、DIAG、LOG、网络数据等。

它和 QMI、MBIM 的关系是:

MHI 是传输通道/总线;
QMI / MBIM / AT / DIAG 是运行在 MHI 通道上的协议或业务。

例如一个 5G 模组通过 PCIe 接入 Linux 后,可能同时暴露:

MHI channel: IP_HW0     -> 网络数据
MHI channel: MBIM       -> MBIM 控制
MHI channel: QMI/QRTR   -> QMI 控制
MHI channel: DUN/AT     -> AT 串口
MHI channel: DIAG       -> 诊断
MHI channel: LOGGING    -> 日志

2. Linux PCIe MHI 总体架构

Linux 中 PCIe MHI 不是一个单独驱动,而是一套分层驱动体系:

+--------------------------------------------------+
| 用户态                                           |
| ModemManager / qmicli / mbimcli / AT工具 / 自研程序 |
+--------------------------------------------------+
| 客户端驱动                                       |
| mhi_wwan_ctrl / mhi_wwan_mbim / mhi_net /        |
| mhi_uci / qrtr_mhi / 自定义 mhi client driver    |
+--------------------------------------------------+
| MHI Core                                         |
| channel/event/command/ring/power/firmware load   |
+--------------------------------------------------+
| MHI Controller Driver                            |
| mhi_pci_generic / qcom mhi glue / vendor driver  |
+--------------------------------------------------+
| PCIe 子系统                                      |
| BAR0/MMIO, MSI/MSI-X, DMA, Bus Master, PM       |
+--------------------------------------------------+
| 硬件 Modem/加速器                                |
| MHI device side                                  |
+--------------------------------------------------+

3. 内核源码位置

不同内核版本目录略有差异。常见路径如下:

drivers/bus/mhi/host/
    main.c
    init.c
    boot.c
    pci_generic.c
    ...

drivers/bus/mhi/ep/
    MHI endpoint 侧驱动,如果 Linux 跑在 device/endpoint 侧时使用

drivers/net/wwan/
    mhi_wwan_ctrl.c
    mhi_wwan_mbim.c

net/qrtr/
    qrtr-mhi 或类似 QRTR over MHI 支持

4. PCIe 层需要什么资源

对于主机侧 Linux,PCIe MHI 设备首先是一个 PCIe 设备。lspci 通常能看到类似 Qualcomm 的设备:

lspci -nn | grep -i qualcomm
lspci -vvv -s <BDF>

MHI 驱动依赖以下 PCIe 资源:

4.1 BAR0 / MMIO

MHI 寄存器通常映射在 PCIe BAR0 上,包括:

BHI / BHIe 寄存器
MHI 控制寄存器
Channel Doorbell
Event Doorbell
中断状态寄存器
电源状态寄存器

Linux MHI controller driver 一般会做:

pci_request_regions(pdev, "mhi");
mhi_cntrl->reg_base = pci_iomap(pdev, 0, 0);

如果 BAR 没有分配成功,常见现象是:

can't allocate BAR
MHI register base invalid
PCIe device not ready

4.2 MSI / MSI-X 中断

MHI 是中断驱动的。常见中断用途:

数据事件中断
控制事件中断
BHI 固件加载中断
PM/wake 中断
错误中断

通常 MHI controller 会申请一个或多个 MSI/MSI-X vector。

如果 BIOS、PCIe Root Complex 或设备树/ACPI 配置异常,可能出现:

No IRQ
mhi_power_up timeout
event ring not progressing

4.3 DMA / Bus Master

MHI 的核心数据路径是 DMA:

主机内存中的 ring buffer / context / data buffer
被 PCIe Modem 通过 DMA 读取或写入

因此必须:

pci_set_master(pdev);
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));

如果启用了 IOMMU/SMMU,还需要保证 PCIe 设备对该内存有 DMA 权限。

常见错误:

DMA mapping error
IOMMU fault
SMMU context fault

4.4 电源和边带信号

实际平台里,Modem 往往还涉及:

PERST#
WAKE#
CLKREQ#
模组供电
SIM 电源
天线/射频使能

在 x86 平台,这些可能由 BIOS/ACPI 管理;在 ARM 嵌入式平台,可能需要设备树、GPIO、regulator、pinctrl 配合。


5. MHI 通信模型

MHI 的核心抽象是:

Channel
Transfer Ring
Event Ring
Command Ring
Doorbell
DMA buffer

5.1 Channel

Channel 是逻辑通道。一个 MHI 设备通常由多个 channel 组成。

常见 channel 举例:

IP_HW0        硬件加速 IP 数据
IP_SW0        软件 IP 数据
MBIM          MBIM 控制/数据
QMI/QRTR      QMI 控制
DUN           AT 串口
DIAG          诊断
LOGGING       日志
LOOPBACK      测试
SAHI          某些控制/辅助通道

Linux MHI core 会根据控制器配置和模组状态创建若干 mhi_device

可以查看:

ls /sys/bus/mhi/devices/

通常会看到包含通道名的设备,例如类似:

..._IP_HW0
..._MBIM
..._DUN
..._DIAG

具体命名依赖内核版本和模组固件。

5.2 Transfer Ring,TRE

每个 channel 有对应的传输环,里面存放传输描述符,通常称为:

TRE / Transfer Ring Element

一个 TRE 通常包含:

数据缓冲区物理地址
长度
控制标志
方向信息或完成控制信息

主机向设备发数据时,大致流程是:

1. 客户端驱动准备 skb/buffer;
2. MHI core 做 DMA mapping;
3. 生成 TRE,写入 channel transfer ring;
4. 写 doorbell 通知 Modem;
5. Modem DMA 读取数据;
6. Modem 写 event ring 并触发 MSI;
7. Linux MHI core 处理完成,回调客户端驱动。

5.3 Event Ring

Event Ring 用于设备向主机上报事件:

传输完成
命令完成
状态变化
电源事件
错误事件

MHI controller driver 收到中断后,会处理 event ring。

简化逻辑:

MSI/IRQ
  -> mhi_irq_handler()
  -> 找到对应 event ring
  -> 读取 event descriptor
  -> 判断事件类型
  -> 调用 channel/client callback

5.4 Command Ring

Command Ring 用于主机向设备发送控制命令,例如:

START CHANNEL
STOP CHANNEL
RESET CHANNEL
NOOP
电源相关命令

命令执行结果通过 control/event ring 返回。

5.5 Doorbell

Doorbell 是一种 MMIO 通知机制。

例如主机往 transfer ring 里放了新的 TRE 后,需要写 channel doorbell 通知设备:

Host writes CHDB register
  -> Modem knows there are new transfer requests

事件方向上也有 event doorbell 或类似机制,用于主机告知设备事件消费情况。


6. MHI 控制器驱动

MHI controller driver 是连接 PCIe 和 MHI core 的胶水层。

主线常见的是:

pci_generic.c

它可以匹配 PCIe Vendor/Device ID,然后初始化 MHI controller。

一个典型 probe 流程如下:

PCIe 枚举发现设备
  -> mhi pci controller probe
  -> 使能 PCIe 设备
  -> 申请 BAR
  -> 设置 DMA mask
  -> pci_set_master()
  -> 申请中断
  -> 分配 mhi_controller
  -> 填写寄存器基址、channel/event 配置
  -> 注册 MHI controller
  -> MHI core 初始化
  -> 固件加载/启动
  -> MHI power up
  -> 创建 mhi_device
  -> client driver probe

7. MHI 固件加载与启动

很多 PCIe Modem 上电后并不直接进入完整运行态,而是需要主机下载固件或触发启动。

MHI 在早期启动阶段常用:

BHI  = Boot Host Interface
BHIe = Boot Host Interface extended

BHI 主要用于:

固件下载
启动状态查询
错误状态上报

典型启动流程:

1. PCIe link up
2. Modem 处于 BOOT/BHI 状态
3. Linux MHI controller 分配 DMA buffer
4. 加载固件镜像到内存
5. 通过 BHI 寄存器告诉设备固件地址和长度
6. 写 BHI doorbell
7. 等待 BHI 中断或状态变化
8. Modem 校验并启动固件
9. MHI 进入 READY
10. MHI power up 到 M0
11. 创建 channel/client 设备

固件路径通常在:

/lib/firmware/
/lib/firmware/mhi/

常见固件名类似:

amss.mbn
sbl1.mbn
modem.mbn
firehose programmer
vendor specific firmware

具体名字取决于厂商和内核配置。


8. MHI 状态机

MHI 有设备状态和电源状态。

常见 MHI state:

MHI_STATE_RESET
MHI_STATE_READY
MHI_STATE_M0
MHI_STATE_M1
MHI_STATE_M2
MHI_STATE_M3

粗略理解:

状态 含义
RESET MHI 复位/未初始化
READY MHI 基础初始化完成,可进一步启动
M0 Active,正常工作
M1 低功耗,可较快恢复
M2 更深低功耗
M3 挂起/关闭状态,通常需要唤醒或重新 power up

此外还有:

SYS_ERR
FW_DL_ERR
POR

等异常或上电状态。

Linux MHI core 会维护类似:

device state
pm state
execution environment
channel state
event ring state

电源状态迁移大致:

RESET
  -> READY
  -> M0

M0
  -> M1/M2/M3 低功耗

M3
  -> wake -> M0

9. MHI 客户端驱动

MHI core 只负责传输,真正的业务由客户端驱动完成。

9.1 mhi_net

mhi_net 是一个比较基础的 MHI 网络驱动,把 MHI channel 暴露成 Linux netdev。

可能生成类似:

mhi0
rmnet_mhi0
wwan0

具体名字取决于驱动和配置。

它负责:

接收 IP 包
发送 IP 包
NAPI polling
DMA buffer 管理
netdev 统计

9.2 mhi_wwan_ctrl

这是 WWAN 控制框架驱动,通常配合 Linux WWAN 子系统。

它会创建:

/dev/wwan0
wwan port
control port

并可能注册多个端口:

QMI port
MBIM port
AT port
NMEA port
DIAG port

具体暴露哪些端口取决于模组和固件。

9.3 mhi_wwan_mbim

用于 MBIM 数据网络。

MBIM 是移动宽带常见控制/数据协议,Linux 用户态可通过:

mbimcli
ModemManager

操作。

典型链路:

Modem
  -> MHI MBIM channel
  -> mhi_wwan_mbim
  -> Linux netdev
  -> IPv4/IPv6

9.4 QRTR / QMI over MHI

Qualcomm 平台常使用 QRTR 承载 QMI。

Linux 中可能有:

CONFIG_QRTR
CONFIG_QRTR_MHI

链路:

Modem QMI service
  -> QRTR
  -> MHI channel
  -> Linux qrtr-mhi
  -> qmicli / ModemManager / qrtr clients

用户态可能使用:

qmicli
qmi-proxy
qrtr-ns

具体设备节点和用法取决于发行版和内核配置。

9.5 mhi_uci

mhi_uci 提供用户态字符设备接口,便于用户态直接读写 MHI channel。

常见设备节点类似:

/dev/mhi_xxx

例如:

/dev/mhi_DUN
/dev/mhi_DIAG
/dev/mhi_LOGGING

具体命名视内核版本和 channel 名而定。

用途:

AT 命令透传
DIAG 工具
LOG 抓取
厂商私有协议调试

10. 数据传输路径

以网络上行/下行为例。

10.1 上行:Linux 发包到 Modem

应用层
  -> socket
  -> Linux TCP/IP 协议栈
  -> netdev
  -> mhi_net/wwan 数据驱动
  -> MHI core
  -> DMA map skb
  -> 生成 TRE
  -> 写 doorbell
  -> PCIe Modem DMA 读取数据
  -> 完成事件
  -> MHI IRQ
  -> ul_xfer_cb
  -> 释放/回收 buffer

10.2 下行:Modem 发包到 Linux

Modem
  -> DMA 写入 host buffer
  -> 写 event ring
  -> MSI 中断
  -> MHI IRQ handler
  -> MHI core 解析 event
  -> dl_xfer_cb
  -> mhi_net/wwan 驱动
  -> netif_receive_skb()
  -> Linux TCP/IP 协议栈
  -> 应用层

11. 电源管理

MHI 的电源管理比较复杂,因为它同时涉及:

PCIe PM
Linux runtime PM
MHI 自身 M-state
Modem 固件电源状态
边带 WAKE 信号

11.1 System Suspend/Resume

系统休眠时,Linux 可能执行:

MHI power down
MHI state -> M3
PCIe device -> D3hot/D3cold

唤醒时:

PCIe link resume
MHI power up
Modem 恢复
client driver resume

常见问题:

resume timeout
MHI power up failed after suspend
PCIe link retrain failed
modem not waking host

11.2 Runtime PM

运行时电源管理可能让 Modem 在空闲时进入低功耗。

相关节点:

/sys/bus/pci/devices/<BDF>/power/control
/sys/bus/pci/devices/<BDF>/power/runtime_status
/sys/bus/pci/devices/<BDF>/power/autosuspend_delay_ms

常见值:

auto
on

调试时可临时关闭 runtime PM:

echo on > /sys/bus/pci/devices/<BDF>/power/control

但生产环境不建议长期关闭,需要结合平台稳定性测试。

11.3 Wake 机制

Modem 唤醒主机通常依赖:

PCIe PME
WAKE#
MSI/MSI-X wake interrupt
MHI PM interrupt

如果唤醒异常,需要检查:

ACPI/设备树是否描述 wake 能力
PCIe Root Port 是否支持 wake
BIOS 是否启用 PCIe wake
MHI controller 是否注册 wake IRQ

12. 常见内核配置

不同内核配置名可能略有差异,常见相关选项包括:

CONFIG_MHI_BUS
CONFIG_MHI_BUS_DEBUG
CONFIG_MHI_BUS_PCI_GENERIC
CONFIG_MHI_NET
CONFIG_MHI_WWAN_CTRL
CONFIG_MHI_WWAN_MBIM
CONFIG_MHI_UCI
CONFIG_QRTR
CONFIG_QRTR_MHI

还需要启用:

CONFIG_PCI
CONFIG_PCI_MSI
CONFIG_DMADEVICES
CONFIG_NET
CONFIG_WWAN
CONFIG_USB_NET_QMI_WWAN 不一定需要,取决于是否走 USB

如果你使用 ModemManager,还需要用户态组件:

modemmanager
libmbim
libqmi

13. 一个典型启动流程示例

假设一切正常,内核日志可能类似:

pci 0000:01:00.0: [17cb:xxxx] type 00 class 0x...
pci 0000:01:00.0: BAR 0: assigned ...
mhi_pci_generic 0000:01:00.0: enabling device
mhi_pci_generic 0000:01:00.0: MHI PCI device found
mhi mhi0: firmware loading
mhi mhi0: BHI firmware download completed
mhi mhi0: MHI state: READY
mhi mhi0: MHI power up to M0
mhi mhi0: Channel IP_HW0 started
mhi_net mhi0_IP_HW0: registering network

具体日志格式会因内核和厂商驱动不同而变化。


14. MHI Endpoint 简述

前面讲的都是 Host Side。

Linux 也逐步支持 MHI Endpoint,用于 Linux 运行在设备侧/Endpoint 侧,例如某个 SoC 通过 PCIe Endpoint 连接到主机。

相关代码可能在:

drivers/bus/mhi/ep/

这种场景下:

Linux 是 MHI device/endpoint;
对端主机是 MHI host;

如果你是在 x86/主机侧调试蜂窝模组,一般不需要关心 endpoint 侧。


15. 总结

Linux PCIe MHI 驱动本质上是一套围绕 PCIe 设备构建的高速通信框架。理解它可以抓住几个核心点:

  1. PCIe 是物理承载
    依赖 BAR、MSI、DMA、Bus Master、PM。

  2. MHI 是传输协议
    使用 channel、transfer ring、event ring、command ring、doorbell。

  3. MHI controller driver 是平台胶水层
    常见是 pci_generic.c,负责 PCIe 资源和 MHI core 对接。

  4. MHI core 管理状态、固件、电源和数据环
    包括固件加载、power up、channel start、event 处理。

  5. 客户端驱动决定业务形态
    网络、MBIM、QMI、AT、DIAG、LOG 都是挂在 MHI channel 上的客户端。

  6. 调试重点是 PCIe、固件、中断、DMA、电源五个方向
    大多数 MHI 问题都落在这几类。