How to create a simple sysfs class attribute in Linux kernel v3.2
Asked Answered
I

3

20

I'm learning how to use sysfs in my Linux modules, but I'm having the hardest time finding current documentation on these topics. The Linux Device Drivers 3rd Edition book I've been using seems to be rather dated in this area unfortunately (e.g. the class_device structure appears to be completely gone in current Linux versions).

I'm simply trying to get an attribute to appear, under the respective sysfs class for my module, that will allow me to read the value of a module variable from kernel space.

In my code, I have a class created that allows udev to create a device node at /dev/foo for my module:

dev_t foo_dev;
alloc_chrdev_region(&foo_dev, 0, 1, "bar");

struct class *bar = class_create(THIS_MODULE, "bar");

device_create(bar, NULL, foo_dev, NULL, "foo");

struct cdev foo_dev_file;
cdev_init(&foo_dev_file, &fops); /* fops defined earlier */
cdev_add(&foo_dev_file, foo_dev, 1);

When I insert the module I get a sysfs class directory created and populated with some default attributes at /sys/class/bar/foo/. How can I create attributes that show up under this new directory?

I have the concepts down pretty well I believe -- create attribute structure, define sysfs_ops functions, etc -- my problem is that I don't know which particular kernel structure to use (class_attribute?), nor how to make these attributes appear under the right sysfs directory.

Would anyone point me to a tutorial or article detailing the process for current Linux kernels?

Immunize answered 26/4, 2013 at 20:56 Comment(0)
G
15

Even though my knowledge is still fairly low on the topic, I'm going to post an answer just because of the age of this question. If somebody else has a better answer, please post! :)

First off, I'm going to assume that you've read that whole chapter (specifically about kobjects & ksets). So just about every struct in the device driver model has these cutely included in them. If you want to manipulate the kobject for the class its self (not sure if that's wise or not), that's your struct class's dev_kobj member.

However, you want to manipulate the attributes of that class. I believe you do this by defining a (usually static), NULL-terminated array of them as follows and then assigning its address to the struct class's class_attrs member (taken from drivers/uwb/driver.c):

static struct class_attribute uwb_class_attrs[] = {
    __ATTR(beacon_timeout_ms, S_IWUSR | S_IRUGO,
           beacon_timeout_ms_show, beacon_timeout_ms_store),
    __ATTR_NULL,
};

/** Device model classes */
struct class uwb_rc_class = {
    .name        = "uwb_rc",
    .class_attrs = uwb_class_attrs,
};

When I don't know how to use something, I usually git grep the repository for somebody else who has used it and try to learn from it that way. It would seem that this is why they tend to say kernel "hackers" and not "developers".

Gahnite answered 27/4, 2013 at 15:30 Comment(5)
I noticed in the uwb_subsys_init function, the uwb_rc_class is passed to the class_register function. What's confusing me is whether I can call this function after class_create.Immunize
It seems from the Linux source code that class_create just makes a call to class_register, so the question really boils down to whether class_register can be called multiple times.Immunize
I just realized: I can use class_create_file to make my class attribute appear instead of a second call to class_register. Thanks for the help @DanielSantos, and beautiful example of "hacking" the Linux kernel for a solution! :)Immunize
Sorry for my delayed response, but I'm glad you figured it out! You've done exactly what I've been doing: looked at the code to find your answer. There is a lot of documentation for the kernel... and then there's a lot of kernel without documentation. On a side note, if you're crashing your machine a lot (like me), you might consider something like a raspberry pi to test your kernels & modules on -- it reboots in about 25 seconds, is very small and you can just use ssh to connect to it (of course, you'll need a cross-compile toolchain though since it's an ARM).Gahnite
There is also example for read only attribute via macro __ATTR_RO at lxr.free-electrons.com/source/sound/pci/hda/hda_hwdep.c#L180Farah
F
10

Minimal runnable example

Usage:

insmod /sysfs.ko
cd /sys/kernel/lkmc_sysfs
printf 12345 >foo
cat foo
# => 1234
dd if=foo bs=1 count=2 skip=1 status=none
# => 23

sysfs.c

#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <uapi/linux/stat.h> /* S_IRUSR, S_IWUSR  */

enum { FOO_SIZE_MAX = 4 };
static int foo_size;
static char foo_tmp[FOO_SIZE_MAX];

static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
        char *buff)
{
    strncpy(buff, foo_tmp, foo_size);
    return foo_size;
}

static ssize_t foo_store(struct  kobject *kobj, struct kobj_attribute *attr,
        const char *buff, size_t count)
{
    foo_size = min(count, (size_t)FOO_SIZE_MAX);
    strncpy(foo_tmp, buff, foo_size);
    return count;
}

static struct kobj_attribute foo_attribute =
    __ATTR(foo, S_IRUGO | S_IWUSR, foo_show, foo_store);

static struct attribute *attrs[] = {
    &foo_attribute.attr,
    NULL,
};

static struct attribute_group attr_group = {
    .attrs = attrs,
};

static struct kobject *kobj;

static int myinit(void)
{
    int ret;

    kobj = kobject_create_and_add("lkmc_sysfs", kernel_kobj);
    if (!kobj)
        return -ENOMEM;
    ret = sysfs_create_group(kobj, &attr_group);
    if (ret)
        kobject_put(kobj);
    return ret;
}

static void myexit(void)
{
    kobject_put(kobj);
}

module_init(myinit);
module_exit(myexit);
MODULE_LICENSE("GPL");

GitHub upstream.

Tested with Linux kernel 5.0.

Foxing answered 28/7, 2017 at 7:22 Comment(0)
C
2

There is a good tutorial in the link below

http://pete.akeo.ie/2011/08/writing-linux-device-driver-for-kernels.html

parrot_driver.c:

/*
 * Linux 2.6 and 3.0 'parrot' sample device driver
 *
 * Copyright (c) 2011, Pete Batard <[email protected]>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/kfifo.h>
#include "parrot_driver.h"

/* Module information */
MODULE_AUTHOR(AUTHOR);
MODULE_DESCRIPTION(DESCRIPTION);
MODULE_VERSION(VERSION);
MODULE_LICENSE("GPL");

/* Device variables */
static struct class* parrot_class = NULL;
static struct device* parrot_device = NULL;
static int parrot_major;
/* Flag used with the one_shot mode */
static bool message_read;
/* A mutex will ensure that only one process accesses our device */
static DEFINE_MUTEX(parrot_device_mutex);
/* Use a Kernel FIFO for read operations */
static DECLARE_KFIFO(parrot_msg_fifo, char, PARROT_MSG_FIFO_SIZE);
/* This table keeps track of each message length in the FIFO */
static unsigned int parrot_msg_len[PARROT_MSG_FIFO_MAX];
/* Read and write index for the table above */
static int parrot_msg_idx_rd, parrot_msg_idx_wr;

/* Module parameters that can be provided on insmod */
static bool debug = false;  /* print extra debug info */
module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "enable debug info (default: false)");
static bool one_shot = true;    /* only read a single message after open() */
module_param(one_shot, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "disable the readout of multiple messages at once (default: true)");


static int parrot_device_open(struct inode* inode, struct file* filp)
{
    dbg("");

    /* Our sample device does not allow write access */
    if ( ((filp->f_flags & O_ACCMODE) == O_WRONLY)
      || ((filp->f_flags & O_ACCMODE) == O_RDWR) ) {
        warn("write access is prohibited\n");
        return -EACCES;
    }

    /* Ensure that only one process has access to our device at any one time
    * For more info on concurrent accesses, see http://lwn.net/images/pdf/LDD3/ch05.pdf */
    if (!mutex_trylock(&parrot_device_mutex)) {
        warn("another process is accessing the device\n");
        return -EBUSY;
    }

    message_read = false;
    return 0;
}

static int parrot_device_close(struct inode* inode, struct file* filp)
{
    dbg("");
    mutex_unlock(&parrot_device_mutex);
    return 0;
}

static ssize_t parrot_device_read(struct file* filp, char __user *buffer, size_t length, loff_t* offset)
{
    int retval;
    unsigned int copied;

    /* The default from 'cat' is to issue multiple reads until the FIFO is depleted
     * one_shot avoids that */
    if (one_shot && message_read) return 0;
    dbg("");

    if (kfifo_is_empty(&parrot_msg_fifo)) {
        dbg("no message in fifo\n");
        return 0;
    }

    retval = kfifo_to_user(&parrot_msg_fifo, buffer, parrot_msg_len[parrot_msg_idx_rd], &copied);
    /* Ignore short reads (but warn about them) */
    if (parrot_msg_len[parrot_msg_idx_rd] != copied) {
        warn("short read detected\n");
    }
    /* loop into the message length table */
    parrot_msg_idx_rd = (parrot_msg_idx_rd+1)%PARROT_MSG_FIFO_MAX;
    message_read = true;

    return retval ? retval : copied;
}

/* The file_operation scructure tells the kernel which device operations are handled.
 * For a list of available file operations, see http://lwn.net/images/pdf/LDD3/ch03.pdf */
static struct file_operations fops = {
    .read = parrot_device_read,
    .open = parrot_device_open,
    .release = parrot_device_close
};

/* Placing data into the read FIFO is done through sysfs */
static ssize_t sys_add_to_fifo(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    unsigned int copied;

    dbg("");
    if (kfifo_avail(&parrot_msg_fifo) < count) {
        warn("not enough space left on fifo\n");
        return -ENOSPC;
    }
    if ((parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX == parrot_msg_idx_rd) {
        /* We've looped into our message length table */
        warn("message length table is full\n");
        return -ENOSPC;
    }

    /* The buffer is already in kernel space, so no need for ..._from_user() */
    copied = kfifo_in(&parrot_msg_fifo, buf, count);
    parrot_msg_len[parrot_msg_idx_wr] = copied;
    if (copied != count) {
        warn("short write detected\n");
    }
    parrot_msg_idx_wr = (parrot_msg_idx_wr+1)%PARROT_MSG_FIFO_MAX;

    return copied;
}

/* This sysfs entry resets the FIFO */
static ssize_t sys_reset(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    dbg("");

    /* Ideally, we would have a mutex around the FIFO, to ensure that we don't reset while in use.
     * To keep this sample simple, and because this is a sysfs operation, we don't do that */
    kfifo_reset(&parrot_msg_fifo);
    parrot_msg_idx_rd = parrot_msg_idx_wr = 0;

    return count;
}

/* Declare the sysfs entries. The macros create instances of dev_attr_fifo and dev_attr_reset */
static DEVICE_ATTR(fifo, S_IWUSR, NULL, sys_add_to_fifo);
static DEVICE_ATTR(reset, S_IWUSR, NULL, sys_reset);

/* Module initialization and release */
static int __init parrot_module_init(void)
{
    int retval;
    dbg("");

    /* First, see if we can dynamically allocate a major for our device */
    parrot_major = register_chrdev(0, DEVICE_NAME, &fops);
    if (parrot_major < 0) {
        err("failed to register device: error %d\n", parrot_major);
        retval = parrot_major;
        goto failed_chrdevreg;
    }

    /* We can either tie our device to a bus (existing, or one that we create)
     * or use a "virtual" device class. For this example, we choose the latter */
    parrot_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(parrot_class)) {
        err("failed to register device class '%s'\n", CLASS_NAME);
        retval = PTR_ERR(parrot_class);
        goto failed_classreg;
    }

    /* With a class, the easiest way to instantiate a device is to call device_create() */
    parrot_device = device_create(parrot_class, NULL, MKDEV(parrot_major, 0), NULL, CLASS_NAME "_" DEVICE_NAME);
    if (IS_ERR(parrot_device)) {
        err("failed to create device '%s_%s'\n", CLASS_NAME, DEVICE_NAME);
        retval = PTR_ERR(parrot_device);
        goto failed_devreg;
    }

    /* Now we can create the sysfs endpoints (don't care about errors).
     * dev_attr_fifo and dev_attr_reset come from the DEVICE_ATTR(...) earlier */
    retval = device_create_file(parrot_device, &dev_attr_fifo);
    if (retval < 0) {
        warn("failed to create write /sys endpoint - continuing without\n");
    }
    retval = device_create_file(parrot_device, &dev_attr_reset);
    if (retval < 0) {
        warn("failed to create reset /sys endpoint - continuing without\n");
    }

    mutex_init(&parrot_device_mutex);
    /* This device uses a Kernel FIFO for its read operation */
    INIT_KFIFO(parrot_msg_fifo);
    parrot_msg_idx_rd = parrot_msg_idx_wr = 0;

    return 0;

failed_devreg:
    class_unregister(parrot_class);
    class_destroy(parrot_class);
failed_classreg:
    unregister_chrdev(parrot_major, DEVICE_NAME);
failed_chrdevreg:
    return -1;
}

static void __exit parrot_module_exit(void)
{
    dbg("");
    device_remove_file(parrot_device, &dev_attr_fifo);
    device_remove_file(parrot_device, &dev_attr_reset);
    device_destroy(parrot_class, MKDEV(parrot_major, 0));
    class_unregister(parrot_class);
    class_destroy(parrot_class);
    unregister_chrdev(parrot_major, DEVICE_NAME);
}

/* Let the kernel know the calls for module init and exit */
module_init(parrot_module_init);
module_exit(parrot_module_exit);

parrot_driver.h:

/*
 * Linux 2.6 and 3.0 'parrot' sample device driver
 *
 * Copyright (c) 2011, Pete Batard <[email protected]>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#define DEVICE_NAME "device"
#define CLASS_NAME "parrot"
#define PARROT_MSG_FIFO_SIZE 1024
#define PARROT_MSG_FIFO_MAX  128

#define AUTHOR "Pete Batard <[email protected]>"
#define DESCRIPTION "'parrot' sample device driver"
#define VERSION "0.3"

/* We'll use our own macros for printk */
#define dbg(format, arg...) do { if (debug) pr_info(CLASS_NAME ": %s: " format , __FUNCTION__ , ## arg); } while (0)
#define err(format, arg...) pr_err(CLASS_NAME ": " format, ## arg)
#define info(format, arg...) pr_info(CLASS_NAME ": " format, ## arg)
#define warn(format, arg...) pr_warn(CLASS_NAME ": " format, ## arg)
Curmudgeon answered 17/8, 2015 at 11:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.