Skip to content

Instantly share code, notes, and snippets.

@ytakano
Created March 17, 2025 07:38
Show Gist options
  • Save ytakano/af34da9307f148aeeb89da1835e16e28 to your computer and use it in GitHub Desktop.
Save ytakano/af34da9307f148aeeb89da1835e16e28 to your computer and use it in GitHub Desktop.
i225 (ChatGPT)

Intel I225のレジスタマップとPCIeのBase Address Register(BAR)の関係について調査します。

また、OpenBSDのソースコード(if_igc.c)を解析し、I225のレジスタ操作の実装についても詳しく調べます。

調査結果がまとまり次第、お知らせします。

I225のレジスタマップ概要: Intel I225 EthernetコントローラはPCI Expressデバイスとして実装されており、主要なレジスタ群はPCI構成空間のBase Address Register (BAR0)によって指し示されるメモリ空間にメモリマップされています (/usr/src/sys/dev/pci/if_igc.c)。BAR0に割り当てられた物理メモリアドレス領域(デバイスのレジスタマップ)は、デバイス制御・ステータス、送受信制御、割り込み制御、DMAリングバッファ設定など多数のレジスタで構成されています。OpenBSDドライバではpci_mapreg_map関数を用いてこのBAR0領域(オフセット0x10)をカーネル仮想空間にマッピングし (/usr/src/sys/dev/pci/if_igc.c)、bus_space経由で読み書きアクセスします。例えばIGC_READ_REG/IGC_WRITE_REGマクロにより、bus_space_read_4/bus_space_write_4を用いてメモリマップされたレジスタにアクセスしています (src/sys/dev/pci/if_igc.h at cc53d18db392718e57448775d53ed87ec3704753 - SecBSD/src - LAYLO Code: Securing your code)。以下に主要なレジスタと機能を示します。

  • デバイス制御レジスタ (CTRL: オフセット0x00000) – デバイス全体の制御ビットを持つ32bitレジスタです。ソフトウェアからのデバイスリセット(GLobal reset)やPHYリセット、リンクの強制アップ(Set Link Up)、通信速度/全二重の強制設定などを制御します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。例えばビット31はPHYリセット、ビット26はデバイス全体のリセット(RST)を意味します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。またビット30のIGC_CTRL_VME (0x40000000)は「VLANモード有効化」を表し、VLANタグの処理をハードウェアに有効にする機能です (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。IGC_CTRL_RFCE/TFCEビットでフロー制御(受信/送信PAUSEフレーム)の有効化も設定できます (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。

  • デバイスステータスレジスタ (STATUS: オフセット0x00008) – 現在のリンク状態や速度、通信モードなどを示す読み取り専用の32bitレジスタです。例えばビット0は全二重モード(1ならFull Duplex)、ビット1はリンクアップ状態(1ならリンク確立)を示します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。さらにビット6・7・22などでリンク速度が100Mb/s、1000Mb/s、2.5Gb/sであることを示すフラグが立ちます (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。ドライバはこのSTATUSレジスタを読み取ることでリンクの有無や速度を検出します(OpenBSDドライバでもigc_update_link_status内で利用)。

  • 受信制御レジスタ (RCTL: オフセット0x00100) – 受信ユニットを制御する32bitレジスタです。ビット1のIGC_RCTL_EN (0x2)で受信エンジンの有効/無効を切り替え、ビット3のSBPでエラーパケットをメモリに保存するかを制御します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。またビット2のUPE (Unicast Promiscuous Enable, 0x8)やビット4のMPE (Multicast Promiscuous Enable, 0x10)をセットすることで、MACアドレスフィルタを無視して全てのユニキャスト/マルチキャストパケットを受信するプロミスキャスモードを制御できます (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。ビット5のLPE (0x20)は巨大小パケット受信(Long Packet Enable)の許可、ビット15のBAM (0x8000)は全ブロードキャストパケット受信許可を意味します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。その他、受信バッファサイズやFIFO閾値などの設定フィールドも含みます。RCTLの各種ビット設定により受信の動作モード(例えばCRCストリップの有効化IGC_RCTL_SECRCなど)を細かく制御します (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。

  • 送信制御レジスタ (TCTL: オフセット0x00400) – 送信ユニットを制御する32bitレジスタです。ビット1のIGC_TCTL_EN (0x2)で送信エンジンの有効化、ビット3のPSP (Pad Short Packets, 0x8)で短いフレームに自動パディング挿入の有効化を行います (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。また、IGC_TCTL_CT (Collision Threshold)フィールド(ビット4-11, 0x00000FF0)でコリジョン発生時の再送閾値を設定し、IGC_TCTL_RTLC (ビット24, 0x01000000)をセットするとレイトコリジョン時の自動再送を有効にします (/usr/src/sys/dev/pci/if_igc.c) (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。ドライバはこれらを適切に設定して送信動作を最適化します(デフォルトではコリジョン閾値15回など (/usr/src/sys/dev/pci/if_igc.c))。

  • 割り込み制御レジスタ – I225ではMSI-Xをサポートしており、レジスタ体系としてレガシー割り込み用のICR/ICS/IMS/IMCや、MSI-X用のExtended Interrupt Cause/Maskセット(EICR/EICS/EIMS/EIMC)があります。例えばICR(Interrupt Cause Register)は現在発生している割り込み要因を示し、IMS(Interrupt Mask Set)は割り込みマスクの設定に使われます。OpenBSDドライバではリンクステータス変化割り込みを有効化するためIMSレジスタにIGC_IMS_LSCビット(0x4)を書き込み (/usr/src/sys/dev/pci/if_igc.c)、リンク変化時にICRの該当ビットを検出して処理しています(LSCビットのチェック (/usr/src/sys/dev/pci/if_igc.c))。MSI-Xを用いる場合、各キュー毎に割り込みを割り当てるためIVAR/IVAR0-3GPIE(General PCI Interrupt Enable)レジスタも設定します。IGC_GPIEではMSI-Xモード有効化(IGC_GPIE_MSIX_MODE)や自動EICR割り当て(IGC_GPIE_EIAME)、割り込みスロットル無効(IGC_GPIE_NSICR)等のフラグを設定可能で、OpenBSDドライバはデバイス初期化時にこれらのビットを設定しています (/usr/src/sys/dev/pci/if_igc.c)。

  • DMAリング関連レジスタ – 送受信キューそれぞれに対し、DMAで利用する descriptor ring の物理アドレスおよびサイズを指定するレジスタがあります。受信キューiについてはRDBAL(i) (Receive Descriptor Base Address Low)およびRDBAH(i)にリングバッファの物理アドレス下位32bit/上位32bitを設定し、RDLEN(i)にリング長(バイト単位)を設定します。送信キューについてはTDBAL/TDBAH/TDLENが同様に存在します。キュー0~3まではそれぞれのベースレジスタが0x02800~0x02BFF(受信)および0x03800~0x03BFF(送信)の範囲に配置され、キュー番号に応じて+0x100間隔で次のキューのレジスタが並びます (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)(4番以降のキューは別のオフセット範囲0x0C000~や0x0E000~に配置 (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c))。さらに各キューにはRDH/TDH(Head index)とRDT/TDT(Tail index)レジスタがあり、NICが処理した最新のdescriptor位置(Head)と、ソフトウェアが供給した最後のdescriptor位置(Tail)を示します。これらのレジスタを介してドライバとNIC間でdescriptorのやり取り(バッファリング制御)を行います。

  • PBA (Packet Buffer Allocation)レジスタ – NIC内部のパケットバッファの配分を決めるレジスタです。値の下位16ビットで受信側に割り当てるメモリ容量を決定し、残りを送信側のFIFOに充てます (/usr/src/sys/dev/pci/if_igc.c)。例えばOpenBSDドライバではPBAに0x0022を書き込み(10進で34 = 34KB相当)受信バッファに34KBを割り当て、残りを送信バッファとする設定を行っています (/usr/src/sys/dev/pci/if_igc.c)。この設定に基づきフロー制御の高水位/低水位も計算され、後述のDMA Coalescing機能(DMAC)などでも使用されます (/usr/src/sys/dev/pci/if_igc.c)。

以上のように、I225のレジスタはBAR0経由でCPUからメモリマップドI/Oとしてアクセスでき、ドライバは適切なオフセットに読み書きすることでデバイスを制御します。

OpenBSD版igc(4)ドライバ(if_igc.c)におけるレジスタ操作: OpenBSDのif_igcドライバでは、PCIeデバイスであるI225に対し以下のような手順でレジスタアクセスと設定を行っています。

  • BARのマッピング: デバイスアタッチ時に、pci_mapreg_map()を使ってPCI BAR0(オフセット0x10)に指定されたレジスタ空間をカーネルにマップします (/usr/src/sys/dev/pci/if_igc.c)。OpenBSDの場合、pci_mapreg_mapによりsc->osdep.os_memt(バス空間タグ)とos_memh(バスハンドル)が取得され、以降このos_memt/os_memhを通じてレジスタ読み書きを行います (/usr/src/sys/dev/pci/if_igc.c)。実際、if_igc.cではIGC_READ_REG/IGC_WRITE_REGマクロが定義されており、内部でbus_space_read_4/bus_space_write_4を呼び出してBAR0上の指定オフセットにアクセスしています (src/sys/dev/pci/if_igc.h at cc53d18db392718e57448775d53ed87ec3704753 - SecBSD/src - LAYLO Code: Securing your code)。この抽象化により、ドライバコード中ではIGC_READ_REG(&sc->hw, レジスタ名)のように記述するだけで適切なMMIO読み書きが行われるようになっています。

  • デバイス初期化 (アタッチ時): OpenBSDドライバのattachルーチンでは、まずEEPROMチェックサムの検証やMACアドレス読み出しを行った後、MSI-X割り込みのセットアップ、ネットワークインタフェース構造体の初期化 (igc_setup_interface) を実施します (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)。続いてigc_reset(sc)を呼び出しデバイスをリセット状態から初期設定します (/usr/src/sys/dev/pci/if_igc.c)。このigc_reset内部や関連する初期化関数で、上述したレジスタの初期設定が行われます。

    • デバイスリセットと基本設定: igc_resetによりNICをリセットした後、受信・送信ユニットを一旦無効化し、内部FIFOの割り当て(PBA)やフロー制御関連の初期値設定が行われます。コードでは例えばPBAレジスタに0x22を書き込み、約34KBを受信バッファに割り当てています (/usr/src/sys/dev/pci/if_igc.c)。さらにこの値から受信高水位(high_water)を計算し、DMA Coalescing(DMAC)の設定関数igc_init_dmacを呼ぶことで遅延転送の閾値を設定しています (/usr/src/sys/dev/pci/if_igc.c)。加えてVLAN Ethertypeレジスタ(VET)に0x8100を書き込み、VLANフレーム(Ethertype 0x8100)の認識を有効化しています (/usr/src/sys/dev/pci/if_igc.c)。

    • 制御レジスタの更新: 初期化時に必要に応じCTRLレジスタのビットも設定します。OpenBSDドライバでは、IGC_CTRL_VMEビット(0x40000000)をセットしてデバイスのVLAN受信処理を有効にしています。この操作はctrl = IGC_READ_REG(..., IGC_CTRL)で現在のCTRL値を読み込み、ビットORでVMEをセットして書き戻すことで行われます(IEEE 802.1Q VLANモードを有効化) (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub)。同様に、フロー制御を有効にする場合はIGC_CTRL_RFCE/TFCEビットを設定しますが、OpenBSDドライバではデフォルトで必要に応じ有効化しています。

    • デバイス所有権の取得: I225はAMT等の管理エンジンとの協調動作があるため、ドライバ起動時に「ドライバがデバイスを制御中」であることをファームウェアに示す必要があります。OpenBSDドライバではigc_get_hw_control(sc)を呼び出し、CTRL_EXTまたはFWSM(Register)のDRV_LOADビットをセットしています (/usr/src/sys/dev/pci/if_igc.c)。これによりIntelマネジメントファームウェアに対し「OS側ドライバがNICを掌握した」ことを通知し、以降はファームウェアによる干渉を防ぎます(対応する解除処理として、デタッチ時や停止時にigc_release_hw_control(sc)でビットをクリア (/usr/src/sys/dev/pci/if_igc.c))。

    • DMAリングの構築: メモリ上に割り当てた送受信descriptor ringについて、その物理アドレスとサイズをNICのレジスタに設定します。OpenBSDドライバではigc_setup_transmit_structures/igc_setup_receive_structures等でDMA用メモリを確保した後、各キューについてRDBAL/RDBAHに受信リングのアドレス、RDLENにバッファ長を設定し、TDBAL/TDBAH/TDLENに送信リングのアドレス・長を設定します (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)。例えばキュー0の場合、RDBAL(0)に下位32bitアドレス、RDBAH(0)に上位アドレス、RDLEN(0)にリングのサイズ(デスクリプタ数×16バイト)を書き込みます。同様に送信キューについても設定します。これらの設定によりNICはホストメモリ上のリング位置を認識し、以降DMAでデスクリプタとパケットデータをやり取りします。

    • 受信エンジンの有効化: 受信を開始する前に、RCTLレジスタの各種ビットを適切に設定して有効化します。OpenBSDドライバではまず現在のRCTL値を読み出し、IGC_RCTL_ENビットをクリアして受信を一時停止します (/usr/src/sys/dev/pci/if_igc.c)。次に必要なビットを設定します。具体的には、マルチキャストフィルタのオフセット値(MO)をクリアし (/usr/src/sys/dev/pci/if_igc.c)、全ブロードキャスト受信を許可するBAMビット(0x00008000)をセット、誤りパケットはメモリに残さないようSBPビット(0x4)をクリア、長尺パケット受信を許可するLPEビット(0x20)をセット、受信フレームのCRCを削除するSECRCビット(0x04000000)をセットする、といった設定を行っています (/usr/src/sys/dev/pci/if_igc.c)。また必要に応じてUPE/MPEビットを設定し、プロミスキャスやオールマルチキャスト受信モードもここで調整します。最後にIGC_RCTL_ENビット(0x2)を再びセットして受信エンジンを有効化し、NICに受信動作を開始させます (/usr/src/sys/dev/pci/if_igc.c)。

    • 送信エンジンの有効化: 送信に関してもTCTLレジスタを設定します。ドライバは一旦既定値のTCTLを読み込み、コリジョン閾値(CT)フィールドを適切な値(例えば15)に設定しなおします (/usr/src/sys/dev/pci/if_igc.c)。またIGC_TCTL_PSPビット(0x08)をセットして短パケットの自動パディングを有効化、IGC_TCTL_RTLCビット(0x01000000)をセットしてレイトコリジョン時の再送を許可します (/usr/src/sys/dev/pci/if_igc.c)。最後にIGC_TCTL_ENビット(0x2)をセットし送信ユニットを有効化します (/usr/src/sys/dev/pci/if_igc.c)。この時点でNICは送信準備完了となり、以降キューにデスクリプタが供給されれば送信を実行可能な状態になります。

  • 割り込みの設定: OpenBSDのigcドライバはMSI-Xを利用しており、複数の割り込みベクタを使用します。アタッチ時にigc_allocate_msix(sc)でキュー毎のMSI-Xベクタを確保し、igc_configure_queues(sc)内でIGC_GPIEレジスタにMSI-Xモード関連のビットをセットします (/usr/src/sys/dev/pci/if_igc.c)。具体的にはIGC_GPIE_MSIX_MODE (MSI-Xモード有効)、IGC_GPIE_EIAME (Extended Interrupt Auto Mask機能有効)、IGC_GPIE_PBA (Packet Buffer割り込み割当)等のビットを立てています (/usr/src/sys/dev/pci/if_igc.c)。さらに各MSI-Xベクタに対し受信キュー・送信キューを対応付けるためIGC_IVARテーブル(Interrupt Vector Allocation Register)にエントリを設定し、IGC_EITRレジスタで割り込みレート制御値(Interrupt Throttling Rate)を初期化しています (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)。最後にEIMS(Extended Interrupt Mask Set)レジスタに各キューのビットマスクを設定し、そのキューの割り込みを有効化します (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)。リンクステータス変化用の割り込みについてもIGC_IMSにLSCビット(リンク変化)をセットしてマスクを解除し (/usr/src/sys/dev/pci/if_igc.c)、別途sc->msix_linkmaskとして管理されるリンク用MSI-XベクタをEIMSに登録しています (/usr/src/sys/dev/pci/if_igc.c)。

  • パケット送受信時のレジスタ操作: データ送受信の際、ドライバは適宜レジスタを更新します。

    • 送信: 送信パスでは、OSプロトコルスタックからパケットが渡されると、新しい送信デスクリプタにバッファアドレスや長さ、必要なコントロールフラグ(例えばEOP: End of Packetやチェックサムオフロードフラグなど)を設定します。設定後、ドライバは該当キューのTDT (Transmit Descriptor Tail) レジスタにテールインデックスを更新して書き込みます。例えばキューtxr->meに対し、IGC_WRITE_REG(&sc->hw, IGC_TDT(txr->me), prod)とすることで、NICに対し「prod番目までの送信デスクリプタに有効なデータが入った」と通知します (/usr/src/sys/dev/pci/if_igc.c)。この書き込みによりNICは新たに供給されたデスクリプタをDMAで読み込み、該当パケットの送信処理を開始します。送信完了後NICは該当デスクリプタのステータスビット(DD: Descriptor Done)を立て、割り込みを発生させます。ドライバは割り込みハンドラ内で送信完了したデスクリプタを検知し、mbufの開放やカウンタ更新を行います。

    • 受信: 受信側では、NICがパケットを受信するごとに空きデスクリプタにDMA書き込みし、RDH (Receive Descriptor Head)レジスタを更新して進めていきます。ドライバは定期的(割り込み駆動またはポーリング)に受信デスクリプタをチェックし、新規受信パケットがあればそれを取り出して上位層に渡します。処理が終わった受信デスクリプタは再利用のため新しいバッファを割り当てなおし、リングに戻す必要があります。この際ドライバはRDT (Receive Descriptor Tail)レジスタを進めて、NICに「この位置まで空きデスクリプタを補充した」ことを通知します。OpenBSDドライバでも、受信処理ルーチンの最後でIGC_WRITE_REG(&sc->hw, IGC_RDT(rxr->me), 新しいテール位置)と書き込み、例えばキューrxr->meのRDTレジスタを更新しています (/usr/src/sys/dev/pci/if_igc.c)。これによりNICはリング上の空きエントリを認識し、今後の受信に利用できるようになります。

  • リンクステータスとその他: リンクのアップ/ダウンやその他のイベントは割り込み経由で処理されます。NICがリンクダウン等を検出するとICRレジスタのLSCビットを1にします。OpenBSDドライバではMSI-Xのリンク用ハンドラ内でreg_icr = IGC_READ_REG(&sc->hw, IGC_ICR)により割り込み原因を読み出し (/usr/src/sys/dev/pci/if_igc.c)、LSCビット(0x4)がセットされていればsc->hw.mac.get_link_status = trueとすることでリンク再チェック要求を立て、igc_update_link_status(sc)を呼んで現在のリンク状態をPHYから再取得しています (/usr/src/sys/dev/pci/if_igc.c)。これに伴い、リンクダウンであればOSのネットワーク層にインタフェースダウンを通知し、リンクアップであれば速度やデュプレックスをSTATUSレジスタから確認してifmedia状態を更新します。

以上のように、OpenBSDのigcドライバ(if_igc.c)ではI225の各種レジスタを適切に読み書きすることでデバイスを初期化し、送受信や割り込み処理を行っています。主要なレジスタ設定箇所は、デバイス初期化時(attach直後のigc_reset/igc_init_hw 内)におけるCTRL/RCTL/TCTL/PBA設定、割り込み有効化、リングアドレス設定など (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)、およびパケット送受信時のRDT/TDT更新処理 (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)となります。また、BAR0でマッピングしたメモリ空間を介し、bus_spaceツールキットで抽象化された読み書きを行う実装になっており、コード中ではレジスタ名により直接アクセスする形で記述されています (src/sys/dev/pci/if_igc.h at cc53d18db392718e57448775d53ed87ec3704753 - SecBSD/src - LAYLO Code: Securing your code)。このような構造により、I225のハードウェアリソースをOpenBSDカーネル上で制御・操作しています。

参考資料: I225データシートおよびOpenBSD if_igc.cドライバのソースコード (/usr/src/sys/dev/pci/if_igc.c) (src/sys/dev/pci/if_igc.h at cc53d18db392718e57448775d53ed87ec3704753 - SecBSD/src - LAYLO Code: Securing your code) (linux/drivers/net/ethernet/intel/igc/igc_defines.h at master · torvalds/linux · GitHub) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c) (/usr/src/sys/dev/pci/if_igc.c)など。これらより、レジスタマップ構成とドライバによるアクセス方法・設定内容を確認しました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment