DMA 対応の PCIe ハードウェア デバイスからユーザー空間にできるだけ早くデータを取得したいと考えています。
Q: 「DMA 転送を使用したユーザー空間への直接 I/O」と「DMA 転送経由」をどのように組み合わせればよいですか?
LDD3 を読んでみると、いくつかの異なるタイプの IO 操作を実行する必要があるようです。
dma_alloc_coherent
get_user_pages
ハードウェア デバイスに渡すことができる物理アドレスが提供されます。ただし、転送が完了したらセットアップしてタイプ呼び出しを実行する必要がありますcopy_to_user
。デバイスにカーネル メモリへの DMA (バッファーとして動作) を要求し、その後ユーザー空間に再度転送するのは無駄に思えます。LDD3 p453:/* Only now is it safe to access the buffer, copy to user, etc. */
私が理想的に望むのは、次のようなメモリです。
- ユーザー空間で使用できます (DMA 可能なメモリ/バッファを作成するために ioctl 呼び出しを介してドライバーを要求する可能性があります)
- 物理アドレスを取得してデバイスに渡すことができるので、ユーザー空間ではドライバーの読み取りを実行するだけで済みます。
- 読み取りメソッドは DMA 転送をアクティブにし、DMA 完了割り込みを待機してブロックし、その後ユーザー空間の読み取りを解放します (ユーザー空間はメモリを安全に使用/読み取りできるようになります)。
シングルページ ストリーミング マッピング、セットアップ マッピング、およびマップされたユーザー スペース バッファーは必要ですかget_user_pages
dma_map_page
?
これまでの私のコードは、get_user_pages
ユーザー空間から指定されたアドレスでセットアップします (これを Direct I/O 部分と呼びます)。次に、dma_map_page
からのページを使用して、 からの戻り値をDMA 物理転送アドレスとしてget_user_pages
デバイスに渡します。dma_map_page
私はいくつかのカーネル モジュールを参照として使用しています:drivers_scsi_st.c
およびdrivers-net-sh_eth.c
。Infiniband コードを調べたいのですが、どれが最も基本的なものかがわかりません。
よろしくお願いします。
ベストアンサー1
実は今、私はまったく同じことに取り組んでいて、そのioctl()
方法を採用しています。基本的な考え方は、ユーザー空間で DMA 転送に使用するバッファを割り当て、ioctl()
このバッファのサイズとアドレスをデバイス ドライバに渡すというものです。ドライバは、スキャッター ギャザー リストとストリーミング DMA API を使用して、デバイスとユーザー空間バッファ間で直接データを転送します。
私が使用している実装戦略は、ioctl()
ドライバがループに入り、ユーザ空間バッファを 256k のチャンクで DMA するものです (これは、ハードウェアが処理できるスキャッタ/ギャザーエントリの数の制限です)。これは、各転送が完了するまでブロックする関数内で分離されます (以下を参照)。すべてのバイトが転送されるか、増分転送関数がエラーを返すと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);
これは単なる一般的なアプローチであることに注意してください。私はここ数週間このドライバーに取り組んできましたが、実際にテストしたことはまだありません... したがって、この疑似コードを絶対的な真実として扱わず、すべてのロジックとパラメーターを必ず再確認してください ;-)。