弄了两天,把platform模型看了。
linux设备驱动模型中,三个重要的实体,总线、设备、驱动。
其中的概念和意义不再强调,网上还有书上都有很多。
这里只强调两点:
1)引入platform使得设备被挂在在总线上,符合了linux2.6内核的设备模型。
2)隔离bsp和驱动。在bsp中定义的platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用api去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
几个重要的结构体:
platform_bus_type是platform总线定义了一个bus_type的实例。
这部分有系统内部为我们完成,我们不需要去做特殊的定义或者初始化。
-
struct bus_type platform_bus_type = {
-
.name = "platform", //总线名称
-
.dev_attrs = platform_dev_attrs,
-
.match = platform_match, //匹配函数,该函数确定了platform_device和platform_driver之间如何匹配
-
.uevent = platform_uevent, //用于添加环境变量
-
.pm = platform_pm_ops_ptr,
-
};
-
export_symbol_gpl(platform_bus_type);
-
-
int __init platform_bus_init(void) //总线的初始化
-
{
-
int error;
-
-
early_platform_cleanup();
-
-
error = device_register(&platform_bus);
-
if (error)
-
return error;
-
error = bus_register(&platform_bus_type);
-
if (error)
-
device_unregister(&platform_bus);
-
return error;
-
}
-
总线也是一种设备,要先注册总线设备,在使用总线设备完成总线注册。
-
static struct platform_device *smdk6410_devices[] __initdata = {
-
-
//#ifdef config_smdk6410_sd_ch0
-
&s3c_device_hsmmc0,
-
//#endif
-
//#ifdef config_smdk6410_sd_ch1
-
&s3c_device_hsmmc1,
-
//#endif
-
&s3c_device_i2c0,
-
// &s3c_device_i2c1,
-
&s3c_device_fb,
-
-
&s3c_device_ohci,
-
&s3c_device_usb_hsotg,
-
......
描述了总线,就要描述设备和驱动了。下面是两个重要的数据结构体,定义在
-
struct platform_device { //platform设备 在设备源文件中实现
-
const char * name;
-
int id;
-
struct device dev;
-
u32 num_resources;
-
struct resource * resource;
-
-
const struct platform_device_id *id_entry;
-
-
/* arch specific additions */
-
struct pdev_archdata archdata;
-
};
-
-
struct platform_driver { //platform驱动 在驱动源文件中实现
-
int (*probe)(struct platform_device *); //函数指针也有驱动程序实现
-
int (*remove)(struct platform_device *);
-
void (*shutdown)(struct platform_device *);
-
int (*suspend)(struct platform_device *, pm_message_t state);
-
int (*resume)(struct platform_device *);
-
struct device_driver driver;
-
const struct platform_device_id *id_table;
-
};
resource结构,dm9
000_resources是他的一个实例(ok6410)
对resource的定义通常在bsp板文件中进行,具体设备驱动通过platform_get_resource()这个api来获取。
-
struct resource {
-
resource_size_t start; /*资源的起始*/ //起始和结束都依赖于设备类型
-
resource_size_t end; /*资源的结束*/
-
const char *name; /*资源的名称*/
-
unsigned long flags; /*资源的类型*/
-
struct resource *parent, *sibling, *child; /*资源的链表指针*/
-
};
-
-
static struct resource dm9000_resources[] = {
-
[0] = {
-
.start = s3c64xx_pa_dm9000,
-
.end = s3c64xx_pa_dm9000 3,
-
.flags = ioresource_mem,
-
},
-
[1] = {
-
.start = s3c64xx_pa_dm9000 4,
-
.end = s3c64xx_pa_dm9000 s3c64xx_sz_dm9000 - 1,
-
.flags = ioresource_mem,
-
},
-
[2] = {
-
.start = irq_eint(7),//dm9000ae中断信号使用s3c6410处理器中断eint7信号
-
.end = irq_eint(7),
-
.flags = ioresource_irq | irqf_trigger_high,
-
},
-
};
除了这些信息,硬件设备可能还会有一些配置信息,也是依赖于板的,不适合放在驱动程序中。
因此,platform提供platform_date支持
-
static struct dm9000_plat_data dm9000_setup = {
-
.flags = dm9000_platf_16bitonly,
-
.dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x90, 0x90 },
-
};
-
-
static struct platform_device s3c_device_dm9000 = {
-
.name = "dm9000",
-
.id = 0,
-
.num_resources = array_size(dm9000_resources),
-
.resource = dm9000_resources,
-
.dev = {
-
.platform_data = &dm9000_setup,
-
}
-
};
-
#endif //#ifdef config_dm9000
而在网卡驱动中,通过
struct dm9000_plat_data *pdate=pdev->dev.platform_data
获得platform_data 即硬件的配置信息
最重要的在于platform_device与platform_driver之间是如何通过platform总线建立联系的。
通过对函数platform_driver_register的追踪,可以看到,最终两者通过platform_mach建立联系的。
-
static int platform_match(struct device *dev, struct device_driver *drv)
-
{
-
struct platform_device *pdev = to_platform_device(dev);
-
struct platform_driver *pdrv = to_platform_driver(drv);
-
-
/* match against the id table first */
-
if (pdrv->id_table)
-
return platform_match_id(pdrv->id_table, pdev) != null;
-
-
/* fall-back to driver name match */
-
return (strcmp(pdev->name, drv->name) == 0);
-
}
可以看出,platform_device与platform_driver是通过name来匹配的。
匹配成功后,调用probe函数。
-
int driver_probe_device(struct device_driver *drv, struct device *dev)
-
{
-
。。。。。。。。。
-
ret = really_probe(dev, drv);
-
。。。。。。。。
-
}
-
static int really_probe(struct device *dev, struct device_driver *drv)
-
{
-
。。。。。。。。
-
if (dev->bus->probe) {
-
ret = dev->bus->probe(dev);
-
if (ret)
-
goto probe_failed;
-
} else if (drv->probe) {
-
ret = drv->probe(dev);
-
if (ret)
-
goto probe_failed;
-
}
-
。。。。。。。。
-
}
可以看出,如果bus定义了probe函数,则调用bus的probe函数;如果bus,没有定义而driver定义了probe函数,则调用driver的probe函数。由上边的platform_bus_type可以看出bus并没有定义probe函数,所以调用driver的probe函数。
我的测试程序用的是linux设备驱动开发详解这本书,但是这里出现一个问题。
书上的例程是将device和driver放在同一个文件中,我将源代码完全拷贝后编译可以通过,但是insmod并没有把生成的模块加载进来。
用lsmod可以查看到加载的模块,但是cat /proc/devices找不到设备号,无法创建设备节点,也无法测试应用程序。
然后按照网友的说明,将两者分开,然后分别编译分别加载,就可以通过了。
部分代码如下,没列出的和原来的部分是一样的。
在globalfifo-device.c中添加
-
static struct platform_device *globalfifo_device;
-
/* module_init */
-
static int __init globalfifo_dev_init(void)
-
{
-
int ret;
-
globalfifo_device=platform_device_alloc("globalfifo",-1);//分配设备
-
ret=platform_device_add(globalfifo_device); //注册设备 挂载设备到总线
-
if(ret)
-
{
-
printk("platform_device_add failed\n");
-
}
-
else
-
printk("platform_device_add success!\n");
-
}
-
-
/* module_exit */
-
void __exit globalfifo_dev_exit(void)
-
{
-
platform_device_unregister(globalfifo_device); //设备注销
-
}
在platform-driver.c中添加
-
static int __devexit globalfifo_remove(struct platform_device *pdev)
-
{
-
cdev_del(&globalfifo_devp->cdev);
-
kfree(globalfifo_devp);
-
unregister_chrdev_region(mkdev(globalfifo_major, 0), 1);
-
return 0;
-
}
-
-
static struct platform_driver globalfifo_driver=
-
{
-
.probe=globalfifo_probe,
-
.remove=__devexit_p(globalfifo_remove),
-
.driver=
-
{
-
.name="globalfifo",
-
.owner=this_module,
-
}
-
};
-
-
/* module_init */
-
static int __init globalfifo_drv_init(void)
-
{
-
return platform_driver_register(&globalfifo_driver);
-
}
-
-
/* module_exit */
-
void __exit globalfifo_drv_exit(void)
-
{
-
platform_driver_unregister(&globalfifo_driver);
-
}
同时还需要在板文件中mach-smdk6410.c中添加如下信息
-
static struct platform_device globalfifo_device={
-
.name="globalfifo",
-
.id=-1,
-
};
为了完成globalfifo_device这个设备的注册,修改如下
-
static struct platform_device *smdk6410_devices[] __initdata = {
-
&globalfifo_device
-
//#ifdef config_smdk6410_sd_ch0
-
&s3c_device_hsmmc0,
-
//#endif
-
//#ifdef config_smdk6410_sd_ch1
-
&s3c_device_hsmmc1,
-
//#endif
-
&s3c_device_i2c0,
-
// &s3c_device_i2c1,
-
&s3c_device_fb,
-
-
&s3c_device_ohci,
下面编译,加载就可以了。
加载后可以发现如下结点
/sys/bus/platform/devices/globalfifo
/sys/devices/platform/globalfifo
cat /proc/devices可以查看到设备号。
mknod创建设备节点后就可以测试应用程序了。
阅读(2955) | 评论(0) | 转发(2) |