Linux mx6ull"/>
Linux mx6ull
编写第一个驱动,hello_drv
一、获取内核、编译内核。
这里为什么要获取内核呢,因为我们写的是驱动程序,而不是裸机程序。也就是我们的板子已经烧入进去了uboot、内核,根文件。然后我们要在这个板子的内核的基础上,来编写实现我们需要功能的代码,那么也就是说,我们的驱动代码是依赖于我们的内核的,那为什么需要编译我们的内核呢,这里简单点说就是我们的内核是经过编译后,生成了一些文件,然后烧入进去板子的,那么我们的驱动文件是依赖于这份编译后的内核的,那么我们的驱动代码也需要编译,这里的内核你可以理解成,就像51单片机写代码的时候,需要引入reg52.h,等头文件一样。不多说上正文。
获取内核文件
获取Linux内核文件,可以从Linux Kernel官网下载,这里我使用的是正点原子的mx6ull nand板子,为了跟开发板中的系统一致,避免出现其他问题,所以使用的是linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek,需要的话直接去正点原子liunx开发板A光盘下的例程源码下的03、正点原子Uboot和Linux出厂源码下载就可以了,用韦东山的也可以,不过需改改下网卡驱动口,比较麻烦就不弄了,需要注意的一点是,这里获取的内核,必须和开发板的烧入的内核一样,如果不一样可能出现驱动代码不兼容的情况。
链接:=0106
提取码:0106
编译内核
在编译之前,要在~/.bashrc文件下添加两行内容,来指定编译的平台和工具链
vim ~/.bashrc
在行尾添加或修改,加上下面几行
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
然后是打开内核压缩包所在文件夹,如图
输入如下命令解压文件
tar -vxjf linux-imx-4.1.15-2.1.0-g3dc0a4b-v2.7.tar.bz2
然后进入文件中,打开终端,开始编译内核,输入如下命令:
新建一个shell脚本:
touch imx6ull_alientek_nand.sh
在脚本文件里面添加以下内容:
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_nand_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
运行脚本:
./imx6ull_alientek_nand.sh
注意期间要是出现图形化界面配置,直接点击两次Esc退出就可以了,然后等待编译完成。
二、建立VScode文件,添加路径。
新建一个文件夹,用于保存我们的驱动文件,也就是工程目录。然后利用VSCode打开。然后在文件夹下面,新建一个.vscode文件夹。
使用快捷键shift+ctrl+p,进入配置,点击下面这个配置选择,会自动生成c_cpp_properties.josn文件.
把里面内容换成这个
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/","/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/asm/mach/" ],"defines": [],"compilerPath": "/usr/bin/clang","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4
}
注意,这里的/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/,也就是你们内核所在的路径,如果你们路径不是这个的话就自己手动改一下。
三、了解字符型驱动的编写步骤.
Linux 下的应用程序是如 何调用驱动程序的,Linux 应用程序对驱动程序的调用如图
Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启 动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在
Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译 为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。 而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编 译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进
Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。
今天我们编写的数字符型驱动,步骤大概如下
1.引入头文件
2.定义并创建设备信息结构体
3.定义并创建字符设备的文件操作函数结构体
4.编写对应的字符设备的文件操作函数
5.编写入口函数来注册驱动程序,告诉内核:我来啦。
编写入口函数具体实现:
(1)动态分配设备号:使用
alloc_chrdev_region()
函数或register_chrdev_region()
函数动态分配字符设备号。这些函数将为字符设备分配主设备号和次设备号。(2)初始化字符设备结构体:调用cdev_init()函数来初始化字符设备结构体。该函数需要传入要初始化的struct cdev变量以及指向文件操作函数表的指针。
(3)创建字符设备节点:调用cdev_add()函数将字符设备添加到内核中,在/dev/目录下创建相应的字符设备节点。
(4) 使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。
(5) 使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点
6.有入口函数就有出口函数:卸载驱动程序时就会去调用这个出口函数。在模块卸载时,使用 cdev_del() 函数注销字符设备驱动。
(1)使用 unregister_chrdev_region() 函数注销设备号,释放设备号资源。
(2)使用device_destroy销毁设备,删除相应的设备节点
(3)使用class_destroy销毁设备类,释放相关资源
7.调用函数把我们写的出口函数和入口函数告诉内核。
(1)使用module_init(hello_init)把我们编写的入口函数添加进去
(2)使用module_exit(hello_exit)把我们编写的出口函数添加进去
四、编写字符型驱动代码。
1.引入头文件。
这里我们可以仿照下别人的写法,打开内核目录下的drivers/char/1302目录(看名字就猜到该目录下存放应该是字符驱动代码)
接下来就是按照我上面的步骤走了。
1.引入头文件
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/major.h>
2.定义并创建设备信息结构体
/* newchrl设备信息结构体 */
struct newchr_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */
};struct newchr_dev newchr;
3.定义并创建字符设备的文件操作函数结构体
/* 2. 定义自己的file_operations结构体 */static struct file_operations hello_drv = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,
};
4.编写对应的字符设备的文件操作函数
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
5.编写入口函数来注册驱动程序,告诉内核:我来啦。
static int __init hello_init(void){/* 动态注册字符设备的流程一般如下:1.调用 alloc_chrdev_region() 函数申请设备编号。2.使用 cdev_init() 函数初始化设备描述结构体。3.使用 cdev_add() 函数将设备号与设备描述结构体关联起来,注册字符设备驱动。4.使用 class_create() 函数创建一个设备类.5.使用 device_create() 函数创建一个设备*/printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/*1 创建设备号根据是否定义了设备号,通过条件判断选择不同的创建方式。如果定义了设备号,则使用MKDEV宏将主设备号和次设备号合成为设备号,并调用register_chrdev_region()函数注册字符设备号。如果没有定义设备号,则使用alloc_chrdev_region()函数动态分配设备号,并通过MAJOR和MINOR宏获取分配得到的主设备号和次设备号。*/if (newchr.major) { /* 定义了设备号 */newchr.devid = MKDEV(newchr.major, 0);/*register_chrdev_region() 是 Linux 内核中用于向系统申请指定范围内的字符设备编号的函数*/register_chrdev_region(newchr.devid, NEWCHR_CNT, NEWCHR_NAME);} else { /* 没有定义设备号 */alloc_chrdev_region(&newchr.devid, 0, NEWCHR_CNT, NEWCHR_NAME); /* 申请设备号 */newchr.major = MAJOR(newchr.devid); /* 获取分配号的主设备号 */newchr.minor = MINOR(newchr.devid); /* 获取分配号的次设备号 */}/* 2 初始化cdev设置cdev结构体的拥有者为当前模块(THIS_MODULE),然后使用 cdev_init() 函数初始化cdev结构体。参数包括待初始化的cdev结构体和用于操作该设备的file_operations结构体(hello_drv) */newchr.cdev.owner = THIS_MODULE;cdev_init(&newchr.cdev, &hello_drv);/* 3、添加一个cdev */cdev_add(&newchr.cdev, newchr.devid, NEWCHR_CNT);/*4 创建设备类使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。该函数的参数包括所属的模块(THIS_MODULE)和设备类的名称(NEWCHR_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */newchr.class = class_create(THIS_MODULE, NEWCHR_NAME);if (IS_ERR(newchr.class)) {return PTR_ERR(newchr.class);}/*5 创建设备使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点。参数包括设备所属的类(newchr.class)、父设备(NULL,如果没有父设备)、设备号(newchr.devid)、设备私有数据(NULL,一般为设备驱动程序提供一个指针)和设备名称(NEWCHR_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */newchr.device = device_create(newchr.class, NULL, newchr.devid, NULL, NEWCHR_NAME);if (IS_ERR(newchr.device)) {return PTR_ERR(newchr.device);}printk("newchr init!\r\n");return 0;}
6.有入口函数就有出口函数:卸载驱动程序时就会去调用这个出口函数。在模块卸载时,使用 cdev_del() 函数注销字符设备驱动。
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */static void __exit hello_exit(void){/*在模块卸载时,使用 cdev_del() 函数注销字符设备驱动,并使用 unregister_chrdev_region() 函数释放设备号资源。*//* 注销字符设备驱动 */cdev_del(&newchr.cdev);/* 删除cdev */unregister_chrdev_region(newchr.devid, NEWCHR_CNT); /* 注销设备号 */device_destroy(newchr.class, newchr.devid);// 销毁设备,删除相应的设备节点class_destroy(newchr.class);// 销毁设备类,释放相关资源printk("hello_drv exit!\r\n");}
7.调用函数把我们写的出口函数和入口函数告诉内核。
最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否 则的话编译的时候会报错,作者信息可以添加也可以不添加。
/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("oudafa");
五、编写驱动对应应用代码。
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include "stdlib.h"int main(int argc, char **argv){int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf(" %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/newchr", O_RDWR);if (fd == -1){printf("can not open file /dev/newchr\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024); buf[1023] = '\0';printf("APP read : %s\r\n", buf);}close(fd);return 0;}
六、编写Makefile。
KERN_DIR = /home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientekall:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o newcharApp newcharApp.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f newcharAppobj-m += newchar.o
七、验证hello驱动。
使用make命令把上面驱动生成的.ko文件还有APP文件 通过nfs挂载的方式,传输到开发板上,然后复制到开发板的/lib/modules/4.1.15/下,然后使用insmod命令加载模块。
更多推荐
Linux mx6ull
发布评论