A char driver, char device and char device number
In this article, we delve into the world of character drivers, device numbers, and the essential kernel APIs. This knowledge is crucial for creating a seamless connection between user-space applications and kernel drivers.
Character drivers play a pivotal role in interfacing user-level applications with hardware devices. In this article, we’ll explore the intricacies of character drivers, device numbers, and the critical kernel APIs that facilitate their interaction.
- Character drivers are used to interact with devices in the Linux kernel.
- Char devices refer to hardware components like memory areas, often called pseudo devices.
- Userspace applications communicate with these devices using system calls like open, read, and write.
User Space vs. Kernel Space (Figure 1)
Character drivers operate in the kernel space and manage device interactions for user-space applications.
In our case, the hardware is a simple memory area. We call it a sudo device.
When the user application wants to talk to the hardware, it uses it’s a system call, such as open, read and write.
When the user application uses an open system call, that open system call should connect to the open system call implementation of your driver.
And when the user that application uses to read and write system calls, those system calls must connect to the read and write implementations or methods of your driver.
Connecting Userspace to Driver:
We have to understand how to establish such connections from userspace to the device drivers. And the drivers read and write functions or methods that will then forward data to the hardware, or it will read data from the hardware.
The read method of the driver will read from the hardware, and it should submit data back to the user, and the write method of the driver should accept data from the user space, it should write the data to the hardware.
If you’re wondering how these a user space system calls will get connected to the driver system call implementation methods, this is taken care by the VFS (that is virtual file system) of the kernel. That means our device driver has to get registered with the VFS by using virtual files systems kernel APIs. All those APIs we are going to understand in the subsequent articles.
Device Number Importance
Device numbers are crucial for connecting device files to their respective drivers.
Here, the driver which we are going to write lives in the kernel space. And it controls the hardware using its file operation methods such as open, read, write, etc. And the driver creates a device file interface in the user’s space for user-level programs to communicate with the driver.
Now the question is when you use, let’s say, open system call on /dev/rtc device file, how does the kernel connect the open system call to intended driver’s to open method.
How does that connection is established?
To establish the connection, the kernel uses something called “Device number.” Let’s assign a number to the driver as 4 and also to /dev/rtc device file, let’s say 4:0.
Here 4 denotes the number of the driver which should be used to connect this device file access to the driver’s methods. And 0 here is a device instance. There could be multiple device files, all are handled by the same driver.
For example, there could be 4 RTC devices, as shown in Figure 3. All are handled by a single RTC driver.
In that case, there could be four device files. Like dev/rtc0, rtc1, or 1, or 3, like that. In this case, the device numbers could be 4:0, 4:1, 4:2, or 4:3.
Observe here this part is the same for all the devices, which refers to the driver, and this part refers to the device instances.
Device numbers consist of two parts:
The major number (identifying the driver) and the minor number (indicating the specific device file).
Major numbers are essential for driver identification, while minor numbers help the driver differentiate between various device files.
Collectively, number 4 is called as a device number. Left part is called a major number, and the right part is minor number. When the user-level program uses an open system, call on this device file. The system call will first handled by the virtual file system, that is, VFS in the kernel space. So, the VFS gets the device number and compares it with its driver registration list. That means this driver has to get registered with the VFS using the device number. That is what we call a character device add, known as CDEV_ADD.
The VFS compares the device number of the device file with the driver’s list and picks the correct driver, and connects the user requests to the file operation methods of the selected driver.
If you just do ls -l in the /dev directory(as shown in Figure 5), you see various device files, and here, there are two important columns. These columns signifies the device number.
For example, if you consider nvram. nvram is a device file which is created to handle the nvram device of the system. This is a device number of this a device. And here, 10 is the major number which identifies the driver, and 144 is a minor number.
Here there are two device files, sda, and sda1.
Sda1 is a partition of my hard disk. This signifies the hard disk, and this is a partition of a hard disk. The disk partition is of type EXT3 or EXT4. And these two device files they have the same major number that is 8, and these are the minor numbers 0 and 1.
The driver will use these minor numbers to distinguish on which device file the read and write operations have been issued. That’s why, minor numbers help the driver to differentiate between the device files.
From the previous article, we came to know that, first we have to a create some device numbers, and after that, the driver has to get registered with the virtual file system of the kernel to make a successful connection.
Let’s see some of the steps for connection establishment between a device file access and the driver. First, the driver has to create a device number, it has to create device files. After that, the driver has to make a registration with the VFS, what we call as character device registration CDEV_ADD.
And after that, the driver has to implement the file operation methods for open, read, write, lseek system calls.
Let’s start implementing these steps one by one.
Let’s create a device number. Your driver has to ask the kernel to dynamically allocate the device number or numbers. Basically, what you should be doing here is, you should be using kernel APIs and kernel utilities(shown in Figure 6) in order to a request various services from the kernel.
Now, to create a device number, you just have to use a kernel API alloc_chrdev_region(). So, you have to use alloc_chrdev_region(). This creates a device number. And for the registration, you can use these APIs cdev_init() and cdev_add().
And after that, the driver should create a device files. For that, you can use these kernel APIs class_create() and device_create(). The creation things we are going to do in module initialization function.
Whenever you load a module, so these creation a services must be executed and your driver must be ready to accept a system calls from the user space program.
That’s why it makes sense to do this creation process in the module initialization function.
When you remove the module, it’s better you delete all those a resources what you requested from the kernel. Otherwise, it will simply consume a resources of the kernel. That’s why, let’s say, if you use unregister_chrdev_region(), it will delete the device number which is allocated for your module, that it can be reused for some other module.
After that, you can use cdev_del() to delete the registration, that will free some memory. class_destroy and device_destroy will delete your device files. That’s why, all these deletion things we are going to do in module clean-up function. Because these deletion things should be executed whenever you remove the module.
Use of Kernel APIs and Header Files:
When you use all these a kernel functions and data structures, you should be using the appropriate kernel header file (as shown in Figure 8).
For example, alloc_chrdev_region() and unregister_chrdev_region(), these APIs prototypes are mentioned in include/linux/fs.h. Whenever you use any services of the VFS, then I think most probably you should be using a fs.h.
Even if you are using any data structures of the VFS such as file operation structures, a cdev structures, a file structure, which will be exploring in later articles, you should be using a fs.h. Because, the structure definitions are mentioned in the fs.h. And for cdev_init(), cdev_add(), cdev_del(), you should be using a include/linux/cdev.h and for a device creation, class creation, device_destroy(), class_destroy() related APIs. You should be including linux/device.h.
Basically for a character device, all these header files are very important and doesn’t matter whether you use it or not. Please include fs.h, cdev.h,device.h uaccess.h. This is basically for copy to user and copy from user a utilities, I will talk about these utilities later. And for the VFS structure definition, also you should include fs.h.
In the next article, let me explain how to use alloc_chrdev_region() to dynamically create char devicenumbers.
Get the full course on Linux Device Driver Here.
FastBit Embedded Brain Academy Courses
Click here: https://fastbitlab.com/course1