User: Password:
|
|
Subscribe / Log in / New account

Driver porting: Device classes

This article is part of the LWN Porting Drivers to 2.6 series.
Previous articles in this series have shown how the device model maintains a data structure representing the physical structure of the host system. There is more to know about a system than how it is plugged together, however; indeed, most of the time, user space really does not care about physical connections. Users (and the applications they run) are much more interested in questions like "what disks does this system have" or "where is the mouse?"

To help with this sort of resource discovery issue, the driver model exports a "class" interface. Devices, once registered, can be associated with one or more classes which describe the function(s) performed by the device. Class memberships show up under the /sys/class sysfs directory, and, of course, can be decorated with all kinds of attributes. There are also mechanisms which provide notification - both within and outside of the kernel - when a device joins or leaves a class. The class interface can also be the easiest way for a driver to make arbitrary attributes available via sysfs.

For many (if not most) drivers, class membership will be handled automatically in the higher layers. Block devices, for example, are associated with the "block" class when their associated gendisk structures are registered. (This class currently appears in /sys/block, incidentally; it will likely move to /sys/class/block at some point). Occasionally, however, it can be necessary to explicitly associate a device with a specific class. This article describes how to do that, and - though remaining superficial - it provides more information than is really needed in order to, with luck, provide an understanding of how the class system works.

For those wishing for a hands-on example, the full source for a version of the "simple block driver" module that understands classes is available.

Creating a class

It is a rare device which exists in a unique class of its own; as a result, drivers will almost never create their own classes. Should the need arise, however, the process is simple. The first step is the creation of a struct class (defined in <linux/device.h>). There are two necessary fields, being the name and a pointer to a "release" function; the SBD driver sets up its class as:

   static struct class sbd_class = {
	.name = "sbd",
	.release = sbd_class_release
    };

The name is, of course, how this class will show up under /sys/class. We will get to the release function shortly, after we have looked at class devices.

Beyond that, there is only one other thing that a class definition can provide: a "hotplug" function:

    int (*hotplug)(struct class_device *dev, char **envp, 
		   int num_envp, char *buffer, int buffer_size);

The addition of a device to a class creates a hotplug event. Before /sbin/hotplug is called to respond to that event, the class's hotplug() method (if any) will be called. That method can add variables to the environment that is passed to /sbin/hotplug; they should be put into buffer (respecting the given buffer_size) with pointers set into envp (but no more than num_envp of them, and with a NULL pointer to terminate the list). The return value should be zero, or the usual negative error code.

Classes need to be registered, of course:

    int class_register(struct class *cls);

The return value will be zero of all goes well. The void function class_unregister() will do exactly what one would expect.

Class devices

If your device type lacks a specific registration function of its own (such as add_disk() or register_netdev()), or if you have created your own custom class, you may find yourself adding your device(s) to a class explicitly. Membership in a class is represented by an instance of struct class_device. There are three fields that should normally be filled in:

	struct class *class;
	struct device *dev;
	char class_id[BUS_ID_SIZE];

The class pointer, of course, should be aimed at the proper class structure. The dev pointer is optional; it is used to create the device and driver symbolic links in the device's class entry in sysfs. Since user-space processes looking to discover devices of a particular class probably want to have that pointer, you should make it easy for them. The class_id is a string which is unique within the class - it becomes, of course, the name of the device's sysfs entry.

Once the class_device structure has been set up, it can be added to the class with:

    int class_device_register(struct class_device *class_dev);

class_device_unregister() can be used at module unload time.

Once you register a class device, it becomes available to the world as a whole. If your class device is allocated dynamically, you must be very careful about when you free it. Remember that user-space processes can retain references to your device via your sysfs attributes; you must not free the class device until all of those references are gone.

That, of course, is the purpose of the release function stored in struct class. This function has a simple prototype:

    void release_fn(struct class_device *cd);

This function is called when the last reference to the given device goes away; it should respond by freeing the device. That call will typically happen when you call class_device_unregister() on the device, but it could happen later if other references persist.

Please note that, if your class device structure is dynamically allocated, or it embedded within another, dynamic structure, you must use a release function to free that structure or your code is buggy.

Class device attributes

Attributes are easily added to a class device entry. If the attribute is to be readable, it will need a "show" function to respond to reads; the function used to export the driver version in SBD looks like:

    static ssize_t show_version(struct class_device *cd, char *buf)
    {
	sprintf(buf, "%s\n", Version);
	return strlen(buf) + 1;
    }

If the attribute is to be writable, you will need a store function too:

    ssize_t (*store)(struct class_device *, const char *buf, size_t count);

These functions are then bundled into an attribute structure with:

    CLASS_DEVICE_ATTR(name, mode, show, store);

The name should not be a quoted string; it is joined in the macro to create a structure called class_device_attr_name.

The final step is to create the actual device attribute, using:

    int class_device_create_file(struct class_device *, 
                                 struct class_device_attribute *);

You can call class_device_remove_file() to get rid of an attribute, but that is also done automatically for you when a device is removed from a class.

Interfaces

The term "interface," as used within the device model, is a bit confusing. A better way to think of interfaces is as a sort of constructor and destructor mechanism for class device entries. An interface provides add() and remove() methods which are called as devices are added to (and removed from) a class; their usual purpose is to add class-specific attributes to the class device entry. They can, however, perform any other kernel function that might be useful in response to class device events.

Briefly, the creation of an interface requires the creation of a class_interface structure, which needs to have the following fields filled in:

    struct class *class;
    int (*add) (struct class_device *);
    void (*remove) (struct class_device *);

Once the interface is set up with:

    int class_interface_register(struct class_interface *);

The add() and remove() functions will be called when devices are added to (or removed from) the given class. A call to class_interface_unregister() undoes the registration.


(Log in to post comments)


Copyright © 2003, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds