LINUX.ORG.RU

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

 ,


6

13

Есть некое устройство (на базе ПЛИС), которое видится в системе через lspci, оно сконфигурировано, назначены адресные пространства и т.д. Осилена книга «PCI Express Technology. Comprehensive Guide to Generations 1.x, 2.x, 3.0», принципы работы PCI Express стали полностью понятны.

Теперь нужно с этим устройством работать. Для этого требуется свой драйвер для ОС Linux. Есть крохи информации в LDD3 (почти бесполезные), есть такой пример http://www.fpga4fun.com/PCI6.html

Подскажите какой-нибудь простейший пример PCI драйвера (самый маленький в ядре), или может есть руководство или книжка на эту тему.

Требуется: самый наипростейший интерфейс, чтобы устройство виделось как файл и его можно было бинарно считывать (с большой скоростью, разумеется).

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

Ответ на: комментарий от sergej

структурированные костыли и подпорки! :)

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

Начни с реализации своего конфигспейса и помигай светодиодами тогда, ну и читай доку/примеры на свой IP блок о том как заставить это хозяйство басмастерить.

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

MMIO это значит каждый байт/word/dword будет записываться под контролем CPU?

Да.

Если я выделил в BAR3 диапазон 16 мегабайт, разве нет пути заставить компьютер осуществить непрерывную запись по этому диапазону N байт без участия CPU?

Нет.

А чтение например 8 мегабайт начиная с определенного адреса разве невозможно без участия CPU не реализуя ничего особенного на стороне ПЛИС?

Нет.

Может я буду выгружать что-то из FIFO и отправлять с каждым пакетом чтения всё новые данные из FIFO.

Простой вопрос - _куда_ ты будешь их отправлять? Откуда ты возьмешь адрес?

я надеялся что раз у меня есть диапазон адресов, то компьютер может работать с ним как с куском памяти и читать его без CPU, а на стороне ПЛИС это будет просто ряд TLP MrD запросов по определенным адресам...

Эти запросы будет генерировать процессор, потому что больше некому.

выяснив что устройство хочет диапазон 4K - выделяет его в общем адресном пространстве и это значение адреса попадает например в BAR0

В адресном пространстве, да, но это не адрес памяти. Это шинный адрес: http://lxr.free-electrons.com/source/Documentation/io-mapping.txt

Вижу примеры для ПЛИСины по части PCI-E+SG-DMA, но там есть и другой пример, который чисто на проверку пропускной способности... и там PCI-E БЕЗ DMA на стороне ПЛИС. Там чисто чтение и запись по инициативе хоста (драйвера).

Оборотной стороной этого является полная загрузка одного ядра.

Хочу чтобы когда нужные данные накопятся (например, если это изображение, то несколько строк) - я формирую прерывание (Message Signaled Interrupt, корка поддерживает) из ПЛИС и драйвер считывает новые данные САМ

Разумная стратегия для начала, но, по опыту, это 5МБайт/сек при 100% загрузке процессора.

tailgunner ★★★★★
()
Ответ на: комментарий от slapin

Отлаживаюсь через UART :)

#define mypci_readl(mmio, reg) readl(mmio + reg)
#define mypci_writel(mmio, reg, value) writel((value), mmio + reg)

Запись

mypci_writel(mmio, 0xABC, 0x78563412);
выливается в такой пакет на стороне устройства:
78 56 34 12 bc fa 9f fe 0f 00 00 00 01 00 00 40
(прошу не пугаться, байты справа налево идут, не смог заставить работать свою нормальную реализацию UART+FIFO). Ясельный уровень, но я все равно рад. Адрес BAR0 «mypci: mem region FE9FF000 4096» - всё верно, пакет на адрес FE9FFABC.

Вопрос: а как записать сразу 100 байт? Чтобы сформировался один очень длинный TLP пакет. В какой главе LDD это описано?

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

Чтобы сформировался один очень длинный TLP пакет.

Очень длинный --проблематично. Я делал так:

http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/pcie...

Получил около гига в секунду при 100% занятости одного ядра. Чтоб быстрее, ПЛИС сам должен просить данные.

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

Этот способ лишь для x86-архитектуры? Нет универсального способа сделать пакет, допустим, длиной 10-ть DW? Нет так нет, буду знать :) Значит DMA впереди, не знаю осилю ли, но попытаюсь.

I-Love-Microsoft ★★★★★
() автор топика
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Про другие архетиктуры я не знаю. С DMA ничего сложного нет. У меня правда основной поток из ПЛИСа в хост. Драйвер туп как грабли: mmapит BAR в и пиннит память по ioctl. Все остальное вне ядра. ПЛИС по мере надобности вычитывает список адресов прибитых страниц и тупо льёт туда данные. Даже прерываний ненужно.

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

#define mypci_readl(mmio, reg) readl(mmio + reg)

Смысл? Обычно делают, чтобы макрос в неявном виде использовал контекст устройства для базового адреса. Например, во всех функциях у тебя будет локальная переменная dev. И будет

#define mypci_readl(reg) readl(dev->mmio + reg)

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

Лучше всего басмастерить. Есть еще PCI burst + pio, но по моему мнению его эффективность сомнительна. Лучше сразу бысмастерить и burst'ить PCIe-м.

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

Короче почитай вот это, дя затравки, http://xillybus.com/tutorials/pci-express-tlp-pcie-primer-tutorial-guide-1

Но мн кажется, что должна быть дока значительно лучше на твою FPGA и твой IP блок. Нету там ничего сложного реально. Единственное - если юзаешь embedded хост (не PC) лучше если дебажный комп это не то же устройство, в которое карточку вставляешь. При определенной кривизне рук можно записать немного не туда, и понять что было не так будет очень сложно. Если процессора DMA/реквестов и scatterlist не реализованы в IP блоке, советую добавить какой-нить недопроцессор туда. Ну это зависит от мироощущений, некоторые к этому относятся с яростной ненавистью, но мне проще так было делать, чем возиться с состояниями. И базовая идея, что ты заводишь регистры в конфигспейсе (или в BARx) в которых или просто адрес, длина или ссылка на дескриптор, где массив этих данных лежит в памяти, и ты по биту «давай» в регистре делаешь трансфер согласно доке на IP блок. Минус бас-мастера - он должен выполняться максимально быстро, то есть никаких ожиданий готовности и тп, только передача из подготовленного буфера в подготовленный буфер. Да, и как закончишь, генеришь прерывание.

slapin ★★★★★
()
Ответ на: комментарий от Andrey_Utkin

Смысл?

Я это понимаю для чего макрос, просто слегка запутался как сделать свою структуру для хранения специфичного для данного устройства данных, и чтобы не зацикливаться, сделал пока так. Но потом сделаю как положено.

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

Да, там эти три статьи у Xillibus по PCI-E отличные! Спасибо за эти ссылки. С помощью информации по ссылке я наконец сделал обработку чтения из устройства (ответный пакет с правильным Tag высылаю) - драйвер считывает правильные значения!

Теперь буду постигать DMA. Тот же драйвер от Xillibus http://lxr.free-electrons.com/source/drivers/char/xillybus/ весьма интересен, там видимо DMA используется и кода очень мало.

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

MRd
MRd
MRd
MRd

Вы чего меня поминаете всуе, ась?

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

Це буде 2 МБ за секунду, хлопчик. Але епоху юних не спинити, так що працюй над пдп.

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

tailgunner Извините, снова я беспокою :)

Теперь встал вопрос непосредственно с SG-DMA. Кажется я понял суть этого метода, в LDD3 это хорошо описано. В документации есть такие строки:

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
    hw_address[i] = sg_dma_address(sg);
    hw_len[i] = sg_dma_len(sg);
}
Но возникает вопрос - а что это за sglist? В документации выше он абсолютно не описан, он берется из космоса? Я предположил что так надо:
    int n_sg = 10;
    struct scatterlist sg[10];
    n_sg = dma_map_sg(&dev, sg, n_sg, DMA_FROM_DEVICE);
    for(i = 0; i < n_sg; i++)
    {
	printk(KERN_INFO "mypci: SG %d -> %d @ %d\n", i, sg_dma_address(&sg[i]), sg_dma_len(&sg[i]));
    }
    dma_unmap_sg(&dev, sg, n_sg, DMA_FROM_DEVICE);
Однако на этой строке «n_sg = dma_map_sg(&dev, sg, n_sg, DMA_FROM_DEVICE)» всё валится.

Разобрался с прерываниями, генерировать MSI в корке оказалось очень просто, повесил обработчик - генерируется с нужной частотой. Правда каждое третье прерывание отлетает USB мышка и снова реконнектится - странно что есть влияние на не связанные устройства.

irqreturn_t mypci_isr(int irq, void *data)
{
    printk(KERN_INFO "mypci: interrupt!\n");
    return IRQ_HANDLED;
}
EXPORT_SYMBOL(mypci_isr);
...
    pci_set_master(dev);
    pci_enable_msi(dev);
    devm_request_irq(&dev->dev, dev->irq, mypci_isr, 0, "mypci", NULL);
Тут я правильно сделал? Обработчики ошибок убрал для читаемости, ошибок не возникает на этих строках в процессе работы.

I-Love-Microsoft ★★★★★
() автор топика
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Пишут что надо заполнить эти структуры struct page *page; unsigned int length; unsigned int offset; перед тем как что-то выделять. А я то всё нулевое подаю, это и есть ошибка.

Допустим я заполню так: offset = i*4096; а length у каждого элемента будет 4096 (хотя лучше потом узнать размер страницы памяти в системе, что мельчить если страница будет 64К?).

Одно непонятно: struct page как заполнять? Судорожно ищу примеры в драйверах, пока не нашел.

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

Теперь встал вопрос непосредственно с SG-DMA

Но возникает вопрос - а что это за sglist? В документации выше он абсолютно не описан, он берется из космоса?

Documentation/DMA-mapping.txt

Documentation/DMA-API-HOWTO.txt

irqreturn_t mypci_isr(int irq, void *data)
{
printk(KERN_INFO «mypci: interrupt!\n»);
return IRQ_HANDLED;
}

В настоящих драйверах ISR обычно сбрасывает причину прерывания на устройстве.

devm_request_irq(&dev->dev, dev->irq, mypci_isr, 0, «mypci», NULL);

Для учебного примера, наверное, подойдет.

Тут я правильно сделал?

Сначала устанавливается обработчик прерывания, потом разрешается их генерация.

struct page как заполнять?

virt_to_page на адресе, возвращенном k{m,z}alloc, или get_user_pages, если у тебя DMA в юзерспейный буфер (обычно это не нужно). Это есть в LDD3.

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

Для начала самый простой вариант с одним буфером:

int dma_size = 4 * 1024;
dma_addr_t dma_device;
void *dma_driver;

void test_dma(struct pci_dev *dev)
{
	int ret = 0, i;
	printk(KERN_INFO "----- test_dma\n");
	//ret = dma_set_mask(dev, 0xFFFF);
	//printk(KERN_INFO "mask= %d\n", ret);
	dma_driver = dma_alloc_coherent(dev, dma_size, &dma_device, GFP_DMA);
	printk(KERN_INFO "dma= %d %d\n", dma_driver, dma_device);
	//dma_free_coherent(dev, dma_size, dma_driver, dma_device);
}
При попытке вызова всегда получаю «BUG: unable to handle kernel paging request at 0000000067e9cec0». Пробовал разные размеры, дошел до 4К - тоже не работает. Пробовал GFP_KERNEL, теперь GFP_DMA. Одна строка, очевидные параметры, вообще не удается. Может ли это быть из за устройства? Полагаю что на таких начальных этапах система не пытается работать с устройством, а лишь должна выделить что-то внутри себя. Но всё же... вдруг оно пытается подергать память в процессе dma_alloc_coherent и потому валится, ведь я не отвечаю устройством.

Andrey_Utkin Пытаюсь разобраться. Пока безуспешно, самого простого понятного примера пока не нашел. Но обнаружил что перед dma_map_sg используется еще

void test_sgdma(struct pci_dev *dev)
{
	int ret = 0, i, count = 0;
	struct sg_table table;
	printk(KERN_INFO "----- test_sgdma\n");
	ret = sg_alloc_table(&table, 10, GFP_KERNEL);
	count = dma_map_sg(dev, table.sgl, table.nents, DMA_FROM_DEVICE);
	printk(KERN_INFO "alloc: %d -> %d/%d = %d\n", ret, table.nents, table.orig_nents, count);
	for(i = 0; i < count; i++)
	{
		printk(KERN_INFO "	SG %d -> %d / %d\n", i, sg_dma_address(&table.sgl[i]), sg_dma_len(&table.sgl[i]));
	}
}
Тут не валится, просто всё нули...

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

Может ли это быть из за устройства?

Из-за того, что конструируемое тобой устройство что-то испортило? Да. Но, если у тебя валится именно вызов dma_alloc_coherent - покажи стектрейс.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner
[  905.475587] mypci: module license 'unspecified' taints kernel.
[  905.475596] Disabling lock debugging due to kernel taint
[  905.475644] mypci: module verification failed: signature and/or required key missing - tainting kernel
[  905.476635] mypci: init
[  905.476788] ----- test_dma
[  905.476803] BUG: unable to handle kernel paging request at 0000000067e9cec0
[  905.476840] IP: [<ffffffff8101c36e>] dma_generic_alloc_coherent+0x6e/0x140
[  905.476865] PGD 0
[  905.476876] Oops: 0000 [#1] SMP
[  905.476891] Modules linked in: mypci(POE+) bnep rfcomm bluetooth nouveau pl2303 input_leds usbserial snd_hda_codec_hdmi hid_generic usbhid hid mxm_wmi wmi gpio_ich video snd_hda_intel ttm drm_kms_helper snd_hda_codec snd_intel8x0 drm snd_hda_core snd_ac97_codec snd_hwdep ac97_bus snd_mpu401_uart snd_seq_midi snd_seq_midi_event lpc_ich snd_rawmidi i2c_algo_bit ns558 snd_seq snd_pcm snd_seq_device serio_raw snd_timer snd shpchp soundcore gameport mac_hid 8250_fintek parport_pc ppdev lp parport 8139too 8139cp floppy psmouse pata_acpi mii
[  905.477112] CPU: 1 PID: 1854 Comm: insmod Tainted: P           OE   4.2.0-36-generic #42~14.04.1-Ubuntu
[  905.477130] Hardware name: ECS 945P-A/945P-A, BIOS 080012  10/18/2005
[  905.477143] task: ffff88001f0cd780 ti: ffff88001ab28000 task.ti: ffff88001ab28000
[  905.477158] RIP: 0010:[<ffffffff8101c36e>]  [<ffffffff8101c36e>] dma_generic_alloc_coherent+0x6e/0x140
[  905.477181] RSP: 0018:ffff88001ab2ba68  EFLAGS: 00010246
[  905.477192] RAX: 0000000000000000 RBX: 00000000ffffffff RCX: 0000000000000004
[  905.477207] RDX: 000000001cc2e1f0 RSI: 0000000000000000 RDI: ffff88001cc2e000
[  905.477223] RBP: ffff88001ab2baa8 R08: 0000000000000000 R09: ffffffff8101c300
[  905.477238] R10: 0000000000000000 R11: 0000000000000359 R12: 0000000000000000
[  905.477253] R13: ffff88001cc2e000 R14: 0000000000001000 R15: 0000000000000004
[  905.477269] FS:  00007fb29bd4e740(0000) GS:ffff88001fd00000(0000) knlGS:0000000000000000
[  905.477286] CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
[  905.477299] CR2: 0000000067e9cec0 CR3: 00000000114ee000 CR4: 00000000000006e0
[  905.477315] Stack:
[  905.477323]  ffffffffc026b4c8 ffffffff00000000 ffff88001ab2bac8 ffff88001cc2e000
[  905.477346]  ffff88001cc2e000 ffffffffc026b000 ffff88001cc2e098 ffff88001ab2bec8
[  905.477367]  ffff88001ab2bab8 ffffffff8101c16c ffff88001ab2bad8 ffffffffc026919d
[  905.477390] Call Trace:
[  905.477403]  [<ffffffff8101c16c>] dma_alloc_attrs+0x5c/0x90
[  905.477419]  [<ffffffffc026919d>] test_dma+0x3d/0x70 [mypci]
[  905.477993]  [<ffffffffc02691f2>] device_probe+0x22/0x39 [mypci]
[  905.478471]  [<ffffffff813f8925>] local_pci_probe+0x45/0xa0
[  905.478905]  [<ffffffff813f9ad4>] ? pci_match_device+0xf4/0x120
[  905.479616]  [<ffffffff813f9c0a>] pci_device_probe+0xca/0x110
[  905.480018]  [<ffffffff814fb2a3>] driver_probe_device+0x213/0x460
[  905.480018]  [<ffffffff814fb580>] __driver_attach+0x90/0xa0
[  905.480018]  [<ffffffff814fb4f0>] ? driver_probe_device+0x460/0x460
[  905.480018]  [<ffffffff814f90ad>] bus_for_each_dev+0x5d/0x90
[  905.480018]  [<ffffffff814fac1e>] driver_attach+0x1e/0x20
[  905.480018]  [<ffffffff814fa770>] bus_add_driver+0x1d0/0x290
[  905.484025]  [<ffffffffc000c000>] ? 0xffffffffc000c000
[  905.484025]  [<ffffffff814fbfb0>] driver_register+0x60/0xe0
[  905.484025]  [<ffffffff813f829c>] __pci_register_driver+0x4c/0x50
[  905.484025]  [<ffffffffc000c02c>] init_module_mypci+0x2c/0x1000 [mypci]
[  905.484025]  [<ffffffff8100213d>] do_one_initcall+0xcd/0x1f0
[  905.484025]  [<ffffffff817bff1c>] ? _cond_resched+0x1c/0x30
[  905.484025]  [<ffffffff811d0e04>] ? kmem_cache_alloc_trace+0x1b4/0x240
[  905.484025]  [<ffffffff817b628f>] ? do_init_module+0x28/0x1ea
[  905.484025]  [<ffffffff817b62c8>] do_init_module+0x61/0x1ea
[  905.484025]  [<ffffffff810fdfc1>] load_module+0x1401/0x1b40
[  905.484025]  [<ffffffff810fa760>] ? __symbol_put+0x40/0x40
[  905.484025]  [<ffffffff810fe8e0>] SyS_finit_module+0x80/0xb0
[  905.484025]  [<ffffffff817c35b2>] entry_SYSCALL_64_fastpath+0x16/0x75
[  905.484025] Code: 7f 41 bc ff ff ff ff 41 89 cf 48 c1 e8 0c 4c 0f bd e0 41 83 c4 01 44 89 65 c8 e9 83 00 00 00 48 63 d0 44 89 f8 44 89 e6 c1 e8 12 <48> 8b 14 d5 40 bf d2 81 44 89 ff 83 e0 01 48 8d 0c c5 00 00 00
[  905.484025] RIP  [<ffffffff8101c36e>] dma_generic_alloc_coherent+0x6e/0x140
[  905.484025]  RSP <ffff88001ab2ba68>
[  905.484025] CR2: 0000000067e9cec0
[  905.501237] ---[ end trace 8ca2dc1a9351ddc1 ]---
I-Love-Microsoft ★★★★★
() автор топика
Ответ на: комментарий от I-Love-Microsoft
dma_alloc_coherent(struct device *dev, size_t size,
			     dma_addr_t *dma_handle, gfp_t flag)

Как ты умудрился передать туда pci_dev? Компилятор должен был тебя предупредить.

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

dev->dev

Как будто конкурс по запутанному коду на Си.

gag ★★★★★
()

Кстати, а планируешь написать обо всём этом заметку/статью на хабре или необязательно на нём? Уверен, народ был бы благодарен. Arduino, Raspi - это очень хорошо, но вот ещё бы FPGA сделать бы доступнее и популярнее.

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

О, хороший драйвер, всё очень аккуратно. Обязательно изучу, спасибо за эту ссылку.

Статью на хабре?.. В принципе, может она была бы полезной. Но для начала, нужно действительно написать красивый аккуратный драйвер, всё по канонам, чтоб ошибки все обрабатывала, данные правильно хранила и многое другое.

Мне пока рано писать какую-либо статью, вряд ли то что я делаю на стороне ПЛИС является правильным и действительно будет работать везде (нужно обрабатывать не только 32-битные но и 64-битные обращения) и многие другие ньюансы.

Может, когда сделаю нормальное устройство (товарищ поможет развести плату, он СВЧ платы умеет и соображает) и оно заработает, то только тогда уже буду проповедовать учение о писиае :)

А пока я не совсем понимаю как были настроены пины интерфейса кор-генератором, хотя допускаю что из ПЛИС выведено что-то самое первое что берется по дефолту генератором, и вообще раз корка для PCI-E то всё под нее настраивается само соответственно...

Вот когда будет что-то больше чем x1 (x4 например) на самодельной плате - тогда наверное статью напишу. Хотя видно что у Lattice, Xilinx и Ti XIO1100 похожие принципы работы с PCI-E, у Altera со стороны user-логики может быть сложнее чуток наверное.

I-Love-Microsoft ★★★★★
() автор топика

Для тех кто следит за темой - удалось осуществить работу при помощи DMA :)

Выделяю при помощи dma_alloc_coherent, получившийся адрес из параметра сую в устройство через MMIO, когда устройство получает этот адрес то я шлю MWr на этот адрес длиннющий пакет и оно потом успешно читается в драйвере :)

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

заставить это хозяйство басмастерить

tailgunner Что ж, удалось заставить устройство писать 1404 байта в выделенный буфер при помощи 13 пакетов по 27 DW. Но возникли странности:

Я задаю payload length как 28 DW (0x1C), но оно упорно шлет лишь 27 DW, это я вижу по одиночному пакету, буфер заполняется лишь в 27-ми DW, остальное не тронуто. Любые попытки увеличить поле length или уменьшить или увеличить число данных на один DW приводит к отбрасыванию пакета вообще.

Лишь 13-ть пакетов (27 + 3 DW) можно отправить последовательно, делая паузу в один такт 125 МГц паузу между ними. Для 13 это срабатывает, большее число не лезет. При этом как только отсылается серия из уже 13 пакетов, lspci -v показывает вот что:

[  124.399217] mypci: max= 351 -> 13
[  124.436578] mypci: device removed
02:00.0 Non-VGA unclassified device: Adnaco Technology Inc. Device bbbb (rev ff) (prog-if ff)
        !!! Unknown header type 7f
Я подозреваю что есть некоторый лимит 1440 байт или просто так совпало, после которого надо дать буферам отправиться и прочиститься, получив Ack. Буду выяснять, из корки уточнить ее готовность принимать новые данные.

I-Love-Microsoft ★★★★★
() автор топика

Для тех кто следит за темой, кому интересны затыки, которые могут случиться в процессе разработки PCI драйвера:

Разобравшись как корка сигнализирует что она готова кушать данные, смог разогнаться вплотную до границы 4096 байт. И тут пришла мысль, что если есть фундаментальный запрет на передачу байтов в одном пакете, которые ложатся между разными страницами памяти?.. Очень очень похоже что так, не учел этого ранее.

I-Love-Microsoft ★★★★★
() автор топика

Для примера, сделал DMA-буфер размером 256К (64 страницы по 4К). Когда сделал пакеты по 64 байта, что соответственно позволило не пересекать границы страниц памяти - стало успешно без сбоев передавать полный объем данных.

Проблема что последний DW не передавался тоже решилась. Невнимательно читал http://xillybus.com/tutorials/pci-express-tlp-pcie-primer-tutorial-guide-1 Там написано «The Last BE field must be zero when Length is unity, since the first DW and the last is the same one», но в примере то один DW передавался, только для этого случая было актуально LastBE=0, а когда так много данных в пакете - нужно 0xF.

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

Вижу странность вокруг функции request_mem_region - где-то в дровах проверяют что если эта функция вернула меньше нуля - значит ошибка (тут), где-то если не равна нулю - ошибка (тут), а где-то если равна нулю то ошибка (тут). Где-то вообще не смотрят возвращаемое значение - просто вызывают по несколько раз с разными параметрами, например.

Помогите, пожалуйста, понять почему одна функция в четырех вариантах используется...

Я пока сделал так:

	if(request_mem_region(pci_resource_start(dev, 0), pci_resource_len(dev, 0), "mypci") < 0)
	{
		ret = -EBUSY;
		printk(KERN_ERR "mypci: ERROR: mem region failed\n");
		goto failed_mem_region;
	}

Andrey_Utkin tailgunner

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

Вижу странность вокруг функции request_mem_region

Зачем она тебе вообще? Лично я не припомню, чтобы ее использовал.

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

Встретив неоднозначность, отбрось искушение угадать.

В случае nand/au1550, похоже, явная ошибка, в случае ralink (проверка на отрицательность) - плохой стиль. Можно уточнить у разрабов и послать патчики.

Я бы рекомендовал следующую, более простую как для написания, так и чтения форму, которая делает то же самое:

        err = pci_request_regions(pci_dev, dev->name);
        if (err) {
                dev_err(&dev->pci->dev, "Cannot request regions for MMIO\n");
                goto disable_pci;
        }

С последующим

        dev->mmio = pci_ioremap_bar(pci_dev, 0);
        if (!dev->mmio) {
                err = -EIO;
                dev_err(&dev->pci->dev, "can't ioremap() MMIO memory\n");
                goto release_mmio;
        }

(у тебя, наверное, что-то подобное уже есть в коде).

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

Это просто проверка что ресурс не занят. на самом деле этот код мне почему-то кажется архаичным, кажется что была более специальная функция, которая сразу и request_mem_region() и ioremap() в одном флаконе. сейчас лень смотреть.

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

Это просто проверка что ресурс не занят.

Мне кажется, это проверка из времен ISA или с архитектур, где регионы памяти не привязаны к устройствам черз OF, dts или еще что-то.

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

Не, обычно ресурсы всё равно проверяются, но это обычно проще. Можно тут основную идею почитать http://haifux.org/lectures/323/haifux-devres.pdf И пользовать надо эти методы заместо прямой работы с ресурсами.

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

Полезная ссылка, но тема именно request_mem_region там не раскрыта. Более того, в вызове request_mem_region не указывается устройство-владелец, что противоречит изложенному по ссылке.

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

Ну поэтому я и говорю, что код архаичный.

slapin ★★★★★
()
7 октября 2016 г.
Ответ на: комментарий от I-Love-Microsoft

Добрый вечер! Прошу прощения что кастую, возникла проблема, есть догадки. Сделал чтобы устройство отдавало 256К (=256 * 1024) байт в виде 64К 32-битных слов, каждое слово равно предыдущему значению плюс единица.

Когда драйвер только загружается, то первый блок 64К 32-битных слов может быть поврежден в 33 66 99 или 115-ти позициях, т.е. тупо 66 32-битных слов не являются инкрементом предыдущего. Перед тем как шлю пакет в ПЛИС чтобы оно начало накладывать данные - обнуляю весь буфер нулями, для надежности. Выгружаю и снова загружаю (insmod) - опять первый запрос будет с пробитостями.

Если драйвер уже был загружен перед запуском тестовой программы - все 64К слов всегда правильные (по ОДНОМУ блоку), и сколько бы я не запрашивал - счетчик корректно инкрементируется между этими запросами.

А вот как только тестовая программа запрашивает два блока по 64 DW - чудо: первый блок весь полностью корректен без ошибок, а в последующем может быть 33 66 99 или 115 ошибок, прям магические характерные числа. Может быть одна или несколько последовательных цепочек битых данных в рамках 64К DW.

У меня только догадка: может оно тупо не успевает записать данные? Может после отсылки из ПЛИС последнего TLP-пакета в блоке надо подождать и только после этого выкидывать прерывание? Если да, то сколько надо ждать тактов, должен же быть какой-то признак, независимый от скорости системы? Должно пройти время, пока данные упадут в память и запишутся, после чего их можно читать. Может даже пакеты не успевают долететь с данными.

tailgunner Andrey_Utkin slapin ebantrop

I-Love-Microsoft ★★★★★
() автор топика
Последнее исправление: I-Love-Microsoft (всего исправлений: 1)
Ответ на: комментарий от I-Love-Microsoft

Телепатия не самая моя сильная сторона, но попробую. В PCIe прерывание это просто TLP пакет, поэтому если он отсылается после пачки пакетов с данными и ты его видишь, значит все предыдущие должны быть обработанны. Как ты делаешь проверку? Поинтер volatile? Какие там данные вместо счётчика: нули или мусор какой-то? Если нули, ты уверен что счётчик адреса работает правильно? В том смысле что все пакеты падают в твой буфер?

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

Решил инициализировать перед запросом блока не нулями, а например AAAAAAAA... и увидел что там где ошибки - всегда эти А. Ну ясно, всё теперь понятно - не успевает упасть в память, не долетают пакеты. А ведь я полагал точно так же, что раз TLP пакет с прерыванием высылается после последнего пакета с данными то всё должно быть окей.

Сбойные данные возникают последовательной пачкой, размер которой не кратен размеру TLP-пакетов (они по 64 байта payload, или по 16 DW). Сбои наблюдаются в в районе первой тысячи DW (всего 65 тысяч).

Я полагаю, что я должен добавить некоторую задержку после последнего пакета с данными, и лишь после этого высылать MSI (прерывание). Но какой же размер задержки? Как узнать когда хост всё прожевал? Не люблю костыли вроде задержек, времен i80386.

Поинтер volatile?

dma_addr_t dma_device;
void *dma_driver; // работаю с этим
...
unsigned int *dw;
dw = (unsigned int*) device->dma_driver;

Данные смотрю в device->dma_driver. Как volatile поможет?

Проверяю так:

static ssize_t mypci_read(struct file *f, char *buffer, size_t size, loff_t *offset)
{
        int i = 0, err = 0;
        unsigned int *dw;
        unsigned int prev = 0;

        int minor = iminor(f->f_inode), ret, sz = 0;
        struct mypci_device *device = NULL;

        if(minor >= mypci_used) return 0;
        if(f->f_flags & O_NONBLOCK) return 0;
        if(size == 0) return 0;

        device = &devices[minor];
        device->dma_finished = 0;
        dw = (unsigned int*) device->dma_driver;
        for(i = 0; i < (MYPCI_DMA_BLOCK / 4); i++) dw[i] = 0xAAAAAAAA;
        writel(device->dma_device & 0xFFFFFFFF, device->mmio);
        if(wait_event_interruptible(device->wait, device->dma_finished != 0)) return 0;

#if 1 // check counter data
        prev = dw[0];
        for(i = 1; i < (MYPCI_DMA_BLOCK / 4); i++)
        {
                if(dw[i] != (prev + 1))
                {
                        printk(KERN_ERR "mypci: 0x%08X -> %d @ %d\n", dw[i], err, i);
                        err++;
                }
                prev = dw[i];
        }
        if(err > 0) printk(KERN_ERR "mypci: read err %d\n", err);
        else printk(KERN_ERR "mypci: read no errors\n");
        printk(KERN_ERR "mypci: 0x%08X / 0x%08X\n", dw[0], dw[(MYPCI_DMA_BLOCK / 4) - 1]);
#endif

Так читаются 8 блоков: http://paste.org.ru/?7r2dk5

I-Love-Microsoft ★★★★★
() автор топика
Последнее исправление: I-Love-Microsoft (всего исправлений: 3)
Ответ на: комментарий от ebantrop

Сбойные данные возникают последовательной пачкой, размер которой не кратен размеру TLP-пакетов (они по 64 байта payload, или по 16 DW). Сбои наблюдаются в в районе первой тысячи DW (всего 65 тысяч).

Присмотрелся - всё же кратен. Как правило это ДВА смежных, иногда больше, пакета по 16 DW, итого 32 DW битых. Их начало в блоке выровнено по 16 DW. Раньше я не видел этой закономерности, ибо не очищал буфер, и там оставались старые данные, которые меня удивляли.

Буду думать какую задержку ставить между последним пакетом данных и MSI. Не хотелось бы колдовства.

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

Я полагаю, что я должен добавить некоторую задержку после последнего пакета с данными, и лишь после этого высылать MSI

Тебе нужна какая-то обратная связь с хостом. Задержка - это типичный говнокод.

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

Тебе нужна какая-то обратная связь с хостом. Задержка - это типичный говнокод.

Эта обратная связь есть уже, только что должно быть критерием «всё дошло» на стороне хоста? Как только я это (не знаю что) проверю - сообщу устройству через MMIO.

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

Я конечно понимаю что 15 минут это не опоздала, но у тебя между первым принтом и принтом с ошибкой проходят десятки миллисекунд. TLP уезжает за десятки-сотни нан. Спрашивается где они шлялись всю ночь?

Кроме того что слипы — говнокод, как правильно заметил tailgunner, сколько ты собираешься ждать сверх того что ты уже ждёшь?

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

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

На время можно не обращать внимания - сначала я принимаю пакет, а затем смотрю его, параллельно вывод в ttyS0. Я ничего не жду, просто в обработчике прерывания я пробуждаю wait_event_interruptible и проверяю счетчик в данных.

Мне не понятно почему возникает пропадание, и что сделать чтобы хост был уверен что все данные пришли.

I-Love-Microsoft ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.