DMA to User-Space
Jonathan Haws
Jonathan.Haws at sdl.usu.edu
Thu Nov 5 04:40:22 EST 2009
> Jonathan Haws wrote:
> > All,
> >
> > I have what may be an unconventional question:
> >
> > Our application consists of data being captured by an FPGA,
> processed, and transferred to SDRAM. I simply give the FPGA an
> address of where I want it stored in SDRAM and it simply DMAs the
> data over and interrupts me when finished. I then take that data
> and store it to disk.
> >
> > I have code in user space that handles all of the writing to disk
> nicely and fast enough for my application (I am capturing data at
> about 35-40 Mbytes/sec).
> >
> > My question is this: is it possible to give a user-space pointer
> to the FPGA to DMA to? It seems like I would have problems with
> alignment, address manipulation, and a whole slew of other issues.
> >
> > What would be the best way to accomplish something like that? I
> want to handle all the disk access in user-space, but I do not want
> to have to copy 40 MB/s from kernel space to user-space either.
> >
> You can maintain a DMA buffer in kernel, then mmap to user space.
> And
> maybe you need some handshake between FPGA and the apps to balance
> input
> datas with datas to disk.
> > I can maintain an allocated, DMA-safe buffer in kernel space if
> needed. Can I simply get a user-space pointer to that buffer? What
> calls are needed to translate addresses?
> >
> Use remap_pfn_range() in your kernel DMA buffer manipulation driver
> .mmap() handler to export DMA buffer address to user space.
>
Can you provide an example for how to do that? I have an mmap routine to map BARs that the FPGA maintains and I can access those, however when I try to map the DMA buffer and access what is in it, the system crashes. Here is the mmap function I have right now:
/* fpga_mmap()
*
* Description:
* The purpose of this function is to serve as a 'file_operation'
* which maps different PCI resources into the calling processes
* memory space.
*
* NOTE: The file offset are in page size; i.e.:
* offset 0 in process's mmap syscall -> BAR0
* offset 4096 in process's mmap syscall -> BAR1
* offset 8192 in process's mmap syscall -> BAR2
* offset 12288 -> Streaming DMA buffer
*
* Arguments:
* struct file *filp -- struct representing an open file
* struct vm_area_struct *vma -- struct representing memory 'segment'
*
* Returns:
* int -- indication of success or failure
*
*/
int fpga_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct pci_dev *dev;
unsigned long addressToMap;
uint8_t mapType = 0; /* 0 = IO, 1 = memory */
/* Get the PCI device */
dev = (struct pci_dev*)(filp->private_data);
/* Map in the appropriate BAR based on page offset */
if (vma->vm_pgoff == FPGA_CONFIG_SPACE)
{
/* Map BAR1 (the CONFIG area) */
printk(KERN_ALERT "FPGA: Mapping BAR1 (CONFIG BAR).\n");
addressToMap = pci_resource_start(dev, FPGA_CONFIG_SPACE);
printk(KERN_ALERT "FPGA: PCI BAR1 (CONFIG BAR) Size -> %#08x.\n",
pci_resource_len(dev, FPGA_CONFIG_SPACE));
mapType = 0;
}
else if(vma->vm_pgoff == FPGA_TEST_SPACE)
{
/* Map BAR2 (the TEST area) */
printk(KERN_ALERT "FPGA: Mapping BAR2 (TEST BAR).\n");
addressToMap = (pci_resource_start(dev, FPGA_TEST_SPACE) +
pci_resource_len(dev, FPGA_TEST_SPACE)) - FPGA_TEST_LENGTH;
printk(KERN_ALERT "FPGA: PCI BAR2 (TEST BAR) Size -> %#08x.\n",
pci_resource_len(dev, FPGA_TEST_SPACE));
mapType = 0;
}
else if(vma->vm_pgoff == 3)
{
addressToMap = (unsigned long)&fpga_drv.strmData[0];
mapType = 1;
}
else
{
printk(KERN_ALERT " FPGA: Invalid BAR mapping specified.\n");
return ERROR;
}
/* Execute the mapping */
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
printk(KERN_ALERT "FPGA: vmSize -> 0x%x.\n",
(unsigned int)(vma->vm_end - vma->vm_start));
if( mapType == 0 )
{
if(io_remap_pfn_range(vma, vma->vm_start, addressToMap >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot) != 0)
{
printk(KERN_ALERT "FPGA: Failed to map BAR PCI space to user space.\n");
return ERROR;
}
}
else
{
printk(KERN_NOTICE "FPGA: Mapping stream ptr (%#08x) to user space\n",(uint32_t)&fpga_drv.strmData[0]);
printk(KERN_NOTICE "FPGA: Setting strmData[0][0] to 0x37\n");
fpga_drv.strmData[0][0] = 0x37;
if(remap_pfn_range(vma, vma->vm_start, addressToMap >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot) != 0)
{
printk(KERN_ALERT "FPGA: Failed to map BAR PCI space to user space.\n");
return ERROR;
}
}
return OK;
}
And here is the failure:
~ # ./main
FPGA: vmSize -> 0x4000.
FPGA: Mapping stream ptr (0xd10613bc) to user space
FPGA: Setting strmData[0][0] to 0x37
Unable to handle kernel paging request for data at address 0x00000000
Faulting instruction address: 0xd105f5e0
Oops: Kernel access of bad area, sig: 11 [#1]
PREEMPT Kilauea
Modules linked in: fpgaDriver
NIP: d105f5e0 LR: d105f5d8 CTR: c003fff4
REGS: cf86dde0 TRAP: 0300 Not tainted (2.6.30.3-wolverine-dirty)
MSR: 00029030 <EE,ME,CE,IR,DR> CR: 24008022 XER: 0000005f
DEAR: 00000000, ESR: 00800000
TASK = cf873950[281] 'main' THREAD: cf86c000
GPR00: 00000037 cf86de90 cf873950 00000028 17d78400 c0302028 c0302068 00000000
GPR08: 00000000 00000000 00000003 c0300000 24008024 100e89c8 100d6590 100917d8
GPR16: 100d0000 100d0000 c0320000 cfaca07c 00000001 cf8c0230 00000003 000000fb
GPR24: 00000000 cf8a6840 cfaca07c 00000000 d10613bc d106128c 00000004 cfaca07c
NIP [d105f5e0] fpga_mmap+0x9c/0x214 [fpgaDriver]
LR [d105f5d8] fpga_mmap+0x94/0x214 [fpgaDriver]
Call Trace:
[cf86de90] [d105f5d8] fpga_mmap+0x94/0x214 [fpgaDriver] (unreliable)
[cf86deb0] [c00c1a4c] mmap_region+0x29c/0x408
[cf86df10] [c000377c] sys_mmap+0x78/0x100
[cf86df40] [c00103fc] ret_from_syscall+0x0/0x3c
Instruction dump:
386302a8 48000735 3b9d0130 3c60d106 7f84e378 386302c4 48000721 3c60d106
386302f8 48000715 813d0130 38000037 <98090000> 7fe3fb78 809f0004 80df0008
---[ end trace 4393e8cf23caef19 ]---
Where am I going wrong?
Using mmap() would work well for me.
Also, what about this:
1. I open /dev/mem and get a file descriptor
2. I use mmap to reserve some physical addresses for my buffers in user space.
3. I give that address to the FPGA for DMA use.
4. When I get the FPGA interrupt, I invalidate the data cache and write the data to disk
Does that sound like it would work? Would the address I receive from mmap() and pass to the FPGA be the actual physical address, or would I need to send the physical address to the FPGA and use the mmap() address to access and write to disk?
Thanks for the help!
Jonathan
More information about the Linuxppc-dev
mailing list