6.5 About Character Device Drivers

Character device drivers support devices that handle variable rather than fixed amounts of data, and which do not access physically addressable storage media or support file system access. Keyboards, mice, and video cards are examples of devices that might use character devices, although USB keyboards and mice require the use of USB device drivers.

The implementation of most character device drivers is very similar on both Linux and UNIX operating systems, with most differences arising from the structures that are used to define a driver's file operations.

The file_operations structure for a character device, defined in <linux/fs.h>, contains a set of method pointers that specify how the system interacts with the device via the device files under /dev when using system calls such as open(), read(), write(), and ioctl().

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
                       unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
                                size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,
                               size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
};

A simple character device driver might only need to implement a subset of the functions defined for this structure, for example:

struct file_operations driver_fileops = {
    .owner   = THIS_MODULE,
    .llseek  = driver_llseek,
    .read    = driver_read,
    .write   = driver_write,
    .ioctl   = driver_ioctl,
    .open    = driver_open,
    .release = driver_release,
};

As well as the methods, it is usual to define module_init() initialization and module_exit() cleanup routines for the driver that are called when a driver is loaded and unloaded. These routines should call register_chrdev() and unregister_chrdev() to register and unregister the device major number for the driver.

Most character device drivers declare an interrupt handler to accept incoming data asynchronously from the device. When initializing the driver, use request_irq() to install the interrupt handler, remembering to specify shared interrupts by setting the SA_SHIRQ bit in the flags argument as well as a unique dev_id argument that the handler can use to identify interrupts that it should process. Cleanup should call free_irq() to unregister the handler. Alternatively, a driver can use polling to check for incoming data, although this is uncommon.

The file structure for a character device, defined in <linux/fs.h>, specifies the kernel-space data structure that the kernel creates when the device is opened.

The most important members of the file structure are:

f_flags

File control flags that indicate how device operations should behave, for example, non-blocking reads.

f_mode

The mode, which indicates whether the file is readable and writable.

f_op

The operation associated with a file, usually depending on the minor device number.

f_pos

The current offset in the file for reading or writing.

private_data

Allows data about the device to be retained between system calls until the device is released.

To obtain the major and minor numbers from a device's inode, use the following macros that are also defined in <linux/fs.h>:

unsigned iminor(struct inode *inode);
unsigned imajor( struct inode *inode);