【Linux驱动开发】Linux DMA 技术详解与驱动开发实战

Linux DMA 技术详解与驱动开发实战

摘要

本文档全面解析 Linux 内核中的直接内存访问 (DMA) 技术。从硬件工作原理出发,深入分析 Linux DMA 子系统的架构设计,详细阐述一致性 DMA 与流式 DMA 的 API 使用规范。结合字符设备驱动实战,演示 Scatter-Gather DMA 的实现细节,并探讨 IOMMU、CMA 及 PCIe P2P DMA 等高级主题,旨在为嵌入式及驱动工程师提供一份权威的技术指南。

关键词:Linux Kernel, DMA, Scatter-Gather, dma_map_single, IOMMU, PCIe P2P


1. 技术原理

1.1 DMA 基本概念

直接内存访问 (Direct Memory Access) 是一种允许硬件子系统直接读写系统主内存的技术,而无需中央处理器 (CPU) 介入数据的每次传输。

在没有 DMA 的系统中,CPU 必须执行指令来从 I/O 设备读取数据到寄存器,再写入内存(PIO 模式)。这会导致 CPU 在大量数据传输期间处于忙碌状态,无法处理其他任务。

1.2 DMA 控制器的作用

DMA 控制器 (DMAC) 是系统总线上的一个专用处理器。

  • 总线主控 (Bus Master):DMAC 可以接管总线控制权。
  • 地址生成:自动生成源地址和目的地址。
  • 中断触发:传输完成后触发中断通知 CPU。

1.3 有无 DMA 的对比

  • PIO 模式 (Programmed I/O)
    • 数据路径:Device <-> CPU <-> Memory
    • 缺点:CPU 占用率高,传输速度受限于 CPU 指令周期。
  • DMA 模式
    • 数据路径:Device <-> Memory (由 DMAC 控制)
    • 优点:CPU 仅需发送配置指令(源/目地址、长度),随后即可释放处理其他中断或进程。

2. Linux 内核实现

2.1 DMA 子系统架构

Linux DMA 子系统提供了一层屏蔽硬件差异的抽象层:

  1. DMA Engine API:用于通用的 Slave DMA(如嵌入式 SoC 中的 SPI/UART DMA)。
  2. DMA Mapping API:用于 PCI/PCIe 等具备总线主控能力的设备,处理虚拟地址到物理地址(总线地址)的映射,以及缓存一致性问题。

2.2 核心 API 详解

2.2.1 一致性 DMA (Coherent DMA)

用于分配常驻内存(如描述符环、控制块),CPU 和设备都能看到最新数据,无需手动同步 Cache。

c 复制代码
/* 分配 */
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);

/* 释放 */
dma_free_coherent(dev, size, cpu_addr, dma_handle);
  • 参数dev (struct device*), size (字节数), dma_handle (返回的总线地址).
  • 特点:开销较大,通常在驱动初始化时分配。
2.2.2 流式 DMA (Streaming DMA)

用于单次数据传输(如网络包、磁盘块),通常使用已有的内核缓冲区(如 kmalloc 申请的内存)。

c 复制代码
/* 映射 (CPU -> Device) */
dma_addr_t dma_handle = dma_map_single(dev, ptr, size, DMA_TO_DEVICE);

/* 检查错误 */
if (dma_mapping_error(dev, dma_handle)) {
    /* 错误处理 */
}

/* ... 启动 DMA 传输 ... */

/* 取消映射 (传输完成后) */
dma_unmap_single(dev, dma_handle, size, DMA_TO_DEVICE);
  • 方向DMA_TO_DEVICE, DMA_FROM_DEVICE, DMA_BIDIRECTIONAL
  • 注意 :在 unmap 之前,CPU 不应触碰这块内存,否则可能导致数据损坏(Cache 一致性问题)。

2.3 Scatter-Gather (分散-聚集) DMA

当需要传输的数据在虚拟内存中连续,但在物理内存中不连续时(例如巨大的视频帧),使用 SG DMA 可以避免内存拷贝。

数据结构struct scatterlist

c 复制代码
struct scatterlist {
    unsigned long   page_link;
    unsigned int    offset;
    unsigned int    length;
    dma_addr_t      dma_address; // 映射后的总线地址
    unsigned int    dma_length;  // 映射后的长度
};

API 使用

c 复制代码
int nents = dma_map_sg(dev, sg_list, n_pages, DMA_FROM_DEVICE);
/* 遍历映射后的段 */
for_each_sg(sg_list, sg, nents, i) {
    dma_addr_t addr = sg_dma_address(sg);
    unsigned int len = sg_dma_len(sg);
    /* 配置硬件描述符 */
}
dma_unmap_sg(dev, sg_list, n_pages, DMA_FROM_DEVICE);

3. 驱动开发实践

本节演示一个模拟的字符设备 DMA 驱动。

3.1 设备树配置 (Device Tree)

dts 复制代码
my_dma_device: my-dma@40000000 {
    compatible = "my,dma-device";
    reg = <0x40000000 0x1000>;
    interrupts = <0 20 4>;
    dmas = <&dma0 1 0>; /* 引用系统 DMA 控制器 */
    dma-names = "rx";
};

3.2 驱动代码示例

c 复制代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/dma-mapping.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>

struct my_dma_dev {
    struct device *dev;
    struct cdev cdev;
    void *dma_buf_virt;
    dma_addr_t dma_buf_phys;
    size_t buf_size;
};

#define BUF_SIZE (4 * 1024) // 4KB

static int my_open(struct inode *inode, struct file *file)
{
    struct my_dma_dev *mydev = container_of(inode->i_cdev, struct my_dma_dev, cdev);
    
    /* 1. 分配一致性 DMA 缓冲区 */
    mydev->buf_size = BUF_SIZE;
    mydev->dma_buf_virt = dma_alloc_coherent(mydev->dev, mydev->buf_size,
                                           &mydev->dma_buf_phys, GFP_KERNEL);
    if (!mydev->dma_buf_virt)
        return -ENOMEM;

    file->private_data = mydev;
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    struct my_dma_dev *mydev = file->private_data;
    
    /* 2. 模拟启动 DMA 传输 (硬件寄存器操作省略) */
    /* writel(mydev->dma_buf_phys, REG_SRC_ADDR); */
    /* writel(count, REG_LEN); */
    /* writel(START_BIT, REG_CTRL); */
    
    /* 3. 等待 DMA 完成 (wait_event_interruptible...) */
    
    /* 4. 将数据拷贝给用户 (实际场景中可能使用 mmap 零拷贝) */
    if (copy_to_user(buf, mydev->dma_buf_virt, count))
        return -EFAULT;
        
    return count;
}

static int my_release(struct inode *inode, struct file *file)
{
    struct my_dma_dev *mydev = file->private_data;
    
    /* 5. 释放缓冲区 */
    if (mydev->dma_buf_virt)
        dma_free_coherent(mydev->dev, mydev->buf_size, 
                         mydev->dma_buf_virt, mydev->dma_buf_phys);
    return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .read = my_read,
    .release = my_release,
};

/* Platform Driver Probe */
static int my_dma_probe(struct platform_device *pdev)
{
    struct my_dma_dev *mydev;
    /* ... cdev 初始化 ... */
    
    /* 设置 DMA 掩码 (如 32位或64位) */
    if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) {
        dev_err(&pdev->dev, "No suitable DMA available\n");
        return -EIO;
    }
    return 0;
}

3.3 性能优化与排查

  • 合并传输:尽量使用 SG DMA 合并小的物理块,减少中断频率。
  • 对齐:确保缓冲区地址和长度按照 Cache Line 对齐(通常 64 字节),避免 Cache Bouncing。
  • 调试 :开启 CONFIG_DMA_API_DEBUG,内核会检查 dma_map/unmap 配对错误和越界访问。

4. 高级主题

4.1 IOMMU (Input-Output MMU)

IOMMU 位于设备和系统总线之间,类似于 CPU 的 MMU。

  • 功能:将设备可见的虚拟地址 (IOVA) 映射到物理地址。
  • 优势
    • 内存保护:防止设备非法访问其他内存。
    • 分散聚集:让物理不连续的页面在设备看来是连续的(简化 DMA 硬件设计)。
  • Linux 支持:DMA Mapping API 自动处理 IOMMU,驱动通常无感。

4.2 CMA (Contiguous Memory Allocator)

在没有 IOMMU 的嵌入式系统中,硬件往往需要大块连续物理内存(如 1080p 帧缓冲)。

  • 原理:在启动时预留一大块内存区域(CMA Heap)。
  • 作用dma_alloc_coherent 在申请大内存时,底层会自动从 CMA 分配器中获取。

4.3 PCIe P2P DMA (Peer-to-Peer)

传统 DMA 数据必须经过系统内存(Device A -> Memory -> Device B)。

P2P DMA 允许 PCIe 设备直接交换数据(Device A -> Device B)。

  • 优势:大幅降低系统内存带宽压力和 CPU 延迟。
  • APIpci_p2pdma_map_sg(), NVMe 和 RDMA 网卡已广泛支持。

5. 参考文献

相关推荐
2301_795167201 小时前
Python 高手编程系列一十八:子类化内置类型
linux·windows·python
斌蔚司李1 小时前
笔记本、台式机、平板二合一?Mac、Win、Linux?
linux·macos·电脑
DeeplyMind1 小时前
AMD rocr-libhsakmt分析系列6-2:共享机制-import
linux·amdgpu·dma-buf·rocm·kfd·rocr
Dillon Dong1 小时前
【超详细】Ubuntu 上 MySQL 5.7 升级 MySQL 8 完整指南
linux·mysql·ubuntu
DARLING Zero two♡1 小时前
【Linux操作系统】简学深悟启示录:线程同步与互斥
linux·运维·服务器
hhwyqwqhhwy1 小时前
linux 驱动iic
linux·运维·服务器
知识分享小能手1 小时前
CentOS Stream 9入门学习教程,从入门到精通, CentOS Stream 9中的文件和目录管理(3)
linux·学习·centos
一只努力学习的Cat.1 小时前
Linux:NAPT等其他补充内容
linux·运维·网络
摸鱼仙人~1 小时前
VMware配置从开始踩坑总结-2025最新
linux·ubuntu