当前位置: 首页 > news >正文

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_driverplatform_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



http://www.mrgr.cn/news/28055.html

相关文章:

  • JAVA接入WebScoket行情接口
  • Design Compiler:Topographical Workshop Lab2
  • UEFI PEI阶段的一些基本概念
  • Docker部署Kafka SASL_SSL认证,并集成到Spring Boot
  • Flutter:android studio无法运行到模拟机的问题
  • 数字化转型企业架构设计手册(交付版),企业数字化转型建设思路、本质、数字化架构、数字化规划蓝图(PPT原件获取)
  • Linux:vim编辑技巧
  • 优思学院|质量工程师在APQP中具体做哪些工作?
  • Linux基础开发环境(git的使用)
  • PCIe进阶之TL:Completion Rules TLP Prefix Rules
  • 【计算机毕设-大数据方向】基于Hadoop的在线教育平台数据分析可视化系统的设计与实现
  • 微服务实战系列之玩转Docker(十五)
  • 代码随想录训练营第34天|dp前置转移
  • Unity多国语言支持
  • 改进RRT*的路径规划算法
  • 让水凝胶不再怕溶胀:一步浸泡,拥有抗溶胀 “盔甲”
  • 【第12章】SpringBoot之SpringBootActuator服务监控(上)
  • 克隆虚拟机,xshell无法传文件,windows无法ping克隆虚拟机,已解决
  • Pandas缺失值处理
  • Dina靶机详解
  • JDBC注册驱动及获取连接
  • 【字幕】恋上数据结构与算法之015动态数组03简单接口的实现
  • TikTok商家如何通过真人测评提高流量和销量?
  • C++之AVL树
  • VUE3初学者必备的快速开发入门指南
  • 系统架构设计师教程 第5章 5.6 基于构件的软件工程 笔记