2410平台上dm9000a网卡驱动分析.docx
《2410平台上dm9000a网卡驱动分析.docx》由会员分享,可在线阅读,更多相关《2410平台上dm9000a网卡驱动分析.docx(21页珍藏版)》请在冰豆网上搜索。
2410平台上dm9000a网卡驱动分析
2410平台上dm9000a网卡驱动分析
该驱动基于linux-2.6.24.4内核。
首先,需要在arch/arm/mach-s3c2410/mach-smdk2410.c文件中添加如下代码:
staticstructresources3c_dm9000_resource[]={
[0]={
.start=0x10000000,
.end=0x10000040,
.flags=IORESOURCE_MEM
},
[1]={
.start=IRQ_EINT2,
.end=IRQ_EINT2,
.flags=IORESOURCE_IRQ,
}
};
注意上面的start、end等地址是指的网卡的物理地址。
然后,还要在该文件中加入如下代码:
struct platform_devices3c_device_dm9000={
.name="dm9000",
.id=-1,
.num_resources=ARRAY_SIZE(s3c_dm9000_resource),
.resource=s3c_dm9000_resource,
};
需要特别注意上面的name字段,当设备驱动程序寻找设别资源时,会根据该字段对设备进行匹配。
另外,该文件中的smdk2410_devices[]数组中,还需要加入s3c_device_dm9000,不然系统启动时没有找到该资源就不会调用相应的probe函数。
下面分析驱动程序的probe函数。
若驱动被编译进内核,则在系统启动的时候,该函数会被调用。
该函数的源代码如下:
static int dm9k_drv_probe(struct platform_device *pdev)
{
struct net_device *ndev;
unsigned long base;
unsigned int *addr = NULL;
int ret = -ENODEV;
ndev = alloc_etherdev(sizeof(struct board_info));
if (!
ndev) {
printk("%s:
couldnotallocatedevice.\n", CARDNAME);
return -ENOMEM;
}
ndev->dma = (unsigned char)-1;
if (pdev->num_resources < 2 || pdev->num_resources > 3) {
printk("DM9000:
Wrongnumofresources%d\n", pdev->num_resources);
ret = -ENODEV;
goto out;
}
base = pdev->resource[0].start;
ndev->irq = pdev->resource[1].start;
/*
*Requesttheregions.
*/
if (!
request_mem_region(base, 4, ndev->name)) {
ret = -EBUSY;
goto out;
}
addr = ioremap(base, 4);
if (!
addr) {
ret = -ENOMEM;
goto release_mem;
}
ret = dm9k_probe(ndev, (unsigned long)addr);
if (ret !
= 0) {
iounmap(addr);
release_mem:
release_mem_region(base, 4);
out:
printk("%s:
notfound(%d).\n", CARDNAME, ret);
kfree(ndev);
}
return ret;
}
函数首先调用alloc_etherdev,该函数在include/linux/etherdevice.h中声明,其中有如下语句:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
而alloc_etherdev_mq函数又定义在net/ethernet/eth.c中,如下:
struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)
{
return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
}
可见,该函数只是用自己的参数来调用alloc_netdev_mq函数。
alloc_netdev_mq函数定义在net/core/dev.c中,原型如下:
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
void (*setup)(struct net_device *), unsigned int queue_count)
关于该函数的说明:
/**
* alloc_netdev_mq-allocatenetworkdevice
* @sizeof_priv:
sizeofprivatedatatoallocatespacefor
* @name:
devicenameformatstring
* @setup:
callbacktoinitializedevice
* @queue_count:
thenumberofsubqueuestoallocate
*
* Allocatesastructnet_devicewithprivatedataareafordriveruse
* andperformsbasicinitialization. Alsoallocatessubquuestructs
* foreachqueueonthedeviceattheendofthenetdevice.
*/
可见,alloc_etherdev为设备驱动分配了私有数据空间,并对设备驱动做了一些初始化工作。
接下来,设备驱动将要检查设备的resources的数量,如果数量小于2或者大于3,则初始化函数自动返回,初始化失败。
我们的设备驱动中,resources的数量为2:
一个表示设备的IO地址,另一个是设备的中断号。
代码
base = pdev->resource[0].start;
ndev->irq = pdev->resource[1].start;
分别得到设备的端口地址和中断号。
接下来,驱动程序将向系统申请io内存,从地址base开始,大小为4个字节。
如果申请成功,接下来需要做的就是将地址重新映射,从地址base开始,长度为4个字节。
这样做的原因主要是驱动程序一般不直接访问物理地址,而访问虚拟地址。
地址重新映射成功后,就调用dm9k_probe函数进行设备初始化。
dm9k_probe函数的全部代码如下
int __initdm9k_probe(struct net_device *dev, unsigned long addr)
{
struct board_info *db; /*Pointaboardinformationstructure*/
u32id_val;
u16i, j;
int retval;
/*SearchforDM9000serialNIC*/
PUTB(DM9KS_VID_L, addr);
id_val = GETB(addr + 2); /*Changeoffsetto2^^^^^*/
PUTB(DM9KS_VID_H, addr);
id_val |= GETB(addr + 2) << 8;
PUTB(DM9KS_PID_L, addr);
id_val |= GETB(addr + 2) << 16;
PUTB(DM9KS_PID_H, addr);
id_val |= GETB(addr + 2) << 24;
if (id_val !
= DM9KS_ID && id_val !
= DM9010_ID) {
/*Dm9kchipnotfound*/
printk("dmfe_probe():
DM9000notfound.ID=%08X\n", id_val);
return -ENODEV;
}
printk("I/O:
%lx,VID:
%x\n",addr, id_val);
/*Allocatedboardinformationstructure*/
memset(dev->priv, 0, sizeof(struct board_info));
db = (board_info_t *)dev->priv;
dmfe_dev = dev;
db->io_addr = addr;
db->io_data = addr + 2; /*Changeoffsetto2^^^^^*/
/*driversystemfunction*/
dev->base_addr = addr;
dev->irq = IRQ_EINT2;
dev->open = &dmfe_open;
dev->hard_start_xmit = &dmfe_start_xmit;
dev->watchdog_timeo = HZ;
dev->tx_timeout = dmfe_timeout;
dev->stop = &dmfe_stop;
dev->get_stats = &dmfe_get_stats;
dev->set_multicast_list = &dm9000_hash_table;
dev->do_ioctl = &dmfe_do_ioctl;
for(i=0,j=0x10; i<6; i++,j++)
{
db->srom[i] = ior(db, j);
}
/*SetNodeAddress*/
for (i=0; i<6; i++)
dev->dev_addr[i] = db->srom[i];
retval = register_netdev(dev);
if (retval == 0) {
/*now,printoutthecardinfo,inashortformat..*/
printk("%s:
at%#lxIRQ%d\n",
dev->name, dev->base_addr, dev->irq);
if (dev->dma !
= (unsigned char)-1)
printk("DMA%d\n", dev->dma);
if (!
is_valid_ether_addr(dev->dev_addr)) {
printk("%s:
InvalidethernetMACaddress.Please"
"setusingifconfig\n", dev->name);
} else {
/*PrinttheEthernetaddress*/
printk("%s:
Ethernetaddr:
", dev->name);
for (i = 0; i < 5; i++)
printk("%2.2x:
", dev->dev_addr[i]);
printk("%2.2x\n", dev->dev_addr[5]);
}
}
return 0;
}
函数首先调用PUTB来写dm9000a芯片,来看看PUTB的实现
#define PUTB(d,a) *((volatile unsigned char *) (a)) = d
可见,PUTB是直接使用的指针,而没有使用内核提供的write等函数,同样,GETB函数如下
#define GETB(a) *((volatile unsigned char *) (a))
注意,这里的地址都是虚拟地址,因为前面调用函数dm9k_probe时传递的addr时重新映射后的,而不是直接传送的物理地址。
具体操作涉及到dm9000a的硬件实现,做简单的说明。
dm9000a有两个PORT,一个是INDEXPORT,另一个就是DATAPORT。
具体访问哪一个是根据CMD引脚的信号来确定的:
CMD为0,则访问INDEX,否则,访问DATA。
访问寄存器之前,必须将寄存器的地址存放在INDEXPORT。
首先,驱动程序需要读芯片的ID。
DM9000A的ID存放在四个不同的字节中,分别叫做VendorID和ProductID。
将着四个字节读出来,组合后应该得到0x90000A46,如果读出来的ID与该值不相等,说明不是DM9000A网卡,程序将返回,初始化失败。
读出ID相同后,就可以认为系统中存在dm9000a网卡了,接下来就开始进行其他初始化工作。
主要工作
dev->base_addr = addr;
dev->irq = IRQ_EINT2;
dev->open = &dmfe_open;
dev->hard_start_xmit = &dmfe_start_xmit;
dev->watchdog_timeo = HZ;
dev->tx_timeout = dmfe_timeout;
dev->stop = &dmfe_stop;
dev->get_stats = &dmfe_get_stats;
dev->set_multicast_list = &dm9000_hash_table;
dev->do_ioctl = &dmfe_do_ioctl;
就是为net_device的成员指定功能函数,以便系统需要的时候进行调用。
完成这些基本的工作后,就可以向系统注册设备了
retval = register_netdev(dev);
注册完成,该函数就返回。
probe函数剩下的就是对返回值的判断了,若注册成功,直接推出,probe完成;失败的话,还需要将ioremap过的地方ioumap掉,request_mem_region的地方release掉。
前面分析了dm9000a网卡的probe部分,接下来继续其他部分。
当用户在命令行下使用ifconfig等命令的时候,网卡设备将打开,系统将调用open函数。
dm9000a的open函数如下
static int dmfe_open(struct net_device *dev)
{
board_info_t *db = (board_info_t *)dev->priv;
u8reg_nsr;
int i;
if (request_irq(dev->irq,&dmfe_interrupt,IRQF_SHARED,dev->name,dev))
return -EAGAIN;
/*GrabtheIRQ*/
set_irq_type(dev->irq, IRQ_TYPE_EDGE_RISING);
/*InitilizeDM910Xboard*/
dmfe_init_dm9000(dev);
/*Initdrivervariable*/
db->reset_counter = 0;
db->reset_tx_timeout = 0;
db->cont_rx_pkt_cnt = 0;
/*checklinkstateandmediaspeed*/
db->Speed =10;
i=0;
do {
reg_nsr = ior(db,0x1);
if(reg_nsr & 0x40) /*linkOK!
!
*/
{
/*waitfordetectedSpeed*/
mdelay(200);
reg_nsr = ior(db,0x1);
if(reg_nsr & 0x80)
db->Speed =10;
else
db->Speed =100;
break;
}
i++;
mdelay
(1);
}while(i<3000); /*wait3second*/
//printk("i=%dSpeed=%d\n",i,db->Speed);
/*setandactiveatimerprocess*/
init_timer(&db->timer);
db->timer.expires = DMFE_TIMER_WUT * 2;
db->timer.data = (unsigned long)dev;
db->timer.function = &dmfe_timer;
add_timer(&db->timer); //MovetoDM9000initiallizationwasfinished.
netif_start_queue(dev);
return 0;
}
函数首先向系统申请中断,利用内核提供的request_irq函数。
该函数声明于include/linux/interrupt.h中,而它的定于位于kernel/irq/manage.c中。
关于它的说明和原型如下
/**
* request_irq-allocateaninterruptline
* @irq:
Interruptlinetoallocate
* @handler:
FunctiontobecalledwhentheIRQoccurs
* @irqflags:
Interrupttypeflags
* @devname:
Anasciinamefortheclaimingdevice
* @dev_id:
Acookiepassedbacktothehandlerfunction
*
* Thiscallallocatesinterruptresourcesandenablesthe
* interruptlineandIRQhandling.Fromthepointthis