Linux内核总线的作用,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是否相同来进行匹配

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值