目录
简介:
一、初识platform平台设备驱动
1、platform_driver驱动代码框架
2、platform_device设备代码框架
3、测试结果
3.1 Makefile编译
3.2 加载驱动
二、platform框架分析
1、注册platform总线
1.1 创建platform平台总线函数调用流程
1.2 platform_bus_init() 函数
2、注册platform_driver
2.1 platform_driver_register函数流程
2.2 匹配过程
2.3 调用probe函数
3、注册platform_device
3.1 platform_device_register函数流程
三、dts设备树
1、设备树的匹配过程
2、设备树与platform_driver匹配示例
2.1 dts设备树文件
编辑
2.2 platform_driver 驱动代码
简介:
Linux系统为了驱动的可重用性,提出驱动的分离与分层的软件思路,为了保持设备驱动的统一性,platform平台设备驱动模型就此诞生。相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是虚拟、抽象出来的总线,实际中并不存在这样的总线。
说明:本文基于Linux版本为4.1.15
一、初识platform平台设备驱动
platform平台总线下有驱动链表和设备链表,当调用 platform_driver_register() 注册platform驱动,或调用 platform_device_register() 注册platform设备时,都会执行match匹配函数。当链表中有platform驱动和platform设备匹配上,就会调用platform驱动的 probe() 函数。
图解platform_driver和platform_device匹配过程:
比较 platform_driver 的 driver->name 与 platform_device 的 name 相同都为 "myled",就会执行 platform_driver 的 probe函数,这里为 led_probe。led_probe函数由我们自己定义实现注册字符设备等功能。
platform 总线设备驱动大概有以下步骤:
- platform_device_register() 注册平台设备
- platform_driver_register() 注册平台驱动
- platform总线自动匹配name,匹配上就调用 driver 的 .probe
- probe中注册字符设备等操作
1、platform_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>
/* 当驱动driver->name和设备name相同, 则调用驱动的.probe函数, .probe函数可以做任何事情 */
static int led_probe(struct platform_device *pdev)
{
/* 注册字符设备等操作 */
printk("platform_driver probe run!\n");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 卸载字符设备等操作 */
printk("platform_driver remove run!\n");
return 0;
}
/* 定义平台drv,通过.name来比较dev */
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
}
};
static int led_drv_init(void)
{
platform_driver_register(&led_drv); //注册驱动,最终调用driver_register()
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");
MODULE_AUTHOR("dongao");
2、platform_device设备代码框架
#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>
static void led_release(struct device * dev) //构造一个release函数,防止报错
{
}
/* platform_device结构体.name与platform_driver的driver->name比较 */
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
//.num_resources = ARRAY_SIZE(led_resource), //可以将device的资源传给driver使用
//.resource = led_resource,
.dev = {
.release = led_release,
},
};
static int led_dev_init(void)
{
platform_device_register(&led_dev); //注册1个平台设备,最终调用device_add()
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev); //卸载1个平台设备
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongao");
3、测试结果
3.1 Makefile编译
KERN_DIR = /home/linux-imx-rel_imx_4.1.15_2.1.0
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv.o
obj-m += led_dev.o
3.2 加载驱动
加载编译出来的2个ko文件。
$ insmod led_dev.ko # 加载 platform_device 驱动
$ insmod led_drv.ko # 加载 platform_driver 驱动
结果:
结果,运行probe 打印 "platform_driver remove run!"
查看在系统中的驱动:
$ ls /sys/bus/platform/devices
$ ls /sys/bus/platform/drivers/
二、platform框架分析
Linux的这种platform driver机制和传统的device_driver机制相比,一个十分明显的优势在于platform机制将本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform_device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。
1、注册platform总线
1.1 创建platform平台总线函数调用流程
start_kernel() //启动linux内核,路径:init/main.c
--->rest_init() //init/main.c
--->--->kernel_init() //1号进程
--->--->--->kernel_init_freeable()
--->--->--->--->do_basic_setup()
--->--->--->--->--->driver_init() //初始化driver模块,路径:drivers\base\init.c
--->--->--->--->--->--->platform_bus_init()
// 注册平台总线,路径:drivers\base\platform.c
->bus_register(&platform_bus_type)
bus_register(&platform_bus_type) 注册了platform总线platform_bus_type,对应sysfs下的 /sys/bus/platform 目录。
1.2 platform_bus_init() 函数
/* 总线结构体为 struct bus_type */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type); //platform_bus_type 平台总线
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type); // 注册平台总线
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
总线注册:
bus_register //注册一条总线,路径:drivers\base\bus.c
bus_unregister //注销总线
bus_register(&platform_bus_type) 注册了平台总线 platform_bus_type ,之后在注册platform_driver和platform_device时会与platform_bus_type 建立联系。
2、注册platform_driver
platform_driver_register() 源码路径:include/linux/platform_device.h
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
drv->driver.bus = &platform_bus_type; 与 platform_bus_type 建立联系,并设置了 probe、remove、shutdown函数。
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
2.1 platform_driver_register函数流程
platform_driver_register(drv)
--->__platform_driver_register(drv, THIS_MODULE) // drivers/base/platform.c
--->--->driver_register(&drv->driver) // drivers/base/driver.c
--->--->--->bus_add_driver(drv) // drivers/base/bus.c
--->--->--->--->driver_attach(drv) // drivers/base/dd.c
--->--->--->--->--->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) // drivers/base/bus.c
--->--->--->--->--->--->__driver_attach() // drivers/base/dd.c
--->--->--->--->--->--->--->driver_match_device(drv, dev) // dev 和 drv 匹配
--->--->--->--->--->--->--->driver_probe_device(drv, dev)
--->--->--->--->--->--->--->--->really_probe(dev, drv)
dev->driver = drv; // dev 和 drv 建立联系
drv->probe(dev)
bus_for_each_dev 会轮询每个dev,并调用__driver_attach。__driver_attach 非常关键,它主要匹配dev和drv,匹配上就会调用drv的probe()。
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
if (!driver_match_device(drv, dev)) // drv 和 dev 匹配
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev); // 调用probe
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
2.2 匹配过程
driver_match_device() 函数将drv和dev做匹配,返回匹配结果。
// drivers\base\base.h
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
最终调用 platform_bus_type 总线的match函数, platform_match() 如下:
// drivers\base\platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
platform_match() 中有4种匹配方式:
(1)通过of_driver_match_device:设备树方式匹配,device_driver结构体里面的of_match_table变量保存着驱动的compatible的属性字符串表。设备树中的每个节点的compatible属性会of_match_table 表中的所有成员比较,相同则匹配,设备和驱动匹配成功以后 probe 函数就会执行;
(2)ACPI 匹配方式;
(3)id_table 匹配。每个 platform_driver 结构体有一个 id_table成员变量,它保存了很多 id,这些 id 信息存放着这个 platformd 驱动所支持的驱动类型;
(4)如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
上面例子实际上用第四种匹配方式
return (strcmp(pdev->name, drv->name) == 0);
2.3 调用probe函数
platform_match() 匹配OK后,执行driver_probe_device(),最终执行really_probe(),really_probe实现了probe()函数的调用。
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev))
return -ENODEV;
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
... ...
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
... ...
}
drv->probe(dev) 就是在 __platform_driver_register 中注册的函数,它指向 platform_drv_probe()
static int platform_drv_probe(struct device *_dev)
{
//从 struct device_driver 倒推 struct platform_driver 结构体
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
... ...
if (drv->probe) {
ret = drv->probe(dev); //执行 platform_driver 的probe()函数
if (ret)
dev_pm_domain_detach(_dev, true);
}
... ...
}
to_platform_driver调用container_of从成员struct device_driver倒推宿主结构体 platform_driver。并执行我们自己定义的 platform_driver驱动 中的 probe() 函数。
3、注册platform_device
注册platform_device其实跟注册platform_driver极其相似。主要也是匹配和调用platform_driver的probe()函数。
3.1 platform_device_register函数流程
platform_device_register() 源码路径:drivers\base\platform.c
platform_device_register(struct platform_device *dev) //路径:drivers\base\platform.c
--->platform_device_add(pdev);
--->--->device_add(&pdev->dev); //drivers\base\core.c
--->--->bus_probe_device(dev); //drivers\base\bus.c
--->--->--->device_attach(dev); //drivers\base\dd.c
--->--->--->--->bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
--->--->--->--->--->__device_attach
--->--->--->--->--->--->driver_match_device(drv, dev);// 匹配成功才向下执行probe!
--->--->--->--->--->--->--->driver_probe_device(drv, dev);
--->--->--->--->--->--->--->--->really_probe(dev, drv);
--->--->--->--->--->--->--->--->--->dev->bus->probe(dev);
或者 drv->probe(dev);
bus_for_each_drv 会轮询每个drv,调用到 __device_attach 核心代码
// drivers\base\dd.c
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
if (!driver_match_device(drv, dev))
return 0;
return driver_probe_device(drv, dev);
}
只有当 driver_match_device() 匹配成功才会调用 driver_probe_device() 函数,driver_probe_device() 最终执行了 platform_drviver 的probe()函数,这些上面都已经介绍了,不再赘述。
三、dts设备树
在Linux 2.6及之前,大量板级信息被硬编码到内核里,十分庞大,大量冗余代码,此背景下,引入dts设备树。
在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。使用设备树时,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver ,与设备树中的platform_device 匹配即可。
1、设备树的匹配过程
platform_match() 中有4种匹配方式,设备树和platform_driver匹配函数为 of_driver_match_device() 。
// 匹配函数
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
// drivers/of/device.c
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
// drivers/of/base.c
const struct of_device_id *of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *match;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
match = __of_match_node(matches, node);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return match;
}
EXPORT_SYMBOL(of_match_node);
static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches)
return NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}
__of_device_is_compatible() 最终会比较设备树子节点的 "compatible" 与 驱动中 of_match_table->compatible,判断是否匹配。
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!device->type || of_node_cmp(type, device->type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!device->name || of_node_cmp(name, device->name))
return 0;
score++;
}
return score;
}
至于dts设备树如何解析生成 struct device_node的?之后会专门写一篇博文介绍。
2、设备树与platform_driver匹配示例
2.1 dts设备树文件
/ {
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "donga-gpioled"; /* platform 总线通过 compatible 属性值来匹配驱动 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>; /* 设置 LED 灯所使用的 PIN 对应的 pinctrl 节点 */
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; /* 指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1 的 IO03,低电平有效 */
status = "okay";
};
}
查看设备树中gpioled是否生效
$ ls /proc/device-tree/ # 查看设备树
$ cat /proc/device-tree/gpioled/compatible # 查看gpioled的compatible
2.2 platform_driver 驱动代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1 /* 设备号长度 */
#define LEDDEV_NAME "dtsplatled" /* 设备名字 */
static dev_t devid; /* 设备号 */
static int major; /* 主设备号 */
static struct cdev cdev; /* cdev */
static struct class *class; /* 类 */
static struct device *device; /* 设备 */
static int led_open(struct inode *inode, struct file *filp)
{
printk("led_open run!\n");
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
printk("led_write run!\n");
return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
/* probe函数,当驱动与设备匹配以后此函数就会执行 */
static int led_probe(struct platform_device *dev)
{
printk("platform_driver probe run!\n");
/* 1、设置设备号 */
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, LEDDEV_CNT, LEDDEV_NAME);
} else {
alloc_chrdev_region(&devid, 0, LEDDEV_CNT, LEDDEV_NAME);
major = MAJOR(devid);
}
/* 2、注册设备 */
cdev_init(&cdev, &led_fops);
cdev_add(&cdev, devid, LEDDEV_CNT);
/* 3、创建类 */
class = class_create(THIS_MODULE, LEDDEV_NAME);
if (IS_ERR(class)) {
return PTR_ERR(class);
}
/* 4、创建设备 */
device = device_create(class, NULL, devid, NULL, LEDDEV_NAME);
if (IS_ERR(device)) {
return PTR_ERR(device);
}
/* of函数获取dts中的资源,gpio等操作,略 */
return 0;
}
/* platform驱动的remove函数,移除platform驱动的时候此函数会执行 */
static int led_remove(struct platform_device *dev)
{
printk("platform_driver remove run!\n");
cdev_del(&cdev); /* 删除cdev */
unregister_chrdev_region(devid, LEDDEV_CNT); /* 注销设备号 */
device_destroy(class, devid);
class_destroy(class);
return 0;
}
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "donga-gpioled" }, /* 此参数要与设备树匹配 */
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "led_drv", /* 驱动名字,用于和设备匹配 */
.of_match_table = led_of_match, /* 设备树匹配表 */
},
.probe = led_probe,
.remove = led_remove,
};
/* 驱动模块加载函数 */
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
/* 驱动模块卸载函数 */
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");
insmod加载驱动后打印 "platform_driver probe run!"
同时probe中创建了字符设备,字符设备相关介绍可以看下这篇文章:
https://blog.csdn.net/hinewcc/article/details/140672331
其他博主画的图非常好,参考文章:
https://blog.csdn.net/qq_16504163/article/details/118562670