标签:网络
很多网络设备都是基于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驱动程序
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;
}
标签:网络
原文地址:http://blog.csdn.net/wangpeihuixyz/article/details/37375945