底层之旅——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()针对以太网的“快捷”函数。该函数将生成用户可见的“eth0eth1ethx……*/

       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_workINIT_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*/

       /*重复读取DM9000ID*/

       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结构体中公有成员快速赋值的函数。因为对以太网设备来讲,很多操作与属性是固定的*/

       ether_setup(ndev);

       /*填充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;

}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值