在 PCIe 设备驱动中使用 DMA (Direct Memory Access) 进行数据传输时,通常需要分配一段可以被设备访问的内存。这可以通过 page_alloc
和其他相关函数来完成。下面是一些常用的方法和步骤,用于在 Linux 内核中为 PCIe 设备分配 DMA 可访问的内存。
1. 使用 dma_alloc_coherent
最常用的方法是使用 dma_alloc_coherent()
函数,它会为你分配一块连续的物理内存,并返回相应的虚拟地址。这个地址是可直接由 PCIe 设备访问的。
#include <linux/dma-mapping.h>
void *cpu_addr;
dma_addr_t dma_handle;
// 分配一块 DMA 可访问的内存
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
// 错误处理
}
dev
是指向你的 PCIe 设备结构体的指针。size
是你需要分配的字节数。GFP_KERNEL
是用于分配内存的标志,表示这是一个内核态分配。
2. 使用 dma_alloc_writecombine
如果你的设备对性能有较高要求,可以选择使用写合并方式分配内存:
cpu_addr = dma_alloc_writecombine(dev, size, &dma_handle, GFP_KERNEL);
3. 使用 get_free_pages()
如果你希望手动管理页面(例如,如果你只需要单个页面),可以使用 __get_free_pages()
来获取一个物理页面,然后进行相应的映射:
unsigned long addr;
addr = __get_free_pages(GFP_KERNEL, order); // order 表示需要多少个连续页面
if (!addr) {
// 错误处理
}
但是请注意,得到的是一个虚拟地址,仍然需要将其转换为 DMA 地址以供设备使用。这可以通过调用 virt_to_phys()
得到:
dma_addr_t dma_address = virt_to_phys((void *)addr);
4. 使用页表操作
对于更复杂的场景(如大容量缓冲区或多个页),可能还需要使用页表管理来动态映射与解除映射。
清理资源
无论您采用哪种方法,都必须确保在不再使用时释放这块 DMA 内存:
dma_free_coherent(dev, size, cpu_addr, dma_handle);
// 或者
free_page(addr); // 如果使用了 __get_free_pages()
注意事项
- 确保在合适上下文中调用这些 API,例如避免在不可睡眠上下文中使用 GFP_KERNEL 标志。
- 在多 CPU 系统中,请注意缓存一致性问题,因此通常建议使用适当选项来保证数据的一致性,比如用
dma_sync_single_for_cpu
和dma_sync_single_for_device
等函数来同步数据。
通过上述方法,可以有效地为 PCIe 驱动程序中的 DMA 操作分配所需的内存。
内容由零声教学AI助手提供,问题来源于学员提问