Character device driver Foundation

1, Open the road of driving development

1. Preparation for driving development
(1) The development board of linux system running normally. It is required that the zImage of linux in the development board must be compiled by itself and cannot be compiled by others. Because you need a kernel source tree.

(2) The kernel source code tree is actually a kernel source code after configuration and compilation. When compiling the driver, you need to use the kernel source tree. The kernel source tree is the kernel source code. Compile the module, you need the kernel source code.

(3) For nfs mounted rootfs, an nfs server must be built in the host ubuntu.
1. Start in NFS mode and mount the root file system;
2. Burn the root file system into the development board, start it normally, and mount it through the mount command after startup;

2. Steps to drive development
(1) Driver source code writing, Makefile writing (with a fixed mode, you can refer to the existing writing), compilation

(2)insmod loading module m (dynamic installation of driver), testing (the complete driver is to be called by the application program, and the driver is encapsulated into API functions, which are called by the application program to work), rmmod unloading module

3. Practice
(1)copy the kernel source code of the development board, find a clean directory (such as / root/driver), unzip it, and configure compilation. After compilation, you get:
<1> Kernel source tree (/ root/driver/kernel)
<2> Compiled zImage

Compile step 1 Modify the makefile and specify the schema and cross compilation chain directory

Compile step 2 make x210ii_ qt_ Defconfig (if you can't remember, you will be prompted if an error is reported. Which directory is the target of make)

(2) Use the fastboot tool to burn the zImage obtained in step 1 into the development board and start it (or throw the zImage into the tftp shared directory, and tftp downloads and starts when uboot starts). After the driver is compiled in the future, it can be tested in this kernel. Because this zImage is together with the kernel source tree, there will be no error in the version verification during driver installation (otherwise, errors may occur).

The final generation of the compiled driver file needs to be ko file (kernel object), driver file.

2, The simplest module source code analysis

//module_test.c

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit

// Module installation function
static int __init chrdev_init(void)
{	
	printk(KERN_INFO "chrdev_init helloworld init\n");
	//printk("<7>" "chrdev_init helloworld init\n");
	//printk("<7> chrdev_init helloworld init\n");

	return 0;
}

// Module unload function
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx macro is used to add module description information
MODULE_LICENSE("GPL");				// Describe the license for the module
MODULE_AUTHOR("aston");				// Describe the author of the module
MODULE_DESCRIPTION("module test");	// Describe the introduction of the module
MODULE_ALIAS("alias xxx");			// Describes the alias information for the module

1. Common module operation commands
(1)lsmod(list module, which displays the module list) is used to print out the list of modules installed in the current kernel

(2)insmod (install module) is used to install a module into the current kernel. The usage is insmod xxx.ko

(3)modinfo (module information) is used to print the information of a kernel module. The usage is modinfo xxx.ko

(4)rmmod (remove module) is used to uninstall an installed module from the current kernel. The usage is rmmod xxx (note that when uninstalling a module, you only need to enter the module name instead of adding a. ko suffix)

(5)modprobe: load or unload kernel modules according to modules The dep.bin file is loaded to automatically solve the dependency table between modules

(6)depmod: find all modules in / lib / modules / (uname - R) / and create modules Dep.bin file, which records the module location and dependencies

2. Module installation
(1) First lsmod, then insmod. Check the module records in the system before and after installation. Practical tests show that the kernel will put the newly installed module at the front of the lsmod display.

(2)insmod and module_init macro. Module is used in the module source code_ The init macro declares a function (chrdev_init function in our example) to specify chrdev_init. This function is bound to the insmod command, that is, when we insmod module_test.ko, the actual operation inside the insmod command is to help us call the chrdev_init function.

According to this analysis, chrdev should be visible when insmod_ A chrdev printed out using printk in init_ Init string, but I didn't actually see it. The reason is that it is intercepted in ubuntu. How can I see it? You can see it by using the dmesg command in ubuntu.

(3) When the module is installed, insmod helps us call the module_ In addition to the functions declared by the init macro, some other things are actually done (for example, lsmod can see an additional module, and insmod helps us record internally), but we don't care.

3. Version information of the module
(1) Use modinfo to view the version information of the module

(2) There is also a certain version information in the kernel zImage

(3) When insmod, the vermagic of the module must be the same as that of the kernel, otherwise it cannot be installed. The error message is: insmod: ERROR: could not insert module module_test.ko: Invalid module format

(4) The module version information is a security measure to ensure the compatibility between the module and the kernel

(5) How to ensure that the vermagic of the module is consistent with that of the kernel?
The kernel source tree of the compilation module is the kernel source tree of the running kernel. To put it bluntly, the module and kernel should go out of the same door.

4. Module source code function analysis

static int _init chrdev_init(void);
static void _exit chrdev_exit(void);

module_init(chrdev_init);
module_exit(chrdev_exit);

When writing driver modules, two things are often used and must be used: module_init and module_exit, which are called respectively when loading and unloading the driver, that is, when calling insmod and rmmod commands, but insmod and rmmod do not recognize these two. It recognizes init_module and cleanup_module, actually init_module and cleanup_module is equivalent to module_init and module_ Alias for exit

5. Module unloading
Detailed explanation:
https://blog.csdn.net/u013216061/article/details/72511653

(1)module_ Corresponding relationship between exit and rmmod
(2)lsmod view the module record changes of the system before and after rmmod

6. Common macros in modules
(1)MODULE_LICENSE, license for the module. It is generally declared as a GPL license, and it is best not to be less, otherwise inexplicable errors may occur (for example, some obvious function promotions cannot be found).
(2)MODULE_AUTHOR, the author who describes the module
(3)MODULE_DESCRIPTION, which describes the introduction information of the module
(4)MODULE_ALIAS, alias of the module

7. Function modifier
(1)__init is essentially a macro definition. There is #define in the kernel source code__ init xxxx. gcc extension syntax, this__ The function of init is to put the function modified by it into init.text section (originally, by default, the function is placed in the. text section).

All such functions in the whole kernel will be linked by the linker init.text section, so all kernel modules__ Init modified functions are actually put together. When the kernel starts, it will be loaded uniformly init. These modules in the text section install functions. After loading, this section will be released to save memory.

(2)__exit
The effect is similar to__ In the init section, put the code into one In the exit section;

(3)static
This keyword makes the modifier function available only in this file; Modified the link properties of this function

8. printk function details
(1)printk is a function used to print information in the kernel source code. Its usage is very similar to printf.

(2) The biggest difference between printk and printf: printf is a C library function, which is used in application layer programming and cannot be used in linux kernel source code; Printk is a printing function encapsulated in the linux kernel source code. It is an ordinary function in the kernel source code. It can only be used within the scope of the kernel source code, not in application programming.

(3)printk has more than printf: the setting of print level. The print level of printk is used to control whether the information printed by printk is displayed on the terminal. The debugging information in the application is either turned on or off. It is generally implemented by conditional compilation (DEBUG macro). However, in the kernel, because the kernel is very large and there is a lot of printing information, sometimes when debugging the kernel as a whole, the printing information is either too much to find, or one cannot be debugged. Therefore, the concept of printing level comes into being.

(4) The command line of the operating system also has a print information level attribute with values of 0-7.
When executing printk in the current operating system, the print level in printk will be compared with the print level set in my command line. Information less than the level set in my command line will be released for printing, and information greater than will be intercepted.
For example, the default print level in my ubuntu is 4, so those with a level lower than 4 set in printk can be printed, and those with a level greater than 4 cannot be printed.

Viewing method: cat /proc/sys/kernel/printk
Modification method: echo 4 > / proc / sys / kernel / printk

(5) The print level control of this printk in ubuntu cannot be practiced. No matter how you set the level in ubuntu, it cannot be printed directly. You must check it with the dmesg command.

9. About header files in driver module
(1) The header file contained in the driver source code is not the same as the header file contained in the original application programming program.
The header file contained in the application program is the header file of the application layer, which is brought by the compiler of the application program (for example, the header file path of gcc is under / usr/include, which is independent of the operating system). Driver source
Code is part of the kernel source code. The header file in the driver source code is actually the header file in the include directory under the kernel source code directory.

The format of the header file included in this directory is:
#include <linux/xxx.h>

#Makefile
#Part I
#The kernel source tree of ubuntu. If you want to compile the modules installed in ubuntu, open these two
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

# Source tree directory of linux kernel of development board
KERN_DIR = /root/driver/kernel

#Part II
obj-m	+= module_test.o

#Part III and IV
all:
	make -C $(KERN_DIR) M=`pwd` modules 
#	
# KERN_DIR Represents the kernel source code directory, which is suitable for cross compilation of embedded development, KERN_DIR The directory contains all the information required by the kernel driver module##Type header files and dependencies.
# -C means enter the specified directory, namely KERN_DIR is the kernel source code directory. Call the Makefile at the top level of the directory, and the target is modules.
# The M=$(shell pwd) option allows the Makefile to return to the module source code directory before constructing the modules target, and generate XXX specified by obj-m in the current directory O objective
#Standard module.	
#	

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean

10. Makefile analysis of driver compilation
(1)KERN_DIR, the value of the variable is the directory of the kernel source tree we use to compile this module

(2)obj-m += module_test.o. This line indicates that we want to add module_test.c file is compiled into a module (- M means module, - y means to compile and link into zImage)

(3)make -C $(KERN_DIR) M=pwd modules is used to actually compile modules. The working principle is: use make -C to enter the directory of the specified kernel source code tree, and then use the module compilation rules defined in the kernel source code to compile the module under the source code directory tree. After compilation, copy the generated files to the current directory to complete the compilation.

(4)make clean, used to clear compilation traces

Summary: the makefile of the module is very simple. It can not complete the compilation of the module itself. Instead, enter the kernel source code tree through make -C (and then make modules) to borrow the kernel source code system to complete the compilation link of the module. The makefile itself is very patterned. Parts 3 and 4 never need to be moved. Only parts 1 and 2 need to be moved. 1 is the directory of the kernel source code tree. You must set it according to your own compilation environment; 2 is to be generated o documentation.

3, Debug the module with the development board

1. Set the bootcmd of uboot to make the development board download the zImage compiled from its own kernel source tree through tftp
set bootcmd tftp 0x30008000 zImage;bootm 0x30008000

2. Set the bootargs of uboot to enable the development board to mount rootfs from nfs (remember to enable rootfs in nfs form in kernel configuration)
setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

Modify according to your own file directory.

3. Modify Kern In Makefile_ Dir makes it point to its own kernel source tree

4. Compile your own driver Put the ko file into the nfs shared directory

5. After the development board is started, insmod, rmmod and lsmod are used for module experiments

If using modinfo fails, Baidu search solution: https://blog.csdn.net/weixin_39888281/article/details/93136861

Try to modify the printk print level so that the print information of the program can be printed when calling the command (essentially calling the function in the execution program module_test.c).

4, Working principle of character device driver

A module is a mechanism. The driver uses this mechanism.

1. Overall working principle of the system

(1) Application layer - > API - > device driver - > Hardware

(2)API: open, read, write, close, etc

(3) The driver source code provides real open, read, write, close and other function entities

2,file_operations structure (related to file operations)

Structure file_operations is in the header file Linux / Fs H, which is used to store the pointer of the function provided by the driver module to perform various operations on the device. Each element of the structure corresponds to the address of the function used by the driver module to process a requested transaction.

Detailed explanation: https://www.cnblogs.com/sunyubo/archive/2010/12/22/2282079.html
http://www.voidcn.com/article/p-vtmfwdfb-dm.html

(1) The element is mainly a function pointer, which is used to hook the address of the entity function (the function that really does things)

(2) Each device driver needs a variable of this structure type

(3) The device driver provides variables of this structure type when registering with the kernel

3. Register character device driver

(1) Why register drivers
Otherwise, the application call cannot find the driver

(2) Who is responsible for registration
Drive itself

(3) To whom
Register with kernel

(4) Where does the register_chrdev come from
From the kernel

(5) How about before registration? How about after registration? What are the results of registration?
The kernel issues it a certificate (such as a numeric number) so that the application can find and call the driver

4,register_ Detailed explanation of chrdev (#include < Linux / Fs. H >)

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __reg ister_chrdev(major, 0, 256, name, fops);
}

(1) The driver registers its own file with the kernel_ operations

(2) Parameters
Major: the main equipment number used for dynamic allocation. If the parameter major is equal to 0, it indicates the main equipment number dynamically allocated by the system; If it is not 0, it means static registration.

Name: name of this series of devices

fops: file operations associated with this device

(3)inline and static

Inline modifier, expressed as an inline function. Inline is only suitable for functions with simple code in the culvert body. It cannot contain complex structural control statements, such as while and switch, and the inline function itself cannot be a direct recursive function (it also calls its own function internally).

Inlining is code inflation At the cost of (copying), it only saves the cost of function call, so as to improve the execution efficiency of the function. If the time of executing the code in the function is greater than the cost of function call, the efficiency gain will be less. On the other hand, the code must be copied for each call of inline function, which will increase the total code of the program and consume more memory space.

It is appropriate to implement inline functions in header files, saving you the trouble of implementing them once for each file So the declaration and definition should be consistent. In fact, if the inline function is implemented once in each file, it is best to ensure that each definition is the same. Otherwise, undefined behavior will be caused. That is, if the definitions in each file are not the same, which one the compiler expands depends on the specific compiler Therefore, it is best to put the inline function definition in the header file

Static local variables are defined with the static modifier. Even if they are declared without an initial value, the compiler will initialize them to 0. Static local variables are stored in the global data area of the process. Even if the function returns, its value will remain unchanged. Variables allocate memory space in the global data area; The compiler automatically initializes it. Its scope is local scope. When the function that defines it ends, its scope ends.

5. How does the kernel manage character device drivers

(1) There is an array in the kernel (structure array, which can load up to 255 characters at the same time, but it is impossible for 255 character device drivers to work at the same time, generally not more than ten) to store the registered character device drivers

(2)register_chrdev stores the information of the driver we want to register (mainly) in the corresponding position in the array

(3)cat /proc/devices view the character device drivers (and block device drivers) that have been registered in the kernel

(4) Understand the concept of major
Multiple meanings: the number of equipment, the subscript of the equipment array (which should be minus one and counted from the beginning), and the main equipment numbers of multiple equipment are the same, indicating that they are related

6. The role of the / proc file system

The files in it are virtualized by the kernel with data structure Proc file system is a pseudo file system. As a special interface to access the kernel, it is often mounted under / proc. Most of the files in it are read-only, but we can still set some variables to change the kernel settings.

Detailed explanation: https://blog.csdn.net/acs713/article/details/78887800
https://blog.csdn.net/qq_42014600/article/details/90301888

/The proc file system provides a file based Linux internal interface. It can be used to determine the status of various devices and processes of the system.

/Proc file system is a special file system created by software. The kernel uses it to export information to the outside world. The / proc system only exists in memory and does not occupy disk space.

/Each file under proc is bound to a kernel function, which dynamically generates the contents of the file when the user reads the file. You can also modify the kernel parameters by writing the / proc file

5, Character device driver code practice

(1)Linux Drive common error return value:https://blog.csdn.net/encourage2011/article/details/53680056

(2)module license 'unspecified' taints kernel
 resolvent:https://blog.csdn.net/qq_37600027/article/details/100797451

(3)unregister_chrdev() -- Old version character device logoff function

Function: log off the device

static inline void unregister_chrdev(unsigned int major, const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}

explain:
Unregister kernel functions of device drivers

Variable:
major Main equipment No
name Equipment file

1. Ideas and framework
(1) Purpose: to add a drive housing to the empty module

(2) Core workload: file_operations and its element filling and registration drivers

2. How to write driver code
(1) Have a frame in your mind and know what you want

(2) The detailed code does not need to be typed word by word. You can find the reference code in the kernel and copy it to change it

(3) All the code you write must be clear in your heart and can't be ambiguous

3. Start
(1) Define file first_ Operations struct variable
Detailed explanation of specified initialization in Linux kernel driver: https://zhuanlan.zhihu.com/p/55768099

eg:
Similar to arrays, in standard C, structure variables are initialized in a fixed order. In GNU C, we can also initialize and specify a member through the domain.

struct student{
    char name[20];
    int age;
};

int main(void)
{
    struct student stu1={ "wit",20 };
    printf("%s:%d\n",stu1.name,stu1.age);

    struct student stu2=
    {
        .name = "wanglitao",
        .age  = 28
    };
    printf("%s:%d\n",stu2.name,stu2.age);

    return 0;
}

In the program, we define a structure type student, and then define two structure variables stu1 and stu2 respectively. When initializing stu1, we use the initialization method of standard C, that is, direct initialization in a fixed order. When initializing stu2, we use GNU C to initialize the domain name through the structure Name and age, we can directly assign a value to a specified member of the structure variable. Very convenient.

In the Linux kernel driver, GNUC is widely used to initialize structure variables through structure members. For example, in the character driver, we often see such a beginning
Initiation:

static const struct file_operations ab3100_otp_operations = {
.open        = ab3100_otp_open,
.read        = seq_read,
.llseek      = seq_lseek,
.release     = single_release,
};

In the driver, we often use file_operations is a structural variable to register the driver we developed, and then execute the relevant functions of our driver implementation in the form of callback. Structure file_operations is defined in the Linux kernel as follows:

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);//Corresponding close function
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *,
               unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, 
            struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, 
            struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                  loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
        #ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
        #endif
    };

Structure file_ Many structure members are defined in operations. In this driver, we only initialize some member variables. It is very convenient to specify initialization by accessing the members of the structure.

(2) According to file_ The operations structure enables the open and close functions to be prototyped and filled with content

4. Register driver
(1) Selection of main equipment number
View the idle device number through cat /proc/devices

(2) Detection of return value

5. Drive test
(1) Make & & make CP

(2)insmod and check the phenomenon of device registration

(3)rmmod and view the phenomenon of device logout

6. Let the kernel automatically assign the master device number
(1) Why do you want the kernel to allocate automatically
When you use a device number, it may not be occupied here. When you migrate this code to another platform, the device number is occupied, resulting in the program unable to execute and failure.

(2) How?
By passing different parameters to the function of registered device driver, the parameter is 0, and the dynamic automatic allocation of device number can be realized

(3) Testing

5, How does the application invoke the driver

Udev is the device manager of Linux kernel 2.6 series. Its main function is to manage the device nodes under the / dev directory. It is also used to take over the functions of devfs and hot plug, which means that it has to deal with the / dev directory and all user space behaviors when adding / removing hardware, including loading firmware and Linux 2.6 13 kernel. To use the latest version of udev depends on whether the upgraded uevent interface is the latest version. The system version using the new version udev must be higher than 2.6 13, unless you use the noudev parameter to disable udev and use the traditional / dev for device reading.

1. Creation of drive device files
Detailed explanation: http://www.jinbuguo.com/kernel/device_files.html
https://www.jianshu.com/p/579a7b715aab
(1) What is a device file

Device files usually provide a simple interface with standard devices such as printers and serial ports, but can also be used to access specific unique resources on these devices such as disk partitions. In addition, device files are useful for accessing system resources that are not related to any actual devices such as data receivers and random number generators.

(2) The key information of the equipment file is: * * equipment number = primary equipment number + secondary equipment number, * * use ls -l to view the equipment file to get the primary and secondary equipment numbers corresponding to the equipment file.

Devices with the same master device number are similar devices and use the same driver (although the current kernel allows multiple drivers to share a master device number, most devices still follow the principle that one driver corresponds to one master device number). You can view the main device number of the currently loaded device driver through the cat /proc/devices command.

In the / dev directory, in addition to various device nodes, there are usually FIFO pipes, sockets, soft / hard connections and directories. These things are not equipment files, so there is no primary / secondary equipment number.

(3) Create a device file using mknod: mknod /dev/xxx c primary device number secondary device number
xxx is named by itself. The primary device number and secondary device number are assigned by the driver written by itself. The secondary device number is 0 by default. When the device file vi is opened, no information can be seen. It needs to be operated through open, write, read, close, etc

2. Write an application to test the driver
(1)open, write, read, close, etc
(2) Prediction and verification of experimental phenomena

3. Add read / write interface
(1) Add in driver
(2) Add in app
(3) Testing
(4) Data exchange between application and driver
<1>copy_ from_ User, which is used to copy data from user space to kernel space
<2>copy_to_user
Note: replication is distinguished from the mapping of mmap

4. Read write interface practice
When learning the driver, for many functions used, you can search and view the corresponding code of the kernel in sourceinsight.

This is a function I found in a driver file of the kernel:

static inline long copy_from_user(void *to,
		const void __user * from, unsigned long n)
{
	might_sleep();
	if (access_ok(VERIFY_READ, from, n))
		return __copy_from_user(to, from, n);
	else
		return n;
}

static inline long copy_to_user(void __user *to,
		const void *from, unsigned long n)
{
	might_sleep();
	if (access_ok(VERIFY_WRITE, to, n))
		return __copy_to_user(to, from, n);
	else
		return n;
}

6. Complete the write and read functions
(1)copy_ from_ The definition of the return value of the user function is a little different from the general definition. Return value returns 0 if the copy is successful, and returns the number of bytes that have not been successfully copied if the copy is unsuccessful.

For how to include the header files of these two functions, you can refer to the kernel source code and find the header file defining the function from the function file calling them. After knowing the header file name, you can call their file to see how to include the function. Sometimes indirect inclusion is used, so sometimes the previous method is not necessarily available. You need to refer to the file calling the function, Try to find the file that contains the header

7, How to control the hardware in the driver

1. Or the hardware

(1) The physical principle of hardware remains unchanged
(2) The hardware operation interface (register) remains unchanged
(3) The hardware operation code remains unchanged

2. What's different?

(1) Different register addresses. Originally, the physical address was used directly. Now, the virtual address corresponding to the physical address in the kernel virtual address space needs to be used. The physical address of the register is determined by the CPU design and found in the datasheet.

(2) Different programming methods. Bare metal machines are used to directly operate register addresses with pointers, while kernel is used to operate registers with encapsulated io read-write functions to achieve maximum portability.

3. Virtual address mapping method of kernel

(1) Why do I need virtual address mapping
It can improve efficiency, such as LCD display. Mapping the displayed content storage space and the display space (video memory) to the same memory space can improve efficiency

(2) There are two sets of virtual address mapping methods in the kernel: dynamic and static

(3) Characteristics of static mapping method:
When the kernel is transplanted, it is hard coded in the form of code. If you want to change it, you must change the source code and recompile the kernel. Establish a static mapping table when the kernel is started and destroy it when the kernel is shut down. The middle is always valid. For the transplanted kernel, whether you use it or not, it is there

(4) Characteristics of dynamic mapping method:
The driver dynamically establishes, uses and destroys the mapping at any time according to the needs. The mapping is short-term and temporary

4. How to select virtual address mapping method
(1) The two mappings are not exclusive and can be used at the same time

(2) Static mapping is similar to global variables in C language, and dynamic mapping is similar to malloc heap memory in C language

(3) The advantage of static mapping is high execution efficiency, but the disadvantage is that it always occupies the virtual address space (until the kernel is shut down and destroyed); the advantage of dynamic mapping is that it uses the virtual address space on demand, and the disadvantage is that it requires code to establish mapping & destroy mapping before and after each use (you have to learn to use those kernel functions)

Two physical addresses cannot correspond to a virtual address at the same time. Otherwise, mmu does not know the physical address corresponding to this virtual address during mapping, and vice versa.

8, Static mapping operation LED

1. What to say about static mapping

(1) The location and file name of the static mapping table may be different in different versions of the kernel

(2) Static mapping table locations and file names may be different for different SOCS

(3) The so-called mapping table is actually the macro definition in the header file

2. Static mapping table in Samsung version kernel

(1) The main mapping table is located at: arch / arm / plat-s5p / include / plat / map-s5p h

When CPU arranges register addresses, they are not randomly distributed in random order, but distinguished according to modules. The addresses of many registers inside each module are continuous. Therefore, when defining the register address, the kernel finds the base address first, and then uses the base address + offset to find a specific register.

map-s5p.h is the register base address of several modules to be used.
map-s5p.h is the virtual address of the register base address of the module.

(2) The virtual address base address is defined in: arch / arm / plat sampling / include / plat / map base h

#define S3C_ADDR_BASE	(0xFD000000)	
// The base address of the static mapping table determined during Samsung migration. All virtual addresses in the table are based on this address + offset
//To specify

(3) The main mapping table related to GPIO is located at: arch / arm / mach-s5pv210 / include / Mach / regs GPIO h,
The table is the definition of the base address of each port of GPIO

(4) The specific register definitions of GPIO are located in: arch / arm / mach-s5pv210 / include / Mach / GPIO bank h

3. Refer to the operation method in bare metal machine and add LED operation code

/*
*Lighting with C language
*Author: Mr.Zhang
*/
#define rGPJ0CON   *((unsigned int *)0xE0200240)
#define rGPJ0DAT   *((unsigned int *)0xE0200244)

void delay(void);

void led_blink(void )
{
	while(1)
	{
		rGPJ0CON = 0x11111111;//Set GPJ0CON to output mode
		
		rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5));//Output low level, let three lights on
		delay();//delayed
		rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5));
		//Output high level and let the three lights go out
		delay();//delayed
		
	}
}

void delay(void)
{
	volatile unsigned int i =10000;
	while(i--);
	
}

(1) Macro definition
(2) In the init and exit functions, turn on and off the LED respectively

4. Practical test
(1) Observe the change of LED on and off during insmod and rmmod
(2) Print out the value of the register and compare it with the analysis in the static mapping table

5. Move the code to the open and close functions
Remember to create a device file

6. Add write function in driver
(1) First define the control interface between the application and the driver, which is defined by yourself. For example, the definition is: if the application writes "on" to the driver, the driver will make the LED light, and if the application writes "off" to the driver, the driver will make the led off

(2) The interface definition of application and driver shall be as simple as possible, for example, expressed in one word. For example, the definition is: the application writes "1" to indicate that the light is on, and writes "0" to indicate that the light is off.

7. Write application to test write function
The memset function used in the driver is different from the application program, and the header file contained is also different. The application layer calls the library function, and the library function is provided by the kernel. The library function cannot be used in the kernel. You can find the header file that declares and implements the memset function in the kernel and include the file. You can find out how the file using the function in the kernel contains the header file by looking up the function name
Learn how to include the header file by referring to the kernel source code

8. Add read function to driver and application
Drivers are generally only used to operate the hardware, and the application code related to user requirements should be placed in the application.

The above codes are as follows:

#include <linux/module.h>    //module_init module_exit
#include <linux/init.h>      //__init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>		// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>


#define MYMAJOR    200
#define MYNAME     "testchar"

#define GPJ0CON 		 S5PV210_GPJ0CON / / macro definitions related to register addresses in the kernel
#define GPJ0DAT		S5PV210_GPJ0DAT

#define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)

int mymajor = 0;


char kbuf[100] = {0};//Kernel space buf

ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int ret = -1; 

	printk(KERN_INFO "this is test_chrdev_read.\n");
	
	ret = copy_to_user(ubuf, kbuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail.\n");
		
		return -EINVAL; 
	}
	else
	{
		printk(KERN_INFO "copy_to_user successfully.\n");
		
	}
	
	return 0;
}

//The essence of writing a function is to copy the data passed from the application layer to the kernel first, and then write it in the correct format
//Mode write to hardware to complete the operation

ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "this is test_chrdev_write.\n");
	
	//Use this function to copy the contents of the ubuf transmitted from the application layer to a buf in the driver space
	//memcpy(kbuf, ubuf);  // Error, these two are not in the same address space, one belongs to the kernel and the other belongs to the application
	memset(kbuf, 0, sizeof(kbuf));
	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail.\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_INFO "copy_from_user successfully.\n");
		printk(KERN_INFO "GPJ0CON = %p.\n", GPJ0CON);
		printk(KERN_INFO "GPJ0DAT = %p.\n", GPJ0DAT);
	}
	
	//In the real driver, after the data is copied from the application layer to the driver, we need to use this data
	//To operate the hardware, so the following should be the code to operate the hardware
	//Mode 1:
	if (kbuf[0] == '0')//Lights out
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	else if (kbuf[0] == '1')//Light on
	{	
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	
	//Mode 2:
	
	if (!strcmp(kbuf, "on"))
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		
	}
	else if (!strcmp(kbuf, "off"))
	{
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		
	}	
	
	return 0;
}


int test_chrdev_open(struct inode *inode, struct file *file)
{
	//This function opens the hardware operation code of the device
	printk(KERN_INFO "this is test_chrdev_open.\n");
	rGPJ0CON = 0x11111111;
	rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// bright

	return 0;
}


int test_chrdev_release(struct inode *node, struct file *file)//Corresponding close function
{
	printk(KERN_INFO "this is test_chrdev_release.\n");

	rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));		// Extinguish
	
	return 0; 	
}
//Define a file_operations struct variable, and de populate
//File can be searched in kernel source code_ Operations copy one for modification
/* File operations struct for character device */
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,			// Convention, just write it directly
	
	.open		= test_chrdev_open,		//When the application opens this device in the future, it will actually call this open corresponding function
	.write 		= test_chrdev_write,
	.release	= test_chrdev_release,	//Corresponding close function 
	.read		= test_chrdev_read,
	
};



//Module installation function
static int __init chrdev_init(void)
{
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	//In module_ Register the character device driver in the function called by init macro
	//The 0 passed in by major means that the kernel will automatically assign us an appropriate unused master device number
	//The kernel will return the allocated device number if the allocation is successful; if the allocation fails, it will return a negative number
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail.\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_ERR "register_chrdev successfully.\n");
		printk(KERN_ERR "mymajor = %d.\n", mymajor);
		
	}

	return 0;
}

//Module unload function
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit hellowworld exit\n");
	
	//In module_ Unregister the character device driver in the function called by the exit macro
	unregister_chrdev(mymajor, MYNAME);//Release device number
	
}

//These two functions are called respectively when loading and unloading drivers, that is, when calling insmod and rmmod commands,
//However, insmod and rmmod do not recognize these two functions. They can only recognize init_module and cleanup_module,
//Actually init_module and cleanup_module is equivalent to module_init and module_ Alias for exit

module_init(chrdev_init);//Macro definition: the macro expands different contents according to whether the MODULE macro is defined or not
module_exit(chrdev_exit);//Macro definition: the macro expands different contents according to whether the MODULE macro is defined or not
                        
//MODULE_XXX macro is used to add module description information
MODULE_LICENSE("GPL");				// Describe the license for the module
MODULE_AUTHOR("aston");				// Describe the author of the module
MODULE_DESCRIPTION("module test");	// Describe the introduction of the module
MODULE_ALIAS("alias xxx");			// Describes the alias information for the module

9, Dynamic mapping operation LED

1. How to establish dynamic mapping

(1)request_mem_region, which requests (reports) the memory resources to be mapped from the kernel. Avoid confusion, such as multiple drivers accessing a register at the same time.

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

For request_region, the three parameters start, N and name indicate that you want to use the I/O port resource with size n starting from start, and name is naturally your name

(2)ioremap, which is really used to realize mapping, passes it to its physical address, and it returns you a virtual address for mapping

Online materials:

#include <linux/io.h>
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size);
Parameters:
offset:The starting address of the physical memory area to map.
size:Physics·Range of addresses.

Kernel found:

#define ioremap(cookie,size)		__arm_ioremap(cookie, size, MT_DEVICE)

void __iomem *__arm_ioremap(unsigned long phys_addr, size_t size,
			    unsigned int mtype)
{
	return (void __iomem *)phys_addr;
}

These two functions can check the kernel source code to find out the principle and process of implementation.

2. How to destroy dynamic mapping

(1)iounmap unmap

#include <linux/io.h>
#define iounmap(cookie)   __iounmap(cookie)
Parameters:
cookie: Pointer to the virtual address.

Function prototype:
void __iounmap(volatile void __ iomem *io_addr);
Parameters:
io_addr: Pointer to the virtual address.

(2)release_mem_region release application
Note: when creating a mapping, you must apply first and then map; Then use; When unmapping is finished, unmap first and then release the application. Avoid that there are other programs to apply for dynamic mapping before unmapping

Header files are included indirectly or directly. If you don't know how to include header files, you can refer to the kernel source code.

3. Mapping mode

(1) Two registers are mapped separately
(2) Two registers are mapped together
The register next to the address can be realized in the following way.
p,(p+1)

4. Code implementation

#include <linux/module.h>    //module_init module_exit
#include <linux/init.h>      //__init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>		// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#include <linux/io.h>
#include <linux/ioport.h>

#define MYMAJOR    200
#define MYNAME     "testchar"

#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244

int mymajor = 0;

unsigned int *rGPJ0CON;
unsigned int *rGPJ0DAT;

char kbuf[100] = {0};//Kernel space buf

ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int ret = -1; 

	printk(KERN_INFO "this is test_chrdev_read.\n");
	
	ret = copy_to_user(ubuf, kbuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_to_user fail.\n");
		
		return -EINVAL; 
	}
	else
	{
		printk(KERN_INFO "copy_to_user successfully.\n");
		
	}
	
	return 0;
}

//The essence of writing a function is to copy the data passed from the application layer to the kernel first, and then write it in the correct format
//Mode write to hardware to complete the operation

ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
	int ret = -1;
	
	printk(KERN_INFO "this is test_chrdev_write.\n");
	
	//Use this function to copy the contents of the ubuf transmitted from the application layer to a buf in the driver space
	//memcpy(kbuf, ubuf);  // Error, these two are not in the same address space, one belongs to the kernel and the other belongs to the application
	memset(kbuf, 0, sizeof(kbuf));
	ret = copy_from_user(kbuf, ubuf, count);
	if (ret)
	{
		printk(KERN_ERR "copy_from_user fail.\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_INFO "copy_from_user successfully.\n");

	}
	
	//In the real driver, after the data is copied from the application layer to the driver, we need to use this data
	//To operate the hardware, so the following should be the code to operate the hardware
	//Mode 1:
	if (kbuf[0] == '0')//Lights out
	{
		*rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
	}
	else if (kbuf[0] == '1')//Light on
	{	
		*rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
	}
	
	//Mode 2:
	
	if (!strcmp(kbuf, "on"))
	{
		*rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		
	}
	else if (!strcmp(kbuf, "off"))
	{
		*rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		
	}	
	
	return 0;
}


int test_chrdev_open(struct inode *inode, struct file *file)
{
	//This function opens the hardware operation code of the device
	printk(KERN_INFO "this is test_chrdev_open.\n");
	*rGPJ0CON = 0x11111111;
	*rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// bright

	return 0;
}


int test_chrdev_release(struct inode *node, struct file *file)//Corresponding close function
{
	printk(KERN_INFO "this is test_chrdev_release.\n");

	*rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));		// Extinguish
	
	return 0; 	
}


//Define a file_operations struct variable, and de populate
//File can be searched in kernel source code_ Operations copy one for modification
/* File operations struct for character device */
static const struct file_operations test_fops = {
	.owner		= THIS_MODULE,			// Convention, just write it directly
	
	.open		= test_chrdev_open,		//When the application opens this device in the future, it will actually call this open corresponding function
	.write 		= test_chrdev_write,
	.release	= test_chrdev_release,	//Corresponding close function 
	.read		= test_chrdev_read,
	
};



//Module installation function
static int __init chrdev_init(void)
{
	printk(KERN_INFO "chrdev_init helloworld init\n");
	
	//In module_ Register the character device driver in the function called by init macro
	//The 0 passed in by major means that the kernel will automatically assign us an appropriate unused master device number
	//The kernel will return the allocated device number if the allocation is successful; if the allocation fails, it will return a negative number
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
		printk(KERN_ERR "register_chrdev fail.\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_ERR "register_chrdev successfully.\n");
		printk(KERN_ERR "mymajor = %d.\n", mymajor);
		
	}
	
	//Use dynamic mapping to manipulate registers
#if 0
	//Mode 1:
	
	//apply
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		return -EINVAL;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
		return -EINVAL;
	//Establish connection and truly realize mapping	
		rGPJ0CON = ioremap(GPJ0CON_PA, 4);
		rGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
#endif

#if 1
	//Mode 2:
	//apply
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		return -EINVAL;
	//Establish connection and truly realize mapping	
		rGPJ0CON = ioremap(GPJ0CON_PA, 4);
		rGPJ0DAT = rGPJ0CON + 1;	
#endif	
	
	return 0;
}

//Module unload function
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit hellowworld exit\n");
	
	//Unmap
	iounmap(rGPJ0CON);
	iounmap(rGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	
	
	//In module_ Unregister the character device driver in the function called by the exit macro
	unregister_chrdev(mymajor, MYNAME);

	
}

//These two functions are called respectively when loading and unloading drivers, that is, when calling insmod and rmmod commands,
//However, insmod and rmmod do not recognize these two functions. They can only recognize init_module and cleanup_module,
//Actually init_module and cleanup_module is equivalent to module_init and module_ Alias for exit

module_init(chrdev_init);//Macro definition: the macro expands different contents according to whether the MODULE macro is defined or not
module_exit(chrdev_exit);//Macro definition: the macro expands different contents according to whether the MODULE macro is defined or not
                        

//MODULE_XXX macro is used to add module description information
MODULE_LICENSE("GPL");				// Describe the license for the module
MODULE_AUTHOR("aston");				// Describe the author of the module
MODULE_DESCRIPTION("module test");	// Describe the introduction of the module
MODULE_ALIAS("alias xxx");			// Describes the alias information for the module

If you need the complete project documents described in this article, please send them to me personally or leave an email in the comment area.

Note: most of this information is compiled from the course notes of the Internet of things lecture hall of Mr. Zhu. In case of infringement, please contact to delete it! The level is limited. If there are errors, you are welcome to communicate in the comment area.

Keywords: Linux IoT Linux Driver ARM

Added by soul on Mon, 20 Dec 2021 08:49:27 +0200