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.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_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);    // 调用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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值