上一篇:《 字符设备号的静态 / 动态分配 》 | 下一篇: |
目录
一、Platform 的作用
类似于 i2c / spi 等总线,SoC 中虽然很多外设没有总线这个概念,但仍想用总线、驱动和设备的模型方便控制。Linux 提出了 platform 这个虚拟总线概念,于是也就有了 platform 驱动 / platform 设备:platform_drive / platform_device.
二、Platform 设备驱动
当写到 Platform 设备驱动,便是很完善的驱动了,需要把大部分格式、数据规范化一下。
1. 私有数据结构体化
之前的驱动中,没有涉及私有数据或者直接定义了全局变量,而全局变量毕竟不安全,所以一般使用结构体来存储该驱动所对应设备的私有数据。一般以 xxx_data / xxx_priv / xxx_dev 为名,写在最前面,后面要用到时基本是用指针来调取使用。
在这个私有数据结构体里,一般最先定义好 platform 设备结构体:
struct demo_priv {
struct platform_device *pdev;
platform_device 是封装好的 platform 设备的相关数据结构体,只需简单了解即可,深入探究可以在论坛上继续搜索,在此定义好的 platform_device 类型的 *pdev 最大作用,就是在后面 probe 函数中获取设备数据。其原型定义如下:
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
接下来可根据自己驱动所需定义,加入这个 demo 需要 input_dev,以及 kobject 来创建设备节点来读写数据,就需要在这里定义好相关结构体指针:
struct demo_priv {
struct platform_device *pdev;
struct input_dev *input;
struct kobject *kobj;
/* 其他所需数据,仅作为例子
* 这里先不用初始化,用到时再初始化 */
int gpio;
int time;
}
其实在这里定义好结构体类型,在后面就可以用来存储数据了,就已经算定义好了数据结构体。不过规范起见,我们可以多写一行明确定义好结构体,这样更安全、数据脉络更清晰、更不容易出 bug
struct demo_priv *demo;
这样后面通过指针操控结构体指针 demo,便可以很方便的读写私有数据啦!
2. probe 函数 / remove 函数
probe 函数可以说是 platform 设备驱动的初始化函数。一般内存的分配、其他函数或功能的定义初始化、节点的注册等等,一切和这个设备驱动初始建立相关的事,都在这里做,probe就是这个驱动中,所有 “实质性” 函数最先运作的(这个实质性是我自己定义的,后面会说什么是所谓的 “非实质性” )。其定义应如下:
static int demo_porbe(struct platform_device *pdev)
{
/* 一些局部变量 */
int ret;
/* 分配内存空间 */
demo = kzalloc(sizeof(struct demo_priv), GFP_KERNEL);
/* 其他工作 */
return 0;
}
函数的参数基本固定,就是我们之前私有数据中的 platform_device 结构体。我这个实例里先列了几个比较基本的功能:检验和内存分配。
static int demo_probe(struct platform_device *pdev)
{
int ret = 0;
/* 输出log用于debug */
printk(KERN_ERR"[demo] get in the demo probe!\n");
/* 内存空间分配 */
demo = kzalloc(sizeof(struct demo_priv), GFP_KERNEL);
if (!demo) { // 返回值出现错误,说明函数执行错误,没有分配好内存
ret = -ENOMEM;
printk(KERN_ERR"[demo] kzalloc fail\n");
goto exit; // 跳转至后方处理部分
}
/* 给dev赋值 */
demo->pdev = pdev;
dev_set_drvdata(&pdev->dev, data);
/* kobj */
data->kobj = kobject_create_and_add("demo", NULL);
if (data->kobj == NULL) { // kobject 创建失败
printk(KERN_ERR"[demo] create kobj failed!\n");
goto kobj_exit; // 跳转至处理阶段
}
/* device */
/* 函数中 &dev_attr_demo 与原代码中 DEVICE_ATTR 相关,本文不涉及 */
ret = device_create_file(&pdev->dev, &dev_attr_demo);
if (ret) { // device 设备创建失败
printk(KERN_ERR"[demo] create devive node failed!\n");
goto dev_exit; // 跳转至处理阶段
}
/* sysfs */
ret = sysfs_create_link(data->kobj, &pdev->dev.kobj, "demo");
if (ret) { // sysfs 节点创建映射失败
printk(KERN_ERR"[demo] create sysfs link failed!\n");
goto sys_exit; // 跳转至处理阶段
}
/* gpio 申请中断 */
/* 原代码中这个函数是自己在前文写的,因为申请中断又要申请 gpio 复用
* 又得申请中断还得使能,而且原驱动涉及了多个引脚,直接写在 probe 函数中冗长乏味
* 所以将这些操作自己打包成专用函数使用,在这里可以简单看做申请了一个 gpio 口
* probe 中推荐将代码写的清晰易懂,可以长但不要繁琐冗长,方便 debug 或者后人 */
ret = gpio_to_irq_init(data);
if (ret) { // 申请 gpio 失败
printk(KERN_ERR"[demo] init gpio failed!\n");
goto sys_exit; // 跳转至处理阶段
}
/* 以上阶段都没有出错,才会进入到 return 0 */
return 0;
/* 处理错误阶段 */
/* 这里要按照以上初始化顺序的倒序来释放
* 因为中间有一个出错,就要将从这开始往前注册的所有内容释放掉
* 这里没有 gpio_free 是因为 gpio 的申请在最后一步
* 如果出错的话压根就不会有,也就无法注销了
* 所以这个也需要考虑逻辑上的顺序,跳转过来后是应该注销当前步骤的,还是上一步的 */
sys_exit:
sysfs_remove_link(data->kobj, "demo");
device_remove_file(&pdev->dev, &dev_attr_demo);
dev_exit:
kobject_del(demo->kobj);
kobj_exit:
kfree(demo);
exit:
return ret; // 返回出错时的 ret 值
}
remove 函数即与 probe 函数相反,用于注销、释放所用到的函数、节点、gpio等资源,一般来说,大部分内容与 probe 里处理错误部分相似,还需要将一些去全局结构体中的指针部分清空之类,与上文实例相对应的 remove 函数:
static int fan_feedback_remove(struct platform_device *pdev)
{
gpio_free(demo->gpio);
sysfs_remove_link(demo->kobj, "demo");
device_remove_file(&pdev->dev, &dev_attr_demo);
kobject_del(demo->kobj);
/* 将之前的 drvdata 和 pdev 设为 NULL */
dev_set_drvdata(&pdev->dev, NULL);
demo->pdev = NULL;
kfree(demo);
return 0;
}
3. probe 函数的启用—— of_match
在之前的字符设备驱动中,整个驱动启用的流程是:
module_init(chrtest_init); // 设置为入口函数
module_init(chrtest_exit); // 设置为出口函数
/* ↓ */ // 上层调用入口函数
static int __init chrtest_init(void) // 执行 init 函数
/* ↓ */
alloc_chrdev_region(&devid, 0, 1, CHRDEVTEST_NAME); // init 内注册字符设备驱动
/* ↓ */ // 上层调用出口函数
static void __exit chrtest_exit(void) //执行 exit 函数
/* ↓ */
unregister_chrdev_region(devid, 1); // exit 内注销字符设备驱动
那么现在用到了 platform 设备驱动,很大程度上因为需要有条理地控制多个设备,并且在所有驱动需要控制的设备中,找到自己需要控制的设备,这就和传说中因为 Linus 一句脏话而引入的设备树完美契合
那么我们先了解一下 platform 设备中基本的启用流程再去具体学习什么是设备树。其实相较于字符设备驱动,只是多了两个部分:struct of_device_id / struct platform_driver,示例:
/* 与设备树进行设备匹配的 of_match_table */
static struct of_device_id demo_of_match[] = {
/* 用于匹配的 compatible 项 */
{ .compatible = "demodemo", },
{ }, // 必不可少的一行
};
/* platform 驱动结构体 */
static struct platform_driver demo_driver = {
/* 定义本驱动中 probe 函数是 demo_probe */
.probe = demo_probe,
/* 定义本驱动中 remove 函数是 demo_probe */
.remove = demo_remove,
/* 定义本驱动中的 owner 和 name
* 以及接下来的重点,用于匹配设备树的 of_match_table 是 demo_of_match */
.driver = {
.owner = THIS_MODULE,
.name = "demo",
.of_match_table = of_match_ptr(demo_of_match),
}
};
/* 入口函数 */
static int __init demo_init(void)
{
/* 注册 platform 设备驱动 */
return platform_driver_register(&demo_driver);
}
/* 出口函数 */
static void __exit demo_exit(void)
{
/* 注销 platform 设备驱动 */
platform_driver_unregister(&demo_driver);
}
/* 声明入口、出口函数 */
module_init(demo_init);
module_exit(demo_exit);
那么,在此 platform 驱动中,启用的流程是这样的:
module_init(demo_init); // 设置为入口函数
module_init(demo_exit); // 设置为出口函数
/* ↓ */ // 上层调用入口函数
static int __init demo_init(void) // 执行 init 函数
/* ↓ */
platform_driver_register(&demo_driver); // init 内注册 platform 设备驱动
/* ↓ */
static struct platform_driver demo_driver // 按照此结构体数据注册
/* ↓ */
static struct of_device_id fan_feedback_of_match[]; // 调用此结构体数组
/* ↓ */
{ .compatible = "HONDALY, fan-ctrl", }, // compatible 项与设备树中比对
/* ↓ */ // 匹配成功
static int demo_probe(struct platform_device *pdev) // 执行 probe 函数
/* ↓ */ // 上层调用出口函数
static void __exit demo_exit(void) // 执行 exit 函数
/* ↓ */
platform_driver_unregister(&demo_driver); // exit 内注销 platform 设备驱动
/* ↓ */
static struct platform_driver demo_driver // 按照此结构体数据注销
/* ↓ */
static int demo_remove(struct platform_device *pdev) // 执行 remove 函数
看着繁琐了很多,但实际上编写时我们只需要额外编写 of_match_table / platform_driver 即可,剩下的流程就不用我们管啦,而且这样,可以在项目中很好地联系驱动与设备。
现在,联系着驱动与设备的 of_match_table 已经完美建立起来,驱动.c这边的工作全部完成,接下来就需要在设备上完善体系,这就要用到一直提到的设备树。由于篇幅问题,以及设备树也有许多笔记,我们下一篇再继续详细记录。
以上便是本篇全部内容,感谢阅读。