提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、编写驱动文件
- 1.相关头文件
- 2.驱动入口 &出口
- 3.申明
- 完整代码
- 二、编译驱动的方式
- 三、编译驱动
- 1. 和内核一起编译:
- 2. 编译成驱动模块:
- 3.开发板加载驱动模块
- 总结
前言
这里给大家介绍最基本的驱动开发框架
一、编写驱动文件
主要有下面几个部分组成:
1.相关头文件
这两个头文件黑丝驱动开发必须包含的头文件
#include<linux/init.h>
#include<linux/module.h>
2.驱动入口 &出口
static int hello_init(void)
{
printk("hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("by-by\n");
}
module_init(hello_init);//入口
module_exit(hello_exit);//出口
入口函数一般是主要完成平台总线的注册等操作,出口函数就是对资源的回收
3.申明
由于Linux是开源的所以需要声明凭证GPL,同时内核也提供了很多相关的宏,给写着描述
MODULE_LICENSE("GPL");//这个是必须要写的,不然编译会有问题
MODULE_AUTHOR("ljm");//作者
MODULE_DESCRIPTION("这是一个基本的驱动框架");
完整代码
#include<linux/init.h>
#include<linux/module.h>
static int hello_init(void)
{
printk("hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("by-by\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ljm");
二、编译驱动的方式
我们编译驱动有两种方式,一种是和内核一起编译,另一种是编译成内核模块,这两种有什么区别:
和内核一起编译:
对于和内核一起编译来说,是直接把驱动文件添加到内核源码的driver目录下的某一目录下,之后和内核一起编译,这中方式适合我们完成驱动调试好之后
编译成内核模块:
对于编译成内核模块来说,是没有这种要求,我们可以把驱动放在任何地方,可以单独编译成模块即带有.ko的文件,之后我们再使用命令insmod xxx.ko
来加载驱动即可,优点就是我们不需要的话可以直接使用命令rmmod xxx.ko
卸载掉,而不需要再去修改编译内核,可以看出这种方式适合我们在驱动开发时使用,适合我们调试,节省时间。
三、编译驱动
下面使用上面介绍的两种方式来演示一下如何编译驱动:
1. 和内核一起编译:
进入到内核源码目录下的如下目录:
drivers/misc
1)添加驱动文件,这个文件就是我们前面写好的文件
2)修改Makefile文件(也是在该目录下)
这个配置就是告诉makefile文件要把hellmodule.c编译
补充:这里除了这种配置外还可以按照下面做法配置:
细心的朋友可能注意到自己添加的哪部分为什么是obj-y而别人是obj-$(变量),如下:
这里解释一下,-y表示默认编译到内核中,而 obj-¥(变量)表示要不要编译,编译成什么状态由这个变量决定,那么问题来了,这个变量又是哪来的,在哪里设置?这个问题也就是要说的第二种配置:
还是和前面一样,把hellomodule.c拷贝到drivers/misc目录下
(也可以在misc下新建一个目录给自己的驱动,不过需要在自己驱动文件同级目录下写Kconfig,Makefile,还有修改misc的Kconfig文件添加自己Kconfig的路径 下面不介绍这种)
修改Makefile
文件
表示告诉makefil文件hellomodule.c要不要编译,编译成什么状态(模块或者和内核一起编译)由变量CONFIG_HELLOMODULE
决定
变量CONFIG_HELLOMODULE
要依据下面决定
修改Kconfig
文件:
具体意思如下:
config HELLOMODULE //变量,如果我们选择编译到内核相当于*,模块相当于m,不编译就空 ,图形配置决定,后面会说
tristate "HELLOMODULE" //表示该驱动有三态,* M 空
default n 默认不编译
help
this is my first driver. //帮助信息
图形配置该驱动
输入以下命令:
export ARCH=arm64 //导出环境变量为arm64
make rockchip_linux_defconfig //配置默认的内核配置文件,会产生.config文件,即最终的配置文件
输入以下命令进入图形配置:
make menuconfig
这里我选择编译到内核
然后保存配置
之后退出即可
保存退出之后,输入以下命令保存配置内核文件。
make savedefconfig
输入以下命令保存到默认的配置文件中:
cp defconfig arch/arm64/configs/rockchip_linux_defconfig
之所以进行这两步,是因为我们后面使用的是瑞芯微的脚本编译内核,而脚本里面默认编译的配置文件就是rockchip_linux_defconfig,所以我们得更新一下,不然看不到,因为我们的配置文件.config,并没有被这个脚本使用,当然你也可以使用直接使用make命令,但是使用这种方法编译出来的貌似还不能用
接下来后面的步骤是一样
3)重新编译内核
回到SDK的目录下输入以下命令编译
4)编译完成:
5)烧录新编译的镜像:
前面章节已经介绍了如何烧录传送门
6)烧录完成后在串口打印中可以看到如下所示的打印:
这个打印消息就是我们驱动加载的时候打印的消息
可以看出这里打印消息一模一样,表示已经加载驱动成功
2. 编译成驱动模块:
前面的方法中我们是将驱动和内核一起编译,但是我们每次修改驱动就要从新编译一次内核,特别费时间,所以我们经常把驱动编译成模块
1)编写Makefile文件
KERNEL_DIR=/home/topeet/Linux/linux_sdk/kernel #内核源码路径
ARCH=arm64 #芯片架构
CROSS_COMPILE=/usr/local/arm64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- #编译器
export ARCH CROSS_COMPILE
obj-m := hellomodule.o #编译成内核模块
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean #伪命令
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
读者要依据自己的内核源码路径和编译器的路径设置
其中对于编译器,瑞芯微提供的SDK里面就有编译器,我们只需要使用这个即可
如下编写Makefile设置:
KERNEL_DIR=/home/topeet/Linux/linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
export ARCH CROSS_COMPILE
obj-m := hellomodule.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean copy
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
sudo cp *.ko /home/embedfire/workdir
如果你出现如下错误,那么就说明你还没有设置好编译器的环境:
为了方便我们以后编写应用层的代码,我们最好把这个编译器环境导出来感兴趣的读者可以看这篇文章传送门
2).编译
把Makefile文件和驱动文件放在一起,如下:
执行以下命令:
make //编译
make clean //清除编译
其中hellomodule.ko就是我们的驱动模块
3.开发板加载驱动模块
通过网络Nfs或者其他方式把hellomodule.ko
文件发给开发板,在开发板中执行以下操作:
insmod hellomodule.ko //表示加载驱动模块
rmmod hellomodule.ko //卸载模块
总结
本文从零开始搭建了驱动的编写,编译,加载到实现,相信对于没有基础的读者会有一定的帮助,如果有帮助到你的地方清点击收藏+关注哦