e1000网络驱动分析-阿里云开发者社区

开发者社区> 云计算> 正文
登录阅读全文

e1000网络驱动分析

简介: e1000网络驱动分析 e1000是intel千兆以太网卡的驱动源码。官方关于驱动的使用可以参考如下链接。 https://www.intel.cn/content/www/cn/zh/support/articles/000006866/network-and-i-o/ethernet-products.html 本文主要结合e1000驱动的源码进行分析. 版本是#define DRV_VERSION "7.3.21-k8-NAPI",这个是Ubuntu4.4.0-63版本上的内核。

e1000是intel千兆以太网卡的驱动源码。官方关于驱动的使用可以参考如下链接。

https://www.intel.cn/content/www/cn/zh/support/articles/000006866/network-and-i-o/ethernet-products.html

本文主要结合e1000驱动的源码进行分析. 版本是#define DRV_VERSION "7.3.21-k8-NAPI",这个是Ubuntu4.4.0-63版本上的内核。代码文件如下几个:

e1000_ethtool.c

e1000.h

e1000_hw.c

e1000_hw.h

e1000_main.c

e1000_osdep.h

e1000_param.c

Makefile

1.2.1 加载和卸载

驱动加载时候先运行e1000_init_module函数,主要实现注册到PCI子系统中,代码如下。

static int __init e1000_init_module(void)

{

        int ret;

        pr_info("%s - version %s\n", e1000_driver_string, e1000_driver_version);

 

        pr_info("%s\n", e1000_copyright);

 

        ret = pci_register_driver(&e1000_driver);

        if (copybreak != COPYBREAK_DEFAULT) {

                if (copybreak == 0)

                        pr_info("copybreak disabled\n");

                else

                        pr_info("copybreak enabled for "

                                   "packets <= %u bytes\n", copybreak);

        }

        return ret;

}

module_init(e1000_init_module);

            通过调用pci_register_driver函数注册e1000_driver(pci_driver为驱动主结构)驱动,将其注册到PCI设备的链表中。

     具体如下:

pci_register_driver会调用

__pci_register_driver(drivers/pci/pci-driver.c)函数,该函数调用driver_register函数. driver_register会检测是否已在总线上已经注册或是否需要更新,没问题就问题就调用bus_add_driver函数,bus_add_driver将驱动加到总线上,然后调用pci_driver中定义的probe函数。

还有driver_add_groups函数创建组,kobject_uevent函数发送一个uevent通知用户层。

     部分原型如下:

int __pci_register_driver(struct pci_driver *drv, struct module *owner,

                          const char *mod_name)

            我们知道在bus_add_driver函数中,会调用pci_driver结构体指向的probe函数。e1000的probe函数是e1000_probe,该函数执行完也就是驱动和设备绑定了, e1000_probe函数完成设备初始化工作,包括pci_dev结构体,操作系统初始化,e1000_adapter结构体配置,和硬件重启工作。设备device也初始化完了。

            卸载是e1000_exit_module函数,直接调用pci_unregister_driver,和pci_register_driver函数是对立的。

static void __exit e1000_exit_module(void)

{

        pci_unregister_driver(&e1000_driver);

}

module_exit(e1000_exit_module);

驱动支持的参数是copybreak,默认是256.

module_param(copybreak, uint, 0644);

MODULE_PARM_DESC(copybreak,

                  "Maximum size of packet that is copied to a new buffer on receive");

1.2.2 网络驱动关键数据结构

设计到的主要结构体有pci_dev,e1000_adapter和net_device。

pci_dev结构是网卡的配置空间和I/O与内存区,net_device结构则向内核提供了操作网卡的抽象接口。

e1000_adapter结构除了体现相应的硬件无关性外,还管理了发送与接收数据包的相应缓冲空间,网卡的物理地址空间映射后的虚拟地址也在此结构中保存。

e1000_adapter结构中的e1000_hw结构主要保存网卡的硬件参数,其值就是通过读取pci_dev的内容获取而来的。

pci_device_id: PCI Device ID Table

e1000_adapter:设备私有数据结构,定义在e1000.h中。

e1000_hw:硬件相关的结构体,定义在e1000_hw.h中。

pci_driver表示pci驱动。

static struct pci_driver e1000_driver = {

        .name     = e1000_driver_name,//驱动的名字,必须唯一

        .id_table = e1000_pci_tbl,//指向pci_device_id表的指针

        .probe    = e1000_probe,//驱动的probe函数指针,被PCI核心调用。

        .remove   = e1000_remove,//当有pci_dev从系统移除时候调用函数的指针,或驱动被卸载时

#ifdef CONFIG_PM                  //如果定义电源管理

        /* Power Management Hooks */

        .suspend  = e1000_suspend,//pci_dev被挂起时调用的指针。

        .resume   = e1000_resume,//pci_dev恢复时调用的函数指针

#endif

        .shutdown = e1000_shutdown,//系统关闭时候调用

        .err_handler = &e1000_err_handler//错误处理句柄

};

static const struct pci_error_handlers e1000_err_handler = {

        .error_detected = e1000_io_error_detected,

        .slot_reset = e1000_io_slot_reset,

        .resume = e1000_io_resume,

};

pci_error_handlers结构体是错误发现,插槽重启的函数句柄。

            其中net_device_ops的定义如下, 包含了网络设备接口操作函数:

static const struct net_device_ops e1000_netdev_ops = {

        .ndo_open               = e1000_open,

        .ndo_stop               = e1000_close,

        .ndo_start_xmit         = e1000_xmit_frame,

        .ndo_get_stats          = e1000_get_stats,

        .ndo_set_rx_mode        = e1000_set_rx_mode,

        .ndo_set_mac_address    = e1000_set_mac,

        .ndo_tx_timeout         = e1000_tx_timeout,

        .ndo_change_mtu         = e1000_change_mtu,

        .ndo_do_ioctl           = e1000_ioctl,

        .ndo_validate_addr      = eth_validate_addr,

        .ndo_vlan_rx_add_vid    = e1000_vlan_rx_add_vid,

        .ndo_vlan_rx_kill_vid   = e1000_vlan_rx_kill_vid,

#ifdef CONFIG_NET_POLL_CONTROLLER

        .ndo_poll_controller    = e1000_netpoll,

#endif

        .ndo_fix_features       = e1000_fix_features,

        .ndo_set_features       = e1000_set_features,

};

            用来填充net_device结构体。

结构体e1000_buffer是buffer_info缓冲块数组的单元,用与接收从硬件传送过来的数据(是skb的包裹函数)

struct e1000_buffer {

        struct sk_buff *skb;

        dma_addr_t dma;

        struct page *page;

        unsigned long time_stamp;

        u16 length;

        u16 next_to_watch;

        unsigned int segs;

        unsigned int bytecount;

        u16 mapped_as_page;

};

            结构体e1000_tx_ring指向ring内存的描述符。

struct e1000_tx_ring {

        /* pointer to the descriptor ring memory */

        void *desc;

        /* physical address of the descriptor ring */

        dma_addr_t dma;

        /* length of descriptor ring in bytes */

        unsigned int size;

        /* number of descriptors in the ring */

        unsigned int count;

        /* next descriptor to associate a buffer with */

        unsigned int next_to_use;

        /* next descriptor to check for DD status bit */

        unsigned int next_to_clean;

        /* array of buffer information structs */

        struct e1000_buffer *buffer_info;

        u16 tdh;

        u16 tdt;

        bool last_tx_tso;

};

借用《Linux-千兆网卡驱动实现机制浅析》一张图如下,比较直观的说明了e1000_adaper,e1000_rx_ring,e1000_rx_desc,e1000_buffer结构体之间的关系:

a1c9f8dd1626838cb699317d2291314260db0876

            其中缓冲块的初始化由函数e1000_tx_map实现。

 

1.2.3 设备打开和关闭

根据e1000_netdev_ops结构体中的定义,打开设备使用的函数为e1000_open。当网络接口激活的时候被调用。

            关闭函数为e1000_close也定义在e1000_netdev_ops结构体中,用于禁用网络接口。

 

1.2.4 数据接收和发送

数据传输是net_device接口的关键组件。驱动程序提供一个hard_start_xmit函数,协议层在发送时调用它来向下传递数据包,现在也是定义在net_dev_ops结构体中如下:

static const struct net_device_ops e1000_netdev_ops = {

        .ndo_open               = e1000_open,

        .ndo_stop               = e1000_close,

        .ndo_start_xmit         = e1000_xmit_frame,

        .ndo_get_stats          = e1000_get_stats,

        .ndo_set_rx_mode        = e1000_set_rx_mode,

        .ndo_set_mac_address    = e1000_set_mac,

        .ndo_tx_timeout         = e1000_tx_timeout,

        .ndo_change_mtu         = e1000_change_mtu,

        .ndo_do_ioctl           = e1000_ioctl,

        .ndo_validate_addr      = eth_validate_addr,

        .ndo_vlan_rx_add_vid    = e1000_vlan_rx_add_vid,

        .ndo_vlan_rx_kill_vid   = e1000_vlan_rx_kill_vid,

#ifdef CONFIG_NET_POLL_CONTROLLER

        .ndo_poll_controller    = e1000_netpoll,

#endif

        .ndo_fix_features       = e1000_fix_features,

        .ndo_set_features       = e1000_set_features,

};

可以看到发送函数为e1000_xmit_frame

 现在的中断驱动程序都采用的是NAPI方式,需要提供poll函数,本驱动中是e1000_netpoll轮询函数。

            当有新数据包要发送时候,首先上层协议调用e1000_xmit_frame函数,然后在该函数中调用e1000_tx_queue来根据相应的参数找到缓冲块存放,缓冲块中有dma成员,表示该数据包所在的总线地址,控制总线会把内容映射到总线地址,然后由网卡传送出去。

            当有新数据包达到时,首先触动中断处理函数e1000_intr,该中断函数会将数据包放在buffer_info的缓冲块中(),就是讲总线地址指向的内容复制到skb中,然后根据skb中的协议将其传给上层协议的接收函数。

 

1.2.5 个别函数分析说明

e1000_get_hw_dev函数通过结构体e1000_hw来得到net_device.

e1000_request_irq函数,通过request_irq函数申请一个终端号,同时关联终端处理函数e1000_intr。

static int e1000_request_irq(struct e1000_adapter *adapter)

e1000_intr函数为终端处理函数,硬中断。

static irqreturn_t e1000_intr(int irq, void *data)

e1000_free_irq函数,调用free_irq释放中断。

e1000_irq_disable函数禁用中断。

e1000_setup_all_tx_resources函数分配发送描述符。

e1000_setup_all_rx_resources函数分配接收描述符

  netif_carrier_on/netif_carrier_off/netif_carrier_ok网卡驱动程序通过来和内核网络子系统传递信息。netif_carrier_on,告诉内核子系统网络链接完整。netif_carrier_off,告诉内核子系统网络断开。netif_carrier_ok,查询网络断开还是链接。以上函数主要是改变net_device dev的state状态来告知内核链路状态的变化。

e1000_power_up_phy函数给物理设备上电。

 

1.2.6 单独编译e1000驱动模块

路径在drivers下,找到对应驱动如net,例如:

drivers/net/ethernet/intel/e1000

可以单独复制出来,

然后在Makefile中增加如下代码:

KERN_DIR = /usr/src/linux-headers-4.4.0-63-generic

all:

    make -C $(KERN_DIR) M=`pwd` modules

clean:

     make -C $(KERN_DIR) M=`pwd` modules clean

-C 参数后面加config文件所在的文件夹,也可以是

KERN_DIR = /lib/modules/`uname -r`/build

M:参数后面是要编译的模块

 

1.2.7 参考

Linux-千兆网卡驱动实现机制浅析 关于PCIE的粗的理解可以查看此文

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: