DMA有効、PCIeハードウェアデバイスからユーザースペースにできるだけ早くデータを取得したい。
Q:「DMA transfer)を介して/および/を介してユーザー空間への直接I/Oを組み合わせるにはどうすればよいですか?
LDD3を読んで、いくつかの異なるタイプのIO操作を実行する必要があるようです!?
dma_alloc_coherent
は、ハードウェアデバイスに渡すことができる物理アドレスを提供します。ただし、転送が完了すると、セットアップget_user_pages
を実行し、copy_to_user
タイプの呼び出しを実行する必要があります。これは無駄に思われ、DMAをカーネルメモリに(バッファとして機能)して)要求し、それを再度ユーザー空間に転送します。LDD3p453:/* Only now is it safe to access the buffer, copy to user, etc. */
私が理想的に欲しいのは、次のようなメモリです。
単一ページのストリーミングマッピング、セットアップマッピング、およびget_user_pages
dma_map_page
でマッピングされたユーザー空間バッファが必要ですか?
これまでのところ、私のコードは、ユーザー空間からの指定されたアドレスにget_user_pages
を設定しています(これを直接I/O部分と呼びます)。次に、dma_map_page
のページにget_user_pages
を追加します。デバイスにdma_map_page
からの戻り値をDMA物理転送アドレスとして指定します。
いくつかのカーネルモジュールを参照として使用しています:drivers_scsi_st.c
およびdrivers-net-sh_eth.c
。私はinfinibandコードを見てみましたが、どれが最も基本的なコードかわかりません!
よろしくお願いします。
私は実際にはまったく同じことを現在取り組んでおり、ioctl()
ルートを使用しています。一般的な考え方は、DMA転送に使用されるバッファーをユーザースペースに割り当てることであり、ioctl()
はこのバッファーのサイズとアドレスを渡すために使用されますデバイスドライバーは、ストリーミングDMA APIと共にスキャッター/ギャザーリストを使用して、デバイスおよびユーザー空間バッファーとの間で直接データを転送します。
私が使用している実装戦略は、ドライバーのioctl()
がループに入るというものです。DMAは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);
これは一般的なアプローチにすぎないことに注意してください。ここ数週間、このドライバに取り組んでおり、実際にテストする必要はありません...したがって、この疑似コードを福音として扱わないでください。すべてのロジックとパラメーターを確認してください;-)。
あなたは基本的に正しい考えを持っています:2.1では、ユーザー空間に古いメモリを割り当てることができます。ページ揃えにする必要があるので、posix_memalign()
は便利なAPIです。
次に、このバッファのユーザー空間仮想アドレスとサイズをユーザー空間に渡します。 ioctl()は、これを行うための迅速かつダーティーな方法です。カーネルで、適切なサイズの_struct page*
_-_user_buf_size/PAGE_SIZE
_エントリーのバッファー配列を割り当て、get_user_pages()
を使用してユーザースペースバッファーのstruct page *のリストを取得します。
それができたら、ページ配列と同じサイズの_struct scatterlist
_の配列を割り当て、sg_set_page()
を実行するページのリストをループできます。 sgリストが設定されたら、スキャッタリストの配列に対してdma_map_sg()
を実行すると、スキャッタリストの各エントリの_sg_dma_address
_および_sg_dma_len
_を取得できます(必要なメモDMAマッピングコード)によってマージされる可能性があるため、マップされたエントリが少なくなる可能性があるため、dma_map_sg()
の戻り値を使用します。
これにより、すべてのバスアドレスがデバイスに渡され、DMAをトリガーして、必要なだけ待機することができます。read()ベースのスキームはおそらく問題ありません。
このマッピングを構築する一部のコードについては、drivers/infiniband/core/umem.c、具体的にはib_umem_get()
を参照できますが、そのコードが処理する必要がある一般性により、少し混乱する場合があります。
または、デバイスがスキャッター/ギャザーリストを適切に処理せず、連続したメモリが必要な場合は、get_free_pages()
を使用して物理的に連続したバッファーを割り当て、その上でdma_map_page()
を使用できます。ユーザー空間にそのメモリへのアクセスを許可するには、ドライバーは上記のioctlの代わりにmmap
メソッドを実装する必要があります。
ある時点で、ユーザースペースアプリケーションがDMAバッファーを割り当て、それをユーザースペースにマップし、物理アドレスを取得してデバイスを制御し、DMAトランザクション(バスマスタリング)完全にユーザースペースから、完全にLinuxカーネルをバイパスします。ただし、少し異なるアプローチを使用しました。最初に、PCIeデバイスの初期化/プローブと作成を行う最小限のカーネルモジュールから始めました。次に、そのドライバはユーザー空間アプリケーションに2つのことを許可します。
remap_pfn_range()
関数を使用して、PCIeデバイスのI/Oバーをユーザー空間にマップします。基本的には、それはmmap()
呼び出しのカスタム実装に要約されます(ただし、_file_operations
_を使用します)。 I/Oバーの1つは簡単です。
_struct vm_operations_struct a2gx_bar_vma_ops = {
};
static int a2gx_cdev_mmap_bar2(struct file *filp, struct vm_area_struct *vma)
{
struct a2gx_dev *dev;
size_t size;
size = vma->vm_end - vma->vm_start;
if (size != 134217728)
return -EIO;
dev = filp->private_data;
vma->vm_ops = &a2gx_bar_vma_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_private_data = dev;
if (remap_pfn_range(vma, vma->vm_start,
vmalloc_to_pfn(dev->bar2),
size, vma->vm_page_prot))
{
return -EAGAIN;
}
return 0;
}
_
また、pci_alloc_consistent()
を使用してDMAバッファを割り当てるもう1つの方法は、少し複雑です。
_static void a2gx_dma_vma_close(struct vm_area_struct *vma)
{
struct a2gx_dma_buf *buf;
struct a2gx_dev *dev;
buf = vma->vm_private_data;
dev = buf->priv_data;
pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr, buf->dma_addr);
buf->cpu_addr = NULL; /* Mark this buffer data structure as unused/free */
}
struct vm_operations_struct a2gx_dma_vma_ops = {
.close = a2gx_dma_vma_close
};
static int a2gx_cdev_mmap_dma(struct file *filp, struct vm_area_struct *vma)
{
struct a2gx_dev *dev;
struct a2gx_dma_buf *buf;
size_t size;
unsigned int i;
/* Obtain a pointer to our device structure and calculate the size
of the requested DMA buffer */
dev = filp->private_data;
size = vma->vm_end - vma->vm_start;
if (size < sizeof(unsigned long))
return -EINVAL; /* Something fishy is happening */
/* Find a structure where we can store extra information about this
buffer to be able to release it later. */
for (i = 0; i < A2GX_DMA_BUF_MAX; ++i) {
buf = &dev->dma_buf[i];
if (buf->cpu_addr == NULL)
break;
}
if (buf->cpu_addr != NULL)
return -ENOBUFS; /* Oops, hit the limit of allowed number of
allocated buffers. Change A2GX_DMA_BUF_MAX and
recompile? */
/* Allocate consistent memory that can be used for DMA transactions */
buf->cpu_addr = pci_alloc_consistent(dev->pci_dev, size, &buf->dma_addr);
if (buf->cpu_addr == NULL)
return -ENOMEM; /* Out of juice */
/* There is no way to pass extra information to the user. And I am too lazy
to implement this mmap() call using ioctl(). So we simply tell the user
the bus address of this buffer by copying it to the allocated buffer
itself. Hacks, hacks everywhere. */
memcpy(buf->cpu_addr, &buf->dma_addr, sizeof(buf->dma_addr));
buf->size = size;
buf->priv_data = dev;
vma->vm_ops = &a2gx_dma_vma_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_private_data = buf;
/*
* Map this DMA buffer into user space.
*/
if (remap_pfn_range(vma, vma->vm_start,
vmalloc_to_pfn(buf->cpu_addr),
size, vma->vm_page_prot))
{
/* Out of luck, rollback... */
pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr,
buf->dma_addr);
buf->cpu_addr = NULL;
return -EAGAIN;
}
return 0; /* All good! */
}
_
それらが配置されると、ユーザー空間アプリケーションはほとんどすべてを行うことができます— I/Oレジスタの読み取り/書き込みによってデバイスを制御し、任意のサイズのバッファを割り当てて解放しますDMAバッファ、およびデバイスがDMA=トランザクションを実行します。欠落している部分は割り込み処理のみです。ユーザースペースでポーリングを実行し、CPUを書き込み、割り込みを無効にしました。
それが役に立てば幸い。幸運を!
実装する方向に混乱しています。したい...
ドライバーを設計するときは、アプリケーションを考慮してください。
データの移動、頻度、サイズの性質、およびシステムで他に何が起こっている可能性がありますか?
従来の読み取り/書き込みAPIで十分ですか?デバイスをユーザー空間に直接マッピングしても問題ありませんか?リフレクティブ(セミコヒーレント)共有メモリは望ましいですか?
手動でデータを操作する(読み取り/書き込み)ことは、データが十分に理解されている場合に非常に適しています。汎用VMを使用し、インラインコピーでは読み取り/書き込みで十分な場合があります。キャッシュできないアクセスをペリフェラルに直接マッピングすることは便利ですが、扱いにくい場合があります。アクセスが比較的頻繁でない場合、大きなブロックの場合、通常のメモリを使用し、ドライブピンを用意し、アドレスを変換し、DMAおよびページを解放することは理にかなっています。最適化として、ページ(おそらく巨大)を事前に固定して変換されます。ドライブは準備されたメモリを認識し、動的変換の複雑さを回避できます。多くの小さなI/O操作がある場合、ドライブを非同期で実行することは意味があります。エレガンスが重要な場合、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にデータをコピーすることができます。user_bufferは、たとえば、文字デバイスのread()システムコール実装のバッファー引数である可能性があります。それでも_kernel_dma_buffer
_の割り当てに注意する必要があります。これは、たとえばdma_alloc_coherent()
呼び出しから取得されたメモリによって発生する可能性があります。
もう1つのトリックは、ブート時にシステムメモリを制限し、それを巨大な連続したDMAバッファとして使用することです。これは、ドライバおよびFPGA DMAコントローラの場合に特に役立ちます。開発であり、本番環境では推奨されません。PCに32 GBのRAMがあるとします。_mem=20GB
_をカーネルブートパラメーターリストに追加すると、12 GBを巨大な連続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はOSによって完全に省略されており、アドレススペースにマップしたプロセスでのみ使用できます。隣接メモリアロケータ(CMA)を使用して、それを回避することができます。
ここでも、上記のトリックは完全なScatter-Gather、ゼロコピーDMAドライバーに置き換わるものではありませんが、開発時または一部のパフォーマンスの低いプラットフォームで役立ちます。