Linux驱动编程 - platform平台设备驱动总线
目录
简介:
一、初识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.0all:make -C $(KERN_DIR) M=`pwd` modulesclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-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.cbus_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); // 调用probedevice_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