linux网卡驱动分析之probe函数

来源:互联网 发布:win7引导ubuntu双系统 编辑:程序博客网 时间:2024/06/08 02:15
 

linux网卡驱动分析之probe函数

分类: linux内核学习 576人阅读 评论(0) 收藏 举报
probe函数中一般完成一下任务:
1、通知内核设备执行DMA的寻址能力,说明设备支持64位还是32位的DMA地址。如果不支持64位的地址,则尝试32位的:
[cpp] view plaincopy
  1. err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(64));  
  2.     if (!err) {  
  3.         err =  
  4.             dma_set_coherent_mask(pci_dev_to_dev(pdev),  
  5.                       DMA_BIT_MASK(64));  
  6.         if (!err)  
  7.             pci_using_dac = 1;  
  8.     } else {  
  9.         err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(32));  
  10.         if (err) {  
  11.             err = dma_set_coherent_mask(pci_dev_to_dev(pdev),  
  12.                             DMA_BIT_MASK(32));  
  13.             if (err) {  
  14.                 dev_err(pci_dev_to_dev(pdev),  
  15.                     "No usable DMA configuration, aborting\n");  
  16.                 goto err_dma;  
  17.             }  
  18.         }  
  19.     }  
 
2、给设备分配IO内存并映射内存,一般的设备都是通过IO内存映射设备寄存器和设备内存。
        有些设备使用IO端口映射设备寄存器,x86处理器上一共有64KB的IO空间,其中有些IO端口已作为固定的用途,比如80口,作为数码管的显示,比如CF8和CFC用作读取PCI设备配置空间寄存器。如果设备需要IO端口,使用如下函数分配IO端口:
[cpp] view plaincopy
  1. request_region(start,n,name)  
        IO端口分配后可以直接操作,使用如下函数:
[cpp] view plaincopy
  1. inb(),outb()  
  2. inw(),outw()  
  3. inl(),outl()  
        如果设备需要IO内存,根据设备需要的IO内存大小,分配IO内存:
[cpp] view plaincopy
  1. pci_request_selected_regions(pdev, bars, name);  
        在操作IO内存之前,需要映射IO内存,之后可以使用readl或者writel等读写内存的函数操作该块内存:
[cpp] view plaincopy
  1. ioremap(mmio_start, mmio_len);  
        每个PCI设备最多可以支持6个BAR(Base Address Register),但大部分设备不会使用这么多空间。
        PCI设备复位之后,该寄存器存放PCI设备需要使用的IO空间基地址,这段空间是IO空间还是内存空间,网卡设备一般使用的是内存空间,也可以称为IO内存。该信息是设备出厂时已经设置好的。
        BIOS扫描PCI设备时,会根据系统中的硬件配置为PCI设备分配地址空间,BIOS为所有PCI设备分配的地址空间都不会冲突,之后该信息会传递给操作系统。
       系统软件对PCI总线进行配置时,首先获取BAR空间寄存器中的初始化信息,之后根据处理器的配置,将合理的基地址写入相应的BAR寄存器。系统软件还可以使用该寄存器或者PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0xFFFFFFFF,之后读取该寄存器。
        在设备驱动加载之前,设备所需要的地址空间还不会真正的分配,需要在驱动程序中给设备分配IO空间,最后进行ioremap才能访问。
        在系统下,可以通过如下命令查看设备使用的IO内存:
[cpp] view plaincopy
  1. $ lspci | grep "Ethernet controller"  
  2. 02:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection  
  3. 06:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection  
  4.   
  5. $ cat /proc/iomem | grep "02:00.0"                                       
  6.   fea00000-fea1ffff : 0000:02:00.0  
  7.   fea20000-fea23fff : 0000:02:00.0  
       从上面结果可以看出,该设备使用了6个BAR中的2个BAR,即BAR0和BAR1,该设备申请了两块IO内存,BAR0的范围为:fea00000-fea1ffff,大小为128KB,用来映射设备寄存器,BAR1的范围为fea20000-fea23fff,大小为32KB,用来映射flash。设备需要的空间大小是由硬件指定的,但是这两块IO内存的起始地址是在BIOS启动阶段PCI扫描时由BIOS分配的。在e1000e网卡驱动中有如下代码:
      BAR0用来映射设备寄存器,即设备有关寄存器都映射到内存空间,我们可以通过操作内存来操作设备寄存器,pci_resource_start(pdev, 0)就是用来获取BAR0的起始地址:
[cpp] view plaincopy
  1. mmio_start = pci_resource_start(pdev, 0);  
  2. mmio_len = pci_resource_len(pdev, 0);  
  3.   
  4. err = -EIO;  
  5. adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);  
       BAR1用来映射flash,pci_resource_start(pdev, 1)用来获取BAR1的起始地址:
[cpp] view plaincopy
  1. if ((adapter->flags & FLAG_HAS_FLASH) &&  
  2.      (pci_resource_flags(pdev, 1) & IORESOURCE_MEM)) {  
  3.      flash_start = pci_resource_start(pdev, 1);  
  4.      flash_len = pci_resource_len(pdev, 1);  
  5.      adapter->hw.flash_address = ioremap(flash_start, flash_len);  
  6.      if (!adapter->hw.flash_address)  
  7.          goto err_flashmap;  
  8.  }  

3、分配网络设备的核心数据结构net_device。
[cpp] view plaincopy
  1. netdev = alloc_etherdev(sizeof(struct e1000_adapter));  
[cpp] view plaincopy
  1. struct net_device *alloc_etherdev(int sizeof_priv)  
  2. {  
  3.     return alloc_netdev(sizeof_priv, "eth%d", ether_setup);  
  4. }  
        该函数分配net_device结构,同时分配网卡的私有数据e1000_adapter,使用函数netdev_priv(netdev)获取网卡私有数据;网卡设备名为ethx,该函数分配有关数据结构后,会调用ether_setup初始化net_device一些成员,这是一个共用的函数,以太网卡驱动都会使用这个函数来初始化以太网网卡设备:
[cpp] view plaincopy
  1. void ether_setup(struct net_device *dev)  
  2. {  
  3.     dev->change_mtu      = eth_change_mtu;  
  4.     dev->hard_header = eth_header;  
  5.     dev->rebuild_header  = eth_rebuild_header;  
  6.     dev->set_mac_address     = eth_mac_addr;  
  7.     dev->hard_header_cache   = eth_header_cache;  
  8.     dev->header_cache_update= eth_header_cache_update;  
  9.     dev->hard_header_parse   = eth_header_parse;  
  10.   
  11.     dev->type        = ARPHRD_ETHER;  
  12.     dev->hard_header_len     = ETH_HLEN;  
  13.     dev->mtu     = ETH_DATA_LEN;  
  14.     dev->addr_len        = ETH_ALEN;  
  15.     dev->tx_queue_len    = 1000; /* Ethernet wants good queues */      
  16.     dev->flags       = IFF_BROADCAST|IFF_MULTICAST;  
  17.     memset(dev->broadcast,0xFF, ETH_ALEN);  
  18. }  
       下面列出了不同网卡类型使用 alloc_netdev 函数的不同封装:

Network device type

Wrapper name

Wrapper definition

Ethernet

alloc_etherdev

return alloc_netdev(sizeof_priv, "eth%d", ether_setup);//以太网

Fiber Distributed Data Interface

alloc_fddidev

return alloc_netdev(sizeof_priv, "fddi%d", fddi_setup);

High Performace Parallel Interface

alloc_hippi_dev

return alloc_netdev(sizeof_priv, "hip%d", hippi_setup);

Token Ring

alloc_trdev

return alloc_netdev(sizeof_priv, "tr%d", tr_setup);//令牌环网络

Fibre Channel

alloc_fcdev

return alloc_netdev(sizeof_priv, "fc%d", fc_setup);//光纤

Infrared Data Association

alloc_irdadev

return alloc_netdev(sizeof_priv, "irda%d", irda_device_setup);


4、初始化net_device和私有数据e1000_adapter有关成员
      在net_device结构中有几个比较重要的成员:
[cpp] view plaincopy
  1.        netdev->open = &e1000_open;  
  2. netdev->stop = &e1000_close;  
  3. netdev->hard_start_xmit = &e1000_xmit_frame;  
      在ifup某个网卡的时候需要调用open函数,ifdown某个网卡的时候需要调用close函数,发送数据则调用hard_start_xmit。

5、置一些标志位
      在net_device有几个成员容易让人模糊。
      以下两个成员表示设备的状态:
      state:一组由网络队列子系统使用的标志,为枚举类型的常量,对应的bit通过set_bit和clear_bit来设置或者清除。
[cpp] view plaincopy
  1. enum netdev_state_t  
  2. {  
  3.     __LINK_STATE_XOFF=0,  
  4.     __LINK_STATE_START,  
  5.     __LINK_STATE_PRESENT,  
  6.     __LINK_STATE_SCHED,  
  7.     __LINK_STATE_NOCARRIER,  
  8.     __LINK_STATE_RX_SCHED,  
  9.     __LINK_STATE_LINKWATCH_PENDING,  
  10.     __LINK_STATE_DORMANT,  
  11.     __LINK_STATE_QDISC_RUNNING,  
  12.     __LINK_STATE_NETPOLL  
  13. };  
      比如,调用netif_stop_queue来停止队列:
[cpp] view plaincopy
  1. static inline void netif_stop_queue(struct net_device *dev)  
  2. {  
  3.     ...  
  4.     set_bit(_ _LINK_STATE_XOFF, &dev->state);  
  5. }  

       reg_state:表示设备的注册状态。
[cpp] view plaincopy
  1. enum {   
  2.                NETREG_UNINITIALIZED=0,  
  3.            NETREG_REGISTERED,   /* completed register_netdevice */  
  4.            NETREG_UNREGISTERING,    /* called unregister_netdevice */  
  5.            NETREG_UNREGISTERED, /* completed unregister todo */  
  6.            NETREG_RELEASED,     /* called free_netdev */  
  7.     } reg_state;  

      以下这几个字段跟网络设备的配置有关:
      flag:该成员中的一些位表示网卡的能力(比如IFF_MULTICAST),其他一些位则表示网卡状态的变化(比如IFF_UP,IFF_RUNNING),下面列出几个,全部的标志可以在/linux/if.h中找到:
[cpp] view plaincopy
  1. #define IFF_UP      0x1     /* interface is up      */  
  2. #define IFF_BROADCAST   0x2     /* broadcast address valid  */  
  3. #define IFF_DEBUG   0x4     /* turn on debugging        */  
  4. #define IFF_LOOPBACK    0x8     /* is a loopback net        */  
  5. #define IFF_POINTOPOINT 0x10        /* interface is has p-p link    */  
  6. #define IFF_NOTRAILERS  0x20        /* avoid use of trailers    */  
  7. #define IFF_RUNNING 0x40        /* interface RFC2863 OPER_UP    */  
  8. #define IFF_NOARP   0x80        /* no ARP protocol      */  
  9. #define IFF_PROMISC 0x100       /* receive all packets      */  
  10. #define IFF_ALLMULTI    0x200       /* receive all multicast packets*/  
[cpp] view plaincopy
  1. $ ifconfig eth0  
  2. eth0      Link encap:Ethernet  HWaddr 00:0C:29:C5:9C:3F    
  3.           inet addr:172.16.252.202  Bcast:172.16.255.255  Mask:255.255.0.0  
  4.           inet6 addr: fe80::20c:29ff:fec5:9c3f/64 Scope:Link  
  5.           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1  
  6.           RX packets:40631942 errors:0 dropped:0 overruns:0 frame:0  
  7.           TX packets:288276 errors:0 dropped:0 overruns:0 carrier:0  
  8.           collisions:0 txqueuelen:1000   
  9.           RX bytes:2702596852 (2.5 GiB)  TX bytes:248532391 (237.0 MiB)  
       在上面的例子中,网卡eth0有以下标志:IFF_UP IFF_BROADCAST IFF_RUNNING IFF_MULTICAST。
       priv_flags:该成员保存的标志用户空间不可见,可以被VLAN和虚拟桥设备(bridge virtual device)使用。虚拟设备跟真实的设备(比如eth0)不一样,虚拟设备是在真实设备的基础上,做了一些逻辑的处理,比如bonding设备bond1,就是把多个设备(比如eth0,eth1)绑定在一起,bond1在内核中也有一个net_device结构。 
      gflag:该标志从未使用。
      features:该成员保存设备的其他能力,使用该成员保存一些标志不是多余的,该成员保存的标志用来报告该网卡的能力给CPU,比如该网卡是否支持DMA或者是否支持硬件数据包校验,所有的能力已在net_device结构中已经定义了,下面列出一部分:
[cpp] view plaincopy
  1. unsigned long       features;  
  2. #define NETIF_F_SG      1   /* Scatter/gather IO. */  
  3. #define NETIF_F_IP_CSUM     2   /* Can checksum only TCP/UDP over IPv4. */  
  4. #define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */  
  5. #define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */  
  6. #define NETIF_F_HIGHDMA     32  /* Can DMA to high memory. */  
  7. #define NETIF_F_FRAGLIST    64  /* Scatter/gather IO. */  
  8. #define NETIF_F_HW_VLAN_TX  128 /* Transmit VLAN hw acceleration */  
  9. #define NETIF_F_HW_VLAN_RX  256 /* Receive VLAN hw acceleration */  
  10. #define NETIF_F_HW_VLAN_FILTER  512 /* Receive filtering on VLAN */  
  11. #define NETIF_F_VLAN_CHALLENGED 1024    /* Device cannot handle VLAN packets */  

5、检查nvram及eeprom,拷贝硬件地址。

6、初始化有关定时器和工作队列。

7、初始化接收和发送描述符环的个数。
[cpp] view plaincopy
  1. adapter->rx_ring->count = 256;  
  2.     adapter->tx_ring->count = 256;  
关于描述符环下一篇会讲到。

8、使能中断,如果是MSIX中断则需要使能,一般的网卡驱动中断的申请是在用户ifup网卡之后,调用了驱动的open函数,在open函数中会申请中断。

9、注册网络设备。
[cpp] view plaincopy
  1. err = register_netdev(netdev);  
       内核中有一个全局指针变量:
[cpp] view plaincopy
  1. struct net_device *dev_base;  
       通过该指针,内核可以很方便的遍历所有的网络设备,不管是1Gb速率的网卡还是10Gb的网卡,如果需要获取某个网卡的数据或者修改某个网卡的配置,可以很方便的查找到该设备。
       由于每个网卡都有自己的私有数据结构,而私有数据结构大小可能不一样,因此链表里每个节点的大小也可能不一样。   


      内核中还有两个有关的全局变量:
[cpp] view plaincopy
  1. static struct hlist_head dev_name_head[1<<NETDEV_HASHBITS];  
  2. static struct hlist_head dev_index_head[1<<NETDEV_HASHBITS];  
      上面两个变量是长度为256的链表数组,可以保存256个链表。dev_name_head是根据设备的名称(比如“eth0”)生存的哈希值组成的链表,dev_index_head是根据分配给设备唯一的ID值组成的链表,该ID值保存在net_device中的ifindex成员中。
      通过某种算法,将设备名生存一个哈希值,实际上是一个unsigned int类型的数据:
[cpp] view plaincopy
  1. static inline struct hlist_head *dev_name_hash(const char *name)  
  2. {  
  3.     unsigned hash = full_name_hash(name, strnlen(name, IFNAMSIZ));  
  4.     return &dev_name_head[hash & ((1<<NETDEV_HASHBITS)-1)];  
  5. }  
      设备的ID值,即ifindex,是一个int类型的数据:
[cpp] view plaincopy
  1. static int dev_new_index(void)  
  2. {  
  3. static int ifindex;  
  4. for (;;) {  
  5. if (++ifindex <= 0)  
  6. ifindex = 1;  
  7. if (!__dev_get_by_index(ifindex))  
  8. return ifindex;  
  9. }  
  10. }  
       在net_device有两个链表节点:
[cpp] view plaincopy
  1. struct hlist_node   name_hlist;//设备名链表节点  
  2. struct hlist_node   index_hlist;//ID值链表节点  
       在register_netdevice函数中,会根据设备名生存的哈希值和设备ID值,找到256个链表中对应的链表,把上面两个链表节点加入到对应的链表中。整个链表是一个拉链型的链表。
      dev_index_head链表:


       dev_name_head链表与上图是类似的,我们可以通过设备的ID值或者设备名来获取设备的net_device结构。
       内核中提供了两个函数
       下面函数通过设备ID值获取该设备的net_device结构:
[cpp] view plaincopy
  1. dev_get_by_index():  
  2. struct net_device *__dev_get_by_index(int ifindex)  
  3. {  
  4.     struct hlist_node *p;  
  5.   
  6.     hlist_for_each(p, dev_index_hash(ifindex)) {  
  7.         struct net_device *dev  
  8.             = hlist_entry(p, struct net_device, index_hlist);  
  9.         if (dev->ifindex == ifindex)  
  10.             return dev;  
  11.     }  
  12.     return NULL;  
  13. }  
        dev_index_hash函数的目的就是找到255个链表中的某一个链表,然后比较net_device结构中的ifidex与当前的ifindex值,如果相等,就找到了该结构。
        下面的函数通过设备名获取该网卡设备的net_device结构:
[cpp] view plaincopy
  1. dev_get_by_name():  
  2. struct net_device *__dev_get_by_name(const char *name)  
  3. {  
  4.     struct hlist_node *p;  
  5.   
  6.     hlist_for_each(p, dev_name_hash(name)) {  
  7.         struct net_device *dev  
  8.             = hlist_entry(p, struct net_device, name_hlist);  
  9.         if (!strncmp(dev->name, name, IFNAMSIZ))  
  10.             return dev;  
  11.     }  
  12.     return NULL;  
  13. }  
       dev_name_hash同上面类似,先找到对应的节点,再比较设备名是否相同。

       为什么要这样做呢?目的是提高查找的效率,通过hash算法,一开始就可以缩小查找的范围。
       网络配置工具ip(来自IPROUTE包),使用netlink机制来与内核进行通信,netlink机制中很多代码中使用了上面的方法,通过设备ID值或者设备名获取net_device结构。比如命令:
[cpp] view plaincopy
  1. ifup eth0  
      该命令最终会调用/sbin/ip命令,/sbin/ip是一个应用程序,ip命令中使用netlink机制与内核通信,最终调用网卡驱动的相关接口修改网卡的配置。老的配置机制中,使用的是ioctl方法,比如ethtool命令。

      register_netdevice大致流程如下:
1、初始net_device的一些成员,包括一些锁;
2、调用alloc_divert_blk,如果驱动支持divert 特性,为其分配空间;
3、如果设备驱动有初始化过dev->init,调用该函数;
4、调用dev_new_index函数为设备分配唯一的ID值。内核中使用了一个32位的静态变量,每当有新的设备加入到系统中时,该变量加1,如果变量溢出,又从0开始计数,但是系统中不会有这么多的设备。
5、根据设备名(例如:eth0)生存的哈希值,找到对应的链表头,此时该链表头代表的链表里不可能有当前的设备,否则就出错了。
6、在/sys/class/net下创建有关sys文件;
7、设置dev->state中的__LINK_STATE_PRESENT标志,使该设备在系统中可见。当一个热插拔的设备被拔出时,该标志会被清除。
8、调用dev_init_scheduler初始化设备的队列规则(queuing discipline),由流量控制(Traffic Control)实现Qos(Quality of service)。队列规则定义了出包(egress packet)时如何入列和出列的。
9、将net_device结构中的index_hlist和name_hlist节点加入到255个链表中对应的链表中去;
10、调用raw_notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);通知其他子系统该设备已向内核注册。
       内核中的其他子系统如果对网络设备子系统感兴趣,就会调用register_netdevice_notifier来注册有关处理函数,所有注册的通知块(notifier_block)即有关处理函数都放在netdev_chain链表中.。
       比如rtnetlink与网络设备子系统有关,该模块需要知道网络设备子系统中发生的一些变化,其可以调用:
[cpp] view plaincopy
  1. register_netdevice_notifier(&rtnetlink_dev_notifier);  
函数将通知块,即有关处理函数注册到网络设备子系统,当网络设备子系统发生变化时,比如设备注册到内核或者设备取消注册,就会调用函数:
[cpp] view plaincopy
  1. raw_notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);  
通知rtnetlink模块。
0 0
原创粉丝点击