Обслуживание периферии в коде модулей ядра: Часть 53. Организация обмена данными с помощью DMA

Comments

Организация обмена данными по DMA полностью зависит от деталей аппаратной реализации PCI-устройства, поэтому мы рассмотрим только обобщённую схему взаимодействия.

Выделение буфера DMA

Любой модуль, организующий обмен данными по DMA, должен предоставить устройству буфер для выполнения операций DMA. При предоставление буфера необходимо указать два его параметра: начальный адрес и размер. Буфера DMA могут выделяться только в строго определённых областях памяти:

  • эта память должна распределяться в физически непрерывной области памяти, поэтому выделение посредством vmalloc() неприменимо, а память под буфер должна выделяться kmalloc() или странично __get_free_pages();
  • для многих архитектур выделение памяти должно быть специфицировано с флагом GFP_DMA, для x86 PCI-устройств это будет выделение ниже адреса MAX_DMA_ADDRESS (=16MБ);
  • память должна выделяться начиная с границы страницы (PAGE_SIZE) физической памяти, и в объёме целого числа страниц физической памяти.

Поскольку это достаточно фиксированный набор условий, то ядро предоставляет несколько альтернативных групп API для распределения памяти под буфера DMA. Реализация этих API зависит от конкретной архитектуры, но вызовы скрывают это и создают дополнительный уровень абстракции.

  1. Coherent DMA mapping API, при котором не требуется предварительно распределять буфер DMA, применяется для устойчивых распределений многократно используемых буферов;
    void *dma_alloc_coherent( struct device *dev, size_t size,
                              dma_addr_t *dma_handle, gfp_t flag );
    void dma_free_coherent( struct device *dev, size_t size,
                            void *vaddr, dma_addr_t dma_handle );
  2. Streaming DMA mapping API применяется для выделения буфера под однократные операции DMA, где direction это планируемое направление передачи данных: PCI_DMA_TODEVICE, PCI_DMA_FROMDEVICE, PCI_DMA_BIDIRECTIONAL, PCI_DMA_NONE;
    dma_addr_t dma_map_single( struct device *dev, void *ptr, size_t size,
                               enum dma_data_direction direction );
    void dma_unmap_single( struct device *dev, dma_addr_t dma_handle, size_t size,
                           enum dma_data_direction direction );
  3. DMA pool API используется для выделения буферов DMA небольшого размера, так как dma_alloc_coherent() допускает минимальное выделение только в одну физическую страницу (как видно из прототипов, пул struct dma_pool должен быть сначала создан вызовом dma_pool_create(), и только затем из него производятся выделения dma_pool_alloc());
    struct dma_pool *dma_pool_create( const char *name, struct device *dev,
                     size_t size, size_t align, size_t allocation );
    void dma_pool_destroy( struct dma_pool *pool );
    void *dma_pool_alloc( struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle );
    void dma_pool_free( struct dma_pool *pool, void *vaddr, dma_addr_t handle );
  4. Устаревший API, впервые появившийся в версии ядра 2.4, содержит две пары вызовов, аналогичных первому и второму API. Утверждается, что новые интерфейсы не зависят от типа аппаратной шины (в перспективе на новые расширения), а устаревший API предназначен исключительно для работы с PCI-шиной.
    void *pci_alloc_consistent( struct device *dev, size_t size, dma_addr_t 
    *dma_handle );
    void pci_free_consistent( struct device *dev, size_t size,
                              void *vaddr, dma_addr_t dma_handle );
    dma_addr_t pci_map_single( struct device *dev, void *ptr, size_t size, 
    int direction );
    void pci_unmap_single( struct device *dev, dma_addr_t dma_handle,
                           size_t size, int direction );

Выделив любым подходящим способом блок памяти для обмена по DMA, драйвер в цикле выполняет следующую последовательность операций:

  1. адрес начала блока записывается в соответствующий регистр одной из 6-ти областей ввода-вывода PCI устройства (конкретные адреса таких регистров определяются спецификацией устройства);
  2. ещё в один специфический регистр заносится длина блока для обмена;
  3. в регистр команды заносится значение (чаще это выделенный бит) команды начала операции по DMA;
  4. когда внешнее PCI-устройство сочтёт, что оно готово приступить к выполнению этой операции, оно аппаратно захватывает шину PCI, и под собственным управлением записывает (считывает) указанный блок данных;
  5. завершив выполнение операции устройство освобождает шину PCI под управление процессора, и извещает систему прерыванием по выделенной устройству линии IRQ о завершении операции.

Из сказанного становится ясным, что принципиальной операцией при организации DMA-обмена в модуле является только создание буфера DMA, а все остальные задачи должно выполнять периферийное устройство. Примеры различного выделения буферов DMA показаны в архиве dma.tgz, который можно найти в разделе "Материалы для скачивания". Результаты выполнения находятся в этом же архиве в файле dma.hist, а сама идея тестов заимствована из 31-ой главы книги Джерри Купперстайна (Jerry Cooperstein) "Writing Linux Device Drivers".

Листинг 1. Выполнение операций DMA с помощью современных API.
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>

#include "out.c"
#define pool_size 1024
#define pool_align 8

/* обмен данными будет осуществляться в обоих направлениях */
static int direction = PCI_DMA_BIDIRECTIONAL;

static int __init my_init( void ) {
   char *kbuf;
   dma_addr_t handle;
   size_t size = ( 10 * PAGE_SIZE );
   struct dma_pool *mypool;
   /* использование метода dma_alloc_coherent */
   kbuf = dma_alloc_coherent( NULL, size, &handle, GFP_KERNEL );
   output( kbuf, handle, size, "This is the dma_alloc_coherent() string" );
   dma_free_coherent( NULL, size, kbuf, handle );
   /* использование метода dma_map/unmap_single */
   kbuf = kmalloc( size, GFP_KERNEL );
   handle = dma_map_single( NULL, kbuf, size, direction );
   output( kbuf, handle, size, "This is the dma_map_single() string" );
   dma_unmap_single( NULL, handle, size, direction );
   kfree( kbuf );
   /* использование метода dma_pool */
   mypool = dma_pool_create( "mypool", NULL, pool_size, pool_align, 0 );
   kbuf = dma_pool_alloc( mypool, GFP_KERNEL, &handle );
   output( kbuf, handle, size, "This is the dma_pool_alloc() string" );
   dma_pool_free( mypool, kbuf, handle );
   dma_pool_destroy( mypool );
   return -1;
}

Тот же код, но использующий API, предназначенный только для шины PCI.

Листинг 2. Использование устаревшего PCI API.
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>

#include "out.c"

/* обмен данными будет осуществляться в обоих направлениях */
static int direction = PCI_DMA_BIDIRECTIONAL;

static int __init my_init( void ) {
   char *kbuf;
   dma_addr_t handle;
   size_t size = ( 10 * PAGE_SIZE );
   /* использование метода pci_alloc_consistent */
   kbuf = pci_alloc_consistent( NULL, size, &handle );
   output( kbuf, handle, size, "This is the pci_alloc_consistent() string" );
   pci_free_consistent( NULL, size, kbuf, handle );
   /* использование метода pci_map/unmap_single */
   kbuf = kmalloc( size, GFP_KERNEL );
   handle = pci_map_single( NULL, kbuf, size, direction );
   output( kbuf, handle, size, "This is the pci_map_single() string" );
   pci_unmap_single( NULL, handle, size, direction );
   kfree( kbuf );
   return -1;
}

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

Листинг 3. Общая часть тестов.
static int __init my_init( void );
module_init( my_init );

MODULE_AUTHOR( "Jerry Cooperstein" );
MODULE_AUTHOR( "Oleg Tsiliuric" );
MODULE_DESCRIPTION( "LDD:1.0 s_23/lab1_dma.c" );
MODULE_LICENSE( "GPL v2" );

#define MARK "=> "
static void output( char *kbuf, dma_addr_t handle, size_t size, char *string ) {
   unsigned long diff;
   diff = (unsigned long)kbuf - handle;
   printk( KERN_INFO MARK "kbuf=%12p, handle=%12p, size = %d\n",
           kbuf, (void*)(unsigned long)handle, (int)size );
   printk( KERN_INFO MARK "(kbuf-handle)= %12p, %12lu, PAGE_OFFSET=%12lu, compare=%lu\n",
           (void*)diff, diff, PAGE_OFFSET, diff - PAGE_OFFSET );
   strcpy( kbuf, string );
   printk( KERN_INFO MARK "string written was, %s\n", kbuf );
}

Вот как выглядит результат запуска этих примеров:

$ sudo insmod lab1_dma.ko
insmod: error inserting 'lab1_dma.ko': -1 Operation not permitted
$ dmesg | tail -n200 | grep '=>'
=> kbuf=    c0c10000, handle=      c10000, size = 40960
=> (kbuf-handle)=     c0000000,   3221225472, PAGE_OFFSET=  3221225472, compare=0
=> string written was, This is the dma_alloc_coherent() string
=> kbuf=    d4370000, handle=    14370000, size = 40960
=> (kbuf-handle)=     c0000000,   3221225472, PAGE_OFFSET=  3221225472, compare=0
=> string written was, This is the dma_map_single() string
=> kbuf=    c0c02000, handle=      c02000, size = 40960
=> (kbuf-handle)=     c0000000,   3221225472, PAGE_OFFSET=  3221225472, compare=0
=> string written was, This is the dma_pool_alloc() string
$ sudo insmod lab1_dma_PCI_API.ko
insmod: error inserting 'lab1_dma.ko': -1 Operation not permitted
$ dmesg | tail -n50 | grep '=>'
=> kbuf=    c0c10000, handle=      c10000, size = 40960
=> (kbuf-handle)=     c0000000,   3221225472, PAGE_OFFSET=  3221225472, compare=0
=> string written was, This is the pci_alloc_consistent() string
=> kbuf=    d4370000, handle=    14370000, size = 40960
=> (kbuf-handle)=     c0000000,   3221225472, PAGE_OFFSET=  3221225472, compare=0
=> string written was, This is the pci_map_single() string

Эти примеры интересны не только своими результатами, но и тем, что фрагменты этого кода могут быть использованы в качестве стартовых шаблонов для написания реальных DMA обменов.

Заключение

Работа драйвера по организации обмена данными зависит исключительно от алгоритма функционирования самого PCI-устройства, заложенного его автором, поэтому в написании этой части модуля большую роль имеет скрупулёзная работа с техническими спецификациями устройства. Тем не менее, есть несколько достаточно общих моментов в реализации работы PCI устройства, связанных с огранизацией обмена данными между модулем и устройством. Эти статья была посвящена общим вопросам, связанным с организацией обмена данными с помощью DMA, а специфические аспекты реализации мы оставляем читателю для самостоятельного изучения.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=931167
ArticleTitle=Обслуживание периферии в коде модулей ядра: Часть 53. Организация обмена данными с помощью DMA
publish-date=05232013