KV260 の QSPI フラッシュメモリをのぞいてみる

## はじめに

Kria KV 260 Vision AI Starter Kit (以下 KV260) の System On Module(以下 SOM) 上には QSPI に接続されたフラッシュメモリがあります。[『KV260 が Linux をブートするまでのシーケンス』](https://qiita.com/ikwzm/items/aca938c98b636c0048bc) で説明したように、KV160 のプライマリーブートデバイスは、この QSPI 上のフラッシュメモリです。この記事では、このフラッシュメモリを Linux から読む方法について説明します。

## QSPI のデバイスツリー

KV260 の QSPI 関連のデバイスツリーは次のようになっています。

```devicetree:devicetree-5.10.120-zynqmp-fpga-generic-kv260-revB.dts

		spi@ff0f0000 {
			u-boot,dm-pre-reloc;
			compatible = "xlnx,zynqmp-qspi-1.0";
			status = "okay";
			clock-names = "ref_clk" "pclk";
			interrupts = <0x00 0x0f 0x04>;
			interrupt-parent = <0x04>;
			num-cs = <0x01>;
			reg = <0x00 0xff0f0000 0x00 0x1000 0x00 0xc0000000 0x00 0x8000000>;
			#address-cells = <0x01>;
			#size-cells = <0x00>;
			#stream-id-cells = <0x01>;
			iommus = <0x0d 0x873>;
			power-domains = <0x0c 0x2d>;
			clocks = <0x03 0x35 0x03 0x1f>;
			is-dual = <0x00>;
			spi-rx-bus-width = <0x04>;
			spi-tx-bus-width = <0x04>;
			phandle = <0x6d>;

			flash@0 {
				compatible = "mt25qu512a" "jedec,spi-nor";
				#address-cells = <0x01>;
				#size-cells = <0x01>;
				reg = <0x00>;
				spi-tx-bus-width = <0x01>;
				spi-rx-bus-width = <0x04>;
				spi-max-frequency = <0x2625a00>;

				partition@0 {
					label = "Image Selector";
					reg = <0x00 0x80000>;
					read-only;
					lock;
				};

				partition@80000 {
					label = "Image Selector Golden";
					reg = <0x80000 0x80000>;
					read-only;
					lock;
				};

				partition@100000 {
					label = "Persistent Register";
					reg = <0x100000 0x20000>;
				};

				partition@120000 {
					label = "Persistent Register Backup";
					reg = <0x120000 0x20000>;
				};

				partition@140000 {
					label = "Open_1";
					reg = <0x140000 0xc0000>;
				};

				partition@200000 {
					label = "Image A (FSBL, PMU, ATF, U-Boot)";
					reg = <0x200000 0xd00000>;
				};

				partition@f00000 {
					label = "ImgSel Image A Catch";
					reg = <0xf00000 0x80000>;
					read-only;
					lock;
				};

				partition@f80000 {
					label = "Image B (FSBL, PMU, ATF, U-Boot)";
					reg = <0xf80000 0xd00000>;
				};

				partition@1c80000 {
					label = "ImgSel Image B Catch";
					reg = <0x1c80000 0x80000>;
					read-only;
					lock;
				};

				partition@1d00000 {
					label = "Open_2";
					reg = <0x1d00000 0x100000>;
				};

				partition@1e00000 {
					label = "Recovery Image";
					reg = <0x1e00000 0x200000>;
					read-only;
					lock;
				};

				partition@2000000 {
					label = "Recovery Image Backup";
					reg = <0x2000000 0x200000>;
					read-only;
					lock;
				};

				partition@2200000 {
					label = "U-Boot storage variables";
					reg = <0x2200000 0x20000>;
				};

				partition@2220000 {
					label = "U-Boot storage variables backup";
					reg = <0x2220000 0x20000>;
				};

				partition@2240000 {
					label = "SHA256";
					reg = <0x2240000 0x10000>;
					read-only;
					lock;
				};

				partition@2250000 {
					label = "User";
					reg = <0x2250000 0x1db0000>;
				};
			};
		};
```

QSPI を直接制御するデバイスドライバは Xilinx が提供する drivers/spi/spi-zynqmp-gqspi.c です。ただし、このデバイスドライバを直接使ってフラッシュメモリにアクセスするわけではありません。その上に Memory Technology Device (以下 MTD) があって、この MTD デバイスドライバを使ってユーザーアプリケーションから QSPI に接続されているフラッシュメモリを読み書きします。

なお、このデバイスツリーにフラッシュメモリの各パーティション情報(名前、開始位置、サイズ、属性) が定義されています。

## フラッシュメモリのパーティションとデバイス名

前述のデバイスツリーを使って KV260 をブートすると、各々のパーティションが次のようなデバイスとして見えるようになります。

| デバイス名   |    開始位置   |  サイズ       |  説明 |
|:-----------|:------------:|:-----------:|:---------------------------------|
| /dev/mtd0  | 0x0000_00000 | 0x0080_0000 | Image Selector                   |
| /dev/mtd1  | 0x0008_00000 | 0x0080_0000 | Image Selector Golden            |
| /dev/mtd2  | 0x0010_00000 | 0x0002_0000 | Persistent Register              |
| /dev/mtd3  | 0x0012_00000 | 0x0002_0000 | Persistent Register Backup       |
| /dev/mtd4  | 0x0014_00000 | 0x000C_0000 | Open_1                           |
| /dev/mtd5  | 0x0020_00000 | 0x00D0_0000 | Image A (FSBL, PMU, ATF, U-Boot) | 
| /dev/mtd6  | 0x00F0_00000 | 0x0008_0000 | IMGSel Image A Cache             |
| /dev/mtd7  | 0x00F8_00000 | 0x00D0_0000 | Image B (FSBL, PMU, ATF, U-Boot) | 
| /dev/mtd8  | 0x01C8_00000 | 0x0008_0000 | IMGSel Image B Cache             |
| /dev/mtd9  | 0x01D0_00000 | 0x0010_0000 | Open_2                           |
| /dev/mtd10 | 0x01E0_00000 | 0x0020_0000 | Recovery Image                   |
| /dev/mtd11 | 0x0200_00000 | 0x0020_0000 | Recovery Image Backup            |
| /dev/mtd12 | 0x0220_00000 | 0x0002_0000 | U-Boot Storage variables         |
| /dev/mtd13 | 0x0222_00000 | 0x0002_0000 | U-Boot Storage variables Backup  |
| /dev/mtd14 | 0x0224_00000 | 0x0001_0000 | SHA256                           |
| /dev/mtd15 | 0x0225_00000 | 0x01DB_0000 | User                             |

QSPI のフラッシュメモリには Image A と Image B と呼ばれる二つの BOOT.BIN が格納されています。例えば、次のようにして Image A の BOOT.BIN をファイルに書き出すことができます。

```console
shell$ sudo dd if=/dev/mtd7 of=boot_a.bin bs=1024
13312+0 records in
13312+0 records out
13631488 bytes (14 MB, 13 MiB) copied, 2.9342 s, 4.6 MB/s
```

おそらく書き込みも出来ますが、うかつに BOOT.BIN を書き換えるとブートしなくなる可能性があるので注意が必要です。BOOT.BIN を変更するには次節で説明するアプリケーションを使った方が良いでしょう。

## BOOT.BIN の更新

BOOT.BIN が二つ格納されているのは、例えば、新しい BOOT.BIN をフラッシュメモリ の Image B に書き込んだとして、万が一ブートに失敗した時には Image A に書き込まれているブートすることが確認されている BOOT.BIN を起動するようにするためだろうと思われます。
どちらの Image を利用してブートするかを制御するのは Persistent Register に格納されている情報を使うようです。このような BOOT.BIN の更新を行うためのアプリケーションが Xilinx から提供されています。

  * https://github.com/Xilinx/linux-image_update

また、上記のアプリケーションを実行するためのラッパープログラムも Xilinx から提供されています。

  * https://github.com/Xilinx/xmutil

## BOOT.BIN の確認

BOOT.BIN にどのような情報が格納されているかは bootgen コマンドで確認することができます。

```console
fpga@debian-fpga:~$ sudo bootgen -read /dev/mtd5


****** Xilinx Bootgen v2019.2
  **** Build date : Jan 16 2020-08:00:00
    ** Copyright 1986-2019 Xilinx, Inc. All Rights Reserved.

--------------------------------------------------------------------------------
   BOOT HEADER
--------------------------------------------------------------------------------
        boot_vectors (0x00) : 0x1400000014000000140000001400000014000000140000001400000014000000
     width_detection (0x20) : 0xaa995566
            image_id (0x24) : 0x584c4e58
 encryption_keystore (0x28) : 0x00000000
      header_version (0x2c) : 0xfffc0000
   fsbl_sourceoffset (0x30) : 0x00002800
         fsbl_length (0x34) : 0x0001fae0
   fsbl_load_address (0x38) : 0x0001fae0
   fsbl_exec_address (0x3C) : 0x0001b200
   fsbl_total_length (0x40) : 0x0001b200
    qspi_config-word (0x44) : 0x00000800
            checksum (0x48) : 0xfd16d281
          iht_offset (0x98) : 0x000008c0
          pht_offset (0x9c) : 0x00001100
--------------------------------------------------------------------------------
   IMAGE HEADER TABLE
--------------------------------------------------------------------------------
             version (0x00) : 0x01020000        total_images (0x04) : 0x00000004
          pht_offset (0x08) : 0x00001100           ih_offset (0x0c) : 0x00000900
       hdr_ac_offset (0x10) : 0x00000000
--------------------------------------------------------------------------------
   IMAGE HEADER (zynqmp_fsbl.elf)
--------------------------------------------------------------------------------
          next_ih(W) (0x00) : 0x00000250
         next_pht(W) (0x04) : 0x00000440
    total_partitions (0x08) : 0x00000000
    total_partitions (0x0c) : 0x00000001
                name (0x10) : zynqmp_fsbl.elf
--------------------------------------------------------------------------------
   IMAGE HEADER (bl31.elf)
--------------------------------------------------------------------------------
          next_ih(W) (0x00) : 0x00000260
         next_pht(W) (0x04) : 0x00000450
    total_partitions (0x08) : 0x00000000
    total_partitions (0x0c) : 0x00000001
                name (0x10) : bl31.elf
--------------------------------------------------------------------------------
   IMAGE HEADER (u-boot.dtb)
--------------------------------------------------------------------------------
          next_ih(W) (0x00) : 0x00000270
         next_pht(W) (0x04) : 0x00000460
    total_partitions (0x08) : 0x00000000
    total_partitions (0x0c) : 0x00000001
                name (0x10) : u-boot.dtb
--------------------------------------------------------------------------------
   IMAGE HEADER (u-boot.elf)
--------------------------------------------------------------------------------
          next_ih(W) (0x00) : 0x00000000
         next_pht(W) (0x04) : 0x00000470
    total_partitions (0x08) : 0x00000000
    total_partitions (0x0c) : 0x00000001
                name (0x10) : u-boot.elf
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (zynqmp_fsbl.elf.0)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x0000eb38  unencrypted_length (0x04) : 0x0000eb38
        total_length (0x08) : 0x0000eb38           load_addr (0x0c) : 0x00000450
           exec_addr (0x10) : 0xfffc0000    partition_offset (0x14) : 0x00000000
          attributes (0x18) : 0xfffc0000       section_count (0x1C) : 0x00000000
     checksum_offset (0x20) : 0x00000a00          iht_offset (0x24) : 0x00000116
           ac_offset (0x28) : 0x00000001            checksum (0x3c) : 0x00052cb0
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-64]     dest_device [none]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (bl31.elf.0)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x000031ec  unencrypted_length (0x04) : 0x000031ec
        total_length (0x08) : 0x000031ec           load_addr (0x0c) : 0x00000460
           exec_addr (0x10) : 0xfffea000    partition_offset (0x14) : 0x00000000
          attributes (0x18) : 0xfffea000       section_count (0x1C) : 0x00000000
     checksum_offset (0x20) : 0x0000f540          iht_offset (0x24) : 0x00000117
           ac_offset (0x28) : 0x00000001            checksum (0x3c) : 0x00012d32
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-64]     dest_device [PL]
              encryption [yes]                 core [a53-1]
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (u-boot.dtb.0)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x000021e0  unencrypted_length (0x04) : 0x000021e0
        total_length (0x08) : 0x000021e0           load_addr (0x0c) : 0x00000470
           exec_addr (0x10) : 0x00000000    partition_offset (0x14) : 0x00000000
          attributes (0x18) : 0x00100000       section_count (0x1C) : 0x00000000
     checksum_offset (0x20) : 0x00012730          iht_offset (0x24) : 0x00000116
           ac_offset (0x28) : 0x00000001            checksum (0x3c) : 0xffee6b46
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-64]     dest_device [none]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (u-boot.elf.0)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x0004174e  unencrypted_length (0x04) : 0x0004174e
        total_length (0x08) : 0x0004174e           load_addr (0x0c) : 0x00000000
           exec_addr (0x10) : 0x10080000    partition_offset (0x14) : 0x00000000
          attributes (0x18) : 0x10080000       section_count (0x1C) : 0x00000000
     checksum_offset (0x20) : 0x00014910          iht_offset (0x24) : 0x00000114
           ac_offset (0x28) : 0x00000001            checksum (0x3c) : 0xdfe26d7d
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-64]     dest_device [none]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------
   AUTHENTICATION CERTIFICATE (zynqmp_fsbl.elf.0)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
   AUTHENTICATION CERTIFICATE (bl31.elf.0)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
   AUTHENTICATION CERTIFICATE (u-boot.dtb.0)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
   AUTHENTICATION CERTIFICATE (u-boot.elf.0)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
```

### 余談１

bootgen は KV260 上の Debian や Ubuntu で動かすことができます。詳細は次の記事を参照してください。

 * [『bootgen を Debian GNU/Linux、Ubuntu で動かす』@Qiita](https://qiita.com/ikwzm/items/97811fcff7876181209f)

### 余談２

bootgen の -read コマンドは bootgen のヘルプコマンドで説明されません(何故？)。