一、驱动开发的前期准备
在进入驱动开发之前,需要烧写UBoot、内核、设备树,做一些前期的准备工作,确保我们开发板上的内核版本和Ubuntu上是一致的才能进行正式开发
1.U-Boot
2.内核版本
3.使用TFTP挂载的内核和设备树
二、Linux驱动开发与裸机开发的区别
Linux驱动开发和裸机开发的区别主要体现在以下几个方面:
- 开发环境:
- Linux驱动开发:在带有Linux操作系统的环境下进行,开发者利用Linux提供的各种驱动框架和API进行开发。这通常意味着开发者需要熟悉Linux内核的工作原理和相关的系统调用。
- 裸机开发:在没有操作系统支持的环境中直接运行程序,开发者需要直接操作硬件的底层细节,包括寄存器、中断等。裸机开发常见于嵌入式系统、实时控制系统等对系统性能和资源使用有极高要求的场景。
- 开发技术:
- Linux驱动开发:主要使用C语言进行开发,并可能使用汇编语言进行某些特定的优化。开发者需要熟悉Linux内核编程和相关的驱动框架,如字符设备驱动、块设备驱动、网络设备驱动等。
- 裸机开发:可能涉及汇编语言、C语言或更低级别的编程语言,因为需要直接操作硬件的底层细节。开发者需要深入了解硬件的工作原理和如何与之交互。
- 开发流程:
- Linux驱动开发:通常包括需求分析、设计、编码、测试等阶段。开发者需要编写符合Linux内核规范的代码,并通过系统调用或设备文件与应用程序进行交互。
- 裸机开发:开发流程可能更为复杂,因为需要直接操作硬件。这通常包括编写启动代码、初始化硬件、配置中断、编写外设驱动程序等步骤。
- 关注点:
- Linux驱动开发:主要关注如何在Linux操作系统下实现对硬件设备的控制和管理,以及与操作系统的交互。
- 裸机开发:主要关注如何直接操作硬件,实现底层的功能和性能优化。
- 可移植性和复用性:
- Linux驱动开发:由于是基于Linux操作系统的,因此驱动程序的可移植性和复用性通常较好。Linux支持多种硬件平台和架构,开发者可以编写通用的驱动程序以支持不同的硬件。
- 裸机开发:由于直接操作硬件,因此可移植性和复用性通常较差。不同的硬件平台和架构可能需要完全不同的驱动程序。
- 调试和测试:
- Linux驱动开发:可以利用Linux提供的各种调试工具和技术进行调试和测试,如GDB、KDB等。此外,由于是在操作系统环境下进行开发,因此可以利用操作系统的功能进行辅助调试和测试。
- 裸机开发:调试和测试通常更为复杂和困难,因为需要直接操作硬件。开发者可能需要使用专门的调试工具和技术,如JTAG调试器、逻辑分析仪等。
总结来说,Linux驱动开发和裸机开发在开发环境、技术、流程、关注点、可移植性和复用性、调试和测试等方面都存在显著的区别。选择哪种开发方式取决于具体的应用场景和需求
驱动最终表现就是/dev/xxx文件,对文件的读写、打开关闭;现在新的内核支持设备树,这个一个.dts文件,此文件描述了板子的设备信息
驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作
2.1 驱动设备的分类
字符设备驱动:字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的
块设备驱动:所谓的块设备驱动就是存储器设备的驱动,比如 EMMC、 NAND、 SD卡和 U盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。网络设备驱动就更好理
网络设备驱动:就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。一个设备可以
一个设备可以属于多种设备驱动类型,比如 USB WIFI,其使用 USB接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。
三、Linux驱动开发的VScode的环境搭建
1.禁止鼠标中键复制与创建驱动文件夹
//禁止鼠标中键复制
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ xmodmap -e "pointer = 1 25 3 4 5 6 7 2"
Warning: Only changing the first 8 of 16 buttons.
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ pwd
/home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ ls
chrdevbase.c chrdevbase.code-workspace
///home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase在该路径下,新建第一个驱动的文件夹,编写代码
2.将创建的驱动文件夹,添加到Ubuntu的vscode中
3.将我们的内核源码路径添加到c_cpp_properties.json和Makefile
(1)c_cpp_properties.json文件
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
(2)Makefile文件
KERNELDIR := /home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
//KERNELDIR表示开发板所使用的 Linux内核源码目录,使用绝对路径
CURRENT_PATH := $(shell pwd)
//CURRENT_PATH表示当前路径,直接通过运行“ pwd”命令来获取当前所处路
径。
obj-m := chrdevbase.o
//obj-m表示将 chrdevbase.c这个文件 编译为 chrdevbase.ko模块。
build: kernel_modules
//表示我们编译的是模块
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
//编译时,先把目录切换到源码中,因为使用了源码的一些函数;编译完成后再把目录切换到当前目录,需要把编译生成的文件放在当前目录下,Linux内核中我们已经指定好交叉编译器了
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3.先清理(make clean)再编译(make)
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ make clean
make -C /home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek M=/home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase clean
make[1]: Entering directory '/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek'
CLEAN /home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase/.tmp_versions
CLEAN /home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase/Module.symvers
make[1]: Leaving directory '/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek'
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ make
make -C /home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek M=/home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase modules
make[1]: Entering directory '/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek'
CC [M] /home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase/chrdevbase.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase/chrdevbase.mod.o
LD [M] /home/alientek/linux/IMX6ULL/Linux_Drivers/1_chrdevbase/chrdevbase.ko
make[1]: Leaving directory '/home/alientek/alpha/alientek-zzk/kernel-chao/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek'
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ ls
chrdevbase.c chrdevbase.code-workspace chrdevbase.ko chrdevbase.mod.c chrdevbase.mod.o chrdevbase.o Makefile modules.order Module.symvers
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$
4.先清理(make clean)再编译(make)
四、驱动模块加载与卸载
Linux驱动有两种运行方式,第一种就是将驱动编译进 Linux内核中,这样当 Linux内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块 (Linux下模块扩展名为 .ko),在Linux内核启动以后使用“ insmod”命令加载驱动模块。
1.将我们在vscode中编译出来的文件拷贝到制作好的根文件目录下
alientek@ubuntu16:~/linux/IMX6ULL/Linux_Drivers/1_chrdevbase$ sudo cp chrdevbase.ko /home/alientek/linux/nfs/rootfs/lib/modules/4.1.15 -f
2.modprobe来加载模块
insmod命令不能解决模块的依赖关系,modprobe会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,我们使用modprobe来加载模块;
modprobe命令默认会去/lib/modules/<kernel-version>目录中查找模块,比如本书使用的 Linux kernel的版本号为 4.1.15因此 modprobe命令默认会到 /lib/modules/4.1.15这个目录中查找相应的驱动模块,一般自己制作的根文件系统中是不会有这个目录的,所以需要自己手 动创建。
对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令;驱动模块加载成功以后可以使用lsmod查看一下;卸载模块使用rmmod命令
2.lsmod查看当前驱动模块是否有我们刚刚加载的模块
3.rmmod查看当前驱动模块
4.执行命令
/lib/modules/4.1.15 # modprobe chrdevbase.ko
chrdevbase: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
module_init!
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: P
chrdevbase 664 0
/lib/modules/4.1.15 # rmmod chrdevbase.ko
module_exit!
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: P
/lib/modules/4.1.15 # ls
8188eu.ko ft5x06.ko libcomposite.ko
8189fs.ko g_audio.ko modules.alias
8192cu.ko g_mass_storage.ko modules.dep
8821cu.ko gpioled.ko modules.symbols
adcApp ledAPP usb_f_mass_storage.ko
chrdevbase.ko ledApp usb_f_uac1.ko
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: P
/lib/modules/4.1.15 # modprobe chrdevbase.ko
module_init!
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: P
chrdevbase 664 0
/lib/modules/4.1.15 # rmmod chrdevbase.ko
module_exit!
/lib/modules/4.1.15 # lsmod
Module Size Used by Tainted: P
5.在Ubuntu上的根文件系统中同步可以看到文件
alientek@ubuntu16:~/linux/nfs/rootfs/lib/modules/4.1.15$ pwd
/home/alientek/linux/nfs/rootfs/lib/modules/4.1.15
alientek@ubuntu16:~/linux/nfs/rootfs/lib/modules/4.1.15$ ls
8188eu.ko chrdevbase.ko ledApp modules.symbols
8189fs.ko ft5x06.ko ledAPP usb_f_mass_storage.ko
8192cu.ko g_audio.ko libcomposite.ko usb_f_uac1.ko
8821cu.ko g_mass_storage.ko modules.alias
adcApp gpioled.ko modules.dep
alientek@ubuntu16:~/linux/nfs/rootfs/lib/modules/4.1.15$ ls
8188eu.ko chrdevbase.ko ledApp modules.symbols
8189fs.ko ft5x06.ko ledAPP usb_f_mass_storage.ko
8192cu.ko g_audio.ko libcomposite.ko usb_f_uac1.ko
8821cu.ko g_mass_storage.ko modules.alias
adcApp gpioled.ko modules.dep
我们的第一个驱动模块加载与卸载编译完成
五、代码
chrdevbase.c
#include <linux/module.h>
#include <linux/init.h>//printk函数头文件
static int __init chrdevbase_init(void)
{
printk("module_init!\r\n");
return 0;
}
static void __exit chrdevbase_exit(void)
{
//没有返回值
printk("module_exit!\r\n");
}
/*
模块的入口与出口
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
/*
模块的一些许可证信息
*/
MODULE_AUTHOR("Chao");//作者是谁
MODULE_LICENSE("GPL");//开源协议