设备驱动框架之LED

文章目录

  • 前言
  • 一、什么是驱动框架
  • 二、使用步骤
    • 1.注册LED设备
    • 2.卸载LED设备
    • 3.内核中申请内存
    • 4.container_of
    • 5.platform_get_drvdata 和 platform_set_drvdata
    • 6.module_platform_driver
  • 三、驱动示例
  • 总结


前言

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


一、什么是驱动框架

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;elselevel = 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驱动框架,还有一些常用的接口及使用方法。制作不易,多多包涵。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/35898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试-Java线程池

1.利用Excutors创建不同的线程池满足不同场景的需求 分析&#xff1a; 如果并发的请求的数量非常多&#xff0c;但每个线程执行的时间非常短&#xff0c;这样就会频繁的创建和销毁线程。如此一来&#xff0c;会大大降低系统的效率。 可能出现&#xff0c;服务器在为每个线程创建…

利用powershell开展网络钓鱼

要确保人们打开我们的恶意文件并执行它们&#xff0c;我们只需让微软努力工作多年来赢得人们的信任&#xff0c;然后将一些危险的宏插入到幻灯片中。 本博文将介绍如何通过屏幕顶部的一个友好的警告提示&#xff0c;在用户启用宏后立即运行您的宏。 首先&#xff0c;我们需要打…

浏览器扩展V3开发系列之 chrome.commands 快捷键的用法和案例

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 chrome.commands API 允许开发者使用快捷键来执行特定的命令。 在使用 chrome.commands API 之前必须…

Vue 学习之 axios

目录 执行安装命令&#xff1a;npm install axios 使用的时候导入 axios以data&#xff0c;params&#xff0c;headers传参方式的区别 axios封装 是一个基于 promise 的 网络请求库&#xff0c;作用于浏览器和 node.js 中。使用Axios可以在前端项目中发送各种方式的HTTP请求…

如何查看websocket连接信息

Chrome 浏览器中查看 webSocket 连接信息_谷歌浏览器看不到 websocket-CSDN博客 Getting Started — Flask-SocketIO documentation 运作原理 | Socket.IO

VS Code准备JAVA环境

背景 由于IntelliJ IDEA是需要激活码的,简单的java demo测试,除了可以直接使用命令行java和javac进行执行, 推荐使用VS code, 其功能比较强大,扩展插件也比丰富,对阅读和书写代码都是比较友好的. JDK环境准备 Linux JDK可以使用Open JDK 11. sudo apt-get install openjdk-1…

UE引擎实现ShadowMap、体积光(C++)

前言 整体上参考了YivanLee大佬的这两篇文&#xff1a; 虚幻4渲染编程&#xff08;灯光篇&#xff09;【第一卷&#xff1a;各种ShadowMap】 虚幻4渲染编程&#xff08;灯光篇&#xff09;【第二卷&#xff1a;体积光】 正文 1、ShadowMap &#xff08;1&#xff09;创建工…

【浦语开源】深入探索:大模型全链路开源组件 InternLM Lagent,打造灵笔Demo实战指南

一、准备工作&#xff1a; 1、环境配置&#xff1a; pip、conda换源&#xff1a; pip临时换源&#xff1a; pip install -i https://mirrors.cernet.edu.cn/pypi/web/simple some-package# 这里的“https://mirrors.cernet.edu.cn/pypi/web/simple”是所换的源&#xff0c;…

AI绘画Stable Diffusion人物背景替换实操教程,让创意无限延伸

大家好&#xff0c;我是画画的小强 Stable Diffusion以其强大的能力可以实现人物背景的更换。本文将带你深入了解如何利用Stable Diffusion中的Inpaint Anything插件快速且精准地实现人物背景的替换&#xff0c;从而让你的图片焕发新生。 前期准备 本文会使用到Inpaint Anyt…

观星观景大屏呈现 实时拍摄长焦定格 当当狸智能天文望远镜TW2来啦

《宇宙的奇迹》中有这样一句话&#xff1a;“我们与那些遥远星系息息相关&#xff0c;无论它们是如何与我们天各一方&#xff0c;那些经过数十亿年旅行到达地球的光线&#xff0c;终究会把我们联系在一起”。 想象一下—— 等到繁星低垂&#xff0c;月光皎洁之时&#xff0c;…

Linux系统安装和卸载nginx

&#x1f4d6;Linux系统安装和卸载nginx ✅下载✅安装✅启动nginx✅安装成系统服务✅常见问题&#xff1a;80端口被占用了✅卸载✅目录结构 以下介绍的是以源码编译安装方式&#xff1a; ✅下载 官方地址&#xff1a;https://nginx.org/en/download.html 123云盘地址&#x…

基于springboot、vue影院管理系统

设计技术&#xff1a; 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatisvue 工具&#xff1a;IDEA、Maven、Navicat 主要功能&#xff1a; 影城管理系统的主要使用者分为管理员和用户&#xff0c; 实现功能包括管理员&#xff1a; 首页…

ELK集群设置密码

一、软件安装清单 elasticsearch7.17.22logstash7.17.22kibana:7.17.22filebeat7.17.22elasticsearch-head:5 二、配置 生成证书 进入elasticsearch容器 bin/elasticsearch-certutil cert -out /usr/share/elasticsearch/config/elastic-certificates.p12 -pass将证书拷贝…

qt for android 工程添加AndroidManifest.xml 文件

1.选择左边图形栏目中的Projects&#xff0c;在Build steps下的Build Android APK中Details 2.点击Create Templates&#xff0c;并勾选 此时在工程下面会多出一个文件夹android 3.将这个android的中所有文件加入工程中&#xff0c;编辑.pro 4.通过QT 图形化编辑设置属性&#…

JAVA【案例5-5】二月天

【二月天】 1、案例描述 二月是一个有趣的月份&#xff0c;平年的二月有28天&#xff0c;闰年的二月由29天。闰年每四年一次&#xff0c;在判断闰年时&#xff0c;可以使用年份除于4&#xff0c;如果能够整除&#xff0c;则该年是闰年。 本案例要求编写一个程序&#xff0c;…

python e怎么表示

exp()方法返回x的指数&#xff0c;ex。 语法 以下是 exp() 方法的语法: import math math.exp( x ) 注意&#xff1a;exp()是不能直接访问的&#xff0c;需要导入 math 模块&#xff0c;通过静态对象调用该方法。 参数 x -- 数值表达式。 返回值 返回x的指数&#xff0c;…

01背包问题求解

来源于 https://kamacoder.com/problempage.php?pid1046 使用动态规划&#xff0c;五步走 1.定义状态数组和具体状态含义&#xff1a; dp是个二维数组&#xff0c;第一维代表物品索引&#xff0c;第二维代表背包空间状态。 dp[i][j]是指物品i 在背包空间j 的情况下所能放的…

【redis】redis安装

1、安装前准备 1.1环境准备 VMware安装 参考博文&#xff1a;【VMware】VMware虚拟机安装_配置_使用教程_选择虚拟机配置选项,设置dvd镜像为 点击启动虚拟机-CSDN博客 安装centOS的linux操作系统 xshell xftp 参考博文&#xff1a;【Linux】Xshell和Xftp简介_安装_VMwar…

最新版Git安装指南使用指南

首先&#xff0c;访问Git的官方网站https://git-scm.com下载适用于您操作系统的安装包。您也可以选择使用阿里云镜像来加速下载过程。 也可以用国内地址下载https://pan.quark.cn/s/0293d76e58bchttps://pan.quark.cn/s/0293d76e58bc安装过程 在这里插入图片描述 2、点击“…

vue3 Cesium 离线地图

源码&#xff1a;cesium-demo: Cesium示例工程&#xff0c;基于vue3 1、vite-plugin-cesium 是一个专门为 Vite 构建工具定制的插件&#xff0c;用于在 Vite 项目中轻松使用 Cesium 库。它简化了在 Vite 项目中集成 Cesium 的过程。 npm i cesium vite-plugin-cesium vite -D…