嵌入式驱动程序设计
1.部分Linux命令
Linux使用mknod命令创建设备节点
Linux使用mkdir命令创建目录文件
Linux使用mkfifo命令创建管道文件
☞ linux mknod命令解析_童安格粉丝的博客-CSDN博客_mknod
☞ mkfifo函数使用_superywf的博客-CSDN博客_mkfifo函数
2. Linux设备驱动
Linux设备驱动主要分为:字符设备、块设备、网络设备
. Linux驱动模块的后缀名为: .ko
☞ Linux中后缀名为ko、o、a、so、la的文件介绍_LINUX_操作系统
3. Linux系统调用
Linux下常用系统调用名,系统调用实际也是VFS层提供的功能:
read:读取;open:打开设备;close:关闭设备; lseek:偏移设备
注意:fopen\fread为标准IO,不是系统调用
☞ Linux常用系统调用 - LaplaceDemon - 博客园 (cnblogs.com)
4. platform设备驱动
内核platform总线设备是一种虚拟的设备驱动
Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver
platform_driver和platform_device在总线中匹配成功后,执行probe接口
☞ Linux驱动之Platform device/driver - 知乎 (zhihu.com)
5. Linux实现物理地址和虚拟地址的映射的方式
Linux通过ioremap函数实现物理地址和虚拟地址的映射
☞ Linux虚拟地址和物理地址的映射 - 流浪的Coder - 博客园 (cnblogs.com)
6.设备树
设备树是一种描述硬件结构的数据结构,采用设备树后,许多硬件的细节可以直接通过它传递给linux,而不再需要在内核中进行大量的冗余编码。
设备树由一系列被命名的节点和属性(成对出现的名称和值)组成,可描述的信息包括:
a.CPU数量和类别 b.内存基地址和大小
c.总线和桥 d.外设连接
e.中断控制器和中断使用情况 f.GPIO控制器和GPIO使用情况
g.时钟控制器和时钟使用情况
基本上就是画一颗电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,内核识别出这棵树,并根据它展开出Linux内核中的platform_device、i2C_client等设备,而这些设备用到的内存、IRQ等资源也被传递给了内核,内核会将这些资源给展开的相应的设备。
设备树被内核识别后,会在/sys目录下为用户提供可视化操作接口
/sys:sys是system的缩写,该目录也是一个虚拟目录,主要用于收集和存放系统核心设备信息,主要指各类系统核心设备及其驱动信息,例如块设备block、总线bus、内核kernel、电源power等等;
设备树定义节点时,定义资源的属性名是reg
reg:给出访问该device node的地址信息
☞ 设备树之节点和属性_麻辣小新的博客-CSDN博客_设备树label
7.miscdevice
miscdevice类型定义的是misc设备驱动
linux的miscdevice的主设备号是10
☞ misc设备驱动_eurphan_y的博客-CSDN博客_misc驱动
☞ linux驱动开发 --miscdevice_Sunnie_ge的博客-CSDN博客_miscdevice
8.printk内核打印函数
☞ 内核打印函数printk_ptonlix的博客-CSDN博客_内核打印函数
☞ printk 内核打印 – 人人都懂物联网 (getiot.tech)
在printk打印内核信息时,以信息级打印应设置KERN_INFO前缀
#define pr_info(fmt,arg...) \ printk(KERN_INFO fmt,##arg)
9.内核空间拷贝到用户空间
实现内核空间拷贝到用户空间的接口名:copy_to_user
☞ linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问 (aliyun.com)
10.cdev结构体
内核提供了cdev结构体描述字符设备,cdev_add是向内核添加,cdev_del是从内核中移除
☞ Linux 字符设备驱动结构(一)—— cdev 结构体、设备号相关知识解析
11.内核模块
11-1:内核模块源码编译方式
内核模块源码可以配置为编译进内核和以模块编译的方式
objs-y的目标被编译进内核镜像中,objs-m 以模块方式编,其余的跟内核没关系。
11-2:内核模块入口
内核模块的入口需要通过module_init进行注册到内核里
11-3:内核模块加载
内核模块通过insmod命令实现用户空间向内核空间加载
☞ Linux 系统设置 : insmod 命令详解_HarkerYX的博客-CSDN博客_insmod
12.linux主设备号
Linux字符设备和块设备独占主设备号
Linux设备号中主设备号为12位
13.kmalloc和malloc
使用kmalloc函数在内核空间申请内存,使用malloc函数从用户空间申请内存
14.sysfs文件系统
sysfs是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,它的一个目的就是战士设备驱动模型中各组件的层次关系。
内核通过sysfs文件系统导出内核设备模型
☞ sysfs 文件系统 - 简书 (jianshu.com)
15.linux内核的虚拟内存
☞ linux虚拟内存与物理内存,内核态与用户态_selfsongs的博客-CSDN博客
在linux系统中,进程的4GB内存空间被分成2个部分,用户空间与内核空间。用户空间的地址一般分布为0~3GB,这样,剩下的3~4G为内核空间。
linux内核的虚拟内存是3G-4G空间
16.wait_event接口
使用wait_event接口将当前访问进程设置为等待状态
☞ linux内核中的wait_event_interruptible_timeout接口解析_weixin_30263277的博客-CSDN博客
17.驱动的Makefile框架
BASE_KERNEL ?= 内核的源码目录
obj-m += 驱动源文件名.o
all:
make -C $(BASE_KERNEL) M=$(PWD) modules
clean:
make -C $(BASE_KERNEL) M=$(PWD) clean
eg.驱动源文件为abc.c和hello.c
# 若是虚拟机自身内核,可以定义变量为: /lib/modules/$(shell uname -r)/build # 定义内核源码根目录 BASE_KERNEL ?= /lib/modules/$(shell uname -r)/build # 定义目标名 TARGET := hello obj-m := abc.o obj-m += hello.o all: make -C $(BASE_KERNEL) M=$(PWD) modules clean: make -C $(BASE_KERNEL) M=$(PWD) clean
18.linux驱动模块组成
1)模块加载函数,通过insmod命令加载内核,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
2)模块卸载函数,通过rmmod命令卸载某模块,被调用执行。
3)模块许可证声明,使用MODULE_LICENSE配置GPL的声明。
4)可选模块参数、模块导出符号表、模块作者等信息声明。
☞ linux驱动模块组成及其它_天天向上_好好学习的博客-CSDN博客_linux驱动模块的组成
GPL:GNU 通用公共许可协议
module_init(hello_init);//模块加载 module_exit(hello_exit);//模块卸载 MODULE_LICENSE("GPL");//模块许可证声明 MODULE_AUTHOR("wit@zhaixue.cc");//模块作者等信息声明
insmod 命令用法:
sudo insmod hello.ko #加载模块到内核中
rmmod 命令用法:
sudo rmmod hello.ko #卸载模块
19.编写global_memory的open函数
globalmem意味着“全局内存”,在globalmem字符设备驱动中会分配一片大小为GLOBALMEM_SIZE(4KB)的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过Linux系统调用获取或设置这片内存的内容。
对于globalmem驱动而言,私有数据的设置是在global_open()中完成的。
open在驱动中,主要做面向对象的解包。想办法从基类找子类,把子类绑到所有的文件描述符里面,便于read()、write()、ioctl()、llseek()函数进行访问。思路是先通过inode节点,想办法找到它的子类,映射给虚拟的文件。
static int mem_open(struct inode *node, struct file *fp) {
struct mem_dev *dev; //定义一把子类的钥匙
dev = container_of(node->i_cdev, struct mem_dev, cdev);//container_of 通过基类找子类
fp->private_data = dev; //与虚拟的文件系统进行绑定 (private_data:地址空间的私有成员)
return 0; //代表成功
}
container_of(ptr,type,member)//其中ptr、type、member分别代表指针、结构体类型、成员
☞ container of()函数简介_叨陪鲤的博客-CSDN博客_container_of函数
eg.
struct mem_dev{ struct cdev abc; unsigned char mem[GLOBALMEM_SIZE]; //... } static int mem_open(struct inode *node, struct file *fp) { struct mem_dev *dev; dev = container_of(node->i_cdev, struct mem_dev, abc); fp->private_data = dev; return 0; }
20.最简单的驱动程序“Hello World”编写
#include <linux/init.h>
#include <linux/module.h>
static __init int hello_init(void) {
printk("Hello world!\n");
return 0;
}
static __exit void hello_exit(void) {
printk("Goodbye!\n");
}
module_init(hello_init)
module_exit(hello_exit)
MODULE_LICENSE("GPL");
运行结果:
模块加载之后的结果:
模块卸载之后的结果:
食用方法☞:Linux内核驱动学习---编写最简单Linux内核模块HelloWorld_九阈的博客-CSDN博客_添加最简单的linux内核模块
21.从设备树获取资源
从设备树获取资源一般由 platform_get_resource 函数来实现
☞ platform_get_resource_铁头小哥的博客-CSDN博客_platform_get_resource
static int drv_probe(struct platform_device *pdev) {
const char *value;
struct resource *res;
device_property_read_string(&pdev->dev, "xxx_value", &value);
printk("the value is %s\n", value);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
printk("the start: %x, the end: %x\n",res->start, res->end);
return 0;
}
platform的OF匹配表,使驱动与.dfs中描述的设备节点进行匹配,从而使驱动的probe()函数执行:
static struct of_device_id matchs[] = { {.compatible = "swpu,embed,led", }, {} }; static struct platform_driver drv_05025_driver = { .probe = drv_probe, .remove = drv_remove, .driver = { .name = "drv", .of_match_table = matchs, }, }; module_platform_driver(drv_05025_driver); MODULE_LICENSE("GPL");