Предупреждение: я не специалист по linux ядру.
Книга LDD (которая может быть лучше читать для начала) говорит, что пул DMA лучше работает для небольших областей dma ( короче страницы) - https://static.lwn.net/images/pdf/LDD3/ch15.pdf стр. 447 или https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch15.html, раздел «Пулы DMA»:
Пул DMA - это механизм выделения для небольшие, согласованные отображения DMA. Отображения, полученные из dma_alloc_coherent
, могут иметь минимальный размер одной страницы. Если вашему устройству требуются области DMA меньшего размера, возможно, вам следует использовать пул DMA. Пулы DMA также полезны в ситуациях, когда у вас может возникнуть желание выполнить DMA для небольших областей, встроенных в большую структуру. Некоторые очень неясные ошибки драйвера были отнесены к проблемам когерентности кэша с полями структуры, смежными с небольшими областями прямого доступа к памяти. Чтобы избежать этой проблемы, вы всегда должны явно выделять области для операций DMA, вдали от других структур данных, не относящихся к DMA. ... Распределения обрабатываются с помощью dma_pool_alloc
То же самое указано в https://www.kernel.org/doc/Documentation/DMA-API-HOWTO.txt
Если вашему драйверу требуется много меньших областей памяти Вы можете написать собственный код для разделения страниц, возвращаемых dma_alloc_coherent (), или вы можете использовать API dma_pool для этого. Dma_pool похож на kmem_cache, но он использует dma_alloc_coherent (), а не __get_free_pages (). Кроме того, он понимает общие аппаратные ограничения для выравнивания, такие как заголовки очередей, которые необходимо выравнивать по границам байтов N.
Таким образом, пулы DMA являются оптимизацией для меньших распределений. Вы можете использовать dma_alloc_coherent
для каждой маленькой памяти dma индивидуально (с большими издержками) или вы можете попытаться создать свой собственный пул (больше пользовательского кода для управления смещениями и распределениями). Но пулы DMA уже реализованы, и их можно использовать.
Производительность методов должна быть профилирована для вашего случая.
Пример динамической c регистрации dma в сетевом драйвере (используется для фрагментов skb ): https://elixir.bootlin.com/linux/v4.6/source/drivers/net/ethernet/realtek/r8169.c
static struct sk_buff *rtl8169_alloc_rx_data
mapping = dma_map_single(d, rtl8169_align(data), rx_buf_sz,
DMA_FROM_DEVICE);
static int rtl8169_xmit_frags
mapping = dma_map_single(d, addr, len, DMA_TO_DEVICE);
static netdev_tx_t rtl8169_start_xmit
mapping = dma_map_single(d, skb->data, len, DMA_TO_DEVICE);
static void rtl8169_unmap_tx_skb
dma_unmap_single(d, le64_to_cpu(desc->addr), len, DMA_TO_DEVICE);
Регистрация фрагментов skb для dma на месте может быть лучше (если sg dma поддерживается NI C чип), чем копировать каждый фрагмент из skb в некоторую память DMA. Обратитесь к книге «Понимание Linux Сетевые компоненты» для раздела «Функция dev_queue_xmit» и Глава 21; и skb_linearize
Пример использования пула DMA - драйвер nvme (prp равен часть элемента очереди передачи , страницы физического региона, 64-битный указатель, и «Список PRP содержит список PRP, как правило, без смещений. "):
https://elixir.bootlin.com/linux/v4.6/source/drivers/nvme/host/pci.c#L1807
static int nvme_setup_prp_pools(struct nvme_dev *dev)
{
dev->prp_page_pool = dma_pool_create("prp list page", dev->dev,
PAGE_SIZE, PAGE_SIZE, 0);
static bool nvme_setup_prps
prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);
static void nvme_free_iod
dma_pool_free(dev->prp_small_pool, list[0], prp_dma);