NIC中的描述符概念

Has*_*ell 8 cpu cpu-architecture

我试图理解网络驱动程序代码中使用的Rx和Tx描述符的概念.

  1. 是软件(RAM)或硬件(NIC卡)中的描述符.
  2. 他们是如何被填补的.

编辑:所以在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

快速回答:

  1. 它们是遵循NIC硬件定义的软件构造,因此它们都能理解并且可以相互通信.
  2. 它们可以以任何方式填充,具体取决于供应商定义的合同.可能的情况可能包括但不限于:
    • 通过驱动程序(例如,由驱动程序准备的空缓冲区由硬件Rx接收;对于由驱动程序准备的数据包缓冲区由硬件Tx传输)
    • 通过NIC(例如,对于完成的Rx数据包,由硬件写回的数据包缓冲区;对于由硬件指示的已完成的Tx数据包缓冲区,它被传输)

更多建筑细节:

注意:我假设您了解环数据结构,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使用的惯例(为了更好地理解,名称已经过简化):

XL710 RX读取描述符 XL710 Rx回写描述符

/*
   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)

RX描述符数据结构

在此输入图像描述

  1. 驱动程序在RAM中分配一些数据包缓冲区(存储在packet_buffer_pool数据结构中).

    pool = alloc_packet_buffer_pool(buffer_size=2048, num_buffer=512);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 驱动程序将每个数据包缓冲区的地址放在描述符字段中,如

    rx_ring[i]->read.pkt_addr = pool.get_free_buffer(); 
    
    Run Code Online (Sandbox Code Playgroud)
  3. 驱动程序告诉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)
  4. 现在,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)
  5. 驱动程序读取新的尾指针偏移量,发现{x,y,z}带有新的数据包.它会读取pkt_addr_backup [{x,y,z}]中的数据包以及相关的预处理结果.

  6. 当使用数据包完成上层软件时,{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数据路径.

http://www.intel.com/content/www/us/en/embedded/products/networking/xl710-10-40-controller-datasheet.html

您还可以检查开源驱动程序代码(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流程的简短摘要.