网卡设备驱动_第1页
网卡设备驱动_第2页
网卡设备驱动_第3页
网卡设备驱动_第4页
网卡设备驱动_第5页
已阅读5页,还剩42页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

网卡设备驱动8139too网卡设备简介一个PCI设备,总共有三个地址空间:内存,端口和配置。内存和端口其实是同一个内容的不同访问路径而已。PCI设备的配置空间是标准化的,每个PCI设备的配置空间的前64个字节的含义都是一样的。但各个设备的内存和端口空间是完全不一样的。在硬件概念上,设备的I/O内存和I/O端口空间没有区别,其实都是设备拥用的一些读写寄存器。

8139too网卡拥有256字节的读写寄存器空间。它的整个布局如下:

/*Symbolicoffsetstoregisters.*/

enumRTL8139_registers{

MAC0=0,

/*Ethernethardwareaddress.*/

MAR0=8,

/*Multicastfilter.*/

TxStatus0=0x10,

/*Transmitstatus(Four32bitregisters).*/

TxAddr0=0x20,

/*Txdescriptors(alsofour32bit).*/

RxBuf=0x30,

ChipCmd=0x37,

RxBufPtr=0x38,

RxBufAddr=0x3A,

IntrMask=0x3C,

IntrStatus=0x3E,

TxConfig=0x40,

RxConfig=0x44,

Timer=0x48,

/*Ageneral-purposecounter.*/

RxMissed=0x4C,

/*24bitsvalid,writeclears.*/

Cfg9346=0x50,

Config0=0x51,

Config1=0x52,

FlashReg=0x54,

MediaStatus=0x58,

Config3=0x59,

Config4=0x5A,

/*absentonRTL-8139A*/

HltClk=0x5B,

MultiIntr=0x5C,

TxSummary=0x60,

BasicModeCtrl=0x62,

BasicModeStatus=0x64,

NWayAdvert=0x66,

NWayLPAR=0x68,

NWayExpansion=0x6A,

/*Undocumentedregisters,butrequiredforproperoperation.*/

FIFOTMS=0x70,

/*FIFOControlandtest.*/

CSCR=0x74,

/*ChipStatusandConfigurationRegister.*/

PARA78=0x78,

PARA7c=0x7c,

/*Magictransceiverparameterregister.*/

Config5=0xD8,

/*absentonRTL-8139A*/

};

每个寄存器都有它特殊的含义和用途。举个例子(我们假设使用I/O内存的方式,ioaddr为设备内存映射在CPU内存地址空间的起始地址,下述代码能读取板卡的版本号):

#defineHW_REVID(b30,b29,b28,b27,b26,b23,b22)\

(b30<<30|b29<<29|b28<<28|b27<<27|b26<<26|b23<<23|b22<<22)

#defineHW_REVID_MASK

HW_REVID(1,1,1,1,1,1,1)

/*identifychipattachedtoboard*/

version=ioread32(ioaddr+TxConfig)&HW_REVID_MASK;

在我的电脑上,version读出来的值的0x74400000,经过比对,板卡名称为:RTL-8100B/8139D。

下面是整个系例版本号的表格:

enumchip_flags{

HasHltClk=(1<<0),

HasLWake=(1<<1),

};

/*directlyindexedbychip_t,above*/

conststaticstruct{

constchar*name;

u32version;/*fromRTL8139C/RTL8139Ddocs*/

u32flags;

}rtl_chip_info[]={

{"RTL-8139",

HW_REVID(1,0,0,0,0,0,0),HasHltClk,},

{"RTL-8139revK",HW_REVID(1,1,0,0,0,0,0),HasHltClk,},

{"RTL-8139A",

HW_REVID(1,1,1,0,0,0,0),HasHltClk,},

{"RTL-8139ArevG",HW_REVID(1,1,1,0,0,1,0),HasHltClk,},

{"RTL-8139B",

HW_REVID(1,1,1,1,0,0,0),HasLWake,},

{"RTL-8130",

HW_REVID(1,1,1,1,1,0,0),HasLWake,},

{"RTL-8139C",

HW_REVID(1,1,1,0,1,0,0),HasLWake,},

{"RTL-8100",

HW_REVID(1,1,1,1,0,1,0),HasLWake,},

{"RTL-8100B/8139D",HW_REVID(1,1,1,0,1,0,1),HasLWake,},

{"RTL-8101",

HW_REVID(1,1,1,0,1,1,1),HasLWake,},

};

在这个表格中,RTL_8139B以下(包括RTL_8139B)的板卡都属于较新的版本。新老版本之间有不同的唤醒方式。先看新版本的:

#defineLWAKE

0x10

#defineCfg1_PM_Enable

0x01

u8new_tmp8,tmp8;

enumConfig4Bits{

LWPTN=(1<<2),

/*noton8139,8139A*/

};

enumCfg9346Bits{

Cfg9346_Lock=0x00,

Cfg9346_Unlock=0xC0,

};

new_tmp8=tmp8=ioread8(ioaddr+Config1);

if((rtl_chip_info[tp->chipset].flags&HasLWake)&&(tmp8&LWAKE))

new_tmp8&=~LWAKE;

new_tmp8|=Cfg1_PM_Enable;

if(new_tmp8!=tmp8){

iowrite8(Cfg9346_Unlock,ioaddr+Cfg9346);

iowrite8(tmp8,ioaddr+Config1);

iowrite8(Cfg9346_Lock,ioaddr+Cfg9346);

}

if(rtl_chip_info[tp->chipset].flags&HasLWake){

tmp8=ioread8(ioaddr+Config4);

if(tmp8&LWPTN){

iowrite8(Cfg9346_Unlock,ioaddr+Cfg9346);

iowrite8(tmp8&~LWPTN,ioaddr+Config4);

iowrite8(Cfg9346_Lock,ioaddr+Cfg9346);

}

}

基本的一个流程是:如果板卡版本本身支持了HasLWake,而Config1中读出的值带有LWAKE,把Config1的值写回,并把Config4中的LWPTN去除。而我的网卡中从Config1,Config4读取的值分别为0x8d,0x88,所以,无需做任何操作。

下面是旧版本的唤醒方式:

enumConfig1Bits{

Cfg1_PM_Enable=0x01,

Cfg1_VPD_Enable=0x02,

Cfg1_PIO=0x04,

Cfg1_MMIO=0x08,

LWAKE=0x10,

/*noton8139,8139A*/

Cfg1_Driver_Load=0x20,

Cfg1_LED0=0x40,

Cfg1_LED1=0x80,

SLEEP=(1<<1),

/*onlyon8139,8139A*/

PWRDN=(1<<0),

/*onlyon8139,8139A*/

};

tmp8=ioread8(ioaddr+Config1);

tmp8&=~(SLEEP|PWRDN);

iowrite8(tmp8,ioaddr+Config1);

下面是一个板卡的复位操作:

enumChipCmdBits{

CmdReset=0x10,

CmdRxEnb=0x08,

CmdTxEnb=0x04,

RxBufEmpty=0x01,

};

staticvoidrtl8139_chip_reset(void__iomem*ioaddr)

{

inti;

/*Softresetthechip.*/

iowrite8(CmdReset,ioaddr+ChipCmd);

/*Checkthatthechiphasfinishedthereset.*/

for(i=1000;i>0;i--){

barrier();

if((ioread8(ioaddr+ChipCmd)&CmdReset)==0)

break;

udelay(10);

}

}

写一个CmdReset命令到ChipCmd位置,等待该命令消失,即可。

关于网卡的寄存器操作,还有一些,再进行过程中遇到时再介绍。2.网络设备的初始化网络接口是字符设备,块设备之后的第三类标准Linux设备。网络驱动程序和其它内核模块一样,当被装载到正在运行的内核中时,它要请求资源并提供一些功能设施。网络驱动程序对每个新检测到的接口,会向全局的网络设备链表中插入一个数据结构。每个接口由一个net_device结构描述,这是一个很庞大的结构,在下面的描述中,我们会看到一些这个结构的成员。在8139too网卡的驱动程序中,我们看到alloc_etherdev动态分配了该结构,这是为以太网接口封装的一个分配函数:structnet_device*alloc_etherdev(intsizeof_priv){returnalloc_netdev(sizeof_priv,"eth%d",ether_setup);}EXPORT_SYMBOL(alloc_etherdev);真正用来实现网络设备接口分配的函数是alloc_netdevalloc_ehterdev的封装为以太网设备接口分配了形如“eth%d”的名字,同时,指定了一个ether_setup的初始化函数:voidether_setup(structnet_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;/*Ethernetwantsgoodqueues*/dev->flags=IFF_BROADCAST|IFF_MULTICAST;memset(dev->broadcast,0xFF,ETH_ALEN);}EXPORT_SYMBOL(ether_setup);它为表示以太网设备接口的net_device结构进行了部分初始化,因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。alloc_netdev分配sizeof(dev)+sizeof_priv的内核页,并调用初始化函数。这些都是在PCI的探测函数中做的事情,在完成了网络设备接口的分配后,我们要访问PCI设备的一些资源。在能够访问PCI设备的任何资源之前,我们必须激活PCI设备:/*enabledevice(incl.PCIPMwakeupandhotplugsetup)*/rc=pci_enable_device(pdev);if(rc)gotoerr_out;激活PCI设备后,我们可以从PCI设备的配置空间读取6个I/O地址区域,在我们的例子程序中,是从8139网卡的第1个I/O地址区域读取I/O内存空间,然后调用pci_request_regions(pdev,"8139too");进行I/O内存分配。该函数实际调用request_mem_region函数。接下来,进行一系例的板卡相关的初始化操作。最后,接着ether_setup的操作,我们要对net_device作一些自己的初始化操作:dev->open=rtl8139_open;dev->hard_start_xmit=rtl8139_start_xmit;dev->get_stats=rtl8139_get_stats;dev->stop=rtl8139_close;open函数会在网络设备接口被注册的时候被调用,hard_start_xmit在有数据需要传输的时候被调用,get_stats返回网络接口的统计信息。以上三个成员如果没有被初始化,则在注册网络接口的时候会造成内核崩溃。最后一个stop会在网络接口被停掉的时候被调用。完成了这些初始化操作之后,我们就可以注册网络设备接口了,一旦注册完毕,网络设备接口就可以被使用了:i=register_netdev(dev);if(i)returni;register_netdev首先为网络设备接口分配一个名称(比如把eth%d替换为eth0)。然后将dev插入到一个叫做dev_base的网络设备全局链表中。下面是整个8139too网卡设备初始化代码:/*8139_init.h*helinqiang@*2006-3-11*/#ifndefRTL_8139_INIT_H#defineRTL_8139_INIT_H#include<linux/mod_devicetable.h>//forstructpci_device_id#include<linux/module.h>//forMODULE_DEVICE_TABLE#include<linux/pci.h>//forstructpci_driver#include<linux/netdevice.h>#defineDRV_NAME"8139too"#defineDRV_VERSION"0.9.27"#defineRTL8139_DRIVER_NAMEDRV_NAME"FastEthernetdriver"DRV_VERSIONtypedefenum{RTL8139=0,RTL8129,}board_t;staticstructpci_device_idrtl8139_pci_tbl[]={{0x10ec,0x8139,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x10ec,0x8138,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1113,0x1211,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1500,0x1360,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x4033,0x1360,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1186,0x1300,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1186,0x1340,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x13d1,0xab06,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1259,0xa117,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1259,0xa11e,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x14ea,0xab06,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x14ea,0xab07,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x11db,0x1234,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1432,0x9130,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x02ac,0x1012,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x018a,0x0106,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x126c,0x1211,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x1743,0x8139,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},{0x021b,0x8139,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},#ifdefCONFIG_SH_SECUREEDGE5410/*Bogus8139siliconreports8129withoutexternalPROM:-(*/{0x10ec,0x8129,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},#endif#ifdefCONFIG_8139TOO_8129{0x10ec,0x8129,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8129},#endif/*somecrazycardsreportinvalidvendoridslike*0x0001here.Theotheridsarevalidandconstant,*sowesimplydon'tmatchonthemainvendorid.*/{PCI_ANY_ID,0x8139,0x10ec,0x8139,0,0,RTL8139},{PCI_ANY_ID,0x8139,0x1186,0x1300,0,0,RTL8139},{PCI_ANY_ID,0x8139,0x13d1,0xab06,0,0,RTL8139},{0,}};MODULE_DEVICE_TABLE(pci,rtl8139_pci_tbl);staticint__devinitrtl8139_init_one(structpci_dev*pdev,conststructpci_device_id*id);staticvoid__devexitrtl8139_remove_one(structpci_dev*pdev);staticintrtl8139_open(structnet_device*dev);staticstructnet_device_stats*rtl8139_get_stats(structnet_device*dev);staticintrtl8139_start_xmit(structsk_buff*skb,structnet_device*dev);staticintrtl8139_close(structnet_device*dev);staticvoidrtl8139_chip_reset(void__iomem*ioaddr);staticint__devinitrtl8139_init_board(structpci_dev*pdev,structnet_device**dev_out);staticvoid__rtl8139_cleanup_dev(structnet_device*dev);staticstructpci_driverrtl8139_pci_driver={.name=DRV_NAME,.id_table=rtl8139_pci_tbl,.probe=rtl8139_init_one,.remove=__devexit_p(rtl8139_remove_one),};/*BitsinConfig4*/enumConfig4Bits{LWPTN=(1<<2),/*noton8139,8139A*/};enumchip_flags{HasHltClk=(1<<0),HasLWake=(1<<1),};enumChipCmdBits{CmdReset=0x10,CmdRxEnb=0x08,CmdTxEnb=0x04,RxBufEmpty=0x01,};enumCfg9346Bits{Cfg9346_Lock=0x00,Cfg9346_Unlock=0xC0,};typedefenum{CH_8139=0,CH_8139_K,CH_8139A,CH_8139A_G,CH_8139B,CH_8130,CH_8139C,CH_8100,CH_8100B_8139D,CH_8101,}chip_t;structrtl8139_private{structpci_dev*pci_dev;void__iomem*mmio_addr;unsignedlongregs_len;unsignedintchipset;structnet_device_statsstats;};/*Symbolicoffsetstoregisters.*/enumRTL8139_registers{MAC0=0,/*Ethernethardwareaddress.*/MAR0=8,/*Multicastfilter.*/TxStatus0=0x10,/*Transmitstatus(Four32bitregisters).*/TxAddr0=0x20,/*Txdescriptors(alsofour32bit).*/RxBuf=0x30,ChipCmd=0x37,RxBufPtr=0x38,RxBufAddr=0x3A,IntrMask=0x3C,IntrStatus=0x3E,TxConfig=0x40,RxConfig=0x44,Timer=0x48,/*Ageneral-purposecounter.*/RxMissed=0x4C,/*24bitsvalid,writeclears.*/Cfg9346=0x50,Config0=0x51,Config1=0x52,FlashReg=0x54,MediaStatus=0x58,Config3=0x59,Config4=0x5A,/*absentonRTL-8139A*/HltClk=0x5B,MultiIntr=0x5C,TxSummary=0x60,BasicModeCtrl=0x62,BasicModeStatus=0x64,NWayAdvert=0x66,NWayLPAR=0x68,NWayExpansion=0x6A,/*Undocumentedregisters,butrequiredforproperoperation.*/FIFOTMS=0x70,/*FIFOControlandtest.*/CSCR=0x74,/*ChipStatusandConfigurationRegister.*/PARA78=0x78,PARA7c=0x7c,/*Magictransceiverparameterregister.*/Config5=0xD8,/*absentonRTL-8139A*/};#defineHW_REVID(b30,b29,b28,b27,b26,b23,b22)\(b30<<30|b29<<29|b28<<28|b27<<27|b26<<26|b23<<23|b22<<22)#defineHW_REVID_MASKHW_REVID(1,1,1,1,1,1,1)/*directlyindexedbychip_t,above*/conststaticstruct{constchar*name;u32version;/*fromRTL8139C/RTL8139Ddocs*/u32flags;}rtl_chip_info[]={{"RTL-8139",HW_REVID(1,0,0,0,0,0,0),HasHltClk,},{"RTL-8139revK",HW_REVID(1,1,0,0,0,0,0),HasHltClk,},{"RTL-8139A",HW_REVID(1,1,1,0,0,0,0),HasHltClk,},{"RTL-8139ArevG",HW_REVID(1,1,1,0,0,1,0),HasHltClk,},{"RTL-8139B",HW_REVID(1,1,1,1,0,0,0),HasLWake,},{"RTL-8130",HW_REVID(1,1,1,1,1,0,0),HasLWake,},{"RTL-8139C",HW_REVID(1,1,1,0,1,0,0),HasLWake,},{"RTL-8100",HW_REVID(1,1,1,1,0,1,0),HasLWake,},{"RTL-8100B/8139D",HW_REVID(1,1,1,0,1,0,1),HasLWake,},{"RTL-8101",HW_REVID(1,1,1,0,1,1,1),HasLWake,},};/*BitsinConfig1*/enumConfig1Bits{Cfg1_PM_Enable=0x01,Cfg1_VPD_Enable=0x02,Cfg1_PIO=0x04,Cfg1_MMIO=0x08,LWAKE=0x10,/*noton8139,8139A*/Cfg1_Driver_Load=0x20,Cfg1_LED0=0x40,Cfg1_LED1=0x80,SLEEP=(1<<1),/*onlyon8139,8139A*/PWRDN=(1<<0),/*onlyon8139,8139A*/};#endif//RTL_8139_INIT_H/*8139_init.c*helinqiang@*2006-3-11*/#include"8139_init.h"#include<linux/init.h>#include<linux/types.h>#include<linux/etherdevice.h>#include<linux/delay.h>MODULE_AUTHOR("LinqiangHe,HangzhouChina");MODULE_LICENSE("DualBSD/GPL");staticint__initrtl8139_init_module(void){/*whenwe'reamodule,wealwaysprintaversionmessage,*evenifno8139boardisfound.*/#ifdefMODULEprintk(KERN_INFORTL8139_DRIVER_NAME"\n");#endifreturnpci_module_init(&rtl8139_pci_driver);}staticvoid__exitrtl8139_cleanup_module(void){pci_unregister_driver(&rtl8139_pci_driver);}module_init(rtl8139_init_module);module_exit(rtl8139_cleanup_module);int__devinitrtl8139_init_one(structpci_dev*pdev,conststructpci_device_id*id){structnet_device*dev=NULL;inti=rtl8139_init_board(pdev,&dev);if(i<0)returni;dev->open=rtl8139_open;dev->hard_start_xmit=rtl8139_start_xmit;dev->get_stats=rtl8139_get_stats;dev->stop=rtl8139_close;i=register_netdev(dev);if(i)returni;pci_set_drvdata(pdev,dev);return0;}void__devexitrtl8139_remove_one(structpci_dev*pdev){structnet_device*dev=pci_get_drvdata(pdev);unregister_netdev(dev);__rtl8139_cleanup_dev(dev);pci_disable_device(pdev);}staticint__devinitrtl8139_init_board(structpci_dev*pdev,structnet_device**dev_out){void__iomem*ioaddr;structrtl8139_private*tp;u8tmp8;intrc,disable_dev_on_err=0;unsignedinti;unsignedlongmmio_start,mmio_end,mmio_flags,mmio_len;u32version;*dev_out=alloc_etherdev(sizeof(*tp));if(*dev_out==NULL){printk(KERN_ERR"%s:Unabletoallocnewnetdevice\n",pci_name(pdev));return-ENOMEM;}SET_MODULE_OWNER(*dev_out);SET_NETDEV_DEV(*dev_out,&pdev->dev);tp=netdev_priv(*dev_out);tp->pci_dev=pdev;/*enabledevice(incl.PCIPMwakeupandhotplugsetup)*/rc=pci_enable_device(pdev);if(rc)gotoerr_out;mmio_start=pci_resource_start(pdev,1);mmio_end=pci_resource_end(pdev,1);mmio_flags=pci_resource_flags(pdev,1);mmio_len=pci_resource_len(pdev,1);rc=pci_request_regions(pdev,"8139too");if(rc)gotoerr_out;disable_dev_on_err=1;/*enablePCIbus-mastering*/pci_set_master(pdev);/*ioremapMMIOregion*/ioaddr=pci_iomap(pdev,1,0);(*dev_out)->base_addr=(long)ioaddr;tp->mmio_addr=ioaddr;tp->regs_len=mmio_len;/*Bringoldchipsoutoflow-powermode.*/iowrite8('R',ioaddr+HltClk);/*checkformissing/brokenhardware*/if(ioread32(ioaddr+TxConfig)==0xFFFFFFFF){printk(KERN_ERR"%s:Chipnotresponding,ignoringboard\n",pci_name(pdev));rc=-EIO;gotoerr_out;}/*identifychipattachedtoboard*/version=ioread32(ioaddr+TxConfig)&HW_REVID_MASK;for(i=0;i<ARRAY_SIZE(rtl_chip_info);i++)if(version==rtl_chip_info[i].version){tp->chipset=i;gotomatch;}tp->chipset=0;match:printk(KERN_INFO"chipsetid(%d)==index%d,'%s'\n",version,i,rtl_chip_info[i].name);if(tp->chipset>=CH_8139B){u8new_tmp8=tmp8=ioread8(ioaddr+Config1);printk(KERN_ERR"tmp81:%x\n",tmp8);if((rtl_chip_info[tp->chipset].flags&HasLWake)&&(tmp8&LWAKE))new_tmp8&=~LWAKE;new_tmp8|=Cfg1_PM_Enable;if(new_tmp8!=tmp8){iowrite8(Cfg9346_Unlock,ioaddr+Cfg9346);iowrite8(tmp8,ioaddr+Config1);iowrite8(Cfg9346_Lock,ioaddr+Cfg9346);}if(rtl_chip_info[tp->chipset].flags&HasLWake){tmp8=ioread8(ioaddr+Config4);printk(KERN_ERR"tmp8:%x\n",tmp8);if(tmp8&LWPTN){iowrite8(Cfg9346_Unlock,ioaddr+Cfg9346);iowrite8(tmp8&~LWPTN,ioaddr+Config4);iowrite8(Cfg9346_Lock,ioaddr+Cfg9346);}}}else{tmp8=ioread8(ioaddr+Config1);tmp8&=~(SLEEP|PWRDN);iowrite8(tmp8,ioaddr+Config1);}rtl8139_chip_reset(ioaddr);return0;err_out:__rtl8139_cleanup_dev(*dev_out);if(disable_dev_on_err)pci_disable_device(pdev);returnrc;}staticintrtl8139_open(structnet_device*dev){return0;}staticstructnet_device_stats*rtl8139_get_stats(structnet_device*dev){structrtl8139_private*tp=netdev_priv(dev);return&tp->stats;}staticintrtl8139_start_xmit(structsk_buff*skb,structnet_device*dev){structrtl8139_private*tp=netdev_priv(dev);dev_kfree_skb(skb);tp->stats.tx_dropped++;return0;}staticintrtl8139_close(structnet_device*dev){return0;}staticvoidrtl8139_chip_reset(void__iomem*ioaddr){inti;/*Softresetthechip.*/iowrite8(CmdReset,ioaddr+ChipCmd);/*Checkthatthechiphasfinishedthereset.*/for(i=1000;i>0;i--){barrier();if((ioread8(ioaddr+ChipCmd)&CmdReset)==0)break;udelay(10);}}staticvoid__rtl8139_cleanup_dev(structnet_device*dev){structrtl8139_private*tp=netdev_priv(dev);structpci_dev*pdev;pdev=tp->pci_dev;if(tp->mmio_addr)pci_iounmap(pdev,tp->mmio_addr);/*it'soktocallthisevenifwehavenoregionstofree*/pci_release_regions(pdev);free_netdev(dev);pci_set_drvdata(pdev,NULL);3.一个简单的网络流量统计程序前面讲到,register_netdev首先为网络设备接口分配一个名称,然后将dev插入到一个叫做dev_base的网络设备全局链表中。由此我们不难想到,通过访问dev_base,就可以遍历到系统中的所有网络设备,而每一个网络设备接口都有一个net_device结构来表示。该结构中有一个成员函数指针:

structnet_device_stats*(*get_stats)(structnet_device*dev);它返回一个structnet_device_stats结构,该结构保存了所在网络设备接口的详细的流量统计信息:

structnet_device_stats

{

unsignedlongrx_packets;/*totalpacketsreceived

*/

unsignedlongtx_packets;/*totalpacketstransmitted

*/

unsignedlongrx_bytes;/*totalbytesreceived

*/

unsignedlongtx_bytes;/*totalbytestransmitted

*/

unsignedlongrx_errors;/*badpacketsreceived

*/

unsignedlongtx_errors;/*packettransmitproblems*/

unsignedlongrx_dropped;/*nospaceinlinuxbuffers

*/

unsignedlongtx_dropped;/*nospaceavailableinlinux

*/

unsignedlongmulticast;/*multicastpacketsreceived

*/

unsignedlong

collisions;

/*detailedrx_errors:*/

unsignedlongrx_length_errors;

unsignedlongrx_over_errors;/*receiverringbuffoverflow

*/

unsignedlongrx_crc_errors;/*recvedpktwithcrcerror*/

unsignedlongrx_frame_errors;/*recv'dframealignmenterror*/

unsignedlongrx_fifo_errors;/*recv'rfifooverrun

*/

unsignedlongrx_missed_errors;/*receivermissedpacket

*/

/*detailedtx_errors*/

unsignedlong

tx_aborted_errors;

unsignedlong

tx_carrier_errors;

unsignedlong

tx_fifo_errors;

unsignedlong

tx_heartbeat_errors;

unsignedlong

tx_window_errors;

/*forcslipetc*/

unsignedlong

rx_compressed;

unsignedlong

tx_compressed;};

OK,有了这些基础知识,我们就可以写一个网络流量统计的内核模块了,这是一个最简单的程序,它每次被插入到内核中时,会在日志文件中打出当前系统上的所有网络设备接口的传输的和收到的字节数和包数。我们可以把它进一步改造,输出到/proc文件系统,再通过用户空间的一个程序计算单位时间的流量,就成为一个真正的网络流量统计程序了。下面是代码:/*netbase.c

*helinqiang@

*2006-03-14

*/#include<linux/init.h>#include<linux/netdevice.h>MODULE_AUTHOR("LinqiangHe,HangzhouChina");MODULE_LICENSE("DualBSD/GPL");staticint__initnetbase_init_module(void){

structnet_device*dev=dev_base;

structnet_device_stats*stats;

while(dev!=NULL){

printk(KERN_INFO"devname:%s\n",dev->name);

stats=dev->get_stats(dev);

printk(KERN_INFO"\ttransmittedbytes:%lu\n",stats->tx_bytes);

printk(KERN_INFO"\treceivedbytes:%lu\n",stats->rx_bytes);

printk(KERN_INFO"\ttransmittedpackets:%lu\n",stats->tx_packets);

printk(KERN_INFO"\treceivedpackets:%lu\n",stats->rx_packets);

dev=dev->next;

}

return0;}staticvoid__exitnetbase_exit_module(void){}module_init(netbase_init_module);module_exit(netbase_exit_module);下面是在我的电脑上的某一时刻的输出结果:Mar1421:22:34localhostkernel:devname:loMar1421:22:34localhostkernel:

transmittedbytes:4660Mar1421:22:34localhostkernel:

receivedbytes:4660Mar1421:22:34localhostkernel:

transmittedpackets:61Mar1421:22:34localhostkernel:

receivedpackets:61Mar1421:22:34localhostkernel:devname:sit0Mar1421:22:34localhostkernel:

transmittedbytes:0Mar1421:22:34localhostkernel:

receivedbytes:0Mar1421:22:34localhostkernel:

transmittedpackets:0Mar1421:22:34localhostkernel:

receivedpackets:0Mar1421:22:34localhostkernel:devname:eth0Mar1421:22:34localhostkernel:

transmittedbytes:437071Mar1421:22:34localhostkernel:

receivedbytes:4046403Mar1421:22:34localhostkernel:

transmittedpackets:2607Mar1421:22:34localhostkernel:

receivedpackets:177734.net_device结构体详解

结构体net_device代表了一个网络设备接口,它是我们理解网络设备驱动程序的关键。这里,我们选择其中的一些重要成员,一一作详细分析,并结合以太网设备,看看Linux内核是如何为以太网设备提供结构体中某些成员的缺省值的。

在Linux内核源代码中是这样为这个结构体作注释的:实际上,这个结构体是一个很大的错误,它把I/O数据和更高层的数据混合在一起,而且它几乎必须知道INET模块中的每个数据结构。

毫无疑问,这是一个巨型结构体。但我们为编写网络设备驱动程序,只需要了解其中的一部分,下面选择其中的一些作分析,并给出以太网设备的缺省值。

unsignedshort

flags;void(*set_multicast_list)(structnet_device*dev);

这是一个接口标志,包含了很多值的位掩码。在以太网的缺省初始化函数中,该标志被设置为:IFF_BROADCAST|IFF_MULTICAST,表示以太网卡是可广播的,并且是能够进行组播发送的。另外,该标志接口还有一些只读标志,如IFF_UP,当接口被激活并可以开始传输数据包时,内核设置该标志。而IFF_PROMISC被设置或清除时,会调用set_multicast_list函数通知板卡上的硬件过滤器。unsignedshorthard_header_len;unsignedshorttype;

hard_header_len是硬件头的长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即以太网头的长度,该值为14,下面是以太网头的定义:structethhdr{

unsignedchar

h_dest[ETH_ALEN];

/*destinationethaddr*/

unsignedchar

h_source[ETH_ALEN];/*sourceetheraddr

*/

unsignedshort

h_proto;

/*packettypeIDfield*/}__attribute__((packed));

ETH_ALEN被定义为6,即以太网MAC地址的长度。h_proto保存type的值。type是接口的硬件类型,以太网设备的初始化函数中将其赋值为ARPHRD_ETHER,即10Mb以太网。int(*hard_header)(structsk_buff*skb,structnet_device*dev,

unsignedshorttype,void*daddr,void*saddr,

unsignedlen);

该函数在数据被传输之前被调用,它根据先前检索到的源和目标地址建立硬件头。以太网设备的默认函数是eth_header:inteth_header(structsk_buff*skb,structnet_device*dev,

unsignedshorttype,void*daddr,

void*saddr,unsignedlen){

structethhdr*eth=(structethhdr*)skb_push(skb,ETH_HLEN);

/*

*

Settheprotocoltype.Forapacketof

*

typeETH_P_802_3weputthelength

*

inhereinstead.Itisuptothe802.2

*

layertocarryprotocolinformation.

*/

if(type!=ETH_P_802_3)

eth->h_proto=htons(type);

else

eth->h_proto=htons(len);

//Setthesourcehardwareaddress.

if(!saddr)

saddr=dev->dev_addr;

memcpy(eth->h_source,saddr,dev->addr_len);

//Anyway,theloopback-deviceshouldnever

//usethisfunction...

if(dev->flags&(IFF_LOOPBACK|IFF_NOARP)){

memset(eth->h_dest,0,dev->addr_len);

returnETH_HLEN;

}

if(daddr){

memcpy(eth->h_dest,daddr,dev->addr_len);

returnETH_HLEN;

}

return-ETH_HLEN;}

它根据传入的参数建立以太网头,如果传入的源地址为空,则使用设备的地址作为源地址。与之相关的还有一个int(*rebuild_header)(structsk_buff*skb)函数,在以太网设备中,它是用于在ARP地址解析完成以后,重新建立以太网头,主要是更新目标MAC地址,因为在前一次建立时,由于没有经过ARP协议,有可能目标MAC地址是错误的(未完,待续)。5.net_device结构体详解(续)int(*set_mac_address)(structnet_device*dev,void*addr);

改变网卡的mac地址。实际上,许多硬件设备接口根本不支持这种功能,就算支持,也需要硬件厂商提供的工具修改EPROM中的硬件地址。一般我们在某一操作系统下所谓的修改MAC地址,只是修改了操作系统在安装网卡时从网卡EPROM中读出来的值,如果系统被重装,则MAC地址又恢复为原来的值。以太网设备的默认函数是eth_mac_addr,它只是简单地修改了dev->dev_addr的值。int(*hard_header_cache)(structneighbour*neigh,structhh_cache*hh);void(*header_cache_update)(structhh_cache*hh,structnet_device*dev,

unsignedchar*

haddr);int(*hard_header_parse)(structsk_buff*skb,unsignedchar*haddr);

这三个函数在以太网设备接口中都有默认的值,hard_header_cache把硬件地址缓存到structhh_cache中;header_cache_update在地址发生变化中,更新hh_cache结构中的目标地址;而hard_header_parse则从skb中的数据包中获得源地址,并将其复制到位于haddr的缓冲区。int(*change_mtu)(structnet_device*dev,intnew_mtu);

下面是以太网设备接口的change_mtu的实现:

staticinteth_change_mtu(structnet_device*dev,intnew_mtu)

{

if((new_mtu<68)||(new_mtu>1500))

return-EINVAL;

dev->mtu=new_mtu;

return0;

}

在net_device中,还有很多网络驱动程序必须涉及的重要成员,随着我们的8139too网卡驱动程序的进一步深入,在涉及时再作详细分析6.分配中断号&为网卡驱动设置MAC地址PCI设备的中断其实很容易处理。在Linux的引导阶段,计算机固件已经为设备分配了一个唯一的中断号。中断号保存在配置寄存器60(PCI_INTERRUPT_LINE)中,该寄存器为一个字节宽。所以驱动程序无需检测中断号。只需从配置寄存器中读取即可:

result=pci_read_config_byte(pci_dev,PCI_INTERRUPT_LINE,&myirq);

但事实上,留给具体的PCI驱动程序编写者的事情可能更为简单。在我们前面讲到过的8139too网卡的初始化程序的rtl8139_init_one函数(PCI设备注册成功即被调用的函数,注意,不是网络设备)的第一行加上以下语句:

printk(KERN_INFO"theirq:%d\n",pdev->irq);

OK,我们能得到结果:

theirq:10

也就是说PCI设备驱动程序在注册自己时,已经自动完成中断号的获取。

再回到我们前面讲过的8139too网卡的初始化驱动程序,编译完成,insmod后,我们运行系统的ifconfig命令:

ifconfigeth0

得到结果:

eth0

Linkencap:Ethernet

HWaddr00:00:00:00:00:00

inetaddr:00

Bcast:55

Mask:

inet6addr:fe80::200:ff:fe00:0/64Scope:Link

UPBROADCASTRUNNINGMULTICAST

MTU:1500

Metric:1

RXpackets:0errors:0dropped:0overruns:0frame:0

TXpackets:0errors:0dropped:7overruns:0carrier:0

collisions:0txqueuelen:1000

RXbytes:0(0.0b)

TXbytes:0(0.0b)

Baseaddress:0x4800

怎么样?五脏俱全?没错,由于以太网设备接口本身已由内核提供了很多缺省成员值,已经能够正确显示MTU,并且有了传输队列的长度。但它的MAC地址:HWaddr00:00:00:00:00:00似乎不大对。这是因为代表网络设备接口的net_device结构体的成员dev_addr没有被正确设置。网络驱动程序还需要从网卡设备的EPROM中读取MAC地址填入到dev_addr字符数组中才行。

前文已经有讲到,8139too网卡的设备内存的头6个字节即是MAC地址,直接读取即可。但我不知道为什么,8139too网卡的驱动程序采用了一个非常复杂的从EPROM读取信息的函数,也许是为了安全,或者通用。下面的代码插入到rtl8139_init_one函数中注册网络设备之前的位置:

inti,addr_len;

void__iomem*ioaddr;

structrtl8139_private*tp;

tp=netdev_priv(dev);

ioaddr=tp->mmio_addr;

addr_len=read_eeprom(ioaddr,0,8)==0x8129?8:6;

for(i=0;i<3;i++)

((u16*)(dev->dev_addr))[i]=

le16_to_cpu(read_eeprom(ioaddr,i+7,addr_len));

read_eeprom的实现可直接查阅8139too.c源代码,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论