LINUX.ORG.RU

Передача данных через DMA и PCIe

 , ,


2

2

Всем привет.

Подскажите пожалуйста, что я делаю не так.

Задача: передать массив данных в ПЛИС через PCIe и DMA.

Пока данные могу слать и читать с помощью ioread и iowrite, так же можно переслать через memcry, это говорит что девайс на pci жив и с ним можно работать, dma в пределах ПЛИСА так же отлично работает.

Вот теперь пытаюсь выделить кусок физической памяти в хосте, проинитеть его тестовыми данными (я просто решил её полностью заполнить константным значением), затем передать адрес этой памяти в pcie ПЛИСА и забрать от туда данные через dma (те pcie заберет по адресу данные, положит их к себе в выделенный бар (axi bar) и даст команду dma что можно забирать и складывать в ddr.

регистры ddr и pcie ПЛИСА так же пишутся, проверял. По всей видимости, косяк в коде.

Изначально область BAR0 (axi bar) со стороны ПЛИС обнулена, область для тестовых данных в DDR я тоже зануляю по включению питания. Когда шлю данные из хоста в BAR0 (axi bar) появляются странные, но достаточно не хаотичные цифры, но отличные от тех что шлю. Это говорит о том что что-то всётаки приходит из хоста.

Посмотрите пожалуйста код и подскажите что исправить.

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>

#include "vi_cntr.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Drakonof");
MODULE_DESCRIPTION("PCI");
MODULE_VERSION("0.1");
 

#define CDMA_BAR  0
#define PCI_BAR   1
#define DDR_BAR   2


#define DEVICE_NAME "drakonof_pci"

static dma_addr_t dma_handle = 0;
static void * dmabuf = NULL;
static volatile uint32_t *pci_reg_mem = NULL, *cdma_reg_mem = NULL;
static volatile uint32_t  *ddr_mem = NULL;
static resource_size_t cdma_start,cdma_len,pci_start,pci_len,ddr_start,ddr_len;


static struct pci_device_id vi_cntr_pci_id[] = {
{ PCI_DEVICE(VI_CNTR_VENDOR, VI_CNTR_ID), },
{ 0, }
};


MODULE_DEVICE_TABLE(pci, vi_cntr_pci_id);


static int vi_cntr_probe(struct pci_dev *pDev, const struct pci_device_id *id);
static void vi_cntr_remove(struct pci_dev *pDev);


static struct pci_driver vi_cntr_pci = {
    .name = DEVICE_NAME,
    .id_table = vi_cntr_pci_id,
    .probe = vi_cntr_probe,
    .remove = vi_cntr_remove
};


static int __init pci_init(void)
{
    struct pci_dev *p_pci_dev = NULL;

    if((p_pci_dev = pci_get_device(VI_CNTR_VENDOR,VI_CNTR_ID,                     p_pci_dev)) == NULL)
    {
        printk(KERN_NOTICE "PCI device not finded\n");
    }

    return pci_register_driver(&vi_cntr_pci);
}


static void __exit pci_exit(void)
{
    pci_unregister_driver(&vi_cntr_pci);
}


static int vi_cntr_probe(struct pci_dev *p_pci_dev, const struct pci_device_id *pId)
{
    u16 vendor = 0,
        id = 0;

    u32 i = 0;


    if(pci_enable_device(p_pci_dev))
    {
        printk(KERN_INFO "pci_enable_device error\n");
    }

    pci_read_config_word(p_pci_dev, PCI_VENDOR_ID, &vendor);
    pci_read_config_word(p_pci_dev, PCI_DEVICE_ID, &id);

    printk(KERN_INFO "\n----------------------------------\n");
    printk(KERN_INFO "Device vendor: 0x%X\nDevice id: 0x%X\n", vendor, id);

    cdma_start = pci_resource_start(p_pci_dev,CDMA_BAR);
    cdma_len = pci_resource_len(p_pci_dev,CDMA_BAR);
    pci_start = pci_resource_start(p_pci_dev,PCI_BAR);
    pci_len = pci_resource_len(p_pci_dev,PCI_BAR);
    ddr_start = pci_resource_start(p_pci_dev,DDR_BAR);
    ddr_len = pci_resource_len(p_pci_dev,DDR_BAR);

    if(pci_set_dma_mask(p_pci_dev, DMA_BIT_MASK(64)))
    {
        printk(KERN_INFO "pci_set_dma_mask error.\n");
        return -1;
    }


    if((dmabuf=dma_alloc_coherent(&p_pci_dev->dev,8192,&dma_handle,GFP_USER)) == NULL)
    {
        printk(KERN_INFO "dma_alloc_coherent error.\n");
        return -1;
    }

    if(pci_request_regions(p_pci_dev, DEVICE_NAME))
    {
        printk(KERN_INFO "pci_request_regions error.\n");
        return -1;
    }

    pci_set_master(p_pci_dev);

    if((cdma_reg_mem = ioremap(cdma_start,cdma_len)) == NULL)
    {
        printk(KERN_INFO "ioremap pci_reg_mem error.\n");
        return -1;
    }

    if((pci_reg_mem = ioremap(pci_start,pci_len)) == NULL)
    {
        printk(KERN_INFO "ioremap pci_reg_mem error.\n");
        return -1;
    }

    if((ddr_mem = ioremap(ddr_start,ddr_len)) == NULL)
    {
        printk(KERN_INFO "ioremap pci_reg_mem error.\n");
        return -1;
    }

    // здесь я заполняю массив для передачи константой
    dma_sync_single_for_cpu(&p_pci_dev->dev, dma_handle, 8192, DMA_TO_DEVICE);
    for(i = 0; i < 256; i++)
    {
        *(u32*)(dmabuf + i)= 0xABCDEF89;
    }
    dma_sync_single_for_device(&p_pci_dev->dev, dma_handle, 8192, DMA_TO_DEVICE);

    // передаю адрес выделенной физической памяти в pcie ПЛИСА
    pci_reg_mem[0x208/4]=(dma_handle >> 32);
    pci_reg_mem[0x20c/4]=(dma_handle & 0xFFFFFFFF);

    // заполняю регистры cdma ПЛИС
    cdma_reg_mem[0x18/4]=0x30000000; // из BAR0 (axi bar)
    cdma_reg_mem[0x20/4]=0x80000000; // в ddr
    // передать 256 байт, это так же запускает копирование 
    // данных из BAR0 (axi bar) в ddr
    cdma_reg_mem[0x28/4]=256;

    // жду статуса конца передачи от cdma
    i = 0;
    while(!(cdma_reg_mem[0x4/4] & 0x2))
    {
        if(i == 100000)
    { 
        printk(KERN_INFO "SR ERR %X\n",cdma_reg_mem[0x4/4]);
        break;
    }
        else i++;
    }

    iounmap(pci_reg_mem);
    iounmap(cdma_reg_mem);
    iounmap(ddr_mem);
    pci_release_regions(p_pci_dev);
    pci_disable_device(p_pci_dev);
    dma_free_coherent(&p_pci_dev->dev, 8192, dmabuf, dma_handle);

  return 0;
}

Спасибо!

TL;DR

Но похоже забыто важное обстоятельства про адреса и необходимость их трансляции:

  • виртуальные адреса «внутри» CPU (как kernel-mode так и user-mode для каждого процесса);
  • физический адрес на стороне CPU;
  • адрес на конкретной физической шине PCIe со стороны «хоста» (обычно это окно из физического адресного пространства CPU);
  • физический адрес на стороне PCIe-устройства (может быть другим «хостом», в том числе CPU).
anonymous ()
    // передаю адрес выделенной физической памяти в pcie ПЛИСА
    pci_reg_mem[0x208/4]=(dma_handle >> 32);
    pci_reg_mem[0x20c/4]=(dma_handle & 0xFFFFFFFF);

адрес точно передается правильно ? тут довольно странно видеть big-endian - в младшем регистре старшее слово, в старшем регистре младшее слово

anonymous ()
Ответ на: комментарий от Drakonoff

Так а в результате младшие 23 бита dma_handle у тебя точно получаются нулевыми?

The Lower Address Translation registers are limited by the address width of each AXI:BAR. Because both the AXI:BAR0 (AXI_PCIe_SG) and AXI:BAR1 (AXI_PCIe_DM) ports are configured with an 8 megabyte address space, the least significant 23 bits (bits [22:0]) of the Lower Address Translation Registers (offsets 0x20c and 0x214) should always be zero.

anonymous ()
Ответ на: комментарий от Drakonoff

И если ты используешь Scatter Gather (0x208 и 0x20c), то где у тебя кольцо DMA-дескрипторов формируется?

Ощущение что ты спутал его с Data Mover (0x210 и 0x214) и неправильно понял 1/4 datasheet (или не читал).

anonymous ()
Ответ на: комментарий от anonymous

Вы сейчас про даташит к AXI Memory Mapped to PCI Express (PG055)?

0x210 и 0x214 это смещения axi bar 1, он же вроде ничем не отличается от axi bar 0 (0x208 и 0x20C).

или именно это я не понял? не могу найти где написано что разные axi bar’ы под разные цели.

scatter/gather пока не использую

Drakonoff ()
Последнее исправление: Drakonoff (всего исправлений: 1)
Ответ на: комментарий от anonymous

Таки отличается.

Попробовал с AXIBAR2PCIEBAR_1L, как результат 64 слова в брам заполнены 0xCCCCCCCC vs 0xABCDEF89 требуемых.

В общем тоже не саксес… Но хотя бы небольшая подвижка.

Drakonoff ()
Последнее исправление: Drakonoff (всего исправлений: 3)

Я бегло посмотрел, ты вроде всё правильно делаешь, выделил dma_alloc_coherent и dma_handle отправил в устройство, а в dmabuf пишешь. В ПЛИС декодируешь адрес dma_handle из заголовка, значит это данные устройству

Тут на форуме наверное в основном по PCI помочь могут, а я мог бы и по ПЛИС подсказать, покажи немного кода ПЛИС

Что у тебя там, Xilinx? Какая отладочная плата

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

спасибо. RTL кода пока нет. Но могу прикрепить блок диаграмму с настройками ip и таблицу адресов. Xilinx AC701, но проект создан относительно кристалла xc7a200tfbg676-2(который стоит на борде), а не платы.

да, SG буду позже реализовывать, как и прерывания и тп..

сюда я залил 2 pdf. Первый это блок диаграмма. Вторая настройка ip ядер и адреса. https://drive.google.com/open?id=1y1WMkIJ-GCjfnaVEMMCs0CdpLPb2HwW7

еще раз спасибо большое.

Drakonoff ()
Ответ на: комментарий от Drakonoff

т.е. ПЛИС пилит другой сотрудник? Дело в том что я это делал на Altera Xilinx и Lattice врукопашную, все ядра поддерживают низкоуровневый режим. Я разбирал TLP пакеты вручную. Там мне было просто - мне приходит пакет, я читаю адрес, вижу что он такой, какой был передан через BAR

Сложно понять, что куда пишется этот dma_handle и что гарантирует формирование TLP пакета именно с таким адресом. Может программист Microblaze уточнит?

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Плис и микроблэйз тоже я делаю, топ лэвэл плиса это блок диаграмма состоящая из ip ядер. В начале я решил просто довериться ксайлинс и ничего самому не писать, а просто собрать конфигурацию из того что есть. Микроблэйз у меня стоит для того что бы просто смотреть память, он ничего не инитит по включению, потом он станет процом для бутлоадера. На сколько я понял ничего инитеть то и не нужно.

Как Вы делали свою железку, Вы же не писали pcie ip с нуля?

Drakonoff ()
Последнее исправление: Drakonoff (всего исправлений: 1)
Ответ на: комментарий от Drakonoff

Как Вы делали свою железку, Вы же не писали pcie ip с нуля?

Брал либо Soft PCI-E IP готовое на Lattice, либо Hard IP в составе ПЛИС (Altera, Xilinx). Используя готовые ядра, я увидел, что у них всегда есть два интерфейса (даже три, если считать PIPE, но он скорее между логическим и физическим уровнем)

Помимо AXI и Avalon, ядра также поддерживали простой интерфейс, типа как FIFO, нужно было просто давать строб Packet Start и в конце Packet End, без подсчета CRC. Вот так анализируя такие raw-TLP пакеты я и сделал свои проекты

Мне кажется сделать самому проще чем разобраться в этих ядрах поверх PCI-E. Сам сделал обмен, парсинг пакетов (они примитивные пакеты по сути, неожидал от PCI такой простоты), сам сделал аналог SG-DMA, но мне оно нужно было, буферы в ядре Linux нужны были большие

Возможно стоит рассмотреть такой путь, я подскажу если что, будут приходить внятные пакеты, будет ясно видно что в них и что делать. А что делают ядра от Xilinx с тем что приходит - мне пока не ясно из картинок

Отлаживал я свои проекты тупо через UART, приходит пакет, я его ловлю в своем отладочном модуле и медленно выдаю по UART и смотрел что пришло, затем отправлял

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

Извините, что пропал, сессия началась и сразу куча проблем навалилась..

Дело в том, что написать свой PCIe, конечно, хотелось бы, но в то же время хочется доделать то, что есть, и не пробовать множество других вариантов.

я нашел тут код на точно такую же систему как у меня, но программист (драйвер которого очень похож на мой) в конце своего кода создаёт класс может это как то влияет? если честно я вообще не врубаюсь что он делает с этим классом и вообще что это)

class_my_tst = class_create(THIS_MODULE, «my_enc_class»);

res=alloc_chrdev_region(&my_dev, 0, 1, DEVICE_NAME);

my_cdev = cdev_alloc( );

device_create(class_my_tst,NULL,my_dev,NULL,«my_bm%d»,MINOR(my_dev));

Drakonoff ()
Последнее исправление: Drakonoff (всего исправлений: 2)
Ответ на: комментарий от Drakonoff

Мне кажется это ни на что не влияет, может разве что если драйвер является частью стека драйверов, если есть что-то стандартное сверху. Например драйвер PCI а далее класс «видеокарты» или «модемы», тут не могу подсказать, видеокарта может быть например не только PCI, но класс один

А может есть возможность низкоуровнево схватить данные и увидеть что же там принято?

Из сообщений в этой теме я так и не понял, что в ПЛИС хранит этот DMA-адрес, который высылает дайвер? Нужно убедиться, что этот адрес реально приходит в ПЛИС и что он вообще где-то учитывается вообще

I-Love-Microsoft ★★★★★ ()
Ответ на: комментарий от I-Love-Microsoft

хорошо, про класс я понял.

да, в вт пойду на работу и посмотрю в дебагере (ILA) что там бежит, совсем забыл про это..

адрес должен хранить регистр pci и он действительно там записан, я поставил microblaze и в debug смотрел что в памяти и регистрах хранится, а то что как он учитывается это хороший вопрос.

написал в xilinx, прикрепил туда проект и остальную информацию по мосту, посмотрим что ответят

Drakonoff ()
Ответ на: комментарий от Drakonoff

Вот еще моя тема 4 летней давности написать драйвер для своего устройства

У меня всё было низкоуровнево, поэтому я четко видел адрес в своем аналоге ILA, и я четко его брал и формировал пакеты именно с этим адресом - поэтому все работало. Вот проблема проекта из этой темы как раз в «неясности судьбы регистра» этого

Нужна четкость понимания как применяется этот адрес, может оно вообще его не хранит, не применяет, не подставляет в пакеты, вообще не понятно, так что да - ждем прояснения

I-Love-Microsoft ★★★★★ ()