私は実際に今まったく同じことに取り組んでおり、私はioctl()
ルートを進んでいます。一般的な考え方は、ユーザースペースがDMA転送に使用されるバッファーを割り当て、ioctl()
このバッファーのサイズとアドレスをデバイスドライバーに渡すために使用されることです。次に、ドライバーはスキャッターギャザーリストとストリーミングDMA APIを使用して、デバイスとユーザースペースバッファーとの間でデータを直接転送します。
私が使用している実装戦略はioctl()
、ドライバーのinが、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倍にしてください。すべてのロジックとパラメータを確認してください;-)。