34

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

Q:「DMA転送を使用した/および経由したユーザースペースへのダイレクトI/O」を組み合わせるにはどうすればよいですか。

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

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

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

シングルページストリーミングマッピング、セットアップマッピング、およびでマッピングされたユーザースペースバッファーが必要get_user_pages dma_map_pageですか?

これまでの私のコードは、ユーザースペースから指定されたアドレスに設定さget_user_pagesれています(これをDirect I / O部分と呼びます)。次に、dma_map_pageからのページでget_user_pagesdma_map_pageDMA物理転送アドレスとしてデバイスに戻り値を与えます。

いくつかのカーネルモジュールを参照として使用しています:drivers_scsi_st.cおよびdrivers-net-sh_eth.c。インフィニバンドコードを調べますが、どれが最も基本的なものかわかりません。

よろしくお願いします。

4

6 に答える 6

18

私は実際に今まったく同じことに取り組んでおり、私は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(&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);

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

于 2011-04-04T14:38:03.213 に答える
14

基本的に正しい考えがあります。2.1では、ユーザースペースに古いメモリを割り当てることができます。あなたはそれをページに揃えたいので、posix_memalign()使用するのに便利なAPIです。

次に、ユーザースペースにこのバッファのユーザースペース仮想アドレスとサイズを何らかの方法で渡してもらいます。ioctl()は、これを行うための優れた迅速で汚い方法です。struct page*カーネルで、 -user_buf_size/PAGE_SIZEエントリ-の適切なサイズのバッファ配列を割り当て、get_user_pages()ユーザースペースバッファの構造体ページ*のリストを取得するために使用します。

struct scatterlistそれができたら、ページ配列と同じサイズの配列を割り当てて、を実行しているページのリストをループできますsg_set_page()。sgリストを設定した後、dma_map_sg()スキャッターリストの配列で実行し、スキャッターリストの各エントリに対してsg_dma_addressとを取得できます(マップされたエントリが少なくなる可能性があるためsg_dma_len、の戻り値を使用する必要があることに注意してください。dma_map_sg()DMAマッピングコードによってマージされます)。

これにより、すべてのバスアドレスがデバイスに渡され、DMAをトリガーして、必要に応じて待機できます。あなたが持っているread()ベースのスキームはおそらく問題ありません。

特に、このマッピングを構築する一部のコードについては、drivers / infiniband / core / umem.cを参照できますがib_umem_get()、そのコードが処理する必要のある一般性により、少し混乱する可能性があります。

または、デバイスがスキャッター/ギャザーリストを適切に処理せず、連続したメモリが必要な場合はget_free_pages()、物理的に連続したバッファーを割り当てて使用することができますdma_map_page()。ユーザースペースにそのメモリへのアクセスを許可するには、ドライバーmmapは上記のようにioctlの代わりにメソッドを実装する必要があります。

于 2011-05-21T07:16:16.183 に答える
6

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

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

基本的に、それは呼び出しのカスタム実装に要約されmmap()ます(ただしfile_operations)。I/Oバー用のものは簡単です。

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;
}

また、を使用してDMAバッファを割り当てるもう1つの方法pci_alloc_consistent()は、もう少し複雑です。

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を焼き付け、割り込みを無効にしました。

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

于 2013-05-13T02:32:29.893 に答える
2

実装の方向性に戸惑っています。したい...

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

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

データを手動で操作(読み取り/書き込み)することは、データを十分に理解するのに役立つ場合は、かなり良いオプションです。インラインコピーでは、汎用VMと読み取り/書き込みを使用するだけで十分な場合があります。追跡不可能なアクセスを周辺機器に直接マッピングすることは便利ですが、不器用になる可能性があります。アクセスが大きなブロックの比較的まれな移動である場合は、通常のメモリを使用し、ドライブピンを用意し、アドレスを変換し、DMAを実行して、ページを解放することが理にかなっている場合があります。最適化として、ページ(おそらく巨大)を事前に固定して翻訳することができます。ドライブは準備されたメモリを認識し、動的変換の複雑さを回避できます。小さなI/O操作がたくさんある場合は、ドライブを非同期で実行するのが理にかなっています。エレガンスが重要な場合は、VMダーティページフラグを使用して、移動する必要があるものを自動的に識別し、(meta_sync())呼び出しを使用してページをフラッシュできます。おそらく、上記の作品の混合物...

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

于 2014-04-17T13:10:10.547 に答える
0
first_page_offset = udata & PAGE_MASK; 

間違っているようです。次のいずれかになります。

first_page_offset = udata & ~PAGE_MASK;

また

first_page_offset = udata & (PAGE_SIZE - 1)
于 2016-02-16T06:47:09.050 に答える
0

Scatter-Gather DMAをサポートし、ユーザースペースのメモリ割り当てを備えたドライバーが最も効率的で、最高のパフォーマンスを発揮することは特筆に値します。ただし、高性能が必要ない場合や、いくつかの単純化された条件でドライバーを開発したい場合は、いくつかのトリックを使用できます。

ゼロコピーデザインをあきらめます。データスループットがそれほど大きくない場合は、検討する価値があります。このような設計では、 copy_to_user(user_buffer, kernel_dma_buffer, count); user_bufferによってユーザーにコピーできるデータは、たとえば、文字デバイスのread()システムコールの実装におけるバッファ引数である可能性があります。kernel_dma_bufferまだ割り当ての面倒を見る必要があります。dma_alloc_coherent()たとえば、呼び出しから取得したメモリによる場合があります。

もう1つのトリックは、起動時にシステムメモリを制限し、それを巨大な連続DMAバッファとして使用することです。これは、ドライバーおよびFPGA DMAコントローラーの開発中に特に役立ち、実稼働環境では推奨されません。PCに32GBのRAMがあるとしましょう。カーネルブートパラメータリストに追加するmem=20GBと、12GBを巨大な連続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によって完全に省略されており、アドレス空間にマップしたプロセスでのみ使用できます。Contiguous Memory Allocator(CMA)を使用することで、これを回避することができます。

上記のトリックは、完全なScatter-Gather、ゼロコピーDMAドライバーに取って代わるものではありませんが、開発時またはパフォーマンスの低いプラットフォームで役立ちます。

于 2018-05-14T07:36:21.713 に答える