udmabuf を試してみる (Ultra96V2編)

# はじめに

 iwkzm氏の[udmabuf](https://github.com/ikwzm/udmabuf)を試してみたので、自分用備忘録です。

 なお、udmabuf のドライバ名は udmabuf → u-dma-buf と名称変更された経緯があり、古い環境を利用する方は読み替えて頂ければと思います。

[udmabuf を試してみる (ZYBO-Z7編)](https://qiita.com/Ryuz/items/80ea669b00176fb92a7f)とよく似た内容となっていますが、Device Tree Overlay のやり方などで Zynq-7000 と ZynqMP とで細かい部分で差異があるあるのでご注意ください。

# 事前準備

## 環境
今回の環境は下記の通り。

- [Ultra96V2](https://www.avnet.com/wps/portal/japan/products/product-highlights/ultra96/)
-  iwkzm氏の [Debianブートイメージ 2019.2版](https://qiita.com/ikwzm/items/92221c5ea6abbd5e991c)
- Vivado 2019.2

Debianイメージは一度起動SDを作ってしまえば Vivado だけでもいろいろできるのが素敵です。

## Ultra96v2側の準備

bootgen を使うのでインストールしておきます。

```
git clone https://github.com/Xilinx/bootgen  
cd bootgen/  
make  
sudo cp bootgen /usr/local/bin/
```

他にも make や dtc など使うので、不足があれば随時 sudo apt install してください。


## ソースコードの所在
今回は拙作の [Jelly](https://github.com/ryuz/jelly) の１プロジェクトとして追加しています。

```
git clone https://github.com/ryuz/jelly -b v2.0.2
```

で取得でき、[/projects/ultra96v2/ultra96v2_udmabuf_sample/](https://github.com/ryuz/jelly/tree/v2.0.2/projects/ultra96v2/ultra96v2_udmabuf_sample) 以下が今回のプロジェクトです。

# PL用bitstreamの作成
  PS用のbitstreamは PC(WindowsやLinuxなど)で Vivado を使って行います。

  Vivado のプロジェクトは
 /projects/ultra96v2_udmabuf_sample/syn/vivado2019.2
 のにあります。

 ultra96v2_udmabuf_sample.xpr が vivado2019.2 のプロジェクトファイルで、 Block Design は 同じディレクトリにある design_1.tcl に export しております。
（design_1.tcl を実行すれば Block Design が作れるはずですが、一度今あるものをプロジェクトから消してから tcl 実行が必要かもしれません。）

合成すると、ultra96v2_udmabuf_sample.bit が出来上がります。
このファイルを projects/ultra96v2_udmabuf_sample/app にコピーしておいてください。

なお、回路の方は本記事の本質ではないので飛ばしますが、重要な点としてメモリマップのみ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285930/9a08350d-5355-afee-14da-f0750ebb13cf.png)

PS部からは LPDDR4-SDRAM にアクセスするための 128bit幅 のAXI4のバススレーブと、0x00_A000_0000 番地からアサインした、ペリフェラルアクセス用の64bit幅のAXI4Liteのバスマスターを出しております。

あとは [ultra96v2_udmabuf_sample.v](https://github.com/ryuz/jelly/blob/master/projects/ultra96v2_udmabuf_sample/rtl/ultra96v2_udmabuf_sample.v) の方で WISHBONE バスに変換した後に下記のようになるようにアドレスを割り当てています。

- テスト用簡易DMA0  0x00_A000_0000 - 0x00_A000_07FF
- テスト用簡易DMA1  0x00_A000_0800 - 0x00_A000_0FFF
- LED制御レジスタ     0x00_A000_8000 - 0x00_A000_87FF

# ソフト側の作成と実行
  Ultra96V2側のソフトの開発です。
  projects/ultra96v2_udmabuf_sample/app を Ultra96 のどこか適当な箇所にコピーします。
  Ultra96V2側の作業は Debian のブートイメージで起動したあと、常に起動したまま行うことが可能で、運用したままPLとソフトをアップデートすることも可能なのがこのブートイメージの素晴らしいところです。

  Ultra96V2 の debian でも git は動きますので、こちらでも clone する手があります。
  (なお、この app ディレクトリ以下は VS code Remote Development を使ってセルフコンパイル開発してそのままpushしています。)

## とりあえず動かしてみる

sudoできるユーザーで app ディレクトリに移動してください。

```
make run
```

とすればひとまず動くように作っております。
途中、sudo コマンドを使っているのでパスワードを聞かれると思いますが入力ください。

# Device Tree overlay
  ここから本記事のメインの部分です。
  今回は Device Tree overlay によって

- PS部がPLに供給する fabric clock の設定
- PS部とPL部を繋ぐAXIバスのバス幅などの設定
- bitfile のダウンロード
- レジスタアクセスの為の uio の割り当て
- メモリ領域割り当ての為の udmabuf の割り当て

のなどの機能を担っています。

## dts ファイル
ここで [ultra96v2_udmabuf_sample.dts](https://github.com/ryuz/jelly/blob/master/projects/ultra96v2_udmabuf_sample/app/ultra96v2_udmabuf_sample.dts)ファイルの作成が重要となります。

順にみていきたいと思います。
なお、dtsファイルのコンパイルは、実行環境で行うことが必要なようです(内部で既存のDevice Treeのシンボルを参照する為)。

### bitstream 指定

``` 
    fragment@0 {
        target = <&fpga_full>;
        __overlay__ {
            #address-cells = <2>;
            #size-cells = <2>;
            firmware-name = "ultra96v2_udmabuf_sample.bit.bin";
        };
    };
```

上のように指定します。この時、ultra96v2_udmabuf_sample.bit.bin は bitstream から bootgen で生成されたファイルであり、/lib/firmware に置かれている必要があります。

bootgen の使い方としては、下記のような ultra96v2_udmabuf_sample.bif に対して

```ultra96v2_udmabuf_sample.bif
all:
{
    ultra96v2_udmabuf_sample.bit
}
```

bootgenを用いて

```
bootgen -image ultra96v2_udmabuf_sample.bif -arch zynqmp -process_bitstream bin
```

と実行することによって得られます。

### クロックと AXIのバス幅

```
 fragment@1 {
        target-path = "/amba_pl@0";
        
        #address-cells = <2>;
        #size-cells = <2>;
        __overlay__ {
            #address-cells = <2>;
            #size-cells = <2>;
            afi0 {
                compatible    = "xlnx,afi-fpga";
                config-afi    = <0 0>, 
                                <1 0>,
                                <2 0>, 
                                <3 0>,
                                <14 0x0500>;
               /* 0: S_AXI_HPC0_FPD(read)  : 0:128bit, 1:64bit, 2:32bit */
               /* 1: S_AXI_HPC0_FPD(write) : 0:128bit, 1:64bit, 2:32bit */
               /* 2: S_AXI_HPC1_FPD(read)  : 0:128bit, 1:64bit, 2:32bit */
               /* 3: S_AXI_HPC1_FPD(write) : 0:128bit, 1:64bit, 2:32bit */
               /* 14: M_AXI_HPM0_FPD[9:8], M_AXI_HPM0_FPD[11:10] 
                                           : 0:32bit, 1:64bit, 2:128bit */
            };
            clocking0: clocking0 {
                #clock-cells = <0>;
                assigned-clock-rates = <100000000>;
                assigned-clocks = <&zynqmp_clk 71>;
                clock-output-names = "fabric_clk";
                clocks = <&zynqmp_clk 71>;
                compatible = "xlnx,fclk";
            };
        };
```

の config-afi の部分が AXI バスのバス幅の設定です。
これは[こちらの記事](https://qiita.com/ikwzm/items/a1ad7e22ed7c44940d88)を参考にさせて頂きました。

また、clocking0 の部分がクロックで、pclk0 を 100MHz に設定しています。
これは[こちらの記事]([https://qiita.com/ikwzm/items/74f7c5b8474198c8af3e)を参考にさせて頂きました。

### uioとudmabuf

続いて uio と udmabuf です。

``` 
    fragment@2 {
        target-path = "/amba";
        __overlay__ {
            #address-cells = <0x1>;
            #size-cells = <0x1>;
            
            uio_pl_peri {
                compatible = "generic-uio";
                reg = <0xa0000000 0x08000000>;
            };

            udmabuf4 {
                compatible = "ikwzm,udmabuf-0.10.a";
                minor-number = <4>;
                size = <0x00400000>;
            };
        };
    };
``` 

今回はペリフェラル領域をまとめて一個の uio に割り当てています。
開始アドレス 0xa0000000番地から サイズ 0x08000000 バイトの領域が uio_pl_peri  という名前の uio として生成されます。

また udmabuf4 という名前で、0x00400000 バイトの CMA(Continuous Memory Allocator) を確保してもらうように指定しています。udmabuf を用いることで、連続した物理メモリアドレスを割り当ててもらうことが可能になります。

### dtcでのコンパイル

```
dtc -I dts -O dtb -o ultra96v2_udmabuf_sample.dtbo ultra96v2_udmabuf_sample.dts
```

とすることで ultra96v2_udmabuf_sample.dtbo を得ることができます。

## Overlay
いよいよ overlay です

### configfs の mount
初めに configfs をマウントします。

```
sudo mkdir -p /configfs
sudo mount -t configfs configfs /configfs
```

詳しくは[こちら](https://qiita.com/ikwzm/items/ec514e955c16076327ce)や[こちら](https://dora.bk.tsukuba.ac.jp/~takeuchi/?%E9%9B%BB%E6%B0%97%E5%9B%9E%E8%B7%AF%2Fzynq%2FDevice%20Tree%20Overlay)を参考にさせて頂いております。

### ファームウェアのコピー
必要なものを  /lib/firmware にコピーします。

```
sudo mkdir -p /lib/firmware
sudo cp ultra96v2_udmabuf_sample.bit.bin /lib/firmware
sudo cp ultra96v2_udmabuf_sample.dtbo /lib/firmware
```

### overlay 

次に overlay を行います。

```
sudo sh -c "echo 0 > /sys/class/fpga_manager/fpga0/flags"
sudo mkdir /configfs/device-tree/overlays/full
sudo sh -c "echo -n ultra96v2_udmabuf_sample.dtbo > /configfs/device-tree/overlays/full/path"
```

この段階で bitstream は書き込まれ、動作を開始しています。

### 状態確認
状態を確認するには

```
cat /configfs/device-tree/overlays/full/status
```

でできるようで applied と表示されればよいようです。

役目を終えたファイルは削除してよいようです。

```
sudo rm /lib/firmware/ultra96v2_udmabuf_sample.dtbo
sudo rm /lib/firmware/ultra96v2_udmabuf_sample.bit.bin
```

## アプリケーションの実行

ここでアプロケーションを実行します。
/dev 以下に uio や dmabuf に対応するデバイスがが追加されているはずなのでそれらを開いてアクセスすることができます。

このやり方は[別の記事](https://ryuz.hatenablog.com/entry/2021/04/03/201054)で紹介しております。

詳しくは[main.cpp](https://github.com/ryuz/jelly/blob/v2.0.2/projects/ultra96v2/ultra96v2_udmabuf_sample/app/main.cpp)をお読みください。

うまく動けば、udmabuf領域にPLのコアからと、Cortex-A53 の双方からアクセスして、データがやり取りできることが確認できます。
また、uio にマップした RADIO_LED もソフトウェアから点滅させています。

## Device Tree Overlay の解除
すこし自信のないところなのですが

```
sudo rmdir /configfs/device-tree/overlays/full
```

と削除すると、解除できるようです。


# おしらせ
2020/10/30 にて Qrunch がサービス終了したので移転してきた記事となります。
一時退避場所：https://github.com/ryuz/qrunch_blog