13.5 Character Device Drivers

Handling a character device is relatively easy, since usually sophisticated buffering strategies are not needed and disk caches are not involved. Of course, character devices differ in their requirements: some of them must implement a sophisticated communication protocol to drive the hardware device, while others just have to read a few values from a couple of I/O ports of the hardware devices. For instance, the device driver of a multiport serial card device (a hardware device offering many serial ports) is much more complicated than the device driver of a bus mouse.

A small complication, however, comes from the fact that the same major number might be allocated to several different device drivers. For instance, the major number 10 is used by many different device drivers, such as a real-time clock and a PS/2 mouse.

To keep track of which character device drivers are currently in use, the kernel uses a hash table indexed by the major and minor numbers.[9] The hash table array is stored in cdev_hashtable variable; it includes 64 lists of character device descriptors. Each descriptor is a char_device data structure, whose fields are shown in Table 13-10.

[9] A character device driver registered with the devfs device file might not have major and minor numbers. In this case, the kernel assumes that its major and minor numbers are equal to zero.

Table 13-10. The fields of the character device descriptor

Type

Field

Description

struct list_head

hash

Pointers for the hash table list

atomic_t

count

Usage counter for the character device descriptor

dev_t

dev

Major and minor numbers of the character device

atomic_t

openers

Not used

struct semaphore

sem

Semaphore protecting the character device

As for block device drivers, a hash table is required because the kernel cannot determine whether a character device driver is in use by simply checking whether a character device file has been already opened. In fact, the system directory tree might include several character device files having different pathnames but equal major and minor numbers, and they all refer to the very same device driver.

A character device descriptor is inserted into the hash table whenever a device file referring to it is opened for the first time. This job is performed by the init_special_inode( ) function, which is invoked by the low-level filesystem layer when it determines that a disk inode represents a device file. init_special_inode( ) looks up the character device descriptor in the hash table; if the descriptor is not found, the function allocates a new descriptor and inserts that into the hash table. The function also stores the descriptor address into the i_cdev field of the inode object of the device file.

We mentioned in Section 13.2.3 that the dentry_open( ) function triggered by the open( ) system call service routine customizes the f_op field in the file object of the character device file so that it points to the def_chr_fops table. This table is almost empty; it only defines the chrdev_open( ) function as the open method of the device file. This method is immediately invoked by dentry_open( ).

The chrdev_open( ) function rewrites the f_op field of the file object with the address stored in the chrdevs table element that corresponds to the major number of the character device file. Then the function invokes the open method again.

If the major number is assigned to a unique device driver, the method initializes the device driver. Otherwise, if the major number is shared among several device drivers, the method rewrites once more the f_op field of the file object with an address found in the data structure indexed by the minor number of the device file. For instance, the file_operations data structures for the device file that has the major number 10 are stored in the simply linked list misc_list. Finally, the open method is invoked for the last time to initialize the device driver.

Once opened, the character device file usually can be accessed for reading and/or for writing; to do this, the read and write methods of the file object points to suitable functions of the device driver. Most device drivers also support the ioctl( ) system call through the ioctl file object method; it allows special commands to be sent to the underlying hardware device.