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 validdir
pointer to the hosting subdirectory.-
flags
A bit mask of devfs flags.
DEVFS_FL_DEFAULT
can be a good choice, andDEVFS_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. Theinfo
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 toDEVFS_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);
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
.
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.