osdev-jpでは、OS開発に有用な情報を収集し公開しています

View My GitHub Profile

PCI および PCIe についてのメモ書き

2016/05/02 by uchan

PCI を扱う重要性

PCI / PCIe は拡張カードを挿すだけでなく、オンボード NIC や SATA ディスクもつながる、重要なバス。 PCI デバイスは統一的な手法で識別できるので、PCI バスを一度扱えるようになると世界が広がる。

メモのスコープ

最終的に SATA ディスクを制御することを目標に、しかし知的好奇心が満たされるよう、最短経路よりは少し豊富な情報を提供する。 PCI ホストブリッジについて、PCI デバイスの列挙方法、PCI コンフィグレーションレジスタ、BAR とメモリマップといった話題を扱う。

本来 PCI バスはコンピュータ自体のアーキテクチャには依存しないが、ここでは PC/AT 互換機を前提として記述する。

PCI バスの信号タイミングなどの電気的特性の話は扱わない。

PCI ホストブリッジ

PCI ホストブリッジは CPU 側のバスと PCI バスを仲介する部品。

PCI デバイスは、制御のためのレジスタを I/O 空間やメモリ空間にマップして使う。したがって、メモリアドレス空間の中の特定のアドレス範囲を PCI バスへ転送するために、PCI ホストブリッジにはアドレス範囲を設定できる必要がある。さもないと、CPU がメインメモリを読み書きしようとしてホストブリッジが反応してしまい、大変なことになる。

Intel のチップセット(Mobile Intel GS45 Express →データシート)のホストブリッジでは TOLUD レジスタがその機能を提供する。大雑把に区分すると 0 から TOLUD が DRAM、TOLUD から 4GB が PCI や APIC などのメモリマップトレジスタとなっている。TOLUD は起動時に初期化プログラム(BIOS)が適切な値を設定することになっている(すなわち、BIOS はメモリの大きさを計測し、被らない位置に TOLUD を設定する)。

コンフィグレーションレジスタへのアクセス

Intel のチップセット仕様書に記載がある(Mobile Intel GS45 Express →データシート)。PC/AT 互換機はすべて同じはず。

I/O 空間にある DWORD 幅のレジスタ CONFIG_ADDRESS (0CF8h), CONFIG_DATA (0CFCh) を用いてコンフィグレーションレジスタを読み書きする。CONFIG_ADDRESS にアクセスしたいバス番号、デバイス番号、ファンクション番号、レジスタアドレスを書き込むと CONFIG_DATA がそのレジスタを読み書きする窓になる仕組み。

CONFIG_ADDRES (0CF8h, DWORD)

DROWD 幅でのみ読み書き可能。

ビット アクセス 初期値 意味
31 R/W 0 Configuration Enable ビット。1 を書き込むと PCI コンフィグレーション空間へのアクセスが有効になる。
30:24 RO 0 予約
23:16 R/W 0 バス番号
15:11 R/W 0 デバイス番号
10:8 R/W 0 ファンクション番号
7:2 R/W 0 レジスタアドレス
1:0 RO 0 予約

1:0 が 0 固定であることからも分かる通り、コンフィグレーションレジスタへのアクセスは DROWD 境界でのみ可能。

CONFIG_DATA (0CFCh, DWORD)

こちらは特に DROWD 幅のアクセスでなくても良い。CONFIG_ADDRESS[31] が 1 のとき、CONFIG_ADDRESS で指定された PCI デバイスのコンフィグレーションレジスタの内容を CONFIG_DATA レジスタを経由して読み書きできる。

PCI デバイスの発見

PCI デバイスを使おうと思ったら、そのバス番号とデバイス番号を特定する必要がある。チップセットに内蔵されているデバイスなら仕様書にそれぞれの番号が書いてあるが、OS をチップセットの種類に依存させたくないなら(大抵の場合はそうだろう)汎用的に番号を探す仕組みが必要だ。

BIOS や UEFI は PCI デバイスを発見する能力を持つため、それらの情報を取得して利用することができるらしい。ただ、uchan はそのやり方を知らない。

OSDev.org には PCI バスを自力でスキャンして PCI デバイスを列挙する方法が説明されている。Enumerating PCI Buses

BAR: ベース・アドレス・レジスタ

PCI デバイスは I/O 空間およびメモリ空間にレジスタ群を割り当ててアクセスする。マップするアドレスを設定するのがこの BAR。BAR はコンフィグレーションレジスタ群のうちの一つ。BIOS や UEFI が空いている I/O アドレス範囲やメモリアドレス範囲を探して BAR を設定することになっているので、OS は自分で設定しなおす必要はない。しなおしても良い。

PCI コンフィグレーション空間の 10h から 27h まで、24 バイト分の BAR 用領域が定義されている。24 バイトと書いたのは、32 ビット用の BAR も 64 ビット用の BAR もあり得るから。32 ビット用なら 6 本、64 ビット用なら 3 本の BAR ということになる。混在もできるが、そんなデバイスあるのか?

BAR は実は PCI デバイスが必要とする I/O またはメモリ領域のサイズを取得するのにも使える。必要とする領域の大きさの分だけ BAR の下位ビットが 0 で固定されているので、まず全ビットに 1 を書き込み、直後に読みだすと必要な領域の大きさを得ることができる。賢い設計だなあ…

ここから分かる通り、BAR には領域の大きさと同じ大きさにアラインされたアドレスしか設定することはできない(無理に設定しようとしても下位ビットが強制的に 0 になってしまう)。

メモリ用ベース・アドレス・レジスタ

その BAR がメモリ領域用か I/O 領域用か、32 ビットか 64 ビットかは、BAR の下位数ビットを見ると分かる。

メモリ用 BAR のビット配置を示す。

ビット アクセス 初期値 意味
31:N R/W 0 ベースアドレス
N-1:4 RO 0 領域の大きさを表す 0 固定ビット
3 RO x デバイスがホストに対し、プリフェッチを許可するかどうかを示す
2:1 RO x BAR のサイズと配置可能な場所を表す
0 RO 0 メモリ用 BAR であることを示す

N はデバイスが要求する領域の大きさを示す数字。例えば 1MB の領域が必要なデバイスを考える。1MB = 2^20 バイトなので N=20 となる。すなわち、31:4 の全ビットに 1 を書き込んでから読み戻すと、19:4 の範囲だけ 0 になる。

2:1 ビットの詳しい意味は次のとおり。(PCI ローカルバス規格 3.0 では 01 が予約となっているが、古い PCI 規格では「BAR は 32 ビット幅で、先頭から 1MB 以内の場所に配置しなければならない」という意味だった。)

2:1 の値 意味
00 BAR は 32 ビット幅で、32 ビット空間の任意の場所に配置してよい
01 予約
10 BAR は 64 ビット幅で、64 ビット空間の任意の場所に配置してよい
11 予約

64 ビットの BAR の場合、後に続く 32 ビットが上位ビットを表すことになっている。64 ビット BAR に対して必要な領域の大きさを計算するには、FFFFFFFFh を上下両方に書き込み、読み戻した値を結合して 64 ビット値として扱えばよい。CONFIG_DATA が 4 バイト幅しかないので、64 ビットに一気にアクセスすることはできない。

I/O 用ベース・アドレス・レジスタ

I/O 用 BAR のビット配置を示す。

ビット アクセス 初期値 意味
31:N R/W 0 ベースアドレス
N-1:2 RO 0 領域の大きさを表す 0 固定ビット
1 RO 0 予約
0 RO 1 I/O 用 BAR であることを示す

必要な領域の計算方法はメモリ用 BAR の場合と同じだが、I/O 用 BAR には 64 ビット幅のものは存在しない。

ベンダ ID など

ICH8 に搭載の SATA コントローラについて。

レジスタ名
Vendor ID 8086h
Device ID ICH のバージョンと、SATA コントローラの動作モードによって異なる
Revision ID ICH のリビジョンにより異なることがある
ベースクラス 01h = mass storage device
サブクラス MAP.SMS の設定により異なる
PI サブクラスの値により異なる

Devide ID, Revision ID については Intel® I/O Controller Hub 8 (ICH8) Family Specification Update に値が載っている。

MAP レジスタの SMS (SATA Mode Select) の値により、サブクラスコードが決まる。

ソフトウェアはデバイス動作中に SMS の値を変えてはいけない。すなわち OS やドライバは値を変えてはいけない。その代わり BIOS や UEFI が POST の段階で SMS の値を設定する。AHCI コントローラ用のドライバを書きたいのに、検出された PCI デバイスのサブクラスが 06h 以外になっていたら、BIOS や UEFI の設定を変えて AHCI モードにすればいいかもしれない。

SMS の値 動作モード 対応するサブクラスコード
00b IDE モード 01h
01b AHCI モード 06h
10b RAID モード 04h
11b 予約