本文主要是介绍(一)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相同。
*/
{
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,这两组可编程并且大小相同。如下图所示:
然后再让我们来看一看dm9000与cpu的连接情况:
我们以实例来分析一下,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分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!