• 网络数据包收发流程(三):e1000网卡和DMA


    一、硬件布局

    每个网卡(MAC)都有自己的专用DMA Engine,如上图的 TSEC 和 e1000 网卡intel82546。
    上图中的红色线就是以太网数据流,DMA与DDR打交道需要其他模块的协助,如TSEC,PCI controller
    以太网数据在 TSEC<-->DDR  PCI_Controller<-->DDR 之间的流动,CPU的core是不需要介入的
    只有在数据流动结束时(接收完、发送完),DMA Engine才会以外部中断的方式告诉CPU的core

    二、DMA Engine

    上面是DMA Engine的框图,以接收为例:
    1.在System memory中为DMA开辟一端连续空间,用来BD数组  (一致性dma内存)
      BD是给DMA Engine使用的,所以不同的设备,BD结构不同,但是大致都有状态、长度、指针3个成员。

    2.初始化BD数组,status为E,length为0
       在System memory中再开辟一块一块的内存,可以不连续,用来存放以太网包
       将这些内存块的总线地址赋给buf(dma映射)

    3.当MAC接收以太网数据流,放在了Rx FIFO中

    4.当一个以太网包接收完全后,DMA engine依次做以下事情
        fetch bd:开始一个个的遍历BD数组,直到当前BD状态为Empty为止
        update bd:更新BD状态为Ready
        move data:把数据从Rx FIFO中搬移到System Memory中dma映射的部分
        generate interrupt:数据搬移完了,产生外部中断给cpu core

    5.cpu core处理外部中断,此时以太网数据已经在System memory中dma映射的部分了
        解除dma映射,更新bd状态为Empty
        再开辟一端内存,将这块内存的总线地址赋给bd的指针字段

    三、内核中DMA相关API

    void *dma_alloc_cohrent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
    功能:分配一致性dma内存,返回这块内存的虚拟地址EA, 这块内存的物理地址保存在 dma_handle
    dev:  NULL也行
    size: 分配空间的大小
    dma_handle: 用来保存内存的总线地址(物理地址)

    注意:一致性DMA映射BD所占内存就是靠dma_alloc_cohrent来分配的。

    dma_addr_t *dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction);
    功能:将一块连续的内存 buffer 映射为DMA内存来使用。映射后,CPU不能再操作这块 buffer
    返回:这块buffer的总线地址(物理地址)
    dev: NULL也行
    buffer: 一块连续内存的虚拟地址EA
    size: 连续内存的大小
    dma_data_direction: dma数据流的方向

    注意:流式DMA映射以太网包所占内存先通过kmalloc来分配,然后通过dma_map_single来映射给bd的

    四、e1000驱动中的DMA

    网卡驱动中使用DMA的套路差不多都一样,以e1000驱动为例讲一下(TSEC驱动的dma见这里

    4.1 加载e1000网卡驱动

    e1000_probe(){                        //主要是初始化钩子函数
         netdev = alloc_etherdev(sizeof(struct e1000_adapter));
        netdev->open = &e1000_open;       //重要
        netdev->stop = &e1000_close;
        netdev->hard_start_xmit = &e1000_xmit_frame;
        netdev->get_stats = &e1000_get_stats;
        netdev->set_multicast_list = &e1000_set_multi;
        netdev->set_mac_address = &e1000_set_mac;
        netdev->change_mtu = &e1000_change_mtu;
        netdev->do_ioctl = &e1000_ioctl;
        e1000_set_ethtool_ops(netdev);
        netdev->tx_timeout = &e1000_tx_timeout;
        netdev->watchdog_timeo = 5 * HZ;
    #ifdef CONFIG_E1000_NAPI
        netif_napi_add(netdev, &adapter->napi, e1000_clean, 64); //重要
    #endif
    }

    4.1 启动e1000网卡

       e1000_open() //当用户敲ifconfig up命令时,最终调用网卡驱动的open函数
         -->e1000_setup_all_rx_resources(adapter)
             -->e1000_setup_rx_resources(adapter, &adapter->rx_ring[i])
                   //给rx bd分配一致性dma内存
                   rxdr->desc = pci_alloc_consistent(pdev, rxdr->size, &rxdr->dma);
         -->e1000_configure(adapter)
             -->e1000_configure_rx(adapter)
                   adapter->clean_rx = e1000_clean_rx_irq;
                   adapter->alloc_rx_buf = e1000_alloc_rx_buffers;
             -->调用 adapter->alloc_rx_buf钩子函数,即 e1000_alloc_rx_buffers
                    --> skb = netdev_alloc_skb(netdev, bufsz); //调用kmalloc新建一个skb
                        buffer_info->dma = pci_map_single(pdev,
                            skb->data,
                            adapter->rx_buffer_len,
                            PCI_DMA_FROMDEVICE);               //给skb->data建立DMA映射
                        rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);//初始化bd的buf指针
         -->e1000_request_irq(adapter);
            //挂rx 中断ISR函数为 e1000_intr()

    最终bd数据结构应该是下面这个样子
          
    4.2 e1000的中断

    注意:e1000产生rx中断时,以太网数据包已经在系统内存中,即在skb->data里面
    下面的中断处理过程就简略了,详细的看这里
    do_IRQ()
    {
        中断上半部
           调用e1000网卡的rx中断函数 e1000_intr()
              触发软中断 (使用NAPI的话)
        中断下半部
           依次调用软中断的所有handler
           在net_rx_action中最终调用e1000的napi_struct.poll()钩子函数,即e1000_clean
           e1000_clean()最终调用 e1000_clean_rx_irq()
    }

    e1000_clean_rx_irq()
    {
         rx_desc = E1000_RX_DESC(*rx_ring, i); //获取rx bd
         status = rx_desc->status;
         skb = buffer_info->skb;
         buffer_info->skb = NULL;

         pci_unmap_single(pdev,                //解除skb->data的DMA映射
                       buffer_info->dma,
                       buffer_info->length,
                       PCI_DMA_FROMDEVICE);
         length = le16_to_cpu(rx_desc->length);
         length -= 4;                          //以太网包的FCS校验就不要了
         skb_put(skb, length);
         skb->protocol = eth_type_trans(skb, netdev);
         netif_receive_skb(skb);               //skb进入协议栈
    }

    转载自http://blog.chinaunix.net/uid-24148050-id-1667017.html

  • 相关阅读:
    Tom&Jerry_team——测试总结
    OMCP sprint 第7天
    OMCP sprint 第6天
    OMCP sprint 第5天
    OMCP sprint 第4天
    OMCP sprint 第3天
    OMCP sprint 第2天
    SQL SERVER 如何恢复bak备份文件
    Ext JS
    Linnx环境下常用命令
  • 原文地址:https://www.cnblogs.com/CasonChan/p/5166239.html
Copyright © 2020-2023  润新知