一、ipoib_send函数定义
cpp
int ipoib_send(struct net_device *dev, struct sk_buff *skb,
struct ib_ah *address, u32 dqpn)
{
struct ipoib_dev_priv *priv = ipoib_priv(dev);
struct ipoib_tx_buf *tx_req;
int hlen, rc;
void *phead;
unsigned int usable_sge = priv->max_send_sge - !!skb_headlen(skb);
if (skb_is_gso(skb)) {
hlen = skb_transport_offset(skb) + tcp_hdrlen(skb);
phead = skb->data;
if (unlikely(!skb_pull(skb, hlen))) {
ipoib_warn(priv, "linear data too small\n");
++dev->stats.tx_dropped;
++dev->stats.tx_errors;
dev_kfree_skb_any(skb);
return -1;
}
} else {
if (unlikely(skb->len > priv->mcast_mtu + IPOIB_ENCAP_LEN)) {
ipoib_warn(priv, "packet len %d (> %d) too long to send, dropping\n",
skb->len, priv->mcast_mtu + IPOIB_ENCAP_LEN);
++dev->stats.tx_dropped;
++dev->stats.tx_errors;
ipoib_cm_skb_too_long(dev, skb, priv->mcast_mtu);
return -1;
}
phead = NULL;
hlen = 0;
}
if (skb_shinfo(skb)->nr_frags > usable_sge) {
if (skb_linearize(skb) < 0) {
ipoib_warn(priv, "skb could not be linearized\n");
++dev->stats.tx_dropped;
++dev->stats.tx_errors;
dev_kfree_skb_any(skb);
return -1;
}
/* Does skb_linearize return ok without reducing nr_frags? */
if (skb_shinfo(skb)->nr_frags > usable_sge) {
ipoib_warn(priv, "too many frags after skb linearize\n");
++dev->stats.tx_dropped;
++dev->stats.tx_errors;
dev_kfree_skb_any(skb);
return -1;
}
}
ipoib_dbg_data(priv,
"sending packet, length=%d address=%p dqpn=0x%06x\n",
skb->len, address, dqpn);
/*
* We put the skb into the tx_ring _before_ we call post_send()
* because it's entirely possible that the completion handler will
* run before we execute anything after the post_send(). That
* means we have to make sure everything is properly recorded and
* our state is consistent before we call post_send().
*/
tx_req = &priv->tx_ring[priv->tx_head & (priv->sendq_size - 1)];
tx_req->skb = skb;
if (skb->len < ipoib_inline_thold &&
!skb_shinfo(skb)->nr_frags) {
tx_req->is_inline = 1;
priv->tx_wr.wr.send_flags |= IB_SEND_INLINE;
} else {
if (unlikely(ipoib_dma_map_tx(priv->ca, tx_req))) {
++dev->stats.tx_errors;
dev_kfree_skb_any(skb);
return -1;
}
tx_req->is_inline = 0;
priv->tx_wr.wr.send_flags &= ~IB_SEND_INLINE;
}
if (skb->ip_summed == CHECKSUM_PARTIAL)
priv->tx_wr.wr.send_flags |= IB_SEND_IP_CSUM;
else
priv->tx_wr.wr.send_flags &= ~IB_SEND_IP_CSUM;
/* increase the tx_head after send success, but use it for queue state */
if (atomic_read(&priv->tx_outstanding) == priv->sendq_size - 1) {
ipoib_dbg(priv, "TX ring full, stopping kernel net queue\n");
netif_stop_queue(dev);
}
skb_orphan(skb);
skb_dst_drop(skb);
if (netif_queue_stopped(dev))
if (ib_req_notify_cq(priv->send_cq, IB_CQ_NEXT_COMP |
IB_CQ_REPORT_MISSED_EVENTS) < 0)
ipoib_warn(priv, "request notify on send CQ failed\n");
rc = post_send(priv, priv->tx_head & (priv->sendq_size - 1),
address, dqpn, tx_req, phead, hlen);
if (unlikely(rc)) {
ipoib_warn(priv, "post_send failed, error %d\n", rc);
++dev->stats.tx_errors;
if (!tx_req->is_inline)
ipoib_dma_unmap_tx(priv, tx_req);
dev_kfree_skb_any(skb);
if (netif_queue_stopped(dev))
netif_wake_queue(dev);
rc = 0;
} else {
netif_trans_update(dev);
rc = priv->tx_head;
++priv->tx_head;
atomic_inc(&priv->tx_outstanding);
}
return rc;
}
二、函数解读
是的,这个函数`ipoib_send`是IPoIB驱动中的一个函数,它的责任是从上层网络协议栈接收数据并使用InfiniBand(IB)将其发送到网络中。具体的步骤包括:
-
ipoib_send接收四个参数:`net_device *dev`指向网络设备的指针;`struct sk_buff *skb`是要发送的套接字缓冲区(socket buffer);`struct ib_ah *address`是目标地址句柄(address handle),用于IB通信;`u32 dqpn`是目的QP(Queue Pair)编号。
-
函数首先通过`ipoib_priv(dev)`获取与网络设备关联的IPoIB设备私有数据结构。
-
然后这个函数检查传入的`skb`是否是一个GSO包(Generic Segmentation Offload),也就是大型数据包需要分段发送的情况。如果是,它计算传输头的长度,并通过`skb_pull`调整`skb`的数据指针,排除这些头部。
-
如果`skb`的长度超过了IPoIB的最大传输单位(MTU)加上封装的长度,函数会打印一条警告信息,递增统计计数器,处理`skb`为过长,并返回。
-
接下来的代码检查是否有足够的scatter/gather元素(分布/聚集元素)去描述`skb`的数据。如果不是,会尝试使`skb`变成一个线性的数据结构;如果线性化失败,函数会记录一个错误,释放`skb`并返回。
-
在将`skb`发送到IB硬件之前,函数将它放入传输队列`tx_ring`。这个队列是一个环形缓冲区,用来存储即将被发送或正等待完成通知的包。
-
函数设置`tx_req`结构体中与`skb`相关的字段,并根据条件判定数据是否可以内联发送(内联发送即数据随发送请求一起发送,而非通过DMA读取)。
-
函数还设置是否启用IP校验和卸载功能。
-
在发送操作之前,代码检查传输队列是否已满。如果满了,则会停止网络队列。
-
函数 then calls post_send,该函数负责准备IB发送请求并提交到硬件进行处理。
-
如果`post_send`返回错误,函数会记录错误,回滚上面的操作,并唤醒网络队列(如果它停止了)。
-
如果`post_send`成功,函数修正`tx_head`,也就是传输队列头的位置,并增加处理中的传输数量`tx_outstanding`。
-
最后,函数返回`post_send`的结果或者一个错误码。如果成功,返回的是之前增加的`tx_head`的值。
总体来说,`ipoib_send`负责处理将要发送的数据包,包括准备IB发送请求,处理数据包的线性化和分散/聚集元素,以及向IB硬件提交发送任务等。如果传输过程中遇到错误,它会统一处理错误情况,包括记录错误,释放资源,及时唤醒网络队列等。