NVIDIA SDK Manager docker + flash.sh によるL4TとJetson SDK Componentsインストール

# はじめに

NVIDIA SDK Manager dockerを使用し、以下3点をインストールする方法を紹介します。

・L4TのLinux Kernelに変更を加えビルドした`.img`ファイル(カスタムイメージと呼びます)
・Jetsonから読み取ったカスタムイメージ
・Jetson SDK Components

# セットアップ環境の前提

ホストコンピュータは下記の記事でセットアップしたものを使用します。

https://qiita.com/triple3packun/items/69b5e08876245976c713

# カスタムイメージのインストール準備

カスタムイメージは、L4T Driver Package に含まれる `flash.sh` を使用しJetsonにインストールしますが、`flash.sh` はSDK Managerのプログラムを利用します。過去のバージョンのSDK ManagerはSDK ManagerをインストールするPC(ホストコンピュータと呼びます)のビデオドライバーやCUDAのバージョンをアップデートしなければならない仕様だったことが私のトラウマ(笑)になっており、ホストコンピュータに直接SDK Managerをインストールしたくありません。よって、SDK Manager Dockerを使用します。

## SDK Manager Docker

SDK ManagerのDockerイメージはNVIDIAから配布されていますが、`flash.sh` を使用するにはひと手間が必要です。

まず、公式からDockerイメージをダウンロードします。注意点は次のとおりです。

* 古いSDK Managerはユーザ認証に失敗するため、新しいバージョンをダウンロードします
* イメージのUbuntuバージョンは、インストールするLinux KernelとJetPackのインストールに対応したものを選択します(ダウンロードページに対応表があります)

https://developer.nvidia.com/sdk-manager

ホストコンピュータにDockerイメージをロードします。

:::note info
`.tar.gz`のファイル名は、ダウンロードしたものに置き換えます。
:::

```bash
docker load -i ~/Downloads/sdkmanager-2.1.0.11660-Ubuntu_18.04_docker.tar.gz
```

SDK Managerは、インストールファイルをビルドする際、Jetsonハードウェアの仮想化にQEMUを使用します。しかし、Jetsonのアーキテクチャであるaarch64のプロファイルがSDK Manager Dockerになく、プロファイルをイメージに加えます。また、ホストコンピュータがプロキシを経由してインターネットへ接続する環境下にあり、JetPackをダウンロードするためにプロキシサーバの設定を加えます。これらにDocker composeを使用します。

以下のファイルを準備します。

```file.system
docker_sdkmanager
├── Dockerfile.sdk
├── build_sdk.sh
├── container_bind
│   ├── qemu-aarch64
│   └── setup_sdk.sh
└── docker-compose.yaml
```

:::note info
`sdkmanager:2.1.0.11660-Ubuntu_18.04` はダウンロードしたイメージファイルに変更します。
:::


```Dockerfile.sdk
FROM sdkmanager:2.1.0.11660-Ubuntu_18.04
 
ARG HTTP_PROXY
ARG HTTPS_PROXY
ARG L4T_WORKDIR
ARG CONTAINER_BIND
 
ENV http_proxy ${HTTP_PROXY}
ENV https_proxy ${HTTPS_PROXY}
ENV L4T_WORKDIR ${L4T_WORKDIR}
ENV CONTAINER_BIND ${CONTAINER_BIND}
 
RUN sudo touch /etc/apt/apt.conf.d/90proxy && \
    sudo echo 'Acquire::http::proxy "'${http_proxy}'";' | sudo tee -a /etc/apt/apt.conf.d/90proxy > /dev/null && \
    sudo echo 'Acquire::https::proxy "'${https_proxy}'";' | sudo tee -a /etc/apt/apt.conf.d/90proxy > /dev/null && \
    sudo echo 'Acquire::ftp::proxy "'${http_proxy}'";' | sudo tee -a /etc/apt/apt.conf.d/90proxy > /dev/null && \
    sudo echo 'Acquire::socks::proxy "'${http_proxy}'";' | sudo tee -a /etc/apt/apt.conf.d/90proxy > /dev/null

RUN sudo apt-get update -y
RUN sudo apt-get install -y --no-install-recommends \
    qemu-user-static \
    binfmt-support \
    binutils \
    wget

RUN wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
RUN chmod +x qemu-binfmt-conf.sh
RUN sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian

ENTRYPOINT []
```

:::note info
公式のDockerイメージは ENTRYPOINTに `sdkmanager --cli` が指定されていますが、`flash.sh` なども使用するためENTRYPOINTを無しにします。
:::


```build_sdk.sh
#!/bin/bash

docker-compose build \
    --build-arg HTTP_PROXY=${http_proxy} \
    --build-arg HTTPS_PROXY=${https_proxy} \
    using_jetpack

docker-compose run --name using_jetpack_${USER} using_jetpack /bin/bash -c '${CONTAINER_BIND}/setup_sdk.sh'
docker container commit using_jetpack_${USER} jetpack_setup
docker container rm using_jetpack_${USER}
```

```qemu-aarch64.
package qemu-user-static
interpreter /usr/bin/qemu-aarch64-static
flags: OC
offset 0
magic \x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00
mask \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff
```

```setup_sdk.sh
#!/bin/bash

sudo mkdir -p ${L4T_WORKDIR}
sudo chown nvidia:root ${L4T_WORKDIR}

sudo cp "${CONTAINER_BIND}/qemu-aarch64" /usr/share/binfmts/
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
sudo update-binfmts --enable qemu-aarch64
```

```docker-compose.yaml
version: "3.7"
 
services:
  using_jetpack:
    image: jetpack_setup
    privileged: true
    build: 
      context: ./
      dockerfile: ./Dockerfile.sdk
      args:
        - HTTP_PROXY=${http_proxy}
        - HTTPS_PROXY=${https_proxy}
        - L4T_WORKDIR=/tmp/nvidia/sdkm_downloads
        - CONTAINER_BIND=/var/container_bind
    volumes:
      - type: bind
        source: /dev
        target: /dev
      - type: bind
        source: /dev/bus/usb
        target: /dev/bus/usb
      - type: bind
        source: /media/${USER}
        target: /media/nvidia:slave
      - type: volume
        source: jetpack_temporary
        target: /tmp/nvidia
      - type: bind
        source: ./container_bind
        target: /var/container_bind
    network_mode: host

volumes:
  jetpack_temporary:
```

Dockerイメージをビルドします。

```bash
chmod +x build_sdk.sh ./container_bind/setup_sdk.sh
./build_sdk.sh
```

## JetPack Components

通常、SDK Managerを実行すると、NVIDIAサイトのログインを要求されます。これは、JetPack Componentsをダウンロードするためですが、一度ダウンロードしてしまえば使い回しができ、インストールする度にログインする必要がありません。よって、JetPack Componentsをダウンロードしておきます。ダウンロード先はDockerボリュームを指定するため、ダウンロードファイルはDockerコンテナを停止しても残存します。リフレッシュしたい場合は、Dockerボリュームを削除するだけです。

```bash
JETPACK_VERSION='4.5.1'
TARGET_HARDWARE='JETSON_XAVIER_NX'
docker-compose run --rm using_jetpack sdkmanager \
    --cli \
    --action downloadonly \
    --download-folder /tmp/nvidia/sdkm_downloads \
    --archived-versions \
    --login-type devzone \
    --product Jetson \
    --target-os Linux \
    --version ${JETPACK_VERSION} \
    --target ${TARGET_HARDWARE} \
    --flash skip \
    --license accept \
    --stay-logged-in false \
    --collect-usage-data disable \
    --exit-on-finish
```

:::note info
- この時点で、SDK ManagerからJetsonにJetPackをインストールする環境が整います。L4TのLinux Kernelを含めたJetPackをフルインストールする場合は、リカバリーモードでJetsonを起動し、USBケーブルでJetsonとホストコンピュータを接続し、以下のコマンドを実行します

    ```bash
    docker-compose run --rm using_jetpack sdkmanager \
        --cli \
        --action install \
        --check-for-updates false \
        --login-type offline \
        --download-folder /tmp/nvidia/sdkm_downloads \
        --archived-versions \
        --product Jetson \
        --target-os Linux \
        --version ${JETPACK_VERSION} \
        --target ${TARGET_HARDWARE} \
        --flash all \
        --license accept \
        --stay-logged-in false \
        --collect-usage-data disable \
        --exit-on-finish
    ```
    
- SDK Managerのオプションは頻繁に変わるため、オプションは公式で調べます
    
    https://docs.nvidia.com/sdk-manager/archives/index.html
    
    バージョンを選択 > `Install with the Command Line` > `General Arguments`

- SDK Managerは基本的に最新バージョンと1つ前のJetPackに対応していますが、`--archived-versions` オプションを加える事で過去のバージョンも利用可能になります
:::

## L4T Driver Package & Sample RootFs

公式からL4T Driver PackageとSample RootFsをダウンロードします。

https://developer.nvidia.com/embedded/jetson-linux-archive

以下のファイルを準備します。

:::note info
- `Tegra186_Linux_R32.5.1_aarch64.tbz2` はダウンロードしたL4T Driver Packageに置き換えます
- `Tegra_Linux_Sample-Root-Filesystem_R32.5.1_aarch64.tbz2` はダウンロードしたSample RootFsに置き換えます
- `docker-compose.yaml` は `using_flash` サービスを追記します
:::

```file.system
docker_sdkmanager
├── Dockerfile.l4t
├── Dockerfile.sdk
├── build_l4t.sh
├── build_sdk.sh
├── container_bind
│   ├── Tegra186_Linux_R32.5.1_aarch64.tbz2
│   ├── Tegra_Linux_Sample-Root-Filesystem_R32.5.1_aarch64.tbz2
│   ├── qemu-aarch64
│   ├── setup_l4t.sh
│   └── setup_sdk.sh
└── docker-compose.yaml
```

```Dockerfile.l4t
FROM jetpack_setup
 
ARG L4T_DRIVERS
ARG L4T_SAMPLE_ROOTFS
ARG L4T_BOARD
ARG L4T_PARTITION

ENV L4T_DRIVERS ${L4T_DRIVERS}
ENV L4T_SAMPLE_ROOTFS ${L4T_SAMPLE_ROOTFS}
ENV L4T_BOARD ${L4T_BOARD}
ENV L4T_PARTITION ${L4T_PARTITION}

ENTRYPOINT []
```

```build_l4t.sh
#!/bin/bash

if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
    echo "Error: Missing arguments."
    echo "Usage: $0 <l4t_drivers> <l4t_sample_rootfs> <board> <partition>"
    echo "Example(Product Module): $0 'Tegra186_Linux_R32.5.1_aarch64.tbz2' 'Tegra_Linux_Sample-Root-Filesystem_R32.5.1_aarch64.tbz2' 'jetson-xavier-nx-devkit-emmc' 'mmcblk0p1'"
    echo "Example(Devkit): $0 'Tegra186_Linux_R32.5.1_aarch64.tbz2' 'Tegra_Linux_Sample-Root-Filesystem_R32.5.1_aarch64.tbz2' 'jetson-xavier-nx-devkit' 'mmcblk0p1'"
    exit 1
fi

docker-compose build \
    --build-arg L4T_DRIVERS=$1 \
    --build-arg L4T_SAMPLE_ROOTFS=$2 \
    --build-arg L4T_BOARD=$3 \
    --build-arg L4T_PARTITION=$4 \
    using_flash

docker-compose run --name using_flash_${USER} using_flash /bin/bash -c '${CONTAINER_BIND}/setup_l4t.sh'
docker container commit using_flash_${USER} l4t_setup
docker container rm using_flash_${USER}
```

```setup_l4t.sh
#!/bin/bash

echo -n 'unpack l4t drivers and sample rootfs ...'
sudo tar xpf "${CONTAINER_BIND}/${L4T_DRIVERS}" -C "${L4T_WORKDIR}/"
echo -n ' doing ...'
sudo tar xpf "${CONTAINER_BIND}/${L4T_SAMPLE_ROOTFS}" -C "${L4T_WORKDIR}/Linux_for_Tegra/rootfs/"
echo ' done'

cd ${L4T_WORKDIR}/Linux_for_Tegra

sudo ./apply_binaries.sh
sudo ./flash.sh --no-flash ${L4T_BOARD} ${L4T_PARTITION}
```

```docker-compose.yaml
version: "3.7"
 
services:
  using_jetpack:
    image: jetpack_setup
    privileged: true
    build: 
      context: ./
      dockerfile: ./Dockerfile.sdk
      args:
        - HTTP_PROXY=${http_proxy}
        - HTTPS_PROXY=${https_proxy}
        - L4T_WORKDIR=/tmp/nvidia/sdkm_downloads
        - CONTAINER_BIND=/var/container_bind
    volumes:
      - type: bind
        source: /dev
        target: /dev
      - type: bind
        source: /dev/bus/usb
        target: /dev/bus/usb
      - type: bind
        source: /media/${USER}
        target: /media/nvidia:slave
      - type: volume
        source: jetpack_temporary
        target: /tmp/nvidia
      - type: bind
        source: ./container_bind
        target: /var/container_bind
    network_mode: host

  using_flash:
    image: l4t_setup
    privileged: true
    build: 
      context: ./
      dockerfile: ./Dockerfile.l4t
      args:
        - L4T_DRIVERS
        - L4T_SAMPLE_ROOTFS
        - L4T_BOARD
        - L4T_PARTITION
    volumes:
      - type: bind
        source: /dev
        target: /dev
      - type: bind
        source: /dev/bus/usb
        target: /dev/bus/usb
      - type: bind
        source: /media/${USER}
        target: /media/nvidia:slave
      - type: volume
        source: jetpack_temporary
        target: /tmp/nvidia
      - type: bind
        source: ./container_bind
        target: /var/container_bind
    network_mode: host

volumes:
  jetpack_temporary:
```

Dockerイメージをビルドします。

```bash
chmod +x build_l4t.sh ./container_bind/setup_l4t.sh
./build_l4t.sh 'Tegra186_Linux_R32.5.1_aarch64.tbz2' 'Tegra_Linux_Sample-Root-Filesystem_R32.5.1_aarch64.tbz2' 'jetson-xavier-nx-devkit' 'mmcblk0p1'
```

これで、NVIDIA公式のL4Tをインストールする準備が整いました。あとは、手を加えたL4Tの `.img` や、Jetsonから読み取った `.img` ファイルに置き換えます。`.img` や L4T Driver Packageなどは、Dockerコンテナから参照できる `/tmp/nvidia/Linux_for_Tegra` にあります。例えば、`custom.img` に置き換える場合は次のように実行します。JetPack Components同様、Jetsonにインストールするファイルや `custom.img` はDockerボリュームに保存されるため、Dockerコンテナを停止しても残存します。

```bash
docker-compose run --rm using_flash /bin/bash -c 'cd "${L4T_WORKDIR}/Linux_for_Tegra" && sudo cp "${CONTAINER_BIND}/custom.img" ./bootloader/system.img'
```

:::note info
`L4T_WORKDIR` 等の変数はビルドの際Dockerイメージに組み込んでいます。
:::

# L4T インストール

リカバリーモードでJetsonを起動し、JetsonとホストコンピュータをUSBケーブルで接続し、`flash.sh` を実行します。

```bash
docker-compose run --rm using_flash /bin/bash -c 'cd "${L4T_WORKDIR}/Linux_for_Tegra" && sudo ./flash.sh ${L4T_BOARD} ${L4T_PARTITION}'
```

# Jetson SDK Components インストール

Jetson SDK ComponentsはJetPack Componentsの一部であり、Deep Learningや画像処理などGPGPUの恩恵を受けられる計算においてJetsonのリソースを効率よく使用するためのコンポーネントや、Jetson上の開発を支援するツール等がパッケージされたものです。`flash.sh` は、L4TをJetsonにインストールするスクリプトであり、Jetson SDK Componentsを使用する場合別途インストールしなければなりません。ただし、Jetsonから読み取った `.img` をインストールした場合は、読み取る前にインストールしたJetson SDK Componentsなどのパッケージが含まれています。

```bash
docker-compose run --rm using_jetpack sdkmanager \
    --cli \
    --action install \
    --check-for-updates false \
    --login-type offline \
    --download-folder /tmp/nvidia/sdkm_downloads \
    --archived-versions \
    --product Jetson \
    --target-os Linux \
    --version ${JETPACK_VERSION} \
    --deselect 'Jetson OS' \
    --select 'Jetson SDK Components' \
    --target ${TARGET_HARDWARE} \
    --flash all \
    --license accept \
    --stay-logged-in false \
    --collect-usage-data disable \
    --exit-on-finish
```

# おまけ Jetsonからsystem.imgを読み取る方法

JetsonのメインストレージであるeMMC/SD-Cardなどは、bootloaderやDeviceTree、APP等のパーティションがあり、ここではルートファイルシステムが記憶されているAPPパーティションのみを読み取ります。
リカバリーモードでJetsonを起動し、USBケーブルでJetsonとホストコンピュータを接続し、`flash.sh` を実行します。

```bash
docker-compose run --rm using_flash /bin/bash -c 'cd "${L4T_WORKDIR}/Linux_for_Tegra" && sudo ./flash.sh -r -k APP -G custom.img ${L4T_BOARD} ${L4T_PARTITION}'
 ```

# 参考文献

https://forums.developer.nvidia.com/t/sdkmanager-fails-with-chroot-failed-to-run-command-dpkg-exec-format-error/188560/6

https://qiita.com/katsusuke/items/39dbc42a0fd1664c5590

https://forums.developer.nvidia.com/t/sdkmanager-fails-with-chroot-failed-to-run-command-dpkg-exec-format-error/188560/5

https://forums.developer.nvidia.com/t/installation-fails-on-wsl/274008/23