设备驱动框架之LED


前言

为了尽量降低驱动开发者难度以及接口标准化,就出现了设备驱动框架的概念;


一、什么是驱动框架

Linux 针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现, 然后把不同厂家的同
类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程
师来实现,这就叫驱动框架。
说白了,设备驱动框架就是对原始的驱动开发接口进行了封装,不同种类的设备有不同
的封装和实现方法,驱动开发工程师根据这些封装好的接口来编写自己的驱动程序,这就是
设备驱动框架。
内核源码 drivers/leds 目录下有两个文件 ledclass.c 和 led-core.c,它们就是 LED 驱动框架的核心文件,由内核开发工作者进行维护和升级。
在这里插入图片描述

二、使用步骤

1.注册LED设备

LED 驱动框架提供了 led_classdev_register 宏用于注册 LED 设备,该宏定义在内核源码 drivers/leds/leds.h 头文件中

extern int of_led_classdev_register(struct device *parent,
								struct device_node *np,
								struct led_classdev *led_cdev);
#define led_classdev_register(parent, led_cdev) \
of_led_classdev_register(parent, NULL, led_cdev)

parent: LED 设备的父设备, struct device 类型指针变量。
led_cdev: 需要注册的 LED 设备结构体,LED 驱动框架中使用struct led_classdev结构体来描述一个 LED设备。
返回值:成功返回 0,失败则返回一个负数

struct led_classdev 结构体的内容,该结构体定义在 drivers/leds/leds.h 头文件中
struct led_classdev {
	const char *name; // 设备名字
	enum led_brightness brightness; // LED 默认亮度
	enum led_brightness max_brightness; // LED 的最大亮度
	。。。
}

描述亮度的枚举类型:
enum led_brightness {
	LED_OFF = 0,
	LED_ON = 1,
	LED_HALF = 127,
	LED_FULL = 255,
}

2.卸载LED设备

void led_classdev_unregister(struct led_classdev *led_cdev)

led_cdev:需要卸载的 led 设备结构体, struct led_classdev 结构体类型指针变量
返回值:无

3.内核中申请内存

1.kmalloc

void *kmalloc(size_t size, gfp_t flags);

size:需要分配的内存大小。
flags:分配内存时所使用的标志位,这些 flag 定义在 include/linux/gfp.h 头文件中
主要:
	GFP_ATOMIC: 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断。
	GFP_KERNEL: 内核空间中正常的内存分配过程,也是用的最多的 flag。
	GFP_KERNEL_ACCOUNT: GFP_KERNEL_ACCOUNT 与 GFP_KERNEL 相同,只是分配是由 kmemcg 负责。
	GFP_DMA: 给 DMA 控制器分配内存,需要使用该标志( DMA 要求分配虚拟地址和物理地址连续)。
返回值:申请成功返回的就是内存的起始地址,申请失败返回 NULL

kmalloc()申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过 128KB。 如何要释放内存可以使用 kfree 函数,函数原型如下:

void kfree(const void *addr);

2.kzalloc
kzalloc()函数与 kmalloc()非常相似,参数及返回值是一样的, kzalloc()函数除了申请内存
空间之外,还会对申请到的内存空间进行清零。同样 kzalloc()对应的内存释放函数也是 kfree()。

void *kzalloc(size_t size, gfp_t flags)

3.vmalloc

void *vmalloc(unsigned long size);
void vfree(const void *addr);

函数只有一个参数,就是需要申请的内存空间大小,返回值同样也是内存空间其实地址。 vmalloc()函数则会在虚拟内存空间给出一块连续的内存区域,但这片连续的虚拟内存在物理内存中并不一定连续。
由于 vmalloc()没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。
vmalloc 函数和 vfree 函数可以睡眠,因此不能在中断上下文中使用。

4.总结
kmalloc()、 kzalloc()、 vmalloc()的共同特点是:
1.用于申请内核空间的内存;
2.内存以字节为单位进行分配;
3.所分配的内存空间在虚拟地址上连续;

区别:
1.kzalloc 是强制清零的 kmalloc 操作;
2.kmalloc 分配的内存大小有限制( 128KB),而 vmalloc 没有限制;
3.kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
4.kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞(休眠) ;
5.kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在被 DMA访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。
例如,当模块被动态加载到内核当中时,就把模块装载到由 vmalloc()分配的内存上。

  1. devm_kmalloc 和 devm_kzalloc

一样是内核内存分配函数,但跟设备有关,当内核中设备被卸载时,内存会被自动释放

void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)
void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp)

当内存不再使用时, 也可以手动使用函数 devm_kfree 释放
void devm_kfree(struct device *dev, void *p)

凡是使用了 devm_xxx 开头的函数都是与设备挂钩的,也都是可以在设备被卸载的时候由系统自动释放。

4.container_of

宏定义在 linux 源码目录下的 include/linux/kernel.h 中。

 /**
  * container_of - cast a member of a structure out to the containing structure
  * @ptr:    the pointer to the member.
  * @type:   the type of the container struct this is embedded in.
  * @member: the name of the member within the struct.
  *
  * WARNING: any const qualifier of @ptr is lost.
  */
 #define container_of(ptr, type, member) ({              \
     void *__mptr = (void *)(ptr);                   \
     static_assert(__same_type(*(ptr), ((type *)0)->member) ||   \
               __same_type(*(ptr), void),            \
               "pointer type mismatch in container_of()");   \
     ((type *)(__mptr - offsetof(type, member))); })

在linux内核用得非常普遍,用于从包含在某个结构体中的指针获得结构体本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。
需要提供三个参数,第一个就是结构体变量中某个成员的首地址,第二个参数是结构体的类型,第三个参数是该成员的名字。

5.platform_get_drvdata 和 platform_set_drvdata

两个函数定义在内核源码 include/linux/platform_device.h 头文件中

static inline void *platform_get_drvdata(const struct platform_device *pdev)
{
	return dev_get_drvdata(&pdev->dev);
}

static inline void platform_set_drvdata(struct platform_device *pdev,void *data)
{
	dev_set_drvdata(&pdev->dev, data);
}

用于保存data指针数据到pdev->dev.driver_data,通常用于保存相关数据,方便通过struct platform_device获取数据并使用。

6.module_platform_driver

宏定义在内核源码 include/linux/platform_device.h头文件中

#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
		platform_driver_unregister)
		
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

功能就是定义模块入口函数和出口函数,宏展开后跟以前platform注册和卸载是一样的。

驱动加载成功之后查看/sys/bus/platform/devices 目录下的文件看是否存在 led 设备(设备树节点名)。
查看/sys/bus/platform/drivers 目录下的文件是否存在 zynq-led 驱动(.driver中的name)。
可以通过去读写/sys目录下属性文件的方式操作设备,如/sys/bus/platform/devices/led/leds目录下brightness、 max_brightness、 trigger 等这些文件就是属性文件,是LED驱动框架核心层代码帮我们实现的。

三、驱动示例

设备树节点如下所示:

	led{
		compatible = "my_led";
		status = "okay";
		default-status = "on";
		
		led-gpio = <&gpio 38 GPIO_ACTIVE_HIGH>
	};

驱动代码:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/leds.h>


struct myled_data {
	struct led_classdev cdev;	//用于描述一个LED设备
	int gpio;
};
//通过 struct myled_data 结构体变量中 cdev 成员的首地址进而得到整个 struct myled_data 结构体变量的首地址
static inline struct myled_data *cdev_to_led_data(struct led_classdev *led_cdev)
{
	return container_of(led_cdev,struct myled_data,cdev);
}

void myled_brightness_set(struct led_classdev *led_cdev,enum led_brightness value)
{
	struct myled_data *led_data = cdev_to_led_data(led_cdev);
	int level;
	
	if(value == LED_OFF)
		level = 0;
	else
		level = 1;
	
	gpio_set_value(led_data->gpio,level);
}

int myled_brightness_set_blocking(struct led_classdev *led_cdev,enum led_brightness value)
{
	myled_brightness_set(led_cdev,value);
	return 0;
}

int led_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct myled_data *led_data;
	struct led_classdev *led_cdev;
	
	dev_info(&pdev->dev,"led probe begin init success !\n");
	
	led_data = devm_kzalloc(&pdev->dev,sizeof(struct myled_data),GFP_KERNEL);
	if(!led_data)
		return -ENOMEM;
	
	led_data->gpio = of_get_named_gpio(pdev->dev.of_node,"led-gpio",0);
	ret = devm_gpio_request(&pdev->dev,led_data->gpio,"LED GPIO");
	if(ret)
	{
		dev_err(&pdev->dev,"devm_gpio_request failed !\n");
		return ret;
	}
	
	gpio_direction_output(led_data->gpio,0);

	platform_set_drvdata(pdev,led_data);

	led_cdev = &led_data->cdev;
	led_cdev->name = "myled";
	led_cdev->brightness = LED_OFF;	//	初始亮度
	led_cdev->max_brightness = LED_FULL;	//最大亮度
	led_cdev->brightness_set = myled_brightness_set;	//绑定设置函数,不可休眠
	led_cdev->brightness_set_blocking = myled_brightness_set_blocking;	//绑定设置函数,可休眠
	
	return led_classdev_register(&pdev->dev,led_cdev);
}

int led_exit(struct platform_device *pdev)
{
	struct myled_data *led_data = platform_get_drvdata(pdev);
	led_classdev_unregister(&led_data->cdev);

	return 0;
}

struct of_device_id led_of_match[] = {
	{.compatible = "my_led"},
};

struct platform_driver myled_driver = 
{
	.driver = {
		.name = "zynq_led",
		.of_match_table = led_of_match,
	},
	.probe = led_probe,
	.remove = led_exit,
};


module_platform_driver(myled_driver);

MODULE_AUTHOR("LZW");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED DRIVER BASE ON LED FRAMEWORK");

总结

以上就是今天要讲的内容,本文简单介绍了Linux内核中的LED驱动框架,还有一些常用的接口及使用方法。制作不易,多多包涵。

  • 50
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值