linux设备驱动之platform平台总线工作原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux设备驱动之platform平台总线工作原理相关的知识,希望对你有一定的参考价值。
5.5.5.platform平台总线工作原理2
5.5.5.1、平台总线体系的工作流程
(1)第一步:linux内核系统启动时在bus系统中注册platform。
1、什么叫做bus系统,操作系统中有一套管理总线的体系,内核里有一个子系统,就叫做总线子系统。就是内核来管理总线的。bus系统在内核启动时建立起来,比platform建立的时间还要早,bus系统的是由内核编写的人提供的,我们将来分析代码的时候不需要去分析他。在bus系统起来以后,就需要在bus系统中注册这个platform平台总线的bus,将其注册到bus系统中,来让内核的bus系统来进行管理,因为bus系统是来管理总线的嘛,所以你想要使用一种总线,自然要将这个总线注册到bus系统中,让其来进行管理,这个注册的过程将来可以分析一下,来知道platform平台总线是怎么注册的。
第一步做的就是系统起来时,先把这套体系构建起来,什么体系,当然是bus系统管理platform等总线的这个体系构建起来,以及platform总线注册到bus系统中,platform这个平台总线体系可以工作起来。bus系统能工作管理其他总线,platform总线能工作。这是第一步
(2)第二步:内核移植的人负责提供platform_device
何为内核移植的人,就是uboot移植、Linux内核移植、构建rootfs,做这些工作的人,这些人首先要提供platform_device,比如你的板子上有4个led灯,那么你在移植这个板子的时候,系统中就应该提供led对应的platform_device(因为led灯是扩展到cpu地址总线上去的,根据前面讲的,这类设备是属于platform平台下的,所以既然有了led这个设备,那么移植的人就要提供platform总线下的platform_device这个led设备),要提供platform_device对应的那个结构体变量,一个platform_device结构体变量对应的就是一个led设备,这个结构体就是对设备的一种抽象。这个变量的定义是由内核移植的人来负责的,内核移植说白了,就是将硬件的相关信息写到软件里面去,比如你用到了几个led,你的led对应的gpio是什么,需不要用到中断什么的,这些都是系统移植的人做的,所以系统移植的人,需要先将platform_device个注册好,理论上platform_device是由系统移植的人提供的,但是如果没有提供,那些驱动的人需要自己给他填上。
提供的意思就是你要编写相关的代码,一般platform_device你提供这个代码的时候,一般是在mach-什么什么的文件中进行提供,对应你的Soc的名字,系统移植的人提供板子上的硬件的platform_device对应的结构体变量,要放在mach-xxx的文件中,xxx一般为soc的名字。
像我在mach-smdkv210.c文件中找到了许多platform_device类型结构体定义变量,这些变量都是对应于Soc中属于平台总线下的一些硬件设备。
如:DM9000设备对应的平台总线platform下的设备platform_device定义的结构体变量smdkv210_dm9000
static struct platform_device smdkv210_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(smdkv210_dm9000_resources),
.resource = smdkv210_dm9000_resources,
.dev = {
.platform_data = &smdkv210_dm9000_platdata,
},
};
其中name成员表示设备的名字,很显然是dm9000,num_resources成员是表示该设备使用的资源个数,使用了几个资源,资源包括内存,io等,resource表示所使用的具体资源,指向的是一个含有资源的一个数组,数组中的元素个数,也就是资源个数是用的ARRAY_SIZE(smdkv210_dm9000_resources),方法来计算出使用的资源个数,其实就是资源数组的总大小/数组一个元素的大小,只是这里是用的宏的方式来实现的。
如:
static struct platform_device smdkv210_lcd_lte480wv = {
.name = "platform-lcd",
.dev.parent = &s3c_device_fb.dev,
.dev.platform_data = &smdkv210_lcd_lte480wv_data,
};
lcd的platform_device。
最后用的一个总的表示smdk设备的platform_device的结构体数组,这个数组表示该Soc所有的现在使用的platform总线下的设备。
如:
static struct platform_device *smdkv210_devices[] __initdata = {
&s3c_device_adc,
&s3c_device_cfcon,
&s3c_device_fb,
&s3c_device_hsmmc0,
&s3c_device_hsmmc1,
&s3c_device_hsmmc2,
&s3c_device_hsmmc3,
&s3c_device_i2c0,
&s3c_device_i2c1,
&s3c_device_i2c2,
&s3c_device_rtc,
&s3c_device_ts,
&s3c_device_usb_hsotg,
&s3c_device_wdt,
&s5p_device_fimc0,
&s5p_device_fimc1,
&s5p_device_fimc2,
&s5p_device_fimc_md,
&s5p_device_jpeg,
&s5p_device_mfc,
&s5p_device_mfc_l,
&s5p_device_mfc_r,
&s5pv210_device_ac97,
&s5pv210_device_iis0,
&s5pv210_device_spdif,
&samsung_asoc_idma,
&samsung_device_keypad,
&smdkv210_dm9000,
&smdkv210_lcd_lte480wv,
};
定义了一个platform_device类型的结构体指针数组,数组里面每一个元素都是一个指针,都指向一个platform_device类型的抽象出来的设备。
最后使用
platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));
函数将当前smdkv210_devices中的所有platform_device设备进行添加,这个函数里面的内容实际上就是注册设备的操作。
如下:
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
此时可以说,该Soc的属于platform平台总线下的platform_device所有设备才注册到了platform总线中。之后就等待写驱动的人将平台总线下的platform_driver写好,所有的平台设备驱动写好,然后进行注册到platform总线中。
(3)第三步:写驱动的人负责提供platform_driver
提供的意思就是你要编写相关的代码,写驱动的人需要将对应的platform_device的驱动给写好,比如dm9000设备,驱动的人就需要写一个关于dm9000这个platform_device设备的驱动platform_driver。
如:
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
};
这就是需要的人要实现的,.name是驱动的名字,因为设备和驱动是通过名字来进行匹配的,所以名字一定不能写错,.owner应该是说明这是个驱动模块。.pm是电源管理对应的电源管理函数。.probe是dm9000的探测函数,.remove是dm9000的卸载函数,这些函数都是要写驱动的人去实现的,实现好后,绑定到这个dm9000_driver的函数指针成员中。然后调用platform提供的注册函数,注册这个驱动到platform总线中。platform总线将来就会利用match函数来发现driver和device从而来进行匹配。找到匹配上后,就会执行驱动的probe函数来完成探测初始化和安装,然后就工作起来了。
(4)第四步:platform的match函数发现driver和device匹配后,调用driver和probe函数来完成驱动的初始化和安装,然后设备就工作起来了
platform总线,他的地位有这系统的管理员的地位,来管理该总线下的驱动和设备,platform_device和platform_driver各自像管理员platform总线去注册自己,从而让platform总线这个管理员来进行管理,来用match进行设备和驱动的匹配。通过platform总线下的platform_device这个分支,是一个链表来管理的,找这些设备的name,然后在通过platform总线下的platform_driver这个分支,也是一个链表来进行找设备的name名字,如果在驱动这个分支中找到了这个name,说明这个设备有这个驱动,然后驱动对应的函数等就开始工作了。
5.5.5.2、代码分析
1、platform.c中入口函数,就是platform_bus_init函数,这个函数就是platform这个总线本身自己初始化,本身自己安装注册到bus系统中的时候。
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);//这句话的作用就是在目录下弄出来一个platform
//目录,在device目录下
if (error)
return error;
error = bus_register(&platform_bus_type); //注册platform到bus系统中,在bus目录下会
//看到platform
if (error)
device_unregister(&platform_bus);
return error;
}
其中
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
中的.match方法就是给platform总线下的驱动和设备来提供匹配的方法。
2、每种总线,都会带一个match方法,用来对总线下的device和driver进行匹配,理论上每种总线匹配device和driver的方法是不一样的,比如看name,看id,但实际上匹配都是看name进行匹配的。match函数怎么写的,就是怎么匹配设备和驱动的。platform_match函数就是平台总线的一个match匹配方法。
如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);//这个宏是里面有一个continer
//宏,用来给一个结构体成员的指针,来得到整个结构体的首地址的。因为struct device是所有设备
//都用的部分,每一个设备中都会有这么一个struct device,所以可以通过platform_device中的
//struct device来找到struct platform_device这个结构体的首地址。
//同时因为这个总线是由struct bus_type来定义出来的platform_bus_type,但是bus_type不知道
//自己这个这个总线类型结构体将来会定义出来什么结构体,所以bus_type里面的match函数指针的
//参数只能是struct device和struct driver而不能是struct platform_device和struct //platform_driver,所以只能是struct device,从而因为所有设备都有struct device,所以就能找
//到每个总线下的设备,比如struct platform_device,是这么玩的
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table) //如果pdrv中的id表是非空的,则条用下面的这个函数进行匹配
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); //如果pdrv的id是空的,则直接比较
//名字name是否相同来进行匹配
}
本文出自 “whylinux” 博客,谢绝转载!
以上是关于linux设备驱动之platform平台总线工作原理的主要内容,如果未能解决你的问题,请参考以下文章
Linux——Linux驱动之基于平台总线platform的设备驱动编写实战(手把手教你以platform形式利用GPIO控制蜂鸣器)
Linux——Linux驱动之基于平台总线platform的设备驱动编写实战(手把手教你以platform形式利用GPIO控制蜂鸣器)