How to Create a sysfs File Correctly

1359

One common Linux kernel driver issue that I see all the time is a driver
author attempting to create a sysfs file in their code by doing
something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int my_driver_probe(...)
{
        ...
        retval = device_create_file(my_device, &my_first_attribute);
        if (retval)
                goto error1;
        retval = device_create_file(my_device, &my_second_attribute);
        if (retval)
                goto error2;
        ...
        return 0;
error2:
        device_remove_file(my_device, &my_first_attribute);
error1:
        /* Clean up other things and return an error */
        ...
        return -ENODEV;
}

That’s a good first start, until they get tired of adding more and more
sysfs files, and they discover attribute groups, which allows
multiple sysfs files to be created and destroyed all at once,
without having to handle the unwinding of things if problems occur:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int my_driver_probe(...)
{
        ...
        retval = sysfs_create_group(&my_device->kobj, &my_attribute_group);
        if (retval)
                goto error;
        ...
        return 0;
error:
        /* Clean up other things and return an error */
        ...
        return -ENODEV;
}

And everyone is happy, and things seem to work just fine (oh, you did
document all of these sysfs files in Documentation/ABI/,
right?)

Anyway, one day the developer gets an email saying that for some reason,
userspace can’t see the sysfs files that are being created.
The user is using a library, or udev rule, and the attribute seems to
not exist. This is quite odd, because if you look at sysfs,
the files are there, but yet, libudev doesn’t think it is. What is
going on?

It turns out that the driver is racing with userspace to notice when the
device is being created in sysfs. The driver core, and at a
more basic level, the kobject core below it, announces to userspace when
a new device is created or removed from the system. At that point in
time, tools run to read all of the attributes for the device, and store
them away so that udev rules can run on them, and other libraries can
have access to them.

The problem is, when a driver’s probe() function is called,
userspace has already been told the device is present, so any
sysfs files that are created at this point in time, will
probably be missed entirely.

The driver core has a number of ways that this can be solved, making the
driver author’s job even easier, by allowing “default attributes” to be
created by the driver core before it is announced to userspace.

These default attribute groups exist at lots of different levels in the
driver / device / class hierarchy.

If you have a bus, you can set the following fields in struct
bus
:

1
2
3
4
5
6
7
struct bus {
        ...
        struct bus_attribute    *bus_attrs;
        struct device_attribute *dev_attrs;
        struct driver_attribute *drv_attrs;
        ...
}

If you aren’t dealing with a bus, but rather a class, then set these
fields in your struct class:

1
2
3
4
5
6
7
struct class {
        ...
        struct class_attribute          *class_attrs;
        struct device_attribute         *dev_attrs;
        struct bin_attribute            *dev_bin_attrs;
        ...
}

If you aren’t in control of the bus logic or class logic, but you have
control over the struct device_driver structure, then set this
field:

1
2
3
4
5
struct device_driver {
        ...
        const struct attribute_group **groups;
        ...
}

Sometimes you don’t have control over the driver either, or want
different sysfs files for different devices controlled by your
driver (platform_device drivers are like this at times.) Then,
at the least, you have control over the device structure itself. If so,
then use this field:

1
2
3
4
5
struct device {
        ...
        const struct attribute_group **groups;
        ...
}

By setting this value, you don’t have to do anything in your
probe() or release() functions at all in order for the
sysfs files to be properly created and destroyed whenever your
device is added or removed from the system. And you will, most
importantly, do it in a race-free manner, which is always a good thing.