Драйвер Linux PCIe DMA (Xilinx XDMA)

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

во-первых, есть два основных режимы драйвера, AXI-Memory Mapped (AXI-MM) и AXI-Streaming (AXI-ST). Для моего конкретного приложения мне требуется AXI-ST, так как данные будут непрерывно поступать с устройства.

драйвер написан, чтобы воспользоваться преимуществами списков scatter-gather. В режиме AXI-MM это работает, потому что считывания являются довольно случайными событиями (т. е. нет потока данных из устройства, вместо этого приложение userspace просто запрашивает данные, когда это необходимо). Таким образом, передача DMA построена вверх, данные передаются, а затем передача сносится. Это комбинация get_user_pages(), pci_map_sg() и pci_unmap_sg().

для AXI-ST все становится странным, и исходный код далек от ортодоксального. Драйвер выделяет круговой буфер, в который должны непрерывно поступать данные. Этот буфер обычно имеет размер несколько Большой (мой установлен порядка 32 МБ), так как вы хотите иметь возможность обрабатывать переходные события, когда приложение userspace забыло о драйвер, а затем может позже работать с входящими данными.

вот где все становится ясно... круговой буфер выделяется с помощью vmalloc32() и страницы из этого распределения отображаются так же, как буфер пространства пользователей находится в режиме AXI-MM (т. е. с помощью pci_map_sg() интерфейс). В результате, поскольку круговой буфер разделяется между устройством и процессором, каждый read() вызов требует, чтобы я позвонила pci_dma_sync_sg_for_cpu() и pci_dma_sync_sg_for_device(), что абсолютно разрушает мою производительность (я не могу идти в ногу с помощью устройства!), так как это работает на весь буфер. Забавно, что Xilinx никогда не включал эти вызовы синхронизации в свой код, поэтому я сначала знал, что у меня была проблема, когда я редактировал их тестовый скрипт, чтобы попытаться более одной передачи DMA перед выходом, и результирующий буфер данных был поврежден.

в результате, мне интересно, как я могу это исправить. Я рассмотрел возможность перезаписи кода для создания собственного буфера, выделенного с помощью pci_alloc_consistent()/dma_alloc_coherent(), но это легче сказать, чем сделать. А именно, код архитектура предполагает использование списков scatter-gather везде (между списком scatter-gather и дескрипторами памяти, которые понимает FPGA, появляется странное, проприетарное сопоставление).

есть ли другие вызовы API, о которых я должен знать? Могу ли я использовать" одиночные " варианты (т. е. pci dma_sync_single_for_cpu()) через какой-то механизм перевода не синхронизировать весь буфер? В качестве альтернативы, возможно, есть какая-то функция, которая может сделать круговой буфер, выделенный с vmalloc() связный?

3 ответов


хорошо, я понял.

в принципе, мои предположения и / или понимание документации ядра относительно API синхронизации были совершенно неправильными. А именно, я ошибся в двух ключевых предположениях:

  1. если буфер никогда не записывается процессором, вам не нужно синхронизировать устройство. Удаление этого вызова удвоило мой read() пропускная способность.
  2. вам не нужно синхронизировать весь scatterlist. Вместо этого, теперь в моем read() звонок, я выяснить, какие страницы будут затронуты copy_to_user() вызов (т. е. то, что будет скопировано из кругового буфера) и синхронизировать только те страницы, которые меня волнуют. В принципе, я могу назвать что-то вроде pci_dma_sync_sg_for_cpu(lro->pci_dev, &transfer->sgm->sgl[sgl_index], pages_to_sync, DMA_FROM_DEVICE) здесь sgl_index там, где я понял, что копия начнется и pages_to_sync - это то, насколько велики данные в количестве страниц.

с вышеуказанными двумя изменениями мой код теперь соответствует моим требованиям к пропускной способности.


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

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

Если вы используете ПЛИС Zynq, вы можете подключить Двигатель DMA к порту ACP так, что доступ к памяти FPGA будет когерентен. Кроме того, можно сопоставить области памяти как не кэшированные/буферизованные, а не кэшированные.

наконец, в моих приложениях FPGA я сопоставляю управляющие регистры и буферы в процесс приложения и только реализую mmap() и poll() в драйвере, чтобы дать приложениям больше гибкости в том, как они делают DMA. Я обычно реализую свои собственные двигатели DMA.


Пит, я оригинальный разработчик кода драйвера (до того, как X XMDA пришел на место).

ringbuffer всегда был неортодоксальной вещью и действительно предназначался для кеш-когерентных систем и отключен по умолчанию. Первоначальная цель состояла в том, чтобы избавиться от задержки запуска DMA (re); даже при полной асинхронной поддержке ввода-вывода (даже при цепочке дескрипторов с нулевой задержкой в некоторых случаях) у нас были случаи, когда это не могло быть гарантировано и где истинное оборудование требовался режим ringbuffer / cyclic / loop.

в Linux нет эквивалента API ringbuffer, поэтому он немного открыт.

Я счастлив переосмыслить дизайн IP / драйвера.

можете ли вы поделиться своим исправлением?