web-dev-qa-db-ja.com

LinuxカーネルデバイスドライバーをDMAデバイスからユーザー空間メモリに

DMA有効、PCIeハードウェアデバイスからユーザースペースにできるだけ早くデータを取得したい。

Q:「DMA transfer)を介して/および/を介してユーザー空間への直接I/Oを組み合わせるにはどうすればよいですか?

  1. 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. */

  2. 私が理想的に欲しいのは、次のようなメモリです。

    • ユーザー空間で使用できます(DMA可能なメモリ/バッファを作成するためにioctl呼び出しを介してドライバを要求する可能性がありますか?)
    • から物理アドレスを取得してデバイスに渡すことができるため、すべてのユーザー空間で実行する必要があるのは、ドライバーで読み取りを実行することです
    • readメソッドはDMA転送をアクティブ化し、DMA完全な割り込みを待ってブロックし、後でユーザースペースの読み取りを解放します(ユーザースペースは安全にメモリの使用/読み取り)。

単一ページのストリーミングマッピング、セットアップマッピング、およびget_user_pagesdma_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コードを見てみましたが、どれが最も基本的なコードかわかりません!

よろしくお願いします。

30
Ian Vaughan

私は実際にはまったく同じことを現在取り組んでおり、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(&current->mm->mmap_sem);
ret = get_user_pages(current,
                     current->mm,
                     udata,
                     npages,
                     is_writing_to_userspace,
                     0,
                     &pages_array,
                     NULL);
up_read(&current->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);

これは一般的なアプローチにすぎないことに注意してください。ここ数週間、このドライバに取り組んでおり、実際にテストする必要はありません...したがって、この疑似コードを福音として扱わないでください。すべてのロジックとパラメーターを確認してください;-)。

15
Rakis

あなたは基本的に正しい考えを持っています: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メソッドを実装する必要があります。

12
Roland

ある時点で、ユーザースペースアプリケーションがDMAバッファーを割り当て、それをユーザースペースにマップし、物理アドレスを取得してデバイスを制御し、DMAトランザクション(バスマスタリング)完全にユーザースペースから、完全にLinuxカーネルをバイパスします。ただし、少し異なるアプローチを使用しました。最初に、PCIeデバイスの初期化/プローブと作成を行う最小限のカーネルモジュールから始めました。次に、そのドライバはユーザー空間アプリケーションに2つのことを許可します。

  1. remap_pfn_range()関数を使用して、PCIeデバイスのI/Oバーをユーザー空間にマップします。
  2. DMA=バッファを割り当てて解放し、それらをユーザー空間にマップし、物理バスアドレスをユーザー空間アプリケーションに渡します。

基本的には、それは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を書き込み、割り込みを無効にしました。

それが役に立てば幸い。幸運を!

6
user405725

実装する方向に混乱しています。したい...

ドライバーを設計するときは、アプリケーションを考慮してください。
データの移動、頻度、サイズの性質、およびシステムで他に何が起こっている可能性がありますか?

従来の読み取り/書き込みAPIで十分ですか?デバイスをユーザー空間に直接マッピングしても問題ありませんか?リフレクティブ(セミコヒーレント)共有メモリは望ましいですか?

手動でデータを操作する(読み取り/書き込み)ことは、データが十分に理解されている場合に非常に適しています。汎用VMを使用し、インラインコピーでは読み取り/書き込みで十分な場合があります。キャッシュできないアクセスをペリフェラルに直接マッピングすることは便利ですが、扱いにくい場合があります。アクセスが比較的頻繁でない場合、大きなブロックの場合、通常のメモリを使用し、ドライブピンを用意し、アドレスを変換し、DMAおよびページを解放することは理にかなっています。最適化として、ページ(おそらく巨大)を事前に固定して変換されます。ドライブは準備されたメモリを認識し、動的変換の複雑さを回避できます。多くの小さなI/O操作がある場合、ドライブを非同期で実行することは意味があります。エレガンスが重要な場合、VMダーティページフラグを使用して、何を移動する必要があるかを自動的に識別し、(meta_sync())呼び出しを使用してページをフラッシュできます。おそらく、上記の組み合わせで動作します...

多くの場合、詳細を掘り下げる前に、より大きな問題を見ていない。多くの場合、最も単純なソリューションで十分です。行動モデルを構築する少しの努力は、どのAPIが望ましいかをガイドするのに役立ちます。

1
fbp
first_page_offset = udata & PAGE_MASK; 

それは間違っているようです。次のいずれかである必要があります。

first_page_offset = udata & ~PAGE_MASK;

または

first_page_offset = udata & (PAGE_SIZE - 1)
0
Suman

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ドライバーに置き換わるものではありませんが、開発時または一部のパフォーマンスの低いプラットフォームで役立ちます。

0
SlawekS