Драйвер устройства ядра Linux для DMA с устройства в память пользовательского пространства
я хочу, чтобы получить данные от DMA включен, PCIe аппаратное устройство в пользовательское пространство как можно быстрее.
Q: Как объединить "прямой ввод-вывод в пользовательское пространство с/и / через передачу DMA"
-
читать через LDD3, кажется, что мне нужно выполнить несколько различных типов операций ввода-вывода!?
dma_alloc_coherent
дает мне физический адрес, который я могу передать на устройство. Но нужно будет иметь setupget_user_pages
и выполнитьcopy_to_user
введите вызов, когда передача завершится. Это кажется пустой тратой, запрашивая устройство DMA в память ядра (действуя как буфер), а затем снова перенося его в пользовательское пространство. LDD3 p453:/* Only now is it safe to access the buffer, copy to user, etc. */
-
то, что я идеально хочу, это некоторая память, которая:
- я могу использовать в пользовательском пространстве (возможно, запросить драйвер через вызов 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(¤t->mm->mmap_sem);
ret = get_user_pages(current,
current->mm,
udata,
npages,
is_writing_to_userspace,
0,
&pages_array,
NULL);
up_read(¤t->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 и создавал символьное устройство. Этот драйвер затем разрешил пространство пользователя приложение, чтобы сделать две вещи:
- карта 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, но полезны во время разработки или в меньшей производительности платформы.