👏

ZynqMP用に Devicetree Overlay できる Dockerイメージを buildx で作ってみる

2022/06/22に公開

概要

ZynqMP には Cortex-A53 が搭載されていて Linux が動くわけですが、最近は arm64(aarch64)用の Docker も普及してきており大変便利です。

私は ZynqMP 上で OpenCV や riscv-gnu-toolchain を利用したりするのですが、SD カードを作り直すたびにコンパイルしていては大変ですので Docker イメージにしてしまうと便利そうです。

しかしながら、OpenCV のようなライブラリは、ビルド時だけでなく、実行時にも必要となるため、ビルド環境と実行環境の両方をこなせる必要があります。

その際、実行環境としては ZynqMP の PL に bitstream をダウンロードしたり、uio や u-dma-buf などを利用するために、Devicetree Overlay が必要ですし、OpenCV の 表示を X-Window で行ったりも必要です。

加えて、 SD カードと限られたメモリ内でのビルドはいろいろと制約があるので、Docker の buildx の機能で、ハイパフォーマンスなPC上でビルドしてしまおうというのがこの記事の趣旨です。

作成したビルド環境

今回作成した環境は こちらに置いております。

今回は Windows11 + WSL2(Ubuntu 20.04) の環境にて Docker Desktop を用いてビルドを行なっております。

Devicetree Overlay できるようにする

今回、一番重要なのが Devicetree Overlay できるようにする点ですが、これは Twitter 上で多くの方のお知恵をお借りして解決しました。

以下が、今回作成した docker-compose.yml ですが、

version: "3"

services:
  zynqmp_develop:
    build: .
    image: ryuz88/zynqmp_jelly
    container_name: zynqmp_jelly
    ports:
      - 20022:20022
    privileged: true
    volumes:
      - /home/${LOCAL_USER}:/home/${LOCAL_USER}
      - /lib/firmware:/lib/firmware
      - /configfs:/configfs
      - /dev:/dev
    environment:
      LOCAL_USER: ${LOCAL_USER}
      LOCAL_UID: ${LOCAL_UID}
      LOCAL_GID: ${LOCAL_GID}

このうち

    privileged: true

と volumes の

      - /lib/firmware:/lib/firmware
      - /configfs:/configfs
      - /dev:/dev

の部分が、最終的に Devicetree Overlay できるようにするのに必要だった項目です。

これでちゃんと、Devicetree Overlay で uio などを増やすと、/dev の下にデバイスが追加されてくれ、privileged によって、/sys/class 以下から情報を受け取ることも可能となるようです。

ホストと同じユーザーで作業できるようにする

これは、ビルド環境を作る場合の宿命的な部分でもありますが、前処理や後処理で他のツールと連携しないといけないことが多々あります。

ホストの home をマウントして、ホストと同じ権限でファイル操作できると何かと便利そうです。

方法はいくつかあるようですが、結局試してみて、Docker 内でホストと同じ UID、GID を持つユーザーを新規に作る方法に落ち着きました。

下記のような compose.sh という docker-compose を呼び出す前に変数設定するスクリプトを準備したうえで

set -eu
cat <<EOT > .env
LOCAL_USER=`whoami`
LOCAL_UID=`id -u`
LOCAL_GID=`id -g`
EOT

docker-compose $@

docker-compose.yml に

    environment:
      LOCAL_USER: ${LOCAL_USER}
      LOCAL_UID: ${LOCAL_UID}
      LOCAL_GID: ${LOCAL_GID}

や volumes 部分の

      - /home/${LOCAL_USER}:/home/${LOCAL_USER}

を加え、下記のような entrypoint.sh を

#!/bin/bash

USER_NAME=${LOCAL_USER:-user}
USER_ID=${LOCAL_UID:-9001}
GROUP_ID=${LOCAL_GID:-9001}

echo "Starting with UID : $USER_ID, GID: $GROUP_ID"
useradd -u $USER_ID -o -m $USER_NAME --shell /usr/bin/bash
groupmod -g $GROUP_ID $USER_NAME
echo "$USER_NAME:fpga" | chpasswd

gpasswd -a $USER_NAME sudo

/usr/sbin/sshd -D

Dokerfile の最後で

# entrypoint
COPY ./files/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

とすることで、ユーザーを作成してから sshd を起動するようにしております。

注意点はここでパスワードが固定で設定されてしまう点です。
開発環境がクローズな場合を想定しておりますので、そうでない場合は別途、パスワードの保護や、アクセス制限を行う工夫の追加をご検討ください。

sshd での接続であれば X11Forwarding が使えますので、X-Window を使った表示アプリの問題も解決できます。

buildx でのビルド

ビルド自体は Docker Desktop には buildx は含まれているようですので

ビルダーを作ったあと

docker buildx create custum-builder
docker buildx use custum-builder
./buildx.sh

とすれば zynqmp_jelly.tar が出来上がりますので、ZynqMP の環境にコピーして

docker load -i zynqmp_jelly.tar

のようにすればOKです。

もし私の環境をそのまま使いたい方は、現在の内容はこちらに push しておりますので、ZynqMP 側から

docker pull ryuz88/zynqmp_jelly:latest

としていただければ、利用可能になるはずです。

起動は、利用したいユーザーアカウントで

./compose.sh up -d

とするだけで、終了時は

./compose.sh down

とすれば、起動時したユーザー名でポート 20022 で ssh を待ち受けるコンテナが起動します。

その際、オリジナルの home をボリュームとして接続しますので .bashrc も今使っているものが読まれます。

Docker コンテナ内では /.dockerenv がありますので、下記のような感じの if を追加しておくとよいでしょう。

if [ -f /.dockerenv ]; then
  export PATH="/opt/opencv4/bin:$PATH"
  export PKG_CONFIG_PATH="/opt/opencv4/lib/pkgconfig:$PKG_CONFIG_PATH"
  export LD_LIBRARY_PATH="/opt/opencv4/lib:$LD_LIBRARY_PATH"

  export PATH="/opt/opencv3/bin:$PATH"
  export PKG_CONFIG_PATH="/opt/opencv3/lib/pkgconfig:$PKG_CONFIG_PATH"
  export LD_LIBRARY_PATH="/opt/opencv3/lib:$LD_LIBRARY_PATH"

  export PATH="/opt/grpc/bin:$PATH"
  export PATH="/opt/riscv/bin:$PATH"
fi

これで、SDカードを作り直すたびに OpenCV 等のビルドを行う状況から脱却できると嬉しいです。

同じような悩みを持つ方の参考になれば幸いです。

GitHubで編集を提案

Discussion