Linux驱动---/sys接口
一、伪文件 sys
伪文件(Pseudo File) 是 Linux 系统中一种特殊的文件,它不占用物理存储空间,而是由内核或系统动态生成,用于提供某种特定的功能或信息。我们本篇文章所整理的 /sys 便是伪文件,它提供了内核对象(如设备、驱动、总线)的属性和状态信息。
在上篇LED驱动文章中,我们通过在 /dev 下创建设备节点,提供了用户空间访问硬件设备的入口。不过这种情况下,用户只能通过open()、read()、write()等函数编程来操作设备。而 /sys 下的文件是内核为用户空间提供的高级抽象接口,直接读写文件(如cat、echo等)会触发内核中对应的回调函数,完成配置或信息获取。
这样,当我们回顾以前的知识就会发现,在学习Linux下的GPIO操作时为什么有 libgpiod 和 sysfs 两种操作方式,其实就是一直是通过编程操作 /dev 的设备,另一种是直接通过命令行操作 /sys 的设备。
二、led_classdev结构体
每个 LED 设备在内核中通过一个 led_classdev 结构体来表示。这个结构体包含了 LED 设备的各种属性和控制函数,例如亮度设置函数、最大亮度等,定义如下(只整理了常见部分):
struct led_classdev
{
const char *name; // 设备名字
enum led_brightness brightness; // LED 默认亮度
enum led_brightness max_brightness; // LED 的最大亮度
// 用于设置 LED 亮度的函数指针,不可休眠
void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
//用于设置 LED 亮度的函数指针,可以休眠
int (*brightness_set_blocking)(struct led_classdev *led_cdev, enum led_brightness brightness);
struct device *dev;
const char *default_trigger;
}
(1)name:表示设备名字;
(2)brightness和max_brightness:这两个成员都是枚举类型 enum led_brightness 的变量,一个表示 LED 的初始化亮度,一个表示 LED 的最大亮度,这个枚举 类型定义了 LED 的亮度等级,来看看这个枚举类型:
enum led_brightness {
LED_OFF = 0,
LED_ON = 1,
LED_HALF = 127,
LED_FULL = 255,
};
(3)default_trigger:该属性设置LED灯的默认动作,比如:
backlight:LED灯作为背光。
default-on:LED灯打开
heartbeat:LED灯作为心跳指示灯,可以作为系统运行提示灯。
ide-disk:LED灯作为硬盘活动指示灯。
timer:LED灯周期性闪烁,由定时器驱动,闪烁频率可以修改
(4)brightness_set:绑定 LED 亮度设置函数(不可休眠);
(5)brightness_set_blocking:绑定 LED 亮度设置函数(可以休眠);
三、注册/注销LED
3.1、led_classdev_register 函数
该函数将一个 LED 设备注册到 LED 子系统中,使其可以通过内核提供的统一接口进行操作。为 LED 设备初始化一些默认的属性,如亮度(brightness)、最大亮度(max_brightness)、触发器(trigger)等。同时在 /sys/class/leds/ 目录下为注册的 LED 设备创建一个对应的设备文件,用户可以通过该文件对 LED 进行操作。
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
//parent:指向父设备的指针。通常为 NULL,表示该 LED 设备没有父设备。如果提供了父设备,则 LED 设备会与父设备关联。
//led_cdev:指向 led_classdev 结构体的指针,该结构体包含了 LED 设备的相关信息,如名称、亮度设置函数、最大亮度等。
//成功时返回 0。失败时返回负的错误码.
3.2、led_classdev_unregister 函数
该函数用于从 LED 子系统中注销一个之前注册的 LED 设备。删除 /sys/class/leds/ 目录下对应的设备文件。释放与该 LED 设备相关的内核资源。
void led_classdev_unregister(struct led_classdev *led_cdev);
//led_cdev:指向之前通过 led_classdev_register 注册的 led_classdev 结构体的指针。
功能
四、/sys接口实现
4.1、编写驱动程序
在上一篇文章中,我们通过注册字符设备,在/dev下创建设备节点,从而实现了用户空间通过编程对硬件进行操作。接下来在这里将通过/sys接口实现用户通过命令行直接操作硬件设备。代码如下(设备树文件参考上篇文章,这里没有做修改):
//vim ldv2.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
struct leds_desc
{
struct led_classdev dev; /* dev for led_classdev_register() */
struct gpio_desc *gpio; /* gpio instance for this led */
char name[8]; /* led name in /sys/class/leds */
};
struct led_priv
{
int nleds; /* number of leds */
struct leds_desc *leds; /* leds array */
};
struct led_priv *priv;
static void led_brightness_set(struct led_classdev *dev, enum led_brightness brightness)
{
struct leds_desc *led = container_of(dev, struct leds_desc, dev);
gpiod_set_value_cansleep(led->gpio, brightness?1:0 );
}
static int led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct leds_desc *led;
int ret, i;
/* allocate memory for private data structure */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* parser the number of LEDs from the device tree */
priv->nleds = gpiod_count(dev, NULL);
if ( priv->nleds < 1) {
dev_err(dev, "Failed to read leds gpio from device tree\n");
return -EINVAL;
}
dev_info(dev, "led driver probe for %d leds from device tree\n", priv->nleds);
/* allocate memory for all the leds */
priv->leds = devm_kzalloc(dev, priv->nleds*sizeof(*priv->leds), GFP_KERNEL);
if( !priv->leds )
return -ENOMEM;
/* parser and request GPIO pins from the device tree */
for (i = 0; i < priv->nleds; i++) {
priv->leds[i].gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
if (IS_ERR(priv->leds[i].gpio))
return PTR_ERR(priv->leds[i].gpio);
/* set GPIO as output mode and default off */
gpiod_direction_output(priv->leds[i].gpio, 0);
}
/* create sysfs file for each led */
for (i = 0; i < priv->nleds; i++) {
led = priv->leds+i;
snprintf(led->name, sizeof(led->name), "led%d", i);
led->dev.name = led->name;
led->dev.brightness_set = led_brightness_set;
ret = led_classdev_register(dev, &led->dev);
if (ret) {
dev_err(dev, "Failed to register LED[%d]\n", i);
goto failed_destroy;
}
}
platform_set_drvdata(pdev, priv);
return 0;
failed_destroy:
for (--i; i >= 0; i--)
led_classdev_unregister(&priv->leds[i].dev);
return ret;
}
static int led_remove(struct platform_device *pdev)
{
struct led_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->nleds; i++) {
led_classdev_unregister(&priv->leds[i].dev);
}
dev_info(&pdev->dev, "led driver removed.\n");
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "rgb,leds", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "leds",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_driver);
MODULE_LICENSE("GPL");
Makefile文件如下:
ARCH ?= arm
CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/
PWD :=$(shell pwd)
obj-m += ledv2.o
modules:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
@rm -f *.unsigned
clean:
@rm -f *.ko
make
4.2、驱动安装测试
将编译后的ledv2.ko下载到开发板上,并进行安装:
insmod ledv2.ko
在新的驱动中,我们并没有注册字符设备,所以 /dev/ 下并不会产生新的设备文件,而在 /sys/class/leds 路径下出现了我们的三个 Led 设备文件。
ls /dev/led*
ls: cannot access '/dev/led*': No such file or directory
ls /sys/class/leds/
led0 led1 led2 mmc0:: mmc1::
ls /sys/class/leds/led1/
brightness device max_brightness power subsystem trigger uevent
接下来我们使用 echo 命令就可以控制相应 Led 亮灭了。
echo 1 > /sys/class/leds/led1/brightness
//效果:绿灯亮
echo 0 > /sys/class/leds/led1/brightness
//效果:绿灯灭
从上面 Led 驱动程序编写过程中我们了解到,要实现一个设备的驱动供应用程序空间使用,可以有多种不同的实现方式。如果我们想要容易编程控制,则可以使用常规的字符设备驱动通过调用 ioctl() 系统调用实现;而如果想要在命令行或Shell脚本中直接实现,则我们可以使用 /sys/class 伪文件系统来实现。
Linux驱动---/sys接口的更多相关文章
- 嵌入式Linux驱动开发日记
嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- 编写linux驱动所用到的头文件(转)
转自:http://blog.csdn.net/lufeiop02/article/details/6448497 关于linux驱动(应用)程序头文件使用 收藏 驱动程序: #include < ...
- 【linux驱动】linux驱动总览
欢迎转载,转载时需保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http:// ...
- zynq linux驱动之PL-PS中断【转】
转自:https://blog.csdn.net/h244259402/article/details/83993524 PC:Windows 10 虚拟机:ubuntu 16.04 vivado:2 ...
- linux驱动(续)
网络通信 --> IO多路复用之select.poll.epoll详解 IO多路复用之select.poll.epoll详解 目前支持I/O多路复用的系统调用有 select,psel ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- linux驱动工程面试必问知识点
linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- Linux驱动框架之misc类设备驱动框架
1.何为misc设备 (1)misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于 杂散设备,也就是misc设备,例如像a ...
随机推荐
- ZCMU-1133
emm就直接看的前辈的了. 唉 #include <stdio.h> #include <string.h> #include <algorithm> //我不成熟 ...
- require.js 笔记
1.前言 随着网站功能逐渐丰富,网页中的js也变得越来越复杂和臃肿,原有通过script标签来导入一个个的js文件这种方式已经不能满足现在互联网开发模式,我们需要团队协作.模块复用.单元测试等等一系列 ...
- IE低版本cors跨域请求
标签:js 坑位 最近接到一个活动需求,但是服务端接口全是跨域的,由于js同源策略,ajax请求是不允许跨域请求的,比较流行的解决方法是jsonp或者cors,但当服务端是走cors的时候,发现IE1 ...
- nginx的子路径重写替换
在nginx中配置proxy_pass代理转发时,如果在proxy_pass后面的url加/,表示绝对根路径:如果没有/,表示相对路径,把匹配的路径部分也给代理走. 假设下面四种情况分别用 http ...
- 01编程语言简介与C++
编程语言是编程的工具 计算机系统是分层的 图1: 图2: 编程语言是软件,也是分层的 图3: 图4: 图5: 图6: visual studio.vscode .dev-c++是三种用于C++编程的集 ...
- Spring AOP实例操作 简单易懂
AOP的功能,不改变源代码可以增强类中的方法 (增强 = 代理) AOP切入点表达式: execution([权限修饰符] [返回值类型] [类全路径] [方法名称] ([参数列表])) 例 ...
- x509.MarshalSm2PrivateKey
根据搜索结果,x509.MarshalSm2PrivateKey 函数需要两个参数:一个 *sm2.PrivateKey 和一个 []byte 类型的密码.以下是使用 x509.MarshalSm2P ...
- IDEA和GIT关于文件中LF和CRLF问题
问题描述:项目软件安装shell脚本上git仓库管理,但拉取后,上linux运行报错. 问题思考:根据描述信息可以查看到\r字样,初步判别为换行符导致 1.将脚本文件移动至notepad++中,通过视 ...
- https://eggjs.org/zh-cn/basics/env.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%8E%AF%E5%A2%83
转载:https://eggjs.org/zh-cn/basics/env.html#自定义环境 运行环境 一个 Web 应用本身应该是无状态的,并拥有根据运行环境设置自身的能力. 指定运行环境 框架 ...
- oracle用命令执行sql脚本文件
当sql命令过多(sql文件过大)时,用plsql执行时比较慢而且容易超时,此时可以用sqlplus命令直接执行sql脚本文件,方法如下: 1.sqlplus登录 >sqlplus userna ...