函数平台底层之旅——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;

    }

文章结束给大家分享下程序员的一些笑话语录: 程序员打油诗   
  写字楼里写字间,写字间里程序员;
  程序人员写程序,又拿程序换酒钱。
  酒醒只在网上坐,酒醉还来网下眠;
  酒醉酒醒日复日,网上网下年复年。
  但愿老死电脑间,不愿鞠躬老板前;
  奔驰宝马贵者趣,公交自行程序员。
  别人笑我忒疯癫,我笑自己命太贱;
  不见满街漂亮妹,哪个归得程序员。

原文地址:https://www.cnblogs.com/xinyuyuanm/p/3033646.html