基于platform总线的驱动分析
来源:互联网 发布:知牛财经直播怎么获利 编辑:程序博客网 时间:2024/06/07 22:11
基于platform总线的驱动分析
在设备驱动模型中,总线负责将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
为什么要有这种匹配机制?难道不是很多余吗?原因详见Linux设备驱动模型与sysfs文末
1.platform总线基本概念
- 设备和驱动若基于设备驱动模型,则它们通常都需要挂接在一种总线上。总线相对于设备和驱动,可谓是“媒人”担当
- 对于本身依附于 USB、 I2C、SPI 等的设备而言,这自然不是问题。但是很多的设备(比如led)实际并不依附于总线,没了“媒人”,设备和驱动怎么产生联系呢?于是内核为这些可怜的设备发明了一种虚拟的总线——platform(平台总线)
- 挂接在platform上的设备和驱动,就称之为platform_device,和platform_driver
2.platform总线驱动工作流程
- 提供并注册platform_device/设备节点
- 提供并注册platform_driver
- 当platform总线内的mach函数会不停的匹配driver和device(老内核是根据driver内的id、name元素;新内核是根据of_match_table中的compatible)
- 一旦匹配成功,则调用driver的probe(探测)函数开始正式执行驱动代码
3.platform总线驱动的独立性和适应性
一个platform总线驱动程序可以对应多个设备,并且设备的变化也不会影响驱动。这是如何实现的呢?
- 简单的说,这是一种类似传参的机制。设备将底层信息(比如寄存器信息、使用到的中断号、设备名称等)传递给驱动,驱动本身代码不用变,只需要根据参数操作底层,便可适应设备的变化
- 现代驱动设计理念就是算法和数据分离,驱动源码中不携带数据,只负责算法(对硬件的操作方法),这样最大程度保持驱动的独立性和适应性
- 具体的实现方法是:老内核中,platform_device包含了一个device结构体,其内部有一个 void *platform_data; 这个有点类似于给用户提供的自留地,用户可以在里面存放各种底层信息。当driver的probe(探测)函数执行时,platform_device会作为参数传进去,这样驱动就能够间接的得到这个 void *platform_data,从而据此操作硬件;新内核则直接在设备节点属性中存放数据,驱动通过API读取节点里的数据
4.老内核下platform总线驱动的编写方法
下面,以led驱动为实例,分析怎么使用platform来写驱动
- 根据上图的流程,首先应该进入mach-xxx.c完成platform设备的注册。
- 第一步:创建适用于我们设备的platform_data类型(为自留地设计一种格式)
- 第二步:为一个具体设备实例化一个platform_data,用来存放该类设备的底层信息
- 第三步:创建一个具体platform设备(实例化一个platform_device),并把各种信息和platform_data填充入该设备
- 第四步:把platform设备丢到专门存放platform_device的数组中,开机时系统会注册数组中所有设备
- 先来看看mach-xxx.c中的情况,如果我们要写新的platform_device,要注意mach-xxx.c内有没有重复功能的。在该mach-xxx.c搜寻“platform”,寻找专门存放platform_device的数组,发现里面并没有led,看来我们要自己从头开始写了
/*sjh_add*//*第一步:创建一个适用于我们设备的platform_data类型*/struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger;};/*第二步:为一个具体设备实例化一个platform_data*/static struct s5pv210_led_platdata x210_led1_pdata = { .gpio = S5PV210_GPJ0(3), .flags = NULL, .name = "led1", .def_trigger = NULL,};/*第三步:实例化一个platform_device,正式创建设备*/static struct platform_device x210_led1 = { .name = "s5pv210_led",//要和platform驱动中的名字对应 .id = 1, .dev = { .platform_data = &x210_led1_pdata,//底层信息 },};/* 第四步:把我们的platform_device添加进数组,开机时系统会注册数组中所有设备*/static struct platform_device *smdkc110_devices[] __initdata = {/*sjh_add*/ &x210_led1,#ifdef CONFIG_FIQ_DEBUGGER &s5pv210_device_fiqdbg_uart2,#endif.../*该数组很长,后面就不贴了*/
- 然后我们还可以尝试添加多个设备,让一个驱动对应多个led,我们只需重复第二、第三步即可。值得注意的是,驱动和设备的匹配是靠name元素的,所以务必保证这几个设备的name元素都要和驱动的name元素相同,并且id不同就行了(如果name和id都一样……那就分不清了……)
- 接着开始驱动部分的编写
/*自留地格式,提供给probe和release函数,让它们可以解析platdata*/extern struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger;};static int s5pv210_led_probe(struct platform_device *pdev){ /*导入自留地格式s5pv210_led_platdata,这样我们才能解析参数*/ struct s5pv210_led_platdata *pdata = pdev->dev.platform_data; int ret = -1; /*这是一个例子,我们如何通过传入的参数获得led的gpio编号信息*/ gpio_num = pdata->gpio; /*各种注册、初始化操作*/ ... return 0;}/*卸载模块将触发remove函数*/static int s5pv210_led_remove(struct platform_device *pdev){ struct s5pv210_led_platdata *pdata = pdev->dev.platform_data; /*各种注销操作*/ return 0;}/*定义我们的platform_driver。注意name要和platform_device中相同*/static struct platform_driver s5pv210_led_driver = { .probe = s5pv210_led_probe, .remove = s5pv210_led_remove, .driver = { .name = "s5pv210_led", .owner = THIS_MODULE, },};/*模块与卸载加载函数,在里面分别添加platform驱动的注册和卸载函数*/static int __init s5pv210_led_init(void){ return platform_driver_register(&s5pv210_led_driver);}static void __exit s5pv210_led_exit(void){ platform_driver_unregister(&s5pv210_led_driver);}module_init(s5pv210_led_init);module_exit(s5pv210_led_exit);//模块描述信息MODULE_LICENSE("GPL");MODULE_AUTHOR("taurenking");MODULE_DESCRIPTION("S5PV210 LED driver");MODULE_ALIAS("S5PV210 LED driver");
- 驱动方面要注意的地方是传参的问题,对于probe函数,传进来的是指向platform_device类型实例的指针,而我们需要的底层信息在platform_device中的dev内的platform_data中,前面的那幅图可以很好的表明这个结构
- 为了解析这个参数,还需要extern一个s5pv210_led_platdata到本文件,它是我们led设备的platform_data类型(因为这个类型是我们自己写在mach-xxx.c中的,没有定义在头文件中,故编译器找不到),让编译器知道格式,才能够解析
- 在解析参数前,先让导入自留地的格式
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
,这样编译器才能知道格式,我们才能解析参数 - 那么其他硬件操作函数怎么获得底层信息呢?它们又没有platform_data这种参数,我们其实在文件头部定义几个全局变量就能解决,在probe函数种解析底层信息,然后赋值给全局变量,比如前面代码中的
gpio_num = pdata->gpio;
,硬件操作函数调用全局变量即可获得设备的底层信息,例如
static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value){ if (value == 0) { gpio_set_value(gpio_num, 1); }else{ gpio_set_value(gpio_num, 0); }}
5.老内核下厂商风格的platform总线驱动
有时,我们需要去分析一些由厂商实现的platform总线驱动,比如framebuffer。此类platform总线驱动本质上和我们之前分析的完全相同,只是在细节上略有区别罢了
- 以framebuffer为例,在mach-xxx中,我们发现在专门存放platform_device的数组中,有一个和fb有关的platform设备
s3c_device_fb
,它被定义在arch/arm/plat-xxx/devs.c内:
struct platform_device s3c_device_fb = { .name = "s3cfb", .id = -1, .num_resources = ARRAY_SIZE(s3cfb_resource), .resource = s3cfb_resource, .dev = { .dma_mask = &fb_dma_mask, .coherent_dma_mask = 0xffffffffUL }};
- 不难发现,里面并没有填充platform_data。这意味着该设备没有platform_data吗?显然不是的,就在s3c_device_fb的下面,定义了一个函数
s3cfb_set_platdata
,该函数明显是用来为platform设备填充platform_data的 - 看一下该函数的reference,发现在mach-xxx里的smdkc110_machine_init函数中被调用了,并且传进去一个貌似是LCD硬件信息的参数:
搜索ek070tn93_fb_data,发现这个结构体内果然包括了LCD所有的硬件信息。看来,这个结构体就是platform_data的灵魂
static struct s3c_platform_fb ek070tn93_fb_data __initdata = { .hw_ver = 0x62, .nr_wins = 5, .default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, .swap = FB_SWAP_WORD | FB_SWAP_HWORD, .lcd = &ek070tn93, .cfg_gpio = ek070tn93_cfg_gpio, .backlight_on = ek070tn93_backlight_on, .backlight_onoff = ek070tn93_backlight_off, .reset_lcd = ek070tn93_reset_lcd,};
- 说到底,厂商搞的这一出,其实就是绕了几个圈子,把platform设备和platform_data分开定义了,本质和我们之前的那种直接填充的方法没有什么不同
6.新内核下platform总线驱动的编写方法
- 对于驱动本身来说,新内核下的变化倒不大;主要是platform设备不再需要在mach-xxx中注册,而是直接以节点形式定义在设备树中。platform设备可以直接定义在dts的根节点内,比如imx6dl-hummingboard.dts内的ir-receiver设备
/ { model = "SolidRun HummingBoard DL/Solo"; compatible = "solidrun,hummingboard", "fsl,imx6dl"; ir_recv: ir-receiver { compatible = "gpio-ir-receiver"; gpios = <&gpio1 2 1>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>; };/*后面一堆代码就省略了*/
- 驱动程序将直接和设备树里的设备节点进行配对,是通过设备节点中的compatible(兼容性)来与设备节点进行配对的。具体方法是定义一个of_match_table,只要里面的compatible与设备节点里的compatible相同,那么就触发probe函数
/*驱动中定义的of_match_table*/static struct of_device_id gpio_ir_recv_of_match[] = { { .compatible = "gpio-ir-receiver", }, { },};/*of_match_table被绑定到driver结构体内*/static struct platform_driver gpio_ir_recv_driver = { .probe = gpio_ir_recv_probe, .remove = gpio_ir_recv_remove, .driver = { .name = GPIO_IR_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(gpio_ir_recv_of_match), },};
- 有关设备的私有数据,新内核不再使用plat_data了,而是直接在节点中定义各种属性,然后在驱动中用特定的API获取,详见设备树详解
1 0
- 基于platform总线的驱动分析
- 基于linux platform总线的LED驱动
- 基于platform总线的ok6410 LED 驱动
- platform总线按键驱动分析
- 基于platform总线的mini2440的led设备驱动例子
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于platform总线的中断(按键)字符设备驱动设计
- 基于 platform 总线的设备驱动编写模式:
- 基于platform总线的中断(按键)字符设备驱动设计
- Linux下的platform总线驱动代码分析
- Platform总线按键驱动分析(精华版)
- Linux驱动中的platform总线分析
- platform总线按键驱动分析(精华版)
- 【Linux内核驱动】基于platform总线的miscdevice驱动(LED)
- Linux Platform总线+SPI总线分析-SPI驱动
- 基于Linux PCI总线驱动模型的网卡驱动分析
- Linux驱动下的platform总线架构
- Eclipse中使用google代码风格
- CALayer的autolayout
- AM账务处理
- Struck算法中每个sample框中矩形框的选取采样方法详解
- 去除inline-block元素间间距的N种方法
- 基于platform总线的驱动分析
- 11、复制对象
- BZOJ2525: [Poi2011]Dynamite
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalab
- stream插件跨域大文件断点续传实战+自定义限速
- 解决Mysql 安装完不能登录的问题
- C#读取INI文件
- CSS 表格和列表(20160823-0023)
- ffmpeg 用于裁剪的选项