1. 程式人生 > >linux網絡卡驅動分析之probe函式

linux網絡卡驅動分析之probe函式

       從上面結果可以看出,該裝置使用了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<<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模組。