0
点赞
收藏
分享

微信扫一扫

Platform 设备驱动 / 设备树 (上)

吓死我了_1799 2022-01-12 阅读 111
上一篇:《 字符设备号的静态 / 动态分配 》                          下一篇:                               

 

目录

一、Platform 的作用

二、Platform 设备驱动

1. 私有数据结构体化

2. probe 函数 / remove 函数

3. probe 函数的启用—— of_match


一、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这边的工作全部完成,接下来就需要在设备上完善体系,这就要用到一直提到的设备树。由于篇幅问题,以及设备树也有许多笔记,我们下一篇再继续详细记录。

        以上便是本篇全部内容,感谢阅读。

举报

相关推荐

0 条评论