LINUX.ORG.RU

ARMv7, DMA и загадка

 , ,


0

3

Доброго времени суток!

Работаю над драйвером под ядро 2.6.37, платформа с чипом TI DM3730, который с ядром Cortex A8, которое на основе ARMv7.

Драйвер у меня позволяет чипу общаться с FPGA по GPMC, и всё это дело вполне сносно работает по DMA, когда я выделяю память под DMA буфер при помощи dma_alloc_coherent().

Но задача смотрит немного дальше: нужно не просто получить данные, а отправить их на DSP ядро, забрать оттуда и затем вручить радостному юзеру. Логично при этом использовать идеологию zero-copy, и вот тут-то возникают нюансы.

Во-первых, памяти нужно много, т.е. больше, чем даёт мне dma_alloc_coherent (1Мб). Во-вторых, в эту память должно уметь лазить DSP ядро. Производитель чипа предоставил модуль, который умеет выделять большие непрерывные объёмы памяти, называется CMEM. В более поздних ядрах появится «официальный» CMA, но тут что есть, тому и рады. Смысл в том, что в параметрах загрузчика указывается, что оперативной памяти меньше, чем есть на самом деле, и адрес зарезервированного остатка передаётся модулю CMEM. Тот выделяет в этой области нужные буферы, указатели на которые передаются и в DSP, и моему драйверу для DMA.

Мой драйвер берёт нужный буфер и последовательно заполняет его кусочками, применяя к ним dma_map_single(NULL,virt_address,size,DMA_FROM_DEVICE) перед тем, как сказать omap_start_dma и соответственно dma_unmap_single(NULL,dma_address,size,DMA_FROM_DEVICE) после вызова omap_stop_dma.

Судя по документации, после вызова unmap юзер волен забирать данные на все четыре стороны. Вот как бы оно так и есть, но не совсем: запускаю юзерскую программу, она выделяет память через CMEM, открывает драйвер, тот сбрасывает счётчики и указатели на начало, и закачивает пачку данных с FPGA. Программа сохраняет данные в NAND-плеш, освобождает память и уходит. Смотрим в файл - данные на месте. Запускаем ещё раз, порядок тот же, только вместо данных все нули. ВСЕ, не частично. И никакого мусора. Запускаем в третий раз - данные снова на месте. Ещё раз - нули. И далее они так чередуются. Я перекрестился, повтыкал flush_cache_all() тут и там, но не помогло. rmmod-insmod драйвера на порядок чередования не влияет.

Что за дела - решительно не понимаю. Какой-то прикол с CMEM? Но буферы-то физически одни и те же... Специально проверил все адреса и указатели в случае нормы и в случае нулей - всё одинаково. Мозг сломался, нужна помощь!

Вообще-то flush нужен перед передачей данных на внешнее устройство, во время/после приема это может быть ударом (иногда частичным) по полученным данным. Перед приемом надо бы invalidate - дабы в кэше старого не осталось.

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

Возможно, хотя трудно этим объяснить текущее поведение...

Тем не менее, если у меня «приёмом» в данном случае является вызов fwrite из userspace, то как именно перед ним сделать предполагаемый invalidate кэша?

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

А если вы имели в виду делать invalidate перед каждым новым приёмом DMA, то invalidate_kernel_vmap_range() тут, к сожалению, погоды не делает.

eternal_ego ()

Непонятно - у вас есть драйвер для FPGA, DSP и код в юзерспейс которые все используют cmem, но cmem это аллокатор ядра, как программа в юзерспейс узнает какой из буферов готов для сохранения в nand ? из готовых механизмов есть v4l2 с mmap/usrptr/read и очереди занятых/свободных буферов - а у вас как построено взаимодействие драйвера и кода в юзерспейс ?

__Мой драйвер берёт нужный буфер__ и последовательно заполняет его кусочками, применяя к ним dma_map_single(NULL,virt_address,size,DMA_FROM_DEVICE) перед тем, как сказать omap_start_dma и соответственно dma_unmap_single(NULL,dma_address,size,DMA_FROM_DEVICE) после вызова omap_stop_dma.

запускаю юзерскую программу, __она выделяет память через CMEM__, открывает драйвер, тот сбрасывает счётчики и указатели на начало, и закачивает пачку данных с FPGA.

или вы пока что с одним буфером экспериментируете ?

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

Механизм синхронизации свой. Буферов четыре (в параллель работают). Драйвер FPGA укладывает данные в буферы при помощи цепного DMA (настроено, чтобы автоматом раскладывалось по четырём буферам, и это работает - проверено). После укладки очередной пачки инкрементируются атомарные счётчики позиции записи и текущей длины. Юзер опрашивает драйвер, когда можно забирать данные (т.е. через ioctl получает доступ к тем самым атомарным счётчикам), и если пора, то берёт и пишет. Опять же, в случае с dma_alloc_coherent это работает всегда, а с упомянутым dma_map_single - странным образом через раз.

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

Наверное нужно на всякий случай отдельно обратить внимание, что не просто четыре больших буфера, а каждый из них - циклический. Т.е. DMA пробегает по ним всем сразу и заполняет в параллель. Но при этом пользователь может решать, какое количество буферов (будем называть их «каналами») активны. Если канал активен, DMA для него инкрементирует счётчики, если нет - топчется на месте. Соответственно, не обязательно делать активными все каналы, поэтому для ловли багов я сначала гоняю один.

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

Механизм синхронизации свой.
Опять же, в случае с dma_alloc_coherent это работает всегда, а с упомянутым dma_map_single - странным образом через раз.

Память в юзерспейс я так понимаю через mmap отображаете в своем драйвере ? dma_map_single если и синхронизирует кеш то для адресного пространства ядра, в юзерспейс свое адресное простанство хоть и общая таблица страниц - один и тот же участок физической памяти прописан дважды, так что думаю вам надо в этом направлении смотреть.

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

Через mmap я в юзерском пространстве получал адрес DMA-буфера, когда выделял тот через dma_alloc_coherent(). Там действительно есть нюанс с кэшем, там перед чтением из буфера приходилось вызывать flush_cache_all().

Здесь же ситуация иная: память выделяется модулем CMEM, поэтому и юзер, и драйвер получают свои указатели сразу. Т.е. юзер - вызвав CMEM_alloc() и передав драйверу CMEM_getPhys() от своего адреса.

Что сделать с кэшем со стороны юзера во время чтения, я ничего нового не придумал. Поэтому покопал в другом месте, и обнаружил, что если выделять память через CMEM с параметром CMEM_HEAP вместо CMEM_POOL, то странное поведение исчезает, и данные всегда в норме. При этом физические адреса в памяти одни и те же. Похоже, какая-то фишка CMEM.

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