linux网卡驱动分析之probe函数

probe函数中一般完成一下任务:
1、通知内核设备执行DMA的寻址能力,说明设备支持64位还是32位的DMA地址。如果不支持64位的地址,则尝试32位的:
err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(64));
	if (!err) {
		err =
		    dma_set_coherent_mask(pci_dev_to_dev(pdev),
					  DMA_BIT_MASK(64));
		if (!err)
			pci_using_dac = 1;
	} else {
		err = dma_set_mask(pci_dev_to_dev(pdev), DMA_BIT_MASK(32));
		if (err) {
			err = dma_set_coherent_mask(pci_dev_to_dev(pdev),
						    DMA_BIT_MASK(32));
			if (err) {
				dev_err(pci_dev_to_dev(pdev),
					"No usable DMA configuration, aborting\n");
				goto err_dma;
			}
		}
	}
 
2、给设备分配IO内存并映射内存,一般的设备都是通过IO内存映射设备寄存器和设备内存。
        有些设备使用IO端口映射设备寄存器,x86处理器上一共有64KB的IO空间,其中有些IO端口已作为固定的用途,比如80口,作为数码管的显示,比如CF8和CFC用作读取PCI设备配置空间寄存器如果设备需要IO端口,使用如下函数分配IO端口:
request_region(start,n,name)
        IO端口分配后可以直接操作,使用如下函数:
inb(),outb()
inw(),outw()
inl(),outl()
        如果设备需要IO内存,根据设备需要的IO内存大小,分配IO内存:
pci_request_selected_regions(pdev, bars, name);
        在操作IO内存之前,需要映射IO内存,之后可以使用readl或者writel等读写内存的函数操作该块内存
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内存:
$ lspci | grep "Ethernet controller"
02:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
06:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection

$ cat /proc/iomem | grep "02:00.0"                                     
  fea00000-fea1ffff : 0000:02:00.0
  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的起始地址:
	mmio_start = pci_resource_start(pdev, 0);
	mmio_len = pci_resource_len(pdev, 0);

	err = -EIO;
	adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);
       BAR1用来映射flash,pci_resource_start(pdev, 1)用来获取BAR1的起始地址:
   if ((adapter->flags & FLAG_HAS_FLASH) &&
        (pci_resource_flags(pdev, 1) & IORESOURCE_MEM)) {
        flash_start = pci_resource_start(pdev, 1);
        flash_len = pci_resource_len(pdev, 1);
        adapter->hw.flash_address = ioremap(flash_start, flash_len);
        if (!adapter->hw.flash_address)
            goto err_flashmap;
    }

3、分配网络设备的核心数据结构net_device。
netdev = alloc_etherdev(sizeof(struct e1000_adapter));
struct net_device *alloc_etherdev(int sizeof_priv)
{
	return alloc_netdev(sizeof_priv, "eth%d", ether_setup);
}
        该函数分配net_device结构,同时分配网卡的私有数据e1000_adapter,使用函数netdev_priv(netdev)获取网卡私有数据;网卡设备名为ethx,该函数分配有关数据结构后,会调用ether_setup初始化net_device一些成员,这是一个共用的函数,以太网卡驱动都会使用这个函数来初始化以太网网卡设备:
void ether_setup(struct net_device *dev)
{
	dev->change_mtu		= eth_change_mtu;
	dev->hard_header	= eth_header;
	dev->rebuild_header 	= eth_rebuild_header;
	dev->set_mac_address 	= eth_mac_addr;
	dev->hard_header_cache	= eth_header_cache;
	dev->header_cache_update= eth_header_cache_update;
	dev->hard_header_parse	= eth_header_parse;

	dev->type		= ARPHRD_ETHER;
	dev->hard_header_len 	= ETH_HLEN;
	dev->mtu		= ETH_DATA_LEN;
	dev->addr_len		= ETH_ALEN;
	dev->tx_queue_len	= 1000;	/* Ethernet wants good queues */	
	dev->flags		= IFF_BROADCAST|IFF_MULTICAST;
	memset(dev->broadcast,0xFF, ETH_ALEN);
}
       下面列出了不同网卡类型使用 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结构中有几个比较重要的成员:
        netdev->open = &e1000_open;
	netdev->stop = &e1000_close;
	netdev->hard_start_xmit = &e1000_xmit_frame;
      在ifup某个网卡的时候需要调用open函数,ifdown某个网卡的时候需要调用close函数,发送数据则调用hard_start_xmit。

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

       reg_state:表示设备的注册状态。
enum { 
               NETREG_UNINITIALIZED=0,
	       NETREG_REGISTERED,	/* completed register_netdevice */
	       NETREG_UNREGISTERING,	/* called unregister_netdevice */
	       NETREG_UNREGISTERED,	/* completed unregister todo */
	       NETREG_RELEASED,		/* called free_netdev */
	} reg_state;

      以下这几个字段跟网络设备的配置有关:

      flag:该成员中的一些位表示网卡的能力(比如IFF_MULTICAST),其他一些位则表示网卡状态的变化(比如IFF_UP,IFF_RUNNING),下面列出几个,全部的标志可以/linux/if.h中找到:
#define	IFF_UP		0x1		/* interface is up		*/
#define	IFF_BROADCAST	0x2		/* broadcast address valid	*/
#define	IFF_DEBUG	0x4		/* turn on debugging		*/
#define	IFF_LOOPBACK	0x8		/* is a loopback net		*/
#define	IFF_POINTOPOINT	0x10		/* interface is has p-p link	*/
#define	IFF_NOTRAILERS	0x20		/* avoid use of trailers	*/
#define	IFF_RUNNING	0x40		/* interface RFC2863 OPER_UP	*/
#define	IFF_NOARP	0x80		/* no ARP protocol		*/
#define	IFF_PROMISC	0x100		/* receive all packets		*/
#define	IFF_ALLMULTI	0x200		/* receive all multicast packets*/
$ ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:0C:29:C5:9C:3F  
          inet addr:172.16.252.202  Bcast:172.16.255.255  Mask:255.255.0.0
          inet6 addr: fe80::20c:29ff:fec5:9c3f/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:40631942 errors:0 dropped:0 overruns:0 frame:0
          TX packets:288276 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          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结构中已经定义了,下面列出一部分:
unsigned long		features;
#define NETIF_F_SG		1	/* Scatter/gather IO. */
#define NETIF_F_IP_CSUM		2	/* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM		4	/* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM		8	/* Can checksum all the packets. */
#define NETIF_F_HIGHDMA		32	/* Can DMA to high memory. */
#define NETIF_F_FRAGLIST	64	/* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX	128	/* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX	256	/* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER	512	/* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED	1024	/* Device cannot handle VLAN packets */

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


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


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

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

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


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



       dev_name_head链表与上图是类似的,我们可以通过设备的ID值或者设备名来获取设备的net_device结构。
       内核中提供了两个函数
       下面函数通过设备ID值获取该设备的net_device结构:
dev_get_by_index():
struct net_device *__dev_get_by_index(int ifindex)
{
	struct hlist_node *p;

	hlist_for_each(p, dev_index_hash(ifindex)) {
		struct net_device *dev
			= hlist_entry(p, struct net_device, index_hlist);
		if (dev->ifindex == ifindex)
			return dev;
	}
	return NULL;
}
        dev_index_hash函数的目的就是找到255个链表中的某一个链表,然后比较net_device结构中的ifidex与当前的ifindex值,如果相等,就找到了该结构。
        下面的函数通过设备名获取该网卡设备的net_device结构:
dev_get_by_name():
struct net_device *__dev_get_by_name(const char *name)
{
	struct hlist_node *p;

	hlist_for_each(p, dev_name_hash(name)) {
		struct net_device *dev
			= hlist_entry(p, struct net_device, name_hlist);
		if (!strncmp(dev->name, name, IFNAMSIZ))
			return dev;
	}
	return NULL;
}
       dev_name_hash同上面类似,先找到对应的节点,再比较设备名是否相同。

       为什么要这样做呢?目的是提高查找的效率,通过hash算法,一开始就可以缩小查找的范围。
       网络配置工具ip(来自IPROUTE包),使用netlink机制来与内核进行通信,netlink机制中很多代码中使用了上面的方法,通过设备ID值或者设备名获取net_device结构。比如命令:
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网络设备子系统有关,该模块需要知道网络设备子系统中发生的一些变化,其可以调用
:

	register_netdevice_notifier(&rtnetlink_dev_notifier);
函数将通知块,即有关处理函数注册到网络设备子系统,当网络设备子系统发生变化时,比如设备注册到内核或者设备取消注册,就会调用函数:
raw_notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
通知rtnetlink模块。

你可能感兴趣的:(linux内核学习)