嵌入式学习linux内核驱动6——dts和GPIO子系统
一、驱动开发基础
MSC驱动模板
- 新的MSC驱动模板相比之前更加简洁,仅保留核心部分。
- 模板中已由系统完成class创建等初始化工作,开发者无需重复实现。
- class代表设备类型,在MSC初始化时已被创建,后续设备均属于该class。
动态编译与静态编译
- 动态编译允许内核启动后动态加载或卸载模块,便于调试,避免每次修改代码都需重启内核。
- 静态编译则将驱动直接编入内核镜像,适用于最终产品发布。
- 开发流程通常为:先动态编译进行调试,完成后转为静态编译集成到内核。
- 当驱动导致内核崩溃时,仍需重启系统恢复。
ioctl接口机制
- ioctl用于实现用户空间对设备的控制命令传递,功能强大且灵活。
- 命令由多个字段组合而成,包括type、number、direction和size。
- 使用宏(如_IOR、IOWR)来构造命令值,其中IOWR表示同时读写数据的操作较少见。
驱动开发:
内核驱动开发结束后即为
应用端开发:
二、设备与驱动分离架构
platform总线设计思想
- platform总线用于管理没有物理总线对应的片上外设资源。
- 设备与驱动分离可实现一对多或多对多匹配,减少重复代码。
- 同一类设备共享同一套驱动方法,只需定义不同的资源信息即可。
- 类比圆珠笔写字,不同颜色的笔不需要重新学习书写方式。
资源描述冗余问题
- 传统方式将设备资源配置硬编码在C文件中,导致内核体积膨胀。
- 不同开发板即使使用相同芯片也需维护各自的device.c文件,造成大量冗余。
- 官方内核需支持众多开发板,因此包含大量未使用的设备描述代码。
三、设备树(Device Tree)机制
引入背景与优势
- 为解决设备资源配置冗余问题,引入设备树机制,将硬件描述从内核代码中剥离。
- 设备树由Linus Torvalds推动发展,采用独立的DTS文件描述硬件资源。(以开发板为单位)
- 内核启动时加载DTB文件解析硬件信息,实现“一次编译,多平台运行”。
文件格式与结构
- DTS:设备树源文件,文本格式,可读性强。
- DTSI:头文件形式的设备树片段,用于共享公共定义。
- DTB:DTS经DTC工具编译后的二进制文件,供内核加载使用。
- 设备树呈树状结构,根节点为"/",每个大括号代表一个节点或子节点。
- 节点包含属性(property),采用键值对形式描述特征,类似JSON格式。
设备树:描述硬件资源 --- 以开发板为单位.dts 描述板级资源的设备树文件(一般一个开发板对应一个文件).dtsi 描述SOC级的设备树文件(类似于.c对应的.h文件).h C语言的头文件 --- 设备树编译支持预处理C文件.dtb 最终编译完成后生成的可加载的设备树文件(类似与a.out)
SOC级与板级描述
- DTSI文件描述SOC级资源,如CPU、PWM、I2C控制器等芯片固有组件。
- DTS文件描述板级外设,如LED、传感器、LCD背光等具体连接信息。
- 开发板DTS文件通过include引用对应SOC的DTSI文件,实现信息复用。
四、设备树实践操作
自定义设备树文件创建
- 复制现有开发板DTS文件作为新板型的基础模板。
- 修改文件名以区分不同开发板,例如从imx6ull.dts改为pute.dts。
- 需在Makefile中添加新DTS文件对应的DTB生成规则。
新加自己的设备树文件:
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dts
arch/arm/boot/dts/pute.dts
修改arch/arm/boot/dts/Makefile 新增一行:
pute.dtb(加到对应的SOC下)
make dtbs 编译所有的设备树文件
make pute.dtb 只编译pute.dts
cp arch/arm/boot/dts/pute.dtb /home/linux/tftpboot/
编译与部署流程
- 修改arch/arm/boot/dts/Makefile,加入新设备树目标。
- 执行make dtbs编译所有设备树文件,或指定目标单独编译。
- 使用TFTP将生成的DTB文件下载至内存并启动测试。
- 启动成功表明设备树基本结构无误,可在其基础上进一步修改适配。
五、开发流程规范
移植与验证原则
- 在移植任何驱动或系统组件时,应首先确保原始版本能正常编译运行。
- 验证基础功能无误后再进行定制化修改,便于问题定位。
- 启动命令参数实为三个地址:kernel镜像地址、DTB地址和unused参数地址。
六、设备树(DTS)语法与结构
节点与属性定义
- DTS文件由节点(node)和属性组成,节点可嵌套形成树状结构。
- 属性以键值对形式存在,每行以分号结尾。
- 节点可包含子节点(sub-node),形成层级关系。
属性合并与覆盖规则
- 当多个相同名称的节点存在时,其属性会进行合并。
- 若属性名重复,后定义的值会覆盖先定义的值,遵循“后写覆盖先写”原则。
- 此机制类似于变量赋值,支持部分重写或完全重写。
通用与自定义属性
- 通用属性(如reg、compatible、status)需遵循标准命名规范。
- 自定义属性可自行命名,适用于特定场景且无标准定义的情况。
- 建议使用语义清晰的名称以提高代码可读性。
地址与大小描述
- reg属性用于描述寄存器地址和大小,格式为。
ptled{#address-cells = <1>;#size-cells = <1>;compatible = "ptled0";name1 = "led1";status = "okay";reg = <0x020E0068 0x40X020E02F4 0x4 0X0209C004 0x4 0x0209C000 0x4>; };
- #address-cells 和 #size-cells 定义了地址和大小字段所占的单元数。
- 多个地址-大小对可在reg中连续列出。
七、设备树在驱动中的应用
节点查找与数据读取
- 使用of_find_node_by_path函数根据路径查找设备树节点。
- 查找结果返回struct device_node指针,用于后续操作。
- 利用of_property_read_string等函数读取节点属性值。
数据类型处理
- 支持读取字符串、整型数组等多种数据类型。
- of_property_read_u32_array用于读取32位整型数组。
- 所有读取操作基于已解析的设备树内存结构,无需重新解析文件。
错误处理与常量指针
- 函数返回值用于判断操作是否成功,需进行错误检查。
- 使用const修饰指针参数,确保指向的数据不可修改,符合只读要求。
八、GPIO子系统介绍与使用
子系统概念
- GPIO子系统是内核提供的标准化接口,封装底层寄存器操作。
- 类似I2C、SPI等其他外设也提供相应子系统接口。
引脚配置方法
- 在设备树中通过pinctrl节点定义引脚功能及电气属性。
- 使用pinmux设置引脚复用功能,避免与其他设备冲突。
- 每个引脚配置需唯一,防止资源竞争。
GPIO操作流程
- 使用of_get_named_gpio获取设备树中定义的GPIO编号。
- 调用gpio_request请求GPIO使用权。
- 设置方向(gpio_direction_output)并控制电平(gpio_set_value)。
- 使用完成后调用gpio_free释放资源。
驱动实现优化
- 将GPIO操作集成到驱动初始化和控制函数中。
- 取代直接寄存器访问,提升代码可移植性和可维护性。
- 支持输入模式下的电平读取(gpio_get_value)。