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

View My GitHub Profile

AHCI (Advanced Host Controller Interface) についてのメモ書き

2016/05/05 by uchan

メモのスコープ

最終的に SATA ディスクを制御することを目標に、しかし知的好奇心が満たされるよう、最短経路よりは少し豊富な情報を提供する。 AHCI デバイスのメモリ空間、FIS の種類と構造、ATA コマンドの送受信方法、ATA コマンドの種類などを扱う。

SATA ディスクを読み書きするまでの道のり

  1. AHCI デバイスを発見する
    • ベースクラス 01h、サブクラス 06h の PCI デバイスを探す
  2. 各メモリマップを設定する
    • Command List や Received FIS 等のメモリ領域を設定する
  3. SATA デバイスを探す
    • 何番ポートにどんなディスクが接続されているか、されていないか
  4. READ DMA コマンドを準備する
    • Command List 上に READ DMA コマンドを構築する
  5. コマンドを送信する
    • PxCI レジスタのコマンドスロット番号に対応するビットに 1 を書く
    • 事前に PxCMD.ST を 1 にセットしておく必要がある
  6. 割り込みを待ってデータを受け取る
    • 割り込みを有効にしておけば、すべてのコマンドのやり取りが終わった後で割り込まれる
    • 割り込み後、PRDT に設定したメモリ領域にデータが格納されている

ABAR: AHCI デバイスのメモリ空間

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 Memory MapFull Size

HBA Memory Registers

AHCI コントローラを制御するためのレジスタ群である。PCI コンフィギュレーション空間にある ABAR によってメモリにマップしてアクセスする。32 ビット幅のレジスタの集合。

HBA Memory Registers は大きく Generic Host Control レジスタと Port Control レジスタに分かれる。前者は名前の通り AHCI コントローラ全体に影響するレジスタだ。後者は同じ構造のレジスタが SATA ポートごとに一組用意されている。規格上は最大 32 ポートまで扱えるが、実際に使える数は AHCI コントローラに依存する。

Offset 00h: CAP - HBA Capabilities

この 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 ポートはサポートしなくてはならない。

Offset 04h: GHC - Global HBA Control

HBA のグローバルな設定などを行うレジスタ。

次に主なビットについて示す。カッコ内はビット番号。

AE(31) は HBA の動作モードを取得または設定する。1 なら AHCI モード、0 ならレガシーモード(IDE モード)。レガシーモードをサポートしない HBA の場合、リードオンリーで 1 固定。

IE(1) は割り込みが有効であることを示す。1 なら有効。

HR(0) はリセット用ビット。1 を書くと内部リセットが発生し、データ転送およびキューイングに関するすべての状態が初期化される。

Offset 0Ch: PI - Ports Implemented

ソフトウェアから使えるポート(実装されたポート)に対応するビットが 1 となる、リードオンリーなレジスタ。1 の個数は CAP.NP+1 を超えてはいけないが、それより少ないことはあり得る。

Port Registers

ポート毎に存在するレジスタ群。レジスタ名の「x」にはポート番号 0 から 31 が入る。

Offset 00h: PxCLB - Port x Command List Base Address

Command List という構造体が置かれているメモリ領域を指すレジスタ。物理メモリアドレスを格納する。その領域は 1K バイトにアラインされている必要があるので、ビット 9:0 は 0 固定。

64 ビットアドレッシングがサポートされている場合、上位 32 ビットは Offset 04h にある PxCLBU レジスタに格納する。

Offset 08h: PxFB - Port x FIS Base Address

Received FIS という構造体が置かれているメモリ領域を指すレジスタ。物理メモリアドレスを格納する。その領域は 256 バイトにアラインされている必要があるので、ビット 7:0 は 0 固定。

64 ビットアドレッシングがサポートされている場合、上位 32 ビットは Offset 0Ch にある PxFBU レジスタに格納する。

Offset 10h: PxIS - Port Interrupt Status

割り込み状態を示すフラグの集合。各ビットの初期値は 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、すなわち「デバイスからホスト」方向という意味)

Offset 20h: PxTFD - Port x Task File Data

HBA が FIS を受信したときに、FIS の対応するフィールドをここにコピーする。このフィールドを持つのは次の 3 つ。

ERR(15:08) はタスクファイルの error レジスタのコピーを持つ。

STS(07:00) はタスクファイルの status レジスタのコピーを持つ。STS はビットごとに意味がある。

Offset 34h: PxSACT - Port x Serial ATA Active (SCR3: SActive)

ネイティブコマンドキューイング対象のコマンドの実行完了を知るためのレジスタ。ビット 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 を書き込む。

Offset 38h: PxCI - Port x Command Issue

Command List の中でデバイスに送信するコマンドスロットを選択するレジスタ。ビット 0 がコマンドスロット 0 に対応する。ソフトウェアは、あるコマンドスロットの送信準備が完了したことを示すために 1 を書き込む。そのコマンドの BSY, DRQ, ERR ビットをクリアするような FIS を HBA が受け取ると、HBA が 0 を書き込む。

Command List

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 コマンドをデバイスに送る手順は次。

  1. Command List の空いているスロットに好きなだけコマンドを構築する
  2. PxCI の対応するビットに 1 を書き込む
  3. HBA が自動的にコマンドをデバイスに送り、応答を待つ

Command Header

Command Header は Command List の各要素の名前である。

1 つの Command Header は 1 つの Command Table を指す。Command List を Command Table の配列としなかったのは、Command Table 構造はサイズが可変で、配列の要素にはできないという事情があると思う。

次に Command Header が含むレジスタ群の説明をする。

Offset 00h: PRDTL など

この 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 の大きさを知る。

Command Table

デバイスへ送る 1 つの SATA コマンドと、ホストとデバイス間で送受信するデータを置く場所を指定する PRDT からなる。SATA コマンドの戻り値は Command Table ではなく、PxFB で指示された Received FIS に書かれる。

次に Command Table が含む、2 つのよく使う領域 CFIS と PRDT を説明する。

CFIS: Command FIS

1 つの FIS を置く領域。

PRDT: Physical Region Descriptor Table

デバイスに書き込むデータやデバイスから読み込んだデータを置くメモリ領域を指定する。0 個から 65535 個までの要素を持つ配列で、1 つの要素は 4 つのダブルワードレジスタからなる。

個々の要素は 1 つのメモリ領域(データブロック)の先頭アドレスとサイズを指定する。それぞれの要素で指定されるメモリ領域は互いに連続している必要はない。細切れのメモリ領域を 1 つのデータセットとして扱えるというわけだ。

Offset 00h: DBA - Data Base Address

データブロックの先頭を指す 32 ビットの物理メモリアドレス。2 バイト境界にアラインされている必要がある。

Offset 04h: DBAU - Data Base Address Upper

データブロックの先頭アドレスの上位 32 ビット。HBA が 64 ビットアドレッシングをサポートしている(CAP.S64A が 1)ときのみ有効。

Offset 0Ch (bit 31): I - Interrupt on Completion

セットすると、このエントリのデータの送受信が終わったときに割り込みが発生するらしい。データセット全体の送受信完了ではなく、あくまでこの PRD エントリの完了を知らせるようだ。詳しくは分からない。

Offset 0Ch (bit 21:00): DBC - Data Byte Count

データブロックのバイト数。

Received FIS

デバイスから送られてきた FIS を格納する領域である。FIS については後で詳しく述べる。

Port Registers の一つである PxFB から指し示される。すなわち、1 ポートにつき 1 つの Received FIS が存在する。

IDENTIFY DEVICE コマンドの実行過程を例に Received FIS の使われ方を見てみる。

  1. ホストがデバイスに IDENTIFY DEVICE をセットした Register FIS を送信する
  2. デバイスが 256 バイトの情報を内部バッファに用意する
  3. デバイスがホストに、データの準備完了とデータサイズを伝えるため PIO Setup FIS を送信する
  4. PIO Setup FIS の送信に成功したら、続けてデバイスが DATA FIS を送信する

この中で 3 でホストが受信した PIO Setup FIS が Received FIS 領域に書き込まれる。4 でホストが受信する DATA FIS は Received FIS 領域には書き込まれず、直接 PRD で指定されたメモリ領域へ書き込まれる。

FIS

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 - Host to Device

ホストからデバイス方向の Register FIS の各フィールドを説明する。

TODO: SATA Revision 3.0 の図 10.3.4 を貼り付ける

LBA

LBA (Logical Block Address) を表すレジスタ。

Device

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 コード例 でいくつかのコード例を紹介する。