Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3426901
  • 博文数量: 198
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7246
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(198)

文章存档

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2018-05-13 22:28:55

dpdk网卡收发包分析

——lvyilong316

   在前面我们分析过dpdk程序启动过程中的对网卡设备初始化已经绑定驱动的逻辑。但是仅仅这样程序还不能正常的收发数据包,因为程序启动通过rte_eal_init初始化的仅仅是设备的基本结构,和网卡设备转发相关的配置以及队列的初始化,是由应用程序自身调用相应api来完成的

我们以dpdk example中的l2fwd为例,分析一下通常网卡转发的初始化和转发逻辑,由于不同网卡由和驱动有关,我们这里以intel82599网卡为例,其对应驱动为drivers\net\ixgbe。我们分为两个部分分析,第一部分是网卡设备信息的配置及启动,第二部分是数据包的收发。

1.1 网卡设备信息的配置及启动

正常配置网卡设备信息由如下五步组成:

(1)     创建一个mbuf_poolrte_pktmbuf_pool_create

(2)     配置队列的个数,以及接口的配置信息: rte_eth_dev_configure

(3)     使用之前创建mbuf_pool初始化每个接收队列:rte_eth_rx_queue_setup

(4)     初始化发送队列:rte_eth_tx_queue_setup

(5)     启动设备:rte_eth_dev_start

第一步创建mbuf pool的逻辑我们不再分析,但这里重点说一下这个mbuf pool的作用。这个mbuf pool主要是给网卡接收数据包提供mbuf的,换句话说网卡通过DMA收到数据需要把数据包通过DMA传送到一块内存,正式这个mbuf pool中的内存。另一方面,既然是个pool,那么这个mbuf pool就可以被一个设备的多个接收队列使用,或者是多个队列使用,因为归根结底网卡接收使用的是pool中的mbuf,至于mbuf是来自一个pool还是多个pool并不重要。下面我们逐个分析其余的四步。

1.1.1 rte_eth_dev_configure

点击(此处)折叠或打开

  1. /*
  2. * @port_id:设备对应的port_id,即rte_eth_dev在全局数组rte_eth_devices的下标
  3. * @nb_rx_q:配置的接收队列个数;
  4. * @nb_tx_q:配置的发送队列个数;
  5. * @dev_conf:设备的其他具体配置信息,如收发模式,速率等;
  6. */
  7. int
  8. rte_eth_dev_configure(uint8_t port_id, uint16_t nb_rx_q, uint16_t nb_tx_q,
  9.                          const struct rte_eth_conf *dev_conf)
  10. {
  11.          struct rte_eth_dev *dev;
  12.          struct rte_eth_dev_info dev_info;
  13.          int diag;
  14.  
  15.         /*......*/
  16.         /* 获取对应的设备结构 */
  17.          dev = &rte_eth_devices[port_id];
  18.  
  19.          RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_infos_get, -ENOTSUP);
  20.          RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_configure, -ENOTSUP);
  21.         /* 如果设备已经开启,则返错 */
  22.          if (dev->data->dev_started) {
  23.                    RTE_PMD_DEBUG_TRACE(
  24.                        "port %d must be stopped to allow configuration\n", port_id);
  25.                    return -EBUSY;
  26.          }
  27.         /* 将设备的配置信息拷贝到设备结构中 */
  28.          /* Copy the dev_conf parameter into the dev structure */
  29.          memcpy(&dev->data->dev_conf, dev_conf, sizeof(dev->data->dev_conf));
  30.  
  31.          /*
  32.           * Check that the numbers of RX and TX queues are not greater
  33.           * than the maximum number of RX and TX queues supported by the
  34.           * configured device.
  35.           */
  36.          /* 调用具体的设备驱动函数获取设备的信息,主要是为了和将要配置的一些信息对比,做检查 */
  37.          (*dev->dev_ops->dev_infos_get)(dev, &dev_info);
  38.         /* 配置队列的个数不能为0 */
  39.          if (nb_rx_q == 0 && nb_tx_q == 0) {
  40.                    RTE_PMD_DEBUG_TRACE("ethdev port_id=%d both rx and tx queue cannot be 0\n", port_id);
  41.                    return -EINVAL;
  42.          }
  43.         /* 配置的队列个数不能超过设备支持的最大值 */
  44.          if (nb_rx_q > dev_info.max_rx_queues) {
  45.                    RTE_PMD_DEBUG_TRACE("ethdev port_id=%d nb_rx_queues=%d > %d\n",
  46.                                      port_id, nb_rx_q, dev_info.max_rx_queues);
  47.                    return -EINVAL;
  48.          }
  49.  
  50.          if (nb_tx_q > dev_info.max_tx_queues) {
  51.                    RTE_PMD_DEBUG_TRACE("ethdev port_id=%d nb_tx_queues=%d > %d\n",
  52.                                      port_id, nb_tx_q, dev_info.max_tx_queues);
  53.                    return -EINVAL;
  54.          }
  55.  
  56.          /*
  57.           * If link state interrupt is enabled, check that the
  58.           * device supports it.
  59.           */
  60.          if ((dev_conf->intr_conf.lsc == 1) &&
  61.                    (!(dev->data->dev_flags & RTE_ETH_DEV_INTR_LSC))) {
  62.                             RTE_PMD_DEBUG_TRACE("driver %s does not support lsc\n",
  63.                                                dev->data->drv_name);
  64.                             return -EINVAL;
  65.          }
  66.  
  67.          /*
  68.           * If jumbo frames are enabled, check that the maximum RX packet
  69.           * length is supported by the configured device.
  70.           */
  71.           /* 如果开启了接收大包的功能,则检查配置的接收长度是否超过了设备限制 */
  72.          if (dev_conf->rxmode.jumbo_frame == 1) {
  73.                    if (dev_conf->rxmode.max_rx_pkt_len >
  74.                        dev_info.max_rx_pktlen) {
  75.                             RTE_PMD_DEBUG_TRACE("ethdev port_id=%d max_rx_pkt_len %u"
  76.                                      " > max valid value %u\n",
  77.                                      port_id,
  78.                                      (unsigned)dev_conf->rxmode.max_rx_pkt_len,
  79.                                      (unsigned)dev_info.max_rx_pktlen);
  80.                             return -EINVAL;
  81.                    } else if (dev_conf->rxmode.max_rx_pkt_len < ETHER_MIN_LEN) {
  82.                             RTE_PMD_DEBUG_TRACE("ethdev port_id=%d max_rx_pkt_len %u"
  83.                                      " < min valid value %u\n",
  84.                                      port_id,
  85.                                      (unsigned)dev_conf->rxmode.max_rx_pkt_len,
  86.                                      (unsigned)ETHER_MIN_LEN);
  87.                             return -EINVAL;
  88.                    }
  89.          } else {
  90.                    if (dev_conf->rxmode.max_rx_pkt_len < ETHER_MIN_LEN ||
  91.                             dev_conf->rxmode.max_rx_pkt_len > ETHER_MAX_LEN)
  92.                             /* Use default value */
  93.                             dev->data->dev_conf.rxmode.max_rx_pkt_len =
  94.                                                                  ETHER_MAX_LEN;
  95.          }
  96.  
  97.          /*
  98.           * Setup new number of RX/TX queues and reconfigure device.
  99.           */
  100.           /* 配置接收队列 */
  101.          diag = rte_eth_dev_rx_queue_config(dev, nb_rx_q);
  102.          if (diag != 0) {
  103.                    RTE_PMD_DEBUG_TRACE("port%d rte_eth_dev_rx_queue_config = %d\n",
  104.                                      port_id, diag);
  105.                    return diag;
  106.          }
  107.         /* 配置发送队列 */
  108.          diag = rte_eth_dev_tx_queue_config(dev, nb_tx_q);
  109.          if (diag != 0) {
  110.                    RTE_PMD_DEBUG_TRACE("port%d rte_eth_dev_tx_queue_config = %d\n",
  111.                                      port_id, diag);
  112.                    rte_eth_dev_rx_queue_config(dev, 0);
  113.                    return diag;
  114.          }
  115.         /* 调用设备的配置函数,进行最后的配置 */
  116.          diag = (*dev->dev_ops->dev_configure)(dev);
  117.          if (diag != 0) {
  118.                    RTE_PMD_DEBUG_TRACE("port%d dev_configure = %d\n",
  119.                                      port_id, diag);
  120.                    rte_eth_dev_rx_queue_config(dev, 0);
  121.                    rte_eth_dev_tx_queue_config(dev, 0);
  122.                    return diag;
  123.          }
  124.  
  125.          return 0;
  126. }

    这个函数参数会配置队列的个数,以及接口的配置信息,如队列的使用模式,多队列的方式等。前面会先进行一些各项检查,如果设备已经启动,就得先停下来才能配置(这时应该叫再配置吧)。然后把传进去的配置参数拷贝到设备的数据区。

memcpy(&dev->data->dev_conf, dev_conf, sizeof(dev->data->dev_conf));

之后获取设备的信息,主要也是为了后面的检查使用:

(*dev->dev_ops->dev_infos_get)(dev, &dev_info);

这里的dev_infos_get是在驱动初始化过程中设备初始化时配置的(eth_ixgbe_dev_init())

eth_dev->dev_ops = &ixgbe_eth_dev_ops;

重要的信息检查过后,下面就是对发送和接收队列进行配置。先看接收队列的配置,接收队列是从rte_eth_dev_tx_queue_config()开始的。

在接收配置中,考虑的是有两种情况,一种是第一次配置;另一种是重新配置。所以,代码中都做了区分。

(1)     如果是第一次配置,那么就为每个队列分配一个指针(注意这里只分配了指针,没有分配实际队列)

代码如下:dev->data->rx_queues = rte_zmalloc("ethdev->rx_queues",

                                     sizeof(dev->data->rx_queues[0]) * nb_queues, RTE_CACHE_LINE_SIZE);

(2)如果是重新配置,配置的queue数量不为0,那么就取消之前的配置,重新配置。

(3)如果是重新配置,但要求的queue0,那么释放已有的配置。

发送的配置也是同样的,在rte_eth_dev_tx_queue_config()

当收发队列配置完成后,就调用设备的配置函数,进行最后的配置。(*dev->dev_ops->dev_configure)(dev),我们找到对应的配置函数,进入ixgbe_dev_configure()来分析其过程,其实这个函数并没有做太多的事。

在函数中,先调用了ixgbe_check_mq_mode()来检查队列的模式。然后设置允许接收批量和向量的模式

adapter->rx_bulk_alloc_allowed = true;

adapter->rx_vec_allowed = true;

到此rte_eth_dev_configure函数的工作就做完了。

1.1.2  队列的初始化

接下来就是收发队列的初始化,非常关键的一部分内容,这部分内容按照收发分别介绍:

l  接收队列的初始化

接收队列的初始化是从rte_eth_rx_queue_setup()开始的,这里的参数需要指定要初始化的port_id,queue_id,以及描述符的个数,还可以指定接收的配置,如释放和回写的阈值等,另外重要的一点是要指定用来接收存放数据使用的mbuf pool,而发送队列是不需要这个的

依然如其他函数的套路一样,先进行各种检查,如初始化的队列号是否合法有效,设备如果已经启动,就不能继续初始化了。检查函数指针是否有效等。检查mbuf的数据大小是否满足默认的设备信息里的配置。

l  rte_eth_rx_queue_setup

点击(此处)折叠或打开

  1. /*
  2. * @port_id: 设备的port_id
  3. * @rx_queue_id: 所要配置的queue id
  4. * @nb_rx_desc: 设置队列的接收描述符(desc)的个数,也决定这接收队列的大小
  5. * @socket_id: 队列的socket_id
  6. * @rx_conf: 接收的配置,如释放和回写的阈值等
  7. * @mp:
  8. */
  9. int
  10. rte_eth_rx_queue_setup(uint8_t port_id, uint16_t rx_queue_id,
  11.                           uint16_t nb_rx_desc, unsigned int socket_id,
  12.                           const struct rte_eth_rxconf *rx_conf,
  13.                           struct rte_mempool *mp)
  14. {
  15.          int ret;
  16.          uint32_t mbp_buf_size;
  17.          struct rte_eth_dev *dev;
  18.          struct rte_eth_dev_info dev_info;
  19.          void **rxq;
  20.  
  21.          RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL);
  22.     /* 获取对应的设备数据结构 */
  23.          dev = &rte_eth_devices[port_id];
  24.          /* 检查当前配置的队列是否超出了之前设置的队列个数范围 */
  25.          if (rx_queue_id >= dev->data->nb_rx_queues) {
  26.                    return -EINVAL;
  27.          }
  28.     /* 如果设备已经启动,就不能再配置队列了 */
  29.          if (dev->data->dev_started) {
  30.                    return -EBUSY;
  31.          }
  32.  
  33.          RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_infos_get, -ENOTSUP);
  34.          RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->rx_queue_setup, -ENOTSUP);
  35.  
  36.          /*
  37.           * Check the size of the mbuf data buffer.
  38.           * This value must be provided in the private data of the memory pool.
  39.           * First check that the memory pool has a valid private data.
  40.           */
  41.           /* 这里获取了设备的信息, 用于校验当前的要配置的信息 */
  42.          rte_eth_dev_info_get(port_id, &dev_info);
  43.          if (mp->private_data_size < sizeof(struct rte_pktmbuf_pool_private)) {
  44.                    return -ENOSPC;
  45.          }
  46.          mbp_buf_size = rte_pktmbuf_data_room_size(mp);
  47.  
  48.          if ((mbp_buf_size - RTE_PKTMBUF_HEADROOM) < dev_info.min_rx_bufsize) {
  49.                    return -EINVAL;
  50.          }
  51.     /* 检查接收描述符的大小是否合法 */
  52.          if (nb_rx_desc > dev_info.rx_desc_lim.nb_max ||
  53.                             nb_rx_desc < dev_info.rx_desc_lim.nb_min ||
  54.                             nb_rx_desc % dev_info.rx_desc_lim.nb_align != 0) {
  55.                    return -EINVAL;
  56.          }
  57.    
  58.          rxq = dev->data->rx_queues;
  59.          if (rxq[rx_queue_id]) { /* 如果之前已经对队列初始化过,则先释放之前的资源 */
  60.                    RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->rx_queue_release,
  61.                                                -ENOTSUP);
  62.                    (*dev->dev_ops->rx_queue_release)(rxq[rx_queue_id]);
  63.                    rxq[rx_queue_id] = NULL;
  64.          }
  65.     /* 如果调用初始化函数时没有指定rx_conf配置,就会设备配置信息里的默认值 */
  66.          if (rx_conf == NULL)
  67.                    rx_conf = &dev_info.default_rxconf;
  68.     /* 调用到队列的setup函数做最后的初始化 */
  69.          ret = (*dev->dev_ops->rx_queue_setup)(dev, rx_queue_id, nb_rx_desc,
  70.                                                      socket_id, rx_conf, mp);
  71.          if (!ret) {
  72.                    if (!dev->data->min_rx_buf_size ||
  73.                        dev->data->min_rx_buf_size > mbp_buf_size)
  74.                             dev->data->min_rx_buf_size = mbp_buf_size;
  75.          }
  76.  
  77.          return ret;
  78. }

如果调用初始化函数时没有指定rx_conf配置,就会设备配置信息里的默认值

点击(此处)折叠或打开

  1. dev_info->default_rxconf = (struct rte_eth_rxconf) {
  2.         .rx_thresh = {
  3.             .pthresh = IXGBE_DEFAULT_RX_PTHRESH,
  4.             .hthresh = IXGBE_DEFAULT_RX_HTHRESH,
  5.             .wthresh = IXGBE_DEFAULT_RX_WTHRESH,
  6.         },
  7.         .rx_free_thresh = IXGBE_DEFAULT_RX_FREE_THRESH,
  8.         .rx_drop_en = 0,
  9.     };

最后,调用到队列的setup函数做最后的初始化。

点击(此处)折叠或打开

  1. ret = (*dev->dev_ops->rx_queue_setup)(dev, rx_queue_id, nb_rx_desc,
  2.                           socket_id, rx_conf, mp);

对于ixgbe设备,rx_queue_setup就是函数ixgbe_dev_rx_queue_setup(),这里就是队列最终的初始化。

l  ixgbe_dev_rx_queue_setup

点击(此处)折叠或打开

  1. int ixgbe_dev_rx_queue_setup(struct rte_eth_dev *dev,
  2.                              uint16_t queue_idx,
  3.                              uint16_t nb_desc,
  4.                              unsigned int socket_id,
  5.                              const struct rte_eth_rxconf *rx_conf,
  6.                              struct rte_mempool *mp)
  7. {
  8.          const struct rte_memzone *rz;
  9.          struct ixgbe_rx_queue *rxq;
  10.          struct ixgbe_hw *hw;
  11.          uint16_t len;
  12.          struct ixgbe_adapter *adapter =
  13.                    (struct ixgbe_adapter *)dev->data->dev_private;
  14.  
  15.          PMD_INIT_FUNC_TRACE();
  16.          hw = IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
  17.  
  18.          if (nb_desc % IXGBE_RXD_ALIGN != 0 ||
  19.                             (nb_desc > IXGBE_MAX_RING_DESC) ||
  20.                             (nb_desc < IXGBE_MIN_RING_DESC)) {
  21.                    return -EINVAL;
  22.          }
  23.  
  24.          /* Free memory prior to re-allocation if needed... */
  25.          if (dev->data->rx_queues[queue_idx] != NULL) {
  26.                    ixgbe_rx_queue_release(dev->data->rx_queues[queue_idx]);
  27.                    dev->data->rx_queues[queue_idx] = NULL;
  28.          }
  29.  
  30.          /* First allocate the rx queue data structure */
  31.          /* 分配接收队列的数据结构 */
  32.          rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),
  33.                                       RTE_CACHE_LINE_SIZE, socket_id);
  34.          if (rxq == NULL)
  35.                    return -ENOMEM;
  36.          rxq->mb_pool = mp; /* 将之前传入的mbuf pool和队列相关联 */
  37.          rxq->nb_rx_desc = nb_desc; /* 设置队列的接收描述符个数 */
  38.          rxq->rx_free_thresh = rx_conf->rx_free_thresh;
  39.          rxq->queue_id = queue_idx;
  40.          rxq->reg_idx = (uint16_t)((RTE_ETH_DEV_SRIOV(dev).active == 0) ?
  41.                    queue_idx : RTE_ETH_DEV_SRIOV(dev).def_pool_q_idx + queue_idx);
  42.          rxq->port_id = dev->data->port_id;
  43.          rxq->crc_len = (uint8_t) ((dev->data->dev_conf.rxmode.hw_strip_crc) ?
  44.                                                                  0 : ETHER_CRC_LEN);
  45.          rxq->drop_en = rx_conf->rx_drop_en;
  46.          rxq->rx_deferred_start = rx_conf->rx_deferred_start;
  47.  
  48.     /* ...... */
  49.  
  50.          /*
  51.           * Allocate RX ring hardware descriptors. A memzone large enough to
  52.           * handle the maximum ring size is allocated in order to allow for
  53.           * resizing in later calls to the queue setup function.
  54.           */
  55.          /* 分配描述符队列的空间,这里直接按照做大个数的描述符分配而不是传入的个数,
  56.           *是为了方便以后调整描述符个数大小 */
  57.          rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,
  58.                                            RX_RING_SZ, IXGBE_ALIGN, socket_id);
  59.          if (rz == NULL) {
  60.                    ixgbe_rx_queue_release(rxq);
  61.                    return -ENOMEM;
  62.          }
  63.  
  64.          /*
  65.           * Zero init all the descriptors in the ring.
  66.           */
  67.          memset(rz->addr, 0, RX_RING_SZ);
  68.  
  69.     /* ...... */
  70.     /* 设置队列的接收描述符ring的物理地址和虚拟地址 */
  71.          rxq->rx_ring_phys_addr = rte_mem_phy2mch(rz->memseg_id, rz->phys_addr);
  72.          rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr;
  73.  
  74.          /*
  75.           * Certain constraints must be met in order to use the bulk buffer
  76.           * allocation Rx burst function. If any of Rx queues doesn't meet them
  77.           * the feature should be disabled for the whole port.
  78.           */
  79.          if (check_rx_burst_bulk_alloc_preconditions(rxq)) {
  80.                    adapter->rx_bulk_alloc_allowed = false;
  81.          }
  82.  
  83.          /*
  84.           * Allocate software ring. Allow for space at the end of the
  85.           * S/W ring to make sure look-ahead logic in bulk alloc Rx burst
  86.           * function does not access an invalid memory region.
  87.           */
  88.          len = nb_desc;
  89.          if (adapter->rx_bulk_alloc_allowed)
  90.                    len += RTE_PMD_IXGBE_RX_MAX_BURST;
  91.  
  92.          rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring",
  93.                                                  sizeof(struct ixgbe_rx_entry) * len,
  94.                                                  RTE_CACHE_LINE_SIZE, socket_id);
  95.          if (!rxq->sw_ring) {
  96.                    ixgbe_rx_queue_release(rxq);
  97.                    return -ENOMEM;
  98.          }
  99.  
  100.          /*
  101.           * Always allocate even if it's not going to be needed in order to
  102.           * simplify the code.
  103.           *
  104.           * This ring is used in LRO and Scattered Rx cases and Scattered Rx may
  105.           * be requested in ixgbe_dev_rx_init(), which is called later from
  106.           * dev_start() flow.
  107.           */
  108.          rxq->sw_sc_ring =
  109.                    rte_zmalloc_socket("rxq->sw_sc_ring",
  110.                                         sizeof(struct ixgbe_scattered_rx_entry) * len,
  111.                                         RTE_CACHE_LINE_SIZE, socket_id);
  112.          if (!rxq->sw_sc_ring) {
  113.                    ixgbe_rx_queue_release(rxq);
  114.                    return -ENOMEM;
  115.          }
  116.  
  117.          if (!rte_is_power_of_2(nb_desc)) {
  118.                    adapter->rx_vec_allowed = false;
  119.          } else
  120.                    ixgbe_rxq_vec_setup(rxq);
  121.  
  122.          dev->data->rx_queues[queue_idx] = rxq;
  123.  
  124.          ixgbe_reset_rx_queue(adapter, rxq);
  125.  
  126.          return 0;
  127. }

依然是先检查,检查描述符的数量最大不能大于IXGBE_MAX_RING_DESC个,最小不能小于IXGBE_MIN_RING_DESC个。接下来的都是重点:

(1)     分配队列结构体,并填充结构

rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),

                 RTE_CACHE_LINE_SIZE, socket_id);

填充结构体的所属内存池,描述符个数,队列号,队列所属接口号等成员。

(2)     分配描述符队列的空间,按照最大的描述符个数进行分配

        rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,

                      RX_RING_SZ, IXGBE_ALIGN, socket_id);

(3)     接着获取描述符队列的头和尾寄存器的地址,在收发包后,软件要对这个寄存器进行处理。

       rxq->rdt_reg_addr =

            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDT(rxq->reg_idx));

        rxq->rdh_reg_addr =

            IXGBE_PCI_REG_ADDR(hw, IXGBE_RDH(rxq->reg_idx));

(4)     设置队列的接收描述符ring的物理地址和虚拟地址。

rxq->rx_ring_phys_addr=rte_mem_phy2mch(rz->memseg_id,rz->phys_addr);

rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr;

(5)     分配sw_ring,这个ring中存储的对象是struct ixgbe_rx_entry,其实里面就是数据包mbuf的指针。

rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring",

                      sizeof(struct ixgbe_rx_entry) * len,

                      RTE_CACHE_LINE_SIZE, socket_id);

以上几步做完以后,新分配的队列结构体重要的部分就已经填充完了,下面需要重置一下其他成员

l  ixgbe_reset_rx_queue

先把分配的描述符队列清空,其实清空在分配的时候就已经做了,没必要重复做

for (i = 0; i < len; i++) {

        rxq->rx_ring[i] = zeroed_desc;

    }

然后初始化队列中一下其他成员

rxq->rx_nb_avail = 0;

rxq->rx_next_avail = 0;

rxq->rx_free_trigger = (uint16_t)(rxq->rx_free_thresh - 1);

rxq->rx_tail = 0;

rxq->nb_rx_hold = 0;

rxq->pkt_first_seg = NULL;

rxq->pkt_last_seg = NULL;

这样,接收队列就初始化完了。

l  发送队列的初始化

发送队列的初始化在前面的检查基本和接收队列一样,只有些许区别在于setup环节,我们就从这个函数说起:ixgbe_dev_tx_queue_setup()。在发送队列配置中,重点设置了tx_rs_threshtx_free_thresh的值。

然后分配了一个发送队列结构txq,之后分配发送队列ring的空间,并填充txq的结构体

txq->tx_ring_phys_addr = rte_mem_phy2mch(tz->memseg_id,tz->phys_addr);

txq->tx_ring = (union ixgbe_adv_tx_desc *) tz->addr;

然后,分配队列的sw_ring,也挂载队列上。

l  重置发送队列

ixgbe_reset_tx_queue()

和接收队列一样,也是要把队列ring(描述符ring)清空,设置发送队列sw_ring,设置其他参数,队尾位置设置为0

点击(此处)折叠或打开

  1. txq->tx_next_dd = (uint16_t)(txq->tx_rs_thresh - 1);
  2. txq->tx_next_rs = (uint16_t)(txq->tx_rs_thresh - 1);
  3. txq->tx_tail = 0;
  4. txq->nb_tx_used = 0;
  5. /*
  6.  * Always allow 1 descriptor to be un-allocated to avoid
  7.  * a H/W race condition
  8.  */
  9. txq->last_desc_cleaned = (uint16_t)(txq->nb_tx_desc - 1);
  10. txq->nb_tx_free = (uint16_t)(txq->nb_tx_desc - 1);
  11. txq->ctx_curr = 0;

这样发送队列的初始化就完成了。

1.1.3设备的启动

经过上面的队列初始化,队列的ringsw_ring都分配了,但是发现木有,DMA仍然还不知道要把数据包拷贝到哪里,那么我们分配的mempool中的对象怎么和队列以及驱动联系起来呢?接下来就是最精彩的时刻了----建立mempoolqueueDMAring之间的关系。话说,这个为什么不是在队列的初始化中就做呢?设备的启动是从rte_eth_dev_start()中开始的:

diag = (*dev->dev_ops->dev_start)(dev);

进而,找到设备启动的真正启动函数:ixgbe_dev_start(),我们只保留关键的代码。

l  ixgbe_dev_start

点击(此处)折叠或打开

  1. static int ixgbe_dev_start(struct rte_eth_dev *dev)
  2. {
  3.          struct ixgbe_hw *hw =
  4.                   IXGBE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
  5.          struct ixgbe_vf_info *vfinfo =
  6.                    *IXGBE_DEV_PRIVATE_TO_P_VFDATA(dev->data->dev_private);
  7.          struct rte_pci_device *pci_dev = IXGBE_DEV_TO_PCI(dev);
  8.          struct rte_intr_handle *intr_handle = &pci_dev->intr_handle;
  9.          uint32_t intr_vector = 0;
  10.          int err, link_up = 0, negotiate = 0;
  11.          uint32_t speed = 0;
  12.          int mask = 0;
  13.          int status;
  14.          uint16_t vf, idx;
  15.          uint32_t *link_speeds;
  16.  
  17.          /*先检查设备的链路设置,暂时不支持半双工和固定速率的模式*/
  18.          if (dev->data->dev_conf.link_speeds & ETH_LINK_SPEED_FIXED) {
  19.                    PMD_INIT_LOG(ERR, "Invalid link_speeds for port %hhu; fix speed not supported",
  20.                                  dev->data->port_id);
  21.                    return -EINVAL;
  22.          }
  23.  
  24.          /* disable uio/vfio intr/eventfd mapping */
  25.          rte_intr_disable(intr_handle);
  26.  
  27.          /* stop adapter */
  28.          hw->adapter_stopped = 0;
  29.          /* 然后把中断禁掉,同时,停掉适配器 */
  30.          ixgbe_stop_adapter(hw);
  31.  
  32.          status = ixgbe_pf_reset_hw(hw);
  33.  
  34.      /* 如果支持多队列,则初始化多个中断向量 */
  35.          /* check and configure queue intr-vector mapping */
  36.          if ((rte_intr_cap_multiple(intr_handle) ||
  37.               !RTE_ETH_DEV_SRIOV(dev).active) &&
  38.              dev->data->dev_conf.intr_conf.rxq != 0) {
  39.                    intr_vector = dev->data->nb_rx_queues;
  40.                    if (rte_intr_efd_enable(intr_handle, intr_vector))
  41.                             return -1;
  42.          }
  43.  
  44.          /* initialize transmission unit */
  45.          ixgbe_dev_tx_init(dev);
  46.  
  47.          /* This can fail when allocating mbufs for descriptor rings */
  48.          err = ixgbe_dev_rx_init(dev);
  49.  
  50.          err = ixgbe_dev_rxtx_start(dev);
  51.  
  52.          dev->data->dev_link.link_status = link_up;
  53.  
  54.          err = ixgbe_get_link_capabilities(hw, &speed, &negotiate);
  55.          if (err)
  56.                    goto error;
  57.  
  58.          err = ixgbe_setup_link(hw, speed, link_up);
  59.          if (err)
  60.                    goto error;
  61.  
  62. skip_link_setup:
  63.  
  64.          return 0;
  65.  
  66. error:
  67.          ixgbe_dev_clear_queues(dev);
  68.          return -EIO;
  69. }

(1)     先检查设备的链路设置,暂时不支持半双工和固定速率的模式。看来是暂时只有自适应模式;

(2)     然后把中断禁掉,同时,停掉适配器: ixgbe_stop_adapter(hw),在其中,就是调用了ixgbe_stop_adapter_generic(),主要的工作就是停止发送和接收单元。这是直接写寄存器来完成的;

(3)     然后重启硬件,ixgbe_pf_reset_hw()->ixgbe_reset_hw()->ixgbe_reset_hw_82599(),最终都是设置寄存器,这里就不细究了。之后,就启动了硬件。

(4)     接着如果是多队列,则为每个队列分配对应的中断向量;

(5)     再然后是初始化接收单元:ixgbe_dev_rx_init()

在这个函数中,主要就是设置各类寄存器,比如配置CRC校验,如果支持巨帧,配置对应的寄存器。还有如果配置了loopback模式,也要配置寄存器。

接下来最重要的就是为每个队列设置DMA寄存器,标识每个队列的描述符ring的地址,长度,头,尾等。

点击(此处)折叠或打开

  1. /* Setup RX queues */
  2.          for (i = 0; i < dev->data->nb_rx_queues; i++) {
  3.                    rxq = dev->data->rx_queues[i];
  4.                    /*
  5.                     * Reset crc_len in case it was changed after queue setup by a
  6.                     * call to configure.
  7.                     */
  8.                    rxq->crc_len = rx_conf->hw_strip_crc ? 0 : ETHER_CRC_LEN;
  9.                    /* Setup the Base and Length of the Rx Descriptor Rings */
  10.                    bus_addr = rxq->rx_ring_phys_addr;
  11.                    IXGBE_WRITE_REG(hw, IXGBE_RDBAL(rxq->reg_idx),
  12.                                      (uint32_t)(bus_addr & 0x00000000ffffffffULL));
  13.                    IXGBE_WRITE_REG(hw, IXGBE_RDBAH(rxq->reg_idx),
  14.                                      (uint32_t)(bus_addr >> 32));
  15.                    IXGBE_WRITE_REG(hw, IXGBE_RDLEN(rxq->reg_idx),
  16.                                      rxq->nb_rx_desc * sizeof(union ixgbe_adv_rx_desc));
  17.                    IXGBE_WRITE_REG(hw, IXGBE_RDH(rxq->reg_idx), 0);
  18.                    IXGBE_WRITE_REG(hw, IXGBE_RDT(rxq->reg_idx), 0);
  19.          }

这里可以看到把描述符ring的物理地址写入了寄存器,还写入了描述符ring的长度。接下来这个函数还计算了数据包数据的长度,写入到寄存器中.然后对于网卡的多队列设置,也进行了配置:ixgbe_dev_mq_rx_configure()

同时如果设置了接收校验和,还对校验和进行了寄存器设置。

最后,调用ixgbe_set_rx_function()对接收函数再进行设置,主要是针对支持LROvector,bulk等处理方法。

这样,接收单元的初始化就完成了。

(6)     初始化发送单元:ixgbe_dev_tx_init();

发送单元的的初始化和接收单元的初始化基本操作是一样的,都是填充寄存器的值,重点是设置描述符队列的基地址和长度。

点击(此处)折叠或打开

  1. /* Setup the Base and Length of the Tx Descriptor Rings */
  2.          for (i = 0; i < dev->data->nb_tx_queues; i++) {
  3.                    txq = dev->data->tx_queues[i];
  4.  
  5.                    bus_addr = txq->tx_ring_phys_addr;
  6.                    IXGBE_WRITE_REG(hw, IXGBE_TDBAL(txq->reg_idx),
  7.                                      (uint32_t)(bus_addr & 0x00000000ffffffffULL));
  8.                    IXGBE_WRITE_REG(hw, IXGBE_TDBAH(txq->reg_idx),
  9.                                      (uint32_t)(bus_addr >> 32));
  10.                    IXGBE_WRITE_REG(hw, IXGBE_TDLEN(txq->reg_idx),
  11.                                      txq->nb_tx_desc * sizeof(union ixgbe_adv_tx_desc));
  12.                    /* Setup the HW Tx Head and TX Tail descriptor pointers */
  13.                    IXGBE_WRITE_REG(hw, IXGBE_TDH(txq->reg_idx), 0);
  14.                    IXGBE_WRITE_REG(hw, IXGBE_TDT(txq->reg_idx), 0);
  15. }

最后配置一下多队列使用相关的寄存器:ixgbe_dev_mq_tx_configure()

如此,发送单元的初始化就完成了。

(7)     启动设备的收发单元:ixgbe_dev_rxtx_start()

先对每个发送队列的threshold相关寄存器进行设置,这是发送时的阈值参数,这个东西在发送部分有说明。然后就是依次启动每个接收队列:

点击(此处)折叠或打开

  1. for (i = 0; i < dev->data->nb_rx_queues; i++) {
  2.                    rxq = dev->data->rx_queues[i];
  3.                    if (!rxq->rx_deferred_start) {
  4.                             ret = ixgbe_dev_rx_queue_start(dev, i);
  5.                             if (ret < 0)
  6.                                      return ret;
  7.                    }
  8.          }

l  ixgbe_dev_rx_queue_start

    先检查,如果要启动的队列是合法的,那么就为这个接收队列分配存放mbuf的实际空间。

点击(此处)折叠或打开

  1. if (rx_queue_id < dev->data->nb_rx_queues) {
  2.                    rxq = dev->data->rx_queues[rx_queue_id];
  3.                    /* Allocate buffers for descriptor rings */
  4.                    if (ixgbe_alloc_rx_queue_mbufs(rxq) != 0) {
  5.                             PMD_INIT_LOG(ERR, "Could not alloc mbuf for queue:%d",
  6.                                           rx_queue_id);
  7.                             return -1;
  8.                    }
  9. }

l  ixgbe_alloc_rx_queue_mbufs

这里是重点,完成了建立mempoolqueueDMAring之间的关系。

点击(此处)折叠或打开

  1. static int __attribute__((cold))
  2. ixgbe_alloc_rx_queue_mbufs(struct ixgbe_rx_queue *rxq)
  3. {
  4.          struct ixgbe_rx_entry *rxe = rxq->sw_ring;
  5.          uint64_t dma_addr;
  6.          unsigned int i;
  7.          /* Initialize software ring entries */
  8.          for (i = 0; i < rxq->nb_rx_desc; i++) {
  9.                    volatile union ixgbe_adv_rx_desc *rxd;
  10.         /* 从mbuf pool中分配一个mbuf */
  11.                    struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool);
  12.                    if (mbuf == NULL) {
  13.                             PMD_INIT_LOG(ERR, "RX mbuf alloc failed queue_id=%u",
  14.                                           (unsigned) rxq->queue_id);
  15.                             return -ENOMEM;
  16.                    }
  17.                    rte_mbuf_refcnt_set(mbuf, 1);
  18.                    mbuf->next = NULL;
  19.                    mbuf->data_off = RTE_PKTMBUF_HEADROOM;
  20.                    mbuf->nb_segs = 1;
  21.                    mbuf->port = rxq->port_id;
  22.                    dma_addr =
  23.                             rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(mbuf));
  24.                    rxd = &rxq->rx_ring[i];
  25.                    rxd->read.hdr_addr = 0;
  26.                    rxd->read.pkt_addr = dma_addr; /*设置描述符的dma地址为mbuf对应的地址*/
  27.                    rxe[i].mbuf = mbuf;
  28.          }
  29.          return 0;
  30. }

我们看到,从队列所属内存池的ring中循环取出了nb_rx_descmbuf指针,也就是为了填充rxq->rx_ring。每个指针都指向内存池里的一个数据包空间。然后就先填充了新分配的mbuf结构,最最重要的是填充计算了dma_addr

dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(mbuf));

然后初始化queue ring,即rxd的信息,标明了驱动把数据包放在dma_addr处。最后一句,把分配的mbuf“放入”queue rx_ring中,这样,驱动收过来的包,就直接放在了rx_ring中。

以上最重要的工作就完成了,下面就可以使能DMA引擎啦,准备收包,完成接收队列的启动。

hw->mac.ops.enable_rx_dma(hw, rxctrl);

接着依次启动每个发送队列:

发送队列的启动比接收队列的启动要简单,只是配置了txdctl寄存器,延时等待TX使能完成,最后,设置队列的头和尾位置都为0

点击(此处)折叠或打开

  1. txdctl = IXGBE_READ_REG(hw, IXGBE_TXDCTL(txq->reg_idx));
  2. txdctl |= IXGBE_TXDCTL_ENABLE;
  3. IXGBE_WRITE_REG(hw, IXGBE_TXDCTL(txq->reg_idx), txdctl);
  4.  
  5. IXGBE_WRITE_REG(hw, IXGBE_TDH(txq->reg_idx), 0);
  6. IXGBE_WRITE_REG(hw, IXGBE_TDT(txq->reg_idx), 0);

这样发送队列就启动完成了。

 

到此网卡设备信息的配置及初始化的主要工作就完成了,我们用下图来回顾一下。

1.2 网卡数据包的收发

数据包的获取是指驱动把数据包放入了内存中,上层应用从队列中去取出这些数据包;发送是指把要发送的数据包放入到发送队列中,为实际发送做准备。

1.2.1数据包的获取

业务层面获取数据包是从rte_eth_rx_burst()开始的

int16_t nb_rx = (*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id],

            rx_pkts, nb_pkts);

这里的dev->rx_pkt_burst在驱动初始化的时候已经注册过了,对于ixgbe设备,就是ixgbe_recv_pkts()函数。

在说收包之前,先了解网卡的DD标志,这个标志标识着一个描述符是否可用的情况:网卡在使用这个描述符前,先检查DD位是否为0,如果为0,那么就可以使用描述符,把数据拷贝到描述符指定的地址,之后把DD标志位置为1,否则表示不能使用这个描述符。而对于驱动而言,恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据放到了内存中,可以读取,读取完后,再把DD位设置为0,否则,就表示没有数据包可读。就重点从这个函数看看,数据包是怎么被取出来的。

首先,取值rx_id = rxq->rx_tail,这个值初始化时为0,用来标识当前ring的尾。然后循环读取请求数量的描述符,这时候第一步判断就是这个描述符是否可用

staterr = rxdp->wb.upper.status_error;

if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD)))

    break;

如果描述符的DD位不为1,则表明这个描述符网卡还没有准备好,也就是没有包!没有包,就跳出循环。如果描述符准备好了,就取出对应的描述符,因为网卡已经把一些信息存到了描述符里,可以后面把这些信息填充到新分配的数据包里。

下面就是一个狸猫换太子的事了,先从mempoolring中分配一个新的狸猫”---mbuf

nmb = rte_mbuf_raw_alloc(rxq->mb_pool);

然后找到当前描述符对应的太子”---ixgbe_rx_entry *rxe

rxe = &sw_ring[rx_id];

中间略掉关于预取的操作代码,之后,就要用这个狸猫换个太子

rxm = rxe->mbuf;

rxe->mbuf = nmb;

这样换出来的太子rxm就是我们要取出来的数据包指针,在下面填充一些必要的信息,就可以把包返给接收的用户了

点击(此处)折叠或打开

  1. rxm->data_off = RTE_PKTMBUF_HEADROOM;
  2. rte_packet_prefetch((char *)rxm->buf_addr + rxm->data_off);
  3. rxm->nb_segs = 1;
  4. rxm->next = NULL;
  5. rxm->pkt_len = pkt_len;
  6. rxm->data_len = pkt_len;
  7. rxm->port = rxq->port_id;
  8. pkt_info = rte_le_to_cpu_32(rxd.wb.lower.lo_dword.data);
  9. /* Only valid if PKT_RX_VLAN_PKT set in pkt_flags */
  10. rxm->vlan_tci = rte_le_to_cpu_16(rxd.wb.upper.vlan);
  11. pkt_flags = rx_desc_status_to_pkt_flags(staterr, vlan_flags);
  12. pkt_flags = pkt_flags | rx_desc_error_to_pkt_flags(staterr);
  13. pkt_flags = pkt_flags |
  14.     ixgbe_rxd_pkt_info_to_pkt_flags((uint16_t)pkt_info);
  15. rxm->ol_flags = pkt_flags;
  16. rxm->packet_type =
  17.     ixgbe_rxd_pkt_info_to_pkt_type(pkt_info,
  18.                        rxq->pkt_type_mask);
  19. if (likely(pkt_flags & PKT_RX_RSS_HASH))
  20.     rxm->hash.rss = rte_le_to_cpu_32(
  21.                 rxd.wb.lower.hi_dword.rss);
  22. else if (pkt_flags & PKT_RX_FDIR) {
  23.     rxm->hash.fdir.hash = rte_le_to_cpu_16(
  24.             rxd.wb.lower.hi_dword.csum_ip.csum) &
  25.             IXGBE_ATR_HASH_MASK;
  26.     rxm->hash.fdir.id = rte_le_to_cpu_16(
  27.             rxd.wb.lower.hi_dword.csum_ip.ip_id);
  28. }
  29. /*
  30.  * Store the mbuf address into the next entry of the array
  31.  * of returned packets.
  32.  */
  33. rx_pkts[nb_rx++] = rxm;

注意最后一句话,就是把包的指针返回给用户。

其实在换太子中间过程中,还有一件非常重要的事要做,就是开头说的,在驱动读取完数据包后,要把描述符的DD标志位置为0,同时设置新的DMA地址指向新的mbuf空间,这么描述符就可以再次被网卡硬件使用,拷贝数据到mbuf空间了。

dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb));

rxdp->read.hdr_addr = 0;

rxdp->read.pkt_addr = dma_addr;

rxdp->read.hdr_addr = 0;一句中,就包含了设置DD位为0

最后,就是检查空余可用描述符数量是否小于阀值,如果小于阀值,进行处理。不详细说了。

这样过后,收取数据包就完成啦!Done

1.2.2数据包的发送

在说发送之前,先说一下描述符的回写(write-back),回写是指把用过后的描述符,恢复其重新使用的过程。在接收数据包过程中,回写是立马执行的,也就是DMA使用描述符标识包可读取,然后驱动程序读取数据包,读取之后,就会把DD位置0,同时进行回写操作,这个描述符也就可以再次被网卡硬件使用了

但是发送过程中,回写却不是立刻完成的。发送有两种方式进行回写:

(1)     Updating by writing back into the Tx descriptor;

(2)     Update by writing to the head pointer in system memory;

第二种回写方式貌似针对的网卡比较老,对于82599,使用第一种回写方式。在下面三种情况下,才能进行回写操作:

(1)     TXDCTL[n].WTHRESH = 0 and a descriptor that has RS set is ready to be written back.

(2)     TXDCTL[n].WTHRESH > 0 and TXDCTL[n].WTHRESH descriptors have accumulated.

(3)     TXDCTL[n].WTHRESH > 0 and the corresponding EITR counter has reached zero. The timer expiration flushes any accumulated descriptors and sets an interrupt event(TXDW).

而在代码中,发送队列的初始化的时候,ixgbe_dev_tx_queue_setup()

txq->pthresh = tx_conf->tx_thresh.pthresh;

txq->hthresh = tx_conf->tx_thresh.hthresh;

txq->wthresh = tx_conf->tx_thresh.wthresh;

pthresh,hthresh,wthresh的值,都是从tx_conf中配置的默认值,而tx_conf如果在我们的应用中没有赋值的话,就是采用的默认值:

点击(此处)折叠或打开

  1. dev_info->default_txconf = (struct rte_eth_txconf) {
  2.     .tx_thresh = {
  3.         .pthresh = IXGBE_DEFAULT_TX_PTHRESH,
  4.         .hthresh = IXGBE_DEFAULT_TX_HTHRESH,
  5.         .wthresh = IXGBE_DEFAULT_TX_WTHRESH,
  6.     },
  7.     .tx_free_thresh = IXGBE_DEFAULT_TX_FREE_THRESH,
  8.     .tx_rs_thresh = IXGBE_DEFAULT_TX_RSBIT_THRESH,
  9.     .txq_flags = ETH_TXQ_FLAGS_NOMULTSEGS |
  10.             ETH_TXQ_FLAGS_NOOFFLOADS,
  11. };

其中的wthresh就是0,其余两个是32.也就是说这种设置下,回写取决于RS标志位。RS标志位主要就是为了标识已经积累了一定数量的描述符,要进行回写了。

了解了这个,就来看看代码吧,从ixgbe_xmit_pkts()开始,为了看主要的框架,我们忽略掉网卡卸载等相关的功能的代码,主要看发送和回写

先检查剩余的描述符是否已经小于阈值,如果小于阈值,那么就先清理回收一下描述符

if (txq->nb_tx_free < txq->tx_free_thresh)

        ixgbe_xmit_cleanup(txq);

这是一个重要的操作,进去看看是怎么清理回收的:ixgbe_xmit_cleanup(txq)

取出上次清理的描述符位置,很明显,这次清理就接着上次的位置开始。所以,根据上次的位置,加上txq->tx_rs_thresh个描述符,就是标记有RS的描述符的位置,因为,tx_rs_thresh就是表示这么多个描述符后,设置RS位,进行回写。所以,从上次清理的位置跳过tx_rs_thresh个描述符,就能找到标记有RS的位置。

desc_to_clean_to = (uint16_t)(last_desc_cleaned + txq->tx_rs_thresh);

当网卡把队列的数据包发送完成后,就会把DD位设置为1,这个时候,先检查标记RS位置的描述符DD位,如果已经设置为1,则可以进行清理回收,否则,就不能清理。接下来确认要清理的描述符个数

点击(此处)折叠或打开

  1. if (last_desc_cleaned > desc_to_clean_to)
  2.         nb_tx_to_clean = (uint16_t)((nb_tx_desc - last_desc_cleaned) +
  3.                             desc_to_clean_to);
  4. else
  5.     nb_tx_to_clean = (uint16_t)(desc_to_clean_to -
  6.                     last_desc_cleaned)

然后,就把标记有RS位的描述符中的RS位清掉,确切的说,DD位等都清空了。调整上次清理的位置和空闲描述符大小。

txr[desc_to_clean_to].wb.status = 0;

/* Update the txq to reflect the last descriptor that was cleaned */

txq->last_desc_cleaned = desc_to_clean_to;

txq->nb_tx_free = (uint16_t)(txq->nb_tx_free + nb_tx_to_clean);

这样,就算清理完毕了!

继续看发送,依次处理每个要发送的数据包:

取出数据包,取出其中的卸载标志

点击(此处)折叠或打开

  1. ol_flags = tx_pkt->ol_flags;
  2. /* If hardware offload required */
  3. tx_ol_req = ol_flags & IXGBE_TX_OFFLOAD_MASK;
  4. if (tx_ol_req) {
  5.     tx_offload.l2_len = tx_pkt->l2_len;
  6.     tx_offload.l3_len = tx_pkt->l3_len;
  7.     tx_offload.l4_len = tx_pkt->l4_len;
  8.     tx_offload.vlan_tci = tx_pkt->vlan_tci;
  9.     tx_offload.tso_segsz = tx_pkt->tso_segsz;
  10.     tx_offload.outer_l2_len = tx_pkt->outer_l2_len;
  11.     tx_offload.outer_l3_len = tx_pkt->outer_l3_len;
  12.     /* If new context need be built or reuse the exist ctx. */
  13.     ctx = what_advctx_update(txq, tx_ol_req,
  14.         tx_offload);
  15.     /* Only allocate context descriptor if required*/
  16.     new_ctx = (ctx == IXGBE_CTX_NUM);
  17.     ctx = txq->ctx_curr;
  18. }

这里卸载还要使用一个描述符,暂时不明白。

计算了发送这个包需要的描述符数量,主要是有些大包会分成几个segment,每个segment

nb_used = (uint16_t)(tx_pkt->nb_segs + new_ctx);

如果这次要用的数量加上设置RS之后积累的数量,又到达了tx_rs_thresh,那么就设置RS标志。

if (txp != NULL &&

        nb_used + txq->nb_tx_used >= txq->tx_rs_thresh)

/* set RS on the previous packet in the burst */

txp->read.cmd_type_len |=

    rte_cpu_to_le_32(IXGBE_TXD_CMD_RS);

接下来要确保用足够可用的描述符

如果描述符不够用了,就先进行清理回收,如果没能清理出空间,则把最后一个打上RS标志,更新队列尾寄存器,返回已经发送的数量。

点击(此处)折叠或打开

  1. if (txp != NULL)
  2.         txp->read.cmd_type_len |= rte_cpu_to_le_32(IXGBE_TXD_CMD_RS);
  3.     rte_wmb();
  4.     /*
  5.      * Set the Transmit Descriptor Tail (TDT)
  6.      */
  7.     PMD_TX_LOG(DEBUG, "port_id=%u queue_id=%u tx_tail=%u nb_tx=%u",
  8.            (unsigned) txq->port_id, (unsigned) txq->queue_id,
  9.            (unsigned) tx_id, (unsigned) nb_tx);
  10.     IXGBE_PCI_REG_WRITE_RELAXED(txq->tdt_reg_addr, tx_id);
  11.     txq->tx_tail = tx_id;

接下来的判断就很有意思了,

unlikely(nb_used > txq->tx_rs_thresh)

为什么说它奇怪呢?其实他自己都标明了unlikely,一个数据包会分为Nsegment,多于txq->tx_rs_thresh(默认可是32啊),但即使出现了这种情况,也没做更多的处理,只是说会影响性能,然后开始清理描述符,其实这跟描述符还剩多少没有半毛钱关系,只是一个包占的描述符就超过了tx_rs_thresh,然而,并不见得是没有描述符了。所以,这时候清理描述符意义不明。

下面的处理应该都是已经有充足的描述符了,如果卸载有标志,就填充对应的值。不详细说了。

然后,就把数据包放到发送队列的sw_ring,并填充信息

点击(此处)折叠或打开

  1. m_seg = tx_pkt;
  2.     do {
  3.         txd = &txr[tx_id];
  4.         txn = &sw_ring[txe->next_id];
  5.         rte_prefetch0(&txn->mbuf->pool);
  6.         if (txe->mbuf != NULL)
  7.             rte_pktmbuf_free_seg(txe->mbuf);
  8.         txe->mbuf = m_seg;
  9.         /*
  10.          * Set up Transmit Data Descriptor.
  11.          */
  12.         slen = m_seg->data_len;
  13.         buf_dma_addr = rte_mbuf_data_dma_addr(m_seg);
  14.         txd->read.buffer_addr =
  15.             rte_cpu_to_le_64(buf_dma_addr);
  16.         txd->read.cmd_type_len =
  17.             rte_cpu_to_le_32(cmd_type_len | slen);
  18.         txd->read.olinfo_status =
  19.             rte_cpu_to_le_32(olinfo_status);
  20.         txe->last_id = tx_last;
  21.         tx_id = txe->next_id;
  22.         txe = txn;
  23.         m_seg = m_seg->next;
  24.     } while (m_seg != NULL);

这里是把数据包的每个segment都放到队列sw_ring,很关键的是设置DMA地址,设置数据包长度和卸载参数。

一个数据包最后的segment的描述符需要一个EOP标志来结束。再更新剩余的描述符数:

cmd_type_len |= IXGBE_TXD_CMD_EOP;

txq->nb_tx_used = (uint16_t)(txq->nb_tx_used + nb_used);

txq->nb_tx_free = (uint16_t)(txq->nb_tx_free - nb_used);

然后再次检查是否已经达到了tx_rs_thresh,并做处理

点击(此处)折叠或打开

  1. if (txq->nb_tx_used >= txq->tx_rs_thresh) {
  2.     PMD_TX_FREE_LOG(DEBUG,
  3.             "Setting RS bit on TXD id="
  4.             "%4u (port=%d queue=%d)",
  5.             tx_last, txq->port_id, txq->queue_id);
  6.     cmd_type_len |= IXGBE_TXD_CMD_RS;
  7.     /* Update txq RS bit counters */
  8.     txq->nb_tx_used = 0;
  9.     txp = NULL;
  10. } else
  11.     txp = txd;
  12. txd->read.cmd_type_len |= rte_cpu_to_le_32(cmd_type_len);

最后仍是做一下末尾的处理,更新队列尾指针。发送就结束啦!!

IXGBE_PCI_REG_WRITE_RELAXED(txq->tdt_reg_addr, tx_id);

txq->tx_tail = tx_id;

1.2.3总结

可以看出数据包的发送和接收过程与驱动紧密相关,也与我们的配置有关,尤其是对于收发队列的参数配置,将直接影响性能,可以根据实际进行调整。

阅读(29186) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~