0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KV260 が Linux をブートするまでのシーケンス (Image Selector編)

Last updated at Posted at 2022-07-18

かつて投稿した『KV260 が Linux をブートするまでのシーケンス』では Image Selector の所を省略してしまいましたが、この記事では Image Selector を詳細に説明します。また、前回投稿した『KV260 の ブートファームウェアを更新』 の「ファームウェアイメージの更新に失敗した場合の復帰方法」で、内部的にどのように動作しているかの説明も行います。

Image Selector とは

『KV260 が Linux をブートするまでのシーケンス』で説明したように、ZynqMP のステージ0ブートローダーはプライマリーブートデバイス(KV260 では SOM にある QSPI に接続されたフラッシュメモリ)の所定のアドレスにある BOOT.BIN 内の FSBL(First Stage Boot Loader) を内部メモリにロードして実行します。

しかし、KV260 では FSBL の代わりに Image Selector が入っています。Image Selector は、プライマリーブートデバイスに入っている二つ(リカバリー用を含めれば3つ) の BOOT.BIN から選択してロードします。

KV260 のプライマリーブートデバイスの構造

KV260 のプライマリーブートデバイス(SOM にある QSPI に接続されたフラッシュメモリ)の構造は次のようになっています。

デバイス名 開始位置 サイズ 説明
/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

フラッシュメモリの先頭アドレスにあるのが Image Selector です。この Image Selector が Image A や Image B (場合によっては Recovery Image) を選択して、実行します。

Image Selector のフォーマット

ステージ0ブートローダーが内部メモリにロードして実行するために、Image Selector は BOOT.BIN のフォーマットに準拠しています。
試しに bootgen コマンドで読んでみると次のようになります。executable.elf が Image Selector の実行プログラムです。

root@debian-fpga:~# bootgen -read /dev/mtd0


****** 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) : 0x00000000
   fsbl_load_address (0x38) : 0x00000000
   fsbl_exec_address (0x3C) : 0x0000f048
   fsbl_total_length (0x40) : 0x0000f048
    qspi_config-word (0x44) : 0x00000800
            checksum (0x48) : 0xfd1c4bb1
          iht_offset (0x98) : 0x000008c0
          pht_offset (0x9c) : 0x00001100
--------------------------------------------------------------------------------
   IMAGE HEADER TABLE
--------------------------------------------------------------------------------
             version (0x00) : 0x01020000        total_images (0x04) : 0x00000001
          pht_offset (0x08) : 0x00001100           ih_offset (0x0c) : 0x00000900
       hdr_ac_offset (0x10) : 0x00000000
--------------------------------------------------------------------------------
   IMAGE HEADER (executable.elf)
--------------------------------------------------------------------------------
          next_ih(W) (0x00) : 0x00000000
         next_pht(W) (0x04) : 0x00000440
    total_partitions (0x08) : 0x00000000
    total_partitions (0x0c) : 0x00000001
                name (0x10) : executable.elf
--------------------------------------------------------------------------------
   PARTITION HEADER TABLE (executable.elf.0)
--------------------------------------------------------------------------------
    encrypted_length (0x00) : 0x00003c12  unencrypted_length (0x04) : 0x00003c12
        total_length (0x08) : 0x00003c12           load_addr (0x0c) : 0x00000000
           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) : 0x00073e72
 attribute list -
               trustzone [non-secure]            el [el-0]
              exec_state [aarch-64]     dest_device [none]
              encryption [no]                  core [none]
--------------------------------------------------------------------------------
   AUTHENTICATION CERTIFICATE (executable.elf.0)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

Image Selector のソースコード

Image Selector のソースコードは Xilinx が Github にて MIT ライセンスで公開しています。

Image Selector はプライマリーデバイスの Persistent Register に格納された内容に従ってブートするイメージを選択します。イメージを選択する部分は lib/sw_apps/imgsel/src/xis_update_a_b.c の XIs_UpdateABMultiBootValue() です。

lib/sw_apps/imgsel/src/xis_update_a_b.c
/*****************************************************************************/
/**
 * This function is used to update the multiboot value
 * @param	None.
 *
 * @return	returns error value defined in xis_error.h file on failure
 *			returns XST_SUCCESS on success
 *
 *
 ******************************************************************************/
int XIs_UpdateABMultiBootValue(void)
{
	int Status = XST_FAILURE;
	u32 *PerstRegPtr;
	u32 Offset;
	u8 CurrentImage;
	u8 ReadDataBuffer[XIS_SIZE_4K] __attribute__ ((aligned(32U)));

	Status = XIs_QspiInit();
	if (Status != XST_SUCCESS) {
		XIs_Printf(DEBUG_GENERAL, "QSPI Init failed\r\n");
		goto END;
	}

	Status = XIs_QspiRead(XIS_PERS_REGISTER_BASE_ADDRESS,
					(u8 *)ReadDataBuffer, XIS_SIZE_4K);
	if (Status != XST_SUCCESS) {
		XIs_Printf(DEBUG_GENERAL, "QSPI Read failed\r\n");
		goto END;
	}
	Status = XIs_DataValidations(ReadDataBuffer);
	if(Status != XST_SUCCESS) {
		XIs_Printf(DEBUG_GENERAL, "Data Validations Failed\r\n");
		goto END;
	}

	/**
	 * If Image A and Image B are both are marked as bootable,
	 * Requested image will be loaded
	 * If Image A or Image B any one of the image is marked as bootable,
	 * Requested image is not bootable bootable image is loaded
	 * If Last Image booted is non-bootable, bootable image will be loaded
	 * If None of the Image is marked as bootable, recovery image is loaded
	 */
	if((ReadDataBuffer[XIS_IMAGE_A_BOOTABLE] == TRUE) ||
			(ReadDataBuffer[XIS_IMAGE_B_BOOTABLE] == TRUE)) {
		if((ReadDataBuffer[XIS_LAST_BOOTED_IMAGE] == XIS_IMAGE_A) &&
					(ReadDataBuffer[XIS_IMAGE_A_BOOTABLE] == FALSE)) {
			CurrentImage = XIS_IMAGE_B;
			PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_B_OFFSET];
		}
		else if((ReadDataBuffer[XIS_LAST_BOOTED_IMAGE]
					== XIS_IMAGE_B) &&
					(ReadDataBuffer[XIS_IMAGE_B_BOOTABLE] == FALSE)){
			CurrentImage = XIS_IMAGE_A;
			PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_A_OFFSET];
		}
		else {
			if(ReadDataBuffer[XIS_REQUESTED_BOOT_IMAGE]
					== XIS_IMAGE_A) {
				CurrentImage = XIS_IMAGE_A;
				PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_A_OFFSET];
			}
			else {
				CurrentImage = XIS_IMAGE_B;
				PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_B_OFFSET];
			}
		}
	}
	else {
		PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_RECOVERY_IMAGE_OFFSET];
		Offset = (u32)(*PerstRegPtr / XIS_SIZE_32KB);
		XIs_UpdateMultiBootValue(Offset);
		goto END;
	}

	Offset = (u32)(*PerstRegPtr / XIS_SIZE_32KB);
	XIs_UpdateMultiBootValue(Offset);

	if(ReadDataBuffer[XIS_LAST_BOOTED_IMAGE] != CurrentImage) {
		ReadDataBuffer[XIS_LAST_BOOTED_IMAGE] = CurrentImage;
		(void)XIs_CheckSumCalculation((u32*)ReadDataBuffer, (u8)TRUE);
		Status = XIs_QspiWrite(XIS_PERS_REGISTER_BASE_ADDRESS,
						(u8 *)ReadDataBuffer, XIS_SIZE_4K);
		if(Status != XST_SUCCESS) {
			XIs_Printf(DEBUG_GENERAL, "QSPI Last image booted"
                                  " Write failed\r\n");
			goto END;
		}

		Status = XIs_QspiWrite(XIS_PERS_REGISTER_BACKUP_ADDRESS,
						(u8 *)ReadDataBuffer, XIS_SIZE_4K);
		if(Status != XST_SUCCESS) {
			XIs_Printf(DEBUG_GENERAL, "QSPI Last image booted"
                                  " Backup Write failed\r\n");
			goto END;
		}
	}

END:
	return Status;
}

この関数は次のように大きく3つに分けることができます。

  1. Persistent Register のロード
  2. Persistent Register の内容に基づいてイメージを選択
  3. どのイメージを選択したかを Persistent Register にストア

この記事では 2. のイメージ選択を次節でもう少し詳しく説明します。

Image Selector のイメージ選択アルゴリズム

Image Selector の選択アルゴリズムは次の URL で詳しく説明されています。

このページでは次のように説明されています。

  1. 両方のイメージが「起動可能」とマークされている場合、ImageSelectorは要求されたイメージを起動します。(いわゆる Normal Boot)
  2. 両方のイメージが「非起動可能」とマークされている場合、この状態はFWが不良状態にあることを意味します。これは、どちらのイメージもLinuxまで起動が確認されないため、システムはImageSelectorアプリケーションのImageRecoveryを起動します。通常のA/B更新メカニズムを使用している場合、この状態は発生しないことに注意してください。(いわゆる Error Recovery)
  3. 要求されたイメージが起動不可能で、最後に起動されたイメージが起動可能である場合、要求されたイメージが前回の起動中に更新されたことを意味します。
  4. 要求されたイメージが起動できず、最後に起動されたイメージが起動できない場合は、新しく更新されたイメージが起動に失敗したことを意味します。この場合、イメージセレクターは既知の正常なイメージにフォールバックします。

1.の Normal Boot と 2. の Error Recovery はわかるのですが、3.と4.が実際にどう動くのか理解しにくいです。

そこで前節で紹介した XIs_UpdateABMultiBootValue() のイメージ選択の部分だけをみてみると次のようになります。

lib/sw_apps/imgsel/src/xis_update_a_b.c
	/**
	 * If Image A and Image B are both are marked as bootable,
	 * Requested image will be loaded
	 * If Image A or Image B any one of the image is marked as bootable,
	 * Requested image is not bootable bootable image is loaded
	 * If Last Image booted is non-bootable, bootable image will be loaded
	 * If None of the Image is marked as bootable, recovery image is loaded
	 */
	if((ReadDataBuffer[XIS_IMAGE_A_BOOTABLE] == TRUE) ||
			(ReadDataBuffer[XIS_IMAGE_B_BOOTABLE] == TRUE)) {
		if((ReadDataBuffer[XIS_LAST_BOOTED_IMAGE] == XIS_IMAGE_A) &&
					(ReadDataBuffer[XIS_IMAGE_A_BOOTABLE] == FALSE)) {
			CurrentImage = XIS_IMAGE_B;
			PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_B_OFFSET];
		}
		else if((ReadDataBuffer[XIS_LAST_BOOTED_IMAGE]
					== XIS_IMAGE_B) &&
					(ReadDataBuffer[XIS_IMAGE_B_BOOTABLE] == FALSE)){
			CurrentImage = XIS_IMAGE_A;
			PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_A_OFFSET];
		}
		else {
			if(ReadDataBuffer[XIS_REQUESTED_BOOT_IMAGE]
					== XIS_IMAGE_A) {
				CurrentImage = XIS_IMAGE_A;
				PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_A_OFFSET];
			}
			else {
				CurrentImage = XIS_IMAGE_B;
				PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_IMAGE_B_OFFSET];
			}
		}
	}
	else {
		PerstRegPtr = (u32 *)&ReadDataBuffer[XIS_RECOVERY_IMAGE_OFFSET];
		Offset = (u32)(*PerstRegPtr / XIS_SIZE_32KB);
		XIs_UpdateMultiBootValue(Offset);
		goto END;
	}

ここでちょっと問題なのは、コメントの内容と実際に記述されている C のコードとが微妙に違っていることです。

コメントでは

  1. イメージAとイメージBの両方が「起動可能」としてマークされている場合、要求されたイメージがロードされます。
  2. イメージAまたはイメージBのどちらか一方のイメージが「起動可能」としてマークされている場合、要求されたイメージは起動可能ではありませんので、起動可能なイメージがロードされます。
  3. 最後に起動したイメージが起動できない場合、起動可能なイメージが読み込まれます。
  4. どのイメージも「起動可能」としてマークされていない場合、リカバリイメージがロードされます。

となっています。

一方、C のコードでは

  1. いずれかのイメージが「起動可能」な場合、最後に起動したイメージが起動可能でない場合はもう片方のイメージがロードされます。
  2. いずれかのイメージが「起動可能」な場合かつ 1 でない場合は、要求されたイメージがロードされます。
  3. どのイメージも「起動可能」としてマークされていない場合、リカバリイメージがロードされます。

となっています。

まあ、結局 C のコードみるのが一番確実で分かりやすいですね。

『KV260 の ブートファームウェアを更新』 で説明した「ファームウェアイメージの更新に失敗した場合の復帰方法」で、正常にブートするファームウェアを書き込まなければいけない理由は、結局のところ Requested Boot Image が正常なブートイメージを指していないと、いつまでもブートに失敗し続けるということです。

Image Selector の状態遷移例

ここでは『KV260 の ブートファームウェアを更新』での例を用いて Persistent Register の内容がどのように遷移していくかを例をあげて説明します。

1 通常にブートしている状態

KV260 を初めてブートした際は次のようになっています。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

A A Y Y

2 ブートファームウェアを現在使用していない方のパーティションに書き込んだ直後

1 の状態でimage_update コマンドを使って新しいブートファームウェアを書き込むとします。
image_update コマンドは、まず Persistent Register を読んで、現在ブートに使われていない方のパーティションに指定したファイルを新たな BOOT.BIN として書き込みます。
image_update コマンド実行直後は次のようになります。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B A Y N

Last Booted Image は現在のブートに使用されているブートファームウェアイメージを示しています。Last Booted Image が Image A だったので、新しいブートファームウェアは Image B に書き込まれました。その際、Image B はブート可能かどうかまだわからないため、Image B は Non Bootable になっています。
Requested Boot Image は次の電源投入時にブートを試すブートファームイメージを示しています。ここでは新しいブートファームウェアを書き込んだ Image B が選択されています。

3 電源を落として再投入

一度電源を落としてから再度電源を投入して KV260 をブートします。この時点では Last Boot Image が A で Image A Bootable も Y なので、Request Boot Image で指定された Image B がブートファームウェアイメージとしてブートを試みます。ブートする際には Last Boot Image を Request Boot Image で指定された Image B に更新します。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B B Y N

無事にブート出来た場合は 4 へ遷移します。
ブートに失敗したばあは 5 へ遷移します。

4 ブートに成功したら新たなブートファームウェアとして登録する

無事にブートしたならば、image_update コマンドの -v オプションを使って、Last Booted Image(今回の場合は Image B) を Bootable に変更します。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B B Y Y

これで無事にブートファームウェアの更新が終了しました。

5 ブートに失敗したらリセットボタンを押す

ブートに失敗した場合はのリセットボタンを押します。この時の状態は次のようになっています。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B B Y N

リセットにより再び Image Selector が起動します。
その際、Last Boot Image が B で、かつ Image B Bootable が N なので、C コードの説明での「1. いずれかのイメージが「起動可能」な場合、最後に起動したイメージが起動可能でない場合はもう片方のイメージがロードされます。」の条件に一致するため、Image B ではなく Image A が選択されてブートを試みます。ブートする際には Last Boot Image を Image A に更新します。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B A Y N

6 正常にブートするブートファームウェアを書き込む

Image A のブートファームウェアは、一度ブートすることが確認されているはずなので、正常にブートするはずです。

しかし、Requested Boot Image がブートに失敗する Image B のままになっているため、このままリブートしてもまた Image B が選択されてしまいます。そのため、正常にブートするブートファームウェアを Image B に上書きしておく必要があります。ブートファームウェアを Image B に上書きした直後は次のようになっています。

Requested

Boot Image

Last

Boot Image

Image A

Bootable

Image B

Bootable

B A Y N

この状態で 「3 電源を落として再投入」を再度実行します。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?