[IMX6ULL driver development and learning] 05.IMX6ULL driver development_write the first hello driver [stay up late liver]

After the following four steps, the driver development can finally start

01. Install the cross-compilation environment [download address attached]
02. IMX6ULL programming Linux system
03. Set the IMX6ULL development board and the virtual machine on the same network segment
04. IMX6ULL development board and the virtual machine exchange files

Table of contents

1. Get the kernel, compile the kernel
2. Create a vscode workspace, add the kernel directory and personal directory
3. Understand the driver program writing process
4. The first driver - hello driver
5. IMX6ULL verify the hello driver

1. Obtain the kernel and compile the kernel

1. Obtain the kernel file

To obtain the Linux kernel file, you can download it from the Linux Kernel official website. In order to be consistent with the system on the development board and avoid other problems, the Linux-4.9.88 kernel file provided by Mr. Wei Dongshan needs to be obtained by yourself.

Link: https://pan.baidu.com/s/111M2FsgJXAPsQ3ppeVwbFQ
Extraction code: p7wp

2. Compile the kernel file
Why compile the kernel file, because the compilation of the driver code is based on the compiled kernel file.
Before compiling, add two lines to the ~/.bashrc file to specify the compiled platform and tool chain

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

Steps to compile the kernel: (If you report an error in the middle, just go to Baidu yourself. There are many solutions on the Internet, so I won’t list them one by one here)

  1. First delete the files and configuration files generated by the previous compilation, backup files, etc.
make mrproper

Success phenomenon: no content output

  1. Set the relevant configuration of the kernel
make 100ask_imx6ull_defconfig

Success phenomenon:

HOSTCC scripts/basic/fixdepHOSTCC scripts/kconfig/conf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCCscripts /kconfig/zconf.tab.o
HOSTLDscripts/kconfig/conf
#
#configuration written to .config
#
  1. Generate an image file (-j4 will be faster, which means that 4 cores are used to compile together, and how many cores are used depends on the specific situation of your virtual machine)
make zImage -j4

Success phenomenon: zImage file is generated in the kernel file /arch/arm/boot/ directory , and no error is reported

  1. Generate the device tree (do this step too, very quickly, although we don't need it, just follow the steps)
make dtbs

Success phenomenon: output a few lines of content, no error

  1. Create a new nfs_rootfs directory in the home directory, and copy the image file and the generated device tree to its directory (optional)
cp arch/arm/boot/zImage ~/nfs_rootfs
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs
  1. Compile the kernel module
make modules

Success phenomenon: output a lot of .o files, finally output some .ko files, no error


2. Configure vscode, add kernel directory and personal directory

  1. Use vscode to open the linux-4.9.88 kernel directory, click "File" in the upper left corner, "Save Workspace As", select the home directory, and name it casually
  2. Use vscode to open the file directory where you want to write the driver, right-click your own folder, select "Add Folder to Workspace", and select the workspace you created
  3. At this point, you can see your own driver files and linux kernel files in the same workspace of vscode, which is convenient for subsequent independent photos of the contents of the kernel files

insert image description here

  1. Set the working path in the c_cpp_properties.json file under .vscode
"/home/me/Linux-4.9.88/tools/virtio",
"/home/me/Linux-4.9.88/include/**",
"/home/me/Linux-4.9.88/include/linux/**",
"/home/me/Linux-4.9.88/arch/arm/include/**",
"/home/me/Linux-4.9.88/arch/arm/include/generated/**"

3. Understand the driver writing process

1. First look at how the original driver is written in the kernel directory
Open the Linux-4.9.88/drivers/char directory (you can guess the character driver code in this directory by looking at the name )
and find a ds1602.c , open it See how it is written (because I have learned it, select this file and show the main code, which is helpful for getting started)

#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>

#include <mach/hardware.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>

static DEFINE_MUTEX(ds1620_mutex);
static const char *fan_state[] = {
    
     "off", "on", "on (hardwired)" };

.....
..... 省略
.....

static int __init ds1620_init(void)
{
    
    
	int ret;
	struct therm th, th_start;

	if (!machine_is_netwinder())
		return -ENODEV;

	ds1620_out(THERM_RESET, 0, 0);
.....
..... 省略
.....

static int ds1620_open(struct inode *inode, struct file *file)
{
    
    
	return nonseekable_open(inode, file);
}

.....
..... 省略
.....

static ssize_t ds1620_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
    
    
	signed int cur_temp;
	signed char cur_temp_degF;

	cur_temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;

	/* convert to Fahrenheit, as per wdt.c */
	cur_temp_degF = (cur_temp * 9) / 5 + 32;

	if (copy_to_user(buf, &cur_temp_degF, 1))
		return -EFAULT;

	return 1;
}


.....
..... 省略
.....

static const struct file_operations ds1620_fops = {
    
    
	.owner		= THIS_MODULE,
	.open		= ds1620_open,
	.read		= ds1620_read,
	.unlocked_ioctl	= ds1620_unlocked_ioctl,
	.llseek		= no_llseek,
};

.....
..... 省略
.....

static void __exit ds1620_exit(void)
{
    
    
#ifdef THERM_USE_PROC
	remove_proc_entry("therm", NULL);
#endif
	misc_deregister(&ds1620_miscdev);
}

module_init(ds1620_init);
module_exit(ds1620_exit);

MODULE_LICENSE("GPL");

2. The main components of the driver (mainly the first four)
· file_operations structure: a structure that provides the driver entry for system calls
· module_init: defines the entry function of the driver module
· module_exit: defines the exit function of the driver module
· MODULE_LICENSE( "GPL"): Declare the module license, indicating that this is any version of the GNU General Public License,
                                                     otherwise when loading this module, you will receive a warning that the kernel is polluted "kernel tainted"

ds1620_init: ds1602 initialization function
ds1620_open: open ds1602 device function
ds1620_read: read ds1602 device function
ds1620_exit: ds1602 exit driver function

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

3. Driver implementation process and calling principle
Module_init specifies the driver entry function
The device object is defined through the struct file_operations structure Call the register_chrdev
function in the entry function , pass in the registered and defined driver device variable, and generate the device number On the development board Load the compiled driver module (.ko file, which needs to be transferred from the virtual machine to the board) into the kernel through the insmod command . The device can be read and written through the application program, and the read and write operations can be called through the system register_chrdev structure When the device is closed , the system calls the driver module specified by module_exit to exit the function


The above is the driver implementation process and the principle of the application calling the driver (written according to personal understanding, it is probably such a process, if there is any inappropriate place, please point out)

Fourth, the first driver - hello driver

1. Create a new hello_drv.c file in the personal directory
2. Copy
the header files** used by the above ds1602 according to
the gourd painting 2. Continue to write the file_operations structure ** according to the gourd painting

static const struct file_operations hello_drv = {
    
    
    .owner 		= THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
	.release    = hello_release,
};

Pretend that our driver can also read and write. Of course, there must be open and release (that is, close).
Of course, .owner = THIS_MODULE must also be there. For the reason, see the blog https://blog.csdn.net/a954423389/article/details/ 6101369

4. Study the file_operations structure.
Each function has a corresponding template, which is not scribbled, because these function groups will be called by the system, and the parameters are fixed. You can hold down the ctrl key, click file_operations with the mouse , and jump Go to the definition of the structure, you can see the form of each function pointer
insert image description here

5. Realize hello_read, hello_write, hello_open, hello_release functions according to the gourd painting

/*养成好习惯,驱动程序都加static修饰*/
static int hello_open (struct inode *node, struct file *filp)
{
    
    
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
    
    
	printk("hello_read\n");
	return size;  //返回读取字节数
}

static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
    
    
	printk("hello_write\n");
	return size;  //返回写入字节数
}

static int hello_release (struct inode *node, struct file *filp)
{
    
    
	printk("hello_release\n");
	return 0;
}

6. Write the entry function and exit function of the hello driver. The entry
function needs to use the register_chrdev function
. The exit function (exit function) needs to use the unregister_chrdev function, but there are no these two functions in ds1602.c. It doesn’t matter
. We search for register_chrdev in vscode. Click one to have a look, and study the usage (I really don’t understand Baidu, haha) You
can also use the linux command to search (search in the drivers/char directory of the kernel)

grep "register_chrdev" * -nwr

insert image description here
Let's find it with vscode

insert image description here
It is found that the register_chrdev function requires three parameters.
The first parameter is the main device number , and 0 represents dynamic allocation. The
second parameter is the name of the device (custom).
The third parameter is a pointer to the structure type of struct file_operations, which represents the operation of applying for the device. function

Similarly, the first parameter of the export function unregister_chrdev is the device number and the second parameter is the device name

Follow the gourd drawing and start writing

/*入口函数*/
static int major;
static int hello_init(void)
{
    
    
	/*返回设备号,定义设备名称为hello_drv*/
	major = register_chrdev(0,"hello_drv",&hello_drv);
	return 0;
}

/*退出函数*/
static int hello_exit(void)
{
    
    
	unregister_chrdev(major,"hello_drv");
	return 0;
}	

7. module_init, module_exit, and declaring licenses

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

The entire hello_drv.c code

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>

static int major;

static int hello_open (struct inode *node, struct file *filp)
{
    
    
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
    
    
	printk("hello_read\n");
	return size;  //返回读取字节数
}

static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
    
    
	printk("hello_write\n");
	return size;  //返回写入字节数
}

static int hello_release (struct inode *node, struct file *filp)
{
    
    
	printk("hello_release\n");
	return 0;
}

/*1.定义 file_operations 结构体*/
static const struct file_operations hello_drv = {
    
    
    .owner 		= THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
	.release    = hello_release,
};


/*2.register_chrdev*/

/*3.入口函数*/
static int hello_init(void)
{
    
    
	//设备号
	major = register_chrdev(0,"hello_drv",&hello_drv);
	return 0;
}

/*4.退出函数*/
static int hello_exit(void)
{
    
    
	//卸载驱动
	unregister_chrdev(major,"hello_drv");
	return 0;
}	

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

8. Write Makefile

Not much to say, directly on the code

KERN_DIR = /home/me/Linux-4.9.88

PWD ?= $(shell KERN_DIR)

all:	
	make -C $(KERN_DIR) M=$(PWD) modules
	#$(CROSS_COMPILE)gcc -o hello_test hello_test.c
clean:
	make -C $(KERN_DIR) M=$(PWD) modules clean
	rm -rf modules.order
	rm -f hello_drv

obj-m += hello_drv.o

KERN_DIR = /home/me/Linux-4.9.88 : The dependency directory of the compiled program

9. Make it to generate the .ko file

me@ubuntu:~/Linux_ARM/IMX6ULL/hello_driver$ ls
hello_drv.c  hello_drv.ko  hello_drv.mod.c  hello_drv.mod.o  hello_drv.o  
hello_test  hello_test.c  Makefile  modules.order  Module.symvers

The main is the .ko file, which is the driver module file loaded into the kernel


Five, IMX6ULL verification hello driver

1. Copy the .ko file compiled by the virtual machine to the IMX6ULL development board.
This step requires the virtual machine and the development board to be on the same network segment. You can refer to the following two blogs.
03. Set the IMX6ULL development board and the virtual machine on the same network Section
04. The IMX6ULL development board and the virtual machine transfer files to each other.
I use the method of NFS mounting

mount -t nfs -o nolock,vers=3 192.168.1.200:/home/me/Linux_ARM/IMX6ULL/hello_driver /mnt

insert image description here
If an error occurs

failed: Device or resource busy

implement

unmount /mnt

2. Load the kernel
Execute the command to load the kernel

insmod hello_drv.ko
[root@100ask:/mnt]# insmod hello_drv.ko 
[   80.794911] hello_drv: loading out-of-tree module taints kernel.
[root@100ask:/mnt]#

Show modules loaded into the system

lsmod

insert image description here
View the device number of the hello_drv.ko driver module

cat /proc/devices
226 drm
240 hello_drv
241 adxl345
242 spidevx
243 irda
244 dht11

You can see that the device number of the hello driver is 240

3. Generate device nodes

mknod /dev/hello c 240 0

/dev/hello: The name of the generated device node
c: The description is a character device
240: The main device number
0: The sub-device number (the sub-device number is not specified, it is 0)

At this time, you can also use the following command to view the hello device

[root@100ask:/mnt]# ls /dev/hello -l
crw-r--r-- 1 root root 240, 0 Jan  1 11:34 /dev/hello

4. Write application verification driver

PS: In Section 4, Section 8, comment out the line $(CROSS_COMPILE)gcc -o hello_test hello_test.c

Write the hello_test.c application

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    
    
    int len;
    char read_buf[10];

    if(argc < 2){
    
    
        printf("please input  at least 2 args\n");
        printf("%s <dev> [string]\n", argv[0]);
        return -1;
    }

    /*open*/
    int fd;
    fd = open(argv[1], O_RDWR);
    if(fd < 0){
    
    
        printf("open failed\n");
        return -2;
    }

    /*read*/
    if(argc == 2){
    
    
        read(fd, read_buf, 10);    //调用read函数,只为了触发系统调用hello驱动的read函数
        printf("read operation \n");
    }

    /*write*/
    if(argc == 3){
    
    
        len = write(fd, argv[2], strlen(argv[2]));   //调用write函数,只为了触发系统调用hello驱动的write函数
        printf("write length = %d \n", len);
    }

    close(fd);

    return 0;
}

How to use the program:

./hello_test   /dev/hello  123abc     两个参数:模拟写操作
./hello_test   /dev/hello             一个参数:模拟读操作

/dev/hello is the name of the device we want to open, in the application, use the open function to open, use the close function to close

If the opening is successful, the system will call the open function driven by hello, and we will see the corresponding printing information (print out the current file name, function name, and line number)

static int hello_open (struct inode *node, struct file *filp)
{
    
    
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

Note that the printk function is used in the driver. This function is called by the kernel. If you want to see the output in the serial port printing information of the development board, you need to execute the following command

echo "7 4 1 7" > /proc/sys/kernel/printk

Test driven write operation, success! ! !

[root@100ask:/mnt]# ./hello_test /dev/hello 
[  499.512588] hello_open
[  499.516872] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[  499.525082] hello_read
read operation [  499.528427] hello_release

[root@100ask:/mnt]# 

Test-driven read operation, success! ! !

[root@100ask:/mnt]# ./hello_test /dev/hello abc123
[  500.725340] hello_open
[  500.727762] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[  500.736217] hello_write
write length = 6 [  500.739735] hello_release

[root@100ask:/mnt]# 

insert image description here

Guess you like

Origin blog.csdn.net/HuangChen666/article/details/131115022