我试图理解网络驱动程序代码中使用的Rx和Tx描述符的概念。
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
卡特有的?它们是否会由制造商在数据表中定义?
由于 拿烟
答案 0 :(得分:38)
快速回答:
更多建筑详情:
注意:我假设您了解环数据结构,DMA的概念。
https://en.wikipedia.org/wiki/Circular_buffer
https://en.wikipedia.org/wiki/Direct_memory_access
描述符,顾名思义,描述了一个数据包。它并不直接包含数据包数据(据我所知,对于NIC),而是描述数据包,即存储数据包字节的位置,数据包的长度等等。
我将使用RX路径作为例子来说明它为何有用。在接收到分组时,NIC将线路上的电子/光学/无线电信号转换为二进制数据字节。然后,NIC需要通知操作系统已收到任何信息。在过去,这是通过中断完成的,操作系统将从NIC上的预定义位置读取字节到RAM。然而这很慢,因为1)CPU需要参与从NIC到RAM的数据传输2)可能存在大量数据包,因此可能有太多的中断无法处理CPU。然后DMA出现并解决了第一个问题。此外,人们设计了轮询模式驱动程序(或混合模式,如在Linux NAPI中),因此CPU可以从中断处理中解脱出来并一次轮询多个数据包,从而解决了第二个问题。
NIC完成信号转换为字节,并希望执行DMA到RAM。但在此之前,NIC需要知道DMA的位置,因为它不能随意将数据放入RAM中,CPU不知道哪里不安全。
因此,在RX队列初始化期间,NIC驱动程序预先分配一些数据包缓冲区以及数据包描述符数组。它根据NIC定义初始化每个数据包描述符。
以下是英特尔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 上重用于此目的(参见描述符联合中的第二个结构)。然后,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读取TX描述符和DMA tx_ring [x] .buffer_addr。
此信息通常出现在NIC数据表中,例如Intel XL710 xl710-10-40-controller-datasheet,第8.3章& 8.4 LAN RX / TX数据路径。
您还可以检查开源驱动程序代码(Linux内核或某些用户空间库,如DPDK PMD),它将包含描述符结构定义。
- 编辑1 -
有关Realtek驱动程序的其他问题: 是的,这些位是特定于NIC的。提示是
之类的行 desc->opts1 = cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);
DescOwn是一个位标志,通过设置它告诉NIC它现在拥有这个描述符和相关的缓冲区。它还需要从CPU端点(可能是电源CPU,即BE)转换为NIC同意理解的Little Endian。
您可以在http://realtek.info/pdf/rtl8139cp.pdf中找到相关信息(例如,针对DescOwn的第70页),尽管它不像XL710那样,但至少包含所有注册/描述符信息。
- 编辑2 -
NIC描述符是一个非常依赖于供应商的定义。如上所示,Intel的NIC描述符使用相同的 RX描述符环来提供要写入的NIC缓冲区,并使NIC能够写回RX信息。还有其他实现,如拆分RX提交/完成队列(在NVMe技术中更常见)。例如,Broadcom的一些NIC有一个提交环(为NIC提供缓冲区)和多个完成环。它设计用于NIC决定并将数据包放入不同的环中,例如不同的流量类优先级,使驱动程序可以先获取最重要的流量。 (来自BCM5756M NIC程序员指南)
- 编辑3 -
英特尔通常会将NIC数据表公开下载,而其他供应商可能只向ODM披露。有关Tx / Rx流程的简短摘要,请参见其Intel 82599系列数据手册,第1.8节“架构和基本操作”。