Драйвер устройства ядра Linux для DMA с устройства в память пользовательского пространства

я хочу, чтобы получить данные от DMA включен, PCIe аппаратное устройство в пользовательское пространство как можно быстрее.

Q: Как объединить "прямой ввод-вывод в пользовательское пространство с/и / через передачу DMA"

  1. читать через LDD3, кажется, что мне нужно выполнить несколько различных типов операций ввода-вывода!?

    dma_alloc_coherent дает мне физический адрес, который я могу передать на устройство. Но нужно будет иметь setup get_user_pages и выполнить copy_to_user введите вызов, когда передача завершится. Это кажется пустой тратой, запрашивая устройство DMA в память ядра (действуя как буфер), а затем снова перенося его в пользовательское пространство. LDD3 p453:/* Only now is it safe to access the buffer, copy to user, etc. */

  2. то, что я идеально хочу, это некоторая память, которая:

    • я могу использовать в пользовательском пространстве (возможно, запросить драйвер через вызов ioctl для создания памяти/буфера DMA'able?)
    • я могу получить физический адрес, чтобы перейти на устройство, чтобы все пользовательское пространство должен сделать, это выполнить чтение на драйвере
    • метод чтения активирует передачу DMA, блокирует ожидание полного прерывания DMA и освобождает пользовательское пространство, прочитанное впоследствии (пользовательское пространство теперь безопасно использовать/читать память).

мне нужны одностраничные потоковые сопоставления, отображение настроек и буферы пользовательского пространства, сопоставленные с get_user_pages dma_map_page?

мой код до сих пор устанавливает get_user_pages по заданному адресу из пользовательского пространства (вызываю это прямая часть ввода / вывода). Затем:dma_map_page страница из get_user_pages. Я даю устройству возвращаемое значение от dma_map_page как физический адрес передачи DMA.

я использую некоторые модули ядра в качестве ссылки:drivers_scsi_st.c и drivers-net-sh_eth.c. Я бы посмотрел на код infiniband, но не могу найти, какой из них самый простой!

заранее большое спасибо.

6 ответов


Я на самом деле работаю над тем же самым прямо сейчас, и я собираюсь ioctl() маршрут. Общая идея заключается в том, чтобы пользовательское пространство выделяло буфер, который будет использоваться для передачи DMA и ioctl() будет использоваться для передачи размера и адреса этого буфера драйверу устройства. Затем драйвер будет использовать scatter-gather lists вместе с streaming DMA API для передачи данных непосредственно в буфер устройства и пространства пользователя.

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

псевдо код ioctl()

/*serialize all DMA transfers to/from the device*/
if (mutex_lock_interruptible( &device_ptr->mtx ) )
    return -EINTR;

chunk_data = (unsigned long) user_space_addr;
while( *transferred < total_bytes && !ret ) {
    chunk_bytes = total_bytes - *transferred;
    if (chunk_bytes > HW_DMA_MAX)
        chunk_bytes = HW_DMA_MAX; /* 256kb limit imposed by my device */
    ret = transfer_chunk(device_ptr, chunk_data, chunk_bytes, transferred);
    chunk_data += chunk_bytes;
    chunk_offset += chunk_bytes;
}

mutex_unlock(&device_ptr->mtx);

псевдо код для функции инкрементной передачи:

/*Assuming the userspace pointer is passed as an unsigned long, */
/*calculate the first,last, and number of pages being transferred via*/

first_page = (udata & PAGE_MASK) >> PAGE_SHIFT;
last_page = ((udata+nbytes-1) & PAGE_MASK) >> PAGE_SHIFT;
first_page_offset = udata & PAGE_MASK;
npages = last_page - first_page + 1;

/* Ensure that all userspace pages are locked in memory for the */
/* duration of the DMA transfer */

down_read(&current->mm->mmap_sem);
ret = get_user_pages(current,
                     current->mm,
                     udata,
                     npages,
                     is_writing_to_userspace,
                     0,
                     &pages_array,
                     NULL);
up_read(&current->mm->mmap_sem);

/* Map a scatter-gather list to point at the userspace pages */

/*first*/
sg_set_page(&sglist[0], pages_array[0], PAGE_SIZE - fp_offset, fp_offset);

/*middle*/
for(i=1; i < npages-1; i++)
    sg_set_page(&sglist[i], pages_array[i], PAGE_SIZE, 0);

/*last*/
if (npages > 1) {
    sg_set_page(&sglist[npages-1], pages_array[npages-1],
        nbytes - (PAGE_SIZE - fp_offset) - ((npages-2)*PAGE_SIZE), 0);
}

/* Do the hardware specific thing to give it the scatter-gather list
   and tell it to start the DMA transfer */

/* Wait for the DMA transfer to complete */
ret = wait_event_interruptible_timeout( &device_ptr->dma_wait, 
         &device_ptr->flag_dma_done, HZ*2 );

if (ret == 0)
    /* DMA operation timed out */
else if (ret == -ERESTARTSYS )
    /* DMA operation interrupted by signal */
else {
    /* DMA success */
    *transferred += nbytes;
    return 0;
}

обработчик прерываний исключительно короткий:

/* Do hardware specific thing to make the device happy */

/* Wake the thread waiting for this DMA operation to complete */
device_ptr->flag_dma_done = 1;
wake_up_interruptible(device_ptr->dma_wait);

обратите внимание, что это просто общий подход, я работал над этим драйвером в течение последних нескольких недель и еще не тестировал его... Поэтому, пожалуйста, не относитесь к этому псевдокоду как к Евангелию и не забудьте дважды проверить всю логику и параметры ;-).


у вас в основном есть правильная идея: в 2.1 вы можете просто выделить userspace любую старую память. Вы хотите, чтобы он выровнялся по странице, поэтому posix_memalign() - удобный API для использования.

тогда у userspace pass в виртуальном адресе userspace и размере этого буфера каким-то образом; ioctl () - хороший быстрый и грязный способ сделать это. В ядре выделите соответствующий размер буферного массива struct page* -- user_buf_size/PAGE_SIZE панель -- и использовать get_user_pages() чтобы получить список страницы структуры* для пространства пользователей буфер.

как только у вас это есть, вы можете выделить массив struct scatterlist это тот же размер, что и Ваш массив страниц, и цикл через список страницsg_set_page(). После того, как список sg настроен, вы делаете dma_map_sg() на массиве scatterlist, а затем вы можете получить sg_dma_address и sg_dma_len для каждой записи в scatterlist (обратите внимание, что вы должны использовать возвращаемое значение dma_map_sg() потому что вы можете получить меньше сопоставленных записей, потому что вещи могут быть объединены сопоставлением DMA код.)

это дает вам все адреса шины для передачи на устройство, а затем вы можете вызвать DMA и ждать его, как вы хотите. Схема на основе read () у вас, вероятно, в порядке.

вы можете обратиться к драйверам / infiniband / core / umem.С, в частности,ib_umem_get(), для некоторого кода, который создает это сопоставление, хотя общность, с которой этот код должен иметь дело, может сделать его немного запутанным.

кроме того, если ваше устройство не обрабатывает разброс / собирать списки слишком хорошо, и вы хотите непрерывную память, вы можете использовать get_free_pages() чтобы выделить физически непрерывный буфер и использовать dma_map_page() об этом. Чтобы предоставить пользователю доступ к этой памяти, ваш драйвер просто должен реализовать mmap метод вместо ioctl, как описано выше.


в какой-то момент я хотел разрешить приложению пользовательского пространства выделять буферы DMA и сопоставлять их с пользовательским пространством и получать физический адрес, чтобы иметь возможность управлять моим устройством и выполнять транзакции DMA (Bus mastering) полностью из пользовательского пространства, полностью минуя ядро Linux. Я использовал немного другой подход. Сначала я начал с минимального модуля ядра, который инициализировал / зондировал устройство PCIe и создавал символьное устройство. Этот драйвер затем разрешил пространство пользователя приложение, чтобы сделать две вещи:

  1. карта PCIe устройства ввода/вывода бар в пространство пользователя с помощью

я путаюсь с направлением в реализации. Я хочу...

рассмотрим приложение при разработке драйвера.
Какова природа движения данных, частота, размер и что еще может происходить в системе?

достаточно ли традиционного API чтения/записи? Прямое отображение устройства в пользовательское пространство нормально? Светоотражающие (полу-последовательной) общей памяти желательно?

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

слишком часто люди не смотрят на большую проблему, прежде чем копаться в деталь. Часто достаточно простейших решений. Небольшое усилие по построению поведенческой модели может помочь определить, какой API предпочтительнее.


first_page_offset = udata & PAGE_MASK; 

Это кажется неправильным. Должно быть либо:

first_page_offset = udata & ~PAGE_MASK;

или

first_page_offset = udata & (PAGE_SIZE - 1)

стоит отметить, что драйвер с поддержкой Scatter-Gather DMA и выделением памяти пользовательского пространства наиболее эффективен и имеет высокую производительность. Однако в случае, если нам не нужна высокая производительность или мы хотим разработать драйвер в некоторых упрощенных условиях мы можем использовать некоторые трюки.

отказаться от нулевого дизайна копии. Стоит учитывать, когда пропускная способность данных не слишком велика. В таком дизайне данные могут быть скопированы пользователю copy_to_user(user_buffer, kernel_dma_buffer, count); user_buffer может быть, например, аргументом буфера в реализация системного вызова character device read (). Мы все еще должны заботиться о kernel_dma_buffer распределение. Это может быть память, полученная из dma_alloc_coherent() вызов, например.

другой трюк-ограничить системную память во время загрузки, а затем использовать ее как огромный непрерывный буфер DMA. Особенно полезно во время водителя и развития регулятора FPGA DMA и довольно не порекомендовано в производственных средах. Допустим, ПК имеет 32 ГБ ОЗУ. Если добавить mem=20GB к списку параметров загрузки ядра мы может использовать 12GB как огромный непрерывный буфер dma. Чтобы сопоставить эту память с пользовательским пространством, просто реализуйте mmap () как

remap_pfn_range(vma,
    vma->vm_start,
    (0x500000000 >> PAGE_SHIFT) + vma->vm_pgoff, 
    vma->vm_end - vma->vm_start,
    vma->vm_page_prot)

конечно, этот 12GB полностью опущен ОС и может использоваться только процессом, который сопоставил его в адресное пространство. Мы можем попытаться избежать этого, используя непрерывный распределитель памяти (CMA).

опять же выше трюки не заменят полный разброс-сбор, драйвер нулевой копии DMA, но полезны во время разработки или в меньшей производительности платформы.