SystemIOの競合はファームウェアのバグではない
SystemIO conflicts are not firmware bugs (codon.org.uk)
要約
この記事は、ACPI(Advanced Configuration and Power Interface)におけるSystemIOの競合警告が、一般的にファームウェアのバグではなく、ACPIの仕組みとハードウェア抽象化の性質に起因することを解説しています。ACPIのOperationRegionとOSドライバー間の競合が発生するメカニズムと、その解決策について説明しています。
全文翻訳
SystemIOの競合はファームウェアのバグではない
全く別のことを調べていたのですが、検索結果で、ACPI Warning: SystemIO range 0x0000000000001828-0x000000000000182F conflicts with OpRegion 0x0000000000001800-0x000000000000187F のようなエラーがファームウェアのバグを示していると考えている人がまだ多くいることに気づきました。これは一般的に正しくありません。なぜそうなのかを明確にするために、ACPIについて少し掘り下げる必要があります。
Advanced Configuration and Power Interface[1] 仕様は多くのことを定義していますが、ここで興味深いのはそれが実行するハードウェア抽象化です。PCは名目上はよく定義されたプラットフォームですが、ある程度の複雑さを超えると、ハードウェアレベルでは実際にはそうではありません。例えば、システムをサスペンドする際にはハードウェアを正しい順序で電源オフにしたいと思いますが、その順序を知るには特定のマザーボード設計の詳細を知る必要があります。組み込みの世界でのアプローチは、その知識を何らかの形でOSに組み込むことであり、それがDevicetreeの登場につながりました。ACPIは別の方法を取ります。この情報をOSドライバーが消費する必要のあるデータとして提供するのではなく、コードとして配布します。
ACPI Source Language、略してASLは、実行時にOSが解釈するバイトコードにコンパイルされるシンプルな言語です。この言語の機能の1つは、「Operation Region」を定義する能力です。これは実質的に、基盤となるハードウェアへのアクセスを記述する構造定義です。2つの公開レジスタを持つシンプルなデバイスを想像してみましょう。最初のレジスタはインデックスレジスタで、アクセスしたい内部レジスタのアドレスを指定します。2番目のレジスタはデータレジスタで、これを読み取るとインデックスレジスタに現在あるアドレスの内部レジスタの値が得られ、書き込むとレジスタが変更されます。操作領域宣言の例は次のようになります。
OperationRegion(OPR1, SystemIO, 0x400, 0x2)
Field(OPR1, ByteAcc, NoLock, Preserve)
{
INDX, 8
DATA, 8
}
これは、IOポート0x400、長さ2バイトの「OPR1」という名前の操作領域を定義します。その内部には、INDXとDATAという2つの8ビットフィールドがあります。これらは一度に1つずつアクセスされ、アクセス時にACPIインタプリタがグローバルロックを取得する必要はなく、レジスタの一部が変更された場合でも他の値は保持されます(この場合はフィールドが1バイト幅なので関係ありません)。これで、このスコープ内のINDXまたはDATAへの参照は、これらのレジスタへのアクセスをトリガーします。したがって、レジスタ0x03の値を読み取るメソッドは次のようになります。
Method (RD03) {
INDX = 0x3
Return (DATA)
}
つまり、INDXを3に設定し、DATAの値を読み取って返します。しかし!もし別のACPIメソッドが同時に実行されていたらどうなるでしょうか?レジスタ0x05に書き込むメソッドがあるとしましょう。
Method (WR05, 1) {
INDX = 0x05
DATA = Arg1
}
WR05の途中でRD03が実行されたらどうなるでしょうか?INDXは0x03にリセットされ、WR05はレジスタ0x05ではなく0x03を変更してしまいます。大変だ!しかし、これを回避できます。ミューテックス(Mutex (MUTX, 0x00))を宣言し、メソッドを次のように更新します。
Method (RD03) {
Acquire (MUTX, 0xFFFF)
INDX = 0x3
Local0 = DATA
Release (MUTX)
Return (Local0)
}
Method (WR05, 1) {
Acquire (MUTX, 0xFFFF)
INDX = 0x05
DATA = Arg1
Release (MUTX)
}
各メソッドはロックを取得し(最大0xffffミリ秒待機し、失敗した場合はエラーを返します)、アクセスを実行します。これで競合の可能性はなくなりました。ふう!
ここで、誰かがこのハードウェアのLinuxドライバーを書いたとします。それはACPIを全く知らずにハードウェアに直接アクセスします。ドライバーがACPIのアクセスメソッドと競合するのを止めるものは何でしょうか?何もありません。また大変だ!これは仮説ではありません。比較的無害な例ですが、かつては温度監視チップがファームウェアとLinuxによって同時にアクセスされ、結果として温度を読み取っていると思ったら実際にはステータスフラグを読み取っており、ありえないほど高い温度と即時のサーマルシャットダウンにつながるケースに遭遇しました。
この場合、カーネルはACPI Warning: SystemIO range 0x000000000000400-0x000000000000401 conflicts with OpRegion 0x000000000000400-0x000000000000401 (OPR1)のようなメッセージを表示してこれを(潜在的にハードウェアを損傷する可能性のある)結果から救います。これは、カーネルがドライバーがIOポート0x400-0x401を割り当てようとしていることを検出したが、同じアドレスを主張するOPI1という名前のACPI操作領域があることを示しています。カーネルは、ファームウェアがその領域でどのような種類のアクセスを行う可能性があるかを知る立場にないため、危険であるとみなし、ドライバーのロードをブロックします。
しかし、すべてが失われたわけではありません!カーネルはまた、役立つアドバイスを表示します。ACPI: If an ACPI driver is available for this device, you should use it instead of the native driver。そして、ACPIテーブルにはしばしば次のような定義が含まれています。
Device (HDW1)
{
Name (_HID, "VEND0001")
OperationRegion(OPR1, SystemIO, 0x400, 0x2)
Field(OPR1, ByteAcc, NoLock, Preserve)
{
INDX, 8
DATA, 8
}
Mutex (MUTX, 0)
Method (RD03) {
Acquire (MUTX, 0xFFFF)
INDX = 0x3
Local0 = DATA
Release (MUTX)
Return (Local0)
}
Method (WR05, 1) {
Acquire (MUTX, 0xFFFF)
INDX = 0x05
DATA = Arg1
Release (MUTX)
}
}
これはACPIデバイスとその関連メソッドを定義します。_HIDフィールドはデバイスタイプを定義し、LinuxドライバーはタイプVEND0001のデバイスが見つかった場合に自動的にロードされるように書くことができます。そのドライバーは、デバイスに関連付けられたACPIメソッドを呼び出し、ファームウェアの期待に沿った方法でリソースにアクセスできます。
(このようなドライバーの作成に興味がありますか?私は2009年にガイドを書きました)
ファームウェアはここで行ったことについて全く何も間違っていませんでしたが[2]、ネイティブドライバーをロードしようとするとエラーが発生し、インターネットはPCファームウェア開発者が無能であると主張し[3]、この動作をオーバーライドするカーネル引数を渡すべきだと言います。そして、それは彼らに決して害を及ぼさず、あなたにも害を及ぼさない可能性が高いですが、それは害を及ぼす可能性があり、システムが時々フリーズしたり発火したりする理由をあなたは決して知らないかもしれません。
- ACPI仕様は以前は
acpi.infoにありましたが、残念ながらUEFIが仕様の管理を引き継いだ後、いつの間にか消えてしまったようです [↩︎] - ファームウェアは実行時に何もすべきではないと主張するかもしれません。それはファームウェアの仕事ではないからです。私もそれを理解しており、必要であれば
acpi=offで起動することもできます。その結果を教えてください。[↩︎] - ここではそれについての意見は述べませんが、これはその主張を裏付ける証拠を提供しないと言うだけです [↩︎]