一、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