有一段时间,我想允许用户空间应用程序分配DMA缓冲区并将其映射到用户空间,并获得物理地址以便能够在用户空间完全绕过Linux内核控制我的设备和执行DMA事务(总线主控)。然而,我使用了略微不同的方法。首先,我从一个最小的内核模块开始,该模块正在初始化/探测PCIe设备并创建字符设备。然后,该驱动程序允许用户空间应用程序做两件事:
1. 使用remap_pfn_range()函数将PCIe设备的I/O bar映射到用户空间。
2. 分配和释放DMA缓冲区,将它们映射到用户空间并将物理总线地址传递给用户空间应用程序。
基本上,这归结为一个自定义实现mmap()调用(虽然是file_operations)。其中一个易于处理的是I/O bar:
struct vm_operations_struct a2gx_bar_vma_ops = {
};
static int a2gx_cdev_mmap_bar2(struct file *filp, struct vm_area_struct *vma)
{
struct a2gx_dev *dev;
size_t size;
size = vma->vm_end - vma->vm_start;
if (size != 134217728)
return -EIO;
dev = filp->private_data;
vma->vm_ops = &a2gx_bar_vma_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_private_data = dev;
if (remap_pfn_range(vma, vma->vm_start,
vmalloc_to_pfn(dev->bar2),
size, vma->vm_page_prot))
{
return -EAGAIN;
}
return 0;
}
使用pci_alloc_consistent()
分配DMA缓冲区的方法稍微复杂一些:
static void a2gx_dma_vma_close(struct vm_area_struct *vma)
{
struct a2gx_dma_buf *buf;
struct a2gx_dev *dev;
buf = vma->vm_private_data;
dev = buf->priv_data;
pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr, buf->dma_addr);
buf->cpu_addr = NULL;
}
struct vm_operations_struct a2gx_dma_vma_ops = {
.close = a2gx_dma_vma_close
};
static int a2gx_cdev_mmap_dma(struct file *filp, struct vm_area_struct *vma)
{
struct a2gx_dev *dev;
struct a2gx_dma_buf *buf;
size_t size;
unsigned int i;
dev = filp->private_data;
size = vma->vm_end - vma->vm_start;
if (size < sizeof(unsigned long))
return -EINVAL;
for (i = 0; i < A2GX_DMA_BUF_MAX; ++i) {
buf = &dev->dma_buf[i];
if (buf->cpu_addr == NULL)
break;
}
if (buf->cpu_addr != NULL)
return -ENOBUFS;
buf->cpu_addr = pci_alloc_consistent(dev->pci_dev, size, &buf->dma_addr);
if (buf->cpu_addr == NULL)
return -ENOMEM;
memcpy(buf->cpu_addr, &buf->dma_addr, sizeof(buf->dma_addr));
buf->size = size;
buf->priv_data = dev;
vma->vm_ops = &a2gx_dma_vma_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_private_data = buf;
if (remap_pfn_range(vma, vma->vm_start,
vmalloc_to_pfn(buf->cpu_addr),
size, vma->vm_page_prot))
{
pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr,
buf->dma_addr);
buf->cpu_addr = NULL;
return -EAGAIN;
}
return 0;
}
一旦这些准备工作就位,用户空间应用程序基本上可以做任何事情——通过读/写I/O寄存器来控制设备,分配和释放任意大小的DMA缓冲区,并使设备执行DMA事务。唯一缺失的部分是中断处理。我正在用户空间进行轮询,消耗我的CPU,并禁用了中断。
希望这可以帮助你。祝你好运!