设备驱动框架1——LED驱动框架的分析(核心层)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、驱动框架的含义

1、理解层面1:驱动的分层设计

设备驱动程序,是由内核驱动部分的维护者,以及驱动开发工程师协作完成的。

内核驱动部分的维护者,往往为同类的设备(比如LED、LCD、蜂鸣器等等)设计了一个成熟的、标准的、典型的框架。他们将同类设备的驱动中通用的一些功能抽离出来,作为驱动框架中的核心层,然后设计好核心层与具体操作层的接口,让驱动开发者来实现具体操作层。

核心层与具体操作层的接口,其设计原则是标准化,尽量降低驱动开发者的编程难度。

设备驱动框架,简而言之就是驱动的分层设计。

2、理解层面2:系统资源的管控

内核维护者设计了一些系统资源管控体系,这些体系能够协调各个驱动的资源分配,保证内核的稳定。比如系统中所有的GPIO就属于系统资源,某个驱动模块如果需要使用某个GPIO,就需要调用特殊的接口进行申请,申请到之后再使用,使用完之后要释放。

这也是设备驱动框架的组成部分。

二、LED驱动框架概述

下面以内核中LED驱动框架为例,说明设备驱动框架的相关内容。 

1、相关文件

内核源码drivers/leds目录,是驱动框架规定的LED这种硬件的驱动应该待的地方。

2、LED驱动框架的核心层

drivers/leds目录中的led-class.c和led-core.c,其属于LED驱动框架的核心层。

它们由内核驱动部分维护者提供,描述的是所有厂家的不同LED硬件的相同部分的逻辑。

3、LED驱动框架的具体操作层

drivers/leds目录中的leds-xxxx.c文件,其属于LED驱动框架的具体操作层。

它们由不同厂商的驱动工程师编写。驱动工程师根据硬件的具体情况来对LED进行操作,使用LED驱动框架核心层提供的接口来与驱动框架进行交互,最终实现驱动的功能。

4、分析具体案例

分析驱动框架的关键,在于分析内核开发者提供了什么,驱动开发者需要完成什么。

以文件drivers/leds/leds-s3c24xx.c为例,这个文件是LED驱动框架的具体操作层。

drivers/leds目录中的led-class.c和led-core.c,这几个文件是LED驱动框架的核心层。

leds-s3c24xx.c文件调用drivers/leds/led-class.c文件的led_classdev_register()函数来完成LED驱动的注册。即驱动工程师通过调用驱动框架中提供的接口来实现自己的驱动。

注意:九鼎没有使用内核推荐的led驱动框架,它写的驱动是drivers/char/led/x210-led.c。

5、典型的驱动开发行业现状

内核驱动部分维护者,对驱动框架进行开发和维护、升级,对应着led-class.c和led-core.c。

SoC厂商(比如三星、华为等芯片厂商)的驱动工程师编写与测试设备驱动源码,提供参考版本,对应着leds-s3c24xx.c。

做产品的厂商(比如九鼎)的驱动工程师以SoC厂商提供的驱动源码leds-s3c24xx.c为基础,来做移植和调试。

三、LED驱动框架的初步分析

1、概述

以drivers/leds/leds-s3c24xx.c为例。

LED驱动框架的具体操作层指的是leds-s3c24xx.c文件。

LED驱动框架的核心层包括 led-class.c 文件、leds.h文件、led-core.c文件。

具体操作层leds-s3c24xx.c文件由于应用了驱动模型平台总线的概念,我们以后再分析。

这里先分析核心层的led-class.c文件

2、led-class.c文件的具体分析

led-class.c文件的内容如下:

//省略部分代码

static int __init leds_init(void)
{   //创建了类名,叫/sys/class/leds           
	leds_class = class_create(THIS_MODULE, "leds");
	if (IS_ERR(leds_class))
		return PTR_ERR(leds_class);
	
    leds_class->suspend = led_suspend;// 关联 LED设备休眠函数
	leds_class->resume = led_resume;// 关联 LED设备唤醒函数
   
	// 创建设备属性文件 brightness、max_brightness、trigger
    leds_class->dev_attrs = led_class_attrs;
	return 0;
}

static void __exit leds_exit(void)
{
	class_destroy(leds_class);//销毁了类名leds
}

subsys_initcall(leds_init); //led-class.c是一个内核模块,体现为有安装与卸载函数
module_exit(leds_exit);     //所以对该文件的分析,应该从下往上阅读

MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Class Interface");

1、led-class.c将被编译成一个内核模块

由代码可以看出,led-class.c有安装与卸载函数,因此它是一个内核模块。LED驱动框架可以根据需要被安装与卸载。我们应该遵照分析模块的方法,从下往上分析。

2、类名“leds”的创建与销毁

led_init()在/sys/class目录下创建“leds”这个类名;led_exit()销毁“leds”这个类名。

3、subsys_initcall(leds_init)的分析

(1)subsys_initcall是一个宏,定义在linux/init.h中。

其功能是将其声明的函数放到一个特定的段:.initcall4.init。

#define subsys_initcall(fn)		       __define_initcall("4",fn,4)

#define __define_initcall(level,fn,id) \
	static initcall_t __initcall_##fn##id __used \
	__attribute__((__section__(".initcall" level ".init"))) = fn

(2)分析module_init宏,可以看出它将函数放到了.initcall6.init段中。

#define module_init(x)	__initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)

也就是说,subsys_initcall和module_init的作用是一样的,只不过前者所声明的函数要比后者在内核启动时的执行顺序更早。

(3)内核实现先后顺序执行初始化操作的方法

将内核启动时要调用的所有函数归类,分类名就叫做“.initcalln.init”,n的值从1到8。内核开发者在编写内核代码时,将函数设置合适的级别,链接的时候,这些函数就会被放入特定的段中,内核启动时再按照(内核链接脚本中指定的)段顺序去依次执行各个段。

内核链接脚本(编译之后才有)在arch/arm/kernel/vmlinux.lds中。

4、led_class_attrs

(1)什么是attribute?

对应于将来/sys/class/leds/ 目录里的内容,这些内容一般是文件和文件夹。这些属性文件其实就是sysfs开放给应用层的一些操作接口,应用程序可以通过操作这些属性文件来操作硬件设备。(这些属性文件非常类似于/dev/目录下的设备文件,对/dev/目录下的设备文件的操作有关的API,对应着file_operations里面的函数。)

(2)attribute有什么用?

应用程序可以通过操作/sys/class/leds/目录下面的属性文件来操作硬件设备。它其实是另一条驱动实现路线(不再有cdev相关的函数操作),有区别于之前的file_operations那条线。

(3)class_create()的返回值类型是struct class类型。

该类型定义在/include/linux/device.h文件中:

struct class {
	const char		*name;      // 类的名称
	struct module		*owner; //类所属的模块,比如usb模块、led模块等

	struct class_attribute		*class_attrs;//类所添加的属性
	struct device_attribute		*dev_attrs;//类所包含的设备所添加的属性
	struct kobject	*dev_kobj;// 用于标识 类所包含的设备属于块设备还是字符设备

    // 用于在设备发出 uevent 消息时添加环境变量
	int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

	char *(*devnode)(struct device *dev, mode_t *mode);// 设备节点的相对路径名

	void (*class_release)(struct class *class);// 类被释放时调用的函数
	void (*dev_release)(struct device *dev);// 设备被释放时调用的函数

    //设备休眠时调用的函数
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);// 设备被唤醒时调用的函数

	const struct kobj_ns_type_operations *ns_type;
	const void *(*namespace)(struct device *dev); 

	const struct dev_pm_ops *pm; // 用于电源管理的函数

	struct class_private *p;// 指向 class_private 结构的指针
};

其中,结构体struct device_attribute定义在/include/linux/device.h文件中:

/* interface for exporting device attributes */
struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

因此led_class_attrs[ ]这个结构体数组的初始化如下:

//位于x210_kernel/drivers/leds/led-class.c文件中
static struct device_attribute led_class_attrs[] = {
         //文件             //对文件操作时实际对应的操作函数1  //操作函数2
	__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),       
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
	__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
	__ATTR_NULL,
};

//位于x210_kernel/include/linux/sysfs.h文件中
#define __ATTR(_name,_mode,_show,_store) { \
	.attr = {.name = __stringify(_name), .mode = _mode },	\
	.show	= _show,					\
	.store	= _store,					\
}

//位于x210_kernel/drivers/leds/led-class.c文件中
static ssize_t led_brightness_show(struct device *dev, 
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	/* no lock needed for this */
	led_update_brightness(led_cdev);

	return sprintf(buf, "%u\n", led_cdev->brightness);
}

static ssize_t led_brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	ssize_t ret = -EINVAL;
	char *after;
	unsigned long state = simple_strtoul(buf, &after, 10);
	size_t count = after - buf;

	if (isspace(*after))
		count++;

	if (count == size) {
		ret = count;

		if (state == LED_OFF)
			led_trigger_remove(led_cdev);
		led_set_brightness(led_cdev, state);
	}

	return ret;
}

5、设备注册函数:led_classdev_register()

(1)函数内容与作用

该函数的内容如下,它用于创建属于“leds”这个类的一个设备,其实就是去注册一个设备。

/**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
				      "%s", led_cdev->name);
	if (IS_ERR(led_cdev->dev))
		return PTR_ERR(led_cdev->dev);
#ifdef CONFIG_LEDS_TRIGGERS
	init_rwsem(&led_cdev->trigger_lock);
#endif
	/* add to the list of leds */
	down_write(&leds_list_lock);
	list_add_tail(&led_cdev->node, &leds_list);
	up_write(&leds_list_lock);
	if (!led_cdev->max_brightness)
		led_cdev->max_brightness = LED_FULL;
	led_update_brightness(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
	led_trigger_set_default(led_cdev);
#endif
	printk(KERN_DEBUG "Registered led device: %s\n",
			led_cdev->name);
	return 0;
}

(2)struct led_classdev结构体

“leds”类别的设备,用结构体struct led_classdev表征,其定义在include/linux/leds.h中:

struct led_classdev {
	const char		*name;
	int			 brightness;
	int			 max_brightness;
	int			 flags;

	/* Lower 16 bits reflect status */
#define LED_SUSPENDED		(1 << 0)
	/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME	(1 << 16)

	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	void		(*brightness_set)(struct led_classdev *led_cdev,
					  enum led_brightness brightness);
	/* Get LED brightness level */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);

	/* Activate hardware accelerated blink, delays are in
	 * miliseconds and if none is provided then a sensible default
	 * should be chosen. The call can adjust the timings if it can't
	 * match the values specified exactly. */
	int		(*blink_set)(struct led_classdev *led_cdev,
				     unsigned long *delay_on,
				     unsigned long *delay_off);

	struct device		*dev;  //注意这里的设备
	struct list_head	 node;			/* LED Device list */
	const char		*default_trigger;	/* Trigger to use */

#ifdef CONFIG_LEDS_TRIGGERS
	/* Protects the trigger data below */
	struct rw_semaphore	 trigger_lock;

	struct led_trigger	*trigger;
	struct list_head	 trig_list;
	void			*trigger_data;
#endif
};

其中的struct device结构体定义在/include/linux/device.h文件中:

struct device {
	struct device		*parent; 
	struct device_private	*p; 
	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	struct device_type	*type;
 	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */
 	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	struct dev_pm_info	power;
 
#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
 
	struct device_dma_parameters *dma_parms;
 	struct list_head	dma_pools;	/* dma pools (if dma'ble) */
 	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
	/* arch specific additions */
	struct dev_archdata	archdata;
#ifdef CONFIG_OF
	struct device_node	*of_node;
#endif
 
	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
 
	spinlock_t		devres_lock;
	struct list_head	devres_head;
 	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */
 	void	(*release)(struct device *dev);
};

(2)补充说明

该函数是LED驱动框架中,内核开发者提供给驱动开发者的一个注册设备的接口。

当使用LED驱动框架编写驱动时,这函数的作用类似于之前使用file_operations方式去注册字符设备驱动的register_chrdev函数。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值