Linux—驱动属性文件添加、DEVICE_ATTR宏、device_create_file()及sysfs_create_group()

  • Post author:
  • Post category:linux




一、DEVICE_ATTR及device_create_file()介绍

在Linux驱动调试过程中常常需要添加属性文件,在sysfs中借助属性文件实现或调试一些功能,我们往往会用到

DEVICE_ATTR

宏,在linux/device.h中有如下定义:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _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);
};
/*面朝大海0902*/

通过使用

DEVICE_ATTR

宏实际会定义一个

struct device_attribute

结构体,并且各个成员也进行了初始化,大家可以自行将上面的宏展开。需要特别说明的是

属性文件的权限mode不可以随便定义

,是有限制的,mode不合理会报错error: negative width in bit-field anonymous。

/* Permissions on a sysfs file: you didn't miss the 0 prefix did you? */
#define VERIFY_OCTAL_PERMISSIONS(perms)						\
	(BUILD_BUG_ON_ZERO((perms) < 0) +					\
	 BUILD_BUG_ON_ZERO((perms) > 0777) +					\
	 /* USER_READABLE >= GROUP_READABLE >= OTHER_READABLE */		\
	 BUILD_BUG_ON_ZERO((((perms) >> 6) & 4) < (((perms) >> 3) & 4)) +	\
	 BUILD_BUG_ON_ZERO((((perms) >> 3) & 4) < ((perms) & 4)) +		\
	 /* USER_WRITABLE >= GROUP_WRITABLE */					\
	 BUILD_BUG_ON_ZERO((((perms) >> 6) & 2) < (((perms) >> 3) & 2)) +	\
	 /* OTHER_WRITABLE?  Generally considered a bad idea. */		\
	 BUILD_BUG_ON_ZERO((perms) & 2) +					\
	 (perms))
#endif

定义了

struct device_attribute

结构体我们还需要使用

device_create_file()

函数将属性文件加入sysfs文件系统中。

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;
}



二、实例代码

这里的实例代码我们是基于之前文章

Linux字符设备驱动新写法

里面的程序添加属性文件的,如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>

#define NEWCHRLED_CNT 1
#define NEWCHRLED_NAME "my_leddrv"
/*https://editor.csdn.net/md/?articleId=121184840 by面朝大海0902*/
static struct gpio_desc *led_gpio;
struct newchar_dev
{
	dev_t devid; 		/* 设备号 */
	struct cdev cdev;   /* cdev */
	int major;			/* 主设备号 */
	int minor;			/* 次设备号 */
	struct class *class;	/* 类 */
	struct device *device;  /* 设备 */
};

struct newchar_dev myled;

static int led_drv_open(struct inode *node, struct file *file)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	//gpiod_direction_output(led_gpio, 0);
	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)
{
	int result;
	char status;
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	result = copy_from_user(&status, buf, 1);
	//gpiod_set_value(led_gpio, status);
	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, "I am what I am\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、创建设备号 */
	alloc_chrdev_region(&myled.devid,  0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
	myled.major = MAJOR(myled.devid); /* 获取主设备号 */
	myled.minor = MINOR(myled.devid); /* 获取次设备号 */
	printk("myled major is %d, minor is %d\r\n",myled.major, myled.minor);

	/* 2、初始化 cdev */
	myled.cdev.owner = THIS_MODULE;
	cdev_init(&myled.cdev, &led_drv_fops);

	/* 3、添加一个 cdev */
	cdev_add(&myled.cdev, myled.devid, NEWCHRLED_CNT);

	/* 4、创建类 */
	myled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);

	/* 5、创建设备 */
	myled.device = device_create(myled.class, NULL,myled.devid, NULL, NEWCHRLED_NAME);
    
    device_create_file(myled.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(&myled.cdev);/* 删除 cdev */
	unregister_chrdev_region(myled.devid, NEWCHRLED_CNT);

	/* 删除设备文件和对应类 */
	device_destroy(myled.class, myled.devid);
	class_destroy(myled.class);

	gpiod_put(led_gpio);

}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");



三、实例测试

我们在Ubuntu系统上加载由上面程序编译的ko模块文件,程序创建了一个主设备号为246,名为“

my_leddrv

”的字符设备,然后在/sys/class/my_leddrv/my_leddrv目录下创建了一个para属性文件,使用

cat和echo

会触发属性文件对应的

show和store

函数被调用,打印如下:

[235264.187930] /home/book/Desktop/modules/attr.c led_init line is 90 
[235264.187933] myled major is 246, minor is 0

root@ubuntu:/home/book/Desktop/modules# ls /dev/ -al | grep my
crw-------   1 root root    246,   0 Nov  8 20:55 my_leddrv

root@ubuntu:/home/book/Desktop/modules# cd /sys/class/my_leddrv/my_leddrv
root@ubuntu:/sys/class/my_leddrv/my_leddrv# ls
dev  para  power  subsystem  uevent

root@ubuntu:/sys/class/my_leddrv/my_leddrv# cat para 
I am what I am
root@ubuntu:/sys/class/my_leddrv/my_leddrv# echo 123 > para 

root@ubuntu:/sys/class/my_leddrv/my_leddrv# dmesg
[235843.176306] /home/book/Desktop/modules/attr.c para_show line is 75 
[235854.031294] /home/book/Desktop/modules/attr.c para_store line is 81 
[235854.031296] 123

在Ubuntu上可以使用如下

通用的Makefile

编译自己的驱动文件,然后进行测试,其中

4.4.0-148-generic

需要对应自己Ubuntu的内核版本,可以使用

uname -r

查询:

#sample driver module  
CONFIG_MODULE_SIG=n
obj-m := attr.o
KDIR = /lib/modules/4.4.0-148-generic/build/

all:  
	$(MAKE) -C $(KDIR) M=$(PWD)  

.PHONY:clean
clean:  
	rm -f *.mod.c *.mod.o *.ko *.o *.tmp_versions



四、多个属性文件添加

对于多个属性文件的添加,我们可以定义属性组,然后将这个属性组使用

sysfs_create_group()

添加进sysfs文件系统中。

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)

在上面实例代码的基础上添加如下代码:

static DEVICE_ATTR(para, 0664, para_show, para_store);
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 *led_attribute[] =
{
    &dev_attr_para1.attr,
    &dev_attr_para2.attr,
    &dev_attr_para3.attr,
    NULL,
};

static struct attribute_group led_attribute_group =
{
    .attrs = led_attribute,
};
sysfs_create_group(&myled.device->kobj, &led_attribute_group);

实际测试如下,在Ubuntu中/sys/class/my_leddrv/my_leddrv目录下,我们看到属性文件除了para又多了para1、para2、para3,上面程序我们为了简单各个属性文件的show函数和store函数都一样,大家可以根据实际情况自行分别定义。

root@ubuntu:/sys/class/my_leddrv/my_leddrv# ls
dev  para  para1  para2  para3  power  subsystem  uevent
root@ubuntu:/sys/class/my_leddrv/my_leddrv# cat para1
I am what I am



版权声明:本文为lihuan680680原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。