The Device Filesystem

As suggested at the beginning of the chapter, recent versions of the Linux kernel offer a special filesystem for device entry points. The filesystem has been available for a while as an unofficial patch; it was made part of the official source tree in 2.3.46. A backport to 2.2 is available as well, although not included in the official 2.2 kernels.

Although use of the special filesystem is not widespread as we write this, the new features offer a few advantages to the device driver writer. Therefore, our version of scull exploits devfs if it is being used in the target system. The module uses kernel configuration information at compile time to know whether particular features have been enabled, and in this case we depend on CONFIG_DEVFS_FS being defined or not.

The main advantages of devfs are as follows:

  • Device entry points in /dev are created at device initialization and removed at device removal.

  • The device driver can specify device names, ownership, and permission bits, but user-space programs can still change ownership and permission (but not the filename).

  • There is no need to allocate a major number for the device driver and deal with minor numbers.

As a result, there is no need to run a script to create device special files when a module is loaded or unloaded, because the driver is autonomous in managing its own special files.

To handle device creation and removal, the driver should call the following functions:

#include <linux/devfs_fs_kernel.h>

devfs_handle_t devfs_mk_dir (devfs_handle_t dir,
  const char *name, void *info);

devfs_handle_t devfs_register (devfs_handle_t dir,
  const char *name, unsigned int flags,
  unsigned int major, unsigned int minor,
  umode_t mode, void *ops, void *info);

  void devfs_unregister (devfs_handle_t de);

The devfs implementation offers several other functions for kernel code to use. They allow creation of symbolic links, access to the internal data structures to retrieve devfs_handle_t items from inodes, and other tasks. Those other functions are not covered here because they are not very important or easily understood. The curious reader could look at the header file for further information.

The various arguments to the register/unregister functions are as follows:

dir

The parent directory where the new special file should be created. Most drivers will use NULL to create special files in /dev directly. To create an owned directory, a driver should call devfs_mk_dir.

name

The name of the device, without the leading /dev/. The name can include slashes if you want the device to be in a subdirectory; the subdirectory is created during the registration process. Alternatively, you can specify a valid dir pointer to the hosting subdirectory.

flags

A bit mask of devfs flags. DEVFS_FL_DEFAULT can be a good choice, and DEVFS_FL_AUTO_DEVNUM is the flag you need for automatic assignment of major and minor numbers. The actual flags are described later.

major , minor

The major and minor numbers for the device. Unused if DEVFS_FL_AUTO_DEVNUM is specified in the flags.

mode

Access mode of the new device.

ops

A pointer to the file operation structure for the device.

info

A default value for filp->private_data. The filesystem will initialize the pointer to this value when the device is opened. The info pointer passed to devfs_mk_dir is not used by devfs and acts as a “client data” pointer.

de

A "devfs entry” obtained by a previous call to devfs_register.

The flags are used to select specific features to be enabled for the special file being created. Although the flags are briefly and clearly documented in <linux/devfs_fs_kernel.h>, it’s worth introducing some of them.

DEVFS_FL_NONE , DEVFS_FL_DEFAULT

The former symbol is simply 0, and is suggested for code readability. The latter macro is currently defined to DEVFS_FL_NONE, but is a good choice to be forward compatible with future implementations of the filesystem.

DEVFS_FL_AUTO_OWNER

The flag makes the device appear to be owned by the last uid/gid that opened it, and read/write for anybody when no process has it opened. The feature is useful for tty device files but is also interesting for device drivers to prevent concurrent access to a nonshareable device. We’ll see access policy issues in Chapter 5.

DEVFS_FL_SHOW_UNREG , DEVFS_FL_HIDE

The former flag requests not to remove the device file from /dev when it is unregistered. The latter requests never to show it in /dev. The flags are not usually needed for normal devices.

DEVFS_FL_AUTO_DEVNUM

Automatically allocate a device number for this device. The number will remain associated with the device name even after the devfs entry is unregistered, so if the driver is reloaded before the system is shut down, it will receive the same major/minor pair.

DEVFS_FL_NO_PERSISTENCE

Don’t keep track of this entry after it is removed. This flags saves some system memory after module removal, at the cost of losing persistence of device features across module unload/reload. Persistent features are access mode, file ownership, and major/minor numbers.

It is possible to query the flags associated with a device or to change them at runtime. The following two functions perform the tasks:

int devfs_get_flags (devfs_handle_t de, unsigned int *flags);
int devfs_set_flags (devfs_handle_t de, unsigned int flags);

Using devfs in Practice

Because devfs leads to serious user-space incompatibilities as far as device names are concerned, not all installed systems use it. Independently of how the new feature will be accepted by Linux users, it’s unlikely you’ll write devfs-only drivers anytime soon; thus, you’ll need to add support for the “older” way of dealing with file creation and permission from user space and using major/minor numbers in kernel space.

The code needed to implement a device driver that only runs with devfs installed is a subset of the code you need to support both environments, so we only show the dual-mode initialization. Instead of writing a specific sample driver to try out devfs, we added devfs support to the scull driver. If you load scull to a kernel that uses devfs, you’ll need to directly invoke insmod instead of running the scull_load script.

We chose to create a directory to host all scull special files because the structure of devfs is highly hierarchical and there’s no reason not to adhere to this convention. Moreover, we can thus show how a directory is created and removed.

Within scull_init, the following code deals with device creation, using a field within the device structure (called handle) to keep track of what devices have been registered:

 /* If we have devfs, create /dev/scull to put files in there */
 scull_devfs_dir = devfs_mk_dir(NULL, "scull", NULL);
 if (!scull_devfs_dir) return -EBUSY; /* problem */

 for (i=0; i < scull_nr_devs; i++) {
  sprintf(devname, "%i", i);
  devfs_register(scull_devfs_dir, devname,
      DEVFS_FL_AUTO_DEVNUM,
      0, 0, S_IFCHR | S_IRUGO | S_IWUGO,
      &scull_fops,
      scull_devices+i);
 }

The previous code is paired by the two lines that are part of the following excerpt from scull_cleanup:

 if (scull_devices) {
  for (i=0; i<scull_nr_devs; i++) {
   scull_trim(scull_devices+i);
   /* the following line is only used for devfs */
   devfs_unregister(scull_devices[i].handle);
  }
  kfree(scull_devices);
 }

 /* once again, only for devfs */
 devfs_unregister(scull_devfs_dir);

Part of the previous code fragments is protected by #ifdef CONFIG_DEVFS_FS. If the feature is not enabled in the current kernel, scull will revert to register_chrdev.

The only extra task that needs to be performed in order to support both environments is dealing with initialization of filp->f_ops and filp->private_data in the open device method. The former pointer is simply not modified, since the right file operations have been specified in devfs_register. The latter will only need to be initialized by the open method if it is NULL, since it will only be NULL if devfs is not being used.

 /*
  * If private data is not valid, we are not using devfs
  * so use the type (from minor nr.) to select a new f_op
  */
 if (!filp->private_data && type) {
  if (type > SCULL_MAX_TYPE) return -ENODEV;
  filp->f_op = scull_fop_array[type];
  return filp->f_op->open(inode, filp); /* dispatch to specific open */
 }

 /* type 0, check the device number (unless private_data valid) */
 dev = (Scull_Dev *)filp->private_data;
 if (!dev) {
  if (num >= scull_nr_devs) return -ENODEV;
  dev = &scull_devices[num];
  filp->private_data = dev; /* for other methods */
 }

Once equipped with the code shown, the scull module can be loaded to a system running devfs. It will show the following lines as output of ls -l /dev/scull:

crw-rw-rw- 1 root  root  144,  1 Jan 1 1970 0
crw-rw-rw- 1 root  root  144,  2 Jan 1 1970 1
crw-rw-rw- 1 root  root  144,  3 Jan 1 1970 2
crw-rw-rw- 1 root  root  144,  4 Jan 1 1970 3
crw-rw-rw- 1 root  root  144,  5 Jan 1 1970 pipe0
crw-rw-rw- 1 root  root  144,  6 Jan 1 1970 pipe1
crw-rw-rw- 1 root  root  144,  7 Jan 1 1970 pipe2
crw-rw-rw- 1 root  root  144,  8 Jan 1 1970 pipe3
crw-rw-rw- 1 root  root  144, 12 Jan 1 1970 priv
crw-rw-rw- 1 root  root  144,  9 Jan 1 1970 single
crw-rw-rw- 1 root  root  144, 10 Jan 1 1970 user
crw-rw-rw- 1 root  root  144, 11 Jan 1 1970 wuser

The functionality of the various files is the same as that of the “normal” scull module, the only difference being in device pathnames: what used to be /dev/scull0 is now /dev/scull/0.

Portability Issues and devfs

The source files of scull are somewhat complicated by the need to be able to compile and run well with Linux versions 2.0, 2.2, and 2.4. This portability requirement brings in several instances of conditional compilation based on CONFIG_DEVFS_FS.

Fortunately, most developers agree that #ifdef constructs are basically bad when they appear in the body of function definitions (as opposed to being used in header files). Therefore, the addition of devfs brings in the needed machinery to completely avoid #ifdef in your code. We still have conditional compilation in scull because older versions of the kernel headers can’t offer support for that.

If your code is meant to only be used with version 2.4 of the kernel, you can avoid conditional compilation by calling kernel functions to initialize the driver in both ways; things are arranged so that one of the initializations will do nothing at all, while returning success. The following is an example of what initialization might look like:

 #include <devfs_fs_kernel.h>

 int init_module()
 {
  /* request a major: does nothing if devfs is used */
  result = devfs_register_chrdev(major, "name", &fops);
  if (result < 0) return result;

  /* register using devfs: does nothing if not in use */
  devfs_register(NULL, "name", /* .... */ );
  return 0;
 }

You can resort to similar tricks in your own header files, as long as you are careful not to redefine functions that are already defined by kernel headers. Removing conditional compilation is a good thing because it improves readability of the code and reduces the amount of possible bugs by letting the compiler parse the whole input file. Whenever conditional compilation is used, there is the risk of introducing typos or other errors that can slip through unnoticed if they happen in a place that is discarded by the C preprocessor because of #ifdef.

This is, for example, how scull.h avoids conditional compilation in the cleanup part of the program. This code is portable to all kernel versions because it doesn’t depend on devfs being known to the header files:

#ifdef CONFIG_DEVFS_FS /* only if enabled, to avoid errors in 2.0 */
#include <linux/devfs_fs_kernel.h>
#else
 typedef void * devfs_handle_t; /* avoid #ifdef inside the structure */
#endif

Nothing is defined in sysdep.h because it is very hard to implement this kind of hack generically enough to be of general use. Each driver should arrange for its own needs to avoid excessive #ifdef statements in function code. Also, we chose not to support devfs in the sample code for this book, with the exception of scull. We hope this discussion is enough to help readers exploit devfs if they want to; devfs support has been omitted from the rest of the sample files in order to keep the code simple.

Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.