GNU/Linux >> Linux の 問題 >  >> Linux

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

<ブロック引用>

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

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

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

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

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


私は実際に今まったく同じことに取り組んでおり、 ioctl() に行きます ルート。一般的な考え方は、DMA 転送と ioctl() に使用されるバッファをユーザー空間に割り当てることです。 このバッファのサイズとアドレスをデバイス ドライバに渡すために使用されます。次に、ドライバーはストリーミング DMA API と共にスキャッター/ギャザー リストを使用して、デバイスおよびユーザー空間バッファーとの間でデータを直接転送します。

私が使用している実装戦略は、ioctl() ドライバーで、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);

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


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

<オール>
  • remap_pfn_range() を使用して PCIe デバイスの I/O バーをユーザー空間にマップします 関数。
  • 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;
    }
    

    pci_alloc_consistent() を使用して DMA バッファを割り当てる別のもの もう少し複雑です:

    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 を燃やし、割り込みを無効にしていました。

    それが役に立てば幸い。幸運を祈ります!


    あなたは基本的に正しい考えを持っています.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() を実行します scatterlist の配列で、 sg_dma_address を取得できます と sg_dma_len scatterlist の各エントリに対して (dma_map_sg() の戻り値を使用する必要があることに注意してください) DMA マッピング コードによってマージされる可能性があるため、マッピングされたエントリが少なくなる可能性があるためです)。

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

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

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


    Linux
    1. MacからLinuxに切り替えた理由

    2. Linuxにデバイスドライバーをインストールする方法

    3. Linux:デバイスに使用されているデバイスドライバーを見つける方法は?

    1. Linux カーネルからメモリのブロックを予約するにはどうすればよいですか?

    2. Webcam の Linux デバイス ドライバーを理解する

    3. Linux プロセスの仮想メモリ空​​間にカーネル部分があることの用途は何ですか?

    1. Linuxカーネル:イノベーショントップ5

    2. Linux – Linuxカーネルはどのようにしてデバイスのメジャー番号とマイナー番号を認識しますか?

    3. カーネル空間からユーザー空間関数を実行する