Linux设备驱动模型_linux驱动模型

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录


前言


1.linux设备驱动模型简介

1、什么是设备驱动模型
(1)类class、总线bus、设备device、驱动driver内核代码中有4个结构体
(2)kobject和对象生命周期自动管理生命周期
(3)sysfs虚拟文件系统在内核空间和用户空间间建立关系把内核的结构体变量的一些值以文件的形式展现出来通过echo、cat操作属性节点
(4)udev实现内核空间和用户空间的信息的同步和转换

2、为什么需要设备驱动模型
(1)早期内核2.4之前没有统一的设备驱动模型但照样可以用
(2)2.6版本中正式引入设备驱动模型目的是在设备越来越多功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
(3)设备驱动模型负责统一实现和维护一些特性诸如电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施
(4)设备驱动模型目的是简化驱动程序编写但是客观上设备驱动模型本身设计和实现很复杂。

3、驱动开发的2个点
(1)驱动源码本身编写、调试。重点在于对硬件的了解。
(2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关完全和设备驱动模型有关。

2.设备驱动模型的底层架构

1、kobject

struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	struct kobj_type	*ktype;
	struct sysfs_dirent	*sd;
	struct kref		kref;
	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
};

(1)定义在linux/kobject.h中
(2)各种对象最基本单元提供一些公用型服务如对象引用计数kref其实是帮助维护对象的生命周期当引用计数归零就可以释放了、维护对象链表、对象上锁(竞争状态避免)、对用户空间的表示kobj_type
(3)设备驱动模型中的各种对象其内部都会包含一个kobject
(4)地位相当于面向对象体系架构中的总基类

2、kobj_type

struct kobj_type {
	void (*release)(struct kobject *kobj);	//释放不同于close我们可能要反复打开在release时要判断他还有没有被别人打开检测对象的引用计数
	const struct sysfs_ops *sysfs_ops;	//store,set函数
	struct attribute **default_attrs;	//属性
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
};

(1)很多书中简称为ktype每一个kobject都需要绑定一个ktype来提供相应功能注意包含是包含了一个变量而绑定是包含了一个指针
(2)关键点1sysfs_ops提供该对象在sysfs中的操作方法show和store
(2)关键点2attribute提供在sysfs中以文件形式存在的属性其实就是应用接口

3、kset

struct kset {
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;// kset包含kobjkobj绑定kset所以kset是比kobj更大的结构体
	const struct kset_uevent_ops *uevent_ops;
};

(1)kset的主要作用是做顶层kobject的容器类
(2)kset的主要目的是将各个kobject代表着各个对象组织出目录层次架构
(3)可以认为kset就是为了在sysfs中弄出目录从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起

3.总线式设备驱动组织方式

1、总线
(1)物理上的真实总线及其作用英文bus
(2)驱动框架中的总线式设计
cpu总线式管理驱动首先创建一些总线如USB总线pci总线然后操作系统管理好总线就可以由总线来管理驱动总线还分两个分支驱动和设备设备之间、驱动之间由链表连起来那么所有的设备来注册比如插了一个热插拔usb设备系统就将设备添加到usb总线设备里面去然后到驱动的链表下面去找安装相应的驱动。总线也是一堆代码插入删除遍历查找……
(3)bus_type结构体(总线的模板)关键是match函数做总线下面的设备和驱动的匹配和uevent函数

2、设备
(1)struct device是硬件设备在内核驱动框架中的抽象
(2)device_register用于向内核驱动框架注册一个设备
(3)通常device不会单独使用而是被包含在一个具体设备结构体中如struct usb_device

3、驱动
(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1name驱动程序的名字很重要经常被用来作为驱动和设备的匹配依据
(3)关键元素2probe驱动程序的探测函数用来检测一个设备是否可以被该驱动所管理

4、类
(1)相关结构体struct class 和 struct class_device
(2)udev热插拔的实现的使用离不开class
(3)class的真正意义在于作为同属于一个class的多个设备的容器。类发明来就是来管理设备的bus也管理设备class也管理设备设备是要进行多重管理的这是不同的思路和管理方法。比如摄像头和U盘在bus角度看都是USB设备但是在class角度看一个是大容量存储设备一个是摄像头。其实class和bus目录下都是devices的链接文件也就是说class是一种人造概念目的就是为了对各种设备进行分类管理。当然class在分类的同时还对每个类贴上了一些“标签”这也是设备驱动模型为我们写驱动提供的基础设施。
一个设备需要有多种方法来管理class和bus就是不同的管理方式。sys/devices才是真正的设备从class或bus进去最终都会指向devices目录下。

5、总结
(1)模型思想很重要其实就是面向对象的思想
(2)全是结构体套结构体对基本功语言功底和大脑复杂度要求很高

4.platform平台总线工作原理

1、何为平台总线
(1)相对于usb、pci、i2c等物理总线来说platform总线是虚拟的、抽象出来的。
(2)回顾裸机中讲的CPU与外部通信的2种方式地址总线式连接和专用接口式连接比如nand、usb。平台总线对应地址总线式连接设备也就是SoC内部集成的各种内部外设。
(3)思考为什么要有平台总线进一步思考为什么要有总线的概念
比如led这种不需要总线是为了管理的方便

2、平台总线下管理的2员大将
(1)platform工作体系都定义在drivers/base/platform.c中
(2)两个结构体platform_device和platform_driver
(3)两个接口函数platform_device_register和platform_driver_register

struct platform_device {
	const char	* name;			// 平台总线下设备的名字不能和其他设备重
	int		id;
	struct device	dev;		// 所有设备通用的属性部分
	u32		num_resources;		// 设备使用到的resource的个数
	struct resource	* resource;	// 设备使用到的资源数组的首地址在不同的设备中需要的资源不一样所以这里不是包含一个数组而是直接连接一个指针具体的resource结构体数组会在具体的设备的.c文件中描述
	const struct platform_device_id	*id_entry;	// 设备ID表用于多个相像的设备比如公司同时发布5款inand这五款inand除了大小不同其他如页表访问方式之类的都相同这时候就可以用一个设备结构体来描述他们而用ID表来区分五款不同大小的inand
	/* arch specific additions */
	struct pdev_archdata	archdata;				// 自留地用来提供扩展性的
};

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;				// 所有设备共用的一些属性device_driver中也带有上面定义的函数不知道是否重复
	const struct platform_device_id *id_table;	// 设备ID表
};

3、平台总线体系的工作流程
(1)第一步系统启动时在bus系统中注册platform
(2)第二步内核移植的人负责提供platform_device
(3)第三步写驱动的人负责提供platform_driver
(4)第四步platform的match函数发现driver和device匹配后调用driver的probe函数来完成驱动的初始化和安装然后设备就工作起来了

4、代码分析platform本身注册
(1)每种总线不光是platformusb、i2c那些也是都会带一个match方法match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的但是实际上一般都是看name的。
(2)platform_match函数就是平台总线的匹配方法。该函数的工作方法是如果有id_table就说明驱动可能支持多个设备所以这时候要去对比id_table中所有的name只要找到一个相同的就匹配上了不再找了如果找完id_table都还没找到就说明没匹配上如果没有id_table或者没匹配上那就直接对比device和driver的name如果匹配上就匹配上了如果还没匹配上那就匹配失败。
调用路径

platform_bus_init
	device_register(&platform_bus)
	bus_register(&platform_bus_type)
上述两个结构体
struct device platform_bus = {
	.init_name	= "platform",
};
struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
platform_match函数
	to_platform_device
			#define to_platform_driver(drv)	(container_of((drv), struct platform_driver, driver))
	to_platform_driver

注意到platform_match函数传参进来是struct device *, struct device_driver *类型的指针为什么不直接传platform_device *和platform_driver *类型的指针而要在这里用container_of做一次转换因为希望程序保持一个一致性在声明bus_type结构体时声明函数如下int (*match)(struct device *dev, struct device_driver *drv);所以这里传参也要传这两个参数。那为什么声明bus_type结构体时声明函数传参类型为struct device *, struct device_driver *因为在定义这个bus_type时还不知道将来会被拿来定义什么类型的bus,换句话说bus_type是通用的struct device * devstruct device_driver *drv也是通用的在我们这里是被platform_device *platform_driver *结构体包含的。
driver、device、bus通用结构体和平台专用结构体

struct device_driver
struct device {
	struct kobject kobj;
	struct device_type	*type;
	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;
};
struct bus_type {
	const char		*name;
	struct bus_attribute	*bus_attrs;
	struct device_attribute	*dev_attrs;
	struct driver_attribute	*drv_attrs;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	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 dev_pm_ops *pm;
	struct bus_type_private *p;
}; 
/*struct device_type {
*	const char *name;
*	const struct attribute_group **groups;
*	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
*	char *(*devnode)(struct device *dev, mode_t *mode);
*	void (*release)(struct device *dev);
*
*	const struct dev_pm_ops *pm;
*};*/
struct bus_type platform_bus_type 
struct device platform_bus 

5、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
(1)platform_driver_register
注册以下驱动

static struct platform_driver s3c24xx_led_driver = {
	.probe		= s3c24xx_led_probe,	//按照led的驱动框架来注册led驱动
	.remove		= s3c24xx_led_remove,
	.driver		= {
		.name		= "s3c24xx_led",
		.owner		= THIS_MODULE,
	},
};

(2)platform_device_register
数据和驱动分离数据就是设备定义的结构体驱动和设备的name一样所以搜name要自己写需要参考

static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,		//如果是-1,就是让内核自动分配
	.dev		= {
		.platform_data	= &mini2440_led1_pdata,	//led所特有的平台数据
	},
};
// mini2440_led1_pdata的结构体类型是led特有的平台数据
struct s3c24xx_led_platdata {
	unsigned int		 gpio;
	unsigned int		 flags;
	char			*name;
	char			*def_trigger;	//默认的触发方式4个led就有四个触发方式也就有四个这样的结构体。
};
static struct platform_device *mini2440_devices[] __initdata = {
	&s3c_device_ohci,
	&s3c_device_wdt,
	&s3c_device_i2c0,
	&s3c_device_rtc,
	&s3c_device_usbgadget,
	&mini2440_device_eth,
	&mini2440_led1,
	&mini2440_led2,
	&mini2440_led3,
	&mini2440_led4,
	&mini2440_button_device,
	&s3c_device_nand,
	&s3c_device_sdi,
	&s3c_device_iis,
	&mini2440_audio,
};

注册device函数

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
int platform_add_devices(struct platform_device **devs, int num)
{
	int i, ret = 0;

	for (i = 0; i < num; i++) {
		ret = platform_device_register(devs[i]);
		if (ret) {
			while (--i >= 0)
				platform_device_unregister(devs[i]);
			break;
		}
	}
	return ret;
}

6、platdata怎么玩
(1)platdata其实就是设备注册时提供的设备有关的一些数据譬如设备对应的gpio、使用到的中断号、设备名称····
(2)这些数据在设备和驱动match之后会由设备方转给驱动方。驱动拿到这些数据后通过这些数据得知设备的具体信息然后来操作设备。
(3)这样做的好处是驱动源码中不携带数据只负责算法对硬件的操作方法。现代驱动设计理念就是算法和数据分离这样最大程度保持驱动的独立性和适应性。
步骤1.add4个led设备。2.match将和4个led设备匹配的驱动匹配。3.probe注册

7、match函数的调用轨迹

8、probe函数的功能和意义

9、平台设备共用一个总线plateform.c、plateform_device.h中
platform_device和platform_driver都只是声明了变量未被初始化初始化过程是我们自己填写的根据自己设备的特征在结构体中初始化相应的元素申请相应的资源。
struct device platform_bus和struct bus_type platform_bus_type填充了相应的元素其中所有的plateform设备都将用到其中的match函数来匹配platform_device和platform_driver。
平台设备注册函数
platform_device_register和platform_driver_register
device_register(&platform_bus);和bus_register(&platform_bus_type);

10、结构体所在位置

kobject.h-> kobject
			kobj_type
			kset
device.h-> 	device
			device_driver
			bus_type
platform_device.h->	platform_device
					platform_driver
plateform.c->		struct device platform_bus
					struct bus_type platform_bus_type

这就是架构。

5.平台总线实践环节

1、检查mach-x210.c中是否有led相关的platform_device
我们之前说过platform_device理论上是写内核移植的人负责但是查了platform_device的数组指针smdkc110_devices没有led说明九鼎的x210没有帮我们实现platform_device所以我们要自己实现。
在开机启动时执行smdkc110_machine_init
platform_add_devices(smdkc110_devices)
就将smdk支持的所有设备都添加了。

2、参考mach-mini2440.c中添加led的platform_device定义
在platform_device *smdkc110_devices[]结构体数组中添加struct platform_device x210_led1类型的结构体新创建一个结构体struct platform_device x210_led1还有他的元素struct s3c24xx_led_platdata x210_led1_pdata。参照s3c24xx_led_platdata结构体定义需要包含结构体定义的头文件在我们的s5pv210架构下没有这个头文件所以在kernel\arch\arm\mach-s5pv210\include\mach下新建一个leds-gpio.h文件将kernel\arch\arm\mach-s3c2410\include\mach\leds-gpio.h文件中的内容复制到新建的leds-gpio.h中稍作修改。
SI中add and remove project files将leds-gpio.h添加进来。
在mach-x210.c中包含头文件leds-gpio.h
将修改过的两个文件同步到内核代码中去。

3、测试只有platform_device没有platform_driver时是怎样的

4、测试platform_device和platform_driver相遇时会怎样
在只有一个driver而有三个led device时出错原因三个led的platform_device结构体名字都一样。在执行probe函数时因为有三个device会执行3次第一次执行成功第二次就会执行失败开发板端打印出gpio_request failed因为第一次gpio_request(GPIO_LED1, “led1_gpj0.3”)去申请资源成功而第二次就会被重复申请必然会报错。

5、在probe函数中想办法区分led
虽然四个platform_device名字一样但是s5pv210_led_platdata名字不一样这样既区分了led又把分配gpio的权力给了设备也就是platform_device结构体驱动中尽量不带数据更好的将设备和驱动分离在需要设备信息时将其导入到驱动的匹配函数中。四颗处理led的函数s5pv210_led_set也都不同我们想办法用一个s5pv210_led_set函数去完成操作就要在s5pv210_led_set函数里面对led进行区分。而该函数传的参数是struct led_classdev结构体led_classdev结构体是不能传gpio的我们只能自己设计一个包括gpio而且包括struct led_classdev的结构体然后用container_of宏来得到自己设计的结构体。

6、platform_set_drvdata(dev, led);
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))
platform_device *dev->device dev-> struct device_private *p->void *driver_data= data;
就是将struct s3c24xx_gpio_led *类型的结构体led放到platform_device *类型的结构体dev中去。

7、结构体指针实例化过后就不需要malloc分配内存了。在probe函数和set函数中都有。

8.

	举个例子==============================================================
	//driver中不存放device信息的但是我们需要操作device,那么可以选择存一个device信息的指针如下所示pdata
	struct hi_gpio {
		struct cdev	cdev;
		struct hi_gpio_platdata	*pdata;
	};
	//platform_driver负责找到devicehi_gpio_probe(struct platform_device *dev)的输入型参数dev就告诉了driver我们的device,
	//是通过match函数通过名字匹配找到的device,	
	static struct platform_driver hi_gpio_driver = {
		.probe		= hi_gpio_probe,
		.release	= hi_gpio_release,
		.driver		= {
			.name		= "hi_gpio",
			.owner		= THIS_MODULE,
		},
	};
	/*可以将用户结构体hi_gpio和platform_device联系起来也就是platform_device->device->driver_data = hi_gpio
	也就是platform_set_drvdata的功能*/
	//device中的platform_device是带有设备信息的
	struct hi_gpio_platdata {
		unsigned int		 gpio;
		unsigned int		 flags;

		char			*name;
		char			*def_trigger;
	};
	static struct hi_gpio_platdata hi_gpio1_pdata = {
		.name		= "gpio1",
		.gpio		= 100,
		.flags		= 1,
		.def_trigger	= "heartbeat",
	};
	/*直接定义结构体填充platform_data	那能不能直接填充.driver_data不能driver在哪都不知道呢device文件中没有driver,我们
	只有在probe函数让device和driver相撞的时候才能将driver的信息填充到device中这个任务是交给platform_set_drvdata()的当然我们
	也可以通过结构体填充的形式来完成*/
	static struct platform_device hi_gpio1 = {
		.name = "hi_gpio",
		id =1,
		.dev = {
			.platform_data = &hi_gpio1_pdata,
		},
	};
	static struct platform_device *hi_devices[] __initdata = {
		&hi_gpio1,
		&hi_gpio2,
		&hi_gpio3,
		&hi_gpio4,
	};
	static void __init hi_init(void)
	{
		platform_add_devices(hi_devices, ARRAY_SIZE(hi_devices));
	}
	3.0之后内核选择将device放到设备树中去了。
	=====================================================================
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux