底层之旅——DM9000网卡驱动源码分析

2023-10-07 05:58

本文主要是介绍底层之旅——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;

}

 

这篇关于底层之旅——DM9000网卡驱动源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/156476

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud