前言
在一些linux开发板中,经常可以看到通过echo的方式来直接控制硬件或者修改驱动,例如:
//灯灭
echo 0 >/sys/class/leds/firefly:blue:power/brightness
//灯亮
echo 1 >/sys/class/leds/firefly:blue:power/brightness
这是怎么做到呢?
实际上,这是因为在驱动中提供了sysfs
接口给用户使用,使得用户可以通过cat
或者echo
命令来查看和修改驱动中某些变量的值。
下面介绍驱动中创建sysfs接口的方法
sysfs接口创建
基本步骤:
1、使用DEVICE_ATTR
声明一个sys
节点
static DEVICE_ATTR(led_status, 0600, led_status_show, led_status_store);
led_status
:在sys接口中显示的节点名字
0600
:表示操作这个led_status节点的权限
led_status_show
:使用cat
命令查看sys接口时调用的函数
led_status_store
:使用echo
命令往sys接口写入内容时调用的函数
2、完成sys节点的读写函数
static unsigned int led = 0;
/*
* sys节点的读函数
* 执行 cat /sys/devices/platform/leds/led_status时会调用
*/
static ssize_t led_status_show(struct device *dev, struct device_attribute *attr, char *buf)
{//buf是通过cat命令显示到终端的内容,这里显示led变量return sprintf(buf, "%s:%d.\n", "led", led);
}/**
* sys节点的写函数
* 用echo命令往sys节点写入内容时,会调用该函数
*/
static ssize_t led_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{//写入的内容会存放到buf中,这里将buf内容赋值给led变量sscanf(buf, "%d", &led);return count;
}
示例中,led_status_show()
函数和led_status_store()
函数的作用分为打印led变量的值和修改led变量的值.
3、定义struct attribute
和struct attribute_group
数组
static struct attribute *led_attributes[]={/*上述使用了DEVICE_ATTR声明节点名字为led_status,* 则struct attribute名字应为:* dev_attr_ + (节点名) + .attr* 所以名字为dev_attr_led_status.attr*/&dev_attr_led_status.attr,NULL,
};static const struct attribute_group led_attrs={.attrs = led_attributes,//引用上述struct attribute数组
};
上述使用了DEVICE_ATTR
声明节点名字为led_status
, 则struct attribute
名字应为:dev_attr_ + (节点名) + .attr
。所以名字为dev_attr_led_status.attr
。
4、在probe函数中调用sysfs_create_group()
函数注册sysfs
接口
完整例子
设备树:
leds:leds{compatible = "xx,xx-led";};
驱动:
static unsigned int led = 0;static ssize_t led_status_show(struct device *dev, struct device_attribute *attr, char *buf)
{return sprintf(buf, "%s:%d.\n", "led", led);
}static ssize_t led_status_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{sscanf(buf, "%d", &led);return count;
}static DEVICE_ATTR(led_status, 0600, led_status_show, led_status_store);static struct attribute *led_attributes[]={&dev_attr_led_status.attr,NULL,
};static const struct attribute_group led_attrs={.attrs = led_attributes,
};static int xx_led_probe(struct platform_device *pdev)
{sysfs_create_group(&pdev->dev.kobj, &led_attrs);return 0;
}static int xx_led_remove(struct platform_device *pdev)
{sysfs_remove_group(&pdev->dev.kobj, &led_attrs);return 0;
}static const struct of_device_id xx_led_of_match[] = {{.compatible = "xx,xx-led"},
};static struct platform_driver xx_led_driver = {.probe = xx_led_probe,.remove = xx_led_remove,.driver = {.name = "xx-led",.owner = THIS_MODULE,.of_match_table = xx_led_of_match,},
};static int __init xx_led_init(void)
{return platform_driver_register(&xx_led_driver );
}static void __exit xx_led_exit(void)
{platform_driver_unregister(&xx_led_driver);
}module_init(xx_led_init);
module_exit(xx_led_exit);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("xx led driver");
MODULE_AUTHOR("Vincent");
MODULE_VERSION("V1.0.00");
驱动加载后,就可以在linux终端中,使用cat
和echo
命令来查看和修改驱动中led
变量的值。例如:
at /sys/devices/platform/leds/led_status
led:0.//修改led变量的值为9
echo 9 > /sys/devices/platform/leds/led_status
//查看
cat /sys/devices/platform/leds/led_status
led:9.
上面介绍了Linux驱动中sysfs接口的创建,今天介绍procfs接口的创建。
procfs
:可实现类似cat /proc/cpuinfo
的操作
procfs接口创建
实现效果:
例如, 在/proc
下创建一个clk节点,通过cat /proc/clk
可查看内容:
代码实现:
系统 | 内核版本 |
---|---|
Linux | 4.9.88 |
在驱动中添加以下代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>struct proc_dir_entry *my_proc_entry;static int proc_clk_show(struct seq_file *m, void *v)
{//cat显示的内容seq_printf(m,"pll0: %u Mhz\n""pll1: %u Mhz\n""pll2: %u Mhz\n",100, 200, 300);return 0;
}static int clk_info_open(struct inode *inode, struct file *filp)
{return single_open(filp, proc_clk_show, NULL);
}static struct file_operations myops =
{.owner = THIS_MODULE,.open = clk_info_open,.read = seq_read,.llseek = seq_lseek,.release = seq_release,
};static int __init my_module_init(void)
{//注册proc接口my_proc_entry = proc_create("clk", 0644, NULL, &myops);return 0;
}static void __exit my_module_exit(void)
{//注销proc接口proc_remove(my_proc_entry);
}module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
procfs接口的创建,主要是实现struct file_operations
结构体,然后通过proc_create
函数进行注册,通过proc_remove
函数进行注销。
procfs通常是用来获取CPU、内存、进程等各种信息,例如cat /proc/cpuinfo
、cat /proc/meminfo
,所以我们只需要实现.open成员函数。当使用cat
命令查看/proc
下的信息时,会调用到.open
对应的实现函数。
这里我们使用了seq_file
接口,需要记住的是,procfs通常会和seq_file接口一起使用。seq_file是一个序列文件接口,当我们创建的proc数据内容由一系列数据顺序组合而成或者是比较大的proc文件系统时,都建议使用seq_file接口,例如cat /proc/meminfo
就会显示很多内容。
seq_file接口主要就是解决proc接口编程存在的问题,推荐在proc接口编程时使用seq_file接口,另外.read、.llseek、.release成员函数也可以直接用seq_read
、seq_lseek
和seq_release
。
proc新接口
注意,在较新版本的内核中,procfs
的函数接口有所变化。
系统 | 内核版本 |
---|---|
Linux | 5.10.111 |
在驱动中添加以下代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>struct proc_dir_entry *my_proc_entry;static int proc_clk_show(struct seq_file *m, void *v)
{seq_printf(m,"pll0: %lu Mhz\n""pll1: %lu Mhz\n""pll2: %lu Mhz\n",100, 200, 300);return 0;
}static int clk_info_open(struct inode *inode, struct file *filp)
{return single_open(filp, proc_clk_show, NULL);
}static const struct proc_ops clk_stat_proc_fops = {.proc_open = clk_info_open,.proc_read = seq_read,.proc_lseek = seq_lseek,.proc_release = seq_release,
};static int __init my_module_init(void)
{my_proc_entry = proc_create("clk", 0, NULL, &clk_stat_proc_fops);return 0;
}static void __exit my_module_exit(void)
{proc_remove(my_proc_entry);
}module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
新的proc
接口中,将原来的struct file_operations
换成了struct proc_ops
,其中成员函数也添加了对应的前缀proc
,但本质还是一样的,只是换了名字,更加规范了一些。
debugfs接口创建
上面介绍了procfs接口的创建,今天再介绍一种debugfs接口的创建。
实现效果
在/sys/kernel/debug/
目录下创建一个ion/test
文件,通过cat
、echo
的方式进行读写操作:
前期准备
内核配置打开debugfs:
CONFIG_DEBUG_FS=y
挂载debugfs文件系统:
mount -t debugfs none /sys/kernel/debug
代码实现
读写变量:
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/types.h>static struct dentry *ion_dir;
static u64 test_u64 = 0;static int __init debugfs_init(void)
{//创建一个/sys/kernel/debug/ion目录ion_dir = debugfs_create_dir("ion", NULL);if (!ion_dir) {printk("ion_dir is null\n");return -1;}/* 创建/sys/kernel/debug/ion/test_u64文件 */debugfs_create_u64("test_u64", 0644,ion_dir, &test_u64);return 0;
}static void __exit debugfs_exit(void)
{debugfs_remove_recursive(ion_dir);
}module_init(debugfs_init);
module_exit(debugfs_exit);
MODULE_LICENSE("GPL");
运行结果:
读写字符串:
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/dcache.h>
#include <linux/types.h>static char ion_buf[512] = "hello\n";
static struct dentry *ion_dir;static int ion_open(struct inode *inode, struct file *filp)
{//printk("ion open\n");return 0;
}ssize_t ion_read(struct file *filp, char __user *buf, size_t count, loff_t *offp)
{int retval = 0;if ((*offp + count) > 512)count = 512 - *offp;if (copy_to_user(buf, ion_buf+*offp, count)) {printk("copy to user failed, count:%ld\n", count);retval = -EFAULT;goto out;}*offp += count;retval = count;
out:return retval;
}ssize_t ion_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
{int retval;if (*offp > 512)return 0;if (*offp + count > 512)count = 512 - *offp;if (copy_from_user(ion_buf+*offp, buff, count)) {printk("copy from user failed, count:%ld\n", count);retval = -EFAULT;goto out;}*offp += count;retval = count;
out:return retval;
}struct file_operations my_fops = {.owner = THIS_MODULE,.read = ion_read,.write = ion_write,.open = ion_open,
};static int __init debugfs_init(void)
{printk("INIT MODULE\n");//创建一个/sys/kernel/debug/ion目录ion_dir = debugfs_create_dir("ion", NULL);if (!ion_dir) {printk("ion_dir is null\n");return -1;}/* 创建/sys/kernel/debug/ion/test文件 */struct dentry *filent = debugfs_create_file("test", 0644, ion_dir, NULL, &my_fops);if (!filent) {printk("test file is null\n");return -1;}return 0;
}static void __exit debugfs_exit(void)
{debugfs_remove_recursive(ion_dir);
}module_init(debugfs_init);
module_exit(debugfs_exit);
MODULE_LICENSE("GPL");
运行结果:
函数接口说明
创建目录、文件函数:
/* 创建目录 */
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);/*创建节点 */
struct dentry *debugfs_create_file(const char *name, umode_t mode,struct dentry *parent, void *data,const struct file_operations *fops);
name:要创建的/sys/kernel/debug
下的目录名
parent:父目录,用struct dentry
结构体表示。如果直接在/sys/kernel/debug/
下创建文件,则为NULL
创建不同大小的文件:
//创建十进制的无符号文件
void debugfs_create_u8(const char *name, umode_t mode,struct dentry *parent, u8 *value);
void debugfs_create_u16(const char *name, umode_t mode,struct dentry *parent, u16 *value);
void debugfs_create_u32(const char *name, umode_t mode,struct dentry *parent, u32 *value);
void debugfs_create_u64(const char *name, umode_t mode,struct dentry *parent, u64 *value);
//创建十六进制的无符号文件
void debugfs_create_x8(const char *name, umode_t mode,struct dentry *parent, u8 *value);
void debugfs_create_x16(const char *name, umode_t mode,struct dentry *parent, u16 *value);
void debugfs_create_x32(const char *name, umode_t mode,struct dentry *parent, u32 *value);
void debugfs_create_x64(const char *name, umode_t mode,struct dentry *parent, u64 *value);
更详细的debugfs用法请参考官方文档:Documentation/filesystems/debugfs.txt