分析网络协议栈的代码,如果不看驱动代码的话,总是感觉没有完全落到实处。
由于手头上只有rtl8169的网卡设备,为了方便调试和分析,因此选择该款设备对应的驱动进行分析:r8169.ko,内核代码还是基于3.1.3
对于协议栈而言:
1)发包的最后步骤为调用驱动注册的ndo_start_xmit钩子函数,具体如何实现由驱动完成
2)收包由中断触发,驱动负责skb的生成以及其他操作如校验等,然后传给协议栈
但是这些都只是模糊的概念,具体如何操作,如卸载功能、NAPI如何在驱动层次实现没有完全清晰的了解。
因此希望通过分析r8169的驱动代码,对更底层的逻辑有更好的掌握。
要分析驱动的代码,数据手册必不可少,特别是寄存器以及描述符的说明。
发包流程:
对于驱动而言,发包流程简单的可以总结为:根据skb赋值好发送描述符,然后告诉硬件进行发包
具体代码如下:
-
static const struct net_device_ops rtl8169_netdev_ops = {
-
.ndo_open = rtl8169_open,
-
.ndo_stop = rtl8169_close,
-
.ndo_get_stats = rtl8169_get_stats,
-
.ndo_start_xmit = rtl8169_start_xmit, //r8169对应的发包函数
-
.ndo_tx_timeout = rtl8169_tx_timeout,
-
.ndo_validate_addr = eth_validate_addr,
-
.ndo_change_mtu = rtl8169_change_mtu,
-
.ndo_fix_features = rtl8169_fix_features,
-
.ndo_set_features = rtl8169_set_features,
-
.ndo_set_mac_address = rtl_set_mac_address,
-
.ndo_do_ioctl = rtl8169_ioctl,
-
.ndo_set_multicast_list = rtl_set_rx_mode,
-
#ifdef CONFIG_NET_POLL_CONTROLLER
-
.ndo_poll_controller = rtl8169_netpoll,
-
#endif
-
-
}
要看懂发包函数的代码,需要先查看r8169的数据手册,对发送描述符以及一些寄存器有一定的了解:
由于r8169支持TSO卸载功能,因此其发送描述符分成两种格式:
ps:网上没有找到完全对应的数据手册,因此名字以及具体的格式可能有一定的差别,但是基本上影响不大
有了上面的说明,就可以看懂r8169的发包流程了
-
static netdev_tx_t rtl8169_start_xmit(struct sk_buff *skb,
-
struct net_device *dev)
-
{
-
struct rtl8169_private *tp = netdev_priv(dev);
-
unsigned int entry = tp->cur_tx % NUM_TX_DESC;
-
struct TxDesc *txd = tp->TxDescArray + entry;
-
void __iomem *ioaddr = tp->mmio_addr;
-
struct device *d = &tp->pci_dev->dev;
-
dma_addr_t mapping;
-
u32 status, len;
-
u32 opts[2];
-
int frags;
-
//首先查看发送描述符是否足够,A skbuff with nr_frags needs nr_frags+1 entries in the tx queue
-
if (unlikely(TX_BUFFS_AVAIL(tp) < skb_shinfo(skb)->nr_frags)) { //这个判断其实有点问题,应该再加1,最新的代码修复了这个问题
-
netif_err(tp, drv, dev, "BUG! Tx Ring full when queue awake!\n");
-
goto err_stop_0;
-
}
-
//从DescOwn的定义可以知道,如果为1表示该描述符为网卡所有,等待发送,即不是空闲的,驱动没法用
-
//基本操作:初始化为0,驱动填充完后为1,网卡发送完后触发中断,释放资源,又赋值为0
-
if (unlikely(le32_to_cpu(txd->opts1) & DescOwn)) //
-
goto err_stop_0;
-
//首先处理线形区的数据,分配一个发送描述符
-
len = skb_headlen(skb);
-
mapping = dma_map_single(d, skb->data, len, DMA_TO_DEVICE); //进行DMA映射
-
if (unlikely(dma_mapping_error(d, mapping))) {
-
if (net_ratelimit())
-
netif_err(tp, drv, dev, "Failed to map TX DMA!\n");
-
goto err_dma_0;
-
}
-
-
tp->tx_skb[entry].len = len;
-
txd->addr = cpu_to_le64(mapping);
-
-
opts[1] = cpu_to_le32(rtl8169_tx_vlan_tag(tp, skb));
-
opts[0] = DescOwn;
-
//根据skb_shinfo(skb)->gso_size决定使用哪种发送描述符
-
rtl8169_tso_csum(tp, skb, opts);
-
-
frags = rtl8169_xmit_frags(tp, skb, opts); //对每一个frag分配一个发送描述符和它对应
-
if (frags < 0)
-
goto err_dma_1;
-
else if (frags) //skb有frag,即该skb对应>=2的发送描述符,即FirstFrag和LastFrag不是同一个
-
opts[0] |= FirstFrag;
-
else { //skb没有frag,即该skb只对应一个发送描述符,FirstFrag和LastFrag是同一个
-
opts[0] |= FirstFrag | LastFrag;
-
tp->tx_skb[entry].skb = skb;
-
}
-
-
txd->opts2 = cpu_to_le32(opts[1]);
-
-
wmb();
-
-
/* Anti gcc 2.95.3 bugware (sic) */
-
status = opts[0] | len | (RingEnd * !((entry + 1) % NUM_TX_DESC));
-
txd->opts1 = cpu_to_le32(status);
-
-
tp->cur_tx += frags + 1;
-
-
wmb();
-
-
RTL_W8(TxPoll, NPQ); //置位TxPoll寄存器的NPQ位,告诉硬件可以发包了
-
-
//下面是流量控制,如果发送描述符不够的话暂时关闭该发送队列,等资源释放后再开启
-
if (TX_BUFFS_AVAIL(tp) < MAX_SKB_FRAGS) {
-
netif_stop_queue(dev);
-
smp_rmb();
-
if (TX_BUFFS_AVAIL(tp) >= MAX_SKB_FRAGS)
-
netif_wake_queue(dev);
-
}
-
-
return NETDEV_TX_OK;
-
-
err_dma_1:
-
rtl8169_unmap_tx_skb(d, tp->tx_skb + entry, txd);
-
err_dma_0:
-
dev_kfree_skb(skb);
-
dev->stats.tx_dropped++;
-
return NETDEV_TX_OK;
-
-
err_stop_0:
-
netif_stop_queue(dev);
-
dev->stats.tx_dropped++;
-
return NETDEV_TX_BUSY;
-
}
由于两种发送描述符的结构不一样,因此分别赋值,开启TSO的话,指定TD_LSO标记,让硬件进行分包和校验
不开启TSO的话,根据传输类型,指定校验标记,让硬件完成校验功能。
-
static inline void rtl8169_tso_csum(struct rtl8169_private *tp,
-
struct sk_buff *skb, u32 *opts)
-
{
-
const struct rtl_tx_desc_info *info = tx_desc_info + tp->txd_version;
-
u32 mss = skb_shinfo(skb)->gso_size;
-
int offset = info->opts_offset;
-
-
if (mss) {
-
opts[0] |= TD_LSO;
-
opts[offset] |= min(mss, TD_MSS_MAX) << info->mss_shift; //分片的大小
-
} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
-
const struct iphdr *ip = ip_hdr(skb);
-
-
if (ip->protocol == IPPROTO_TCP)
-
opts[offset] |= info->checksum.tcp;
-
else if (ip->protocol == IPPROTO_UDP)
-
opts[offset] |= info->checksum.udp;
-
else
-
WARN_ON_ONCE(1);
-
}
-
}
从上面可以看到,驱动是如何实现卸载功能的(分片以及校验功能),和协议栈是如何交互的:
1)根据参数skb_shinfo(skb)->gso_size,决定使用的发送描述符的类型,反过来,
上层协议栈肯定是感知到底层可以分片的情况下才会把大包传下来的。
2)上层没有校验的话,硬件进行校验,反过来上层协议栈肯定是感知到底层可以校验的情况下才不进行校验的
收包流程
和发包类似于同步的流程不一样,收包是异步的,中断触发,直观的感觉发包应该也有中断,即硬件发完一个包后触发一个中断,释放相应的资源,
但是目前很多驱动使用了变通的方法:在收包中断中处理相应的流程,r8169驱动就是这么做的。
目前主流网卡驱动都使用了NAPI的机制,即中断和轮询相结合的方法来提高性能。
在网卡open的时候申请中断号:
-
retval = request_irq(dev->irq, rtl8169_interrupt,
-
(tp->features & RTL_FEATURE_MSI) ? 0 : IRQF_SHARED,
-
dev->name, dev)
在分析中断函数前,需要知道r8169对应的中断寄存器的说明:
-
SYSErr = 0x8000,
-
PCSTimeout = 0x4000,
-
SWInt = 0x0100,
-
TxDescUnavail = 0x0080,
-
RxFIFOOver = 0x0040,
-
LinkChg = 0x0020,
-
RxOverflow = 0x0010,
-
TxErr = 0x0008,
-
TxOK = 0x0004,
-
RxErr = 0x0002,
-
RxOK = 0x0001,
-
static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
-
{
-
struct net_device *dev = dev_instance;
-
struct rtl8169_private *tp = netdev_priv(dev);
-
void __iomem *ioaddr = tp->mmio_addr;
-
int handled = 0;
-
int status;
-
-
/* loop handling interrupts until we have no new ones or
-
* we hit a invalid/hotplug case.
-
*/
-
status = RTL_R16(IntrStatus);
-
while (status && status != 0xffff) { //status的某一位为1表示有相应的中断发生
-
handled = 1;
-
-
/* Handle all of the error cases first. These will reset
-
* the chip, so just exit the loop.
-
*/
-
if (unlikely(!netif_running(dev))) {
-
rtl8169_hw_reset(tp);
-
break;
-
}
-
-
if (unlikely(status & SYSErr)) { //发送错误的一些处理流程
-
rtl8169_pcierr_interrupt(dev);
-
break;
-
}
-
-
if (status & LinkChg)
-
__rtl8169_check_link_status(dev, tp, ioaddr, true);
-
-
/* We need to see the lastest version of tp->intr_mask to
-
* avoid ignoring an MSI interrupt and having to wait for
-
* another event which may never come.
-
*/
-
smp_rmb();
-
if (status & tp->intr_mask & tp->napi_event) {
-
RTL_W16(IntrMask, tp->intr_event & ~tp->napi_event); //IntrMask的某一位置1表示使能该中断,处理过程中先关闭同样的中断
-
tp->intr_mask = ~tp->napi_event; //处理完后再全部打开,具体看rtl8169_poll函数
-
-
if (likely(napi_schedule_prep(&tp->napi))) //和协议栈的NAPI机制交互,触发收包软中断,调用r8169的poll函数:rtl8169_poll
-
__napi_schedule(&tp->napi);
-
else
-
netif_info(tp, intr, dev,
-
"interrupt %04x in poll\n", status);
-
}
-
-
/* We only get a new MSI interrupt when all active irq
-
* sources on the chip have been acknowledged. So, ack
-
* everything we
收包软中断net_rx_action主要是调用各个NAPI对应的poll函数。
接下来看r8169的poll函数,该函数除了完成收包任务外,还附带完成发送相关的资源释放任务
-
static int rtl8169_poll(struct napi_struct *napi, int budget)
-
{
-
struct rtl8169_private *tp = container_of(napi, struct rtl8169_private, napi);
-
struct net_device *dev = tp->dev;
-
void __iomem *ioaddr = tp->mmio_addr;
-
int work_done;
-
-
work_done = rtl8169_rx_interrupt(dev, tp, ioaddr, (u32) budget);
-
rtl8169_tx_interrupt(dev, tp, ioaddr);
-
-
if (work_done < budget) {
-
napi_complete(napi);
-
-
/* We need for force the visibility of tp->intr_mask
-
* for other CPUs, as we can loose an MSI interrupt
-
* and potentially wait for a retransmit timeout if we don't.
-
* The posted write to IntrMask is safe, as it will
-
* eventually make it to the chip and we won't loose anything
-
* until it does.
-
*/
-
tp->intr_mask = 0xffff;
-
wmb();
-
RTL_W16(IntrMask, tp->intr_event);
-
}
-
-
return work_done;
-
}
要看懂收包流程同样需要结合数据手册:
收包和发包相反,其发送描述符相关的内容由硬件赋值好,驱动主要根据该描述符构造skb数据结构然后传给协议栈。
为了使硬件能够正确的使用发送描述符,驱动要先准备好相应的资源:
初始化后的描述符如下:
硬件赋值好的描述符如下,这种描述符是驱动用于构造skb的:
-
static int rtl8169_rx_interrupt(struct net_device *dev,
-
struct rtl8169_private *tp,
-
void __iomem *ioaddr, u32 budget)
-
{
-
unsigned int cur_rx, rx_left;
-
unsigned int count;
-
-
cur_rx = tp->cur_rx;
-
rx_left = NUM_RX_DESC + tp->dirty_rx - cur_rx; //dirty_rx始终等于cur_rx,这段代码没啥意义,新版的代码去除了这个分量
-
rx_left = min(rx_left, budget); //直接使用min(budget, NUM_RX_DESC)
-
-
for (; rx_left > 0; rx_left--, cur_rx++) {
-
unsigned int entry = cur_rx % NUM_RX_DESC;
-
struct RxDesc *desc = tp->RxDescArray + entry;
-
u32 status;
-
-
rmb();
-
status = le32_to_cpu(desc->opts1) & tp->opts1_mask;
-
-
if (status & DescOwn) //从数据手册可以知道,此时这个描述符还属于网卡,即还没有接到数据
-
break;
-
if (unlikely(status & RxRES)) { //错误综合标志位
-
netif_info(tp, rx_err, dev, "Rx ERROR. status = %08x\n",
-
status);
-
dev->stats.rx_errors++;
-
if (status & (RxRWT | RxRUNT))
-
dev->stats.rx_length_errors++;
-
if (status & RxCRC)
-
dev->stats.rx_crc_errors++;
-
if (status & RxFOVF) {
-
rtl8169_schedule_work(dev, rtl8169_reset_task);
-
dev->stats.rx_fifo_errors++;
-
}
-
rtl8169_mark_to_asic(desc, rx_buf_sz);
-
} else {
-
struct sk_buff *skb;
-
dma_addr_t addr = le64_to_cpu(desc->addr);
-
int pkt_size = (status & 0x00001FFF) - 4;
-
-
/*
-
* The driver does not support incoming fragmented
-
* frames. They are seen as a symptom of over-mtu
-
* sized frames.
-
*/
-
if (unlikely(rtl8169_fragmented_frame(status))) {
-
dev->stats.rx_dropped++;
-
dev->stats.rx_length_errors++;
-
rtl8169_mark_to_asic(desc, rx_buf_sz);
-
continue;
-
}
-
-
skb = rtl8169_try_rx_copy(tp->Rx_databuff[entry],
-
tp, pkt_size, addr);
-
rtl8169_mark_to_asic(desc, rx_buf_sz); //数据拷贝完后,该接收描述符可以释放给网卡了
-
if (!skb) {
-
dev->stats.rx_dropped++;
-
continue;
-
}
-
//如果硬件已经校验的话,赋值skb->ip_summed = CHECKSUM_UNNECESSARY;
-
rtl8169_rx_csum(skb, status);
-
skb_put(skb, pkt_size);
-
skb->protocol = eth_type_trans(skb, dev); //根据包L2层的数据赋值protocal
-
-
rtl8169_rx_vlan_tag(desc, skb);
-
-
napi_gro_receive(&tp->napi, skb); //通过GRO,把包传给协议栈
-
-
dev->stats.rx_bytes += pkt_size;
-
dev->stats.rx_packets++;
-
}
-
-
/* Work around for AMD plateform. */
-
if ((desc->opts2 & cpu_to_le32(0xfffe000)) &&
-
(tp->mac_version == RTL_GIGA_MAC_VER_05)) {
-
desc->opts2 = 0;
-
cur_rx++;
-
}
-
}
-
-
count = cur_rx - tp->cur_rx;
-
tp->cur_rx = cur_rx;
-
-
tp->dirty_rx += count;
-
-
return count;
-
}
驱动负责解析L2的头,根据mac地址的值以及协议类型设置skb的分量:
-
__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
-
{
-
struct ethhdr *eth;
-
-
skb->dev = dev;
-
skb_reset_mac_header(skb);
-
skb_pull_inline(skb, ETH_HLEN);
-
eth = eth_hdr(skb);
-
-
if (unlikely(is_multicast_ether_addr(eth->h_dest))) { //根据mac地址赋值pkt_type
-
if (!compare_ether_addr_64bits(eth->h_dest, dev->broadcast))
-
skb->pkt_type = PACKET_BROADCAST;
-
else
-
skb->pkt_type = PACKET_MULTICAST;
-
}
-
-
/*
-
* This ALLMULTI check should be redundant by 1.4
-
* so don't forget to remove it.
-
*
-
* Seems, you forgot to remove it. All silly devices
-
* seems to set IFF_PROMISC.
-
*/
-
-
else if (1 /*dev->flags&IFF_PROMISC */ ) {
-
if (unlikely(compare_ether_addr_64bits(eth->h_dest, dev->dev_addr)))
-
skb->pkt_type = PACKET_OTHERHOST;
-
}
-
-
/*
-
* Some variants of DSA tagging don't have an ethertype field
-
* at all, so we check here whether one of those tagging
-
* variants has been configured on the receiving interface,
-
* and if so, set skb->protocol without looking at the packet.
-
*/
-
if (netdev_uses_dsa_tags(dev))
-
return htons(ETH_P_DSA);
-
if (netdev_uses_trailer_tags(dev))
-
return htons(ETH_P_TRAILER);
-
-
if (ntohs(eth->h_proto) >= 1536)
-
return eth->h_proto; //IP为0x0800,ARP为0x0806
-
/*
-
* This is a magic hack to spot IPX packets. Older Novell breaks
-
* the protocol design and runs IPX over 802.3 without an 802.2 LLC
-
* layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
-
* won't work for fault tolerant netware but does for the rest.
-
*/
-
if (skb->len >= 2 && *(unsigned short *)(skb->data) == 0xFFFF)
-
return htons(ETH_P_802_3);
-
-
/*
-
* Real 802.2 LLC
-
*/
-
return htons(ETH_P_802_2);
-
}
看驱动收包后,在发给上层协议栈前skb的具体情况:
再看一下发包后资源释放的流程:
-
static void rtl8169_tx_interrupt(struct net_device *dev,
-
struct rtl8169_private *tp,
-
void __iomem *ioaddr)
-
{
-
unsigned int dirty_tx, tx_left;
-
-
dirty_tx = tp->dirty_tx;
-
smp_rmb();
-
tx_left = tp->cur_tx - dirty_tx;
-
-
while (tx_left > 0) {
-
unsigned int entry = dirty_tx % NUM_TX_DESC;
-
struct ring_info *tx_skb = tp->tx_skb + entry;
-
u32 status;
-
-
rmb();
-
status = le32_to_cpu(tp->TxDescArray[entry].opts1);
-
if (status & DescOwn) //DescOwn为1表示硬件还没发送完该包,发送完后硬件会把该位置0
-
break;
-
-
rtl8169_unmap_tx_skb(&tp->pci_dev->dev, tx_skb,
-
tp->TxDescArray + entry);
-
if (status & LastFrag) { //如果一个skb对应的所有发送描述符都可以释放的话,该skb也可以释放了
-
dev->stats.tx_packets++;
-
dev->stats.tx_bytes += tx_skb->skb->len;
-
dev_kfree_skb(tx_skb->skb);
-
tx_skb->skb = NULL;
-
}
-
dirty_tx++;
-
tx_left--;
-
}
-
-
if (tp->dirty_tx != dirty_tx) {
-
tp->dirty_tx = dirty_tx;
-
smp_wmb();
-
if (netif_queue_stopped(dev) && //如果之前由于发送描述符不够导致队列关闭的话,重新判断
-
(TX_BUFFS_AVAIL(tp) >= MAX_SKB_FRAGS)) {
-
netif_wake_queue(dev);
-
}
-
/*
-
* 8168 hack: TxPoll requests are lost when the Tx packets are
-
* too close. Let
对于网卡驱动收包流程的话可以总结为:根据接收描述符的内容构造skb,拷贝数据,赋值相应的分量,然后调用napi_gro_receive发给上层协议栈。
阅读(3100) | 评论(0) | 转发(1) |