dm9000驱动介绍

一. 硬件情况

DM9000在电路板上的连接中与编程相关的如下:
1)EECS拉高:16bit模式;
2)EECK拉高,INT连接到2440 EINT7:INT脚为低时为有效中断信号,中断线为EINT7
3)cs连接到2440的nGCS2,CMD连接2440地址总线ADDR[2]:INDEX和DATA端口地址分别为0x1000_0000和0x1000_0004。
知道上面这些信息已经足够移植驱动了。
二. Dm9000驱动移植详细过程
1.在arch/arm/mach-s3c2410/devs.c 中添加dm9000的platform_device。
static struct resource eievk_dm9000_resource[] = {
[0]= {
.start = 0x10000000, //this is based on EIEVK board
.end = 0x10000003,
.flags = IORESOURCE_MEM,
},
[1]={
.start = 0x10000004,
.end = 0x10000007,
.flags = IORESOURCE_MEM,
},
[2]={
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ,
}
};
static struct dm9000_plat_data eievk_dm9000_platdata ={
.flags = DM9000_PLATF_16BITONLY,//work in 16bit mode
};
struct platform_device eievk_dm9000_device = {
.name = "dm9000",
.id = -1,
.num_resources = 3,
.resource = eievk_dm9000_resource,
.dev = {
.platform_data = &eievk_dm9000_platdata,
}
};
EXPORT_SYMBOL(eievk_dm9000_device);
3.在arch/arm/mach-s3c2410/devs.h中 声明平台设备 eievk_dm9000_device :
extern struct platform_device eievk_dm9000_device;
4.在arm/arm/mach-s3c2410/mach-smdk2410.c中将eievk_dm9000_device添加到平台设备列表中:
static struct platform_device *smdk2440_devices[] __initdata = {
& eievk_dm9000_device, //add dm9000
};
5.需要做两方面的工作:设置芯片MAC地址,使能DM9000的中断。
1)GPFCON (56000050) GPF7 [15:14] 置 10 ,功能设置为EINT7 。
这可以用函数实现:s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF3_EINT7); 
2)EXTINT0 (56000088) [30:28] 置0 低电平触发中断 (复位默认为全0,可能不必设)
3)外部中断屏蔽寄存器。EINTMASK (560000a4) [7] 置0 以enable interrupt EINT7
4)全局中断屏蔽寄存器 INTMASK (4A000008) [4] 置0 使能EINT4_7。
代码修改:
在dm9000.C的开始添加如下定义:
static char net_mac_addr[]={0x00,0xe0,0x3d,0xf4,0xdd,0xf7};//MAC 
static void *extint0,*intmsk,*eintmsk; 
#define EINTMASK (0x560000a4) //外部中断屏蔽 
#define EXTINT0 (0x56000088) //外部中断方式
#define INTMSK (0x4A000008) //中断屏蔽 
在dm9000.C中dm9000_probe(struct platform_device *pdev)中适当位置(设置MAC之后,register_netdevice()之前)添加如下代码:
for(i=0;i
ndev->dev_addr=net_mac_addr;
}
extint0=ioremap_nocache(EXTINT0,4);// 
intmsk=ioremap_nocache(INTMSK,4); 
eintmsk=ioremap_nocache(EINTMASK ,4);
s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF7_EINT7); 
writel(readl(extint0)&0x8fffffff,extint0); //eint7 low level 
writel(readl(intmsk)&(~(1
writel(readl(extint0)&(~(1
iounmap(intmsk);
iounmap(extint0);
iounmap(eintmsk);
而platform_data则是设置给struct device dev;中的platform_data指针(void *)。这个指针内核并不使用,而是驱动自身来定义及使用。
比如说对于DM9000,对应的platform_data定义于include/linux/dm9000.H中。
struct dm9000_plat_data {
unsigned int flags;
/* 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);
};
OK,初始化完资源和platform_data,一个平台设备就定义好了。把这个平台设备变量的地址添加到资源列表中去。比如在2410平台:
在arm/arm/mach-s3c2410/mach-smdk2410.c把设备地址添加到*smdk2410_devices[] __initdata 数组中去。
最后在arch/arm/mach-3sc2410/cpu.c 中初始化函数__init s3c_arch_init(void)会对smdk2410_devices[]每一个设备的指针ptr调
用platform_device_register(ptr)。主要是建立device的层次结构(建立sysfs入口),将设备占用的资源添加到内核资源管理。接下
来看看platform_driver:
在probe中获取资源,并且申请资源,最后映射到内核空间,把映射结果保存起来。
在net_device中的open函数里,注册中断处理函数。
Platform_data的使用极为灵活,首先platform_data结构不同设备之间没有定论,一般可用来保存特定于设备的一些配置,操作等。
比如对于DM9000,可以存在按字节,按字访问的不同模式,因此其platform_data定义成这样:
struct dm9000_plat_data {
unsigned int flags;
/* 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);
};
其中flags是8/16位模式的选择标志,下面三个是在该模式下的IO存取函数。 
然后Dm9000驱动只使用了它的flags标志,其余的并不使用。
因为对于网络net_device,有一个叫着private_data的指针,在分配一个net_device的时候可以让内核为其开辟指定大小的内存。这
部分内存可以通过net_device访问,而且内容也是驱动开发者自定义的。在DM9000的驱动中,net_devict的private_data使用了一个
叫board_info的结构体来包括更多设备相关的信息和操作。
dm9000_plat_data提供的内容也被包括进board_info。因此驱动只使用了初始时设置的flags,除此外dm9000_plat_data中的方法没有
使用的必要。
从中得到的启示:
Device 包含一个platform_data。
Net_device则包含一个private区域.
这样既实现了设备模型的统一管理,又实现了保持不同设备的信息与方法的灵活性。
四. Dm9000驱动源码的简要分析。
1.定义并注册DM9000 的 platform_device 。定义设备占用资源和platform_data。(具体的见前面)
2.将platform_device添加到板子的设备列表中去,在系统初始化时注册入内核。
3.在DM9000.C中,定义了dm9000的platform_driver。
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
这里面关健的东西是name和probe,remove。
4.在模块初始化函数module_init(dm9000_init);中注册dm9000_driver。
platform_driver_register(&dm9000_driver);
这将导致驱动的probe函数被调用。
5.驱动还定义了一个数据结构:board_info来记录芯片的信息及操作。如统计信息,读写操作,占用的IO地址资源,状态。
6.模块初始化函数最终将调用probe函数。这个函数完成的基本过程 :
1)获取一个netdevice:
ndev = alloc_etherdev(sizeof (struct board_info));
2)获取设备资源:
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);
3)申请IO资源,映射到内核并保存映射地址:
db->addr_req = request_mem_region(db->addr_res->start, i,pdev->name);
db->data_req = request_mem_region(db->data_res->start, iosize,pdev->name);
db->io_addr = ioremap(db->addr_res->start, i);
db->io_data = ioremap(db->data_res->start, iosize);
这里的db即是驱动自定义的board_info结构指针。伴随ndev申请内存。
4)根据DM9000数据位宽设置 读写数据帧的函数指针。
Dm9000_set_io(db, iosize);
5)复位芯片:dm9000_reset(db); db中已经包含了详细的芯片信息。
6)读取芯片ID号并判断是否为0x90000A46。
7)初始化以太网ndev : ether_setup(ndev);
8)设置ndev的基本操作:
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->get_stats = &dm9000_get_stats;
ndev->set_multicast_list = &dm9000_hash_table;
9)添加一些打开中断,设置MAC地址的操作在这里。
10)将ndev记录于平台设备platform_dev中去。注册ndev。
platform_set_drvdata(pdev, ndev); //pdev->dev->dev_driver_data=ndev.
ret = register_netdev(ndev);
这也是ndev与platform_dev建立联系的地方。
linux的设备模型负责的只是设备的管理(检测,启动,移除),而如何访问这个设备的数据,比如说以字符流模
式,块设备方式,网络接口,则定义相应的cdev,gendisk,ndev,然后注册到内核。所有的数据访问工作都以这三种界面提供。
7.在系统启动之后,配置eth0,这将引起ndev->open()调用.
Open(dev)流程:
申请中断线:
request_irq(dev->irq, &dm9000_interrupt, IRQF_SHARED, dev->name, dev)
复位DM9000,初始化芯片的各个寄存器使其工作在适当的状态。
设置timer(用于传输超时),
调用netif_start_queue(dev);使设备可以开始收发数据。
8.发送数据包:协议层用已经封装好上层协议数据的skb_buffer调用dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)函数。
发送函数流程:
netif_stop_queue(dev); 暂停接口,使上层暂时不能发送数据。
正在发送中的数据计数加1。
如果只有当前包发送,写指令,写数据帧,发送包。
如果多于一包数据正在发送,当前帧不发送。
释放skb。
重新使能接口:netif_wake_queue(dev);
发送结束,DM9000产生中断,在中断函数中读取芯片相关寄存器判断中断原因,如果是发送结束,则递减正发送包计数。并netif_wake_queue(dev);
9.接收过程:
网络数据包到达,DM9000自动接收并存放在DM内部RAM中,产生中断。在中断处理中识别中断原因并调用接收处理函数dm9000_rx(struct net_device *dev)。
dm9000_rx:
读取芯片相关寄存器确认DM9000正确的收到一帧数据。
申请skb_buffer,将数据从DM9000中拷贝到skb_buffer中。设置skb->dev=nev,skb->protocol=eth_type_trans(skb, dev)。
然后把skb_buffer交给上层协议:netif_rx(skb);
最后更新接口统计信息:db->stats.rx_packets++; 收到包总数+1。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章