4. 流式DMA映射单缓冲区
任务目标
用 dma_map_single() 把一块内核缓冲区映射为流式 DMA 地址,交给 PCIe 设备做一次写(设备→内存)和一次读(内存→设备),完成后用 dma_unmap_single() 解除映射,并检查数据一致性。
关键点
- 流式映射是"一次性"的:映射→传输→解除,不能长期缓存 DMA 地址。
- 方向必须正确:设备写内存用
DMA_FROM_DEVICE,设备读内存用DMA_TO_DEVICE。 - 解除映射前不要访问缓冲区(除非先
dma_sync_single_for_cpu);解除后 CPU 才能安全读写。 - 映射失败用
dma_mapping_error()判断,不能只比较 0。
完整代码
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#define BUF_SIZE 256
static int __init dma_single_init(void)
{
struct pci_dev *pdev;
void *buf;
dma_addr_t dma_addr;
int ret = 0;
/* 1. 找一个真实存在的 PCIe 设备(示例:任意第一个设备) */
pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
if (!pdev) {
pr_err("no pci device found\n");
return -ENODEV;
}
pr_info("use pci device %s\n", pci_name(pdev));
/* 2. 分配内核缓冲区 */
buf = kmalloc(BUF_SIZE, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto put_dev;
}
memset(buf, 0xAA, BUF_SIZE); /* 预置已知数据 */
/* 3. 流式映射:设备将写入内存 -> DMA_FROM_DEVICE */
dma_addr = dma_map_single(&pdev->dev, buf, BUF_SIZE, DMA_FROM_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_addr)) {
pr_err("dma_map_single failed\n");
ret = -EIO;
goto free_buf;
}
pr_info("mapped buf=%p dma=%pad\n", buf, &dma_addr);
/* 4. 此处应把 dma_addr 写入设备寄存器并启动 DMA。
* 本示例无真实设备驱动,仅演示映射/解除流程。
* 真实场景:writel(dma_addr, bar + DMA_SRC_REG);
* writel(BUF_SIZE, bar + DMA_LEN_REG);
* writel(START, bar + DMA_CTRL_REG);
* wait_for_completion(...);
*/
/* 5. 解除映射,之后 CPU 才能安全读缓冲区 */
dma_unmap_single(&pdev->dev, dma_addr, BUF_SIZE, DMA_FROM_DEVICE);
/* 6. 数据一致性检查(无真实设备时数据应保持 0xAA) */
{
int i, ok = 1;
u8 *p = buf;
for (i = 0; i < BUF_SIZE; i++) {
if (p[i] != 0xAA) { ok = 0; break; }
}
pr_info("consistency check: %s (first byte=0x%02x)\n",
ok ? "PASS" : "FAIL", p[0]);
}
kfree(buf);
pci_dev_put(pdev);
return 0;
free_buf:
kfree(buf);
put_dev:
pci_dev_put(pdev);
return ret;
}
static void __exit dma_single_exit(void)
{
pr_info("dma_single module unloaded\n");
}
module_init(dma_single_init);
module_exit(dma_single_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("streaming DMA single buffer demo");
编译与运行
# Makefile
obj-m += dma_single.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
make
sudo insmod dma_single.ko
dmesg | tail -n 20
sudo rmmod dma_single
验证方法与通过条件
- 加载后
dmesg应出现use pci device ...、mapped buf=... dma=...、consistency check: PASS。 - 若系统无 PCIe 设备,会打印
no pci device found并返回-ENODEV,属预期失败路径。 - 一致性检查 PASS 表示:映射/解除流程未破坏缓冲区,且解除后 CPU 可正常访问。
- 真实设备接入时,把第 4 步替换为实际寄存器写入与完成等待,再检查设备写入的数据是否符合协议预期。