本文根据自己的理解分析了分离分层结构和机制,后面也结合点灯例子分析了平台设备驱动,水平有限,
错误之处欢迎批评指正。同时也希望能对大家有点帮助。
概念:
分层: 核心层和设备相关层分开,如输入子系统分核心层input.c 和设备相关层 evdev.c ,button.c
分层思想的优点就是能把很多文件共用的代码抽离集中起来成为一个或者多个核心文件供设备相关
层调用,如输入子系统中核心层input.c文件为所有输入设备的设备相关层提供很多共用的函数。
分离: 把设备相关层中那些与硬件紧密联系的代码(这些代码会根据不同的硬件作修改,不是稳定不
变的代码)和驱动(与具体硬件无关的,比较稳定的代码)分离开来,即要编写两个文件:dev.c和drv.c。如
输入子系统中dev.c就是我们自己写的button.c, drv.c就是evdev.c(虽然名字不是evdrv.c,但这里这样理解
还是贴切的,evdev.c是稳定的代码,它是标准字符设备相关的文件,是系统自带的,与具体硬件无关的,
不需要我们修改的)。
分离分层是通过 总线-设备-驱动(bus-device-driver) 模型来进行分离分层的。
核心层: bus
设备相关层: device driver
核心层 bus 与设备相关层device,driver 进行分层。
设备层的 device 与 driver 进行分离,使对应的drv.c 与具体硬件无关,这样同一个drv.c就可以驱动
同种类似的硬件,根据硬件信息的不同,修改dev.c即可。dev.c的主要是收集硬件信息来构造device结
构体,然后把这个结构体添加到核心层的dev链表中。
bus_type类型结构体bus有dev和drv链表,driver有probe成员。
构造好device结构体后,调用device_add()把device结构体放入bus的device链表中,加入新的
device结 构体后,系统会自动从bus的drv链表取出每一个drv,用bus的match函数判断驱动能否支持这
个dev,若支持就调用驱动的probe().
构造好driver结构体后,调用driver_register()把driver结构体放入bus 的driver链表中,加入新的
driver结构体后系统会自动从bus的dev链表取出每一个dev,用bus的match函数判断dev能否支持这个
drv,若支持就调用驱动的probe()。
match函数是根据device的bus_id和driver的name是否一致来匹配的,所以构造这两个结构体的
时要注意device->bus_id要与driver->name 保持一致。
总结:
当有新的device添加到bus的dev链表时,系统就自动根据device->bus_id扫描比较driver->name,
如果一致则匹配,调用driver 的probe()函数。
或者当有新的driver添加到bus的drv链表时,系统就自动根据driver->name扫描比较device-bus_id,
如果一致则匹配,调用driver的probe()函数。
这就是 总线-设备-驱动 模型提供的一种机制,这种机制简单说就是当系统中有了device 或driver
更新的时候,就会自动扫描比较是否有与之匹配的的driver 或device,有则调用driver 的probe()函数,
至于probe()函数里面干什么,完全由我们自己决定,由我们自己现实。
有了这样一种机制,我们写驱动的时候就很容易实现分离分层了,把硬件相关的,经常需要修改的
写在device这一边(后面其他文件需要用到硬件相关的信息的话都由device结构体提供),把比较稳定的
代码写在driver这一边,把想在他们匹配之后要做的事写在driver 的probe()里面。
platform设备驱动:
平台总线-设备-驱动 模型是由 总线-设备-驱动 模型衍生出的,它的设备和驱动结构都内嵌了device
和driver结构体,本质上没有什么区别。下面结合实例分析 平台总线-设备-驱动 模型。
例子:利用patform总线设备驱动来点亮一个LED。
根据总线设备模型,我们需要编写两个文件led_dev.c和led_drv.c。
led_dev.c 主要负责硬件相关代码,platform_device 有一个resource结构体,重点要理解用什么样的
硬件资源来构造这个platform_device的resource 结构体数组,不知怎么表达,大家体会体会,也不难。
编写步骤:
1、构造resource结构体数组
2、分配/设置platform_device
3、注册patform_device
led_dev.c :
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
/* 构造资源结构体数组,platform_device需要的很多硬件资源信息都是通过 resource 结构体获取的 */
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050, // gpfcon寄存器开始地址
.end = 0x56000050 + 8 - 1, // gpfcon寄存器结束地址
.flags = IORESOURCE_MEM, // 标志,mem资源
},
[1] = {
.start = 5, // gpfdat 第五bit,对应某个灯。
.end = 5,
.flags = IORESOURCE_IRQ,
}
};
static void led_release(struct device * dev)
{
}
/* 分配/设置一个platform_device */
static struct platform_device led_dev = {
.name = "myled", // 必须和platform_driver内嵌的driver.name一样
.id = -1,
.num_resources = ARRAY_SIZE(led_resource), // 资源大小
.resource = led_resource, // 前面的资源数组
.dev = {
.release = led_release, // 必须设置,空的也可以
},
};
/* 注册一个platform_device */
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
led_drv.c中需要做的事:
1、获取硬件资源实现设备的读写函数,以便构造操作设备的fops
2、实现platform_driver的probe()函数和remove()函数
3、分配/设置platform_driver
4、注册patform_driver
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static int major;
static struct class *cls;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
static int led_open(struct inode *inode, struct file *file)
{
/* 配置为输出 */
*gpio_con &= ~(0x3<<(pin*2)); //gpio_con 和pin 在 probe()函数中被赋值
*gpio_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
copy_from_user(&val, buf, count);
if (val == 1) // 点灯
*gpio_dat &= ~(1<<pin); //gpio_dat 和pin 在 probe()函数中被赋值,通过
else // 灭灯 //led_dev.c里面的led_resource结构体获得寄存器地址赋给gpio_dat,
*gpio_dat |= (1<<pin); //获得 5 赋给pin,pin表示第5bit.
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/* 实现platform_driver 的probe()函数,这个函数在platform_device 和platform_driver成功匹配的时候
* 被调用,可以在里面实现自己想要的功能,这里获取寄存器地址进行ioremap,获取相关资源注册字符设备,
* 注册了设备之后,应用程序就可以操作设别了。
*/
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start;
/* 注册字符设备驱动程序 */
printk("led_probe, found led\n");
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
static int led_remove(struct platform_device *pdev) //把probe()里面注册,分配,添加或map的东西清理掉。
{
/* 卸载字符设备驱动程序 */
/* iounmap */
printk("led_remove, remove led\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
return 0;
}
struct platform_driver led_drv = {
.probe = led_probe, // 匹配后将调用的函数
.remove = led_remove, // 与pobe功能相关,做清理工作
.driver = {
.name = "myled", //必须和platform_device的name一样
}
};
/* 注册platform_driver */
static int led_drv_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
总结:写platform设备驱动的时候要写两个文件,dev.c和drv.c,
在dev.c里面把所有要用到的硬件资源信息写到resource结构体数组中,然后分配,设置,注册
platf orm_device.
在drv.c里面通过resource获得所需信息,分配和设置好为注册字符设备所需的资源,如构造操作
设备的fops,再实现platfrom_driver的probe()函数和remove()函数,分配,设置,注册platfrom_driver,
在probe()函数里面注册设备。
platform设备驱动模型利用了 总线-设备-驱动 模型的分离分层结构,和匹配成功后自动调用probe()
函数的这种机制。
platform设备驱动模型 较 总线-设备-驱动模型的不同是它在dev.c内部再一次进行分离,把所有与硬
件直接相关的信息统统写到resource结构体中,使其他部分能与硬件分离开来,要用到硬件信息的时候,
通过resource结构体间接获得。硬件有改动时只修改resource结构体即可,其他部分几乎无需修改。