linux驱动开发基础知识(待补充)
内核API文档
1. 操作集函数调用关系
在应用层的open()
、close()
、read()
、write()
…等C函数对应驱动层的open()
、close()
、read()
、write()
…等操作函数
2. kernel驱动操作函数集合
在 Linux 内核文件include/linux/fs.h
中有个叫做 file_operations
的结构体,此结构体就是 Linux 内核驱动操作函数集合
3. 应用层加载和卸载驱动
- 加载
insmod
:不会自动加载驱动依赖项modprobe
(推荐):将所有的依赖模块都加载到内核中,默认会去/lib/modules/<kernel-version>
目录中查找模块
- 卸载
rmmod
(推荐)modprobe -r
:modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。
4. 内核调试printk消息级别
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
// 用法,不显式指定级别时,默认级别4,KERN_WARNING
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");
5. 内存物理地址访问
API文档定位
// GPIO1_DR_BASE=真实地址
#define GPIO1_DR_BASE (0X0209C000)
// GPIO1_DR=虚拟地址
static void __iomem *GPIO1_DR;
// 申请虚拟地址
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
// 释放虚拟地址
iounmap(GPIO1_DR);
6. .dts、.dtb、dtc和.dtsi
.dts
是设备树源码文件.dtb
是将.dts
编译以后得到的二进制文件dtc
是把.dts
编译成.dtb
的编译工具,dtc
源码在Linux内核的scripts/dtc目录下.dtsi
是头文件,在.dts 设备树文件中,还可以通过#include
来引用.h、.dtsi 和.dts 文件。一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范
围,比如 UART、IIC 等等- 编译命令:进入到linux内核源码根目录,然后
make dtbs
- 移植内核
如何添加开发板的dtb文件
:从arch/结构/boot/.dts/Makefile
目录往下面找子目录的Makefile
,找到自己开发板送到的SOC的Makefile
文件,往里面添加新增的…dts文件的名字,同时相应的目录添加…dts文件。例如:全志-A33
---->arch/arm/boot/.dts/allwinner/Makefile
瑞芯微-RK3588
---->arch/arm64/boot/.dts/rockchip/Makefile
7. 设备树编写和使用
设备树规范文档,OF函数API文档
- 每片SOC
/
节点只有一个,多个文件的/
根节点会合并 - 关于
compatible
笔记:设备节点的compatible
:是为了匹配 Linux 内核中的驱动程序根节点中的compatible
:描述了所使用的硬件设备名字,Linux 内核会通过根节点的compoatible属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核
/
中有两个特殊的子节点:aliases
和chosen
:aliases
:aliases 节点的主要功能就是定义别名chosen
:不是一个真实的设备,chosen 节点主要是为了 uboot 向 Linux 内核传递数据
- 在已有设备树上增减内容,参考文档:
linux_kernel/Documentation/devicetree/bindings
- 设备树对应系统路径:设备树的
/
节点---->路径:/proc/device-tree
- OF 函数原型都定义在
include/linux/of.h
8. 设备树绑定文档阅读笔记
路径:Documentation/devicetree/bindings/i2c/allwinner,sun6i-a31-p2wi.yaml
# allOf 指定了该设备树文件继承了 /schemas/i2c/i2c-controller.yaml 的内容
allOf:
- $ref: /schemas/i2c/i2c-controller.yaml#
# properties 列出该设备的属性
properties:
# required 列出了必须定义的属性。这些属性在设备树中是必需的。
required:
# 👇规定除 properties 中列出的属性外,其他属性均不允许使用。避免引入意外属性。
unevaluatedProperties: false
内核代码前后缀含义汇总
inline
:即将函数调用替换为函数体,从而减少函数调用的开销,例如:
// 内联函数
inline int add(int a, int b) {
return a + b;
}
int main() {
int x = add(3, 4); // 编译器会直接把add(3, 4)替换为3 + 4
return 0;
}
// ------------------------------------------------------------------------------------
// 内联效果
int main() {
int x = 3 + 4; // 编译器直接将调用替换为3 + 4
return 0;
}
__initconst
:用于只读常量
,指示编译器将变量放置在一个特定的内存区域,以便在内核初始化完成后释放
这些内存,从而节省内存资源。__init
:用于内核初始化函数,表示这些函数仅在内核初始化
阶段使用,之后会释放掉
其所在的内存。