はじめに
背景
筆者は次の記事で紹介したような Xilinx 製 SoC (通称 ZynqMP) を搭載した評価ボードに Debian/Linux や Ubuntu をビルドして公開しています。また、Linux でユーザー空間からクロックを制御するためのデバイスドライバも提供しています。
- 『UltraZed/Ultra96/Ultra96-V2/KV260 向け Debian GNU/Linux (v2021.1版) ブートイメージの提供』 @Qiita
- 『Linux でユーザー空間から Zynq/ZynqMP の PLクロック信号を制御するデバイスドライバ』@Qiita
症状
実はある時期から、クロックの enable や disable に失敗することがありました。しかも enable や disable に失敗したことが Linux Kernel からは判らず、後になって何故か enable にしたはずのクロックが実は動いてなくてLinux Kernel がパニックを起こしたり、別のところで問題が起こって調べてみたら実はクロックが動いていなかったことが判明したなどの症状が出ました。
原因
原因を先に簡単に述べると、Linux Kernel、 First Stage Boot Loader(以下 FSLB)、 Platform Management Unit Firmware(以下 PMUFW)、ARM Trusted Firmware Boot Loader Stage 3-1(以下 BL31) の組み合わせによって発生することがわかりました。
- PMUFW にはクロックが属するノードがどのプロセッサからの変更を受け付けるか否かを決めるパーミッションという機構がとあるバージョンから追加された。
- FSBL がPMUFW のノードのパーミッションを設定する際、 クロックの属するノードのパーミッションを適切に設定しないことがある。
- 古い Linux Kernel は PMUFW に対してノードの管理(要求/解放)を行わないため、パーミッションをサポートしたPMUFW と古い Linux Kernel の組み合わせではパーミッションエラーが発生する。
- BL31 が PMUFW からのパーミッションエラーを握りつぶして Linux には正常に終了したことにして報告している。
これらの原因の詳細は後述します。
対策
場合によっては BOOT.bin(FSLB、PMUFW、BL31) および Linux Kernel を最新のものに更新するか、新たにビルドしなおすことで解消することがあります。
ZynqMP でのクロック管理のメカニズム
下図に ZynqMP のブートから Linux Kernel が動き出すまでの間に、クロック管理がどのように行われているかを示します。
Fig.1 ZynqMP のブートシーケンスとクロック管理
ブートステージ0 (Stage-0 Boot Loader)
ZynqMP のステージ0ブートローダーは内部ROMにあります。ステージ0ブートローダーは PMU(Platform Management Unit) が実行します。PMU はシステム全体のリソースのパワーアップ、リセットの制御および監視を担当します。PMU はこのため専用のプロセッサで、実態は MicroBlaze という Xilinx が開発したプロセッサのようです。
ステージ0ブートローダーは、Coretex-A53 とプライマリーブートデバイスが動く最低限のクロックの設定をおこなった後、プライマリブートデバイスにある BOOT.BIN に含まれるステージ1ブートローダー(FSBL) を内部RAMをロードして Coretex-A53 を起動します。
ブートステージ1 (FSBL - First Stage Boot Loader)
FSBL は内部RAMにロードされ、 Coretex-A53 によって実行します。FSBL は次の処理を行います。
- PS(Processing System) の初期設定。必要なクロックの初期設定も含む。
- BOOT.BIN に含まれる Bitstream を PL(Programmable Logic) へコンフィギュレーション。
- BOOT.BIN に含まれるステージ2ブートローダー(BL31)を SDRAM にロード。
- BOOT.BIN に含まれるステージ3ブートローダー(U-Boot)を SDRAM にロード。
- BOOT.BIN に含まれるPMUFW(Platform Manager Unit Firmware)を PMU の RAM にロードして PMU を起動。
- PMUFW に対して SET_CONFIGURATION を行う。その際ノードのパーミッションの設定も行う。
- ステージ2ブートローダー(BL31)に制御を移す。
ブートステージ2 (BL31 - ARM Trusted Firmware Boot Loader Stage 3-1)
BL31 は ARM が提供している ATF(ARM Trusted Firmware) の EL3 Runtime Firmware です。BL31 は Coretex-A53 が実行します。BL31 は自分自身を初期化してステージ3ブートローダー(U-Boot)に制御を移した後でも RAM に常駐して各種サービスを提供します。特に Linux と PMUFW との仲立ち的な仕事をしています。
ブートステージ3 (U-Boot)
U-Boot はファイルシステムを使えたり、スクリプトや環境変数などが使えるので、その設定にしたがって、Linux のカーネルイメージ、Device Tree、場合によってはルートファイルシステムを SDRAMに ロードします。その後、Linuxカーネルイメージに制御を移します。
Generic Power Domains on Linux
Linux Kernel 5.1 あたりから Generic Power Domains (以下 genpd) が追加されました。これは Linux が各種ハードウェアの電力制御などをおこなうためのフレームワークです。genpd はデバイスツリーに power-domains プロパティをセットすることで、デバイスの追加/削除時に自動的に電源の制御をおこないます。
ZynqMP ではこのフレームワーク用のドライバとして、次節で紹介する ZynqMP Generic PM Domains が提供されています。
ZynqMP Generic PM Domains
ZynqMP Generic PM Domains は、genpd によってデバイスが接続(Attach) されたとき、BL31を通して PMUFW に対して対応するノードの確保を要求します。また、デバイスが切断(Detach)されたときは、PMUFW に対して対応するノードの解放を要求します。
PMUFW
PMUFW(Platform Manager Unit Firmware) は FSBL によって PMU の内部 RAM にロードされ、PMU によって実行されるファームウェアです。PMUFW は ZynqMP 内部に常駐して、主に電源管理を行います。もともとは電源管理のみをしていたのですが、時間がたつにつれてどんどん機能が増えていき、今では ZynqMP のクロック管理、リセットの管理、信号ピンの管理、PL のコンフィギュレーションなどの処理が追加されています。このようにバージョンアップによって機能がどんどん追加されてきたため、他のシステム (Linux Kernel、U-Boot、BL31、FSBL) とのバージョンの不整合が起こりがちです。今回の不具合の原因は次章で説明します。
原因究明
PMUFW
パーミッションチェック
PMUFW はあるバージョンからパーミッションチェックをするようになりました。パーミッションチェックとは、次の2つの条件が揃っていない限り、クロックの設定を変更することが出来ないようにするメカニズムです。
- プロセッサによるノードへのアクセスが許可されている
- プロセッサによってノードが確保されている状態になっている
ここでのノードとはクロックが属するノードのことです。PMUFW ではクロックはノードに属しており、アクセスのパーミッションの設定やチェックはこのノード単位で行われます。
1 のノードへのアクセスがどのプロセッサに許可されているかは、PMUFW の API であるPM_SET_CONFIGURE によって設定されます。通常はこの PM_SET_CONFIGURE はステージ1ブートローダー(FSBL) が PMUFW をロードして起動する際におこなわれます。したがって、FSBL がちゃんとノードのパーミッションを設定しておかないと、クロックの設定時にパーミッションエラーが発生します。
2 のノードの確保は PMUFW の API である PM_NODE_REQUEST によっておこなわれます。Linux Kernel 5.1 以降では genpd(Generic Power Domains) がデバイスのアタッチの際に ZynqMP Generic PM Domains ドライバが BL31 を通して PMUFW に対して PM_NODE_REQUEST を発行します。成功するとノードがプロセッサによって確保された状態になります。
PMUFW はクロックの設定を変更する際に、上記のパーミッションチェックを行い、違反時には設定の変更を行わずに XST_PM_NO_ACCESS (=2002) を返します。
リポジトリ
PMUFW のソースコードは Xilinx が github にて公開しています。
-
https://github.com/Xilinx/embeddedsw
- lib/sw_apps/zynqmp_pmufw
PmClockGateConfig()
PMUFW のクロックの状態(enable/disable) を変更する際、lib/sw_apps/zynqmp_pmufw/src/pm_core.c のPmClockGeteConfig() が呼ばれます。この関数は次のようになっています。
/**
* PmClockGateConfig() - Configure clock gate if master has privileges to do so
* @master The caller
* @clkId ID of the target clock
* @enable 1=enable the clock, 0=disable the clock
*/
static void PmClockGateConfig(PmMaster* const master, const u32 clkId, const u8 enable)
{
PmClock* clockPtr;
s32 status = XST_SUCCESS;
PmInfo("%s> ClockGate(%lu, %lu)\r\n", master->name, clkId, enable);
clockPtr = PmClockGetById(clkId);
if (NULL == clockPtr) {
status = XST_INVALID_PARAM;
goto done;
}
#ifndef DISABLE_CLK_PERMS
status = PmClockCheckPermission(clockPtr, master->ipiMask);
if (XST_SUCCESS != status) {
goto done;
}
#endif
status = PmClockGateSetState(clockPtr, enable);
done:
IPI_RESPONSE1(master->ipiMask, (u32)status);
}
この関数によって PmClockCheckPermission() によってパーミッションチェックが行われ、成功した時は PmClockGateSetState() によってクロックの状態を変更します。
パーミッションチェックが追加された経緯
PMUFW に クロックのパーミッションのチェックが追加されたコミットを調べてみると、次の二つが見つかりました。
- https://github.com/Xilinx/embeddedsw/commit/5e826fb92267335d9951b1ae3c2aed6202ecc355
- https://github.com/Xilinx/embeddedsw/commit/dc89154dcc7d58afe06294c7ab29b0f022482c4e
まずはコミット https://github.com/Xilinx/embeddedsw/commit/5e826fb92267335d9951b1ae3c2aed6202ecc355 です。このコミットでパーミッションをチェックする関数 PmClockCheckPermission() が追加され、PmClockSetParent()、PmClockGateConfig()、PmClockSetDivider() から呼び出されています。このコミットメッセージは次のようになっていました。
shell$ git show 5e826fb92267335d9951b1ae3c2aed6202ecc355
commit 5e826fb92267335d9951b1ae3c2aed6202ecc355
Author: Mirela Simonovic <mirela.simonovic@aggios.com>
Date: Fri Aug 24 20:37:48 2018 +0530
pmufw: Add permission checking for clock set parent, divider and gate state
No permissions are required to get a clock state, only the permissions to
set it.
Signed-off-by: Mirela Simonovic <mirela.simonovic@aggios.com>
Acked-for-series: Will Wong <WILLW@xilinx.com>
diff --git a/lib/sw_apps/zynqmp_pmufw/src/pm_clock.c b/lib/sw_apps/zynqmp_pmufw/src/pm_clock.c
index ff5887e843..16b76770c0 100644
--- a/lib/sw_apps/zynqmp_pmufw/src/pm_clock.c
+++ b/lib/sw_apps/zynqmp_pmufw/src/pm_clock.c
@@ -2835,4 +2835,37 @@ done:
return status;
}
:
(後略)
この後、さらにコミットhttps://github.com/Xilinx/embeddedsw/commit/dc89154dcc7d58afe06294c7ab29b0f022482c4e でDISABLE_CLK_PERMS が追加されています。
shell$ git show dc89154dcc7d58afe06294c7ab29b0f022482c4e
commit dc89154dcc7d58afe06294c7ab29b0f022482c4e
Author: Mirela Simonovic <mirela.simonovic@aggios.com>
Date: Fri Aug 24 20:37:51 2018 +0530
pmufw: Wrap clock/PLL permission checking with a disable macro
DISABLE_CLK_PERMS can be set in order to disable permission checking
with respect to clock and PLL control.
I do not suggest anyone to ever disable permission checking. If you
wish to do so, you do it at your own risk.
Signed-off-by: Mirela Simonovic <mirela.simonovic@aggios.com>
Acked-for-series: Will Wong <WILLW@xilinx.com>
diff --git a/lib/sw_apps/zynqmp_pmufw/src/pm_core.c b/lib/sw_apps/zynqmp_pmufw/src/pm_core.c
index 568e7742cf..21fb95c66f 100644
--- a/lib/sw_apps/zynqmp_pmufw/src/pm_core.c
+++ b/lib/sw_apps/zynqmp_pmufw/src/pm_core.c
:
(後略)
BL31
PMUFW がパーミッションエラーを返していたにも関わらず、何故か Linux からは正常終了したことになっていました。調査したところ、BL31 が PMUFW からのパーミッションエラーを握りつぶして正常終了したように見せかけていたことが判りました。
リポジトリ
BL31(ARM Trusted Firmware Boot Loader Stage 3-1) ののソースコードは Xilinx が github にて公開しています。
パーミッションエラーが握りつぶされた経緯
ある時期から BL31 でPMUFW からのパーミッションエラーを握りつぶすようになりました。このような変更が行われたのはコミットhttps://github.com/ARM-software/arm-trusted-firmware/commit/f0928d2dd61b5e41b6b5766db673b603934d6125 です。このコミットは次のようになっていました。
shell$ git show f0928d2dd61b5e41b6b5766db673b603934d6125
commit f0928d2dd61b5e41b6b5766db673b603934d6125
Author: Mirela Simonovic <mirela.simonovic@aggios.com>
Date: Fri Aug 24 17:09:07 2018 +0200
zynqmp: pm: Filter errors related to clock gate permissions
Linux clock framework cannot properly deal with these errors. When the
error is related to the lack of permissions to control the clock we
filter the error and report the success to linux. Before recent changes
in clock framework across the stack, this was done in the PMU-FW as a
workaround. Since the PMU-FW now handles clocks and the permissions to
control them using general principles rather than workarounds, it can
no longer distinguish such exceptions and it has to return no-access
error.
Signed-off-by: Mirela Simonovic <mirela.simonovic@aggios.com>
Acked-by: Will Wong <WILLW@xilinx.com>
diff --git a/plat/xilinx/zynqmp/pm_service/pm_api_sys.c b/plat/xilinx/zynqmp/pm_service/pm_api_sys.c
index 156fd5f74..0d5faabcb 100644
--- a/plat/xilinx/zynqmp/pm_service/pm_api_sys.c
+++ b/plat/xilinx/zynqmp/pm_service/pm_api_sys.c
@@ -870,7 +870,13 @@ static enum pm_ret_status pm_clock_gate(unsigned int clock_id,
/* Send request to the PMU */
PM_PACK_PAYLOAD2(payload, api_id, clock_id);
- return pm_ipi_send_sync(primary_proc, payload, NULL, 0);
+ status = pm_ipi_send_sync(primary_proc, payload, NULL, 0);
+
+ /* If action fails due to the lack of permissions filter the error */
+ if (status == PM_RET_ERROR_ACCESS)
+ status = PM_RET_SUCCESS;
+
+ return status;
}
/**
pm_clock_gate() はクロックの状態を変化させるための関数です。実際には PMUFW の PM_CLOCK_ENABLE / PM_CLOCK_DISABLE API を呼び出しています。ここで問題なのは、戻り値が PM_RET_ERROR_ACCESS(=2002) だった場合、それを無視して PM_RET_SUCCESS を返して正常終了扱いにしていることです。
所感
イヤ、コレ、アカンやつやろ。。。現場で発生した問題を中間で握りつぶして上に報告しないって、そんなん組織(システム)でも腐敗の第一歩やで。。。
参考
- 『UltraZed/Ultra96/Ultra96-V2/KV260 向け Debian GNU/Linux (v2021.1版) ブートイメージの提供』 @Qiita
- 『Linux でユーザー空間から Zynq/ZynqMP の PLクロック信号を制御するデバイスドライバ』@Qiita
- 『Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)』@Qiita
- 『KV260 が Linux をブートするまでのシーケンス』@Qiita
- 『Zynq UltraScale+ FSBL』@Xilinx Wiki / Zynq UltraScale+ MPSoC
- 『PMU Firmware』@Xilinx Wiki / Zynq UltraScale+ MPSoC
- 『An Overview of Generic Power Domains (genpd) on Linux』
- https://github.com/Xilinx/embeddedsw
- https://github.com/ARM-software/arm-trusted-firmware