osdev-jpでは、OS開発に有用な情報を収集し公開しています
AHCI (Advanced Host Controller Interface) についてのメモ書き
2016/05/05 by uchan
最終的に SATA ディスクを制御することを目標に、しかし知的好奇心が満たされるよう、最短経路よりは少し豊富な情報を提供する。 AHCI デバイスのメモリ空間、FIS の種類と構造、ATA コマンドの送受信方法、ATA コマンドの種類などを扱う。
AHCI の機能は PCI デバイスとして実装される。そのため、もちろん PCI コンフィグレーションレジスタを持つ。6 本ある 32 ビット BAR のうち、最後の 1 本(オフセット 24h)が AHCI の制御に使われる BAR である。これを ABAR (AHCI Base Address Register) と呼ぶ。
他の BAR はオプショナルであり AHCI には関係ない。AHCI に対応しない古いソフトウェアをサポートするため、IDE コントローラを実装するのに使われたりする。
すべての BAR は BIOS や UEFI が適切な値を設定しているはずなので、自作 OS 側では何も設定する必要はない。cf) PCI Memo
ABAR で指される領域に AHCI コントローラ(HBA: Host Bus Adapter)を制御するレジスタ群がある。さらにその中にもアドレスを格納するレジスタがあり、いろいろなメモリ領域が数珠つなぎになっている。その様子を図示する。
AHCI コントローラを制御するためのレジスタ群である。PCI コンフィギュレーション空間にある ABAR によってメモリにマップしてアクセスする。32 ビット幅のレジスタの集合。
HBA Memory Registers は大きく Generic Host Control レジスタと Port Control レジスタに分かれる。前者は名前の通り AHCI コントローラ全体に影響するレジスタだ。後者は同じ構造のレジスタが SATA ポートごとに一組用意されている。規格上は最大 32 ポートまで扱えるが、実際に使える数は AHCI コントローラに依存する。
この HBA が持つ能力を示すためのレジスタ。64 ビットアドレッシングが可能か(ビット 31)とか、ネイティブなコマンドキューをサポートしているか(ビット 30)など、ビットごとに各機能のサポート状況を示す。
次に主なビットについて示す。カッコ内はビット番号。
S64A(31) はこの HBA が 64 ビットアドレッシングをサポートしていることを示す。
SAM(18) はこの HBA が AHCI モードだけをサポートしていることを示す。1 なら AHCI モードのみ、0 ならレガシーモードもサポートする。
NP(4:0) はこの HBA のシリコンチップがサポートする SATA ポートの最大数-1 を表す。0h なら 1 ポートだけサポートする。1fh なら 32 ポートをサポートする。どんな HBA も最低 1 ポートはサポートしなくてはならない。
HBA のグローバルな設定などを行うレジスタ。
次に主なビットについて示す。カッコ内はビット番号。
AE(31) は HBA の動作モードを取得または設定する。1 なら AHCI モード、0 ならレガシーモード(IDE モード)。レガシーモードをサポートしない HBA の場合、リードオンリーで 1 固定。
IE(1) は割り込みが有効であることを示す。1 なら有効。
HR(0) はリセット用ビット。1 を書くと内部リセットが発生し、データ転送およびキューイングに関するすべての状態が初期化される。
ソフトウェアから使えるポート(実装されたポート)に対応するビットが 1 となる、リードオンリーなレジスタ。1 の個数は CAP.NP+1 を超えてはいけないが、それより少ないことはあり得る。
ポート毎に存在するレジスタ群。レジスタ名の「x」にはポート番号 0 から 31 が入る。
Command List という構造体が置かれているメモリ領域を指すレジスタ。物理メモリアドレスを格納する。その領域は 1K バイトにアラインされている必要があるので、ビット 9:0 は 0 固定。
64 ビットアドレッシングがサポートされている場合、上位 32 ビットは Offset 04h にある PxCLBU レジスタに格納する。
Received FIS という構造体が置かれているメモリ領域を指すレジスタ。物理メモリアドレスを格納する。その領域は 256 バイトにアラインされている必要があるので、ビット 7:0 は 0 固定。
64 ビットアドレッシングがサポートされている場合、上位 32 ビットは Offset 0Ch にある PxFBU レジスタに格納する。
割り込み状態を示すフラグの集合。各ビットの初期値は 0 で、割り込み状態を示すために 1 になる。 一旦 1 にセットされたら、ソフトウェアから 1 を書き込むと 0 にクリアされる。(0 を書き込むのではないので注意!)
次に主なビットについて示す。カッコ内はビット番号。
TFES(30) はタスクファイルのステータスレジスタが更新されると同時に書き換わり、ステータスレジスタのエラービットが 1 のとき 1 となる。
OFS(24) はオーバーフローを示す。HBA がデバイスから PRD テーブルで指定されたサイズより大きいバイト列を受け取った時に 1 になる。
DPS(5) は I ビットがセットされた PRD エントリのデータ転送が完了したことを示す。
SDBS(3) は I ビットがセットされた Set Device Bits FIS を受信し、システムメモリへのコピーが完了したことを示す。
この I ビットと DPS の説明に出てくる I ビットは違うものである。この I ビットはデバイスからホストに送られた FIS の中にあるビットを意味する。
DSS(2) は I ビットがセットされた DMA Setup FIS を受信し、システムメモリへのコピーが完了したことを示す。
PSS(1) は I ビットがセットされた PIO Setup FIS を受信し、FIS 構造と付随するデータのシステムメモリへのコピーが完了したことを示す。 データ転送がエラーになってもセットされる。
DHRS(0) は I ビットがセットされた D2H Register FIS を受信し、システムメモリへのコピーが完了したことを示す。(D2H は Device to Host、すなわち「デバイスからホスト」方向という意味)
HBA が FIS を受信したときに、FIS の対応するフィールドをここにコピーする。このフィールドを持つのは次の 3 つ。
ERR(15:08) はタスクファイルの error レジスタのコピーを持つ。
STS(07:00) はタスクファイルの status レジスタのコピーを持つ。STS はビットごとに意味がある。
ネイティブコマンドキューイング対象のコマンドの実行完了を知るためのレジスタ。ビット 0 がコマンドスロット 0 に対応する。
コマンドを発行する前にソフトウェアが 1 にセットし、デバイスからの Set Device Bits FIS を受けて 0 にクリアされる。具体的には、Set Device Bits FIS の SActive フィールドのうち、1 になっているビットに対応する SACT のビットが HBA によりクリアされる。
SACT のビット番号を TAG とも呼ぶ。TAG はそのまま「タグ」の意味で、キューされたコマンドを識別する番号である。キューに積んでから実行が完了するまでが非同期なので、タグをつけて識別するのだと筆者は理解している。
式を用いて具体的に書くとこうなる: PxCI[TAG]
に 1 を書き込む前に、ソフトウェアはタグ番号 TAG を持つコマンドが未処理であることを示すために PxSACT[TAG]
に 1 を書き込む。
Command List の中でデバイスに送信するコマンドスロットを選択するレジスタ。ビット 0 がコマンドスロット 0 に対応する。ソフトウェアは、あるコマンドスロットの送信準備が完了したことを示すために 1 を書き込む。そのコマンドの BSY, DRQ, ERR ビットをクリアするような FIS を HBA が受け取ると、HBA が 0 を書き込む。
Command List はその名の通り、複数のコマンドを格納するリストである。SATA ディスクは内部にコマンドキューを持っていて、読み書き命令の実行順を最適化する機能を持つ。それに合わせ、HBA にも複数のコマンドを一度に送信する仕組みがあるのだ。
Command List は要素数 32 の Command Header の配列である。1 つの Command Header は 1 つの SATA コマンドに対応する。
Command Header は CTBA0 レジスタを通して Command Table 構造を指し示す。Command Table の中に実際の SATA コマンド(”IDENTIFY DEVICE” など)を構築する。図中の CFIS がコマンドを構築するメモリ領域である。
Command List の各要素を Command Slot と呼ぶこともある。32 個のスロットのうち、空いている任意のスロットにコマンドを構築し… というような文脈で「スロット」と呼ぶことが多いように思う。
SATA コマンドをデバイスに送る手順は次。
Command Header は Command List の各要素の名前である。
1 つの Command Header は 1 つの Command Table を指す。Command List を Command Table の配列としなかったのは、Command Table 構造はサイズが可変で、配列の要素にはできないという事情があると思う。
次に Command Header が含むレジスタ群の説明をする。
この 4 バイトはビットごとに名前がついており、4 バイト全体を表すレジスタ名はない。
PRDTL(31:16) - Physical Region Discriptor Table Length は PRDT の要素数を設定するレジスタである。HBA はこのレジスタを見て PRD のフェッチをいつ止めるか判断する。0 はコマンドによるデータ転送が全くないことを意味する。
C(10) - Clear Busy upon R_OK を 1 に設定すると、FIS の送信が終わり、R_OK を受信した後に HBA が PxTFD.STS.BSY ビットと、そのコマンドスロットに対応する PxCI のビットクリアする。
W(6) - Write はデータ送受信の方向を設定する。1 はデバイスへ書き込む方向(システムメモリからデバイスへのデータ転送)を意味する。0 はその逆。
CFL(4:0) - Command FIS Length はコマンド FIS の大きさを DW 単位で設定するレジスタである。0 または 1 を設定してはいけない。最大値は 16 である。HBA はこのレジスタを見て送信する FIS の大きさを知る。
デバイスへ送る 1 つの SATA コマンドと、ホストとデバイス間で送受信するデータを置く場所を指定する PRDT からなる。SATA コマンドの戻り値は Command Table ではなく、PxFB で指示された Received FIS に書かれる。
次に Command Table が含む、2 つのよく使う領域 CFIS と PRDT を説明する。
1 つの FIS を置く領域。
デバイスに書き込むデータやデバイスから読み込んだデータを置くメモリ領域を指定する。0 個から 65535 個までの要素を持つ配列で、1 つの要素は 4 つのダブルワードレジスタからなる。
個々の要素は 1 つのメモリ領域(データブロック)の先頭アドレスとサイズを指定する。それぞれの要素で指定されるメモリ領域は互いに連続している必要はない。細切れのメモリ領域を 1 つのデータセットとして扱えるというわけだ。
データブロックの先頭を指す 32 ビットの物理メモリアドレス。2 バイト境界にアラインされている必要がある。
データブロックの先頭アドレスの上位 32 ビット。HBA が 64 ビットアドレッシングをサポートしている(CAP.S64A が 1)ときのみ有効。
セットすると、このエントリのデータの送受信が終わったときに割り込みが発生するらしい。データセット全体の送受信完了ではなく、あくまでこの PRD エントリの完了を知らせるようだ。詳しくは分からない。
データブロックのバイト数。
デバイスから送られてきた FIS を格納する領域である。FIS については後で詳しく述べる。
Port Registers の一つである PxFB から指し示される。すなわち、1 ポートにつき 1 つの Received FIS が存在する。
IDENTIFY DEVICE コマンドの実行過程を例に Received FIS の使われ方を見てみる。
この中で 3 でホストが受信した PIO Setup FIS が Received FIS 領域に書き込まれる。4 でホストが受信する DATA FIS は Received FIS 領域には書き込まれず、直接 PRD で指定されたメモリ領域へ書き込まれる。
FIS (Frame Information Structure) は SATA においてホストとデバイス間のデータ転送の基本単位である。SATA は Parallel ATA と同じコマンド体系であり、FIS は ATA コマンドを包み込んだものと捉えることもできる。
FIS には何種類かある。先頭の数値は FIS の種類を表す 1 バイトの値で、FIS 構造の先頭の DWORD のバイト 0 にある。
FIS タイプ値 | FIS の説明 | 送受信方向 |
---|---|---|
27h | Register FIS | ホストからデバイス |
34h | Register FIS | デバイスからホスト |
39h | DMA Activate FIS | デバイスからホスト |
41h | DMA Setup FIS | 両方向 |
46h | Data FIS | 両方向 |
58h | BIST Activate FIS | 両方向 |
5Fh | PIO Setup FIS | デバイスからホスト |
A1h | Set Device Bits FIS | デバイスからホスト |
ホストからデバイス方向の Register FIS の各フィールドを説明する。
TODO: SATA Revision 3.0 の図 10.3.4 を貼り付ける
LBA (Logical Block Address) を表すレジスタ。
ATA 規格における Device/Head レジスタのこと。ビットごとに次のような意味がある。ビット 4 以外はコマンドによって意味が変わるが、ここでは代表的な意味を記した。詳しくは AT Attachment with Packet Interface - 7 Volume 1 を参照(リンクは無料のドラフト版)。
LBA(6) を 1 にすると LBA 方式でのアドレッシングを使うことを示す。0 にすると CHS 方式でのアドレッシングとなる。現代において CHS を使うことはほぼないはずだ。
DEV(4) はマスター/スレーブの選択ビットであるが、SATA では使われないはず。0 にしておく。
HEAD(3:0) は CHS アドレッシングにおける Head アドレスを指定する。LBA モードでは使わない。
AHCI コード例 でいくつかのコード例を紹介する。