2012-06-28 70 views
7

我在寫一個linux設備驅動程序,允許FPGA(當前通過PCI Express連接到PC)將DMA數據直接存入CPU RAM。這需要在沒有任何交互的情況下發生,並且用戶空間需要訪問數據。一些細節: - 運行64位的Fedora 14 - 系統具有的RAM 8GB - 的FPGA(氣旋IV)是PCIe卡允許FPGA直接DMA到CPU RAM的Linux設備驅動程序

上在試圖實現這一點我執行以下操作: - 保留上2GB的內存與memmap 6GB $ 2GB(不會啓動是我添加內存= 2GB)。我可以看到/ proc/meminfo中保留了高2GB的RAM - 映射BAR0允許讀取和寫入FPGA寄存器(這很好用) - 在我的驅動程序中使用remap_pfn_range()實現了mmap函數 - 使用ioremap以獲得緩衝區的虛擬地址 - 增加了ioctl調用(用於測試)將數據寫入緩衝區 - 通過進行ioctl調用將數據寫入緩衝區並驗證數據來自用戶空間緩衝區,對mmap進行了測試

我面臨的問題是當FPGA開始DMA數據到我提供的緩衝區地址。我不斷得到PTE錯誤(來自DMAR :)或下面的代碼我得到以下錯誤: DMAR:[DMA寫入]請求設備[01:00.0]故障地址186dc5000
DMAR:[故障原因01]存在位根條目是明確 DRHD:處理故障狀態章第3條

通過每次0x1000的基礎上,DMA從FPGA

在這裏,在第一線增量的地址是我的init()代碼:

#define IMG_BUF_OFFSET  0x180000000UL // Location in RAM (6GB) 
#define IMG_BUF_SIZE  0x80000000UL // Size of the Buffer (2GB) 

#define pci_dma_h(addr) ((addr >> 16) >> 16) 
#define pci_dma_l(addr) (addr & 0xffffffffUL) 

if((pdev = pci_get_device(FPGA_VEN_ID, FPGA_DEV_ID, NULL))) 
{ 
    printk("FPGA Found on the PCIe Bus\n"); 

    // Enable the device 
    if(pci_enable_device(pdev)) 
    { 
     printk("Failed to enable PCI device\n"); 
     return(-1); 
    } 
    // Enable bus master 
    pci_set_master(pdev); 

    pci_read_config_word(pdev, PCI_VENDOR_ID, &id); 
    printk("Vendor id: %x\n", id); 
    pci_read_config_word(pdev, PCI_DEVICE_ID, &id); 
    printk("Device id: %x\n", id); 
    pci_read_config_word(pdev, PCI_STATUS, &id); 
    printk("Device Status: %x\n", id); 
    pci_read_config_dword(pdev, PCI_COMMAND, &temp); 
    printk("Command Register : : %x\n", temp); 
    printk("Resources Allocated :\n"); 
    pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &temp); 
    printk("BAR0 : %x\n", temp); 

    // Get the starting address of BAR0 
    bar0_ptr = (unsigned int*)pcim_iomap(pdev, 0, FPGA_CONFIG_SIZE); 
    if(!bar0_ptr) 
    { 
    printk("Error mapping Bar0\n"); 
    return -1; 
    } 
    printk("Remapped BAR0\n"); 

    // Set DMA Masking 
    if(!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) 
    { 
    pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); 
    printk("Device setup for 64bit DMA\n"); 
    } 
    else if(!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) 
    { 
    pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); 
    printk("Device setup for 32bit DMA\n"); 
    } 
    else 
    { 
    printk(KERN_WARNING"No suitable DMA available.\n"); 
    return -1; 
    } 

    // Get a pointer to reserved lower RAM in kernel address space (virtual address) 
    virt_addr = ioremap(IMG_BUF_OFFSET, IMG_BUF_SIZE); 
    kernel_image_buffer_ptr = (unsigned char*)virt_addr; 
    memset(kernel_image_buffer_ptr, 0, IMG_BUF_SIZE); 
    printk("Remapped image buffer: 0x%p\n", (void*)virt_addr); 

}

這裏是我的mmap代碼:

unsigned long image_buffer; 
unsigned int low; 
unsigned int high; 

if(remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, 
        vma->vm_end - vma->vm_start, 
        vma->vm_page_prot)) 
{ 
    return(-EAGAIN); 
} 

image_buffer = (vma->vm_pgoff << PAGE_SHIFT); 

if(0 > check_mem_region(IMG_BUF_OFFSET, IMG_BUF_SIZE)) 
{ 
    printk("Failed to check region...memory in use\n"); 
    return -1; 
} 

request_mem_region(IMG_BUF_OFFSET, IMG_BUF_SIZE, DRV_NAME); 

// Get the bus address from the virtual address above 
//dma_page = virt_to_page(addr); 
//dma_offset = ((unsigned long)addr & ~PAGE_MASK); 
//dma_addr = pci_map_page(pdev, dma_page, dma_offset, IMG_BUF_SIZE, PCI_DMA_FROMDEVICE);  
//dma_addr = pci_map_single(pdev, image_buffer, IMG_BUF_SIZE, PCI_DMA_FROMDEVICE); 
//dma_addr = IMG_BUF_OFFSET; 
//printk("DMA Address: 0x%p\n", (void*)dma_addr); 

// Write start or image buffer address to the FPGA 
low = pci_dma_l(image_buffer); 
low &= 0xfffffffc; 
high = pci_dma_h(image_buffer); 
if(high != 0) 
    low |= 0x00000001; 

*(bar0_ptr + (17024/4)) = 0; 

//printk("DMA Address LOW : 0x%x\n", cpu_to_le32(low)); 
//printk("DMA Address HIGH: 0x%x\n", cpu_to_le32(high)); 
*(bar0_ptr + (4096/4)) = cpu_to_le32(low); //2147483649; 
*(bar0_ptr + (4100/4)) = cpu_to_le32(high); 
*(bar0_ptr + (17052/4)) = cpu_to_le32(low & 0xfffffffe);//2147483648; 

printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 4096, *(bar0_ptr + (4096/4))); 
printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 4100, *(bar0_ptr + (4100/4))); 
printk("Process Read Command: Addr:0x%x Ret:0x%x\n", 17052, *(bar0_ptr + (17052/4))); 
return(0); 

感謝您的幫助,您可以提供。

回答

5

您是否控制自己編寫TLP數據包的RTL代碼,或者您是否可以命名您正在使用的DMA引擎和PCIe BFM(總線功能模型)?你的數據包在模擬器中看起來像什麼?大多數體面的BFM應該陷入困境,而不是讓它在PCIe硬件捕獲系統後部署。

要定位高2GB的RAM,您需要從設備發送2DW(64位)地址。您的Fmt/Type中的位是否設置爲這樣?錯誤地址看起來像一個32位掩碼的總線地址,所以這個級別的東西可能不正確。同時請記住,因爲在將目標地址寫入PCIe設備端點時,PCIe是大端的小心。如果Fmt不正確,您可能會將目標地址的較低字節放入有效負載中 - 再次,體面的BFM應該會發現所產生的數據包長度不匹配。

如果您有近期的主板/現代CPU,PCIe端點應該執行PCIe AER(高級錯誤報告),所以如果運行最近的Centos/RHEL 6.3,您應該得到一個關於端點故障的dmesg報告。這個報告非常有用,因爲報告捕獲了數據包的第一批DW到特殊捕獲寄存器,因此您可以查看收到的TLP。

在您的內核驅動程序中,我看到您設置了DMA掩碼,這還不夠,因爲您沒有編寫mmu以允許從設備寫入頁面。看看pci_alloc_consistent()的執行情況,看看你還應該調用什麼來達到這個目的。

+0

「還要記住,因爲PCIe是大端」我以爲PCI是小端的 – cha5on

2

如果您仍然在尋找原因,那麼它會像這樣: 您的內核默認啓用了DMA_REMAPPING標誌,因此IOMMU會拋出上述錯誤,因爲IOMMU上下文/域條目未針對您的設備進行編程。

您可以嘗試在內核命令行中使用intel_iommu = off或將IOMMU置於設備的旁路模式。 Regards, Samir

相關問題