1. DMA基础映射与解除映射

任务目标

编写一个Linux字符设备驱动,使用 dma_alloc_coherent() 分配一致DMA缓冲区,获取并打印内核虚拟地址与总线地址,在模块卸载时通过 dma_free_coherent() 解除映射并释放内存。通过模块加载/卸载日志验证地址转换关系。

环境与依赖

  • Linux内核源码树(本案例基于 5.15+ 接口)
  • 内核配置开启 CONFIG_DMA_API_DEBUG(可选,用于一致性检查)
  • 目标平台:x86_64 或 ARM64(均支持通用DMA API)

核心实现

// dma_map_basic.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>

#define DMA_BUF_SIZE  4096

static struct device *dev;
static void *cpu_addr;
static dma_addr_t bus_addr;

static int __init dma_basic_init(void)
{
    struct platform_device *pdev;

    /* 创建一个最小platform设备以获取struct device */
    pdev = platform_device_register_simple("dma_basic_dev", -1, NULL, 0);
    if (IS_ERR(pdev))
        return PTR_ERR(pdev);
    dev = &pdev->dev;

    /* 分配一致DMA缓冲区(内存已清零) */
    cpu_addr = dma_alloc_coherent(dev, DMA_BUF_SIZE, &bus_addr, GFP_KERNEL);
    if (!cpu_addr) {
        platform_device_unregister(pdev);
        return -ENOMEM;
    }

    /* 写入验证数据 */
    memset(cpu_addr, 0xAB, DMA_BUF_SIZE);

    dev_info(dev, "CPU virt addr: %px\n", cpu_addr);
    dev_info(dev, "Bus addr:      %pad\n", &bus_addr);
    dev_info(dev, "Size: %d bytes\n", DMA_BUF_SIZE);

    return 0;
}

static void __exit dma_basic_exit(void)
{
    if (cpu_addr) {
        dma_free_coherent(dev, DMA_BUF_SIZE, cpu_addr, bus_addr);
        dev_info(dev, "DMA buffer freed and unmapped\n");
    }
    platform_device_unregister(to_platform_device(dev));
}

module_init(dma_basic_init);
module_exit(dma_basic_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Basic DMA coherent allocation and mapping");

Makefile

obj-m += dma_map_basic.o

KDIR := /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

编译与运行

# 编译
make

# 加载模块(需root)
sudo insmod dma_map_basic.ko

# 查看内核日志
dmesg | tail -5

# 卸载模块
sudo rmmod dma_map_basic

# 再次查看日志确认释放
dmesg | tail -3

验证方法与判定标准

  1. 加载验证:dmesg 输出应包含三行信息——CPU虚拟地址(形如 00000000xxxxxxxx)、总线地址(形如 00000000xxxxxxxx)、缓冲区大小4096字节。两个地址均为非零值。
  2. 地址转换验证:在x86_64平台(IOMMU未开启时),总线地址与CPU物理地址通常一致;在ARM64或开启IOMMU时两者可能不同,但均为有效非零地址。可通过 cat /proc/iomem 交叉验证总线地址落在系统RAM范围内。
  3. 数据完整性:加载后可用 devmem 工具(若可用)读取总线地址对应物理内存,前几个字节应为 0xAB(需root权限且平台支持)。
  4. 卸载验证:rmmod 后 dmesg 应显示 DMA buffer freed and unmapped,且无内核错误或警告(如 DMA-API 相关报错)。

常见问题

  • 编译报错:确认内核头文件已安装,且当前内核版本支持 platform_device_register_simple(2.6.36+均支持)。
  • 地址为0:若 dma_alloc_coherent 返回NULL或总线地址为0,检查设备是否已正确初始化DMA掩码(本案例使用默认掩码,若平台需要可添加 dma_set_mask_and_coherent() 调用)。
  • 卸载时崩溃:确保 dma_free_coherent 使用与分配时完全相同的 dev、size、cpu_addr 和 bus_addr 参数。