书接上回,在介绍dm9000_driver三条支线中的两条.driver 和.remove之后,接下来的两篇博文主要介绍.probe。不是吧,前面说什么还有朋友不知所云?请先看回本系列第一篇博文,熟悉这个网卡驱动程序的构成框架再说。(http://blog.csdn.net/jarvis_xian/archive/2011/06/10/6537446.aspx)
先贴出.probe函数如下
static int __devinit dm9000_probe(struct platform_device *pdev) { struct dm9000_plat_data *pdata = pdev->dev.platform_data; struct board_info *db; //board_info结构体,用来保存芯片相关的一些私有信息。 struct net_device *ndev; //定义网络设备ndev结构体指针 const unsigned char *mac_src; int ret = 0; int iosize; int i; u32 id_val; /* Init network device */ 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); //令pdev->dev成为ndev的父节点 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); //初始化互斥信号量 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; } db->irq_wake = platform_get_irq(pdev, 1); //申请中断 if (db->irq_wake >= 0) { dev_dbg(db->dev, "wakeup irq %d/n", db->irq_wake); ret = request_irq(db->irq_wake, dm9000_wol_interrupt, IRQF_SHARED, dev_name(db->dev), ndev); if (ret) { dev_err(db->dev, "cannot get wakeup irq (%d)/n", ret); } else { /* test to see if irq is really wakeup capable */ ret = set_irq_wake(db->irq_wake, 1); if (ret) { dev_err(db->dev, "irq %d cannot set wakeup (%d)/n", db->irq_wake, ret); ret = 0; } else { set_irq_wake(db->irq_wake, 0); db->wake_supported = 1; } } } 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; goto out; } 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 = 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; 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; } /* fill in parameters for net-dev structure */ 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(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); //设备复位 /* try multiple times, DM9000 sometimes gets the read wrong */ for (i = 0; i < 8; i++) { //读芯片id,看是否匹配 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) { //若尝试8次过后,id还没匹配,则设备不是DM9000 dev_err(db->dev, "wrong id: 0x%08x/n", id_val); ret = -ENODEV; goto out; } /* Identify what type of DM9000 we are working on */ id_val = ior(db, DM9000_CHIPR); //得出DM9000的类型 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; } /* 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; } //执行到这里为止,我们应当已经找到了网卡DM9000 /* from this point we assume that we have found a DM9000 */ /* driver system function */ ether_setup(ndev); 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); } 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); 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); return ret; }
主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev注册这个网络设备。
1) 首先定义了几个局部变量:
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
2) 初始化一个网络设备。关键系统函数:alloc_etherdev()
3) 获得资源信息并将其保存在board_info变量db中。关键系统函数:netdev_priv(),
platform_get_resource()
4) 根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db中,并且填充ndev中的参
数。 关键系统函数:request_mem_region(), ioremap()。 自定义函数:dm9000_set_io()
5) 完成了第4步以后,回顾一下db和ndev中都有了什么:
struct board_info *db:
addr_res -- 地址资源
data_res -- 数据资源
irq_res -- 中断资源
addr_req -- 分配的地址内存资源
io_addr -- 寄存器I/O基地址
data_req -- 分配的数据内存资源
io_data -- 数据I/O基地址
dumpblk -- IO模式
outblk -- IO模式
inblk -- IO模式
lock -- 自旋锁(已经被初始化)
addr_lock -- 互斥锁(已经被初始化)
struct net_device *ndev:
base_addr -- 设备IO地址
irq -- 设备IRQ号
6) 设备复位。硬件操作函数dm9000_reset()
7) 读一下生产商和制造商的ID,应该是0x9000 0A46。 关键函数:ior()
8) 读一下芯片类型。
========以上步骤结束后我们可以认为已经找到了DM9000========
9) 借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内
核可以帮助完成。
10) 手动初始化ndev的ops和db的mii部分。
11) (如果有的话)从EEPROM中读取节点地址。这里可以看到mini2440这个板子上没有为DM9000外
挂EEPROM,所以读取出来的全部是0xff。见函数dm9000_read_eeprom。 关于外挂EEPROM,
可以参考datasheet上的7.EEPROM Format一节。
12) 很显然ndev是我们在probe函数中定义的局部变量,如果我想在其他地方使用它怎么办呢? 这就需
要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata()可以将ndev保存成平台
总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata()就可以了。
13) 使用register_netdev()注册ndev。
(以上13点总结摘抄自ypoflyer大哥的文章)
注意到代码中最后两个重要的结构体dm9000_netdev_ops 和dm9000_ethtod_ops,将在之后的博文逐一介绍,时间关系,在此打住。
本系列课程链接地址
DM9000网卡驱动(1)http://blog.csdn.net/jarvis_xian/archive/2011/06/10/6537446.aspx
DM9000网卡驱动(2)http://blog.csdn.net/jarvis_xian/archive/2011/06/12/6539931.aspx
DM9000网卡驱动(3)http://blog.csdn.net/jarvis_xian/archive/2011/06/13/6542411.aspx
DM9000网卡驱动(4)http://blog.csdn.net/jarvis_xian/archive/2011/06/15/6545109.aspx
DM9000网卡驱动(5)http://blog.csdn.net/jarvis_xian/archive/2011/06/15/6547203.aspx