`
wodamazi
  • 浏览: 1416908 次
文章分类
社区版块
存档分类
最新评论

Linux2.6.39下dm9k驱动源码分析(一)

阅读更多

本文基于linux2.6.39内核

CPU:S3C2440

一、s3c2440和dm9k的电路连接如下图:

从上图可以看出dm9k引用了16条数据线(sd0-sd15)和s3c2440(ldata0-ldata15)相连,引用了一条地址线(CMD)和S3C2440(ADDR2)相连。CPU就是通过CMD这条地址线来判断LDATA0-LDATA15这16条数据线传送的究竟是地址还是数据的。dm9k的片选信号AEN(Address enable a low activie signal used to select the dm9k)位和S3C2440的LnGCS4(BANK4)相连,BANK4的访问地址[0x2000 0000 - 0x2800 0000)

在dm9k芯片手册的5.1节有(TXD[2:0] is also used as the strap of IO base address IO base = (strap pin TXD[2:0]) * 10h + 300h),而mini2440开发板中对于dm9k电路TXD[3:0]引脚都未接的所以得出IO base=300h 中断使用了EINT7(GPF7)

这些放在移植的时候来分析再好不过了 其实

2、dm9000.c源码分析

分析之前先看看驱动程序中几个重要的结构

/**************************************************************************************************************************************************************/

* dm9000_driver变量。是platform_driver结构体变量,其中包含了重要的:驱动的名字(内核中用来匹配的唯一标识)和几个重要操作函数。

static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000", //驱动名
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,//只想网卡的挂起和重启函数指针
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
};

/**************************************************************************************************************************************************************/

dm9000_netdev_ops变量。是net_device_ops结构体变量,其中定义了操作net_device的重要函数,我们在驱动程序中根据需要的操作要填充这些函数

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,//进行设备特定的IO控制
.ndo_change_mtu = eth_change_mtu,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,//设置MAC地址
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = dm9000_poll_controller,//采用轮询的方式接收数据
#endif
};

/**************************************************************************************************************************************************************/

* dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(有的驱动程序可能不支持ethtool)

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_wol = dm9000_get_wol,
.set_wol = dm9000_set_wol,
.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,
};

/**************************************************************************************************************************************************************/

* board_info结构体。用来保存芯片相关的一些信息的,差不多每个芯片级驱动里面都有一个类似的结构体

typedef struct board_info {

void __iomem *io_addr; /* Register I/O base address *///虚拟的通过IO映射的
void __iomem *io_data; /* Data I/O address */
u16 irq; /* IRQ */
/**********************************都通过映射后的**********************************************************************************************************/
u16 tx_pkt_cnt;
u16 queue_pkt_len;
u16 queue_start_addr;
u16 queue_ip_summed;
u16 dbug_cnt;
u8 io_mode; /* 0:word, 2:byte */
u8 phy_addr;
u8 imr_all;

unsigned int flags;
unsigned int in_suspend :1;
unsigned int wake_supported :1;
int debug_level;

enum dm9000_type type;
//IO模式
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; /**IO数据资源**/
struct resource *addr_req; /* 分配后的地址内存资源*/
struct resource *data_req; /* 分配后的数据资源*/
struct resource *irq_res; /**中断资源***/

int irq_wake;

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;
u32 wake_state;

int rx_csum;
int can_csum;
int ip_summed;
} board_info_t;

/**************************************************************************************************************************************************************/

2.1、 注册平台驱动。

将驱动添加到总线上,完成驱动和设备的匹配,并执行驱动的probe函数。

static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
};

static int __init dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);

return platform_driver_register(&dm9000_driver);
}

2.2、dm9000_probe探测函数的分析

static int __devinit dm9000_probe(struct platform_device *pdev)
{

//定义局部变量用来保存数据
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
struct board_info *db; /* Point a board information structure */
struct net_device *ndev;
const unsigned char *mac_src;
int ret = 0;
int iosize;
int i;
u32 id_val;
/**内核用net_device结构来描述一个网络设备并使用alloc_etherdev或者alloc_netdev函数来分配一个net_device结构
/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev) {
dev_err(&pdev->dev, "could not allocate device.\n");
return -ENOMEM;
}
//platform_device与net_device关联起来
SET_NETDEV_DEV(ndev, &pdev->dev);//通过这一步网络设备和平台设备即关联起来了

dev_dbg(&pdev->dev, "dm9000_probe()\n");

/* setup board info structure */

db = netdev_priv(ndev);/**获取net_device结构的私有成员保存到struct board_info *db中**/

db->dev = &pdev->dev;
db->ndev = ndev;

spin_lock_init(&db->lock);/**初始化自旋锁**/
mutex_init(&db->addr_lock);
//初始化延迟等待队列并传入dm9000_poll_work该函数将在设备被打开的时候被调度
INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
//获取平台资源?从哪里获取?
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//dm9k平台设备所所使用的IO地址资源
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);//dm9k平台设备所所使用的IO数据资源
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); //dm9k平台设备所所使用中断资源

if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
}
//获取dm9k平台设备所使用的中断号
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 = irq_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 {
irq_set_irq_wake(db->irq_wake, 0);
db->wake_supported = 1;
}
}
}
/*计算上面所获取到的IO地址平台资源的大小*/
iosize = resource_size(db->addr_res);
//为IO地址空间分配IO内存
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;
}
//在访问IO内存之前必须映射IO内存
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;//初始化IO基地址
ndev->irq = db->irq_res->start; //初始化irq

/**根据DM9000的数据位宽,初始化读写数据帧的函数指针初始化net_device给其结构中的成员变量和成员函数赋值*/
/* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize);
/*根据platform的定义(16bit),再次初始化读写数据帧的函数指针*/
/* 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 */
/****IOctl flag在驱动加载的时候被传入********/
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 */
/*检查看看是否有任何IO常规越权*/
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;
}

/**************************************************************************************************************************************

*到此为止总结下prob究竟做了些什么事,那些结构里面多了什么

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、获得dm9k所使用的平台资源并将其保存在board_info变量db中。关键系统函数:netdev_priv(), platform_get_resource()

4、根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db中,并且填充ndev中的参数。 关键系统函数:request_mem_region(), ioremap()。

resource_size(),自定义函数:dm9000_set_io(db, iosize);

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号

***************************************************************************************************************************************/

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif
//复位芯片
dm9000_reset(db);
//读取芯片ID号并判断是否为0x90000A46**/
/* try multiple times, DM9000 sometimes gets the read wrong */
for (i = 0; i < 8; i++) {
id_val = ior(db, DM9000_VIDL);//供应商ID低8位
id_val |= (u32)ior(db, DM9000_VIDH) << 8;//供应商ID高8位
id_val |= (u32)ior(db, DM9000_PIDL) << 16;//产品ID低8位
id_val |= (u32)ior(db, DM9000_PIDH) << 24;//产品ID高8位

if (id_val == DM9000_ID)//芯片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_CHIPR寄存器判断网卡类型
id_val = ior(db, DM9000_CHIPR);//读chip revision寄存器
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;
}

/* from this point we assume that we have found a DM9000 */
//初始化以太网ndev的部分成员
/* driver system function */
/**
*以太网设置
**/
ether_setup(ndev);

//手动初始化ndev的ops和db的mii部分。
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地址,
mac_src = "eeprom";
//从EEPROM中读取MAC
/* 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);
//判断MAC是否合法
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);

random_ether_addr(ndev->dev_addr);
mac_src = "random";
}


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;

/*****************************************************************************************************************************************

5、设备复位。硬件操作函数dm9000_reset()

6、 读一下生产商和制造商的ID,应该是0x9000 0A46。 关键函数:ior()

7、 读一下芯片类型。

========以上步骤结束后我们可以认为已经找到了DM9000========

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

9、手动初始化ndev的ops和db的mii部分。

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

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

12、使用register_netdev()注册ndev。

***************************************************************************************************************************************/
out:
dev_err(db->dev, "not found (%d).\n", ret);

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

return ret;
}








分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics