首页 > 代码库 > 网络设备

网络设备

PCI设备

很多网络设备都是基于PCI接口的。因此尽管网络设备驱动比较特殊,但也要作为PCI驱动注册到内核中。

PCI接口等定义、网络设备驱动相关定义涉及以下文件:

include/linux/mod_devicetable.h 定义导出到用户控件的PCI设备信息

include/linux/pci.h 定义PCI接口驱动相关的结构、宏等

include/linux/netdevice.h 定义网络设备结构、宏等

include/linux/inetdevice.h 定义IPV4专用的网络设备相关的结构、宏等

net/core/dev.c 网络设备注册、输入和输出等接口

net/ethernet/eth.c 以太网网络设备驱动程序专用接口

net/core/link_watch.c 网络设备连接状态通知

drivers/net/e100.c e100驱动程序

PCI驱动程序相关结构

1、pci_device_id结构

#define PCI_ANY_ID (~0)

struct pci_device_id {
	__u32 vendor, device;		/* Vendor and device ID or PCI_ANY_ID*/
	__u32 subvendor, subdevice;	/* Subsystem ID's or PCI_ANY_ID */
	__u32 class, class_mask;	/* (class,subclass,prog-if) triplet */
	kernel_ulong_t driver_data;	/* Data private to the driver */
};
标准PCI设备中都有一个配置寄存器,用来存放各种参数,其中的vendorID(厂商ID)、deviceID(设备ID)、class(类代号)、subsystem vendorID(子系统厂商ID)和subsystem deviceID(子系统设备ID)是我们关注的。

vendor为厂商ID,用于标识硬件制造商。PCI Special Interest Group维护一个全球厂商的编号注册表,制造商必须申请一个唯一的编号并写入到设备的vendorID寄存器中。例如,每个Intel设备都会被标识为相同的厂商编号0x8086

device为设备ID,由制造商自己设置。

设备ID与厂商ID配对生成一个唯一的32位硬件设备标识符,驱动程序通常依靠此标识符来识别设备。

2、pci_driver结构

struct pci_driver {
	struct list_head node;
	char *name;
	const struct pci_device_id *id_table;	/* must be non-NULL for probe to be called */
	int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);	/* New device inserted */
	void (*remove) (struct pci_dev *dev);	/* Device removed (NULL if not a hot-plug capable driver) */
	int  (*suspend) (struct pci_dev *dev, u32 state);	/* Device suspended */
	int  (*resume) (struct pci_dev *dev);	                /* Device woken up */
	int  (*enable_wake) (struct pci_dev *dev, u32 state, int enable);   /* Enable wake event */

	struct device_driver	driver;
	struct pci_dynids dynids;
};
pci_driver结构用来描述一个PCI设备,因此所有的PCI驱动都必须创建一个pci_driver结构的实例,用来向PCI设备管理模块描述PCI驱动程序。

name 驱动程序名,在内核中所有PCI驱动程序名都是唯一的,通常被设置为和驱动程序模块相同的名字

id_table 指向pci_device_id结构数组的指针

probe 指向PCI驱动中的probe函数指针。当有驱动被添加到内核时,会调用此接口进行设备的初始化。

以e100为例来说明驱动程序的注册过程。

static struct pci_device_id e100_id_table[] = {
	{0x8086, 0x1229, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x2449, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1059, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1209, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
  	{0x8086, 0x1029, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1030, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },	
	{0x8086, 0x1031, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
	{0x8086, 0x1032, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1033, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
	{0x8086, 0x1034, PCI_ANY_ID, PCI_ANY_ID, 0, 0, }, 
	{0x8086, 0x1038, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1039, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x103A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x103B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x103C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x103D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x103E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x1055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x2459, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0x8086, 0x245D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
	{0,} /* This has to be the last entry*/
};
MODULE_DEVICE_TABLE(pci, e100_id_table);

static struct pci_driver e100_driver = {
	.name         = "e100",
	.id_table     = e100_id_table,
	.probe        = e100_found1,
	.remove       = __devexit_p(e100_remove1),
#ifdef CONFIG_PM
	.suspend      = e100_suspend,
	.resume       = e100_resume,
#endif
};

static int __init
e100_init_module(void)
{
	int ret;
        ret = pci_module_init(&e100_driver);

	if(ret >= 0) {
#ifdef CONFIG_PM
		register_reboot_notifier(&e100_notifier_reboot);
#endif 
	}

	return ret;
}

static void __exit
e100_cleanup_module(void)
{
#ifdef CONFIG_PM	
	unregister_reboot_notifier(&e100_notifier_reboot);
#endif 

	pci_unregister_driver(&e100_driver);
}

module_init(e100_init_module);
module_exit(e100_cleanup_module);
e100_id_table是e100驱动程序的pci_device_id结构类型列表。由module_init可知,e100_init_module()为e100驱动的初始化接口,在模块装载到内核中时被调用。

与网络设备相关的数据结构

1、net_device结构

struct net_device
{

	/*
	 * This is the first field of the "visible" part of this structure
	 * (i.e. as seen by users in the "Space.c" file).  It is the name
	 * the interface.
	 */
	char			name[IFNAMSIZ];
	/*
	 *	I/O specific fields
	 *	FIXME: Merge these and struct ifmap into one
	 */
	unsigned long		mem_end;	/* shared mem end	*/
	unsigned long		mem_start;	/* shared mem start	*/
	unsigned long		base_addr;	/* device I/O address	*/
	unsigned int		irq;		/* device IRQ number	*/

	/*
	 *	Some hardware also needs these fields, but they are not
	 *	part of the usual set specified in Space.c.
	 */
	unsigned char		if_port;	/* Selectable AUI, TP,..*/
	unsigned char		dma;		/* DMA channel		*/
	unsigned long		state;
	struct net_device	*next;
	
	/* The device initialization function. Called only once. */
	int			(*init)(struct net_device *dev);

	/* ------- Fields preinitialized in Space.c finish here ------- */

	struct net_device	*next_sched;

	/* Interface index. Unique device identifier	*/
	int			ifindex;
	int			iflink;
	struct net_device_stats* (*get_stats)(struct net_device *dev);
        ...
}

net_device结构是网络驱动及接口层中最重要的结构,其中不但描述了接口方面的信息,还包括硬件信息,致使该结构很大很复杂。将接口和驱动完全整合在一起也许是设计上的失误。

net_device结构的成员大致可以分为以下几类:

硬件信息成员变量:与网络设备相关的底层硬件信息,如果是虚拟网络设备驱动,则这部分信息无效。

接口信息成员变量:本节介绍有关接口方面的信息,这些信息主要是为其他硬件类型的setup()而设置的。对以太网来说是ether_setup(),以太网设备利用该函数设置大部分成员。

设备操作接口变量:设备的接口主要提供操作数据或控制设备的一些功能,如发送数据包的接口、激活和关闭设备的接口等。在这些接口中,有些是必须的,而有些是可选的,这与设备提供的特性有关。

辅助成员变量

每个设备都是自定义的私有数据结构,net_device结构全局链表可能链接不同长度的结点。

分配说明如下:

1、当调用alloc_netdev()分配net_device结构时,与具体驱动程序有关的驱动程序私有数据块长度被传递给alloc_netdev(),alloc_netdev()追加私有数据块到net_device接口实例的尾部。

2、dev_base和net_device的next指针指向net_device接口的开始,而不是指向已分配块的开始。初始填充长度保存在dev->padded字段,该字段允许内核在适当的时候释放整个内存块。

网络设备的注册

设备注册的时机

1、加载网络设备驱动程序

2、插入可热插拔网络设备

分配net_device结构空间

1、alloc_netdev()

  网络设备由net_device结构定义,每个net_device结构实例代表一个网络设备,该结构的实例由alloc_netdev()分配空间

/**
 *	alloc_netdev - allocate network device
 *	@sizeof_priv:	size of private data to allocate space for
 *	@name:		device name format string
 *	@setup:		callback to initialize device
 *
 *	Allocates a struct net_device with private data area for driver use
 *	and performs basic initialization.
 */
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
		void (*setup)(struct net_device *))
{
	void *p;
	struct net_device *dev;
	int alloc_size;

	/* ensure 32-byte alignment of both the device and private area */
	alloc_size = (sizeof(*dev) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;
	alloc_size += sizeof_priv + NETDEV_ALIGN_CONST;

	p = kmalloc(alloc_size, GFP_KERNEL);
	if (!p) {
		printk(KERN_ERR "alloc_dev: Unable to allocate device.\n");
		return NULL;
	}
	memset(p, 0, alloc_size);

	dev = (struct net_device *)
		(((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
	dev->padded = (char *)dev - (char *)p;

	if (sizeof_priv)
		dev->priv = netdev_priv(dev);

	setup(dev);
	strcpy(dev->name, name);
	return dev;
}

2、ether_setup()

绝大多数普通的网络设备类型,都会用一个特定的xxx_setup()初始化net_device实例的配置函数字段,这对所有的设备都是一样的。在alloc_etherdev()中,将ether_setup()作为第三个输入参数传给alloc_netdev(),ether_setup()就是以太网设备的xxx_setup()

/*
 * Fill in the fields of the device structure with ethernet-generic values.
 */
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		= 1500; /* eth_mtu */
	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);

}

网络设备注册过程

以e100为例


注册过程并不是简单地把net_device结构实例插入到全局链表或相关散列表中,还包括初始化net_device结构实例的部分成员,产生一个广播形式通知通知其他内核组件其已经注册的消息,以及其他任务。

最终调用register_netdevice注册网络设备,并将网络设备描述符注册到系统中。完成注册后,会发送NETDEV_REGISTER消息到netdev_chain通知连中,使得所有对注册感兴趣的模块都能接收消息。

注册设备的状态迁移

网络设备通过regiseter_netdev()和unregister_netdev()注册与注销。在注册、注销以及释放过程中,伴随着网络设备注册状态的迁移。


1、网络设备的初始化状态为UNINITIALIZED,在调用register_netdev()完成注册后,状态便迁移到REGISTERED

2、处于REGISTERED状态的网络设备,通过unregister_netdev()注销后,迁移到UNREGISTERING状态。同时net_device的两个虚拟init()和uninit()在注册和注销后分别初始化和清除私有数据。

3、在衔接操作中,设备真正被注销要到所有对该实例的相关引用都释放时才执行,netdev_wait_allrefs()直到条件满足才会返回,此时状态迁移到UNINITIALIZED。

4、注销后的网络设备在调用free_netdev()后,释放之前,状态迁移到RELEASED。

设备注册状态通知

内核其他模块和用户空间应用程序可能都想知道网络设备注册、注销、打开、关闭的时间,因此提供两个产生时间通知的途径,即netdev_chain通知链和netlink的RTMGRP_LINK组播组。内核模块只要注册到netdev_chain通知链上,网络设备的相关事件都会通知该模块,而用户控件应用程序只要注册到netlink的RTMGRP_LINK组播组,网络设备事件也会通知到该应用程序。

1、netdev_chain通知链

   内核模块可以通过register_netdevice_notifier()将处理网络设备事件的函数注册到netdev_chain通知链中,之后可以通过unregister_netdevice_notifier()注销。并且可以对一个或多个事件感兴趣。需要注意的是,注册到通知链时,register_netdevice_notifier()会将以前的NETDEV_REGISTER和NETDEV_UP通知重发给系统中当前注册的模块

2、netlink链接通知

   当设备状态或配置改变时,通知被发送到连接组播组RTMGRP_LINK。事实上,那些发送到组播组RTMGRP_LINK的通知也是由netdev_chain通知链驱动的,为了能及时通知netlink链,在netdev_chain通知链页注册了一个实例,通过该实例发送通知到netlink链上

网络设备的注销

设备注销时机

1、卸载网络设备驱动程序

2、移除热插拔网络设备

网络设备注销过程

1、unregister_netdevice()

   为例注销网络设备,内核和相关的网络设备驱动程序需要撤销所有注册时执行的操作,以及下列操作:

   a)通过dev_close()禁止网络设备

   b)释放所有分配的资源,如IRQ、I/O内存、I/O端口等

   c)从全局队列dev_base、dev_name_head和dev_index_head散列表中移除net_device实例

   d)一旦实例的引用为0,就释放net_device实例、驱动程序私有数据结构及其他连接到它的内存块。netdevice实例由free_netdev()释放,如果内核编译支持sysfs,free_netdev()会让sysfs来负责释放。

   e)移除添加到proc和sys文件系统的任何文件

2、衔接操作:netdev_run_todo()

   net_device结构实例的改变受rtnl_mutex原子变量的保护,在修改net_device实例前后需要调用rtnl_lock()和rtnl_unlock()。

   一旦unregister_netdevice()完成了它的工作,会通过net_set_todo()将完成注销的net_device结构加入到net_todo_list中,这个链表包含了注销已经结束的设备。由于互斥变量net_todo_run_mutex控制了其串行化,因此在同一时刻仅能有一个CPU运行netdev_run_todo()。

   netdev_run_todo函数用来处理队列net_todo_list上的网络设备,继续处理相关的注销事物。主要是注销sysfs中该设备的节点。注销时,等待设备的引用计数为0,在调用设备自身的destructor,完成注销过程

网络设备的启用

设备一旦注册后即可使用,但必须在用户或用户空间应用程序使能后才能收发数据。因为注册到系统中的网络设备初始状态是关闭的,此时是不能传输数据的,必须激活后,网络设备才能进行数据的传输。在应用层,可以通过ifconfig up命令(最终是通过ioctl的SIOCSIFFLAGS)来激活网络设备。而SIOCSIFFLAGS命令是通过dev_change_flags()调用dev_open()来激活网络设备。

dev_open将网络设备从关闭状态转到激活状态,并发送一个NETDEV_UP消息到网络设备状态改变通知链上。

/**
 *	dev_open	- prepare an interface for use.
 *	@dev:	device to open
 *
 *	Takes a device from down to up state. The device's private open
 *	function is invoked and then the multicast lists are loaded. Finally
 *	the device is moved into the up state and a %NETDEV_UP message is
 *	sent to the netdev notifier chain.
 *
 *	Calling this function on an active interface is a nop. On a failure
 *	a negative errno code is returned.
 */
int dev_open(struct net_device *dev)
{
	int ret = 0;

	/*
	 *	Is it already up?
	 */

	if (dev->flags & IFF_UP)
		return 0;

	/*
	 *	Is it even present?
	 */
	if (!netif_device_present(dev))
		return -ENODEV;

	/*
	 *	Call device private open method
	 */
	set_bit(__LINK_STATE_START, &dev->state);
	if (dev->open) {
		ret = dev->open(dev);
		if (ret)
			clear_bit(__LINK_STATE_START, &dev->state);
	}

 	/*
	 *	If it went open OK then:
	 */

	if (!ret) {
		/*
		 *	Set the flags.
		 */
		dev->flags |= IFF_UP;

		/*
		 *	Initialize multicasting status
		 */
		dev_mc_upload(dev);

		/*
		 *	Wakeup transmit queue engine
		 */
		dev_activate(dev);

		/*
		 *	... and announce new interface.
		 */
		notifier_call_chain(&netdev_chain, NETDEV_UP, dev);
	}
	return ret;
}

网络设备的禁用

网络设备一旦关闭后就不能传输数据了,网络设备能被用户命令明确地或被其他事件隐含地禁止。在应用层,可以通过ifconfig down命令(最终是通过ioctl的SIOCSIFFLAGS)来关闭设备,或者在网络设备注销时被禁止。SIOCSIFFLAGS命令通过dev_change_flags(),根据网络设备当前状态来确定调用dev_close()关闭网络设备。

dev_close()将网络设备从激活状态转换到关闭状态,并发送NETDEV_GOING_DOWN和NETDEV_DOWN消息到网络设备状态改变通知链上

/**
 *	dev_close - shutdown an interface.
 *	@dev: device to shutdown
 *
 *	This function moves an active device into down state. A
 *	%NETDEV_GOING_DOWN is sent to the netdev notifier chain. The device
 *	is then deactivated and finally a %NETDEV_DOWN is sent to the notifier
 *	chain.
 */
int dev_close(struct net_device *dev)
{
	if (!(dev->flags & IFF_UP))
		return 0;

	/*
	 *	Tell people we are going down, so that they can
	 *	prepare to death, when device is still operating.
	 */
	notifier_call_chain(&netdev_chain, NETDEV_GOING_DOWN, dev);

	dev_deactivate(dev);

	clear_bit(__LINK_STATE_START, &dev->state);

	/* Synchronize to scheduled poll. We cannot touch poll list,
	 * it can be even on different cpu. So just clear netif_running(),
	 * and wait when poll really will happen. Actually, the best place
	 * for this is inside dev->stop() after device stopped its irq
	 * engine, but this requires more changes in devices. */

	smp_mb__after_clear_bit(); /* Commit netif_running(). */
	while (test_bit(__LINK_STATE_RX_SCHED, &dev->state)) {
		/* No hurry. */
		current->state = TASK_INTERRUPTIBLE;
		schedule_timeout(1);
	}

	/*
	 *	Call the device specific close. This cannot fail.
	 *	Only if device is UP
	 *
	 *	We allow it to be called even after a DETACH hot-plug
	 *	event.
	 */
	if (dev->stop)
		dev->stop(dev);

	/*
	 *	Device is now down.
	 */

	dev->flags &= ~IFF_UP;

	/*
	 * Tell people we are down
	 */
	notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev);

	return 0;
}