什么是总线驱动模型
总线在linux中分为物理总线和虚拟总线。物理总线是真实存在的线路,在真是总线上传递数据和命令也会有自己的格式约束。如I2C总线、SPI总线、USB总线等等。以I2C总线为例,在同一组I2C总线上连接着不同的I2C设备,对于物理总线来说,怎么操作跟物理总线的标准有比较强的关系,这是由总线本身决定的;虚拟总线(这里指platform总线)实现了跟物理总线类似的功能,该总线上挂着很多的设备和驱动,并且通过一定的机制实现驱动与设备匹配,这个就是linux在分层思想上的一个体现。
为什么需要platform总线
在很久很久之前,我linux是没有platform总线的,那个时候linux甚至都还没流行起来,每个人写驱动就只需要实现file_operations结构体,里面直接操作硬件,注册它后就可以了(一样工作的很好),直到有一天,一个打工人说,内核中为什么有这么多的类似驱动(如led_am335x、led_2410),这里面不是一模一样的嘛!!他很气愤,表达了不满,然后管理linux源码的大佬们得知了该情况,想了一个办法,设计了一套platform总线,告诉他们,你们用我这个框架,把硬件相关的放到一个.c里(platform_device),驱动放一个.c里(platform_driver),驱动基本上都是通用的,你添加你自己的platform_device不久可以解决了吗?打工人又开开心心的打工去了。(本段故事纯属虚构,如有雷同,纯属巧合)
随后,linux发展得越来越好,每个人都往内核里面添加自己的硬件代码,导致内核非常的臃肿,大部分都是描述硬件的,特别是arm的板子,因为arm内核的特性,很多厂商都设计了不同的soc,自然需要很多.c来描述硬件信息,这时候,维护内核的大佬坐不住了,说:“This whole ARM thing is a f*cking pain in the ass”,从此设备树引入,其实设备树的本质跟platform_device的方式并没有本质区别,都是为了实现分层思想,甚至很多设备树的节点都会转化为platform_device节点(物理总线的转化为相应的物理总线节点)。
说了这么多,相信大家知道为什么需要platform总线了吧
怎么让自己的驱动代码用上platform总线驱动模型
先简单的缕一缕,platform总线驱动模型的工作方式是这样的,当装载一个驱动或者设备时,总线的匹配函数就很在总线中寻找匹配的驱动(本文讲设备树方式),找到后,就执行驱动中提供的probe函数。所以在驱动中,我们就需要注册platform driver,提供设备树匹配选项of_device_id数组。
主要的函数有:
int platform_driver_register (struct platform_driver *driver);
void platform_driver_unregister(struct platform_driver *drv);
主要用到的数据结构有:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
具体的代码(只是代码框架,未真实控制硬件)
该代码设备树只需在设备树根节点添加一个新的节点,新节点的compatible属性为“test,xxx”即可匹配成功
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define XXX_MAJOR 200 /* 主设备号 */
#define XXX_NAME "xxx_test" /* 设备名 */
struct class * xxx_class; /* 类 */
struct device * xxx_device; /* 设备 */
static int xxx_open(struct inode *inode, struct file *filp)
{
//通常返回:0 成功;其他 失败
return 0;
}
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
//通常返回:读取的字节数;小于0,表示读取失败
return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
//通常返回:写入的字节数;小于0,表示写入失败
return 0;
}
static int xxx_release(struct inode *inode, struct file *filp)
{
//通常返回:0 成功;其他 失败
return 0;
}
//本xxx_fops只列出了部分常用的操作
static const struct file_operations xxx_fops = {
//owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
.owner = THIS_MODULE,
//open 函数用于打开设备文件,用户程序打开设备文件会得到一个设备描述符,并执行驱动指定的open函数(不管驱动有无open函数,用户使用都需要打开)
.open = xxx_open,
//read 函数用于读取设备文件,应用执行read时会调用该函数
.read = xxx_read,
//write 函数用于向设备文件写入(发送)数据,应用执行write时会调用该函数
.write = xxx_write,
//release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
.release = xxx_release,
};
//probe函数,平台设备匹配完成后执行该函数
//设备与驱动匹配成功之后,设备的很多硬件信息可以从“struct platform_device *dev”中获取
//“struct platform_device *dev”中的硬件信息现在大多由设备树转化而来,也可以使用.c注册一个struct platform_device xxx结构体注册,本文不涉及
static int xxx_probe(struct platform_device *dev)
{
int ret = 0;
//主设备号为0,自动分配设备号,返回值是分配的设备号
ret = register_chrdev(XXX_MAJOR, XXX_NAME, &xxx_fops);
//返回值为负数,注册失败
if(ret < 0)
{
return -1;
}
//创建类,在/dev目录下生产xxx目录
xxx_class = class_create(THIS_MODULE, "xxx");
//创建设备节点,在/dev/xxx/目录下生成xxx_driver设备文件
xxx_device = device_create(xxx_class, NULL, XXX_MAJOR, NULL, "xxx_driver");
return 0;
}
//remove函数,平台设备卸载时候执行该函数
static int xxx_remove(struct platform_device *dev)
{
//注销字符设备驱动
unregister_chrdev(XXX_MAJOR, XXX_NAME);
//销毁设备节点,会删除/dev/xxx/目录下的xxx_driver设备文件
device_destroy(xxx_class, XXX_MAJOR);
//销毁类,会删除/dev目录下的xxx目录
class_destroy(xxx_class);
return 0;
}
//设备树匹配列表,需要在设备树的compatible属性设置为test,xxx,就能与设备匹配了
static const struct of_device_id xxx_of_match[] = {
{.compatible = "test,xxx"},
{}
};
//platform 平台驱动结构体
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx",
//这个是用来匹配设备的,本文采用设备树的方式进行匹配,不涉及用.c的方式
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};
//模块初始化函数,在模块装载的时候调用
static int __init xxx_init(void)
{
return platform_driver_register(&xxx_driver);
}
//模块退出函数,在模块卸载的时候调用
static void __exit xxx_exit(void)
{
platform_driver_unregister(&xxx_driver);
}
//向内核表明初始化函数,在模块装载的时候调用
module_init(xxx_init);
//向内核表明退出函数,在模块卸载的时候调用
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaoShuATao");
注:作者水平有限,如有错误,请大家及时指出,我会第一时间修改,谢谢大家了。
版权说明:可自由转载使用,转载请注明出处。