На самом деле я сейчас работаю над тем же самым и иду по маршруту ioctl()
.Общая идея состоит в том, чтобы пользовательское пространство выделяло буфер, который будет использоваться для передачи DMA, и ioctl()
будет использоваться для передачи размера и адреса этого буфера драйверу устройства.Затем драйвер будет использовать списки сбора рассеяния вместе с потоковым API DMA для передачи данных непосредственно на устройство и из него и в буфер пространства пользователя.
Стратегия реализации, которую я использую, заключается в том, что ioctl()
вдрайвер зацикливается на том, что DMA является буфером пользовательского пространства кусками по 256 Кб (что является аппаратным пределом для количества записей разброса / сбора, которые он может обработать).Он изолирован внутри функции, которая блокирует до завершения каждой передачи (см. Ниже).Когда все байты передаются или функция инкрементной передачи возвращает ошибку, 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);
Обратите внимание, что это всего лишь общий подход, я работал над этим драйвером в течение последних нескольких недель иЯ еще не проверил это ... Так что, пожалуйста, не рассматривайте этот псевдокод как евангелие и обязательно перепроверьте всю логику и параметры; -).