2. 硬件交互基础:CPU与设备通信方式概述

在嵌入式Linux驱动开发中,驱动程序的本质工作是管理CPU与外部硬件设备之间的数据交换。理解CPU如何与设备“对话”,是编写正确、高效驱动的基石。本章将系统介绍三种核心通信方式:PIO、MMIO和DMA,并深入解析地址空间这一关键概念。

2.1 CPU与设备通信的三种方式

根据数据搬运的发起方和路径,CPU与设备的通信可分为以下三种模式:

通信方式 数据搬运者 典型场景 特点
PIO (Programmed I/O) CPU 早期串口、GPIO、状态寄存器访问 简单直接,但CPU占用率高,吞吐量低
MMIO (Memory-Mapped I/O) CPU 现代SoC中几乎所有外设(网卡、GPU、USB控制器) 统一寻址空间,使用内存指令访问,效率高于PIO
DMA (Direct Memory Access) DMA控制器 大数据块传输(磁盘、显示、音频、高速网络) CPU仅发起和结束,传输过程不占用CPU,吞吐量极高
驱动开发核心认知: 在Linux内核中,PIO几乎已被MMIO取代。现代驱动主要面对MMIO和DMA两种模式。MMIO用于控制/状态交互,DMA用于批量数据搬运。

2.1.1 PIO(程序控制输入输出)

PIO是最早期的通信方式。CPU通过专用的I/O指令(如x86架构的 in/out 指令)直接读写设备寄存器。每次数据传输都需要CPU全程参与:

// 伪代码:PIO方式读取设备状态
while (1) {
    status = inb(DEVICE_STATUS_PORT);  // CPU执行in指令
    if (status & DATA_READY) {
        data = inb(DEVICE_DATA_PORT);  // CPU逐字节读取
        process(data);
    }
}

PIO的局限性:

  • CPU被完全占用,无法执行其他任务。
  • 每次传输一个字节或字,吞吐量极低。
  • 需要独立的I/O地址空间,增加了硬件复杂度。

2.1.2 MMIO(内存映射I/O)

MMIO将设备的寄存器映射到CPU的物理地址空间中。CPU使用标准的 ldr/str(ARM)或 mov(x86)内存访问指令来读写设备寄存器,无需特殊I/O指令。

MMIO的核心优势: 所有访问内存的指令(包括C语言中的指针解引用)都可以直接操作设备寄存器。操作系统和编译器无需区分“内存访问”和“I/O访问”,简化了编程模型。
// 伪代码:MMIO方式操作设备寄存器
// 假设设备基地址已被映射到虚拟地址空间
volatile uint32_t *ctrl_reg = (volatile uint32_t *)(mmio_base + 0x00);
volatile uint32_t *data_reg = (volatile uint32_t *)(mmio_base + 0x04);

*ctrl_reg = 0x01;  // 启动设备
while (!(*ctrl_reg & 0x02));  // 等待设备就绪
uint32_t value = *data_reg;  // 读取数据
关键注意事项: 访问MMIO寄存器时,必须使用 volatile 关键字,防止编译器优化掉看似“重复”或“无用”的读写操作。此外,MMIO访问通常需要保证顺序性和对齐性,Linux内核提供了 readl()/writel() 等封装函数来确保这些语义。

2.1.3 DMA(直接内存访问)

DMA允许硬件设备直接读写系统内存,无需CPU逐字节搬运。CPU只需配置DMA控制器(描述源地址、目的地址、传输长度),然后启动传输。传输完成后,DMA控制器通过中断通知CPU。

// 伪代码:DMA传输流程
// 1. CPU分配DMA缓冲区
dma_addr_t dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);

// 2. CPU配置DMA控制器
dma_controller_config(src = device_fifo, dst = dma_handle, len = 1024);

// 3. CPU启动DMA
dma_controller_start();

// 4. CPU可以去做其他事情,DMA控制器在后台搬运数据

// 5. DMA完成,触发中断
void dma_interrupt_handler(void) {
    // CPU处理已完成的数据
    process_data(dma_buf);
}

下图展示了三种通信方式中CPU的参与程度和数据流向:

CPU与设备通信方式对比 PIO 模式 CPU全程搬运 CPU 设备 数据 特点 • CPU 100%占用 • 逐字节/字传输 • 吞吐量低 MMIO 模式 CPU通过内存指令访问 CPU 设备 ldr/str 特点 • 统一地址空间 • 使用内存指令 • 效率高于PIO DMA 模式 DMA控制器搬运 CPU DMA 配置 特点 • CPU仅发起/结束 • 传输不占CPU • 吞吐量极高

2.2 地址空间概念

理解地址空间是掌握MMIO和DMA的前提。在嵌入式系统中,存在多个独立的地址空间:

  • CPU虚拟地址空间: 进程看到的地址空间,通过MMU映射到物理地址。Linux内核使用虚拟地址访问所有资源。
  • CPU物理地址空间: CPU地址总线上的实际地址。包括系统RAM和MMIO映射的设备寄存器区域。
  • 设备地址空间: 设备内部寄存器和FIFO的地址布局,通常由设备数据手册定义。
  • DMA地址空间: 设备(通过DMA控制器)访问系统内存时使用的地址。在某些架构中,DMA地址可能与CPU物理地址不同(如存在IOMMU或SMMU时)。

下图展示了这些地址空间之间的关系:

嵌入式系统中的地址空间层次 CPU虚拟地址空间 进程/内核视角 通过MMU映射 例:0x7f00_0000 CPU物理地址空间 硬件总线视角 包含RAM + MMIO区域 例:0x8000_0000 (MMIO) MMU映射 设备地址空间 设备内部寄存器布局 由数据手册定义 例:偏移 0x00 = 控制寄存器 DMA地址空间 设备访问内存的地址 可能经过IOMMU/SMMU 例:总线地址 0x1_0000 DMA传输

2.3 MMIO与DMA的协同工作

在实际驱动中,MMIO和DMA通常协同工作:

  1. 控制面(MMIO): 驱动通过MMIO访问设备控制/状态寄存器,完成设备初始化、启动DMA传输、检查传输状态等操作。
  2. 数据面(DMA): 实际的数据搬运由DMA控制器完成,CPU仅在传输开始和结束时介入。

这种分工使得CPU能够专注于控制逻辑和数据处理,而将繁重的数据搬运任务交给专用硬件。

典型工作流程:
1. 驱动通过MMIO写入DMA源地址(设备FIFO)、目的地址(内存缓冲区)和传输长度。
2. 驱动通过MMIO写入控制寄存器,启动DMA传输。
3. DMA控制器在后台搬运数据,CPU可以处理其他任务或进入低功耗状态。
4. DMA传输完成后,设备通过中断线通知CPU。
5. CPU在中断处理函数中通过MMIO读取状态寄存器,确认传输成功,然后处理内存中的数据。

2.4 本章小结

概念 核心要点
PIO CPU使用专用I/O指令逐字节搬运数据,效率低,已基本被MMIO取代。
MMIO 设备寄存器映射到内存地址空间,使用内存指令访问,是现代驱动控制设备的主要方式。
DMA 专用硬件控制器搬运数据,CPU仅负责配置和收尾,适合大吞吐量场景。
地址空间 虚拟地址、物理地址、设备地址、DMA地址是四个关键层次,驱动开发中必须清晰区分。