一、中文注释
// 定义一个处理InfiniBand接收完成工作请求的函数
static void ipoib_ib_handle_rx_wc(struct net_device *dev, struct ib_wc *wc)
{
// 通过网络设备获取私有数据结构
struct ipoib_dev_priv *priv = ipoib_priv(dev);
// 获取工作请求ID,并屏蔽掉接收操作的标志位
unsigned int wr_id = wc->wr_id & ~IPOIB_OP_RECV;
struct sk_buff *skb;
u64 mapping[IPOIB_UD_RX_SG];
union ib_gid *dgid;
union ib_gid *sgid;
// 调试输出接收完成信息
ipoib_dbg_data(priv, "recv completion: id %d, status: %d\n",
wr_id, wc->status);
// 检查工作请求ID是否超出接收队列大小
if (unlikely(wr_id >= priv->recvq_size)) {
ipoib_warn(priv, "recv completion event with wrid %d (> %d)\n",
wr_id, priv->recvq_size);
return; // 超出则返回
}
// 获取与工作请求关联的socket缓冲区
skb = priv->rx_ring[wr_id].skb;
// 检查工作请求的状态是否为成功
if (unlikely(wc->status != IB_WC_SUCCESS)) {
// 若失败且不是因为flush错误,打印警告信息
if (wc->status != IB_WC_WR_FLUSH_ERR)
ipoib_warn(priv,
"failed recv event (status=%d, wrid=%d vend_err %#x)\n",
wc->status, wr_id, wc->vendor_err);
// 解除DMA映射并释放socket缓冲区
ipoib_ud_dma_unmap_rx(priv, priv->rx_ring[wr_id].mapping);
dev_kfree_skb_any(skb);
// 将rx_ring中对应项的skb置为空
priv->rx_ring[wr_id].skb = NULL;
return; // 返回
}
// 复制DMA映射地址到本地变量
memcpy(mapping, priv->rx_ring[wr_id].mapping,
IPOIB_UD_RX_SG * sizeof(*mapping));
/*
* 如果无法分配新的接收缓冲区,则丢弃此数据包
* 并重用旧缓冲区。
*/
if (unlikely(!ipoib_alloc_rx_skb(dev, wr_id))) {
// 增加丢弃的数据包计数
++dev->stats.rx_dropped;
// 跳转到重新提交接收请求的代码段
goto repost;
}
// 调试输出接收到的数据长度和源LID
ipoib_dbg_data(priv, "received %d bytes, SLID 0x%04x\n",
wc->byte_len, wc->slid);
// 解除DMA映射
ipoib_ud_dma_unmap_rx(priv, mapping);
// 设定skb数据长度
skb_put(skb, wc->byte_len);
/* 第一个dgid字节的值为0xff时代表多播 */
dgid = &((struct ib_grh *)skb->data)->dgid;
// 根据GRH判断消息类型(单播、多播或广播)
if (!(wc->wc_flags & IB_WC_GRH) || dgid->raw[0] != 0xff)
skb->pkt_type = PACKET_HOST;
else if (memcmp(dgid, dev->broadcast + 4, sizeof(union ib_gid)) == 0)
skb->pkt_type = PACKET_BROADCAST;
else
skb->pkt_type = PACKET_MULTICAST;
// 获取源GID
sgid = &((struct ib_grh *)skb->data)->sgid;
/*
* 丢弃由此接口发送的数据包,即HCA已复制的多播数据包。
*/
if (wc->slid == priv->local_lid && wc->src_qp == priv->qp->qp_num) {
int need_repost = 1;
if ((wc->wc_flags & IB_WC_GRH) &&
sgid->global.interface_id != priv->local_gid.global.interface_id)
need_repost = 0;
if (need_repost) {
// 释放skb并跳转至重新提交接收请求
dev_kfree_skb_any(skb);
goto repost;
}
}
// 移除GRH头
skb_pull(skb, IB_GRH_BYTES);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)) && ! defined(HAVE_SK_BUFF_CSUM_LEVEL)
/* 仅适用于旧的内核版本,表示重新组装大小 */
skb->truesize = SKB_TRUESIZE(skb->len);
#endif
// 设置协议头
skb->protocol = ((struct ipoib_header *) skb->data)->proto;
// 添加伪头
skb_add_pseudo_hdr(skb);
// 更新接收统计信息
++dev->stats.rx_packets;
dev->stats.rx_bytes += skb->len;
// 如果收到的是多播包,增加多播计数
if (skb->pkt_type == PACKET_MULTICAST)
dev->stats.multicast++;
// 处理ARP协议包,用于路径发现
if (unlikely(be16_to_cpu(skb->protocol) == ETH_P_ARP))
ipoib_create_repath_ent(dev, skb, wc->slid);
// 设置数据包关联的网络设备
skb->dev = dev;
// 如果设备支持接收校验和处理,且工作请求指示校验和ok,则跳过校验和处理
if ((dev->features & NETIF_F_RXCSUM) &&
likely(wc->wc_flags & IB_WC_IP_CSUM_OK))
skb->ip_summed = CHECKSUM_UNNECESSARY;
#ifdef CONFIG_COMPAT_LRO_ENABLED_IPOIB
// 如果设备支持大接收下偏移(LRO),则使用LRO处理skb,否则使用普通的接收处理
if (dev->features & NETIF_F_LRO)
lro_receive_skb(&priv->lro.lro_mgr, skb, NULL);
else
netif_receive_skb(skb);
#else
// 使用NAPI通用接收处理接收到的数据包
napi_gro_receive(&priv->recv_napi, skb);
#endif
// 重新提交接收请求的代码段
repost:
// 重用前面设置好的缓冲区,提交新的接收工作请求
if (unlikely(ipoib_ib_post_receive(dev, wr_id)))
// 如果提交失败,打印警告信息
ipoib_warn(priv, "ipoib_ib_post_receive failed "
"for buf %d\n", wr_id);
}
以上注释形式遵循C语言的单行注释模式,对函数中的每个主要步骤提供了简要的中文解释。此函数是针对IP over InfiniBand (IPoIB) 设备的InfiniBand接收完成处理函数。它处理从InfiniBand网络接收到的数据包,进行协议处理,并最终将收到的数据包发送到上层网络栈。此外,函数还负责根据当前的工作请求和状态更新设备的统计信息,并处理特殊的ARP数据包,进行资源回收以及重用缓冲区来提交新的接收请求。
二、中文讲解
这个函数`ipoib_ib_handle_rx_wc`是处理InfiniBand接口上的接收完成工作请求(Work Completion, WC)的函数,主要用于接收数据包并处理。下面将该函数的主要部分用中文解释:
1. 获取私有数据结构和工作请求ID(wr_id):
struct ipoib_dev_priv *priv = ipoib_priv(dev);
unsigned int wr_id = wc->wr_id & ~IPOIB_OP_RECV;
提取出工作请求ID,这用于索引接收队列。
2. 日志记录:
ipoib_dbg_data(priv, "recv completion: id %d, status: %d\n", wr_id, wc->status);
如果工作请求ID超出了预期范围,则记录一条警告信息。
3. 检查工作请求完成状态:
if (unlikely(wc->status != IB_WC_SUCCESS)) { ... }
如果状态不是成功的,清理相应的资源并返回。
4. 定位接收缓冲区并尝试分配新的RX缓冲区:
skb = priv->rx_ring[wr_id].skb;
if (unlikely(!ipoib_alloc_rx_skb(dev, wr_id))) { goto repost; }
如果分配失败,则丢弃这个数据包并重用旧的缓冲区。
5. 记录接收到的数据包大小和源本地ID(SLID):
ipoib_dbg_data(priv, "received %d bytes, SLID 0x%04x\n", wc->byte_len, wc->slid);
6. 根据数据包第一个字节是否为0xff判断是否为多播,并设置skb的包类型:
if (!(wc->wc_flags & IB_WC_GRH) || dgid->raw[0] != 0xff) { ... }
根据比较目的全局ID(dgid),确认包的类型(单播、广播或者多播)。
7. 如果数据包是由本接口发送(例如多播包被IO控制器复制了),则丢弃该包:
if (wc->slid == priv->local_lid && wc->src_qp == priv->qp->qp_num) { ... }
8. 从数据包中移除Global Routing Header (GRH):
skb_pull(skb, IB_GRH_BYTES);
9. 设置数据包的协议类型,统计信息,并将包交付到网络层:
skb->protocol = ((struct ipoib_header *) skb->data)->proto;
netif_receive_skb(skb);
10. 在必要的情况下,重新将RX缓冲区提交给硬件:
if (unlikely(ipoib_ib_post_receive(dev, wr_id))) { ... }
如果重新提交RX缓冲区失败,则记录警告信息。
整体来说,这个函数负责管理InfiniBand接口接收到的数据包,包括分配和管理资源,以及将接收到的数据递交给更高层的网络协议处理。