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
2. Character Device Drivers
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.
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.
sysfs_create_file(struct kobject * kobj, const struct attribute * attr) Create attribute file
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.
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.