<Linux development>--Driver development--Character device driver (2) Process detailed record

<Linux development> - driver development - character device driver (2) 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

Other drivers can be viewed on the blogger's homepage. Since there will be more and more space in the future, I will not list them one by one and link them to the article.

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 2-led folder and create a new led.c And the ledApp.c file
(3) add the header file path
Because it is to write a Linux driver, 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 during development), remember, unless the system has been modified during porting. 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 led.c, and the function description of the function is explained in the source code comment, 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>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/io.h>

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

#define     LED_MAJOR   200     /*主设备号*/
#define     LED_NAME    "led"   /*设备名字*/

#define     LEDOFF      0       /*关灯*/
#define     LEDON       1       /*开灯*/

/*寄存器物理地址 宏定义*/
#define CCM_CCGR1_BASE                      (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE              (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE              (0X020E02F4)
#define GPIO1_DR_BASE                       (0X0209C000)
#define GPIO1_GDIR_BASE                     (0X0209C004)
/*映射后的寄存器虚拟地址指针*/
static  void    __iomem *IMX6U_CCM_CCGR1;
static  void    __iomem *SW_MUX_GPIO1_IO03;
static  void    __iomem *SW_PAD_GPIO1_IO03;
static  void    __iomem *GPIO1_DR;
static  void    __iomem *GPIO1_GDIR;

/*  
* @description : 打开/关闭  led 
* @param – sta :  LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
* @return : 无
*/
void led_switch(u8  sta)
{
    
    
    u32 val = 0;

    if(sta == LEDON){
    
                   /* 判断控制传入的状态  如果表示 开*/
        val = readl(GPIO1_DR);      /* 读取GPIO1组的DR寄存器,即GPIO1组的所有 IO 的状态*/
        val &= ~(1<<3);             /* 只对第三位进行清零操作 保持其他位不变*/
        writel(val, GPIO1_DR);      /*  将更改后的GPIO1组的DR寄存器值,写回到DR寄存器*/
    }else if(sta == LEDOFF){
    
            /* 判断控制传入的状态  如果表示 关*/
        val = readl(GPIO1_DR);      /* 读取GPIO1组的DR寄存器,即GPIO1组的所有 IO 的状态*/
        val |= (1<<3);              /* 只对第三位进行置1操作 保持其他位不变*/
        writel(val, GPIO1_DR);      /*  将更改后的GPIO1组的DR寄存器值,写回到DR寄存器*/
    }
}

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

/* 
 *@description : 从设备读取数据 
 * @param - filp : 要打开的设备文件(文件描述符) 
 * @param - buf : 返回给用户空间的数据缓冲区 
 * @param - cnt : 要读取的数据长度 
 * @param - offt : 相对于文件首地址的偏移 
 * @return : 读取的字节数,如果为负值,表示读取失败 
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    
    
    printk("led read !\r\n");          /*终端输出提示*/
    return 0;
}

/* 
 @description : 向设备写数据 
 @param - filp : 设备文件,表示打开的文件描述符 
 @param - buf : 要写给设备写入的数据 
 @param - cnt : 要写入的数据长度 
 @param - offt : 相对于文件首地址的偏移 
 @return : 写入的字节数,如果为负值,表示写入失败 
 */ 
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    
    
    int retvalue = 0;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);/*接收用户空间传递给内核的数据*/
    if(retvalue < 0){
    
    
        printk("kernel write failed! \r\n");    /*终端输出提示*/
        return -EFAULT;                         /*返回错误*/
    }

    ledstat = databuf[0];                       /*将读取到的数据 赋值给状态变量*/
    if(ledstat == LEDON){
    
                           /*判断状态变量 为 开灯*/
        led_switch(LEDON);                      /*开灯*/
    }else if(ledstat == LEDOFF){
    
                    /*判断状态变量 为 关灯*/
        led_switch(LEDOFF);                     /*关灯*/
    }

    return 0;
}

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

/* 
 *设备操作函数结构体 
 */ 
static struct file_operations led_fops = {
    
    
    .owner = THIS_MODULE,
    .open  = led_open,
    .read  = led_read,
    .write = led_write,
    .release = led_release,
};

/* 
*@description : 驱动入口函数 
*@param : 无 
*@return : 0 成功;其他 失败 
*/
static int __init led_init(void)
{
    
    
    int retvalue = 0;
    u32 val = 0;

    /*以下开始 初始化LED的GPIO引脚*/
    
    /* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1     = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03   = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03   = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR            = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR          = ioremap(GPIO1_GDIR_BASE, 4);

    /* 2、使能GPIO1时钟 */
    val = readl(IMX6U_CCM_CCGR1);   /*将CCGR1 寄存器的值读取出来*/
    val &= ~(3 << 26);              /*清除旧的设置位*/
    val |=  (3 << 26);              /*设置新位值*/
    writel(val, IMX6U_CCM_CCGR1);   /*写入CCGR1 寄存器的值*/

    /* 3、设置GPIO1_IO03的复用功能,将其复用为GPIO1_IO03,最后设置IO属性  */
    writel(5, SW_MUX_GPIO1_IO03);   
    /* 配置 GPIO1_IO03 的 IO 属性
        *bit 16:0 HYS 关闭
        *bit [15:14]: 00 默认下拉
        *bit [13]: 0 kepper 功能
        *bit [12]: 1 pull/keeper 使能
        *bit [11]: 0 关闭开路输出
        *bit [11]: 0 关闭开路输出
        *bit [5:3]: 110 R0/6 驱动能力
        *bit [0]: 0 低转换率
    */
    writel(0x10B0, SW_PAD_GPIO1_IO03);  /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */

    /* 4、设置GPIO1_IO03为输出功能 */
    val = readl(GPIO1_GDIR);   /*将GPIO1_GDIR 寄存器的值读取出来*/
    val &= ~(1 << 3);              /*清除旧的设置位*/
    val |=  (1 << 3);              /*设置新位值*/
    writel(val, GPIO1_GDIR);   /*写入GPIO1_GDIR 寄存器的值*/

    /* 5默认关闭LED */
    val = readl(GPIO1_DR);   /*将GPIO1_DR 寄存器的值读取出来*/
    val |=  (1 << 3);              /*设置新位值*/
    writel(val, GPIO1_DR);   /*写入GPIO1_DR 寄存器的值*/


    /* 6、注册字符设备驱动*/
    retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(retvalue < 0){
    
    
        printk("led driver register failed !\r\n");
        return -EIO;
    }
    printk("led_init()");
    return 0;
}

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

}

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

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

3. Write the test software code in the ledApp.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. 
 * 文件名 : ledApp.c 
 * 作者 : water 
 * 版本 : V1.0 
 * 描述 : led 驱测试APP。 
 * 其他 : 使用方法:./ledApp /dev/led <1>|<2>
 *                argv[2] 0:关闭LED
 *                argv[2] 1:打开LED
 * 日志 : 初版V1.0 2021/10/27 water创建 
 * ***************************************************************/ 

#define LEDOFF 0
#define LEDON  1

/* 
* @description : main主程序 
* @param - argc : argv数组元素个数 
* @param - argv : 具体参数 
* @return : 0 成功;其他 失败 
*/ 
int main(int argc, char *argv[])
{
    
    
    int fd, retvalue;               //fd: 文件描述符 用以对文件操作    retvalue:存放函数操作后的返回值
    char *filename;                 //filename:文件名,有主函数参数传入赋值
    unsigned char databuf[1];       //定义的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;
    }

    databuf[0] = atoi(argv[2]);                         /* 要执行的操作:打开或关闭 */

    retvalue = write(fd, databuf, sizeof(databuf));     /*向设备驱动写入数据*/
    if(retvalue < 0){
    
    
        printf("LED Control 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 of led.c file and enter the following content:

KERNELDIR := /home/water/water/kernel/linux-imx-onefu-20211024
CURRENT_PATH := $(shell pwd)
obj-m := led.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 lede.c file into the led.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, "led.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 led.c -o ledApp to compile the test APP. Then the executable file ledApp will be generated. You can view the file information through the "file ledApp" command, as shown below:
insert image description here

Fourth, run the test
1. Copy the driver file "led.ko" and the test program "ledApp" to the root file system (the author uses the form of nfs to mount the root file system, please refer to the system porting section for details) "lib" Under the /modules/4.1.15" directory, create a directory if it does not exist. The directory "4.1.15" is mainly 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 led.ko driver file:

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

If the mount is successful, "led_init()" will be output, as shown 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
After the driver is loaded successfully, a corresponding device node file needs to be created in the /dev directory. The application program completes the operation of the specific device by operating the device node file. Enter the following command to create the device node file /dev/chrdevbase:

mknod /dev/led c 200 0

insert image description here

You can use the "ls /dev/led -l" command to view, and the result is shown in the following figure:
insert image description here

6. Operation verification
First turn on the LED and enter the following command:

./ledApp /dev/led 1 

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

Next, test the shutdown operation of the led device and enter the following command:

./ledeApp /dev/led 0

The result is as follows:
insert image description here
The LED lights can be switched on and off through the above two commands, and the readers can test and verify by themselves, and the author's operation runs successfully.

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

rmmod led.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 LED driver development process driven by the character device 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=324485579&siteId=291194637