Zynq + Yocto で USB デバイスをつくってみた


Zynq のボードに Linux 4.4 をいれて、USB デバイスにしてみた。どのバージョンからか知らないけど、USB Gadget 周りが劇的に変わっていてちょっと苦労した。

USB ULPI が変わっていた

Zynq の USB のチップの ChipIDEA というコアは ULPI Viewport をもっている。基本的に Generic な ULPI で OK なはずだが、どういうわけか新しい機構が提供されている。

usb-nop-xceiv.txt

ということでこれを利用するらしい。

ただし、ZedBoard ではうまく動かなかった。ZedBoard では昔ながら(?)の ULPI で動作する。

function がフレキシブルになっている

Linux の g_ether とか g_cdc とかは Legacy 扱いになってそもそも modprobe も insmod もできなかった(今考えると、フォースモードとかでインサートできたのかも。ko は出来ていたので)

変わりすぎていてここが大変だった。まぁ、g_cdc に比べると、フレキシブルになってよかったという気もする。おそらく、USB Device を作っている業者が後押ししているのでしょう。

Yocto の環境構築

本題ではないので簡単に。
次の poky と meta-* を用意した。branch をちゃんと合わせないと python のコンパイルでエラーになるので気を付けよう。使用した branch は krogoth。
- poky.git
- meta-xilinx
- meta-openembedded(option)

私の場合、一旦 git clone --bare でサーバに bare な状態においておき、さらにそこから git clone -b krogoth みたいなことをしている。複数の build 環境で異なる branch を使うことがあるため。

u-boot をつくる、そしてテスト

MACHINE を適切に設定すれば、通常の Yocto の作り方でうまくいくはず。たとえば、zedboard-zynq7 とか。Vivado でつくた ps7_init_gpl.[ch] を取り込む方法もあるようだ。今回実は、特別なボードに移植したので、最初 Vivado でプロジェクトを作って Export Hardware... で上記ソースを生成し、わざわざ bbappend で(Yocto的な)修正を掛けた。普通はそこまでやる必要はない。

u-boot をつくるとイメージに boot.bin (ps7_init_gpl.[ch]を含む小さなboot用ファイル)と u-boot.img ができるので、最小限この2つが SD カードにあれば u-boot は動く。

Zynq> usb start

とすれば usb の host としての動作確認ができる。

Linux もつくる

Yocto にお任せすれば、Linux もできる、、、はず。んが、ZedBoard ではうまくいかなかった。

.config
CONFIG_USB_ULPI=y

を有効にすることで ZedBoard はうまくいった。そして、 DTS はデフォルトのまま Viewport を使う。

zynq-zed.dts
    usb_phy0: phy0 {
        compatible = "ulpi-phy";
        #phy-cells = <0>;
        reg = <0xe0002000 0x1000>;
        view-port = <0x0170>;
        drv-vbus;
    };

Zybo は試していないが、そのままうまくいはず (CONFIG_USB_ULPIは必要ない)。参考までに dts を掲載。

zynq-zybo.dts
    usb_phy0: phy0 {
        #phy-cells = <0>;
        compatible = "usb-nop-xceiv";
        reset-gpios = <&gpio0 46 1>;
    };

本当は Linux の usb-nop-xceiv.txt にあるように、レギュレータとか設定しないといけないみたい。そうしないと、立ち上げ時に不穏なメッセージが出る。逆にいうと、これが見れればちゃんと動いているともいえる。

console_log
[    1.194235] e0002000.usb supply vbus not found, using dummy regulator

ここまでちゃんとできれば USB の host としてうまく動くはずだ。

USB Device として動作させる

USB の Device として動作させるには dts を書き直す必要がある。dr_mode を変更する。次に例を示す。

zynq-kiss4.dts
&usb0 {
    status = "okay";
    dr_mode = "peripheral";
    usb-phy = <&usb_phy0>;
};

RNDIS を使ってみる

.config に RNDIS を追加して動的モジュールを Yocto に作ってもらう。menuconfig を使うとメニューから修正できる。

bitbake linux-xlnx -c menuconfig

USB Functions confiurable through configfs を選ぶのがコツ。ここでは RNDIS 以外に CDC の ACM と ECM も選んでいる。

モジュールをインストール

Yocto でできた module を(たぶん modules--zynq7.tgz という名前だ)SD カードにいれて Linux を起動。起動後、SD カードをマウントして root (/) で tar xf で展開する。 /lib/modules 配下にファイルが解凍されているはず。それらをまずはインストール。

# cd /
# mount /dev/mmcblk0p1 /mnt
# tar xf /mnt/modules-kiss4-zynq7.tgz

# cd /lib/modules/4.4.0-xilinx/kernel
# insmod fs/configfs/configfs.ko
# insmod drivers/usb/gadget/libcomposite.ko
# insmod drivers/usb/gadget/function/u_ether.ko
# insmod drivers/usb/gadget/function/usb_f_rndis.ko

# lsmod
Module                  Size  Used by
usb_f_rndis            11116  0
u_ether                 8981  1 usb_f_rndis
libcomposite           32991  1 usb_f_rndis
configfs               21669  3 usb_f_rndis,libcomposite

USB のコンフィギュレーションを設定

configfs のおかげで USB Device の設定がかなりフレキシブルになった。以下、設定例。idVendor は気を付けよう。今回は某社のを拝借。なお、/sys/class/udc にデバイス名が表示されない場合は設定などがうまくいっていない。dts などを確認しよう。

# cd /
# mount -t configfs none /sys/kernel/config
# cd /sys/kernel/config/usb_gadget/
# mkdir g1
# cd g1
# echo "64" > bMaxPacketSize0
# echo "0x200" > bcdUSB
# echo "0x100" > bcdDevice
# echo "0x0b7e" > idVendor
# echo "0x0001" > idProduct
# mkdir functions/rndis.rn0
[  958.870959] using random self ethernet address
[  958.875462] using random host ethernet address
# mkdir configs/c1.1
# ln -s functions/rndis.rn0 configs/c1.1/
# ls /sys/class/udc/
ci_hdrc.0
# echo "ci_hdrc.0" > UDC
[  996.467049] usb0: HOST MAC f2:fb:88:86:26:4e
[  996.471450] usb0: MAC f6:f4:e5:6e:d7:41

IP アドレスを付与して Linux と対向

本当は Windows と対抗した方がよい(NDIS だけに)。

まずは device 側

# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr F6:F4:E5:6E:D7:41
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

# ifconfig usb0 192.168.105.1 up
[ 1318.396969] IPv6: ADDRCONF(NETDEV_UP): usb0: link is not ready
# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr F6:F4:E5:6E:D7:41
          inet addr:192.168.105.1  Bcast:192.168.105.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

そして、PC Linux 側。これは Ubuntu の Desktop だったので、GUI で設定。(ターミナルで設定すると途中で強制的に落とされるため)

接続後の Device 側の様子。ping が通り、見事 ssh が connection refused されています。

[ 1434.717576] configfs-gadget gadget: full-speed config #1: c1
[ 1434.723350] IPv6: ADDRCONF(NETDEV_CHANGE): usb0: link becomes ready
[ 1435.836948] configfs-gadget gadget: high-speed config #1: c1
# ping -c 3 192.168.105.2
PING 192.168.105.2 (192.168.105.2): 56 data bytes
64 bytes from 192.168.105.2: seq=0 ttl=64 time=1.238 ms
64 bytes from 192.168.105.2: seq=1 ttl=64 time=0.715 ms
64 bytes from 192.168.105.2: seq=2 ttl=64 time=0.695 ms

--- 192.168.105.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.695/0.882/1.238 ms
# ssh 192.168.105.2
ssh: connect to host 192.168.105.2 port 22: Connection refused

おわりに

本当は Zynq 側に dhcp のサーバを立てて(そうすれば Windows でも使えるはず)ウェブサーバでも立ち上げれば見栄えがするようになるだろう。Enjoy your FPGA life!!