hi35xx stmmac网卡驱动源码解读

来源:互联网 发布:勒索软件即服务 编辑:程序博客网 时间:2024/05/22 00:51

Hi35xx stmmac网卡驱动源码解读

一、源文件

drivers/net/stmmac/stmmac_main.c

源码阅读顺序如下图:

 

二、platform_device_register与platform_driver_register

platform_device_register:注册设备

platform_driver_register:注册驱动

两者通过name联系起来,所有两者的name必须要保持一致。

platform_device_register必须在驱动加载前完成,原因驱动注册时需要匹配内核中已经注册的设备名。

platform_driver_register注册时,会将当前注册的drivername和已注册的所有device中name进行比较,只有找到相同了的才能注册成功。匹配后就会调用probe进行检测设备,然后将该设备绑定到驱动上。

Platform是一个虚拟的地址总线,相比pci,USB,它主要用于描述SOC上的片上资源。使用约定:如果不属于任何总线的设备,例如网卡,串口等设备,都挂在这个虚拟总线上。

 

三、平台驱动数据结构

static struct platform_driver stmmac_driver= {

    .probe = stmmac_dvr_probe,(见第四节)

   .remove = stmmac_dvr_remove,

   .driver = {

          .name = STMMAC_RESOURCE_NAME,

          .owner = THIS_MODULE,

          .pm = &stmmac_pm_ops,

          },

};

 

static struct platform_devicestmmac_platform_device[] = {

    {

       .name = STMMAC_RESOURCE_NAME,

       .id = 0,

       .dev = {

           .platform_data = &stmmac_ethernet_platform_data,

           .dma_mask = (u64 *) ~0,

           .coherent_dma_mask = (u64) ~0,

           .release = stmmac_platform_device_release,

           },

       .num_resources = ARRAY_SIZE(stmmac_resources),

       .resource = stmmac_resources,

    }

};

 

static struct plat_stmmacenet_datastmmac_ethernet_platform_data = {

   .bus_id = 1,

   .pbl = DMA_BURST_LEN,

   .has_gmac = 1,

   .enh_desc = 1,

#ifdef TNK_HW_PLATFORM_FPGA

   .clk_csr = 0x2,     /*  For 24Mhz bus clock input */

#else      

   .clk_csr = 0x4,     /*  For 155Mhz bus clock input */

#endif     

};

 

四、驱动探针stmmac_dvr_probe

         当平台驱动在注册时,如果和平台设备名字匹配了,就会调用该初始化接口。

1、接口操作基本流程

该函数流程图如下:

(1)获取系统配置寄存器基地址

ret = stmmac_syscfg_init(pdev);

(2)获取MAC控制器寄存器基地址

stmmac_base_ioaddr =ioremap_nocache(res->start, res->end - res->start + 1);

(3)申请网卡设备和私有数据

ndev =alloc_etherdev(sizeof(struct stmmac_priv));

网卡设备和私有数据紧紧的挨在一起:网卡设备+私用数据结构,通过netdev_priv获取私有数据。网卡设备是通用数据结构,私有数据则为各个MAC控制器的数据结构

(4)MAC控制器设置

ret =stmmac_mac_device_setup(ndev);(见第2小节)

(5)网络设备真正注册

ret = stmmac_probe(ndev);(重要,见第五节)

(6)MDIO总线注册

ret = stmmac_mdio_register(ndev);(见第3小节)

(7)将网卡设备赋给全局变量

stmmac_device_list[priv->id] = ndev;,在其他接口中会使用这个全局变量。

注:3~7步骤,如果是多网卡则循环执行。

(8)复位DMA,GMA等寄存器

stmmac_reset();

(9)判断是否支持checksum offload engine

priv->rx_coe =priv->hw->mac->rx_coe(priv->ioaddr);在上面的第4步进行了函数初始化

(10)设置硬件DMA工作模式

stmmac_dma_operation_mode(priv);(见第4小节)

(11)初始化RX/TX队列

priv->dma_tx_size = STMMAC_ALIGN(dma_txsize);

priv->dma_rx_size =STMMAC_ALIGN(dma_rxsize);

priv->dma_buf_sz = STMMAC_ALIGN(buf_sz);

init_dma_desc_rings(ndev);(见第5小节)

(12)硬件DMA初始化

priv->hw->dma->init(priv->dma_ioaddr,

                         priv->dma_channel,

                         priv->plat->pbl,

                         priv->dma_tx_phy,

                         priv->dma_rx_phy,0)

最重要的意见事情:由init_dma_desc_rings 申请的priv->dma_tx_phy和priv->dma_rx_phy DMA handle写入接收和发送描述子链表地址寄存器

(13)接收发送index初始化

priv->dirty_rx = priv->cur_rx = 0;

priv->dirty_tx = priv->cur_tx = 0;

cur:已获取上传给网络协议层的数据帧index,协议层会释放skb

dirty:已经填充skb到dma队列的index

两个数值不断累加,通过取余%的方式循环处理dma队列。

如图所示:


(14)初始化接收回收队列

skb_queue_head_init(&priv->rx_recycle);

注意:9~11步骤,如果多网卡会循环执行。

(15)初始化TOE_NK

ret = tnk_init(stmmac_base_ioaddr,&pdev->dev);(见第6小节)

(16)请求中断

ret= request_irq(SYNOP_GMAC_IRQNUM, stmmac_interrupt, IRQF_SHARED,STMMAC_RESOURCE_NAME, pdev);(重要,见第七节)

(17)使能所有中断

writel(~0, stmmac_base_ioaddr +TNK_REG_INTR_EN);

 

2MAC控制器设置stmmac_mac_device_setup(ndev);

(1)选择GMAC设备并初始化

device = dwmac1000_setup(priv->ioaddr);

A.申请一个MAC设备:mac =kzalloc();

B.GMAC操作:mac->mac = &dwmac1000_ops;

C.GMAC dma操作:mac->dma = &dwmac1000_dma_ops;

(2)选择增强型描述子数据结构

device->desc = &enh_desc_ops;

注:以上很多都是对MAC寄存器的基本操作。

 

3MII bus注册stmmac_mdio_register

(1)申请MII总线

stmmac_mii_bus = mdiobus_alloc();

(2)初始化mdio工作时钟

tnkclk = mdio_clk_init();

(3)初始化MII总线结构体

stmmac_mii_bus->name = "STMMAC MIIBus";

stmmac_mii_bus->read =&stmmac_mdio_read;

stmmac_mii_bus->write =&stmmac_mdio_write;

stmmac_mii_bus->reset =&stmmac_mdio_reset;

……..

(4)注册MII总线mdiobus_register(stmmac_mii_bus);

A.注册总线设备device_register(&bus->dev);

B.复位总线bus->reset(bus);

C.扫描总线上的PHY设备,最大支持32个

phydev = mdiobus_scan(bus, i);

主要做三件事情:读取PHY ID,创建PHY device,然后注册PHY设备。

创建PHY设备接口get_phy_device中会调用phy_device_create会对PHY设备数据结构初始化,然后初始化工作队列phy_state_machine,该接口中会调用adjust_link接口。

(5)将MII总线赋给stmmac私有数据

priv->mii = stmmac_mii_bus;

 

4、设置硬件DMA工作模式stmmac_dma_operation_mode

priv->hw->dma->dma_mode(priv->dma_ioaddr,priv->dma_channel,

                    tc, SF_DMA_MODE);

发送:tc默认为64,不支持DMA存储转发。TxFIFO根据FIFO阀值来判断是否转发

接收:支持DMA存储转发。当RxFIFO接收到一个完整帧后才将该帧向上转发。

 

5、初始化dma描述子队列init_dma_desc_rings

(1)RX队列申请

priv->rx_skbuff_dma = kmalloc(rxsize *sizeof(dma_addr_t), GFP_KERNEL);

申请256个skb对应的dma地址队列

priv->rx_skbuff =kmalloc(sizeof(structsk_buff *) * rxsize, GFP_KERNEL);

申请256个skb队列

priv->dma_rx =(struct dma_desc *) dma_alloc_coherent(priv->device,

                          rxsize *sizeof(structdma_desc),

                         &priv->dma_rx_phy,

                          GFP_KERNEL);

DMA描述符(在datasheet为接收描述子)建立一致性DMA映射,大小为256* sizeof(structdma_desc),返回缓冲区虚拟内存地址。priv->dma_rx_phy返回正确的DMA handle,个人感觉是虚拟地址对应的物理地址,因为这个地址需要写入接收描述子链表地址寄存器。(该函数不是很明白

(2)TX队列申请

priv->tx_skbuff = kmalloc(sizeof(struct sk_buff*) * txsize, GFP_KERNEL);

申请256个skb指针

priv->tx_page = kmalloc(sizeof(structpage *) * txsize, GFP_KERNEL);

申请256个page指针

priv->dma_tx = (struct dma_desc*)dma_alloc_coherent(priv->device,

                          txsize *sizeof(structdma_desc),

                          &priv->dma_tx_phy,

                          GFP_KERNEL);

同上

(3)RX 队列初始化以及关联(1)中申请的数据

通过for循环申请256个skb,每个skb大小为2KB。

A.skb =netdev_alloc_skb_ip_align(dev, bfsize);

申请skb数据

B.priv->rx_skbuff[i]= skb;

将skb放入接收队列

C. priv->rx_skbuff_dma[i]= dma_map_single(priv->device, skb->data, bfsize, DMA_FROM_DEVICE);

然后将skb数据通过流式DMA映射加入skb dma地址队列

D.(priv->dma_rx +i)->des2= priv->rx_skbuff_dma[i];

再将skb dma地址放入dma描述符

 

(4)TX队列没有类似接收的RX初始化操作

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

       priv->tx_skbuff[i] = NULL;

       priv->tx_page[i] = NULL;

       priv->dma_tx[i].des2 = 0;

}

因为skb是在网络层被申请,传递到驱动中来。

 

(5)初始化RX/TX描述符

priv->hw->desc->init_rx_desc(priv->dma_rx,rxsize, dis_ic);

priv->hw->desc->init_tx_desc(priv->dma_tx,txsize);

(6) dma描述子队列和skb映射图


         上面是接收DMA描述符队列和skb初始化时建立映射过程。对于发送DMA发送队列只有在发送时才会建立映射,如果一个数据包存在多个数据片段,还会调用dma_map_page接口进行页映射处理。

(7)skb申请分配空间图

skb = netdev_alloc_skb_ip_align(dev,bfsize);

         skb->len:数据包中全部数据的长度,skb->data_len分隔存储数据片段长度。当只有一个数据片段时data_len = 0。

 

6TOE_NK初始化tnk_init()

(1)建立proc目录

ret = tnk_proc_init(hitoe ? max_connections: 0);

(2)如果使用TOE,则对TOE进行进行一系列初始化

由于没有使用,所以此处不讲述。

 

 

五、真正注册网络设备stmmac_probe

1、接口基本操作流程

该函数基本流程图:

 

(1)初始化网卡设备以太网基本信息ether_setup();

dev->header_ops     = &eth_header_ops;

   dev->hard_header_len    =ETH_HLEN;(14)

   dev->mtu        = ETH_DATA_LEN;(1500)

   dev->addr_len       = ETH_ALEN;(6)

   dev->tx_queue_len   = 1000; /*Ethernet wants good queues */

   dev->flags      =IFF_BROADCAST|IFF_MULTICAST;

   dev->priv_flags     =IFF_TX_SKB_SHARING;

   memset(dev->broadcast, 0xFF, ETH_ALEN);

 

(2)初始化网卡操作函数和ethtool操作函数

dev->netdev_ops =&stmmac_netdev_ops;(见第2小节)

stmmac_set_ethtool_ops(dev);(见第3小节)

dev->features |= NETIF_F_SG |NETIF_F_HIGHDMA | NETIF_F_IP_CSUM

                | NETIF_F_IPV6_CSUM;

设置网卡接口特性,这些特性驱动和协议层将会用到

dev->watchdog_timeo =msecs_to_jiffies(watchdog); 设置发送超时,超时后最终会调用stmmac_tx_timeout这个进行超时处理

 

(3)初始化NAPI轮询接收数据包接口

netif_napi_add(dev, &priv->napi, stmmac_poll, 64);(重要,见第八节)

初始化napi,poll=stmmac_poll,weight=64,poll_list等信息

Weight: 描述接口的相对重要性,当资源紧张时,在接口上能承受多大的流量。

 

(4)从寄存器中获取MAC地址,并判断有效性

priv->hw->mac->get_umac_addr((void__iomem *)dev->base_addr, dev->dev_addr, 0);

 

(5)初始化自旋锁

spin_lock_init(&priv->lock);

tnk_lock_init(&priv->tlock);

 

(6)注册网卡设备

ret = register_netdev(dev);

 

2、网卡基本操作函数

static const struct net_device_opsstmmac_netdev_ops = {

    .ndo_open = stmmac_open,(见第六节)

    .ndo_start_xmit = stmmac_xmit,(见第九节)

   .ndo_stop = stmmac_release,

   .ndo_change_mtu = stmmac_change_mtu,

   .ndo_set_multicast_list = stmmac_multicast_list,

   .ndo_tx_timeout = stmmac_tx_timeout,

   .ndo_do_ioctl = stmmac_ioctl,

   .ndo_set_config = stmmac_config,

#ifdef STMMAC_VLAN_TAG_USED

   .ndo_vlan_rx_register = stmmac_vlan_rx_register,

#endif

#ifdef CONFIG_NET_POLL_CONTROLLER

   .ndo_poll_controller = stmmac_poll_controller,

#endif

   .ndo_set_mac_address = eth_mac_addr,

};

3ethtool操作函数

static struct ethtool_opsstmmac_ethtool_ops = {

   .begin = stmmac_check_if_running,

   .get_drvinfo = stmmac_ethtool_getdrvinfo,

   .get_settings = stmmac_ethtool_getsettings,

   .set_settings = stmmac_ethtool_setsettings,

   .get_msglevel = stmmac_ethtool_getmsglevel,

   .set_msglevel = stmmac_ethtool_setmsglevel,

   .get_regs = stmmac_ethtool_gregs,

   .get_regs_len = stmmac_ethtool_get_regs_len,

   .get_link = stmmac_ethtool_get_link,

   .get_rx_csum = stmmac_ethtool_get_rx_csum,

   .get_tx_csum = ethtool_op_get_tx_csum,

   .set_tx_csum = ethtool_op_set_tx_ipv6_csum,

   .get_sg = ethtool_op_get_sg,

   .set_sg = ethtool_op_set_sg,

   .get_pauseparam = stmmac_get_pauseparam,

   .set_pauseparam = stmmac_set_pauseparam,

   .get_ethtool_stats = stmmac_get_ethtool_stats,

   .get_strings = stmmac_get_strings,

   .get_wol = stmmac_get_wol,

   .set_wol = stmmac_set_wol,

   .get_sset_count = stmmac_get_sset_count,

   .get_tso = ethtool_op_get_tso,

   .set_tso = ethtool_op_set_tso,

};

 

 

六、打开网卡设备stmmac_open

1、基本流程

基本流程图:


(1)MAC地址有效性

is_valid_ether_addr(dev->dev_addr)

(2)修正传递给驱动参数

stmmac_verify_args();

(3)初始化PHY设备

stmmac_init_phy(dev);(见第2小节)

(4)设备MAC地址到硬件

priv->hw->mac->set_umac_addr(priv->ioaddr,dev->dev_addr, 0);

(5)初始化MAC core

priv->hw->mac->core_init(priv->ioaddr);

(6)使能NAPI并schedule

napi_enable(&priv->napi);

napi_schedule(&priv->napi)

(7)启动DMA收发(写寄存器)

priv->hw->dma->start_tx(priv->dma_ioaddr,priv->dma_channel);

priv->hw->dma->start_rx(priv->dma_ioaddr,priv->dma_channel);

(8)启动相关定时器

priv->poll_timer.function= stmmac_poll_func;

定时唤醒处于挂起状态的RxDMA/TxDMA

priv->check_timer.function= stmmac_check_func;

读取寄存器地址获取当前接收描述符地址,如果在cur_rx和dirty_cur之前,则设置描述子归属为GMAC操作。

(9)使能MAC TX/RX

stmmac_enable_mac(priv->ioaddr);

(10)启动队列

netif_start_queue(dev);

 

2、初始化PHY设备stmmac_init_phy

(1)通知内核载波消失

netif_carrier_off(dev);

(2)以太网设备连接到PHY设备

phydev= phy_connect(dev, phy_id, &stmmac_adjust_link, 0, priv->phy_interface);

stmmac_adjust_link:根绝实际PHY情况,调整连接参数:全双工/半双功,速度,MII/GMII

A.d =bus_find_device_by_name(&mdio_bus_type, NULL, bus_id);

在MDIO bus总线PHY device列表中找到指定的设备

B.设备转化为PHY设备

phydev= to_phy_device(d);

C.rc =phy_connect_direct(dev, phydev, handler, flags, interface);

连接以太网device到指定的PHY 设备,然后启动PHY。

 

 

 

七、中断处理函数stmmac_interrupt

中断里3个并行流程:网卡1、网卡2、TOE,网卡和网卡2处理流程一样,且未使用TOE,只分析网卡1。

接收到第一个数据包或者数据发送完成时,产生中断,进入该中断处理函数。

1、读取中断状态寄存器

不管发送或者接收,产生DMA0中断时,则进入dma中断处理函数

2、stmmac_dma_interrupt

status = priv->hw->dma->dma_interrupt(priv->dma_ioaddr,priv->dma_channel,

                     &priv->xstats);

读取dma中断状态,如果是正确的接收和发送,则进行NAPI调度_stmmac_schedule(priv)。

里面调用napi_schedule(&priv->napi),同时禁止中断(中断由stmmac_poll里面开启)。

3、napi_schedule简单说明

(1)将priv->napi加入到softnet_data->napi,从定义看每个CPU有一个softnet_data

(2)启动网络接收软中断NET_RX_SOFTIRQ

(3)在CPU一定时间内处理该软中断

(4)软中断处理函数为net_rx_action,里面则会调用n->poll进行相应处理(该处的poll为下面probe是注册的stmmac_poll,见后面章节说明

(5)软中断处理函数在subsys_initcall(net_dev_init)时注册

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

   open_softirq(NET_RX_SOFTIRQ, net_rx_action);

(6)最后在do_softirq中统一处理所有的软中断。

常见软中断类型:

enum   

{      

   HI_SOFTIRQ=0,

   TIMER_SOFTIRQ,

   NET_TX_SOFTIRQ,

   NET_RX_SOFTIRQ,

   BLOCK_SOFTIRQ,

   BLOCK_IOPOLL_SOFTIRQ,

   TASKLET_SOFTIRQ,

   SCHED_SOFTIRQ,

   HRTIMER_SOFTIRQ,

   RCU_SOFTIRQ,    /* Preferable RCUshould always be the last softirq */

   NR_SOFTIRQS

};

4、中断里没对接收到的数据进行处理,而是调用NAPI接口在适当的时间进行处理。

八、NAPI接口之stmmac_poll

         该接口完成两个事情:一个是当数据发送完成时产生中断,进入该函数进行资源回收;另外一个是收到数据产生中断,进入该函数进行数据接收和处理。

1、基本操作流程

(1)接口开始时记录proc信息

         stmmac_poll_begin();

(2)回收已经发送完成的资源(放在发送章节讲解)

stmmac_tx(priv);(见第九节7小节)

(3)接收数据包

stmmac_rx(priv, budget);(见第2小节)

(4)如果接收到的数据包没有超过接口的容量,则开启中断

if (work_done< budget) {

       stm_proc.polls_done++;

       napi_complete(napi);

       stmmac_enable_irq(priv);

    }

(5)结束接口调用

stmmac_poll_end();

 

2、数据包接收处理stmmac_rx

unsigned int entry = priv->cur_rx % rxsize;

struct dma_desc *p = priv->dma_rx +entry;

(1)priv->hw->desc->get_rx_owner(p)

判断当前描述子的归属:描述子数据结构中OWN位,0:当前描述子应该由CPU操作,1:前描述子应该由GMAC操作。对于接收,初始化dma描述子队列时设置为1。

         GMAC根据寄存器配置,获取可用接收描述子,然后从RxFIFO中读取从PHY接收的Ethernet报文,如果报文符合接收条件,将该报文写入接收描述子指向的数据缓冲区,并回写接收描述子。这个回写就会将OWN位设置为0。

(2)获取下一帧描述符

next_entry = (++priv->cur_rx) % rxsize;

p_next =priv->dma_rx + next_entry;

priv->cur_rx:已经传递给协议层的index

(3)获取收到帧状态

status =(priv->hw->desc->rx_status(&priv->dev->stats, &priv->xstats,p));

如果是丢弃帧,则什么都不处理;否则上传到上层网络。

(4)获取帧长度

frame_len =priv->hw->desc->get_rx_frame_len(p);

(5)获取帧数据

skb = priv->rx_skbuff[entry];

priv->rx_skbuff[entry] = NULL;

注意:skb将有上层网络处理完后进行释放。

(6)设置skb数据长度和解除流式DMA映射

skb_put(skb, frame_len);

dma_unmap_single(priv->device, priv->rx_skbuff_dma[entry],priv->dma_buf_sz, DMA_FROM_DEVICE);

(7)获取skb的协议类型

skb->protocol = eth_type_trans(skb,priv->dev);

skb->dev = priv->dev;

(8)将skb通过NAPI接口上传上层网络协议处理

napi_gro_receive(&priv->napi,skb);(见第九节)

注:以上是一个while循环操作

(9)重新填充接收队列

stmmac_rx_refill(priv);(见第3小节)

 

3、接收队列填充函数stmmac_rx_refill

         由第2小节,接收到数据的skb从dma接收队列剥离,传递给网络层处理完成后,会将skb给释放。所以,需要重新申请新的skb传递填充dma接收队列。

(1)判断是否需要重新填充

将priv->cur_rx和priv->dirty_rx进行对比,如果priv->cur_rx > priv->dirty_rx即上传给网络协议层的数量大于已经填充dma的数量时,才会重新填充dma接收队列。

(2)检查描述子操作归属

priv->hw->desc->get_rx_owner(p+ entry),只有属于CPU的,才进行填充操作。

 

(3)申请新的SKB

skb= __skb_dequeue(&priv->rx_recycle);

先从回收队列中获取有效的skb。该回收队列来自由协议层申请且发送完成的数据帧skb。在stmmac_tx接口进行回收,它没有释放skb,而是放在了回收队列中,提高利用效率。

skb= netdev_alloc_skb_ip_align(priv->dev,

若回收队列没有,则重新申请skb。

 

(4)填充接收DMA描述符队列

priv->rx_skbuff[entry]= skb;

priv->rx_skbuff_dma[entry]= dma_map_single(priv->device, skb->data, bfsize, DMA_FROM_DEVICE);

(p+ entry)->des2 = priv->rx_skbuff_dma[entry];

 

(5)设置描述子归属,交给GMAC操作

priv->hw->desc->set_rx_owner(p+ entry);

注:以上通过for循环进行,直到没有需要填充为止。

 

(6)激活RxDMA

STMMAC_SYNC_BARRIER();

处理下一条指令前,先flush一下DMA write buffers

priv->hw->dma->enable_dma_receive(priv->dma_ioaddr,priv->dma_channel);

写任何值唤醒处于挂起的RxDMA。

 

九、传递skb数据给协议栈napi_gro_receivegeneric receive offload

1、概念了解

TSO、UFO和GSO是对应网络发送,LRO、GRO是对应网络接收。

TSO(TCPSegmentation Offload)是一种利用网卡对TCP数据包分片,减轻CPU负荷的一种技术,也叫LSO(Largesegment offload),TSO—TCP,UFO—UDP。如果硬件支持TSO功能,也需要硬件支持的TCP校验计算和分散/聚集功能。

    GSO(GenericSegmentation Offload)比TSO更通用。基本思想:尽可能的推迟数据分片直至发送到网卡驱动之前,此时会检查网卡是否支持分片功能(TSO、UFO),如果支持直接发送到网卡,如果不支持就进行分片后再发往网卡。这样大数据包只需走一次协议栈,而不是被分割成几个数据包分别走,提高效率。

    LRO(Large ReceiveOfflaod) 通过接收到的多个TCP数据聚合成一个大的数据包,然后传递给网络协议层处理,以减少上层协议栈处理开销,提高系统接收TCP数据包能力

    GRO(Generic ReceiveOffload)基本思想跟LRO类似,但是更加通用,现在驱动基本使用GRO接口,而不是LRO。

    RSS(Receive SideScaling)是一项网卡的特性,俗称多队列。具备多个RSS队列的网卡,可以将不同的网络流分成不同的队列,再分别将这些队列分配到多个CPU核心进行处理,从而将负荷分散,充分利用多核处理器的能力。

 

2、GRO结构体struct napi_gro_cb

GRO功能使用skb结构体内私有空间char cb[48]来存放gro所用到的一些信息。

具体内容如下:

struct napi_gro_cb {

/* Virtual address ofskb_shinfo(skb)->frags[0].page + offset. */

指向存在skb_shinfo(skb)->frag[0].page页的数据的头部。skb_gro_reset_offset

   void *frag0;

       

    /*Length of frag0.  第一页中数据的长度 */

   unsigned int frag0_len;

   

/* This indicates where we areprocessing relative to skb->data. 表明skb->data到GRO需要处理的数据区的偏移量 */

    比如进入ip层进行GRO处理,这时skb->data指向ip 头, 而ip层的gro 正好要处理ip头,这时偏移量就为0。进入传输层后进行GRO处理,这时skb->data还指向ip头, 而tcp层gro要处理tcp头,这时偏移量就是ip头部长度

    intdata_offset;

   

/* This is non-zero if thepacket may be of the same flow.  挂在napi->gro_list上的报文是否跟现在的报文进行匹配*/

每层的gro_receive都设置该标记位。 接收到一个报文后,使用该报文和挂在napi->gro_list上 的报文进行匹配。 在链路层,使用dev 和 mac头进行匹配,如果一样表示两个报文是通一个设备发过来的,就标记napi->gro_list上对应的skb的same为1. 到网络层,再进一步进行匹配时,只需跟napi->list上刚被链路层标记 same为1的报文进行网络层的匹配即可,不需再跟每个报文进行匹配。 如果网络层不匹配,就清除该标记。 到传输层,也是只配置被网络层标记same为1 的报文即可。这样设计为的是减少没必要的匹配操作

 

    intsame_flow;

 

/* This is non-zero if thepacket cannot be merged with the new skb. */

     如果该字段不为0,表示该数据报文没必要再等待合并,可以直接送进协议栈进行处理了

int flush;

 

    /*Number of segments aggregated.  该报文被合并过的次数*/

    intcount;

   

    /*Free the skb?  是否该被丢弃*/

    intfree;

};

 

3、函数基本流程

1skb_gro_reset_offset(见第4小节)

初始化NAPI_GRO_CB;

2__napi_gro_receive(见第6小节)

连路层gro_receive,实现数据包合并还是上传协议栈

3napi_skb_finish(见第5小节,内容少先讲)

根据第二个函数的返回值决定合并,feed协议栈,还是free。

 

4、初始化GRO cb: skb_gro_reset_offset(skb)

支持S/G IO的网卡存在如下一种可能:skb本身不包含数据(头也没有),所有的数据都保存在skb_share_info中,且frags.page不在higih_mem。此种情况如果要合并,将包头信息取出来即skb_shared_info的frags[0],将头信息保存到napi_gro_cb 的frags0中。

具体源码如下:

NAPI_GRO_CB(skb)->data_offset = 0;

NAPI_GRO_CB(skb)->frag0 = NULL;

NAPI_GRO_CB(skb)->frag0_len = 0;

           

如果mac_header和skb->tail相等并且地址不在高端内存,则说明包头保存在skb_shinfo中,所以我们需要从frags中取得对应的数据包

if(skb->mac_header == skb->tail && !PageHighMem(skb_shinfo(skb)->frags[0].page)){

可以看到frag0保存的就是对应的skb的frags的第一个元素的地址

NAPI_GRO_CB(skb)->frag0=  page_address(skb_shinfo(skb)->frags[0].page)+

           skb_shinfo(skb)->frags[0].page_offset;

然后保存对应的大小

NAPI_GRO_CB(skb)->frag0_len= skb_shinfo(skb)->frags[0].size;

    }

 

5、napi_skb_finish

         根据__napi_gro_receive的返回值处理合并后的数据包。

switch(ret) {

         //将数据包送进协议栈

    case GRO_NORMAL:

        if (netif_receive_skb(skb))(见第8小节)

            ret = GRO_DROP;

        break;

         //报文可以丢弃或者已经合并进gro,则free报文

    case GRO_DROP:

    case GRO_MERGED_FREE:

        kfree_skb(skb);

        break;

   //数据已经被gro保存起来,但是并没有合并的,skb还需要保留不能释放。

    case GRO_HELD:

    case GRO_MERGED:

        break; 

    }          

    return ret;

 

6、__napi_gro_receive接收合并函数

(1)基本概念

每个协议中定义自己的GRO接收合并函数和合并后处理函数,即gro_receive和gro_complete。GRO系统会根据协议来调用对应回调函数。gro_receive将         skb合并到gro_list中,返回值:空则表示合并后无需送入协议栈,非空则需要立即送入协议栈。gro_complete则是当返回值非空时将gro合并数据包送到协议栈时被调用。

__napi_gro_receive和napi_gro_complete可以被看做是链路层的gro_receive和gro_complete。

各个协议层gro_receive:

             .gro_receive = tcp4_gro_receive,

             .gro_receive = inet_gro_receive,

             .gro_receive = ipv6_gro_receive,

各个协议层gro_complete:

             .gro_complete = tcp4_gro_complete,

             .gro_complete = inet_gro_complete,

             .gro_complete = ipv6_gro_complete,

(2)函数基本内容

A.for (p = napi->gro_list;p; p = p->next) {

        unsigned long diffs;

 

        diffs = (unsigned long)p->dev ^(unsigned long)skb->dev;

        diffs |= p->vlan_tci ^skb->vlan_tci;

        diffs |=compare_ether_header(skb_mac_header(p),

                         skb_gro_mac_header(skb));

        NAPI_GRO_CB(p)->same_flow = !diffs;

        NAPI_GRO_CB(p)->flush = 0;

    }   

         遍历gro_list,查找列表中skb是否有和当前skb相同的流,然后给same_flow赋值。如果此层(链路层)相同,则ip层、tcp层再进行相同流判断,否则不会进行判断。不同层流是否相同的判断条件不一样,此处判断3个判断条件:同一设备、同一VLAN、同一MAC头。

  B  returndev_gro_receive(napi, skb);(见第7节)

         真正的接收合并处理

 

7dev_gro_receive真正的接收合并

         可以分为两部分看待,一个是正常的合并处理,另外一个就是frags0处理部分。需要注意GRO不支持切片的IP包,IP切片的组包在内核IP层会做一遍,GRO再做没意义增加复杂度。

(1)合并相同流处理

A.先遍历对应的ptype(协议的类链表),找到匹配协议后,调用对应的回调函数gro_receive;链路层则调用ip层的gro_receive即inet_gro_receive。

B.进入ip层合并处理,主要判断是否same_flow和是否需要flush。只有两个包是same_flow的情况下才会flush判断

same_flow判断:协议需要相同,tos域需要相同,源和目的地址需要相同;只要一个不同就是设置为0。

flush判断:是切片包,ttl不一样,id顺序不对;只要一个满足skb就会flush出gro到协议栈。然后进入TCP层的gro_receive即tcp4_gro_receive。

C.进入TCP层合并处理:类似IP层,对same_flow和flush判断。其中对flush的判断比较多和复杂,如果需要flush就不需要进行合并处理,返回相应的gro_list。

         真正的合并函数:skb_gro_receive(此处暂不分析)。

(2)当gro_receive返回值非空时,对flush出gro的skb进行立即feed进协议栈处理napi_gro_complete。如果当前skb的same_flow非0表明找到同流且已合并直接返回,如果未找到同流,则添加skb到gro_list中。

 

(3)frags0处理部分

         将skb_shinfo结构体存放的frags,往前移动到skb。(为啥这么做?

8netif_receive_skb

最终将skb数据分发给各个协议层。有空再讲解。

 

十、发送数据stmmac_xmit

该接口实现了Scatter/Gather I/O功能,通过skb_shinfo宏来判断数据包是一个数据片段组成,还是由大量数据片段组成。

1、获取可用发送描述子

entry= priv->cur_tx % txsize;

desc= priv->dma_tx + entry;

first= desc; 保存第一个数据片段

 

2、将skb放到发送队列

priv->tx_skbuff[entry]= skb;

priv->tx_page[entry]= NULL;

 

3、发送单个或第一个数据包

unsignedint nopaged_len = skb_headlen(skb);

desc->des2= dma_map_single(priv->device, skb->data,nopaged_len, DMA_TO_DEVICE);

priv->hw->desc->prepare_tx_desc(desc,1, nopaged_len, csum_insertion);

当只有一个数据片段时,skb->data将发送所有数据;当有多个数据片段时,skb->data则指向第一个数据片段,数据长度skb->len –skb->data_len,其他数据存放在共享数据结构frags数组中。(skb->len:数据包中全部数据的长度,skb->data_len分隔存储数据片段长度)

 

4、发送剩余数据片段

对于多个数据片段时,还要进行数据片段的发送,采用页处理。直接处理页结构,而不是内核虚拟地址。

for(i = 0; i < nfrags; i++)

{

        skb_frag_t *frag =&skb_shinfo(skb)->frags[i];

        int len = frag->size;

   

        entry = (entry + 1) % txsize;

        desc = priv->dma_tx + entry;

 

        TX_DBG("\t[entry %d] segment len:%d\n", entry, len);

        desc->des2 = dma_map_page(priv->device, frag->page,

                      frag->page_offset,

                      len, DMA_TO_DEVICE);

        priv->tx_skbuff[entry] = NULL;

        priv->tx_page[entry] =frag->page;

        get_page(frag->page);(需要查一下原理)

       priv->hw->desc->prepare_tx_desc(desc, 0, len, csum_insertion);

       priv->hw->desc->set_tx_owner(desc);

}

5、将第一个数据片段描述子交给GMAC,记录当前发送index

    priv->hw->desc->set_tx_owner(first);

    priv->cur_tx += count;

 

6、激活RxDMA

STMMAC_SYNC_BARRIER();

处理下一条指令前,先flush一下DMA write buffers

priv->hw->dma->enable_dma_transmission(priv->dma_ioaddr,

                          priv->dma_channel);

写任何值唤醒处于挂起的RxDMA

 

7、发送资源回收接口stmmac_tx

当数据发送完成时产生中断,调用stmmac_poll函数进入该接口进行发送资源回收操作。

(1)从寄存器获取当前发送描述符DMA index

(2)循环判断dirty_tx 和 cur_tx

while(priv->dirty_tx != priv->cur_tx)

(3)判断DMA index 和 描述符归属

if (entry == hw_dma_index) // entry =dirty_tx % txsize

            break;

if(priv->hw->desc->get_tx_owner(p))

            break;

(4)skb加入回收接收队列

if((skb_queue_len(&priv->rx_recycle) <priv->dma_rx_size)

&&skb_recycle_check(skb, priv->dma_buf_sz))

__skb_queue_head(&priv->rx_recycle,skb);

else

dev_kfree_skb(skb);

接收回收队列没有超出接收队列总长度(256),且skb可以进行回收的情况下,将skb加入回收队里。其他就释放skb。

(5)释放多数据数据片段的page页

put_page(priv->tx_page[entry]);

priv->tx_page[entry]= NULL;

 

(6)增加dirty_tx

entry= (++priv->dirty_tx) % txsize;

 

 

十一、其他补充

 

参考文档

 

 

原创粉丝点击