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; }
网络设备