ZYNQ UltraScale+でのUIOの使い方
LinuxにはUIO(User Space I/O)という何でもやりたい放題のデバイスドライバがあります。このドライバを使うとユーザモードのプログラムからCPUの物理アドレスでI/Oを発行できるので、FPGAの中に自分で作った回路を動かしたり、FPGAの周辺レジスタを操作したりするのにとても便利です。
UIOの使い方は「UIOの使い方」の記事に書いたとおりで、amba {という括りの中に
名前@アドレス { compatible = "generic-uio"; reg = <先頭アドレス 長さ>};
を書けばよいという非常にシンプルなものです。
そして、bootargsの中に
uio_pdrv_genirq.of_id=generic-uio
を書けば起動時に読み込まれますが、同じことをZYNQ UltraScale+でやるとうまくいかなかったのです!
悩んだ末、この原因がわかりました。
まず、ZYNQ UltraScale+では何もしなくてもuio0~uio4までが勝手に登録されてしまっています。
cat /sys/class/uio/uio0/name
をやって、このUIOが何なのかを調べてみると、axi-pmonとかxilinx_apmが出てくるので、XILINXのPerformance Monitorが既にUIOを使っています。最初はこのUIOが邪魔して自分のUIOが登録されないのかと思ったのですが、そうではありませんでした。
原因は簡単なことで、Linuxのカーネル5.15.0ではUIOがデフォルトではMになっていたのです。
これを*にしてビルドしなおします。Mというのはカーネルモジュールだからinsmodで読み込ませれば使えるはずなんですが、そういうことはしばらく触っていないとやり方をすっかり忘れてしまいます。ですからカーネルをビルドしなおした方が早い。
なお、Linux(WSL)の起動から、環境変数の設定、そしてmenuconfigを出してビルドするときのコマンドラインは、
. /tools/Xilinx/SDK/2018.3/settings64.sh
export CROSS_COMPILE=aarch64-linux-gnu-
make ARCH=arm64 menuconfig
make ARCH=arm64 UIMAGE_LOADADDR=0x8000
mkimage -A arm64 -O linux -C none -T kernel -a 0x3000000 -e 0x3000000 -d arch/arm64/boot/Image uImage
です。忘れたときの備忘録に。
これでDeviceTreeにuioのエントリを書いてあげると、見事に認識されるようになった・・・のですが、Linuxの起動の途中で固まるようになってしまいました。
AXI Performance MonitorもUIOを使っているので、干渉してしまうのでしょう。
まず、現在のデバイスツリーをソースに戻して編集します。
$ dtc -I dtb -O dts /mnt/hoge.dtb -o devicetree.dts
$ emacs devicetree.dts
自分のUIOのエントリはambaの先頭に書くのではなく、
最後の部分に書けばよいようです。
こうすれば起動時に固まりません。
自分のエントリは
my-reg@a0000000 {
compatible = "generic-uio";
reg = <0x0 0xa0000000 0x0 0x10000>;
};
というふうに書きます。
最初のmy-regは名前です。@a0000000は先頭アドレスで単なる名前ではないようなので、正しいアドレスを書かないといけません。
次の
reg = <0x0 0xa0000000 0x0 0x10000>;
は欲しいレジスタの先頭アドレスとサイズです。なぜ4つあるのかというと64bitプロセッサでアドレス空間が64bitあるからです。0x00000000-a0000000から0x00000000-00010000バイトを要求という意味なのだろうと思います。
このアドレスとサイズのサイズは、amba {の先頭で、
#address-cells = <0x2>;
#size-cells = <0x2>;
と定義されているので、この2というのはアドレスで2ワード、サイズで2ワードという意味なのでしょう。Zynq7000のときは<>の中は2つだったけど、UltraScale+になってからは4つになったのはそういう理由なんだろうと思います。正直、デバイスツリーについてはよくわからずに勘と勢いでやっています。
編集が終わったら
# dtc -I dts -O dtb devicetree.dts -o /mnt/hoge.dtb
でバイナリに戻せば書き換わります。
uioは全部で5個になっていて、自分のuioは0番になっています。
デバイスツリーでは最後に書いたものが最も番号が若くなるのでしょうか?それとも、uioとperfmonitorとのロードするタイミングの差でしょうか。bootargsに書いたから最初にロードされるのかもしれませんね。
とにかくLinuxカーネルにUIOを認識することができました。
そうしたらFPGAのデザインを作ります。ZYNQ USPのM-AXI-HPM0_FPDにSMC経由でAXI-GPIOをつなぎます。
AXI GPIOの入力ポートと出力ポートはバラして、出力ポートはLEDに、入力ポートは32bitのconstant値を与えています。このconstant値を読みだせるというデザインはバージョン番号の管理などに便利です。
AXI-GPIOのアドレスはA0000000から割り当てます。
これでFPGAの準備は万端です。
以下のような簡単なプログラムでLEDを操作したり、constantに書いた32bitの数値を読みだすことができるようになりました。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>
int main() {
int fd_regs = open("/dev/uio0",O_RDWR);
unsigned long *regs;
printf("UIO driver test\n");
if(!fd_regs) {
printf("Can not open /dev/uio0\n");
return 1;
}
regs = (unsigned long *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd_regs, 0);
for(int i=0;i<6;i++) {
printf("FPGA reg(%x) data=%08lx\n",i,regs[i]);
}
regs[0] = 0x5;
regs[1] = 0xffffffff;
munmap(regs,0x1000);
close(fd_regs);
return 0;
}
自分で作ったハードウェアをソフトから叩けるようになりました。
いやあ、本当にUIOって素晴らしいですね。
| 固定リンク
コメント