Has*_*ell 8 cpu cpu-architecture
我试图理解网络驱动程序代码中使用的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);
Run Code Online (Sandbox Code Playgroud)
所以,是opts1 and opts2有位喜欢DescOwn特定的卡?它们是否会由制造商在数据表中定义?
谢谢Nayan
Wei*_*hen 39
快速回答:
更多建筑细节:
注意:我假设您了解环数据结构,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 */
....
}
Run Code Online (Sandbox Code Playgroud)
驱动程序在RAM中分配一些数据包缓冲区(存储在packet_buffer_pool数据结构中).
pool = alloc_packet_buffer_pool(buffer_size=2048, num_buffer=512);
Run Code Online (Sandbox Code Playgroud)驱动程序将每个数据包缓冲区的地址放在描述符字段中,如
rx_ring[i]->read.pkt_addr = pool.get_free_buffer();
Run Code Online (Sandbox Code Playgroud)驱动程序告诉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 */
Run Code Online (Sandbox Code Playgroud)现在,NIC知道描述符rx_ring [{x,y,z}]是空闲的,而{x,y,z} .pkt_addr可以放入新的数据包数据.它继续将DMA新数据包发送到{x,y,z} .pkt_addr.与此同时,NIC可以预处理(卸载)数据包处理(如校验和验证,提取VLAN标记),因此它还需要一些地方来保留软件的这些信息.这里,描述符在回写时被重用于此目的(参见描述符联合中的第二个结构).然后,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 */
}
Run Code Online (Sandbox Code Playgroud)驱动程序读取新的尾指针偏移量,发现{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从RAM读取TX描述符和DMA tx_ring [x] .buffer_addr到NIC.
此信息通常出现在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);
Run Code Online (Sandbox Code Playgroud)
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披露.英特尔82599系列数据表的第1.8节"架构和基本操作"中介绍了Tx/Rx流程的简短摘要.
| 归档时间: |
|
| 查看次数: |
9071 次 |
| 最近记录: |