(一)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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值