Linux 2.6 character Device Driver

Source: Internet
Author: User

○ Description
Note is applicable to linux kernels later than 2.6.10.
Take note of the scull program provided by Linux Device driver3 (main. C and scull. h) as the record main line, and in the order of various system calls and function call flows in the driver. For example, module_init () and module_exit () are a pair of system calls, which are generally discussed together in books, but not in this note, therefore, module_init () is placed at the beginning of the note, that is, when the module is loaded, and module_exit () is placed before the end of the note, that is, to uninstall the module, we will discuss it again.
The purpose of this note is to sort out all the knowledge points mentioned in Linux Device drvier3 and clarify the clues so that I can understand the Linux driver as a whole or globally.

Note: For personal understanding, mistakes are inevitable!

**************************************** ***

The workflow of the driver module is divided into four parts:
1. Use commands provided by Linux to load the driver module
2. initialize the driver module (after initialization, it enters the "Latent" State until there is a system call)
3. When a device is operated, that is, when a system call is made, various service functions provided by the driver module are called.
4. Uninstall the driver module

I. Driver Loading

The Linux driver can be directly compiled into the kernel, compiled into a module, and loaded manually when the driver module is required. The former is still to be learned.
Module driver. Linux provides two commands for loading: modprobe and insmod.
Among them, modprobe can solve the dependency of the driver module, that is, if the driver module being loaded references the kernel symbols or other resources provided by other modules, modprobe will automatically load those modules. However, when using modprobe, the driver module to be loaded must be placed in the search path of the current module. The insmod command does not consider the dependency of the driver module, but can load the driver module in any directory.
In general, in the driver development stage, it is easier to use/sbin/insmod, because you do not need to put the module in the current module search path.
Once the module is loaded using insmod, the Linux kernel calls the module_init (scull_init_module) special macro, where scull_init_module is the driver initialization function and can be customized.
When using insmod to load a module, you can also provide module parameters. However, you need to add several statements to the driver source code to make the module parameters visible to insmod and the driver, for example:
Static char * Whom = "world ";
Static int howmany = 10;
Module_param (howmany, Int, s_irugo );
Module_param (whom, CHARP, s_irugo );
In this way, when the driver is loaded using the/sbin/insmod scull. Ko whom = "string" how133 = 20 command, the values of whom and howmay will be passed into the scull driver module.

After the driver module is loaded, if the device is operated (such as open, read, write), the driver module calls the corresponding function to respond to the operation.
Then, when operating on the device, how does the driver module know that it should respond, rather than other driver modules? That is to say, how does the Linux kernel know which driver module should be called?
Currently, I only know that there are two ways to associate the device with the driver module (maybe it should be said that one way to provide access to the device is more appropriate ): one is to call the driver module based on the device IDS (such as the device IDs and product IDs of PCI and USB devices) of some devices; the second is to create the corresponding device node (that is, the device file) under the/dev directory based on the device's primary and secondary device numbers, so that when you operate the device files under the/dev directory, the corresponding driver module is called.

Ii. initialization of the driver module

When using insmod to load a driver module, you need to make the driver module initialize the device to let the Linux kernel know the device (or module ?), And later, let the Linux kernel know which functions of this module can be used for system calls when operating on the device (such as open, read, write, and so on.
Therefore, the scull_init_module function mainly performs the following tasks:
A) Allocate and register the Primary and Secondary device numbers.
B) initialize the struct representing the device: scull_dev
C) initialize the mutex init_mutex (this note is not organized)
D) initialize the cdev struct of the device in the kernel. It is mainly used to associate the device with the file_operations struct.

1. Allocate and register the Primary and Secondary device numbers
The device number is allocated and registered in the driver module. That is to say, the driver module has this device number (in my understanding ), the device files in the/dev directory are created based on the device number. Therefore, when accessing the device files in the/dev directory, the driver module knows that, you should launch the service yourself (kernel notification, of course ).

In the Linux kernel, the main device ID identifies the driver corresponding to the device and tells the Linux kernel which driver is used to serve the device (that is, the device file under/Dev; the device number is used to identify a specific and unique device.

In the kernel, the variables of the dev_t type (actually a 32-bit unsigned integer) are used to save the Primary and Secondary device numbers of the device. The 12-bit high represents the primary device number, the 20-digit device number indicates the next device number.
There are two ways for a device to obtain the Primary and Secondary device numbers: one is to manually specify a 32-digit number and associate it with the device (that is, register with a function ); the other is to call the system function to dynamically allocate a primary/secondary device number to the device.

To manually specify a primary/secondary device number, use the following function:
Int register_chrdev_region (dev_t first, unsigned int count, char * name)
First is the device number that we manually specify, count is the number of consecutive device numbers requested, and name is the name of the device associated with the range of the device numbers, it will appear in/proc/devices and sysfs.
For example, if first is 0x3ffff0 and count is 0x5, this function registers device numbers for five devices, namely 0x3ffffff0, 0x3ffff1, 0x3ffff2, 0x3ffff3, 0x3ffff4, among them, 0x3 (12-bit high) is the master device number that these five devices share (that is, these five devices use the same driver ). 0xffff0, 0xffff1, 0xffff2, 0xffff3, and 0xffff4 are the sub-device numbers of the five devices respectively.
Note that if the Count value is too large, the requested device number range may overlap with the next primary device number. For example, if first is 0x3ffff0 and count is 0x11, first + Count = 0x400001, that is, the number of the primary device allocated to the last two devices is no longer 0x3, it's 0x4!
One disadvantage of using this method to register a device number is that if the driver module is widely used by others, therefore, it cannot be ensured that the registered device number is a device number not assigned to others in Linux.

To dynamically allocate device numbers, use the following functions:
Int alloc_chrdev_region (dev_t * Dev, unsigned int firstminor, unsigned int count, char * name)
This function must be passed to the specified device number firstminor (usually 0), the number of devices to be allocated, and the device name, after this function is called, the device numbers automatically allocated are stored in Dev.
Dynamic Allocation of device numbers can avoid the disadvantages of manually specifying a device number, but it also has its own disadvantages, that is, it is impossible to create a device node under/dev in advance, because the Dynamic Allocation of device numbers cannot always be consistent when the driver module is loaded every time (in fact, if no other module is loaded when the same driver module is loaded twice, the automatically assigned device numbers are the same, because the kernel allocation device numbers are not random, but the book says some kernel developers will handle them randomly in the near future ), however, this disadvantage can be avoided, because after the driver module is loaded, we can read the/proc/devices file to obtain the primary device number allocated to the device by the Linux kernel.
Linux Device driver3 provides the scull_load and scull_unload scripts to create and delete device nodes for devices under dynamic allocation. In fact, it also uses the awk tool to obtain information from/proc/devices, and then uses mknod to create a device node under/dev.
In fact, the scull_load and scull_unload scripts can also be applied to other drivers. You only need to redefine the variables and adjust the mknod statements.

Three macros related to the primary and secondary device numbers:
Major (dev_t Dev): obtains the master device number based on the device number Dev;
Minor (dev_t Dev): obtains the next device number based on the device number Dev;
Mkdev (INT major, int minor): Creates a device number based on the master device number major and sub-device number minor.

2. initialize the scull_dev struct of the device.
Scull Source Code defines a scull_dev struct, including qset, qutuam, semaphore SEM, cdev, and other fields. The initialization of qset and qutuam is irrelevant to the knowledge of the Linux driver, so we will not discuss it.
As long as I know, some device-related variables can be initialized in the module initialization function called during module loading. However, according to the author of Linux Device drvier3, it is best to initialize device-related variables or resources in open functions, such as interrupt numbers, although it is also allowed to register in the module initialization function, it is best to allocate it again in the first time the device is opened, that is, the open function.

3. initialize the mutex init_mutex.
Mutex, a variant of the semaphore, is related to the completion, spin lock, and so on. It will be discussed later.

4. initialize the cdev struct that represents the device in the kernel.
In the Linux kernel, The cdev struct actually represents a device. Before the kernel calls open and read operations on devices, it is necessary to allocate and register one or more cdev structures.
I think it can be understood that the primary and secondary device numbers are foreign-related and are mainly used to determine the identity when interacting with external devices (other than the driver module and the Linux kernel; the cdev struct is internal. When you need to pass some variables, pointers, buffers, and other things between the module or the Linux kernel, or call a service function in the driver module, the cdev struct is required.

In the scull function, a custom scull_setup_cdev function is used for the allocation, registration, and initialization of the cdev struct. In this function, the following four statements are used to initialize cdev:
Cdev_init (& Dev-> cdev, & scull_fops );
Dev-> cdev. Owner = this_module;
Dev-> cdev. Ops = & scull_fops;
Err = cdev_add (& Dev-> cdev, devno, 1 );
(The dev variable is a struct defined by the scull program that represents the device. It contains the cdev struct. For Dev, The cdev struct should be at its core)

The first statement initializes the cdev struct. For example, it allocates memory for the cdev struct and specifies file_operations for the cdev struct. The role of the third statement seems to be the same as that of the first statement. But in the scull program, it must have an intention to write this code. You may need to check the Linux kernel source code to understand it, but I understand it as follows: in the first statement, file_operations is used to tell the Linux kernel that the file_operations related to the cdev struct are scull_fops, while the second statement specifies the file_operations field of cdev as scull_fops.
Scull_fops is a variable of the file_operations type, and file_operations is also a struct and an important struct in the Linux driver. In the scull program, the definition is as follows:
Struct file_operations scull_fops = {
. Owner = this_module,
. Llseek = scull_llseek,
. Read = scull_read,
. Write = scull_write,
. IOCTL = scull_ioctl,
. Open = scull_open,
. Release = scull_release,
};
In the preceding definition, the first one is. the owner field indicates that the owner of the file_operations struct is the driver module, and the following fields tell the driver module that when a corresponding system call arrives at the module, which function should the module call to call the service for the system. For example, if an open system call arrives at the module, the module will know by querying the file_operations struct. The scull_open function is related to the open system call, the module calls the scull_open function to call the service for the open system. The other fields are similar.
Of course, the file_operations struct defined in the Linux kernel also includes some other fields, such as Asynchronous read/write, but let's talk about it later.

The second statement for cdev Initialization is Dev-> cdev. Owner = this_module. This statement indicates that the owner of the cdev struct being initialized is the current module.

The last statement initialized for cdev is err = cdev_add (& Dev-> cdev, devno, 1). The purpose of this statement is to tell the kernel about the cdev struct. Because the cdev_add function may fail to be called, you need to check the return value of the function call. Once the cdev_add call is successful, our device will be "active! That is to say, operations performed by external applications on it will be allowed and called by the kernel. Therefore, cdev_add cannot be called before the driver is fully prepared to process operations on the device.

Iii. device operations

After the driver module is initialized due to insmod loading, it enters the "Latent" state, that is, if there is no system call (such as open or read ), other functions defined in the module will never run!
The device operation here refers to the action that the module should call when a system call reaches the driver module.
For driver development, I only care about how to pass variable values in some external applications to the driver module.
In the scull program, functions related to device operations are mainly divided into three types: initialization functions, actual operation service functions and cleaning functions. There is only one initialization function, that is, the open function, and the operation service function includes the read, write, llseek and other functions. The cleaning function is the release function.

1. Open Functions
The open function provides the initialization capability for the driver to prepare for future operations.
There is also an initialization action after the driver is loaded with insmod, but the initialization is relative to the entire Linux kernel, or to the global initialization of the entire module when it is foreign; the initialization of the open function is relative to the device operation and is an internal initialization of the driver. For example, it initializes a variable (such as a file structure) used for later read operations, for example, initialize the device and clear the buffer.
In most drivers, open should do the following:
A. Are you sure you want to enable the specified device?
B. Check device-specific errors (such as equipment not ready or similar hardware problems)
C. If the device is enabled for the first time, initialize it.
D. Update the f_op pointer if necessary.
E. Allocate and fill in the data structure placed in filp-> private_data

The following is a prototype of the open function (defined in the file_operations struct ):
INT (* open) (struct inode * inode, struct file * filp)
During driver development, you must implement the function. Of course, the name of the open function can be customized. If you enter the open field in the file_operations struct, enter the custom OPEN function name. In the scull program, the scull_open function name is used.

In the open function prototype, there are two parameters: inode and filp, both of which are passed to the driver module by an external application when operating the device by calling the system call. The driver module can use these two parameters to determine the specific device to be opened. In fact, the specific device mentioned here does not mean that the driver module needs to determine the device to be served from all the devices installed in the system, instead, a module needs to determine from a certain type of device with the same master device number that it wants to serve.
The reason for this is that the driver module corresponds to all devices with a master device number. In other words, the Linux kernel only uses the device's primary device number, regardless of the device's secondary device number. If two, three, or more devices have the same primary device number, the Linux kernel will only call the same driver module no matter which of these devices the external applications need to operate on. However, the driver module cannot ignore the device number because it deals with a specific device, therefore, it needs to find the device number in the parameter passed to the open system during the call to determine the unique device (maybe the driver module can also operate on several devices at the same time, but I cannot remember it at the moment ).
However, the above mentioned method is only one of the methods for finding a specific device through the next device number. The other method is to identify a specific device through the cdev struct.
The cdev struct or secondary device number owned by the device is stored in the inode parameter of the open function. We can use container_of macro to determine the specific device through the cdev of inode, you can also use the iminor macro to determine the next device number from the I _rdev of inode (I _rdev is a dev_t type variable in the inode struct, where the real device primary and secondary numbers are saved ).

For the file parameter in the open function, the scull program mainly uses it to do two things: one is to save the scull_dev struct that represents the device according to cdev to file-> private_data, in this way, you can easily access the device struct in the future, instead of calling the container_of macro or iminor macro every time to find the device struct. The second is determined by the f_flags field in the file struct, in this open call, the device is opened in write mode or read mode.

2. Read Functions
Many device operation service functions can be defined in the file_operations struct of the driver module, but I am concerned about how these functions interact with the system call, or external applications, no matter how specific device operations are implemented, only the READ function is recorded as the representative.

The READ function is prototype as follows:
Ssize_t read (struct file * filp, char _ User * Buf, size_t count, loff_t * f_pos)

The READ function prototype has four parameters: filp, Buf, count, and f_pos.
The file struct pointer parameter can be used to determine the device to operate, because in the open function, we save the struct representing the device to the private_data field of filp.
The Buf parameter is a pointer to the buffer of the user space (_ user before the Buf indicates the user space). For read, you can put the data to be transmitted to the external application into this buffer. Of course, we cannot simply copy data to this buffer, but use some functions provided by the Linux kernel, such as the copy_to_user function.
Count is the length of data transmitted by the request.
F_pos is a pointer to a long offset type object, indicating the location where external applications perform access operations in files.
The Return Value of the READ function is a signed integer, which is generally the actual number of accesses to the read operation.

3. Release Functions
The functions of the release function are opposite to those of the open function. Sometimes, the implementation of the release function is called device_close rather than device_release. However, regardless of the form, this device method should complete the following tasks:
A. Release all content allocated by open and stored in file-> private_data.
B. Disable the device during the last close operation.

The relese function is called by the close system, but not every call to the close system will call the release function. Only the close system call that actually releases the device data structure will cause the call of the release function. Because the Linux kernel maintains the number of times that each file struct has been referenced. Only when the counter of the file struct is set to 0 will the close system call reference the release function, this occurs only when this structure is deleted. Therefore, each open driver will only see a corresponding release call.

4. Uninstall the driver module

An clearing function is required for each important module. This function logs out the interface before the module is removed and returns all resources to the system. If a module has no clear function defined, the kernel Cannot uninstall the module.
You can use/sbin/rmmod scull to uninstall the Linux driver module. ko command, then the Linux kernel will call the clear function defined in the special macro of module_exit (scull_cleanup_module) in the driver. That is to say, the module_exit declaration is used to help the Linux kernel find the clear function of the module. In the scull program, the cleanup function is the scull_cleanup_module function.
The clear function of the module needs to cancel all the resources registered by the initialization function, and is used (but not necessary) to cancel the initialization function registration in the opposite order. It should be noted that the initialization function here refers to the initialization function using module_init macro declaration, rather than the open function. The corresponding function to the open function should be the release function.
In the scull program, the cleanup function mainly does two things: first, free all the memory allocated for the scull device, and second, the device number registered by the initialization function.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.