1.驱动前提
编译驱动程序之前要编译内核,原因主要是:
(1)驱动程序要用到内核文件:
比如驱动程序中这样包含头文件: #include <asm/io.h>, 其中的asm是一个链接文件,指向asm-arm或asm-mips,这需要先配置、编译内核才会生成asm这个链接文件。
(2)编译驱动时用的内核,开发板上运行到内核,要一致:
开发板上运行到内核是出厂时烧录的,你编译驱动时用的内核是你自己编译的,这两个内核不一致时会导致- -些问题。所以我们编译驱动程序前,要把自己编译出来到内核放到板子上去,替代原来的内核。
(3)更换板子上的内核之后,板子上的其他驱动也要更换
板子使用新编译出来的内核时,板子上原来的其他驱动也要更换为新编译出来的。所以在编译我们自己的第1个驱动程序之前,要先编译内核、模块,并且放到板子上去。
2.编译内核
首先切换到内核所在目录
cd /home/xxl/100ask_imx6ull_mini-sdk/Linux-4.9.88
之后进行下面的操作
make mrproper
make 100ask_imx6ull_mini_defconfig
make zImage -j4
编译后生成的文件如下:
然后进行下面操作,也就是
make dtbs
cp arch/arm/boot/zImage ~/nfs_rootfs
cp arch/arm/boot/dts/100ask_imx6ull_mini.dtb ~/nfs_rootfs
3.编译安装内核模块
3.1编译内核模块
首先进入内核源码目录,编译内核模块
cd /home/xxl/100ask_imx6ull_mini-sdk/Linux-4.9.88
然后输入下面的指令,编译完成如下图
make modules
3.2安装内核模块到Ubuntu某个目录下面备用
cd /home/xxl/100ask_imx6ull_mini-sdk/Linux-4.9.88
然后执行
make ARCH=arm INSTALL_MOD_PATH=/home/xxl/nfs_rootfs modules_install
安装好如下图所示:
4.安装内核和模块到开发板上面
假设:在Ubuntu的/home/book/nfs_ _rootfs 目录下,已经有了zImage、dtb文件,并且有1ib/modules子目录(里面含有各种模块)。
接下来要把这些文件复制到开发板上。假设Ubuntu IP 为192.168.5.11, .在开发板上执行以下命令,首先进行挂载,挂载之前记得先检查是否可以“ping 192.168.5.11”可以通顺,然后是每次重启开发板之后都要进行这个挂载操作
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/xxl/nfs_rootfs /mnt
然后执行:
cp /mnt/zImage /boot
cp /mnt/100ask_imx6ull_mini.dtb /root
cp /mnt/lib/modules /lib -rfd
sync
最后重启开发板之后,就可以使用zImage、dtb模块了
5.hello驱动
5.1驱动入门知识
1.首先我们通常都是在Linux的终端上打开一个可执行文件,然后可执行文件就会执行程序。那么这个可执行文件做了什么呢?
2.可执行文件先是在应用层读取程序,其中会有很多库函数,库函数是属于内核之中。而内核又会往下调用驱动层程序。最终驱动层控制具体硬件。
其实应用程序到库是比较容易理解的,比如我们刚学习C语言的时候,使用了printf,scanf等等这些函数。而这些函数就在库中。
库可以和系统内核相连接,我们写了一个驱动程序,就需要告诉内核,这个过程叫做注册。我们注册了驱动之后,内核里面就会有这个驱动程序的信息,然后上层应用就可以调用。
3.所以我们只需要知道,需要编写两个程序,一个是驱动层的,一个是应用层的,最后驱动层需要注册进入内核,应用层才能够使用。其他的先不要管。
4.我们在应用层调用read函数,对应驱动层的read函数。write函数和write函数对应。open函数和open函数对应。close函数和release函数对应(这个为什么不一样我们也不用管)。
5.我们对 Linux 应用程序对驱动程序的调用流程有一个简单了解之后,我得知道整个程序编写流程应该怎么做。至于流程为什么是这样的,我们记住即可。因为这些都是人规定的,如果之后学的深了再进行深究也不迟,现在我们主要是入门。
5.2整体框架和步骤
1.整体框架流程图
编写驱动主要为以下七个步骤:
- 确定主设备号,也可以让内核分配
- 定义自己的 file_operations 结构体
- 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file operations 结构体
- 把 file_operations 结构体告诉内核: register_chrdev
- 谁来注册驱动程序啊? 得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
- 有入口函数就应该有出口函数: 卸载驱动程序时,出口函数调用unregister_chrdev
- 其他完善:提供设备信息,自动创建设备节点: class_create,device_create
5.3hello程序的实现
主要包括三个部分,驱动程序,测试程序,MakeFile文件
(1)驱动程序-hello_drv.c
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
/*确定主设备号*/
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a,b)(a<b?a:b)
/*3.实现对应的open/read/write等函数,填入
file_operations结构体*/
//read
static ssize_t hello_drv_read(struct file *file,char _user *buf,
size_t size,loff_t *offset)
{
int err;
printk("s line %d\n",_FILE_,_FUNCTION_,_LINE_);
err = copy_to_user(buf,kernel_buf,MIN(1024,size));
return MIN(1024,size);
}
//write
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);
}
//open
static int hello_drv_open(struct inode *node,struct file *file)
{
printk("%s %s line %d\n",_FILE_,_FUNCTION_,_LINE_);
return 0;
}
//close
static int hello_drv_open(struct inode *node,struct file *file)
{
printk("%s %s line %d\n",_FILE_,_FUNCTION_,_LINE_);
return 0;
}
/*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.将file_operations结构体告诉内核:注册驱动程序*/
/*5.需要入口函数,也就是注册驱动程序,安装驱动程序时
就会去调用这个入口函数*/
static int __init hello_init(void)
{
int err;
printk("%s %s line %d\n",_FILE_,_FUNCTION_,_LINE_);
major = register_hrdev(0,"hello",&hello_drv); /*/dev/hello*/
hello_class = class_create(THIS_MODULE,"hello_class");
err = PTR_ERR(hello_class);
if(IS_ERR(hello_class)){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
unregister_chrdev(major,"hello");
return -1;
}
device_create(hello_class,NULL,MKDEV(major,0),NULL,"hello");/*/dev/hel*/
return 0;
}
/*6.出口函数,卸载驱动程序时就会调用这个出口函数*/
static void __exit hello_exit(void)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
device_destroy(hello_class,MKDEV(major,0));
class_destroy(hello_class);
unregister_chrdev(major,"hello");
}
/*7.其他完善:提供设备信息,自动创建设备节点*/
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
(2)测试程序-hello_drv_test.c
//编写测试程序,实现读写功能
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.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/hello",O_RDWR);
if(fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
/*3.写文件或者读文件*/
if((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\n",buf);
}
close(fd);
return 0;
}
(3)Makefile文件
1
2 # 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
3 # 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
4 # 2.1 ARCH, 比如: export ARCH=arm64
5 # 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
6 # 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc /ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
7 # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
8 # 请参考各开发板的高级用户使用手册
9
10 KERN_DIR = /home/xxl/100ask_imx6ull_mini-sdk/Linux-4.9.88
11
12 all:
13 make -C $(KERN_DIR) M=`pwd` modules
14 $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c
15
16 clean:
17 make -C $(KERN_DIR) M=`pwd` modules clean
18 rm -rf modules.order
19 rm -f hello_drv_test
20
21 obj-m += hello_drv.o
在make之前要进行以下的检查
也就是对环境变量进行检查(这里以IMX6ULL-MINI为例)
vim ~/.bashrc
在最后三行添加上
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/xxl/100ask_imx6ull_mini-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
然后输入下面指令确保生效
source ~/.bashrc
然后就可以输入make进行编译
生成如下:
将这两个文件移动到UBUNTU的挂载目录下
cp *.o hello_drv_test /home/xxl/nfs_rootfs
这时候在开发板进行如下操作
挂载操作
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/xxl/nfs_rootfs /mnt
然后进行下面的操作即可: