Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(x86対応？)

## はじめに ##

前回投稿した[「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ」](http://qiita.com/ikwzm/items/cc1bb33ff43a491440ea)で紹介した[udmabuf](https://github.com/ikwzm/udmabuf)は、もともと Xilinx 社の Zynq など ARM アーキテクチャ用に作ったものでした。
今回、ちょっとしたことがあって x86 アーキテクチャ用にコンパイルしてみたのですが、その際に判かった点を防備録として残しておきます。

## x86 アーキテクチャ用に修正した点 ##

### pgprot_dmacoherent() が未定義 ###

まず x86 アーキテクチャ用にコンパイルしてみて通らなかったところがここです。pgprot_noncached()、pgprot_writecombine()、pgprot_dmacoherent() は mmap() 時の CPU キャッシュのモードを設定する関数ですが、どうやら pgprot_dmacoherent() は ARM アーキテクチャのみ定義されているようで、x86 アーキテクチャでは未定義ということでエラーになりました。


### dma_alloc_coherent() がエラーを返す ###

とりあえず pgprot_dmacoherent() を使わないようにコードを修正したらコンパイルが通りました。しかし `shell% sudo insmod udmabuf.ko udmabuf0=4096` としてドライバをロードしたところ、dma_alloc_coherent() が NULL (つまりエラー)を返しました。

いろいろ調査したところ、dma_alloc_coherent() に渡す struct device *dev の 、dma_mask フィールドが 0 のためエラーになっているようでした。どうやら DMA を使うデバイスドライバはこのフィールドに値を設定しなければならなかったようです(よく今まで動いていたものです)。

注意点は dma_mask フィールドは `u64 *dma_mask` と定義されていて、64ビット変数へのポインタになっていることです。
次の様に設定することで対処しました。なお、dma_mask_bit 変数はデバイスドライバのロード時に引数として指定することができます。指定しなかった場合は 32 に設定されます。

```
    /*
     * setup dma mask 
     */
    {
        this->device->dma_mask = &this->dma_mask;
        if (dma_set_mask(this->device, DMA_BIT_MASK(dma_mask_bit)) == 0) {
            dma_set_coherent_mask(this->device, DMA_BIT_MASK(dma_mask_bit));
        } else {
            printk(KERN_WARNING "dma_set_mask(DMA_BIT_MASK(%d)) failed\n", dma_mask_bit);
            dma_set_mask(this->device, DMA_BIT_MASK(32));
            dma_set_coherent_mask(this->device, DMA_BIT_MASK(32));
        }
    }
```

### CPU キャッシュの設定を変えても速度が変わらなかった ###

udmabuf は デバイスファイルのオープン時に O_SYNC を設定することと /sys/class/udmabuf/<device-name>/sync_mode を変えることで CPU キャッシュの設定を変えることが出来るようになっています。[「ZYNQ(ARM Cortex-A9)でCPUのデータキャッシュの効果を確認する」](http://qiita.com/ikwzm/items/5c08e3e7b7077f1b13eb)参照。

ところが x86アーキテクチャでは何を変えても性能は変わりませんでした。どうやら CPU キャッシュの制御が出来ていないようです。

### dma_free_coherent() でカーネルがエラー(BUG レポート)を吐く ###

ARM アーキテクチャでは mmap() 時に CPU キャッシュの設定を変更するために、ページフォルト時に領域を割り当てるオンデマンド・ページングをしていたのですが、どうも x86 アーキテクチャではこれがうまくいかないようです。

そこで x86 アーキテクチャではオンデマンド・ページングを止めて、mmap() 時に dma_mmap_coherent() 関数を使うようにしました。もともとはこの関数を使うのが正規な方法ですが、実はこの関数を使って mmap() すると、CPU キャッシュの制御が出来なくなるのです。

前節でも示した通り、x86 アーキテクチャでは CPU キャッシュの制御が出来ないようなので、dma_mmap_coherent() を使うように変更しました。

## x86アーキテクチャ版の制限 ##

とりあえず x86 用にコンパイルして動かして見ましたが、ARM版に比べて以下の点に制限があります。

  * CPUキャッシュの制御が出来ない
  * デバイスツリーによる設定が出来ない
  * アクセラレーター側からアクセスできるか未検証

### CPUキャッシュの制御が出来ない ###

CPU キャッシュに関しては前節で説明した通りです。もう少し調査が必要です。

### デバイスツリーによる設定が出来ない ###

デバイスツリーに関しては、もともとデバイスツリーの機構自体が ARM アーキテクチャのためのものなので x86 では使えません。少々不便ですが、デバイスドライバをロードするときに渡す引数でバッファの大きさだけは指定出きるようにしています。

### アクセラレーター側からアクセスできるか未検証 ###

現時点では udmabuf が確保したバッファに CPU(アプリケーション)がアクセスするテストはしていますが、アクセラレータからのアクセスは未検証です。PCIe に接続できる FPGA ボードがあれば試してみたいのですが。



## 最後に ##

以上、udmabuf を x86 アーキテクチャで動かすためには、もう少し調査と検証が必要です。

(どなたか人柱になっていただければよろしいのですが。)

## 参考 ##

* [https://github.com/ikwzm/udmabuf](https://github.com/ikwzm/udmabuf)
* [「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ」@Qiita](http://qiita.com/ikwzm/items/cc1bb33ff43a491440ea)
* [「ZYNQ(ARM Cortex-A9)でCPUのデータキャッシュの効果を確認する」@Qiita](http://qiita.com/ikwzm/items/5c08e3e7b7077f1b13eb)