<Linux Development>--Driver Development--Character Device Driver (1) Detailed process record

<Linux development> - driver development - character device driver (1) process detailed record

The author explained and recorded some of the contents of the system migration, including uboot, Linux and device tree, and the root file system. Next, the development process of the device driver part will be recorded.

The system migration section can refer to the following links:

For uboot migration, please refer to the following:
<Linux Development> -The -Detailed Record of the uboot Migration Process of System Migration (Part 1)
<Linux Development> -The -Detailed Record of the uboot Migration Process of System Migration (Part 2)
<Linux Development> -The- System Migration uboot Migration Process Detailed Record (Part 3) (Uboot Migration Completed)

For Linux kernel and device tree transplantation, please refer to the following:
<Linux Development> System Migration - Part 1 - Detailed Record of Linux Kernel Migration Process (Part 1)
<Linux Development> System Migration - Part 2 - Detailed Record of Linux Kernel Migration Process (End of Part 2)

For the porting of the file system, please refer to the following:
<Linux Development> System porting-of-linux to build the BusyBox root file system and the detailed record of the porting process

Next, I will officially enter the driver development chapter; the author will not explain much about the driver's explanation here. After all, there are a lot of conceptual issues that can be searched on the Internet, and readers can refer to them by themselves. The author's Linux development These chapters mainly talk about the specific development process. So won't explain too many conceptual issues.
There are generally three types of drivers in Linux, including character device drivers, block device drivers, and network device drivers; generally, character device drivers are used more, block device drivers are mainly for storage devices, and network device drivers are obviously for the network. equipment. Then start with the character device driver first, focusing on learning the character device driver development framework under Linux. This chapter will take a virtual device as an example to explain how to develop a character device driver, and how to write a test APP to test whether the driver works normally, so as to lay a solid foundation for future learning.

The experimental process is recorded as follows:

1. Preparation of programming environment
1. Install virtual machine ubuntu and cross tool chain, which is also mentioned in the explanation of system porting and is necessary;
2. Kernel source code, which is also the kernel source code used in system porting, when compiling the driver The kernel source code used should be stored in the same version as the kernel source code running on the development board;
3. The programming software VScode;
4. Install the cross tool chain;

2. Specific programming process
1. Preparation for vscode project creation
(1) Create a directory for storing source code projects, such as the folder created by the author in the figure below;
insert image description here
(2) Use vscode to create a project in the 1-chrdevbase folder and create a new chrdevbase.c
Add the header file path to the chrdevbaseApp.c file (3)
Because the Linux driver is written, the functions in the Linux source code will be used. We need to add the header file path in the Linux source in VSCode.
insert image description here
Open VSCode, press "Crtl+Shift+P" to open the VSCode console, then enter "C/C++: Edit configurations(JSON)" to open the C/C++ editing configuration file, as shown in the figure below: A file named c_cpp_properties.json is generated in the .vscode directory. The modified content of this file is as follows:
insert image description here
Lines 7 to 9 are the paths of the added Linux header files. They are the paths of the include, arch/arm/include and arch/arm/include/generated directories under the Linux source code used by the development board. Note that absolute paths are used here. Mainly, the content in the green box is added, which is the path of the kernel source code, and the red box is the storage directory of the source code (fill in according to the actual storage location of the reader), and the following content is the same.

(4) Modify the top-level Makefile of the Linux kernel source code (the author also stepped on this pit for development), remember, unless the system has been modified during transplantation. As shown in the figure below:
insert image description here
Open the top-level directory of the kernel source code with vscode, then find the Makefile, and find the two variables "ARCH" and "CROSS_COMPILE" in it. After changing, they become "ARCH ?= arm" and "CROSS_COMPILE ?= arm" -linux-gnueabihf-", note that there must be no spaces at the end of the line, otherwise the compilation will fail. The first is the compiled object and the second is the compiled toolchain prefix.

2. Write the character driver source code in chrdevbase.c, the content is as follows:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

/*************************************************************** 
 * Copyright © onefu Co., Ltd. 2019-2021. All rights reserved. 
 * 文件名 : chrdevbase.c 
 * 作者 : water 
 * 版本 : V1.0 
 * 描述 : chrdevbase驱动文件。 
 * 其他 : 无  
 * 日志 : 初版V1.0 2021/10/24 water创建 
 * ***************************************************************/ 

#define CHRDEVBASE_MAJOR    200                 /*主设备号*/
#define CHRDEVBASE_NAME     "chrdevbase"        /*设备名*/

static char readbuf[100];                       /*读缓冲区*/
static char writebuf[100];                      /*写缓冲区*/
static char kerneldata[] = {
    
    "kernel data!"};    /*写入缓冲区的数据*/

/*  
* @description : 打开设备 
* @param – inode : 传递给驱动的inode 
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 
* 一般在open的时候将private_data指向设备结构体。 
* @return : 0 成功;其他 失败 
*/
static int chrdevbase_open(struct inode *inode, struct file *filep)
{
    
    
    printk("chrdevbase open!\r\n");  /*终端输出提示*/
    return 0;
}

/* 
 *@description : 从设备读取数据 
 * @param - filp : 要打开的设备文件(文件描述符) 
 * @param - buf : 返回给用户空间的数据缓冲区 
 * @param - cnt : 要读取的数据长度 
 * @param - offt : 相对于文件首地址的偏移 
 * @return : 读取的字节数,如果为负值,表示读取失败 
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    
    
    int retvalue = 0;
    /*向用户空间发送数据*/
    memcpy(readbuf, kerneldata, sizeof(kerneldata));    /*将kerneldata 内的数据拷贝到 readbuf*/
    retvalue = copy_to_user(buf, readbuf, cnt);          /*将内核内的数据 拷贝 到用户端的buf中*/
    if(retvalue == 0){
    
                              
        printk("kernel senddata ok!\r\n");              /*终端输出提示*/
    }else{
    
    
        printk("kernel senddata failed!\r\n");          /*终端输出提示*/
    }   
    return 0;
}

/* 
 @description : 向设备写数据 
 @param - filp : 设备文件,表示打开的文件描述符 
 @param - buf : 要写给设备写入的数据 
 @param - cnt : 要写入的数据长度 
 @param - offt : 相对于文件首地址的偏移 
 @return : 写入的字节数,如果为负值,表示写入失败 
 */ 
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    
    
    int retvalue = 0;
    /*接收用户空间传递给内核的数据并打印出来*/
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0){
    
    
        printk("kernel recevdata :%s\r\n",writebuf);    /*终端输出提示*/
    }else{
    
    
        printk("kernel recevdata failed!\r\n");
    }
    return 0;
}

/* 
 *@description : 关闭/释放设备 
 *@param - filp : 要关闭的设备文件(文件描述符) 
 *@return : 0 成功;其他 失败 
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    
    
    printk("chrdevbase release ! \r\n");
    return 0;
}

/* 
 *设备操作函数结构体 
 */ 
static struct file_operations chrdevbase_fops = {
    
    
    .owner = THIS_MODULE,
    .open  = chrdevbase_open,
    .read  = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/* 
*@description : 驱动入口函数 
*@param : 无 
*@return : 0 成功;其他 失败 
*/
static int __init chedevbase_init(void)
{
    
    
    int retvalue = 0;
    /*注册字符设备驱动*/
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(retvalue < 0){
    
    
        printk("chedevbase driver register failed !\r\n");
    }
    printk("chedevbase_init()");
    return 0;
}

/*
* @description : 驱动出口函数 
* @param : 无 
* @return : 无 
*/
static void __exit chrdevbase_exit(void)
{
    
    
    /*注销字符设备驱动*/
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chedevbase_exit()\r\n");

}

/*将上面两个函数指定为驱动入口 和 出口 函数*/
module_init(chedevbase_init);
module_exit(chrdevbase_exit);

/*LICENSE 和 作者信息  模块描述信息 设备支持信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("water");
MODULE_DESCRIPTION ("OnFu This is a test");
MODULE_SUPPORTED_DEVICE ("OneFu Device");

3. Write the test software code in the chrdevbaseApp.c file. The code content is as follows:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*************************************************************** 
 * Copyright © onefu Co., Ltd. 2019-2021. All rights reserved. 
 * 文件名 : chrdevbaseApp.c 
 * 作者 : water 
 * 版本 : V1.0 
 * 描述 : chrdevbase 驱测试APP。 
 * 其他 : 使用方法:./chrdevbaseApp /dev/chrdevbase <1>|<2>
 *                argv[2] 1:读文件
 *                argv[2] 2:写文件
 * 日志 : 初版V1.0 2021/10/24 water创建 
 * ***************************************************************/ 

static char usrdata[] = {
    
    "usr data!"}; 

/* 
* @description : main主程序 
* @param - argc : argv数组元素个数 
* @param - argv : 具体参数 
* @return : 0 成功;其他 失败 
*/ 
int main(int argc, char *argv[])
{
    
    
    int fd, retvalue;               //fd: 文件描述符 用以对文件操作    retvalue:存放函数操作后的返回值
    char *filename;                 //filename:文件名,有主函数参数传入赋值
    char readbuf[100],writebuf[100];//定义的buf,用来读写数据用 

    if(argc != 3){
    
                      //判断主函数传入的函数的参数的个数
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];              //获取第1个参数,存放的是文件的路径(即要操作的设备文件路径)
    
    fd = open(filename,O_RDWR);                         /*打开驱动文件*/
    if(fd < 0){
    
    
        printf("Can't open file %s\r\n",filename);      /*打开失败,输出提示*/
        return -1;
    }

    if(atoi(argv[2]) == 1){
    
    
        retvalue = read(fd, readbuf, 50);                   /* 从驱动文件读取数据 */
        if(retvalue < 0){
    
    
            printf("read file %s failed! \r\n",filename);   /* 从驱动文件读取数据失败,提示 */
        }else{
    
    
            printf("read data:%s \r\n",readbuf);            /* 从驱动文件读取数据 成功,输出读到的数据*/
        }
    }

    if(atoi(argv[2]) == 2){
    
    
        
        memcpy(writebuf, usrdata, sizeof(usrdata));         /*将用户传如的数据拷贝到写内存*/
        retvalue = write(fd, writebuf, 50);                 /*向设备驱动写入数据*/
        if(retvalue < 0){
    
    
            printf("write file %s failed! \r\n",filename);  /*写入错误输出提示*/
        }
    }

    retvalue = close(fd);                                   /*关闭文件*/
    if(retvalue < 0){
    
    
        printf("Can't close file %s\r\n",filename);         /*关闭错误输出提示*/
        return -1;
    }

    return 0;
}

3. Compile
1. Drive Compile
Create a Makefile in the same directory as chrdevbase.c and enter the following:

KERNELDIR := /home/water/water/kernel/linux-imx-onefu-20211024
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

# KERNELDIR表示开发板所使用的Linux内核源码目录,使用绝对路径
# CURRENT_PATH表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。
# obj-m表示将chrdevbase.c这个文件编译为chrdevbase.ko模块

# 具体的编译命令,后面的modules表示编译模块,
#-C表示将当前的工作目录切换到指定目录中,
#也就是KERNERLDIR目录。M表示模块源码目录,
#“make modules”命令中加入M=dir以后程序会自动到指定的dir目录中读取模块的源码并将其编译为.ko文件

The first line is the absolute path of the kernel source code, and the reader can modify it according to his actual path. The obj-m in the third line means to compile the chrdevbase.c file into the chrdevbase.ko module, which is basically the same as the above. Can.

After writing, save, and then enter: make in the terminal to compile and drive. The compilation result is as shown in the figure below:
insert image description here
The above figure uses the terminal that comes with vscode to compile, or you can enter the corresponding directory through the terminal of ubuntu and enter the make command. Compile, after the compilation is successful, "chrdevbase.ko" and some other files are generated in the current directory. The driver file used is this ".ko" file, and the rest is ignored.

2. Test APP compilation
Enter the same in the terminal opened by vscode: arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp to compile the test APP. Then the executable file chrdevbaseApp will be generated. You can view the file information through the "file chrdevbaseApp" command, as shown in the figure below:
insert image description here
Fourth, run the test
1. Copy the driver file "chrdevbase.ko" and the test program "chrdevbaseApp" to the root file Under the "lib/modules/4.1.15" directory of the system (the author uses nfs to mount the root file system, please refer to the system porting section for details). If it does not exist, create a directory. The directory "4.1.15" is mainly for Used to distinguish different kernel versions. There are two files in the red box below in the copied directory.
insert image description here
2. Connect the serial port of the development board to the computer, turn on the CRT, and then turn on the power supply. When the countdown is entered, press Enter to let the development board run in the uboot state. In this state, the following environment variables are mainly configured, as follows:

//设置bootcmd 
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000' 
//设置bootargs
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.144:/home/water/linux/nfs/onefu-rootfs,proto=tcp rw ip=192.168.1.145:192.168.1.144:192.168.1.1:255.255.255.0::eth0:off' 
saveenv //保存环境变量
boot //启动

Line 2: setenv bootcmd: Indicates setting the value of bootcmd in the environment variable;
tftp 80800000 zImage: Flag downloads the zImage file from the server to address 80800000 in the form of ftfp;
tftp 83000000 imx6ull-alientek-emmc.dtb: same as above;
bootz 80800000 - 83000000: Set the kernel address and device address of boot startup.
Line 4: setenv bootargs: Indicates setting the value of bootargs in the environment variable;
console=ttymxc0,115200: Setting the terminal and baud rate;
root=/dev/nfs: Setting the root boot directory to be /dev/nfs;
nfsroot= 192.168.1.144:/home/water/linux/nfs/onefu-rootfs: The corresponding directory of the slave server IP is 192.168.1.144;
proto=tcp: Set the communication mode TCP;
rw: Identifies the read-write function
ip=192.168.1.145: 192.168.1.144:192.168.1.1:255.255.255.0: respectively, brother development board IP. Server IP, gateway, mask;
line 5: saveenv: save the set variable of flower and bird volume
Line 6: boot: run into Linux.

3. After entering Linux, enter the directory "/lib/modules/4.1.15", and then use the command "ls" to view the file;
insert image description here
4. Mount the driver
Enter the following command to load the chrdevbase.ko driver file:

insmod chrdevbase.ko
//或
modprobe chrdevbase.ko

If you use modprobe to load the driver, the prompt shown in the figure below may appear: The
insert image description here
solution is:

//先执行命令
depmod 
//在执行
modprobe chrdevbase.ko

If the mount is successful, "chedevbase_init()" will be output, and then other files will be generated in the directory as shown in the figure below:
insert image description here
You can use "lsmod" to view, as shown in the figure below: You
insert image description here
can use the cat command to view the device, as shown in the figure below:
insert image description here
5. Create a device node
driver needs to be loaded successfully Create a corresponding device node file in the /dev directory, and the application program completes operations on specific devices by operating this device node file. Enter the following command to create the device node file /dev/chrdevbase:

mknod /dev/chrdevbase c 200 0

You can use the "ls /dev/chrdevbase -l" command to view, and the result is shown in the following figure:
insert image description here
6. Run verification
First perform the read operation and enter the following command:

./chrdevbaseApp /dev/chrdevbase 1 

The result is as follows:
insert image description here
As can be seen from the above figure, the read operation output prompt after running.

Next, to test the write operation to the chrdevbase device, enter the following command:

./chrdevbaseApp /dev/chrdevbase 2

The result is as follows:
insert image description here
As can be seen from the above figure, the write operation output prompt after running.

7. Uninstall the driver module
Enter the following command to uninstall the driver module:

rmmod chrdevbase.ko

insert image description here
Use the "lsmod" command to check whether the module is still there, as shown in the figure below:
insert image description here
As can be seen from the above figure, the module has been uninstalled.

So far, the character device-driven development framework process is as recorded above.

If there are any shortcomings, please give pointers, welcome to communicate and learn together.
Contact QQ: 759521350

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324486297&siteId=291194637