Understanding Character Device Drivers and Their Relationships

In the process of developing Linux device drivers, one naturally encounters concepts and technologies related to character device drivers, platform device drivers, device driver models, and sysfs. This can be quite confusing for beginners, and even for engineers with a certain foundation in Linux, it is relatively commendable to have a good understanding of these related technologies. To deeply understand the principles involved, one must be very familiar with the frameworks and model code related to device drivers. There are many articles online about these technologies, but most only elaborate on certain aspects, making it difficult to find analyses that compare and relate these technologies. For developers, being familiar with a certain point and sharing it is already a significant achievement, but for those focused on imparting knowledge and experience to learners, it is very necessary and meaningful to horizontally compare and relate various driver-related knowledge points and vertically analyze the entire Linux driver software hierarchy.

This article still aims to understand the above knowledge points from the perspective of demand; the existence of these technologies is justified, as their presence indicates they have a certain role. We focus on understanding the function of each technology point and clarifying its role in driver development.

1. Device Drivers

Linux device drivers can be categorized into three types: character device drivers, block device drivers, and network device drivers. Character devices can only access device memory in a sequential byte stream order and cannot access it randomly. Examples of character devices include mouse, touch screens, and LCDs. Block devices can randomly access any address in device memory; examples include hard drives, SD cards, and NAND FLASH. Network devices refer to devices like network cards that communicate using socket sockets. This article uses character devices as an example to discuss related knowledge.

2. Character Device Drivers

For a framework on character device drivers, please refer to two articles from the Embedded Penguin Circle:

Analysis of Linux Character Device Drivers and Creation of Linux Device Files and mdev

1. Vertical Relationships of Character Device Drivers

From <Analysis of Linux Character Device Drivers>, it can be seen that accessing the device driver from the application layer is very simple, as it is done through the open interface to ultimately obtain the operation interface set struct file_operations of the device driver. The parameter passed to the open interface is the device name under the /dev directory. From <Creation of Linux Device Files and mdev>, it can be understood that the device name corresponds to the device file node inode, which stores the device number, while the global array cdev_map in the driver framework maintains the relationship between the device number and file_operations. Thus, the relationship from the application layer to the underlying layer mainly is (ignoring the VFS layer):

Device Name –> Device Number –> file_operations

The local fd returned by the open function and the relationship with file_operations (ignoring process data structures) is as follows:

fdàfile (current process data structure member) -> file_operations

In this way, through fd, file_operations can be obtained, allowing calls to the driver’s read and write operation functions, ioctl functions, etc., through interfaces like read and write.

2. Tasks of Character Device Drivers

1) The most essential task of a character device driver should be to provide implementations of various interfaces in file_operations such as open, read, write, ioctl, etc.

Additionally, from the above description, in order for the application layer to call the underlying file_operations, the following tasks are also involved:

2) Request a device number and register the device number and file_operations (using the cdev_add interface) to the cdev_map array in the driver framework. This point should be the responsibility of the character device driver, involving proactively notifying the system of its existence.

3) Create device files in the /dev directory, which include the device number. Whether this task should be the responsibility of the character device driver is debatable. Since character device drivers are at the kernel layer, if they are responsible for this task, then the driver must know what device name it is creating. Simple character drivers are okay, but for USB and other pluggable devices, how does the driver know what device name it should create? Some say a set of rules can be established. Indeed, but if this set of rules is placed at the application layer, and application developers clarify this rule (as mdev does), would it not be better? Because the application program directly accesses the device driver corresponding to this device name. Therefore, character device drivers should not directly be responsible for creating device files.

3. Who Creates Device Files

Someone has to do it; otherwise, how can the application layer access it?

One method is for the user to use the mknod command in the shell to create the device file, passing in the device name and device number. This is a manual method, which is not very scientific. However, it serves as a demonstration method.

Another method is to rely on the device model to assist in creating device files. This is also one of the functions of the device model.

4. Programming Process of Character Device Drivers

1) Define struct file_operations my_fops and implement its various interfaces, such as open, read, write, ioctl, etc.

2) Implement the entry function for the driver, such as chardev_init

static int __init chardev_init (void){

alloc_chrdev_region(&devno, …);// Request device number

my_cdev=cdev_alloc();

cdev_init(my_cdev, &my_fops);

cdev_add(my_fops, devno, 1);// Register device number and file_operations

}

3) module_init(chardev_init);// Macro definition for the initialization entry function. The unloading process is not explained.

4) After loading this module with insmod, the user can manually create the device file using mknod in the shell command line.

5) The application layer can then use open to open the device file for access.

5. Summary

It can be seen that the core framework of character device drivers does not have a direct relationship with the device model or platform device drivers; it can function normally without them.

3. Device Driver Models

We mainly discuss the role and function of the device driver model in Linux drivers; the principles and implementation of the device model will be discussed in another article.

1. Functions of Device Driver Models

1) The device driver model implements the uevent mechanism, calling the application layer’s mdev to automatically create device files. This has been discussed above.

2) The device driver model provides a view of device drivers to the user layer through the sysfs file system, as shown below.

Understanding Character Device Drivers and Their Relationships

The above image is just a visual representation that helps everyone understand the device model, similar to the device management program in Windows. In embedded Linux, there are no related applications that visually present this relationship. However, users can access various bus, device, and driver information and relationships through the command window by navigating the /sys folder using ls commands. It can be seen that in the top-level /sys directory, there are three key subdirectories: device classes, devices, and buses.

Devices are specific individual devices, and actual file nodes are created under /sys/devices/. Other directories, such as device classes and devices under the bus subdirectory, appear as symbolic links pointing to files under /sys/devices/.

Device classes categorize various devices under /sys/devices/ to reflect common attributes of a class of devices; for example, both mice and touch screens belong to the input device class.

The bus directory is the core directory of the bus, device, and driver model. This is because devices and drivers are dependent on a certain bus, such as USB, PCI, and platform buses. Devices and drivers rely on the bus’s management functions to find each other; for example, when a device registers with a bus, it looks for a driver, and when a driver registers, it looks for the devices it can support.

Most importantly, without the device model, it would be difficult for the application layer to know the relationship between drivers and devices since character device drivers do not provide this information, making it very troublesome for the device driver managers.

In fact, the bus class, device, and device driver in the kernel do not expose all the information to the user layer; for example, these three data structures have corresponding private data structures used by the kernel to maintain the linked list relationship of subordinate and superior bus device drivers. If exposed to the user layer, it could easily lead to system chaos due to user modifications. In reality, the user layer only cares about the view associations among the three, while the underlying associations do not need to be concerned.

3) The device driver model provides a unified power management mechanism. Clearly, we do not see power management interfaces in the file_operations interface of character device drivers. Power consumption management is essential for operating systems. Power management should not be the responsibility of application developers but rather managed by the system; for example, if a mobile phone has not been touched for a long time, it should enter sleep mode. This state change should be handled by the system, and various devices should also enter sleep mode managed by the system. Therefore, it is reasonable that file_operations does not provide power management interfaces to application programs, and it is very reasonable for the device model to provide power management as a system management mechanism.

4) The device driver model provides reference counting for various object instances to prevent objects from being mistakenly deleted by the application layer. All data structures in the device model inherit from kobject, which provides basic counting functionality.

5) The device driver model provides multiple ways for the application layer; users and the kernel can interact through sysfs, such as directly modifying device parameters by changing the contents of files in the /sys directory.

In summary, the device driver model focuses on the kernel’s management of buses, devices, and drivers, exposing this management information to the application layer, while character device drivers focus on the implementation of device driver functions.

2. Core Interfaces of Device Driver Models

Core interfaces of the device driver model

bus_register(struct bus_type *bus) Register bus

device_add(struct device *dev) Register device

driver_register(struct device_driver *drv) Register driver

class_create(owner, name) Create device class

etc.

3. Differences between Device Driver Models and Character Device Drivers

The device driver model focuses on the kernel’s management of buses, devices, and drivers, exposing this management information to the application layer, while character device drivers focus on the implementation of device driver functions.

4. sysfs File System

1. Relationship between sysfs File System and Device Driver Models

The sysfs file system is the carrier that allows the device driver model to expose its management information to users. Their relationship is as follows:

1) The hierarchical relationship of the device driver model (such as child devices and their parent devices) is reflected through the parent and child directories of the sysfs file system.

2) The peer relationship of the device driver model (such as the relationship between devices managed by device classes and specific devices) is realized through symbolic links in the sysfs file system.

3) The attributes of the device driver model (such as device parameters, device names, device numbers, etc.) are recorded through the contents of files in the sysfs file system.

4) The kobject in the data structure of the device driver model corresponds to directories in the sysfs file system, while the struct attribute members in the data structure correspond to files in the sysfs file system. The correspondence means that when kobjects like device, device_driver, and bus register with the system, they will call the sysfs create_dir interface to create corresponding directories, while those with struct attribute members will call the sysfs sysfs_create_file interface during registration to create files.

2. Core Interfaces of sysfs

sysfs_create_file(struct kobject * kobj, const struct attribute * attr) Create attribute file

sysfs_create_dir(struct kobject * kobj) Create directory

int sysfs_open_file(struct inode *inode, struct file *file) Open file in sysfs file system format

sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos) Read operation

sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t *ppos) Write operation

3. Reading and Writing of sysfs File System and Attribute Files

sysfs_read_file is the entry point for reading and writing in the sysfs file system, but the driver needs to provide the actual read and write operations, which are the show and store interfaces in the struct sysfs_ops data structure.

Sysfs is a memory-based file system that disappears upon power loss, and all operation interfaces of sysfs access kernel data structures in memory. If a user uses the cat command to read the content of an attribute file (such as dev), the following process occurs:
1) fd=open(“dev”)->vfs_open(“dev”)->sysfs_open(“dev”) to obtain the file handle
2) read()->vfs_read()->sysfs_read_file()->sysfs_ops->show() This show interface is generated when the device registers and provides the read interface for that attribute file. The implementation of the read interface naturally involves read access to that attribute parameter.
/sys is mounted with the sysfs file system, so all operations on files or directories under /sys will be accessed through the sysfs file interfaces.

5. Platform Device Drivers

In platform device drivers, the term “platform” refers to the platform bus, i.e., platform_bus_type, which is one of the many buses in Linux, such as USB bus, PCI bus, I2C bus, etc. However, the platform bus is a virtual bus designed specifically to manage controllers on the SOC (such as watchdogs, LCDs, RTCs, etc.), all of which are directly accessed by the CPU on its bus. In contrast, devices like USB and PCI access devices outside the SOC chip through specific timing. The relationship reflected by platform device drivers is a subset of the device driver model, making it easier to understand the relationship between the two when viewing platforms as a concept of a bus.

1. Relationship between Platform Device Drivers and Device Driver Models

1) The platform device driver interface creates the relevant platform device classes (/sys/class/platform_bus), platform buses (/sys/bus/platform), and platform devices (/sys/devices/) in the device driver model view.

2) Both platform_device (platform device) and platform_driver (platform device driver) are registered to the platform bus, creating corresponding device and driver directories under /sys/bus/platform/.

3) The platform bus is responsible for matching the devices and drivers registered to it, and upon successful matching, it calls the driver’s probe interface.

4) Platform device drivers use device driver model interfaces to assist in creating corresponding device files (located under /dev/).

Relevant interfaces include:

platform_device_register(struct platform_device *pdev) Register platform device

platform_driver_register(struct platform_driver *drv) Register platform device driver

Both interfaces implement matching between platform drivers and devices, and upon successful matching, call the driver’s probe interface.

2. Relationship between Platform Device Drivers and Character Device Drivers

We assume that this platform device is a character device.

The relationship between platform device drivers and character device drivers begins with the driver’s probe interface, where the tasks to be completed by the character device driver are implemented, specifically by requesting a device number through alloc_chrdev_region and registering the driver’s struct file_operations using cdev_add. Additionally, to automatically create device files for application layer access, the class_create and device_create interfaces must be called to create corresponding device classes and devices under the platform device class, triggering a uevent event that calls mdev to create the device files.

3. Development Process of Platform Device Drivers

1) Move the implementation of the char_init function from the character device driver to the probe interface of the platform_driver.

2) In char_init, call platform_device_register and platform_driver_register to register the device and driver, respectively. In fact, for a well-portable system, platform_device_register is completed during the Linux startup process. Therefore, char_init generally only registers the driver with platform_driver_register.

The detailed implementation principles and development processes of platform device drivers will be discussed in another article. The focus of this article is to clarify the relationships between character device drivers, device driver models, sysfs, and platform device drivers.

Leave a Comment