How to Create a sysfs File Correctly

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:

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:

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:

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:

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:

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:

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.