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 函数的不同封装:
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<<NETDEV_HASHBITS];
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类型的数据:
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<<NETDEV_HASHBITS)-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模块。