目录
简介:
在Linux驱动调试时,常常需要添加属性文件,sysfs属性节点可以实现用户空间与硬件交互,如:设置GPIO管脚电平、控制驱动等功能。下面介绍如何创建sysfs属性节点。
一、DEVICE_ATTR介绍
1、DEVICE_ATTR宏
DEVICE_ATTR 宏在linux/device.h中有如下定义:
/* 路径:linux/device.h */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
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);
};
/* 路径:include/linux/sysfs.h */
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
DEVICE_ATTR 宏用来定义一个 struct device_attribute 结构体,并对各成员初始化。
1.1 参数说明
DEVICE_ATTR(_name, _mode, _show, _store)
- _name:名称,也就是将在sysfs中生成的文件名称;
- _mode:属性文件的权限mode,与普通文件相同,UGO的格式。只读0444,只写0222,或者读写都行的0666;
- _show:显示函数,cat该文件时,此函数被调用;
- _store:写函数,echo内容到该文件时,此函数被调用;
1.2 调用方法
static DEVICE_ATTR(demo, 0664, demo_show, demo_store);
demo_show、demo_store函数由我们自己定义,展开后
struct device_attribute dev_attr_demo = {
.attr = {.name = __stringify(demo),
.mode = VERIFY_OCTAL_PERMISSIONS(0664) },
.show = demo_show,
.store = demo_store,
}
调用 DEVICE_ATTR 后,就将 device_attribute 结构体初始化完成了。还需要使用 device_create_file() 或 sysfs_create_group() 将属性文件加入sysfs文件系统中。
二、sysfs创建属性文件
1、创建一个sysfs属性文件
1.1 device_create_file()函数
DEVICE_ATTR 宏创建 device_attribute 结构体后,调用 device_create_file() 将属性文件加入sysfs文件系统中,会在 /sys/class/xxx 子目录下生成一个属性文件。
/* 路径:drivers/base/core.c */
int device_create_file(struct device *dev,
const struct device_attribute *attr)
{
int error = 0;
if (dev) {
WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
"Attribute %s: write permission without 'store'\n",
attr->attr.name);
WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
"Attribute %s: read permission without 'show'\n",
attr->attr.name);
error = sysfs_create_file(&dev->kobj, &attr->attr);
}
return error;
}
EXPORT_SYMBOL_GPL(device_create_file);
这种方式一次只能创建一个属性节点。
1.2 device_create_file()实例
使用 device_create_file() 函数时要引用 device_create 创建设备时返回的device*指针,作用是在/sys/class/下创建一个属性文件,从而通过对这个属性文件进行读写就能完成对应的数据操作。
实例代码:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
typedef struct
{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
}CHARDEV_HANDLE_T;
#define CDEV_CNT 1 /* 设备号个数 */
#define CDEV_NAME "my_leddrv" /* 名字 */
static CHARDEV_HANDLE_T stMyled = {0};
static int led_drv_open(struct inode *node, struct file *file)
{
//filp->private_data = &cdev_data; /* 设置私有数据 */
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 1;
}
static int led_drv_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_release,
};
ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return sprintf(buf, "hello world!\n");
}
ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
printk(KERN_INFO "%s\n", buf);
return count;
}
static DEVICE_ATTR(para, 0664, para_show, para_store);
/* 入口函数 */
static int __init led_init(void)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (stMyled.major) { /* 定义了设备号 */
stMyled.devid = MKDEV(stMyled.major, 0);
register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
}
printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);
/* 2、初始化 cdev */
stMyled.cdev.owner = THIS_MODULE;
cdev_init(&stMyled.cdev, &led_drv_fops); //file_operations
/* 3、添加一个 cdev */
cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);
/* 4、创建类 */
stMyled.class = class_create(THIS_MODULE, CDEV_NAME); ///sys/class/目录下会创建一个新的文件夹
if (IS_ERR(stMyled.class)) {
return PTR_ERR(stMyled.class);
}
/* 5、创建设备 */
stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
if (IS_ERR(stMyled.device)) {
return PTR_ERR(stMyled.device);
}
/* 6、将属性文件加入sysfs文件系统中 */
device_create_file(stMyled.device, &dev_attr_para);
return 0;
}
/* 出口函数 */
static void __exit led_exit(void)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注销字符设备驱动 */
cdev_del(&stMyled.cdev); /* 删除 cdev */
unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */
device_destroy(stMyled.class, stMyled.devid);
class_destroy(stMyled.class);
/* 移除sysfs中的device属性节点 */
//device_remove_file(stMyled.device, &dev_attr_para);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");
Makefile编译,生成 .ko 文件
KERN_DIR = /home/linux-imx-rel_imx_4.1.15_2.1.0
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += chardev_drv.o
insmod加载 .ko 驱动后,生成 /sys/class/my_leddrv/my_leddrv/ 下有para属性节点。cat命令会调用 .show 函数,echo 会调用 .store 函数。测试结果如下:
# cat /sys/class/my_leddrv/my_leddrv/para
# echo "DEVICE_ATTR test" > /sys/class/my_leddrv/my_leddrv/para
2、创建多个sysfs属性文件
2.1 sysfs_create_group()函数
对于多个属性文件的添加,我们可以定义属性组,然后将这个属性组使用 sysfs_create_group() 添加进sysfs文件系统中。
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
2.2 sysfs_create_group()实例
关键步骤:
static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);
static struct attribute *para_attribute[] =
{
&dev_attr_para1.attr,
&dev_attr_para2.attr,
&dev_attr_para3.attr,
NULL,
};
static struct attribute_group para_attribute_group =
{
.attrs = para_attribute,
};
sysfs_create_group(&myled.device->kobj, ¶_attribute_group);
上面关键步骤我为了简单各个属性文件的show函数和store函数都一样,大家可以根据实际情况自行分别定义。
示例如下:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
typedef struct
{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
}CHARDEV_HANDLE_T;
#define CDEV_CNT 1 /* 设备号个数 */
#define CDEV_NAME "my_leddrv" /* 名字 */
static CHARDEV_HANDLE_T stMyled = {0};
static int led_drv_open(struct inode *node, struct file *file)
{
//filp->private_data = &cdev_data; /* 设置私有数据 */
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 1;
}
static int led_drv_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* 设备操作函数 */
static struct file_operations led_drv_fops =
{
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_release,
};
ssize_t para_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return sprintf(buf, "hello world!\n");
}
ssize_t para_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
printk(KERN_INFO "%s\n", buf);
return count;
}
static DEVICE_ATTR(para1, 0664, para_show, para_store);
static DEVICE_ATTR(para2, 0664, para_show, para_store);
static DEVICE_ATTR(para3, 0664, para_show, para_store);
static struct attribute *para_attribute[] =
{
&dev_attr_para1.attr,
&dev_attr_para2.attr,
&dev_attr_para3.attr,
NULL,
};
static struct attribute_group para_attribute_group =
{
.attrs = para_attribute,
};
/* 入口函数 */
static int __init led_init(void)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (stMyled.major) { /* 定义了设备号 */
stMyled.devid = MKDEV(stMyled.major, 0);
register_chrdev_region(stMyled.devid, CDEV_CNT, CDEV_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&stMyled.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
stMyled.major = MAJOR(stMyled.devid); /* 获取分配号的主设备号 */
stMyled.minor = MINOR(stMyled.devid); /* 获取分配号的次设备号 */
}
printk("major=%d, minor=%d\r\n", stMyled.major, stMyled.minor);
/* 2、初始化 cdev */
stMyled.cdev.owner = THIS_MODULE;
cdev_init(&stMyled.cdev, &led_drv_fops); //file_operations
/* 3、添加一个 cdev */
cdev_add(&stMyled.cdev, stMyled.devid, CDEV_CNT);
/* 4、创建类 */
stMyled.class = class_create(THIS_MODULE, CDEV_NAME); ///sys/class/目录下会创建一个新的文件夹
if (IS_ERR(stMyled.class)) {
return PTR_ERR(stMyled.class);
}
/* 5、创建设备 */
stMyled.device = device_create(stMyled.class, NULL, stMyled.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
if (IS_ERR(stMyled.device)) {
return PTR_ERR(stMyled.device);
}
/* 6、将属性文件加入sysfs文件系统中 */
sysfs_create_group(&stMyled.device->kobj, ¶_attribute_group);
return 0;
}
/* 出口函数 */
static void __exit led_exit(void)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注销字符设备驱动 */
cdev_del(&stMyled.cdev); /* 删除 cdev */
unregister_chrdev_region(stMyled.devid, CDEV_CNT); /* 注销 */
device_destroy(stMyled.class, stMyled.devid);
class_destroy(stMyled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");
加载 .ko 文件后,在 /sys/class/my_leddrv/my_leddrv 目录下多了 para1、para2、para3。
使用 cat、echo命令测试:
# cat /sys/class/my_leddrv/my_leddrv/para
# echo 1 > /sys/class/my_leddrv/my_leddrv/para