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
验证方法与判定标准
- 加载验证:
dmesg输出应包含三行信息——CPU虚拟地址(形如00000000xxxxxxxx)、总线地址(形如00000000xxxxxxxx)、缓冲区大小4096字节。两个地址均为非零值。 - 地址转换验证:在x86_64平台(IOMMU未开启时),总线地址与CPU物理地址通常一致;在ARM64或开启IOMMU时两者可能不同,但均为有效非零地址。可通过
cat /proc/iomem交叉验证总线地址落在系统RAM范围内。 - 数据完整性:加载后可用
devmem工具(若可用)读取总线地址对应物理内存,前几个字节应为0xAB(需root权限且平台支持)。 - 卸载验证:
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参数。