Armv8.2 CPU は基本的な動作は Armv8 と等価で考えていたのですが、ふと Trusted Firmware-A ソースを見ていた時に Armv8.2 の記述があったので、何が違うのか見てみました。
MT8192の CPU ID取得
MediaTek MT8192 は2020年リリースのSoCで CPU は Cortex-A76/A55 の big.LITTLE 構成ですが、これらは Armv8.2 アーキテクチャです。この SoC に対応する TF-A コードに CPU ID を特定する関数がありますが、何か Armv8.2 の記述があります。
mediatek/mt8192/common/plat_topology.c:
int plat_core_pos_by_mpidr(u_register_t mpidr)
{
unsigned int cluster_id, cpu_id;
if (read_mpidr() & MPIDR_MT_MASK) {
/* ARMv8.2 arch */
if (mpidr & (MPIDR_AFFLVL_MASK << MPIDR_AFF0_SHIFT)) {
return -1;
}
return plat_mediatek_calc_core_pos(mpidr);
}
MPIDR レジスタは CPU のトポロジーを表すレジスタですが、MT bit が立っている場合に Armv8.2 の記述があります。
mediatek/mt8192/aarch64/plat_helper.S:
/* -----------------------------------------------------
* unsigned int plat_mediatek_calc_core_pos(u_register_t mpidr);
*
* In ARMv8.2, AFF2 is cluster id, AFF1 is core id and
* AFF0 is thread id. There is only one cluster in ARMv8.2
* and one thread in current implementation.
*
* With this function: CorePos = CoreID (AFF1)
* we do it with x0 = (x0 >> 8) & 0xff
* -----------------------------------------------------
*/
func plat_mediatek_calc_core_pos
mov x1, #MPIDR_AFFLVL_MASK
and x0, x1, x0, lsr #MPIDR_AFF1_SHIFT
ret
endfunc plat_mediatek_calc_core_pos
何やらコメントがありますが、元々は MPIDR の AFF0(bit[7:0]) は CPU ID、AFF1(bit[15:8]) は Cluster ID でした。しかしこのコメントによると、AFF0 は thread ID に割り当てられ、AFF1,AFF2 はそれぞれ CPU ID、Cluster ID となっているようです。
この処理は MTbit ==1 つまり CPU スレッドが有効な場合に呼ばれますが、まだ CPU スレッドが使える CPU はないようなので AFF0 は 0 になり、MT bit を気にせず CPU ID を取得すると誤認識しそうです。
FVP の CPUID 取得
Arm の FVP(Fixed Virtual Platforms)実装はAFF2まで考慮されていました。
まず MTbit を調べて MTbit==0 なら MPIDR を 8bit左シフトしてAFF0=0としています。この値を元にAFF0,AFF1,AFF2 を値をそれぞれの最大値の倍数で掛けたものを加算して CPU ID を割り出していました。
func plat_arm_calc_core_pos /* * Check for MT bit in MPIDR. If not set, shift MPIDR to left to make it * look as if in a multi-threaded implementation. */ tst x0, #MPIDR_MT_MASK lsl x3, x0, #MPIDR_AFFINITY_BITS csel x3, x3, x0, eq /* Extract individual affinity fields from MPIDR */ ubfx x0, x3, #MPIDR_AFF0_SHIFT, #MPIDR_AFFINITY_BITS ubfx x1, x3, #MPIDR_AFF1_SHIFT, #MPIDR_AFFINITY_BITS ubfx x2, x3, #MPIDR_AFF2_SHIFT, #MPIDR_AFFINITY_BITS /* Compute linear position */ mov x4, #FVP_MAX_CPUS_PER_CLUSTER madd x1, x2, x4, x1 mov x5, #FVP_MAX_PE_PER_CPU madd x0, x1, x5, x0 ret endfunc plat_arm_calc_core_pos
余談として ubfx 命令は初めて見ましたが、Unsigned Bit Field eXtract を表し、LSB からMPIDR_AFFn_SHIFT(0 or 8 or 16) の左シフト位置から MPIDR_AFFINITY_BITS (==8) bit 分を取り出してレジスタにセットしています。つまりこの命令で各 AFFn の値 を Xn レジスタに一発で取り出します。
他の実装をまだ見つけられてないですが、Armv8.2 CPU を採用したチップが広がるとこの辺りの実装は注意が必要と思われます(主に自分用注意書きですが)。
Linux の CPU ID
Linux 上での CPU ID は devicetree の reg property で記述する必要があります。
PSCI で CPU ID を指定して起こす際にこの reg の値が渡されて、MPIDR と比較して一致した場合に SEV 命令で WFE 待ちから復帰させるようになっています。そのため、この reg の値はその CPU に合わせた MPIDR 値を元にを設定する必要があります。
cpu-map { cluster0 { core0 { cpu = <&cpu0>; // cluster0,core0,thread0 }; core1 { cpu = <&cpu1>; // cluster0,core1,thread0 }; }; cluster1 { core0 { cpu = <&cpu2>; // cluster1,core0,thread0 }; core1 { cpu = <&cpu3>; // cluster1,core1,thread0 }; }; }; cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a55"; reg = <0x0 0x0>; }; cpu1: cpu@100 { device_type = "cpu"; compatible = "arm,cortex-a55"; reg = <0x0 0x100>; }; cpu2: cpu@10000 { device_type = "cpu"; compatible = "arm,cortex-a55"; reg = <0x0 0x10000>; }; cpu3: cpu@10100 { device_type = "cpu"; compatible = "arm,cortex-a55"; reg = <0x0 0x10100>; };
コメント