NIC e1000 - oraccha/omicron GitHub Wiki

Linux/デバイスドライバ/NIC

  • [http://www.intel.com/design/network/drivers/index.htm Intelのページ]

  • [http://sourceforge.net/projects/e1000 SourceForge] . Linux 用ドライバ.

  • LinuxFreeBSDSolaris 共に Intel がドライバを開発している.

  • ちなみに FreeBSD のドライバは em(4) か gx(4) になる.PRO/100 は fxp(4).

割込みに関して

e1000 は,ITR (Interrupt Throttle Rate) レジスタの設定で割込み回数を制限できる.では,ドライバは割込み発生に対して,どんな処理を行うのか. ナイーブに考えると,送信処理はリングデスクリプタの設定をしてからパケット送信要求を NIC に出し,送信完了割込みが発生したら,デスクリプタを解放する.一方,受信処理は,受信割込みを待ち,受信用ソフトウェア割込みを起動すればよい.

  • e1000ドライバは対応していないみたいだけど、ethtool -Cでinterrupt coalescingの設定が可能。

ぼっ〜とドライバを読んでみると,送信処理は割込み使わず,ポーリングしている感じがする.

  • Linux/NAPI 有効時では,dev->poll(e1000_clean) -> e1000_clean_tx_irq と呼び出され,デスクリプタをトラバースして,送信完了ビット(E1000_TXD_STAT_DD)が立っているデスクリプタを解放している.ちなみに dev->poll は NAPI のポーリングコールバック関数で,net_rx_action(ソフトウェア割込み)から呼ばれる.
  • NAPI 無効時では,e1000_intr(割込みハンドラ)から e1000_clean_tx_irq が呼び出される.

あと,TIDV,RDTR レジスタの設定で,割込みの発生を指定時間(1024us単位),遅延させることができる.

参考までに,MPI で,2ノード間のレイテンシを測定したところ,

  • default(InterruptThrottleRate=1, TxIntDelay=64, RxIntDelay=0):
    • 0.000499902 (500us)
  • InterruptThrottleRate=0:
    • 9.66378e-05 (100us)
  • InterruptThrottleRate=0, TxIntDelay=0:
    • 8.94528e-05 (90us)

となった.ちなみに,ITR=2000 だと,レイテンシが 500us になる.

(補足)InterruptThrottleRateの設定はTxAbsIntDelay、RxAbsIntDelayより優先される。


メモ

データ構造 {{{ e1000_adapter -> e1000_desc_ring -> e1000_buffer -> e1000_tx_desc }}}

  • e1000_tx_desc の配列がリングディスクリプタとして NIC 側から見える領域.これは TDBAL/TDBAH/TDLEN レジスタで指定する.

    • ディスクリプタ数は ethtool -g で調べることができる.
  • e1000_desc_ring からは e1000_tx_desc へ仮想アドレスポインタ desc でアクセスできる(dma メンバは物理アドレスを指すエリアスになっている)

    • フリーディスクリプタの先頭は next_to_use,使用中ディスクリプタの後尾は next_to_clean がポインタ(ディスクリプタのインデックス)になっている.
  • e1000_buffer と e1000_tx_desc は1対1対応しており,e1000_buffer はソケットバッファへのポインタ skb とそのソケットバッファのデータ領域の物理アドレス dma を保持する.

    • メンバ dma は物理アドレスを保持するという命名規則になっているみたい.
    • ディスクリプタの最大サイズはページサイズ(4KB)なので,ジャンボフレームを送信するには複数のディスクリプタを使用することになる.そこで e1000_buffer は次のフレームの先頭ディスクリプタを指すために next_to_watch メンバを持つ.
  • リングの先頭と末尾は TDH/TDL レジスタに保持される.

  • e1000_tx_queue はディスクリプタをセットアップし,TDL をそのディスクリプタへ変更する.

  • e1000_clean_tx_irq は送信が終了したディスクリプタを変更する.

タイマハンドラ (>> Linux/タイマ管理)

  • watchdog timer
  • tx filo stall timer
  • phy info timer

レジスタ

  • ディスクリプタ関係 . NIC/Tulip でいうところの chain mode ってないのかなぁ. {{{ 送信側 TDBAL: ベースアドレス(低位) TDBAH: ベースアドレス(高位) TDLEN: 長さ (sizeof(struct e1000_tx_desc) * ディスクリプタ数) TDH: 先頭ディスクリプタへのポインタ TDL: 末尾ディスクリプタへのポインタ

TCTL: Transmit Control Register TIDV: Transmit Interrupt Delay TADV: Transmit Absolute Interrupt Delay RCTL: Receive Control Register RDTR: Receive Delay RADV: Receive Absolute Interrupt Delay }}}

  • 割込み関係 {{{ ICR: 割込み原因読込み ICS: 割込み原因設定 ITR: Interrupt Throttle Rate (割込みを間引く) IMC: 割込みマスククリア IMS: 割込みマスクセット }}}

    • 割込みの主な発生原因は TXDW(Trasmit desc written back),TXQE(Transmit queue empty),RXT(rx timer intr).
    • 最近の InterruptThrottleRate のデフォルトは動的 ITR ってのになっている.この実装は,watchdog タイマのコールバック関数(2秒周期で呼ばれる)で次のように ITR レジスタの値を 2000〜8000 の間で調整している. {{{ /* Dynamic mode for Interrupt Throttle Rate (ITR) / if(adapter->hw.mac_type >= e1000_82540 && adapter->itr == 1) { / Symmetric Tx/Rx gets a reduced ITR=2000; Total
      • asymmetrical Tx or Rx gets ITR=8000; everyone
      • else is between 2000-8000. */ uint32_t goc = (adapter->gotcl + adapter->gorcl) / 10000; uint32_t dif = (adapter->gotcl > adapter->gorcl ? adapter->gotcl - adapter->gorcl : adapter->gorcl - adapter->gotcl) / 10000; uint32_t itr = goc > 0 ? (dif * 6000 / goc + 2000) : 8000; E1000_WRITE_REG(&adapter->hw, ITR, 1000000000 / (itr * 256)); } }}}
  • IPG 関係 . IPGT フィールドに IFG 値を設定できる.デフォルトは 8 で,最大は 1023.RTL8139 の TX config reg. にも IPG を設定するフィールドがあるけど,2bit で,FastEther の場合で 840〜960ns (840 + 40x) に設定できる. {{{ +----+----+----+----+----+----+----+----+ | IPGR2(12) | IPGR1(10) | IPGT(10)| +----+----+----+----+----+----+----+----+ }}}

その他

  • チェックサム (e1000_tx_csum)
    • skb->ip_summed が CHECKSUM_HW の場合は NIC でチェックサム計算を行なう.
  • TCP/TSO サポート (e1000_tx_tso)

2.4.32 において(2.6.x では問題ない),スイッチをリブートしたら,リンクアップ時にカーネルパニックが起きた(BUG e1000_hw.c 5101行目).

これは e1000_config_dsp_after_link_change が,割込みコンテキストで動作することを想定していなかったからのようだ.この関数中で呼ばれる msec_delay マクロは sleep を伴うので,割込みコンテキストで呼んではいけない.msec_delay_irq (mdelay に define されている.これはbusy waitするもの)に置き換えたところ問題は起きなくなった. diff はこんな感じ.

{{{ --- e1000_hw.c.orig 2006-03-02 18:35:23.000000000 +0900 +++ e1000_hw.c 2006-03-02 18:35:54.000000000 +0900 @@ -5049,7 +5049,7 @@ if(ret_val) return ret_val;

  •        msec_delay(20);
    
  •        msec_delay_irq(20);
    
           ret_val = e1000_write_phy_reg(hw, 0x0000,
                                         IGP01E1000_IEEE_FORCE_GIGA);
    

@@ -5073,7 +5073,7 @@ if(ret_val) return ret_val;

  •        msec_delay(20);
    
  •        msec_delay_irq(20);
    
           /* Now enable the transmitter */
           ret_val = e1000_write_phy_reg(hw, 0x2F5B, phy_saved_data);
    

@@ -5098,7 +5098,7 @@ if(ret_val) return ret_val;

  •        msec_delay(20);
    
  •        msec_delay_irq(20);
    
           ret_val = e1000_write_phy_reg(hw, 0x0000,
                                         IGP01E1000_IEEE_FORCE_GIGA);
    

@@ -5114,7 +5114,7 @@ if(ret_val) return ret_val;

  •        msec_delay(20);
    
  •        msec_delay_irq(20);
    
           /* Now enable the transmitter */
           ret_val = e1000_write_phy_reg(hw, 0x2F5B, phy_saved_data);
    

}}}