Я работаю с высокоскоростной последовательной картой для высокоскоростной передачи данных из внешнего источника в коробку Linux с картой PCIe. Карта PCIe поставлялась с некоторыми сторонними драйверами, которые используют dma_alloc_coherent для выделения буферов dma для получения данных. Однако из-за ограничений Linux этот подход ограничивает передачу данных до 4 МБ. Я читал и пробовал несколько методов для выделения большого буфера DMA и не смог заставить его работать.
Эта система имеет 32 ГБ памяти и работает под управлением Red Hat с версией ядра 3.10, и я хотел бы выделить 4 ГБ этой памяти для непрерывного прямого доступа к памяти. Я знаю, что предпочтительным методом является разброс / сбор, но в моей ситуации это невозможно, поскольку есть аппаратный чип, который переводит последовательный протокол в DMA вне моего контроля, где единственное, что я могу контролировать, — это добавить смещение к входящие адреса (т. е. нулевой адрес, видимый из внешней системы, может быть сопоставлен с адресом 0x700000000 на локальной шине).
Поскольку это одноразовая лабораторная машина, я думаю, что самый быстрый / самый простой подход — использовать параметр конфигурации загрузки mem = 28GB. У меня это работает нормально, но следующий шаг для доступа к этой памяти из виртуального пространства, где у меня возникают проблемы. Вот мой код, сжатый для соответствующих компонентов:
В модуле ядра:
size_t len = 0x100000000ULL; // 4GB
size_t phys = 0x700000000ULL; // 28GB
size_t virt = ioremap_nocache( phys, len ); // address not usable via direct reference
size_t bus = (size_t)virt_to_bus( (void*)virt ); // this should be the same as phys for x86-64, shouldn't it?
// OLD WAY
/*size_t len = 0x400000; // 4MB
size_t bus;
size_t virt = dma_alloc_coherent( devHandle, len, &bus, GFP_ATOMIC );
size_t phys = (size_t)virt_to_phys( (void*)virt );*/
В приложении:
// Attempt to make a usable virtual pointer
u32 pSize = sysconf(_SC_PAGESIZE);
void* mapAddr = mmap(0, len+(phys%pSize), PROT_READ|PROT_WRITE, MAP_SHARED, devHandle, phys-(phys%pSize));
virt = (size_t)mapAddr + (phys%pSize);
// do DMA to 0x700000000 bus address
printf("Value %x\n", *((u32*)virt)); // this is returning zero
Еще одна интересная вещь заключается в том, что, прежде чем делать все это, физический адрес, возвращаемый из dma_alloc_coherent, больше, чем объем оперативной памяти в системе (0x83d000000). Я думал, что в x86 ОЗУ всегда будет иметь самые низкие адреса, и поэтому я ожидал бы адрес менее 32 ГБ.
Любая помощь будет оценена.
Вместо ограничения объема системной памяти через mem
попробуйте использовать CMA: https://lwn.net/Articles/486301/
Использование аргумента командной строки ядра CMA позволяет зарезервировать определенный объем памяти для операций DMA, который гарантированно будет непрерывным. Ядро позволит не-DMA-процессам получать доступ к этой памяти, но как только операции DMA потребуется эта память, не-DMA-процессы будут исключены. Итак, я бы посоветовал не менять mem
параметр, но добавление cma=4G
на ваш cmdline. dma_alloc_coherent
должен автоматически вытащить из этого зарезервированного пространства, но вы можете включить отладку CMA в конфигурации ядра, чтобы убедиться.
Других решений пока нет …