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

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

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

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

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

Вот где дела идут не так, как надо... круговой буфер выделяется с помощью 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(), но это легче сказать, чем сделать. А именно, код спроектирован так, чтобы предполагать, что везде используются списки разброса (кажется, что между списком разброса и дескрипторами памяти, которые понимает ПЛИС, существует странное, проприетарное отображение.

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

3 ответа

Решение

Хорошо, я понял это.

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

  1. Если процессор никогда не записывает данные в буфер, вам не нужно выполнять синхронизацию для устройства. Удаление этого звонка удвоило мой read() пропускная способность.
  2. Вам не нужно синхронизировать весь список рассеяния. Вместо этого, теперь в моем 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 FPGA, вы можете подключить механизм DMA к порту ACP, чтобы доступ к памяти FPGA был согласованным. Кроме того, вы можете отобразить области памяти как некэшированные / буферизированные вместо кэшированных.

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

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

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

В Linux нет эквивалента API кольцевого буфера, поэтому он немного открыт.

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

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

Другие вопросы по тегам