Linux DTS

Linux DTS
Device Tree设备树是一种描述硬件的数据结构起源于 OpenFirmware用于向 Linux 内核提供不可自动发现的硬件non-discoverable hardware信息。 在 Device Tree 出现之前这些硬件描述信息是硬编码在 Linux 内核源码中的迁移性很差。一. 核心文件类型后缀全名说明.dtsDevice Tree Source描述特定板级的完整硬件资源由驱动工程师编写每块 Board 一个.dtsiDevice Tree Source Include可被多个.dts 共享的公共硬件描述如 SoC 级别通过 #include 引用.dtbDevice Tree BlobDTS 编译后的二进制格式由 Bootloader 传递给内核.dtboDevice Tree Blob Overlay叠加层的二进制可在运行时叠加到 Base DTB 上dtcDevice Tree Compiler将 DTS → DTB 的编译工具二. 文件层级关系drv1.dtsi ┐ ├─→ 8155.dtsi (SoC 公共层) drv2.dtsi ┘ ↓ 8155-idp.dts (Board 级 DTS最终产物) │ dtc 编译 ↓ 8155-idp.dtb (内核使用)dts路径, /kernel/arch/arm64/boot/dts三. DTS 内核解析机制阶段 1DTB 传递Bootloader → KernelABLAndroid Bootloader / UEFI在启动 Kernel 时从 boot.img 中选择匹配的 DTB根据 qcom,msm-id 和 qcom,board-id将 DTB 的物理地址存入 ARM64 寄存器 x0 并传递给 Kernel阶段 2早期解析setup_arch ()start_kernel() └- setup_arch(command_line) └- setup_machine_fdt(__fdt_pointer) // 获取 DTB 物理地址 ├- fixmap_remap_fdt // 将 DTB 映射到虚拟地址 └- early_init_dt_scan() ├- early_init_dt_verify() // 校验 DTB 魔数 └- early_init_dt_scan_nodes() ├- of_scan_flat_dt(...chosen) // 解析 /chosen 节点(bootargs) ├- of_scan_flat_dt(...root) // 解析 address/size-cells └- of_scan_flat_dt(...memory)// 解析 memory 节点阶段 3展开设备树unflatten_device_tree ()setup_arch() └- unflatten_device_tree() └- __unflatten_device_tree() └- unflatten_dt_nodes() ├- dt_alloc() // 分配 device_node 内存 ├- populate_node() // 填充节点信息 └- populate_properties() // 填充属性阶段 4驱动匹配与 Probeof_platform_populate ()重点理解这一块即可// drivers/of/platform.c of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);遍历of_root下所有节点根据compatible属性在驱动表中查找匹配的驱动触发platform_driver.probe()回调设备树中定义示例以高通 8155 举例SPI 控制器是 QUPv3 Serial Engine。节点定义在sm8150.dtsi中DTS就是一个硬件的配置文件配置了硬件的寄存器地址还有硬件参数等在内核启动时被解析为内部变量存储里面的compatible用于和驱动代码进行匹配匹配后触发对应驱动的probe函数回调。/* arch/arm64/boot/dts/qcom/sm8150.dtsi */ qupv3_se12_spi: spia84000 { /* * 1. 标识与匹配 */ compatible qcom,geni-spi; /* ↑ 驱动匹配的关键字串 内核用此在驱动表中查找对应的 platform_driver */ /* * 2. 寄存器基地址 */ reg 0 0x00a84000 0 0x4000; /* ↑ 格式: 高32bit 低32bit(物理地址) 高32bit 长度 0x00a84000 SPI SE12 的 MMIO 寄存器基地址 0x4000 寄存器空间大小(16KB) */ /* * 3. 中断配置 */ interrupts GIC_SPI 358 IRQ_TYPE_LEVEL_HIGH; /* ↑ GIC_SPI: 共享外设中断(SPI类型非Serial Peripheral Interface) 358: 硬件中断号 LEVEL_HIGH: 高电平触发 */ /* * 4. 时钟配置 */ clocks gcc GCC_QUPV3_WRAP1_S6_CLK; clock-names se; /* ↑ 向 GCC(全局时钟控制器) 申请 SE12 的串行引擎时钟 驱动通过 clock-names 的字符串 se 来 devm_clk_get() */ /* * 5. GPIO Pinctrl 配置 */ pinctrl-names default, sleep; pinctrl-0 qupv3_se12_spi_active; /* active 状态 GPIO 组 */ pinctrl-1 qupv3_se12_spi_sleep; /* sleep 状态 GPIO 组 */ /* ↑ 驱动调用 pinctrl_pm_select_default_state() 自动将 MOSI/MISO/CLK/CS 这4个 GPIO 切换到对应的 pin 状态 */ /* * 6. DMA 通道高速传输用 */ dmas gpi_dma1 0 6 QCOM_GPI_SPI, gpi_dma1 1 6 QCOM_GPI_SPI; dma-names tx, rx; /* ↑ 高速模式下使用 GPI DMA 进行 TX/RX 低速小数据走 FIFO 模式由驱动自动切换 */ /* * 7. 互联带宽 (NoC/ICC) */ interconnects clk_virt MASTER_QUP_CORE_1 0 clk_virt SLAVE_QUP_CORE_1 0, gem_noc MASTER_APSS_PROC 0 cnoc2 SLAVE_QUP_1 0; interconnect-names qup-core, qup-config; /* ↑ 向 NoC 框架声明带宽需求避免总线瓶颈 */ /* * 8. 子节点: SPI 上挂载的具体设备 */ #address-cells 1; /* 子节点地址位宽 1 (chip select 编号) */ #size-cells 0; /* 子节点无 size */ /* * 9. 默认禁用板级按需打开 */ status disabled; };Step 1驱动注册spi-geni-qcom.c/* drivers/spi/spi-geni-qcom.c */ static const struct of_device_id spi_geni_dt_match[] { { .compatible qcom,geni-spi }, /* ← 与 DTS 中的 compatible 完全对应 */ {}, }; MODULE_DEVICE_TABLE(of, spi_geni_dt_match); static struct platform_driver spi_geni_driver { .probe spi_geni_probe, /* ← 匹配成功后调用 */ .remove spi_geni_remove, .driver { .name geni_spi, .of_match_table spi_geni_dt_match, /* ← 匹配表 */ .pm spi_geni_pm_ops, }, }; module_platform_driver(spi_geni_driver);Step 2匹配过程of_platform_populate() ├─ 遍历 device_node: spia84000 │ └─ 读取 compatible qcom,geni-spi └─ of_driver_match_device() ├─ 遍历所有已注册的 platform_driver │ └─ 检查 driver.of_match_table └─ 找到 spi_geni_driver (compatible 字符串完全匹配) ├─ 创建 platform_device 并绑定驱动 └─ 调用 spi_geni_probe()Step 3Probe 函数硬件操作逻辑本质操作寄存器static int spi_geni_probe(struct platform_device *pdev) { struct device_node *np pdev-dev.of_node; /* ① 读取寄存器地址来自 DTS reg 字段 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); base devm_ioremap_resource(pdev-dev, res); /* 将物理地址 0xa84000 映射为内核虚拟地址 */ /* ② 获取时钟来自 DTS clocks/clock-names 字段 */ clk devm_clk_get(pdev-dev, se); clk_prepare_enable(clk); /* ③ 获取中断号来自 DTS interrupts 字段 */ irq platform_get_irq(pdev, 0); devm_request_irq(pdev-dev, irq, spi_geni_isr, ...); /* ④ 配置 Pinctrl来自 DTS pinctrl 字段 */ pinctrl devm_pinctrl_get_select_default(pdev-dev); /* ⑤ 注册 SPI 控制器到 SPI 子系统 */ spi devm_spi_alloc_master(pdev-dev, sizeof(*mas)); spi-transfer_one_message spi_geni_transfer_one_message; devm_spi_register_master(pdev-dev, spi); /* → 之后子节点的 SPI 设备如 CAN才能被枚举 */ }四. Linux Module宏说明模块基础宏宏头文件说明module_init(fn)linux/module.h注册模块加载时的初始化函数insmod 或内核启动时调用module_exit(fn)linux/module.h注册模块卸载时的清理函数rmmod 调用MODULE_LICENSE(GPL)linux/module.h声明模块 License必须写否则内核会 taint。常用值GPL、GPL v2、Dual BSD/GPLMODULE_AUTHOR(xxx)linux/module.h声明模块作者可选MODULE_DESCRIPTION(xxx)linux/module.h模块描述说明可选MODULE_VERSION(1.0)linux/module.h模块版本号可选MODULE_ALIAS(platform:xxx)linux/module.h声明模块别名用于 udev 自动加载匹配平台驱动注册宏最常用宏等价展开说明module_platform_driver(drv)module_init platform_driver_register module_exit platform_driver_unregister最常用SA8155 的 SPI/UART/I2C 驱动几乎全部使用此宏注册module_platform_driver_probe(drv, probe_fn)同上但 probe 不走总线匹配直接指定函数用于非标准 probe 场景各总线驱动注册宏宏适用总线说明module_i2c_driver(drv)I2C注册 I2C 客户端驱动SA8155 的传感器、PMIC 子设备常用module_spi_driver(drv)SPI注册 SPI 设备驱动注意这是 SPI 外设驱动不是控制器驱动module_usb_driver(drv)USB注册 USB 设备驱动module_pci_driver(drv)PCI/PCIe注册 PCIe 驱动SA8155 的 WiFi / 以太网常用module_mdio_driver(drv)MDIO注册 MDIO 总线驱动module_auxiliary_driver(drv)Auxiliary Bus注册辅助总线驱动较新内核

最新新闻

日新闻

周新闻

月新闻