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

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

文章分类

全部博文(205)

文章存档

2024年(8)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2018-09-24 18:09:59

dpdk网卡驱动发送函数分析

——lvyilong316

这里主要分析一下dpdk网卡驱动的发送流程,如何将应用中mbuf中的数据DMA到网卡硬件。这里以Broadcombnxt驱动为例分析,代码来自dpdk 17.11

bnxt的发送函数tx_pkt_burst被初始化为了bnxt_xmit_pkts

eth_dev->tx_pkt_burst = &bnxt_xmit_pkts;

l  bnxt_xmit_pkts

点击(此处)折叠或打开

  1. uint16_t bnxt_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts,
  2.              uint16_t nb_pkts)
  3. {
  4.     struct bnxt_tx_queue *txq = tx_queue;
  5.     uint16_t nb_tx_pkts = 0;
  6.     uint16_t db_mask = txq->tx_ring->tx_ring_struct->ring_size >> 2;
  7.     uint16_t last_db_mask = 0;

  8.     /* Handle TX completions */
  9.     bnxt_handle_tx_cp(txq);

  10.     /* Handle TX burst request */
  11.     for (nb_tx_pkts = 0; nb_tx_pkts < nb_pkts; nb_tx_pkts++) {
  12.         if (bnxt_start_xmit(tx_pkts[nb_tx_pkts], txq)) {
  13.             break;
  14.         } else if ((nb_tx_pkts & db_mask) != last_db_mask) {
  15.             B_TX_DB(txq->tx_ring->tx_doorbell,
  16.                     txq->tx_ring->tx_prod);
  17.             last_db_mask = nb_tx_pkts & db_mask;
  18.         }
  19.     }
  20.     if (nb_tx_pkts)
  21.         B_TX_DB(txq->tx_ring->tx_doorbell, txq->tx_ring->tx_prod);

  22.     return nb_tx_pkts;
  23. }

这个函数的逻辑可以分为三个部分来看:

首先是bnxt_handle_tx_cp,这里的cp是指complete(完成),这个函数主要负责处理之前网卡已经发送完成的mbuf,也就是网卡已经通过DMAmbuf中的数据拷贝走,软件可以释放mbuf的逻辑;

其次是bnxt_start_xmit,这个是真正的发送逻辑,其实这里的发送也并不是真的把数据拷贝到网卡上,而是根据每个mbuf的数据地址设置到bd ring,从而告诉网卡DMA拷贝的源地址;

最后是B_TX_DBtx_doorbell的写操作,作用就是前面bd的地址信息已经填充完毕,告诉网卡可以发起DMA了。

在分析具体函数前,首先熟悉一下相关的数据结构。和tx_queue相关联的有两个ring,一个是tx_ring(发送ring),一个是cp_ring(完成ring)。其数据结构关系如下:

下面分别介绍三个阶段的实现。

释放已经DMA完成的mbuf

l  bnxt_handle_tx_cp

点击(此处)折叠或打开

  1. static int bnxt_handle_tx_cp(struct bnxt_tx_queue *txq)
  2. {
  3.     struct bnxt_cp_ring_info *cpr = txq->cp_ring;
  4.     uint32_t raw_cons = cpr->cp_raw_cons; /* 记录完成队列上次完成队列释放(consumer)的index */
  5.     uint32_t cons;
  6.     int nb_tx_pkts = 0;
  7.     struct tx_cmpl *txcmp;

  8.     if ((txq->tx_ring->tx_ring_struct->ring_size -
  9.             (bnxt_tx_avail(txq->tx_ring))) >
  10.             txq->tx_free_thresh) { /*如果发送ring中的已用desc数量大于tx_free_thresh*/
  11.         while (1) {
  12.             cons = RING_CMP(cpr->cp_ring_struct, raw_cons);
  13.             txcmp = (struct tx_cmpl *)&cpr->cp_desc_ring[cons];

  14.             /* struct tx_cmpl中的type由硬件设置,TX_CMPL_TYPE_TX_L2表示网卡已经DMA完成,软件可以释放mbuf中的数据了 */
  15.             if (CMP_TYPE(txcmp) == TX_CMPL_TYPE_TX_L2)
  16.                 nb_tx_pkts++;
  17.             else
  18.                 RTE_LOG_DP(DEBUG, PMD,
  19.                         "Unhandled CMP type %02x\n",
  20.                         CMP_TYPE(txcmp));
  21.             raw_cons = NEXT_RAW_CMP(raw_cons); /* raw_cons = raw_cons + 1 */
  22.         }
  23.         if (nb_tx_pkts) /* nb_tx_pkts记录了本次可以释放的mbuf数量 */
  24.             bnxt_tx_cmp(txq, nb_tx_pkts); /* 释放mbuf */
  25.         cpr->cp_raw_cons = raw_cons; /* 更新 cpr->cp_raw_cons */
  26.         B_CP_DIS_DB(cpr, cpr->cp_raw_cons); /* 通过cp ring的cp_doorbell通知硬件对应的cp ring bd已经可以释放了*/
  27.     }
  28.     return nb_tx_pkts;
  29. }


真正释放mbuf的操作是在bnxt_tx_cmp函数完成的。

l  bnxt_tx_cmp


点击(此处)折叠或打开

  1. static void bnxt_tx_cmp(struct bnxt_tx_queue *txq, int nr_pkts)
  2. {
  3.     struct bnxt_tx_ring_info *txr = txq->tx_ring;
  4.     uint16_t cons = txr->tx_cons;
  5.     int i, j;

  6.     for (i = 0; i < nr_pkts; i++) {
  7.         struct bnxt_sw_tx_bd *tx_buf;
  8.         struct rte_mbuf *mbuf;

  9.         tx_buf = &txr->tx_buf_ring[cons]; /*tx_buf_ring存放txring中的mbuf*/
  10.         cons = RING_NEXT(txr->tx_ring_struct, cons);
  11.         mbuf = tx_buf->mbuf;
  12.         tx_buf->mbuf = NULL;

  13.         /* EW - no need to unmap DMA memory? */
  14.         /* tx_buf->nr_bds记录一个mbuf对应的bd数量,一个mbuf可能对应多个bd */
  15.         for (j = 1; j < tx_buf->nr_bds; j++)
  16.             cons = RING_NEXT(txr->tx_ring_struct, cons); /* cons = cons + 1 */
  17.         rte_pktmbuf_free(mbuf);
  18.     }

  19.     txr->tx_cons = cons; /* 清空了一部分mbuf,更新consumer index */
  20. }


这里注意一点,在函数的最后更新tx_ringconsumer index,虽然对于发送端来说,软件驱动是productor(产生数据),网卡是consumer(消费数据),但是真正释放数据还是由软件驱动完成,所以consumer也是要在软件更新的。

数据包发送

数据包发送是在bnxt_start_xmit中完成的。

l  bnxt_start_xmit


点击(此处)折叠或打开

  1. static uint16_t bnxt_start_xmit(struct rte_mbuf *tx_pkt,
  2.                 struct bnxt_tx_queue *txq)
  3. {
  4.     struct bnxt_tx_ring_info *txr = txq->tx_ring;
  5.     struct tx_bd_long *txbd;
  6.     struct tx_bd_long_hi *txbd1;
  7.     uint32_t vlan_tag_flags, cfa_action;
  8.     bool long_bd = false;
  9.     uint16_t last_prod = 0;
  10.     struct rte_mbuf *m_seg;
  11.     struct bnxt_sw_tx_bd *tx_buf;
  12.     static const uint32_t lhint_arr[4] = {
  13.         TX_BD_LONG_FLAGS_LHINT_LT512,
  14.         TX_BD_LONG_FLAGS_LHINT_LT1K,
  15.         TX_BD_LONG_FLAGS_LHINT_LT2K,
  16.         TX_BD_LONG_FLAGS_LHINT_LT2K
  17.     };

  18.     if (tx_pkt->ol_flags & (PKT_TX_TCP_SEG | PKT_TX_TCP_CKSUM |
  19.                 PKT_TX_UDP_CKSUM | PKT_TX_IP_CKSUM |
  20.                 PKT_TX_VLAN_PKT | PKT_TX_OUTER_IP_CKSUM))
  21.         long_bd = true;
  22.     /* 1. 将待发送的mbuf放入tx_ring的bnxt_sw_tx_bd中 */
  23.     tx_buf = &txr->tx_buf_ring[txr->tx_prod];
  24.     tx_buf->mbuf = tx_pkt;
  25.     tx_buf->nr_bds = long_bd + tx_pkt->nb_segs;
  26.     /* 一个mbuf可能对应多个bd,last_prod指向该mbuf对应的最后一个bd的index */
  27.     last_prod = (txr->tx_prod + tx_buf->nr_bds - 1) &
  28.                 txr->tx_ring_struct->ring_mask;

  29.     if (unlikely(bnxt_tx_avail(txr) < tx_buf->nr_bds))
  30.         return -ENOMEM;
  31.     /* 2. 根据mbuf的信息设置rx tx_desc_ring中对应的bd,其中关键是txbd->addr */
  32.     txbd = &txr->tx_desc_ring[txr->tx_prod];
  33.     txbd->opaque = txr->tx_prod;
  34.     txbd->flags_type = tx_buf->nr_bds << TX_BD_LONG_FLAGS_BD_CNT_SFT;
  35.     txbd->len = tx_pkt->data_len;
  36.     if (txbd->len >= 2014)
  37.         txbd->flags_type |= TX_BD_LONG_FLAGS_LHINT_GTE2K;
  38.     else
  39.         txbd->flags_type |= lhint_arr[txbd->len >> 9];
  40.     /* txbd->addr是mbuf的dma地址,也就是iova地址 */
  41.     txbd->addr = rte_cpu_to_le_32(RTE_MBUF_DATA_DMA_ADDR(tx_buf->mbuf)); /* txr->tx_prod = txr->tx_prod + 1 */

  42.     if (long_bd) {
  43.         txbd->flags_type |= TX_BD_LONG_TYPE_TX_BD_LONG;
  44.         vlan_tag_flags = 0;
  45.         cfa_action = 0;
  46.         if (tx_buf->mbuf->ol_flags & PKT_TX_VLAN_PKT) {
  47.             /* shurd: Should this mask at
  48.              * TX_BD_LONG_CFA_META_VLAN_VID_MASK?
  49.              */
  50.             vlan_tag_flags = TX_BD_LONG_CFA_META_KEY_VLAN_TAG |
  51.                 tx_buf->mbuf->vlan_tci;
  52.             /* Currently supports 8021Q, 8021AD vlan offloads
  53.              * QINQ1, QINQ2, QINQ3 vlan headers are deprecated
  54.              */
  55.             /* DPDK only supports 802.11q VLAN packets */
  56.             vlan_tag_flags |=
  57.                     TX_BD_LONG_CFA_META_VLAN_TPID_TPID8100;
  58.         }
  59.         /* 更新tx_ring的productor index */
  60.         txr->tx_prod = RING_NEXT(txr->tx_ring_struct, txr->tx_prod);

  61.         txbd1 = (struct tx_bd_long_hi *)
  62.                     &txr->tx_desc_ring[txr->tx_prod];
  63.         txbd1->lflags = 0;
  64.         txbd1->cfa_meta = vlan_tag_flags;
  65.         txbd1->cfa_action = cfa_action;
  66.         /* 根据mbuf的ol_flags设置bd中对应的flag */
  67.         if (tx_pkt->ol_flags & PKT_TX_TCP_SEG) {
  68.             /* TSO */
  69.             txbd1->lflags |= TX_BD_LONG_LFLAGS_LSO;
  70.             txbd1->hdr_size = tx_pkt->l2_len + tx_pkt->l3_len +
  71.                     tx_pkt->l4_len + tx_pkt->outer_l2_len +
  72.                     tx_pkt->outer_l3_len;
  73.             txbd1->mss = tx_pkt->tso_segsz;

  74.         } else if ((tx_pkt->ol_flags & PKT_TX_OIP_IIP_TCP_UDP_CKSUM) ==
  75.              PKT_TX_OIP_IIP_TCP_UDP_CKSUM) {
  76.             /* Outer IP, Inner IP, Inner TCP/UDP CSO */
  77.             txbd1->lflags |= TX_BD_FLG_TIP_IP_TCP_UDP_CHKSUM;
  78.             txbd1->mss = 0;
  79.         } else if ((tx_pkt->ol_flags & PKT_TX_IIP_TCP_UDP_CKSUM) ==
  80.              PKT_TX_IIP_TCP_UDP_CKSUM) {
  81.             /* (Inner) IP, (Inner) TCP/UDP CSO */
  82.             txbd1->lflags |= TX_BD_FLG_IP_TCP_UDP_CHKSUM;
  83.             txbd1->mss = 0;
  84.         } else if ((tx_pkt->ol_flags & PKT_TX_OIP_TCP_UDP_CKSUM) ==
  85.              PKT_TX_OIP_TCP_UDP_CKSUM) {
  86.             /* Outer IP, (Inner) TCP/UDP CSO */
  87.             txbd1->lflags |= TX_BD_FLG_TIP_TCP_UDP_CHKSUM;
  88.             txbd1->mss = 0;
  89.         } else if ((tx_pkt->ol_flags & PKT_TX_OIP_IIP_CKSUM) ==
  90.              PKT_TX_OIP_IIP_CKSUM) {
  91.             /* Outer IP, Inner IP CSO */
  92.             txbd1->lflags |= TX_BD_FLG_TIP_IP_CHKSUM;
  93.             txbd1->mss = 0;
  94.         } else if ((tx_pkt->ol_flags & PKT_TX_TCP_UDP_CKSUM) ==
  95.              PKT_TX_TCP_UDP_CKSUM) {
  96.             /* TCP/UDP CSO */
  97.             txbd1->lflags |= TX_BD_LONG_LFLAGS_TCP_UDP_CHKSUM;
  98.             txbd1->mss = 0;
  99.         } else if (tx_pkt->ol_flags & PKT_TX_IP_CKSUM) {
  100.             /* IP CSO */
  101.             txbd1->lflags |= TX_BD_LONG_LFLAGS_IP_CHKSUM;
  102.             txbd1->mss = 0;
  103.         } else if (tx_pkt->ol_flags & PKT_TX_OUTER_IP_CKSUM) {
  104.             /* IP CSO */
  105.             txbd1->lflags |= TX_BD_LONG_LFLAGS_T_IP_CHKSUM;
  106.             txbd1->mss = 0;
  107.         }
  108.     } else {
  109.         txbd->flags_type |= TX_BD_SHORT_TYPE_TX_BD_SHORT;
  110.     }

  111.     m_seg = tx_pkt->next;
  112.     /* i is set at the end of the if(long_bd) block */
  113.     while (txr->tx_prod != last_prod) {
  114.          /* 更新tx_ring的productor index */
  115.         txr->tx_prod = RING_NEXT(txr->tx_ring_struct, txr->tx_prod); /* txr->tx_prod = txr->tx_prod + 1 */
  116.         tx_buf = &txr->tx_buf_ring[txr->tx_prod];

  117.         txbd = &txr->tx_desc_ring[txr->tx_prod];
  118.         txbd->addr = rte_cpu_to_le_32(RTE_MBUF_DATA_DMA_ADDR(m_seg));
  119.         txbd->flags_type = TX_BD_SHORT_TYPE_TX_BD_SHORT;
  120.         txbd->len = m_seg->data_len;

  121.         m_seg = m_seg->next;
  122.     }

  123.     txbd->flags_type |= TX_BD_LONG_FLAGS_PACKET_END;
  124.      /* 更新tx_ring的productor index */
  125.     txr->tx_prod = RING_NEXT(txr->tx_ring_struct, txr->tx_prod); /* txr->tx_prod = txr->tx_prod + 1 */

  126.     return 0;
  127. }


其中值得注意的有两点,一个是mbufdbtx_bd_long)转换的过程,其bd地址设置为mbufiova地址,也就是dma地址。


点击(此处)折叠或打开

  1. txbd->addr = rte_cpu_to_le_32(RTE_MBUF_DATA_DMA_ADDR(tx_buf->mbuf));
  2. #define RTE_MBUF_DATA_DMA_ADDR(mb) \
  3.     ((uint64_t)((mb)->buf_iova + (mb)->data_off))


另一方面是在发送过程中会更新tx_ringproductor index

启动DMA

启动硬件DMA拷贝是通过一下语句完成:

B_TX_DB(txq->tx_ring->tx_doorbell, txq->tx_ring->tx_prod)

tx_ringproductor indextx_ring->tx_prod)写入tx_ringtx_doorbell中。而无论是cp_ringcp_doorbell还是tx_ringtx_doorbell都在在bnxt_alloc_hwrm_rings函数中初始化为设备的bar空间地址的。


点击(此处)折叠或打开

  1. cpr->cp_doorbell = (char *)pci_dev->mem_resource[2].addr + idx * 0x80;
  2. txr->tx_doorbell = (char *)pci_dev->mem_resource[2].addr + idx * 0x80;


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