4412开发板学习之Linux驱动开发(二):总线、设备、驱动注册流程

总线、设备、驱动注册流程图

在这里插入图片描述

说明

以上这个图片是相当重要的一个图片吗,它完整诠释了设备和驱动的注册流程。

首先关于平台platform总线

总线是Linux中很重要的一个概念,有许多条总线,比如media总线、SPI总线、mmc存储设备总线、平台总线(platform)等等,对于我们驱动开发工程师来说,最重要的,要深刻理解的就是平台platform总线了,相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。那为什么需要platform总线呢?其实是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与我们的cpu进行数据交互的,但是在我们的嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。
platform总线相关代码:driver\base\platform.c 文件
相关结构体定义:include\linux\platform_device.h 文件中

关于设备

设备代表真实存在的物理器件,每个器件有自己不同的通信时序,I2C、USB这些都代表不同的时序,这就与总线挂钩了
在最低层, Linux 系统中的每个设备由一个 struct platform_device 代表

 	struct platform_device {
			const char	* name;	//设备名称
			int		id;			//对同类设备的区分标识
			struct device	dev;//结构体中内嵌device结构体
			u32		num_resources;//设备使用资源的数量
			struct resource	* resource;//设备使用的资源数组
		
			const struct platform_device_id	*id_entry;
		
			/* MFD cell pointer */
			struct mfd_cell *mfd_cell;
		
			/* arch specific additions */
			struct pdev_archdata	archdata;
		};

Linux的所有设备由一个设备链表来维护,依次从Device0到DeviceN
我们可以通过device_register()来注册设备

关于驱动

驱动代表操作设备的方式和流程,以应用来说,在程序open设备时,接着read这个这个设备,驱动就是实现应用访问的具体过程。驱动就是一个通信官和翻译官,一是通过对soc的控制寄存器编程,按总线要求输出相应时序的命令,与设备交互,一是对得到数据进行处理,给上层提供特定格式数据。

  • 不同总线的设备驱动过程不同
  • 同一总线的不同类型设备驱动不同,光感和加速度
  • 同一总线的同类设备驱动不同,多点触控和单点触控
    驱动由一个 struct platform_driver 代表
struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
};

驱动两个重要的操作就是注册register和卸载unregister,函数原型如下

extern int platform_driver_register(struct platform_driver *);//注册驱动函数
extern void platform_driver_unregister(struct platform_driver *);//卸载驱动函数

这两个函数的参数都是只有 platform_driver
驱动重点关注操作的流程和方法,如果在驱动中直接针对具体的引脚来编程,其驱动的平台可移植性就比较差,为了提高驱动的可移植性,Linux把驱动要用到的GPIO和中断等资源剥离给设备去管理。即在设备里面包含其自己的设备属性,还包括了其连接到SOC所用到的资源。

注册设备

以我我现在的水平肯定是不会去撸源码的,我现在追求的就是先掌握设备、驱动的注册流程,先能用起来,当然这部分的源码是很重要的,以后还是会去仔细分析的

注册设备的流程

  • 1、注册设备,将设备结构体放到平台文件中,会自动注册设备,不用去调用注册设备的函数
vim arch/arm/mach-exynos/mach-itop4412.c

两个修改点

#ifdef CONFIG_HELLO_CTL
struct platform_device s3c_device_hello_ctl={
	.name = "hello_ctl",
	.id             = -1,
};
#endif
#ifdef CONFIG_HELLO_CTL
	&s3c_device_hello_ctl,
#endif
  • 2、在Kconfig文件中添加编译HELLO设备的宏定义
config HELLO_CTL
tristate "Enable HELLO config"
default y
help
Enable HELLO config
  • 3、配置menuconfig中的HELLO宏定义,生成新的.config文件
    在这里插入图片描述
  • 4、生成新的zImage,烧录
    执行make zImage
    然后到arch/arm/boot中寻找zImage

查看设备是否注册成功

通过以上的步骤就完成了设备的注册,这时进入系统我们可以查看一下开发板的设备,应该能找到我们的hello_ctl

ls /sys/devices/platform/

在这里插入图片描述
在这里插入图片描述
说明我们设备注册成功

注册驱动(很重要)

一些说明

驱动常见的几种状态:初始化、移除、断电、休眠、恢复

  • probe函数
    platform_match函数匹配之后,驱动调用的初始化函数
  • remove函数
    移除驱动函数
  • shutdown函数
    断电后执行的函数
  • suspend函数
    悬挂(休眠)驱动函数
  • resume函数
    休眠后恢复驱动
    在platform_driver结构体中还有一个device_driver结构体类型的变量,所以我们也关注一些device_driver结构体
struct device_driver {
	const char		*name;//和注册的设备name要一致
	struct bus_type		*bus;
		
	struct module		*owner;//一般赋值THIS_MODULE
	const char		*mod_name;	/* used for built-in modules */
		
	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
		
	const struct of_device_id	*of_match_table;
		
	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;
		
	const struct dev_pm_ops *pm;
		
	struct driver_private *p;
};

编写驱动程序

#include <linux/init.h>
#include <linux/module.h>
/*关于驱动注册的头文件,包含一些函数和重要的结构体*/
#include <linux/platform_device.h>

#define DRIVER_NAME "hello_ctl"		//驱动名,注意要与设别名相同
/*开源协议声名和作者*/
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("GYY");

/*驱动初始化函数*/
static int hello_probe (struct platform_device *pdv){
	printk(KERN_EMERG "\tinitialized\n");//打印initialized
	return 0;
}
/*移除驱动函数*/
static int hello_remove (struct platform_device *pdv){
	printk(KERN_EMERG "\tremove\n");//打印remove
	return 0;
}
/*断电后驱动执行的函数*/
static void hello_shutdown (struct platform_device *pdv){
	
	
}
/*驱动休眠函数*/
static int hello_suspend (struct platform_device *pdv){
	
	return 0;
}
/*驱动恢复函数*/
static int hello_resume (struct platform_device *pdv){
	
	return 0;
}

/*重要的platform_driver结构体*/
struct platform_driver hello_driver = {
	/*一些函数的注册,通过函数指针的形式传入*/
	.probe = hello_probe,
	.remove = hello_remove,
	.shutdown = hello_shutdown,
	.suspend = hello_suspend,
	.resume = hello_resume,
	/*device_driver结构体*/
	.driver = {
		.name = DRIVER_NAME,//驱动名,注意要与设备名相同
		.owner = THIS_MODULE,//THIS——MODULE
	}
};

/*驱动入口函数*/
static int hello_init(void)
{
	int DriverState;
	printk(KERN_EMERG "HELLO WORLD enter!\n");
	DriverState=platform_driver_register(&hello_driver);//注册驱动
	
	printk(KERN_EMERG "\t%d\n",DriverState);//打印驱动状态,注册成功的话为0
	return 0;
}
/*驱动的出口函数*/
static void hello_exit(void)
{
	printk(KERN_EMERG "HELLO WORLD exit!\n");
	platform_driver_unregister(&hello_driver);//卸载驱动
}

module_init(hello_init);
module_exit(hello_exit);

代码分析

  • 入口函数
    我们在驱动的入口函数执行了platform_driver_register(),所以我们看一下platform_driver_register的源码
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

这个函数首先初始化了总线类型,接着注册了probe、remove、shutdown函数,最后执行了driver_register()函数
到这里就不再深究了,但是我们知道在注册驱动的时候是会调用probe函数的,所以说当我们安装驱动(insmod)时我们应该先打印出“HELLO WORLD enter!”,接着打印initialized,最后注册完后打印驱动的状态

  • 出口函数
    我们在驱动的出口函数执行了platform_driver_unregister(),platform_driver_unregister()的源码很简单
void platform_driver_unregister(struct platform_driver *drv)
{
	driver_unregister(&drv->driver);
}

它就是调用了driver_unregister()这个函数
在我们卸载驱动的时候会执行驱动程序的remove函数,所以说当我们卸载驱动(rmmod)时会先打印出”HELLO WORLD exit!“,接着调用remove函数打印出remove

实验测试

安装驱动

在这里插入图片描述
符合我们的预期

查看驱动

在这里插入图片描述

卸载驱动

在这里插入图片描述
符合我们的预期

几句总结

要学习驱动开发,设备、驱动的注册是相当的重要的,但是Linux的源码也是相当复杂的,仅platform.c这个文件就有1200多行的代码量,一开始就一头扎进源码里显然是不太现实的,所以说还是先学会怎么用,以后慢慢的深入,相信自己会在不久的将来可以理清原理,手撕源码,写一些详尽的分析笔记。

发布了123 篇原创文章 · 获赞 598 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/a568713197/article/details/89642396