本文共 10359 字,大约阅读时间需要 34 分钟。
在成功移植Dm9000驱动到我的EIEVK-100(SMDK2440)开发板的前提下,本文从以下几个方面说明相关原理及过程:
1.硬件情况
2.Dm9000驱动移植详细过程
3.Platform_device与platform_driver
4.Dm9000驱动代码简要分析
一.
DM9000在电路板上的连接中与编程相关的如下: 1)EECS拉高:16bit模式; 2)EECK拉高,INT连接到2440
知道上面这些信息已经足够移植驱动了。
二.
1.在内核编译配置选项中,driver-->net-->10/100M
2.在arch/arm/mach-s3c2410/devs.c
#include
static
[0]=
.start
.end
.flags
},
[1]={
.start
.end
.flags
},
[2]={
.start
.end
.flags
}
};
static
.flags
};
struct
.name
.id
.num_resources
.resource
.dev
.platform_data
}
};
EXPORT_SYMBOL(eievk_dm9000_device);
3.在arch/arm/mach-s3c2410/devs.h中
4.在arm/arm/mach-s3c2410/mach-smdk2410.c中将eievk_dm9000_device添加到平台设备列表中:
static
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&eievk_dm9000_device,
&s3c_device_nand,
};
5.OK,经过上述努力,Dm9000设备已经成功注册进入驱动核心。下面进入driver/net/dm9000.C中,还需要做两方面的工作:设置芯片MAC地址,使能DM9000的中断。
根据2440资料。有几个地方需要设置:
1)GPFCON
这可以用函数实现:s3c2410_gpio_cfgpin(S3C2410_GPF7,
2)EXTINT0
3)外部中断屏蔽寄存器。EINTMASK
4)全局中断屏蔽寄存器
代码修改:在dm9000.C的开始添加如下定义: #include
static
static
#define
#define
#define
在dm9000.C中dm9000_probe(struct
for(i=0;i<6;i++){
ndev->dev_addr[i]=net_mac_addr[i];
}
iounmap(intmsk);
iounmap(extint0);
iounmap(eintmsk);
OK,编译下载之后,启动内核后:
Mount
Ifconfig
eth0:
开发板与主机之间能正常ping通。
整个驱动移植过程结束。
三.
通过Platform机制开发发底层驱动的大致流程为:
1.
struct
};
定义一个platform_device一般需要初始化两个方面的内容:设备占用的资源resource和设备私有数据dev.platform_data。最重要的是resource
设备占用的资源主要是两个方面:IO内存和irq资源。
Resource定义于kernel\include\linux\ioport.h中,
struct
};
实际上是对地址范围及其属性的一个描述。最后几个用于树型结构的指针是内核用于管理所有资源的。
而platform_data则是设置给struct
比如说对于DM9000,对应的platform_data定义于include/linux/dm9000.H中。
struct
unsigned
void
void
void
};
OK,初始化完资源和platform_data,一个平台设备就定义好了。把这个平台设备变量的地址添加到资源列表中去。比如在2410平台:
在arm/arm/mach-s3c2410/mach-smdk2410.c把设备地址添加到*smdk2410_devices[]
最后在arch/arm/mach-3sc2410/cpu.c
2.
int
int
void
int
int
struct
};
它内部封装了一个device_driver,更有意思的是其它的全是函数,并且这些函数名与device_driver中提供的一样,只是参数由device
驱动应该实现platform_driver中的这些操作,而内嵌的device_driver中的对应函数则在注册时被指定为内核指定的操作,这些指定操作只是把调用参数转换成platform_driver和platform_device来调用platform_driver提供的操作而已。
平台驱动注册:
int
{
drv->driver.bus
if
drv->driver.probe
if
drv->driver.remove
if
drv->driver.shutdown
if
drv->driver.suspend
if
drv->driver.resume
return
}
OK,如果device_driver的方法没有定义就会变成对应的platform_drv_*方法。
来看看其中的一个的实现:比如
static
{
struct
struct
return
}
事情很清楚,先把设备的device_driver转成platform_driver,同样转换device为platform_device。然后去调用platform_driver提供的函数。类型转换当然是通过container_of()宏实现的。
因此,驱动只需要实现platform_driver中的方法。然后注册即可。
关于注册,由上面的代码可知,最终也是通过
3.更深入的小窥一下平台设备与平台驱动的注册:
根据LDD3中指出的设备模型,一个设备和驱动必然属于某一个总线。Platform_device和platform-driver在层次上隶属于叫platform_bus_type的总线类型。OK,平台驱动注册的时候(平台设备必须先于驱动注册)将引用它所属总线的匹配函数去决定总线上每一个设备是否属于自己。然后二者建立联系:设备的驱动指针指向该驱动,驱动的设备列表中加入匹配的设备。
当然,这是在设备和驱动这一层面来说的,更深入一层,kobjects和ksets建立层次关系,建立sysfs入口等等。。
注意,platform_bus_type的匹配函数只是比较一下driver和device的name是否相同。因此,同一设备的platform_device和platform_driver的name应该设为相同的。见platform_bus_type匹配函数定义: static
{
struct
return
}
因此,dm9000的platform_device和platform_driver的name都为"Dm9000"。
4.下面一个问题:资源怎么用??Platform_data一般怎么用?
资源描述的是设备占用的IO内存,IO端口,及中断线。
Dm9000驱动中是这样使用的。这符合惯例:
在probe中获取资源,并且申请资源,最后映射到内核空间,把映射结果保存起来。
在net_device中的open函数里,注册中断处理函数。
Platform_data的使用极为灵活,首先platform_data结构不同设备之间没有定论,一般可用来保存特定于设备的一些配置,操作等。比如对于DM9000,可以存在按字节,按字访问的不同模式,因此其platform_data定义成这样:
struct
unsigned
void
void
void
};
其中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
Net_device则包含一个private区域.
这样既实现了设备模型的统一管理,又实现了保持不同设备的信息与方法的灵活性。
四.
1.定义并注册DM9000
2.将platform_device添加到板子的设备列表中去,在系统初始化时注册入内核。
3.在DM9000.C中,定义了dm9000的platform_driver。 static
.driver
.name
.owner
},
.probe
.remove
.suspend
.resume
};
这里面关健的东西是name和probe,remove。
4.在模块初始化函数module_init(dm9000_init);中注册dm9000_driver。 platform_driver_register(&dm9000_driver); 这将导致驱动的probe函数被调用。
5.驱动还定义了一个数据结构:board_info来记录芯片的信息及操作。如统计信息,读写操作,占用的IO地址资源,状态。
6.OK,现在接着4说,模块初始化函数最终将调用probe函数。这个函数完成的基本过程
1)获取一个netdevice:ndev
2)获取设备资源:db->addr_res
3)申请IO资源,映射到内核并保存映射地址:db->addr_req
4)根据DM9000数据位宽设置
5)OK,现在复位芯片:dm9000_reset(db);
6)读取芯片ID号并判断是否为0x90000A46。
7)初始化以太网ndev
8)设置ndev的基本操作:ndev->open
9)添加一些打开中断,设置MAC地址的操作在这里。
10)将ndev记录于平台设备platform_dev中去。注册ndev。 platform_set_drvdata(pdev,
OK,probe的使命OVER了。这也是ndev与platform_dev建立联系的地方。 可以这么理解,linux的设备模型负责的只是设备的管理(检测,启动,移除),而如何访问这个设备的数据,比如说以字符流模式,块设备方式,网络接口,则定义相应的cdev,gendisk,ndev,然后注册到内核。所有的数据访问工作都以这三种界面提供。
7.OK,一旦probe正常的执行完,内核中注册好了eth0这个网络接口(因为只有一个网卡)。在系统启动之后,配置eth0,这将引起ndev->open()调用。来看看open做些什么? Open(dev)流程:申请中断线:request_irq(dev->irq,
8.OK,配置好eth0接口后,网络设备连接好。数据收发就绪。现在简要的分析一下收发过程: 发送数据包:协议层用已经封装好上层协议数据的skb_buffer调用dm9000_start_xmit(struct
发送结束,DM9000产生中断,在中断函数中读取芯片相关寄存器判断中断原因,如果是发送结束,则递减正发送包计数。并netif_wake_queue(dev);
9.接收过程:网络数据包到达,DM9000自动接收并存放在DM内部RAM中,产生中断。在中断处理中识别中断原因并调用接收处理函数dm9000_rx(struct
然后把skb_buffer交给上层协议:netif_rx(skb);
最后更新接口统计信息:db->stats.rx_packets++;
整个DM9000驱动的移植和源码主要部分的简要分析至此结束。
转载地址:http://nipci.baihongyu.com/