かつて投稿した『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() です。
/*****************************************************************************/
/**
* 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つに分けることができます。
- Persistent Register のロード
- Persistent Register の内容に基づいてイメージを選択
- どのイメージを選択したかを Persistent Register にストア
この記事では 2. のイメージ選択を次節でもう少し詳しく説明します。
Image Selector のイメージ選択アルゴリズム
Image Selector の選択アルゴリズムは次の URL で詳しく説明されています。
このページでは次のように説明されています。
- 両方のイメージが「起動可能」とマークされている場合、ImageSelectorは要求されたイメージを起動します。(いわゆる Normal Boot)
- 両方のイメージが「非起動可能」とマークされている場合、この状態はFWが不良状態にあることを意味します。これは、どちらのイメージもLinuxまで起動が確認されないため、システムはImageSelectorアプリケーションのImageRecoveryを起動します。通常のA/B更新メカニズムを使用している場合、この状態は発生しないことに注意してください。(いわゆる Error Recovery)
- 要求されたイメージが起動不可能で、最後に起動されたイメージが起動可能である場合、要求されたイメージが前回の起動中に更新されたことを意味します。
- 要求されたイメージが起動できず、最後に起動されたイメージが起動できない場合は、新しく更新されたイメージが起動に失敗したことを意味します。この場合、イメージセレクターは既知の正常なイメージにフォールバックします。
1.の Normal Boot と 2. の Error Recovery はわかるのですが、3.と4.が実際にどう動くのか理解しにくいです。
そこで前節で紹介した XIs_UpdateABMultiBootValue() のイメージ選択の部分だけをみてみると次のようになります。
/**
* 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 のコードとが微妙に違っていることです。
コメントでは
- イメージAとイメージBの両方が「起動可能」としてマークされている場合、要求されたイメージがロードされます。
- イメージAまたはイメージBのどちらか一方のイメージが「起動可能」としてマークされている場合、要求されたイメージは起動可能ではありませんので、起動可能なイメージがロードされます。
- 最後に起動したイメージが起動できない場合、起動可能なイメージが読み込まれます。
- どのイメージも「起動可能」としてマークされていない場合、リカバリイメージがロードされます。
となっています。
一方、C のコードでは
- いずれかのイメージが「起動可能」な場合、最後に起動したイメージが起動可能でない場合はもう片方のイメージがロードされます。
- いずれかのイメージが「起動可能」な場合かつ 1 でない場合は、要求されたイメージがロードされます。
- どのイメージも「起動可能」としてマークされていない場合、リカバリイメージがロードされます。
となっています。
まあ、結局 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 電源を落として再投入」を再度実行します。