版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/82776920
一、platform 驱动的工作过程
platform模型驱动编程,需要实现platform_device(设备)与platform_driver(驱动)在platform(虚拟总线)上的注册、匹配,相互绑定,然后再做为一个普通的字符设备进行相应的应用,总之如果编写的是基于字符设备的platform驱动,在遵循并实现platform总线上驱动与设备的特定接口的情况下,最核心的还是字符设备的核心结构:cdev、 file_operations(他包含的操作函数接口)、dev_t(设备号)、设备文件(/dev)等,因为用platform机制编写的字符驱动,它的本质是字符驱动。
我们要记住,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。
在一般情况下,2.6内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。那么我们编写platform模型驱动时,需要完成两个工作:
a – 实现platform驱动
b – 实现platform设备
我们在上一篇说过实现platform设备有两种方式
然而在实现这两个工作的过程中还需要实现其他的很多小工作,在后面介绍。platform模型驱动的实现过程核心架构就很简单,如下所示:
platform驱动模型三个对象:platform总线、platform设备、platform驱动。
1、platform总线对应的内核结构:struct bus_type–>它包含的最关键的函数:match() (要注意的是,这块由内核完成,我们不参与)
2、platform设备对应的内核结构:struct platform_device–>注册:platform_device_register(unregister)
3、platform驱动对应的内核结构:struct platform_driver–>注册:platform_driver_register(unregister)
那具体platform总线的作用是什么呢:
设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;
如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;
如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致它的probe函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。
二、实现platform 驱动与设备的详细过程
1、思考问题?
在分析platform 之前,可以先思考一下下面的问题:
a – 为什么要用 platform 驱动?不用platform驱动可以吗?
b – 设备驱动中引入platform 概念有什么好处?
现在先不回答,看完下面的分析就明白了,后面会附上总结。
2、设备资源结构体
在struct platform_device 结构体中有一重要成员 struct resource *resource
struct resource {
resource_size_t start; 资源起始地址
resource_size_t end; 资源结束地址
const char *name;
unsigned long flags; 区分是资源什么类型的
struct resource *parent, *sibling, *child;
};
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更,如
a – flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;
b – flags为 IORESOURCE_IRQ 时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值;
下面看一个实例:
static struct resource beep_resource[] =
{
[0] = {
.start = 0x114000a0,
.end = 0x114000a0+0x4,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 0x139D0000,
.end = 0x139D0000+0x14,
.flags = IORESOURCE_MEM,
},
};
设备资源结构体的意义?
我们可以将我们需要用到的设备资源resource定义在平台设备platform device中,然后我们在平台驱动platform driver中拿出来使用,如:我们在platform device中定义led的IO引脚号,然后我们在platform driver中操作点灯的时候将platform device中定义的resource中的IO引脚号取出来进行电平操作点灯。
3、将字符设备添加到 platform的driver中
前面我们提到platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳,下面我们看一下platform_driver的壳:
static struct file_operations hello_ops=
{
.open = hello_open,
.release = hello_release,
.unlocked_ioctl = hello_ioctl,
};
static int hello_remove(struct platform_device *pdev)
{
注销分配的各种资源
}
static int hello_probe(struct platform_device *pdev)
{
1.申请设备号
2.cdev初始化注册,&hello_ops
3.从pdev读出硬件资源
4.对硬件资源初始化,ioremap,request_irq( )
}
static int hello_init(void)
{
只注册 platform_driver
}
static void hello_exit(void)
{
只注销 platform_driver
}
可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。
4、platform是如何匹配device和driver
是platform driver调用platform_driver_register注册platform driver,或者platform_add_devices注册platform device时候会调用到platform_match函数进行匹配,与我们之前讲过的总线的匹配一样,我们可以参考总线驱动模型
5、解决问题
现在可以回答这两个问题了
a – 为什么要用 platform 驱动?不用platform驱动可以吗?
b – 设备驱动中引入platform 概念有什么好处?
引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;
设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
三、实例
1、前言
本例中通过使用Linux驱动模型中的platform总线和led驱动框架编写出来的led驱动代码来分析platform总线的工作原理,对此对代码做如下分析:
在platform总线(任意总线)下的驱动代码都是要分为两部分:设备和驱动,在platform总线下是platform_device和platform_driver。
关于这个问题,我在我的上一篇博客中已经做了很好的说明。对于设备部分的注册:
(1)一般是内核的移植工程师在做移植的时候添加的,在我的这个移植好的内核中是放在arch/arm/mach-s5pv210/mach-x210.c文件中,所以如果移植工程师没有添加你需要编写的驱动对应的设备,那么就需要你自己添加,你可以直接在这个文件中添加,系统启动的时候就会直接加载这个文件的代码,所以设备就会被注册到我们的platform总线下,那么就会添加到platform总线管理下的device设备管理相关的数据结构中去(链表),此时platform总线下的match函数就会自动进行匹配(每注册一个设备或者驱动match函数都会被调用),因为此时
相应的驱动还没被注册,所以匹配肯定是失败的;当我们把驱动也注册之后,也会把驱动添加到platform总线管理下的drive驱动管理相关的数据结构中去(也是一个链表),platform总线将会再次执行match函数,此时match函数就会匹配成功,platform总线下的设备和驱动就建立了对应关系了,那么设备就能够工作了。
arch/arm/plat-samsung/devs.c arch/arm/plat-samsung/devs.c
+ // 定义一个结构体用于放置本设备的私有数据
+ struct x210_led_platdata {
+ unsigned int gpio; // led设备用到的GPIO
+ char *device_name; // led设备在/sys/class/leds/目录下的名字
+ char *gpio_name; // 使用gpiolob申请gpio资源时分配的名字
+ };
+
+ // 定义x210_led_platdata类型的变量,分别对应板子上的4颗led小灯
+ static struct x210_led_platdata x210_led1_pdata = {
+ .gpio = S5PV210_GPJ0(3),
+ .device_name = "led1",
+ .gpio_name = "led1-gpj0_3",
+ };
+
+ static struct x210_led_platdata x210_led2_pdata = {
+ .gpio = S5PV210_GPJ0(4),
+ .device_name = "led2",
+ .gpio_name = "led2-gpj0_4",
+ };
+
+ static struct x210_led_platdata x210_led3_pdata = {
+ .gpio = S5PV210_GPJ0(5),
+ .device_name = "led3",
+ .gpio_name = "led3-gpj0_5",
+ };
+
+ static struct x210_led_platdata x210_led4_pdata = {
+ .gpio = S5PV210_GPD0(1),
+ .device_name = "led4",
+ .gpio_name = "led4-gpd0_1",
+ };
+
+ // 定义4个platform_device结构体
+ static struct platform_device x210_led1 = {
+ .name = "x210_led",
+ .id = 0,
+ .dev = {
+ .platform_data = &x210_led1_pdata,
+ .release = x210_led1_release,
+ },
+ };
+
+ static struct platform_device x210_led2 = {
+ .name = "x210_led",
+ .id = 1,
+ .dev = {
+ .platform_data = &x210_led2_pdata,
+ .release = x210_led2_release,
+ },
+ };
+
+ static struct platform_device x210_led3 = {
+ .name = "x210_led",
+ .id = 2,
+ .dev = {
+ .platform_data = &x210_led3_pdata,
+ .release = x210_led3_release,
+ },
+ };
+
+ static struct platform_device x210_led4 = {
+ .name = "x210_led",
+ .id = 3,
+ .dev = {
+ .platform_data = &x210_led4_pdata,
+ .release = x210_led4_release,
+ },
+ };
arch/arm/mach-s3c24xx/mach-smdk2410.c
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
+ &x210_led4
+ &x210_led4
+ &x210_led4
+ &x210_led4
};
只要在板级文件中添加上上面的代码就可以代替下面的leds-x210-device.c。因为系统初始化时会调用arch/arm/mach-s3c24xx/mach-smdk2410.c中的smdk2410_init --> platform_add_devices
(2)设备注册部分也可以单独编写, 我们下驱动的时候提供 xxxxx_device.c(用来编写设备部分)和xxx_driver(用来编写驱动部分),将他们编译成模块,系统启动之后分别使用insmod装载设备和驱动(顺序无所谓)。这种情况一般使用在调试阶段,如果确定我们的驱动是没有bug的情况下,最好还是把驱动编译进内核,把他们放在他们应该在的位置。
2、led驱动代码
本例子采用的是单独编写编译的方式,代码分析如下:
(1)设备部分:leds-x210-device.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
// 定义一个结构体用于放置本设备的私有数据
struct x210_led_platdata {
unsigned int gpio; // led设备用到的GPIO
char *device_name; // led设备在/sys/class/leds/目录下的名字
char *gpio_name; // 使用gpiolob申请gpio资源时分配的名字
};
// 定义x210_led_platdata类型的变量,分别对应板子上的4颗led小灯
static struct x210_led_platdata x210_led1_pdata = {
.gpio = S5PV210_GPJ0(3),
.device_name = "led1",
.gpio_name = "led1-gpj0_3",
};
static struct x210_led_platdata x210_led2_pdata = {
.gpio = S5PV210_GPJ0(4),
.device_name = "led2",
.gpio_name = "led2-gpj0_4",
};
static struct x210_led_platdata x210_led3_pdata = {
.gpio = S5PV210_GPJ0(5),
.device_name = "led3",
.gpio_name = "led3-gpj0_5",
};
static struct x210_led_platdata x210_led4_pdata = {
.gpio = S5PV210_GPD0(1),
.device_name = "led4",
.gpio_name = "led4-gpd0_1",
};
// 定义4个release函数,当我们卸载设备时会调用platform_device结构体中的device结构体下的release函数
void x210_led1_release(struct device *dev)
{
printk(KERN_INFO "x210_led1_release\n");
}
void x210_led2_release(struct device *dev)
{
printk(KERN_INFO "x210_led1_release\n");
}
void x210_led3_release(struct device *dev)
{
printk(KERN_INFO "x210_led1_release\n");
}
void x210_led4_release(struct device *dev)
{
printk(KERN_INFO "x210_led1_release\n");
}
// 定义4个platform_device结构体
static struct platform_device x210_led1 = {
.name = "x210_led",
.id = 0,
.dev = {
.platform_data = &x210_led1_pdata,
.release = x210_led1_release,
},
};
static struct platform_device x210_led2 = {
.name = "x210_led",
.id = 1,
.dev = {
.platform_data = &x210_led2_pdata,
.release = x210_led2_release,
},
};
static struct platform_device x210_led3 = {
.name = "x210_led",
.id = 2,
.dev = {
.platform_data = &x210_led3_pdata,
.release = x210_led3_release,
},
};
static struct platform_device x210_led4 = {
.name = "x210_led",
.id = 3,
.dev = {
.platform_data = &x210_led4_pdata,
.release = x210_led4_release,
},
};
// 将4个platform_device结构体地址放入一个数组中
static struct platform_device *x210_devices[] = {
&x210_led1,
&x210_led2,
&x210_led3,
&x210_led4,
};
// 入口函数
static int __init leds_x210_init(void)
{
if (platform_add_devices(x210_devices, ARRAY_SIZE(x210_devices))) // 循环注册platform平台设备
{
printk(KERN_ERR "platform_add_devices failed.\n");
return -1;
}
return 0;
}
// 出口函数(卸载)
static void __exit leds_x210_exit(void)
{
int i = 0;
for (i = 0; i < 4; i++)
platform_device_unregister(x210_devices[i]); // 当执行到这个函数的时候就会去执行platform_device结构体中的device结构体下的release函数
}
/*函数入口和出口修饰*/
module_init(leds_x210_init);
module_exit(leds_x210_exit);
/*描述模块信息*/
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("Tao Deng <> 773904075@qq.com"); // 描述模块的作者
MODULE_DESCRIPTION("led device for x210."); // 描述模块的介绍信息
MODULE_ALIAS("alias DT"); // 描述模块的别名信息
device
(2)驱动部分:leds-x210-driver.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
enum LED{
LED_ON_ = 0,
LED_OFF_ = 1,
};
struct x210_led_platdata {
unsigned int gpio;
char *device_name;
char *gpio_name;
};
struct x210_platform {
struct led_classdev led_class;
unsigned int gpio;
};
static inline struct x210_platform *pdev_to_gpio(struct platform_device *dev)
{
return platform_get_drvdata(dev);
}
static void x210_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
struct x210_platform *pdata = container_of(led_cdev, struct x210_platform, led_class); // 使用led_classdev结构体反推得到x210_platform结构体
if (0 < brightness)
gpio_set_value(pdata->gpio, LED_ON_); // 使led小灯发亮
else
gpio_set_value(pdata->gpio, LED_OFF_); // 使led小灯熄灭
}
static int x210_led_probe(struct platform_device *dev)
{
// 定义变量
int ret = 0;
struct x210_led_platdata *platdata = dev->dev.platform_data;
struct x210_platform *pform = NULL;
// 分配空间
pform = kzalloc(sizeof(struct x210_platform), GFP_KERNEL); // 将pform指针指向系统分配的地址空间
if (NULL == pform) {
printk(KERN_ERR "kzalloc failed.\n");
return -ENOMEM;
}
platform_set_drvdata(dev, pform); // 将pform值写入到传进来的platform_device结构体中的device结构体下的私有数据区中去,以便后面释放内存时用
//驱动框架接口填充
pform->led_class.name = platdata->device_name;
pform->led_class.brightness = 0;
pform->led_class.brightness_set = x210_led_set;
// 保存gpio管脚信息
pform->gpio = platdata->gpio;
//注册led驱动
ret = led_classdev_register(NULL, &pform->led_class);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed.\n");
goto err_led_classdev_register;
}
// 向gpiolib管理器申请gpio资源
if (gpio_request(platdata->gpio, platdata->gpio_name))
{
printk(KERN_ERR "gpio_request failed.\n");
goto err_gpio_request;
}
// 设置GPIO输出高电平,关闭LED
gpio_direction_output(platdata->gpio, LED_OFF_);
printk(KERN_INFO "x210_led_probe succeseful.\n");
return 0;
err_gpio_request:
led_classdev_unregister(&pform->led_class);
err_led_classdev_register:
return -1;
}
static int x210_led_remove(struct platform_device *dev)
{
struct x210_led_platdata *platdata = dev->dev.platform_data;
struct x210_platform *pform = pdev_to_gpio(dev); // 取出platform_device结构体中的device结构体中的私有数据区的x210_platform指针
// 卸载led驱动
led_classdev_unregister(&pform->led_class);
// 关闭所有led
gpio_set_value(platdata->gpio, LED_OFF_);
// 释放申请的GPIO资源
gpio_free(platdata->gpio);
// 释放内存
kfree(pform); // 这个一定要放在最后
printk(KERN_INFO "x210_led_remove succeseful.\n");
return 0;
}
static struct platform_driver x210_led_driver = {
.probe = x210_led_probe,
.remove = x210_led_remove,
.driver = {
.name = "x210_led",
.owner = THIS_MODULE,
},
};
static int __init leds_x210_init(void)
{
int ret = 0;
ret = platform_driver_register(&x210_led_driver);
if (ret)
printk(KERN_ERR "platform_driver_register failed.\n");
return ret;
}
static void __exit leds_x210_exit(void)
{
platform_driver_unregister(&x210_led_driver);
}
/*函数入口和出口*/
module_init(leds_x210_init);
module_exit(leds_x210_exit);
/*描述模块信息*/
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("Tao Deng <> 773904075@qq.com"); // 描述模块的作者
MODULE_DESCRIPTION("led driver for x210."); // 描述模块的介绍信息
MODULE_ALIAS("alias DT"); // 描述模块的别名信息
driver
3、platform总线工作原理
(1)insmod之后的现象
当我们执行第一个insmod的时候,并没有出现后面的4条打印信息,只是执行了leds_x210_init函数(一次insmod只能对应执行一次xxx_init函数,
一次rmmod只能对应执行一次xxx_exit函数),这里并没有放置打印语句的信息,因为此时匹配并不会成功,所以不会执行驱动层probe函数;
而当执行第二个insmod的时候,此时platform总线下的match函数匹配就会成功,因为对应有4个led设备,所以就会匹配4次,执行了4次probe
函数打印出相应的信息。
(2)rmmod时的现象
当我们卸载调驱动或者是设备中的任何一个,都会执行驱动层的remove函数;如果是先rmmod设备,那么先执行驱动层中的remove函数,之后就
会执行设备层中的platform_device结构体中的device结构体下的release函数,每一个设备都会执行一次,因为这里卸载了4个设备,所以就会执行
4次;如果是先rmmod驱动,那么直接就会执行驱动层的remove函数。
(3)总结:
insmod注册执行入口函数leds_x210_init -> platform总线下match函数进行匹配 -> 匹配成功则执行驱动层platform_driver结构体下的probe函数(失败就没什么可说的了)->
初始化驱动。
rmmod卸载执行出口函数leds_x210_exit -> 执行驱动层platform_driver结构体下的remove函数 -> 如果是设备则还需要执行设备层platform_device结构体中的device结构体下的
release函数。
(4)当我们注册了设备之后,在platform总线的device中会出现我们注册的设备;当我们注册了驱动之后,在platform总线的driver中会出现驱动名;
但是只要match函数没有匹配成功就不会执行probe函数,那么属于操作led驱动框架下的接口/sys/class/leds下的设备接口就不会出现,因为
led_classdev_register函数在probe中执行。