函数平台底层之旅——DM9000网卡驱动源码分析
这段时间朋友几篇文章介绍了改函数平台的文章. 关联文章的地址
将近有一年多没写博客了。。。虽然网上有很多分析DM9000卡网动驱的,但是本文是基于Linux-2,6.32的,虽然动驱源码都差不多一样,不过,还是有点区分的。。。
Linux核内中,DM9000卡网采取平台设备动驱行进设备与动驱的分离。以下先分析DM9000的卡网动驱,然后再行进DM9000平台设备的册注。本文采取的是Linux-2.6.32核内。
分析动驱应从动驱册注开始,在Linux-2.6以上的核内中,核内模块必须调用宏module_init()和module_exit()去册注初始化和退出函数。
module_init(dm9000_init);
module_exit(dm9000_cleanup);
通过module_init(dm9000_init)的指定,dm9000_init即为DM9000动驱口入函数。Linux核内模块加载函数一般以__init标识明声。
static int __init dm9000_init(void)
{
#if defined(CONFIG_ARCH_S3C2410)
unsigned int oldval_bwscon = *(volatileunsigned int *)S3C2410_BWSCON;
unsigned int oldval_bankcon4 = *(volatileunsigned int *)S3C2410_BANKCON4;
*((volatile unsigned int*)S3C2410_BWSCON) =
(oldval_bwscon& ~(3<<16)) | S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 |S3C2410_BWSCON_ST4;
*((volatile unsigned int*)S3C2410_BANKCON4) = 0x1f7c;
#endif
printk(KERN_INFO "%s EthernetDriver, V%s\n", CARDNAME, DRV_VERSION);
/*平台册注函数 */
return platform_driver_register(&dm9000_driver);
}
通过platform_driver_register函数将dm9000_driver册注为平台设备动驱。
static structplatform_driver dm9000_driver = {
.driver ={
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
},
/*探测函数,用于探测平台设备数据*/
.probe = dm9000_probe,
.remove =__devexit_p(dm9000_drv_remove),
};
在平台设备动驱中,平台设备的源资通过dm9000_probe函数行进参数传递。
在dm9000_probe(struct platform_device *pdev)函数中,参数*pdev是即板级平台(arch/arm/mach-xxxx.c)传递过去的。
/* Search DM9000 board, allocate space andregister it */
static int __devinit dm9000_probe(structplatform_device *pdev)
{
/*获得平台数据指针,将其赋值给pdata*/
struct dm9000_plat_data *pdata =pdev->dev.platform_data;
/*在动驱中需重新定义一个DM9000关相的结构体,该结构体即有属性成员,同时也有函数指针。其实有点相似面向对象程序中的类*/
structboard_info *db; /* Point a boardinformation structure */
structnet_device *ndev;
constunsigned char *mac_src;
int ret =0;
intiosize;
int i;
u32id_val;
/* Init network device */
/*alloc_etherdev()函数是alloc_netdev_mq()针对以太网的“快捷”函数。该函数将生成用户可见的“eth0、eth1、ethx、……*/
ndev =alloc_etherdev(sizeof(struct board_info));
if (!ndev){
dev_err(&pdev->dev,"could not allocate device.\n");
return-ENOMEM;
}
SET_NETDEV_DEV(ndev,&pdev->dev);
dev_dbg(&pdev->dev,"dm9000_probe()\n");
/* setup board info structure */
db =netdev_priv(ndev);
db->dev= &pdev->dev;
db->ndev= ndev;
spin_lock_init(&db->lock);
mutex_init(&db->addr_lock);
/*初始化任务队列并定绑处理函数dm9000_poll_work,INIT_DELAYED_WORK宏初始化的任务,须要schedule_delayed_work 宏来度调行运。*/
INIT_DELAYED_WORK(&db->phy_poll,dm9000_poll_work);
/*获得平台设备存内源资和中断源资*/
db->addr_res= platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res= platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ,0);
if(db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev,"insufficient resources\n");
ret= -ENOENT;
gotoout;
}
iosize =resource_size(db->addr_res);
db->addr_req= request_mem_region(db->addr_res->start, iosize,
pdev->name);
if(db->addr_req == NULL) {
dev_err(db->dev,"cannot claim address reg area\n");
ret= -EIO;
gotoout;
}
db->io_addr= ioremap(db->addr_res->start, iosize);
if(db->io_addr == NULL) {
dev_err(db->dev,"failed to ioremap address reg\n");
ret= -EINVAL;
gotoout;
}
iosize = resource_size(db->data_res);
db->data_req= request_mem_region(db->data_res->start, iosize,
pdev->name);
if(db->data_req == NULL) {
dev_err(db->dev,"cannot claim data reg area\n");
ret= -EIO;
gotoout;
}
db->io_data= ioremap(db->data_res->start, iosize);
if(db->io_data == NULL) {
dev_err(db->dev,"failed to ioremap data reg\n");
ret= -EINVAL;
gotoout;
}
/* fill in parameters for net-dev structure */
/*置设络网口接的I/O基地址,ifconfig命令可表现或修改当前值*/
ndev->base_addr= (unsigned long)db->io_addr;
/*被付与的中断号,在列出口接时,ifconfig命令将打印dev->irq的值*/
ndev->irq = db->irq_res->start;
/* ensure at least we have a default set of IO routines */
dm9000_set_io(db,iosize);
/* check to see if anything is being over-ridden */
if (pdata!= NULL) {
/* check to see if the driver wants to over-ride the defaultIO width */
if(pdata->flags & DM9000_PLATF_8BITONLY)
dm9000_set_io(db,1);
if(pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db,2);
if(pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db,4);
/* check to see if there are any IO routine over-rides */
if(pdata->inblk != NULL)
db->inblk= pdata->inblk;
if(pdata->outblk != NULL)
db->outblk= pdata->outblk;
if(pdata->dumpblk != NULL)
db->dumpblk= pdata->dumpblk;
/*置设络网设备特有的flags参数*/
db->flags= pdata->flags;
}
#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags|= DM9000_PLATF_SIMPLE_PHY;
#endif
dm9000_reset(db);
/* try multiple times, DM9000 sometimes gets the read wrong*/
/*重复取读DM9000的ID号*/
for (i =0; i < 8; i++) {
id_val = ior(db, DM9000_VIDL);
id_val|= (u32)ior(db, DM9000_VIDH) << 8;
id_val|= (u32)ior(db, DM9000_PIDL) << 16;
id_val|= (u32)ior(db, DM9000_PIDH) << 24;
if(id_val == DM9000_ID)
break;
dev_err(db->dev,"read wrong id 0x%08x\n", id_val);
}
if (id_val!= DM9000_ID) {
dev_err(db->dev,"wrong id: 0x%08x\n", id_val);
ret= -ENODEV;
gotoout;
}
/* Identify what type of DM9000 we are working on */
id_val =ior(db, DM9000_CHIPR);
dev_dbg(db->dev,"dm9000 revision 0x%02x\n", id_val);
switch(id_val) {
caseCHIPR_DM9000A:
db->type= TYPE_DM9000A;
break;
caseCHIPR_DM9000B:
db->type= TYPE_DM9000B;
break;
default:
dev_dbg(db->dev,"ID %02x => defaulting to DM9000E\n", id_val);
db->type= TYPE_DM9000E;
}
/* dm9000a/b are capable of hardware checksum offload */
if(db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {
db->can_csum= 1;
db->rx_csum= 1;
ndev->features|= NETIF_F_IP_CSUM;
}
/* from this point we assume that we have found a DM9000 */
/* driver system function */
/*ether_setup()是由Linux核内供提的一个对以太网设备net_device结构体中公有成员速快赋值的函数。因为对以太网设备来讲,很多作操与属性是牢固的*/
/*充填DM9000的作操函数*/
ndev->netdev_ops = &dm9000_netdev_ops;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->ethtool_ops = &dm9000_ethtool_ops;
db->msg_enable = NETIF_MSG_LINK;
db->mii.phy_id_mask = 0x1f;
db->mii.reg_num_mask= 0x1f;
db->mii.force_media = 0;
db->mii.full_duplex = 0;
db->mii.dev = ndev;
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write;
mac_src ="eeprom";
/* try reading the node address from the attached EEPROM */
for (i =0; i < 6; i += 2)
dm9000_read_eeprom(db,i / 2, ndev->dev_addr+i);
if(!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src= "platform data";
memcpy(ndev->dev_addr,pdata->dev_addr, 6);
}
if(!is_valid_ether_addr(ndev->dev_addr)) {
/* try reading from mac */
mac_src= "chip";
for(i = 0; i < 6; i++)
ndev->dev_addr[i]= ior(db, i+DM9000_PAR);
}
memcpy(ndev->dev_addr,"\x08\x90\x90\x90\x90\x90", 6);
if(!is_valid_ether_addr(ndev->dev_addr))
dev_warn(db->dev,"%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
platform_set_drvdata(pdev,ndev);
/*将ndev册注到Linux络网子系统中*/
ret =register_netdev(ndev);
if (ret ==0)
printk(KERN_INFO"%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
ndev->name,dm9000_type_to_char(db->type),
db->io_addr, db->io_data,ndev->irq,
ndev->dev_addr, mac_src);
return 0;
out:
dev_err(db->dev,"not found (%d).\n", ret);
dm9000_release_board(pdev,db);
free_netdev(ndev);
returnret;
}
在dm9000_probe()函数中,为ndev充填了理管作操结构体ndev->netdev_ops
= &dm9000_netdev_ops和ndev->ethtool_ops =&dm9000_ethtool_ops。.ndo_open等为函数指针,将函数名dm9000_open赋给它,可即调用该dm9000_open函数。
static const struct net_device_ops dm9000_netdev_ops = {
.ndo_open = dm9000_open,
.ndo_stop = dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_tx_timeout = dm9000_timeout,
.ndo_set_multicast_list = dm9000_hash_table,
.ndo_do_ioctl = dm9000_ioctl,
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = dm9000_poll_controller,
#endif
};
static const struct ethtool_ops dm9000_ethtool_ops = {
.get_drvinfo = dm9000_get_drvinfo,
.get_settings = dm9000_get_settings,
.set_settings = dm9000_set_settings,
.get_msglevel = dm9000_get_msglevel,
.set_msglevel = dm9000_set_msglevel,
.nway_reset = dm9000_nway_reset,
.get_link = dm9000_get_link,
.get_eeprom_len = dm9000_get_eeprom_len,
.get_eeprom =dm9000_get_eeprom,
.set_eeprom =dm9000_set_eeprom,
.get_rx_csum = dm9000_get_rx_csum,
.set_rx_csum = dm9000_set_rx_csum,
.get_tx_csum = ethtool_op_get_tx_csum,
.set_tx_csum = dm9000_set_tx_csum,
};
DM9000成完动驱的册注及初始化以后,要使用它就必须要打开它。即当在Linux终端下输入命令ifconfig时,可即调用dm9000_open函数。
/*
* Openthe interface.
* Theinterface is opened whenever "ifconfig" actives it.
*/
static int dm9000_open(structnet_device *dev)
{
/*先获得设备私有指针*/
board_info_t *db = netdev_priv(dev);
unsignedlong irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
if(netif_msg_ifup(db))
dev_dbg(db->dev,"enabling %s\n", dev->name);
/* If there is no IRQ type specified, default to somethingthat
* may work, and tell the user that this is aproblem */
if(irqflags == IRQF_TRIGGER_NONE)
dev_warn(db->dev,"WARNING: no IRQ resource flags set.\n");
irqflags|= IRQF_SHARED;
/*请申中断处理函数,收接数据采取中断式方收接,中断处理函数dm9000_interrupt*/
if(request_irq(dev->irq, &dm9000_interrupt,irqflags, dev->name, dev))
return-EAGAIN;
/* Initialize DM9000 board */
dm9000_reset(db);
dm9000_init_dm9000(dev);
/* Init driver variable */
db->dbug_cnt= 0;
/*mii_check_media函数主要是检查络网的连接态状,当现出络网连接或断开时,表现“link up…、linkdown”等信息*/
mii_check_media(&db->mii,netif_msg_link(db), 1);
/*激活发送队列*/
netif_start_queue(dev);
/*度调任务队列中的worker行进任务,if (db->type == TYPE_DM9000E)*/
dm9000_schedule_poll(db);
return 0;
}
在打开函数中,请申中断处理函数用于收接数据,激活发送队列用于发送数据,一切准备就绪了。。。
DM9000动驱程序采取的是中断式方。发触中断的机会发生在:(1)DM9000收接到一个数据包以后;(2)DM9000发送完一个数据包以后。
static irqreturn_t dm9000_interrupt(int irq, void*dev_id)
{
structnet_device *dev = dev_id;
board_info_t*db = netdev_priv(dev);
intint_status;
unsignedlong flags;
u8reg_save;
dm9000_dbg(db,3, "entering %s\n", __func__);
/* A real interrupt coming */
/* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock,flags);
/* Save previous register address */
reg_save =readb(db->io_addr);
/* Disable all interrupts */
iow(db,DM9000_IMR, IMR_PAR);
/* Got DM9000 interrupt status */
int_status= ior(db, DM9000_ISR); /* Got ISR */
iow(db,DM9000_ISR, int_status); /* Clear ISR status */
if(netif_msg_intr(db))
dev_dbg(db->dev,"interrupt status %02x\n", int_status);
/* Received the coming packet */
if(int_status & ISR_PRS)
dm9000_rx(dev);
/* Trnasmit Interrupt check */
if(int_status & ISR_PTS)
dm9000_tx_done(dev,db);
if(db->type != TYPE_DM9000E) {
if(int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll,1);
}
}
/* Re-enable interrupt mask */
iow(db,DM9000_IMR, db->imr_all);
/* Restore previous register address */
writeb(reg_save,db->io_addr);
spin_unlock_irqrestore(&db->lock,flags);
returnIRQ_HANDLED;
}
1. 数据发送流程
/*
*Hardware start transmission. Send a packet to media from the upper layer.
*/
static int dm9000_start_xmit(struct sk_buff *skb,struct net_device *dev)
{
unsignedlong flags;
board_info_t*db = netdev_priv(dev);
dm9000_dbg(db,3, "%s:\n", __func__);
if(db->tx_pkt_cnt > 1)
returnNETDEV_TX_BUSY;
spin_lock_irqsave(&db->lock,flags);
/* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD,db->io_addr);
(db->outblk)(db->io_data,skb->data, skb->len);
dev->stats.tx_bytes+= skb->len;
db->tx_pkt_cnt++;
/* TX control: First packet immediately send, second packetqueue */
if(db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev,skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len= skb->len;
db->queue_ip_summed= skb->ip_summed;
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock,flags);
/* free this SKB */
dev_kfree_skb(skb);
returnNETDEV_TX_OK;
}
文章结束给大家分享下程序员的一些笑话语录:
程序员打油诗
写字楼里写字间,写字间里程序员;
程序人员写程序,又拿程序换酒钱。
酒醒只在网上坐,酒醉还来网下眠;
酒醉酒醒日复日,网上网下年复年。
但愿老死电脑间,不愿鞠躬老板前;
奔驰宝马贵者趣,公交自行程序员。
别人笑我忒疯癫,我笑自己命太贱;
不见满街漂亮妹,哪个归得程序员。