プログラミング
忘れっぽいCPU(M4上のLinux)
The Forgetful CPU (Linux on M4) (yuka.dev)
要約
この記事は、M4 Mac miniでLinuxを初めて起動した際の詳細な道のりを記しています。特に、Apple Silicon M4チップに導入されたSPTM(Secure Page Table Monitor)がLinuxの移植を困難にしたこと、そしてデバッグ手法やカーネルの修正を通じて最終的に起動に成功するまでの技術的な挑戦が語られています。最終的には、WFI命令の挙動に関する問題も解決され、全コアが有効な状態でLinuxが動作するようになりました。
全文翻訳
忘れっぽいCPU(M4上のLinux)
この記事では、私がM4 Mac miniで初めてLinuxを起動した詳細について、かなり詳しく説明します。この投稿ではすべての背景を説明できないため、知らない用語や概念は自分で調べることをお勧めします ;)
さらに進む前に、すべてのAsahi Linuxチームの皆様のこれまでの多大な尽力と旅の間の助けに感謝しなければなりません。Apple SiliconでのメインラインLinuxの作業をもっと見たい場合は、Asahi Open Collectiveへの寄付を検討してください!
始まり
2024年11月、私はM4 Mac miniを購入しました。これは、M1-M3 Apple Siliconマシンと似ており、Asahi Linuxで迅速にサポートされるだろうという賭けでした。M4が数ヶ月間私の机の上に置かれている間、このSoCに関する詳細情報が徐々に明らかになってきました。M4マシンは、macOSのXNUカーネルの脆弱性に対する保護を提供するSPTM(Secure Page Table Monitor)を義務付けた最初の世代のApple Siliconであることが判明し、これはより困難であることがわかりました。以前の世代では、Linuxの移植は、m1n1ハイパーバイザーを使用してキャプチャされたMMIOトレースに大きく依存しており、元のmacOSドライバーとハードウェア間の相互作用の分析が可能でした。SPTMでは、ハイパーバイザー下でmacOSを実行するためにm1n1に大幅な変更が必要であり、これらの変更は、この分野の初心者である私には手に負えないものでした。
それでも、完全に無駄だったわけではありません。ハイパーバイザー作業と並行して、私はM4でLinuxを起動する最初の試みを始めました。これには、厳格なブートセキュリティを無効にし、macOSリカバリ1経由でカスタムブートオブジェクトとしてm1n1をインストールし、ログを検査するためにシリアルコンソール2を取得することが含まれていました。
ロックされたレジスタ
当初、m1n1はBRINGUPモードでのみ開始でき、それ以外の場合はGXF3の初期化を試みるとすぐにクラッシュしました。GXF機能は、これらのM4+ SoCの生のブートモードでは無効/ロックされていることが判明したため、その初期化を条件付きにし、これらのマシンでスキップするのが正しい方法でした。
無効化されたGXF機能に加えて、RVBAR(Reset Vector Base Address Register)もありました。これは、CPUコアごとに、電源投入時にコアが実行を開始する場所を決定するメモリ内の場所です。m1n1コードは、カーネルをブートしたり、別のm1n1をチェーンロードしたりする際に、各コアのRVBARにエントリポイントアドレスを書き込みます。これへの書き込みもM4ではクラッシュを引き起こしましたが、長くなりましたが、すでに正しい値が含まれていたため、この書き込みはスキップする必要がありました。
2024-12-01 17:37 <yuka> この状態では動作するUSBプロキシが提供されることを確認できます。lsusbに「Generic m1n1 uartproxy v1.4.17-61-ga24ff77」が表示され、シェルを開くことができます :)
debug_putc
この時点で、私はMac miniに触れることなく長い時間が経過しました。2025年末のChaos Communication Congressで、新たなモチベーションを見つけました。ついに、CPUコアとAIC割り込みコントローラーのみを含む非常に最小限のデバイスツリーをハッキングし、earlyconパラメータを使用してLinuxカーネル(m1n1のlinux.pyを使用)をロードしましたが、「Vectoring to next stage」の後に何も出力が見られませんでした。
カーネルから有用な出力が得られなかったので、何が間違っているのか推測するしかありませんでした。私は総当たり攻撃を試みることにしました:昔ながらのprintlnデバッグです。m1n1からデバッグ_putcアセンブリルーチンを取り出し、1文字の「a」を出力するように調整しました4。これをLinuxカーネルコードのブートの非常に早い段階に挿入したところ、「Vectoring to next stage」の後に確かに「a」が得られました!
実質的に、Linuxブートコードをバイセクトし、MMU初期化コード(まだ非常に早い段階、arch/arm64/kernel/head.Sのアセンブリコード内)にたどり着きました。MMUの初期化がCPUをクラッシュさせていたのでしょうか?
そうではありませんでした。UARTはメモリマップドI/Oを使用してアクセスされます。MMUが有効になると、すべてのメモリアクセスは仮想アドレスにリダイレクトされ、ページテーブルを介して対応する物理アドレスにマッピングされます。m1n1はMMIOアドレス空間を同じ仮想アドレスで公開するマッピングを作成しますが、Linuxはこれを行わないため、MMUが有効になった後にUARTの代わりにマッピングされていない空間にアクセスすることになります。
初期ページテーブルを修正して、MMIO空間の1:1マッピングを追加したところ、MMUが有効になった後、デバッグ_putcはブートプロセスのはるか先まで動作するようになりました。再びバイセクトすると、割り込みコントローラーの初期化のどこかでプリントが機能するようになりました!
実装固有のCPUレジスタSYS_IMP_APL_VM_TMR_FIQ_ENA_EL2への書き込みに絞り込みました。これが新しいクラッシュを引き起こしました。この書き込みをコメントアウトした後、カーネルはシェルまでブートしました。やったー!
SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2は仮想化に関連しており、その後、新しいiBootバージョンでアンロックされたため、書き込みをコメントアウトする必要はなくなりました。
Linuxコードが実際に実行されていることがわかったので、earlyconブートパラメータがあったにもかかわらず、なぜ以前はシリアルコンソールにprintk出力が得られなかったのかを再度調べました。
2026-01-23 12:35 <yuka> earlycon=s5l,0x3ad200000 を my bootargs に追加しました
2026-01-23 12:36 <yuka> そして今、クラッシュが発生する前に有用な出力が得られています!
確かに、デバイスツリーにはstdout-path = "serial0" が欠けていただけでした。それを追加した後、初期のクラッシュからLinuxの完全なレジスタダンプとスタックトレースが得られました。
セカンダリコアとWFI
現時点では、m1n1はsmp_start_offsetが欠けていたため(m1n1にハードコードされたオフセットで、これがないとsmp_initはスキップされます)、セカンダリコアを開始しませんでした。ベースM1-M3で使用されていたオフセットを試したところ、セカンダリコアを開始することができました。再びLinuxをロードしようとすると、別の謎のクラッシュに遭遇しました。
以前のApple Silicon CPUには、WFI命令に関して既知の癖がありました。チキンビット(ベンダーが一部のCPU最適化や機能を無効にしたり、チキンアウトしたりできるレジスタ内のビット)ARM64_REG_CYC_OVRD_ok2pwrdn_force_maskの状態によっては、これらの以前の世代ではWFI命令によってCPUレジスタx0-x31がゼロになります。XNUは、WFIの前後にこれらのレジスタをスタックに保存し、その後復元します。M1-M3 SoCでは、m1n1はこの動作を無効にするため、CPUは他のarm64プロセッサのように動作します。Asahiカーネルは後に、CPUコアがより深いスリープ状態に達して電力を節約できるように、この動作を特別に再アクティブ化します。また、クラスター内の1つのコアが、他のすべてのコアがこのより深いWFIスリープ状態にあるときに、より高いクロックスピードにブーストできるようにするためにも必要です。
このチキンビットはロックされているか、M4では削除されたようで、デフォルトの動作はARM64仕様(特に「システムがWFI命令を完了できるように構成されている場合、WFI命令はアーキテクチャ状態の損失を引き起こしてはならない。」5)に準拠していません。
2026年4月、カーネル内のすべてのWFIおよびWFIT(Wait For Interrupt with Timeout)命令をNOP(no-op)に置き換えることで、M4でLinuxを全コア有効の状態で起動することに成功しました。これにより、WFI問題のアップストリームソリューションへの長い旅が始まりました。
当初、エラッタ(シリコンの誤動作)が通常どのように処理されるかを見ました。Linuxには、ブートの早い段階でメモリ内で自己修正するためのフレームワーク全体があります。しかし、WFIをNOPにする必要があるケースを正確に検出することが非常に困難であるため、このアプローチは却下されました。具体的には、macOSハイパーバイザー下で実行されている仮想マシンもエラッタロジックをトリガーしますが、WFIはmacOSハイパーバイザーによってトラップされ、異なるゲストを効率的にスケジュールするために使用されます。仮想化(特にネストされた仮想化が有効な場合)の検出も複雑であるため、Will Deaconは代替ソリューションを提案しました。カーネルは、新しいブート引数6を使用してWFIアイドルを無効にするサポートを獲得すべきであり、その後m1n1は、壊れたWFIを持つことが知られているベアメタルマシンで起動する際に、適切なブート引数を条件付きで追加できます。その後、Linuxがコアをスリープ状態にできるようにするメカニズムを追加します。現時点では、ダウンストリームのcpuidle-appleドライバーがこれに使用できますが、Svenの