第四章 驱动sysfs

在 Linux 驱动开发中,sysfs 提供了一种强大的机制,用于将内核对象的属性公开给用户空间。通过 sysfs,用户可以方便地读取和修改设备的属性,从而实现设备的配置和监控。本章将详细介绍如何在驱动程序中使用 sysfs。

4.1 什么是 sysfs?

sysfs 是一个基于 RAM 的文件系统,最初基于 ramfs; sysfs 是 Linux 内核中的一种虚拟文件系统,通常挂载在 /sys 目录下。 它提供了一种统一的接口,使用户空间程序可以以文件系统的方式访问内核数据。每个内核对象(如设备、驱动程序、类等)都可以在 sysfs 中有一个对应的目录,目录下包含该对象的属性文件。
如下命令查看sysfs挂载属性:

root@LPA3588:# mount | grep sysfs
sysfs on /sys type sysfs (rw,relatime)
root@LPA3588:# cat /proc/mounts | grep sysfs
sysfs /sys sysfs rw,relatime 0 0
root@LPA3588:# findmnt -t sysfs
TARGET SOURCE FSTYPE OPTIONS
/sys   sysfs  sysfs  rw,relatime
  • sysfs 表示文件系统类型。

  • /sys 是挂载点。

  • rw,nosuid,nodev,noexec,relatime 是挂载选项。

4. 2. 为什么使用 sysfs?

使用 sysfs 有以下几个主要优点:

• 调试和监控:开发人员可以通过 sysfs 属性轻松地调试和监控设备的状态和行为。

• 配置设备:用户可以通过 sysfs 属性配置设备的参数,而无需重新编译内核或驱动程序。

• 简化用户接口:sysfs 提供了一种统一的接口,使用户空间程序可以以文件系统的方式访问内核数据。

4.3. 如何添加 sysfs 属性

以下是更改baudrate属性值示例,展示了如何在驱动程序中添加 sysfs 属性。

4.3.1 定义属性的显示和存储函数

首先,需要定义属性的显示和存储函数。显示函数用于读取属性值,存储函数用于写入属性值。

static int baudrate= 115200;  // the global variable to export this attribute.

/**
 * show this attribute when user 'cat' command from user space.
 */
static ssize_t show_bdrate(struct device *dev,  struct device_attribute *attr,  char *buf) {
    return sprintf(buf, "%d\n", baudrate);
}

/**
 * save attribute to this global variable when user to set a new value by 'echo' from user space.
 */
static ssize_t store_bdrate(struct device *dev,  struct device_attribute *attr,  const char *buf,  size_t count) {
    sscanf(buf, "%d", &baudrate);
    return count;
}

4.3.2 定义属性结构

Linux设备属性结构体定义

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

• attr: 这是一个标准的sysfs属性结构体。

• show: 读取属性值的回调函数。

• store: 写入属性值的回调函数。

使用 DEVICE_ATTR 宏定义属性结构, DEVICE_ATTR宏本身定义如下:。

#define DEVICE_ATTR(name, mode, show, store)

• name: 属性名称。

• mode: 文件权限模式(如0644)。

• show: 读取属性值的函数指针。

• store: 写入属性值的函数指针。

这样使用如下定义即可导出设备属性:

static DEVICE_ATTR(bdrate, 0664,  show_bdrate, store_bdrate);

4.4 创建属性文件

在驱动程序的初始化函数中,使用 device_create_file 函数将属性文件添加到 sysfs。

static int __init sysfsdrv_init(void) {
    int ret;

    /**
     * here is other initialize codes ... 
     */

    ret = device_create_file(demo_sysfs_dev,  &dev_attr_bdrate);
    if (ret) {
        printk(KERN_ALERT "Failed to create sysfs attribute\n");
        return ret;
    }
    return 0;
}

4.5 删除属性文件

在驱动程序的退出函数中,使用 device_remove_file 函数删除属性文件。

static void __exit hello_exit(void) {
    device_remove_file(demo_sysfs_dev,  &dev_attr_bdrate);

    // other free resource codes...
}

4.6 sysfs驱动代码

在第三章的代码基础上, 添加sysfs属性, 创建一个sysfsdrv.c文件, 完整代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME   "demo_sysfs"
#define CLASS_NAME    "demo_sysfs_class"
#define BUFFER_SIZE    1024

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx zhang");
MODULE_DESCRIPTION("A simple driver with sysfs property");

static int major;
static char message[BUFFER_SIZE] = { 0 };
static short message_len;

static struct class *demo_sysfs_class = NULL;
static struct device *demo_sysfs_device = NULL;

static int baudrate = 115200;

static int device_open(struct inode*, struct file *filp);
static int device_release(struct inode*, struct file *filp);
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset);
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset);

static struct file_operations fops = { .open = device_open, .read = device_read,
        .write = device_write, .release = device_release, };

static ssize_t show_bdrate(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", baudrate);
}

static ssize_t store_bdrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
    printk("%s: buf = %s\n", __func__, buf);
    sscanf(buf, "%d", &baudrate);
    return count;
}

static DEVICE_ATTR(bdrate, 0664, show_bdrate, store_bdrate);

static int __init sysfsdrv_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "sysfs: Registering char device failed with %d\n", major);
        return major;
    }

    demo_sysfs_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(demo_sysfs_class)) {
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to register device class\n");
        return PTR_ERR(demo_sysfs_class);
    }

    demo_sysfs_device = device_create(demo_sysfs_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    if (IS_ERR(demo_sysfs_device)) {
        class_destroy(demo_sysfs_class);
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "Failed to create the device\n");
        return PTR_ERR(demo_sysfs_device);
    }

    if (device_create_file(demo_sysfs_device, &dev_attr_bdrate)) {
        printk(KERN_ALERT "Failed to create the sysfs\n");
        device_destroy(demo_sysfs_class, MKDEV(major, 0));
        class_destroy(demo_sysfs_class);
        unregister_chrdev(major, DEVICE_NAME);
        return -ENOMEM;;
    }

    printk(KERN_INFO "sysfs(demo sysfs): Device registered with major number %d\n", major);
    return 0;
}

static void __exit sysfsdrv_exit(void) {
    device_remove_file(demo_sysfs_device, &dev_attr_bdrate);
    device_destroy(demo_sysfs_class, MKDEV(major, 0));
    class_unregister(demo_sysfs_class);
    class_destroy(demo_sysfs_class);
    unregister_chrdev(major, DEVICE_NAME);

    printk(KERN_INFO "Goodbye!\n");
}

/**
 * Applications open this device driver.
 */
static int device_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "sysfs: Device opened\n");
    return 0;
}

/**
 * Applications close this device driver.
 */
static int device_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "sysfs: Device closed\n");
    return 0;
}

/**
 * Send data to applications.
 */
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset) {
    int bytes_read = 0;
    size_t message_len = strlen(message); // Assuming message is a null-terminated string

    // Ensure we do not read more than the message length
    if (len > message_len) {
        len = message_len;
    }

    // Copy data from kernel space to user space
    if (copy_to_user(buffer, message, len)) {
        return -EFAULT; // Return error if copy fails
    }

    bytes_read = len;
    message_len = 0; // Reset message length after reading

    return bytes_read;
}

/**
 * Get user data from applications.
 */
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset) {
    size_t bytes_to_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;

    // Copy data from user space to kernel space
    if (copy_from_user(message, buffer, bytes_to_write)) {
        return -EFAULT; // Return error if copy fails
    }

    message_len = bytes_to_write;
    return bytes_to_write;
}

module_init( sysfsdrv_init);
module_exit( sysfsdrv_exit);

4.7 编译sysfs驱动代码

4.7.1 Makefile

创建一个Makefile文件, 内容如下:

obj-m += sysfsdrv.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

4.7.2 编译

编译环境的搭建, 请参考第二章"编译“小节。

neardi@LPA3588:~/drivers/sysfs$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/sysfs modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
  CC [M]  /home/neardi/drivers/sysfs/sysfsdrv.o
  MODPOST /home/neardi/drivers/sysfs/Module.symvers
  CC [M]  /home/neardi/drivers/sysfs/sysfsdrv.mod.o
  LD [M]  /home/neardi/drivers/sysfs/sysfsdrv.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'

4.7.3 加载驱动

加载sysfsdrv.ko, 如下:

neardi@LPA3588:~/drivers/sysfs$ sudo insmod sysfsdrv.ko
neardi@LPA3588:~/drivers/sysfs$ dmesg
[21790.565127] sysfs(demo sysfs): Device registered with major number 234

上面可以看出加载驱动成功。

4.8 测试sysfs属性

1). 先查看属性节点是否创建。属性节点在如下路径:

neardi@LPA3588:~$ sudo -i
root@LPA3588:~# cd  /sys/class/demo_sysfs_class/demo_sysfs
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# ls -l
total 0
-rw-rw-r-- 1 root root 4096 Aug 15 16:10 bdrate
-r--r--r-- 1 root root 4096 Aug 15 16:10 dev
drwxr-xr-x 2 root root    0 Aug 15 16:10 power
lrwxrwxrwx 1 root root    0 Aug 15 16:10 subsystem -> ../../../../class/demo_sysfs_class
-rw-r--r-- 1 root root 4096 Aug 15 16:04 uevent

2). 读取及更改属性值

root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# cat bdrate
115200
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# echo 9600 > bdrate
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# cat bdrate
9600
root@LPA3588:/sys/class/demo_sysfs_class/demo_sysfs# dmesg
[25078.707651] sysfs(demo sysfs): Device registered with major number 234
[25743.765609] store_bdrate: buf = 9600

上面可以看出, 读取和更改属性值成功。