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

一、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
  • 8
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
`device_create_file` 和 `sysfs_create_file` 函数都可以用于在 sysfs 文件系统中创建文件,但它们的使用场景略有不同。 `sysfs_create_file` 函数是一个通用的函数,它可以在 sysfs 文件系统的任意位置创建文件。该函数的原型如下: ```c int sysfs_create_file(struct kobject *kobj, const struct attribute *attr); ``` 其中,`kobj` 参数是指向 `struct kobject` 结构体的指针,该结构体表示要在其下面创建文件sysfs 对象。`attr` 参数是指向 `struct attribute` 结构体的指针,该结构体描述了要创建的文件属性。`sysfs_create_file` 函数会在 sysfs 文件系统中创建一个与 `attr` 中描述的属性相关联的文件,并将其与 `kobj` 参数指向的 sysfs 对象关联起来。 相比之下,`device_create_file` 函数更加专业化。它是针对 Linux 设备驱动程序的一种特殊机制,用于在与设备相关联的 sysfs 对象下创建文件。该函数的原型如下: ```c int device_create_file(struct device *dev, const struct attribute *attr); ``` 其中,`dev` 参数是指向 `struct device` 结构体的指针,该结构体表示与设备相关联的设备对象。`attr` 参数是指向 `struct attribute` 结构体的指针,该结构体描述了要创建的文件属性。`device_create_file` 函数会在 sysfs 文件系统中创建一个与 `attr` 中描述的属性相关联的文件,并将其与 `dev` 参数指向的设备对象关联起来。 因此,如果你需要在 sysfs 文件系统的任意位置创建文件,可以使用 `sysfs_create_file` 函数。而如果你需要在 Linux 设备驱动程序中为设备创建文件,应该使用 `device_create_file` 函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值