Драйвер устройства Linux, позволяющий ПЛИС выполнять прямой доступ к памяти напрямую в ОЗУ ЦП

Я пишу драйвер устройства Linux, чтобы позволить FPGA (в настоящее время подключенному к ПК через PCI Express) передавать данные DMA непосредственно в ОЗУ ЦП. Это должно происходить без какого-либо взаимодействия, и пользовательское пространство должно иметь доступ к данным. Некоторые подробности: - Запуск 64-битной Fedora 14 - В системе 8 ГБ ОЗУ - ПЛИС (Cyclone IV) находится на карте PCIe.

В попытке добиться этого я выполнил следующее: - Зарезервировал верхние 2 ГБ ОЗУ в grub с memmap 6 ГБ $ 2 ГБ (не загружается, если я добавляю mem = 2 ГБ). Я вижу, что верхние 2 ГБ ОЗУ зарезервированы в / proc / meminfo - Отображенный BAR0, чтобы разрешить чтение и запись в регистры FPGA (это отлично работает) - В моем драйвере реализована функция mmap с remap_pfn_range () - Используйте ioremap, чтобы получить виртуальный адрес буфера - Добавлены вызовы ioctl (для тестирования) для записи данных в буфер - Протестировано mmap, выполнив вызов ioctl для записи данных в буфер и проверив, что данные были в буфере из пользовательского пространства

Проблема, с которой я столкнулся, заключается в том, что ПЛИС начинает передавать данные DMA на адрес буфера, который я предоставляю. Я постоянно получаю ошибки PTE (от DMAR :) или с помощью приведенного ниже кода я получаю следующую ошибку: DMAR: [DMA Write] Запрос устройства [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);

Спасибо за любую помощь, которую вы можете оказать.


person jedrumh    schedule 28.06.2012    source источник


Ответы (2)


Вы сами управляете кодом RTL, который записывает пакеты TLP, или можете назвать используемый механизм DMA и PCIe BFM (функциональная модель шины)? Как ваши пакеты выглядят в симуляторе? Самый достойный BFM должен улавливать это, а не позволять вам находить его после развертывания с помощью системы аппаратного захвата PCIe.

Чтобы настроить таргетинг на верхние 2 ГБ ОЗУ, вам нужно будет отправить 2DW (64-битные) адреса с устройства. Установлены ли биты в вашем Fmt / Type для этого? Адрес с ошибкой выглядит как замаскированный 32-битный адрес шины, поэтому что-то на этом уровне, вероятно, неверно. Также имейте в виду, что, поскольку PCIe является прямым порядком байтов, будьте осторожны при записи целевых адресов в конечную точку устройства PCIe. У вас могут быть младшие байты целевого адреса, попадающие в полезную нагрузку, если Fmt неверен - опять же, приличный BFM должен определить результирующее несоответствие длины пакета.

Если у вас новейшая материнская плата / современный процессор, конечная точка PCIe должна выполнять PCIe AER (расширенный отчет об ошибках), поэтому при запуске последней версии Centos / RHEL 6.3 вы должны получить dmesg отчет об ошибках конечной точки. Это очень полезно, поскольку отчет фиксирует первую горстку DW пакета в специальные регистры захвата, поэтому вы можете просмотреть TLP в том виде, в каком он был получен.

В драйвере ядра я вижу, что вы устанавливаете маску DMA, этого недостаточно, поскольку вы не запрограммировали mmu, чтобы разрешить запись на страницы с устройства. Посмотрите на реализацию pci_alloc_consistent(), чтобы увидеть, что еще вы должны вызывать для этого.

person shuckc    schedule 31.07.2012
comment
Также имейте в виду, что, поскольку PCIe является прямым порядком байтов, у меня сложилось впечатление, что PCI является прямым порядком байтов. - person cha5on; 20.12.2016

Если вы все еще ищете причину, то она выглядит так: в вашем ядре по умолчанию включены флаги DMA_REMAPPING, поэтому IOMMU выдает указанную выше ошибку, поскольку записи контекста / домена IOMMU не запрограммированы для вашего устройства.

Вы можете попробовать использовать intel_iommu = off в командной строке ядра или перевести IOMMU в режим обхода для вашего устройства. С уважением, Самир

person Samir Das    schedule 18.02.2013