(一)mini2440网卡驱动dm9000之dm9000_probe分析

2023-10-15 07:18

本文主要是介绍(一)mini2440网卡驱动dm9000之dm9000_probe分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


2012-04-07 02:05:48|  分类: 跟着国嵌学arm|举报|字号 订阅

/*首先我们必须知道probe函数什么时候调用。其实在平台设备驱动注册的时候,内核会在平台设备总线上去遍历所有的设备,做匹配操作,匹配函数为platform_match(内核源码在driver/base/platform.c中)那么这个函数做了什么呢,不妨来看一下:

static int platform_match(struct device *dev, struct device_driver *drv)

{

struct platform_device *pdev;


pdev = container_of(dev, struct platform_device, dev);//获取platform_device指针

return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);

}

由最后一句可以看出,当pdev->name与drv->name名字相同的时候,匹配成功。这就提醒我们,在编写平台设备驱动的时候,一定要保证设备的name和驱动的name相同。

*/

static int __devinit dm9000_probe(struct platform_device *pdev)
{
 struct dm9000_plat_data *pdata = pdev->dev.platform_data;/*关于这个结构体请见注释一*/

 struct board_info *db; /* 指向一个单板信息结构体,具体关于这个结构体的具体信息请见注释二*/
 struct net_device *ndev; //网络设备结构体
 const unsigned char *mac_src;//硬件资源
 int ret = 0;
 int iosize;
 int i;
 u32 id_val;

# if defined(CONFIG_ARCH_S3C2410)
 unsigned int oldval_bwscon = *(volatile unsigned int *)S3C2410_BWSCON;/*保存位宽等待寄存器??*/
 unsigned int oldval_bankcon4 = *(volatile unsigned int *)S3C2410_BANKCON4;/*保存控制寄存器??*/
# endif

 /* Init network device */
 ndev = alloc_etherdev(sizeof(struct board_info));/*用于分配一个struct net_device结构体,成功则返回指向struct net_device结构体

                                                                               的指针,否则返回NULL*/
 if (!ndev) {
  dev_err(&pdev->dev, "could not allocate device.\n");
  return -ENOMEM;
 }    //出错处理


/*SET_NETDEV_DEV定义在netdevice.h,其原型为:#define SET_NETDEV_DEV(net, pdev) ((net)->dev.parent = (pdev))*/

/*从上面可以看出,这行代码的实际上等于:ndev->dev.parent=&pdev->dev,关于这个问题,我百思不得其解,因为在net_device结构体中,根本就不存在一个名为dev的struct device结构体,那么我的理解就是,不存在就不写,直接来个ndev->pdev->dev,这样就将挂载在平台设备总线上的设备交给了网络设备,也就是说,这时的网路设备就是挂载在平台设备驱动上的设备。
  */ 

 SET_NETDEV_DEV(ndev, &pdev->dev);

 /*定义在device.h,详见注释三*/
 dev_dbg(&pdev->dev, "dm9000_probe()\n");

#if defined(CONFIG_ARCH_S3C2410)
 *((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

 /*下面都是设置board_info结构体*/
 
 db = netdev_priv(ndev);//获取网络设备私有信息指针
 memset(db, 0, sizeof(*db));//清零

 db->dev = &pdev->dev;//是哪一个父设备????????????????????????????
 db->ndev = ndev;//是哪一个网络设备??????????????????????????????

 spin_lock_init(&db->lock);/*初始化自旋锁,并把自旋锁的lock_raw_lock置1(未锁)详见注释四*/
 mutex_init(&db->addr_lock);/*这个函数定义在include\linux\mutex.h中,用于初始化互斥锁*/

 /*用于初始化一个任务,之后如果希望调用dm9000_poll_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;
  goto out;
 }

 iosize = res_size(db->addr_res);//计算资源大小
 /*request_mem_region用于申请I\O内存,申请了I\O内存之后还需要ioremap来映射到内存空间,以便操作,详见注释四*/
 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;
  goto out;
 }
 /*设的 I/O 端口的物理地址就被映射到内存地址空间中,这个内存空间就是上面用request_mem_region申请的空间*/
 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;
  goto out;
 }

 iosize = res_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;
  goto out;
 }

 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;
  goto out;
 }
 
 /* 设置结构体board_info结束 */

 /*填充net_device 结构体*/
 ndev->base_addr = (unsigned long)db->io_addr;//设置网络设备地址
 ndev->irq = db->irq_res->start;//设置网络设备中断资源地址

 /* ensure at least we have a default set of IO routines */
/* dm9000_set_io  根据芯片的外部总线带宽,指定对应的输入输出函数*/
 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
   * default IO 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;

  db->flags = pdata->flags;
 }

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
 db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif

 dm9000_reset(db);/*复位DM9000芯片*/

 /* try multiple times, DM9000 sometimes gets the read wrong */
 /*读一下生产商和制造商的ID,应该是0x90000A46。 关键函数:ior()*/
 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;
  goto out;
 }

 /* Identify what type of DM9000 we are working on */
 /*确认DM9000类型*/

 id_val = ior(db, DM9000_CHIPR);
 dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);

 switch (id_val) {
 case CHIPR_DM9000A:
  db->type = TYPE_DM9000A;
  break;
 case CHIPR_DM9000B:
  db->type = TYPE_DM9000B;
  break;
 default:
  dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
  db->type = TYPE_DM9000E;
 }

 /* from this point we assume that we have found a DM9000 */

 /* driver system function */
 /* 借助ether_setup()函数来部分初始化ndev。因为对以太
    网设备来讲,很多操作与属性是固定的,内核可以帮助完成*/

 ether_setup(ndev);

 /*手动初始化ndev的ops和db的mii部分*/
 ndev->open   = &dm9000_open;
 ndev->hard_start_xmit    = &dm9000_start_xmit;
 ndev->tx_timeout         = &dm9000_timeout;
 ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
 ndev->stop   = &dm9000_stop;
 ndev->set_multicast_list = &dm9000_hash_table;
 ndev->ethtool_ops  = &dm9000_ethtool_ops;
 ndev->do_ioctl   = &dm9000_ioctl;

#ifdef CONFIG_NET_POLL_CONTROLLER
 ndev->poll_controller  = &dm9000_poll_controller;
#endif

 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;

#if defined(CONFIG_ARCH_S3C2410)
 printk("Now use the default MAC address: 08:90:90:90:90:90\n");
 mac_src = "friendly-arm";
 ndev->dev_addr[0] = 0x08;
 ndev->dev_addr[1] = 0x90;
 ndev->dev_addr[2] = 0x90;
 ndev->dev_addr[3] = 0x90;
 ndev->dev_addr[4] = 0x90;
 ndev->dev_addr[5] = 0x90;
#else
 mac_src = "eeprom";

 /* try reading the node address from the attached EEPROM */
/* 
  (如果有的话)从EEPROM中读取节点地址。这里可以看到mini2440这个板子
  上没有为DM9000外挂EEPROM,所以读取出来的全部是0xff。
  见函数dm9000_read_eeprom。 关于外挂EEPROM,可以参考datasheet上的7.EEPROM Format一节
*/

 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);
 }

 if (!is_valid_ether_addr(ndev->dev_addr))
  dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
    "set using ifconfig\n", ndev->name);
#endif

/*
 很显然ndev是我们在probe函数中定义的局部变量,如果我想在其他
 地方使用它怎么办呢? 这就需要把它保存起来。内核提供了这个方
 法,使用函数platform_set_drvdata()可以将ndev保存成平台总线设备的
 私有数据。以后再要使用它时只需调用platform_get_drvdata()就可以了
*/

 platform_set_drvdata(pdev, ndev);
 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:
#if defined(CONFIG_ARCH_S3C2410)
    *(volatile unsigned int *)S3C2410_BWSCON   = oldval_bwscon;
    *(volatile unsigned int *)S3C2410_BANKCON4 = oldval_bankcon4;
#endif
 dev_err(db->dev, "not found (%d).\n", ret);

 dm9000_release_board(pdev, db);
 free_netdev(ndev);

 return ret;
}

 

注释一:

struct dm9000_plat_data {

unsigned int flags;

unsigned char dev_addr[6];


/* allow replacement IO routines */


void (*inblk)(void __iomem *reg, void *data, int len);

void (*outblk)(void __iomem *reg, void *data, int len);

void (*dumpblk)(void __iomem *reg, int len);

};

注释二:(定义在dm9000.c中)

typedef struct board_info {


void __iomem *io_addr; /* Register I/O base address */

void __iomem *io_data; /* Data I/O address */

u16  irq; /* IRQ */


u16 tx_pkt_cnt;

u16 queue_pkt_len;

u16 queue_start_addr;

u16 dbug_cnt;

u8 io_mode; /* 0:word, 2:byte */

u8 phy_addr;

u8 imr_all;


unsigned int flags;

unsigned int in_suspend :1;

int debug_level;


enum dm9000_type type;


void (*inblk)(void __iomem *port, void *data, int length);

void (*outblk)(void __iomem *port, void *data, int length);

void (*dumpblk)(void __iomem *port, int length);


struct device *dev;      /* parent device */


struct resource *addr_res;   /* resources found */

struct resource *data_res;

struct resource *addr_req;   /* resources requested */

struct resource *data_req;

struct resource *irq_res;


struct mutex  addr_lock; /* phy and eeprom access lock */


struct delayed_work phy_poll;

struct net_device  *ndev;


spinlock_t lock;


struct mii_if_info mii;

u32 msg_enable;

} board_info_t;

注释三:这个函数的实质是调用 printk(KERN_DEBUG )来输出打印信息
  可以参考:http://www.cnblogs.com/cute/archive/2011/05/16/2047868.html

注释四:自旋锁http://blog.csdn.net/unbutun/article/details/5730068
 
注释五:http://hi.baidu.com/312860519/blog/item/78a82b60695ba75beaf8f870.html

注释六:http://www.cnblogs.com/cute/archive/2011/03/23/1992651.html

注释七:首先我们来规范两个概念和一个事实。这两个概念就是io端口和io内存,当一个寄存器或内存位于io空间是,称为io端口,而当他们处于内核空间时,我们称之为io内存。一个事实就是ARM处理器只支持内存空间。好,接下来我们正式分析:

我们知道s3c2440将系统的存储空间分为八组,共1G。bank0--bank5的开始地址是固定的,用于ROM和SRAM。band6--bank7用于ROM、SRAM、SDRAM,这两组可编程并且大小相同。如下图所示:

(一)mini2440网卡驱动dm9000之dm9000_probe分析 - 航天 - 航天的博客
 

 然后再让我们来看一看dm9000与cpu的连接情况:

(一)mini2440网卡驱动dm9000之dm9000_probe分析 - 航天 - 航天的博客
从dm9000的连接图上我们看出,使用的片选信号是NGCS4(低电平有效),也就是说我们的网卡是挂在cpu的bank4空间的 ,bank4的基地址是:0x20000000。如果是物理地址直接寻址的话,很简单,就是发送地址,然后读或者写。但是linux系统只能访问内存空间,不能访问io空间,所以我们要将物理地址映射成内存空间的虚拟地址才能访问,ioremap就完成了这一工作。

我们以实例来分析一下,db->io_data= ioremap(db->data_res->start, iosize);其中db->data_res->start为0x20000000。这行代码的意思就是,将db->data_res->start开始,iosize大小的io空间映射到了内核空间,现在内核空间db->io_addr----db->io_addr之间的空间就相当于外设存储空间,之后才可以通过这些虚拟地址对io内存空间进行访问,但i要注意的是,现在对io内存的访问只能使用readb,writeb此类的函数,来进行。(关于这个问题目前也只是明白到这个地步,也不知到底对不对,今后还会更深一步分析)

小结:(1)申请并初始化一个net_device结构体,用到alloc_etherdev(sizeof(struct board_info))

           (2)使net_device与platform_device建立联系,用到SET_NETDEV_DEV

           (3)设置board_info结构体。这一步中,最主要的是将I\O内存映射到内存空间,以便操作。用到

                     request_mem_region和ioremap,前者用来申请I\O内存,后者用于将申请到的I\O内存映射到

                     内存空间。

            (4)设置net_device 结构体。初始化net_device结构体可分为用ether_setup()函数来部分初始化和手动初始化两步。

            (5)网络设备注册。使用函数register_netdev(ndev)。

这篇关于(一)mini2440网卡驱动dm9000之dm9000_probe分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

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

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