ネットワークドライバーコードで使用されるRxおよびTx記述子の概念を理解しようとしています。
編集:Realtekカードのドライバコードで。次の構造体を定義しました。
struct Desc
{
uint32_t opts1;
uint32_t opts2;
uint64_t addr;
};
txd->addr = cpu_to_le64(mapping);
txd->opts2 = cpu_to_le32(opts2);
txd->opts1 = cpu_to_le32(opts1 & ~DescOwn);
では、opts1 and opts2
とDescOwn
カードのようなビットは固有ですか?それらはデータシートでメーカーによって定義されますか?
ありがとうナヤン
クイックアンサー:
その他のアーキテクチャの詳細:
注:リングデータ構造、DMAの概念についての知識があることを前提としています。 https://en.wikipedia.org/wiki/Circular_buffer
https://en.wikipedia.org/wiki/Direct_memory_access
記述子は、その名前が示すように、パケットを記述します。パケットデータ(私が知る限りNICの場合)は直接含まれていませんが、パケットを記述しています。つまり、パケットのバイト数、パケットの長さなどが格納されています。
RXパスを例として使用して、RXパスが役立つ理由を説明します。パケットを受信すると、NICはワイヤ上の電子/光/無線信号をバイナリデータバイトに変換します。次に、NICはOSに何かを受信したことを通知する必要があります。昔は、これは割り込みによって行われ、OSはNIC上の事前定義された場所からRAMにバイトを読み取りました。ただし、1)CPUがNICからRAMへのデータ転送に参加する必要があるため、これは低速です。2)パケットが大量に発生する可能性があるため、CPUで処理するには多すぎる可能性があります。次に、DMAが登場し、最初の問題を解決します。また、人々はポーリングモードドライバー(またはLinux NAPIのようにハイブリッドモード)を設計したので、CPUは中断処理から解放され、一度に多くのパケットをポーリングして、2番目の問題を解決できます。
NICはバイトへの信号変換を終了し、RAMへのDMAを実行したいと考えています。ただし、その前に、NICはDMAの宛先を知る必要があります。これは、CPUがどこを認識せず、安全でないデータをRAMにランダムに配置できないためです。
そのため、RXキューの初期化中に、NICドライバーはパケットバッファーとパケット記述子の配列を事前に割り当てます。 NICの定義に従って各パケット記述子を初期化します。
以下は、Intel XL710 NICで使用される規則です(わかりやすくするために名前が簡略化されています)。
/*
Rx descriptor used by XL710 is filled by both driver and NIC,
* but at different stage of operations. Thus to save space, it's
* defined as a union of read (by NIC) and writeback (by NIC).
*
* It must follow the description from the data sheet table above.
*
* __leXX below means little endian XX bit field.
* The endianness and length has to be explicit, the NIC can be used by different CPU with different Word size and endianness.
*/
union rx_desc {
struct {
__le64 pkt_addr; /* Packet buffer address, points to a free packet buffer in packet_buffer_pool */
__le64 hdr_addr; /* Header buffer address, normally isn't used */
} read; /* initialized by driver */
struct {
struct {
struct {
union {
__le16 mirroring_status;
__le16 fcoe_ctx_id;
} mirr_fcoe;
__le16 l2tag1;
} lo_dword;
union {
__le32 rss; /* RSS Hash */
__le32 fd_id; /* Flow director filter id */
__le32 fcoe_param; /* FCoE DDP Context id */
} hi_dword;
} qword0;
struct {
/* ext status/error/pktype/length */
__le64 status_error_len;
} qword1;
} wb; /* writeback by NIC */
};
/*
* Rx Queue defines a circular ring of Rx descriptors
*/
struct rx_queue {
volatile rx_desc rx_ring[RING_SIZE]; /* RX ring of descriptors */
struct packet_buffer_pool *pool; /* packet pool */
struct packet_buffer *pkt_addr_backup; /* save a copy of packet buffer address for writeback descriptor reuse */
....
}
ドライバーは、RAM(packet_buffer_poolデータ構造に格納されている)にいくつかのパケットバッファーを割り当てます。
pool = alloc_packet_buffer_pool(buffer_size=2048, num_buffer=512);
ドライバーは、次のように、各パケットバッファーのアドレスを記述子フィールドに入力します。
rx_ring[i]->read.pkt_addr = pool.get_free_buffer();
ドライバーはNICにrx_ringの開始位置、その長さ、およびヘッド/テールを通知します。したがって、NICは、どの記述子が空いているかを認識します(したがって、それらの記述子が指すパケットバッファは空です)。このプロセスは、ドライバーがそれらの情報をNICレジスターに書き込むことによって実行されます(修正済み、NICデータシートに記載されています)。
rx_ring_addr_reg = &rx_ring;
rx_ring_len_reg = sizeof(rx_ring);
rx_ring_head = 0; /* meaning all free at start */
/* rx_ring_tail is a register in NIC as NIC updates it */
これで、NICは、記述子rx_ring [{x、y、z}]が空いていて、{x、y、z} .pkt_addrに新しいパケットデータを入れることができることを認識しています。それは先に進み、DMA新しいパケットを{x、y、z} .pkt_addrに入れます。その間、NICはパケット処理を前処理(オフロード)する可能性があるため(チェックサム検証、VLANタグの抽出など)、ソフトウェア用にそれらの情報を残す場所も必要になります。ここで、記述子はwritebackでこの目的のために再利用されます(記述子ユニオンの2番目の構造体を参照)。次に、NICはrx_ringテールポインタオフセットを進め、新しい記述子がNICによって書き戻されたことを示します。[ここでの落とし穴は、記述子が前処理結果に再利用されるため、ドライバは{x、yを保存する必要があることです。 、z} .pkt_addrのバックアップデータ構造]。
/* below is done in hardware, shown just for illustration purpose */
if (rx_ring_head != rx_ring_tail) { /* ring not full */
copy(rx_ring[rx_ring_tail].read.pkt_addr, raw_packet_data);
result = do_offload_procesing();
if (pre_processing(raw_packet_data) & BAD_CHECKSUM))
rx_ring[rx_ring_tail].writeback.qword1.stats_error_len |= RX_BAD_CHECKSUM_ERROR;
rx_ring_head++; /* actually driver sets a Descriptor done indication flag */
/* along in writeback descriptor so driver can figure out */
/* current HEAD, thus saving a PCIe write message */
}
ドライバーが新しいテールポインターオフセットを読み取り、{x、y、z}が新しいパケットであることがわかりました。 pkt_addr_backup [{x、y、z}]および関連する歳差運動の結果からパケットを読み取ります。
上位層のソフトウェアがパケットを処理すると、{x、y、z}がrx_ringに戻され、リングヘッドポインターが更新されて空き記述子が示されます。
これでRXパスは終了です。 TXパスはほとんど逆です。上位層がパケットを生成し、ドライバーがパケットデータをpacket_buffer_poolにコピーし、tx_ring [x] .buffer_addrがそれを指すようにします。ドライバーは、TX記述子にいくつかのTXオフロードフラグ(ハードウェアチェックサム、TSOなど)も準備します。 NICはTX記述子とDMA tx_ring [x] .buffer_addrをRAMからNICに読み取ります。
この情報は通常、Intel XL710 xl710-10-40-controller-datasheet、第8.3章および8.4章LAN RX/TXデータパスなどのNICデータシートに記載されています。
また、記述子構造体定義を含むオープンソースドライバーコード(LinuxカーネルまたはDPDK PMDなどのユーザースペースライブラリ)を確認することもできます。
-編集1--
Realtekドライバーに関する追加の質問:はい、これらのビットはNIC固有です。ヒントは次のような行です
desc->opts1 = cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);
DescOwnはビットフラグであり、設定することにより、NICにこの記述子と関連するバッファを所有していることを通知します。また、CPUエンディアン(BEであるパワーCPUの場合もあります)から、NICが理解することに同意するリトルエンディアンに変換する必要があります。
関連情報は http://realtek.info/pdf/rtl8139cp.pdf (DescOwnの場合は70ページなど)にありますが、XL710ほどではありませんが、少なくともすべてが含まれています。登録/記述子情報。
-編集2-
NIC記述子は、ベンダーに大きく依存する定義です。上に示したように、IntelのNIC記述子はsameRX記述子リングを使用して、書き込み用のNICバッファーを提供し、NIC用にRX情報を書き戻します。分割RX送信/完了キュー(NVMeテクノロジーでより一般的)のような他の実装があります。たとえば、BroadcomのNICの中には、単一の送信リング(NICにバッファを与えるため)と複数の完了リングを備えているものがあります。 NICがパケットを決定し、別のリングに配置するように設計されています。ドライバーが最も重要なパケットを最初に取得できるように、異なるトラフィッククラスの優先度。 (BCM5756M NICプログラマーズガイドより)
-編集3--
Intelは通常、NICデータシートを一般にダウンロードできるようにしますが、他のベンダーはODMにのみ開示する場合があります。 Tx/Rxフローの非常に簡単な要約は、 Intel 82599 ファミリのデータシートのセクション1.8アーキテクチャと基本操作に記載されています。