承接上文,第一篇
3. Device&Tree 引发的 BSP 和驱动变更
有了 Device Tree 后,大量的板级信息都不再需要,譬如过去经常在 arch/arm/plat-xxx 和 arch/arm/mach-xxx 实施的如下事情:
1. 注册 platform_device,绑定 resource,即内存、IRQ 等板级信息。
透过 Device Tree 后,形如
之类的 platform_device 代码都不再需要,其中 platform_device 会由 kernel 自动展开。而这 些 resource 实际来源于.dts 中设备结点的 reg、interrupts 属性。
典型地,大多数总线都与“simple_bus”兼容,而在 SoC 对应的 machine 的.init_machine 成员函数中,调用 of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有 的 platform_device。譬如,假设我们有个 XXX SoC,则可在 arch/arm/mach-xxx/的板文件 中透过如下方式展开.dts 中的设备结点对应的 platform_device:
2. 注册 i2c_board_info,指定 IRQ 等板级信息。 形如
之类的 i2c_board_info 代码,目前不再需要出现,现在只需要把 tlv320aic23、fm3130、 24c64 这些设备结点填充作为相应的 I 2 C controller 结点的子结点即可,类似于前面的
之类的 spi_board_info 代码,目前不再需要出现,与 I 2 C 类似,现在只需要把 mtd_dataflash 之类的结点,作为 SPI 控制器的子结点即可,SPI host 驱动的 probe 函数透过 spi_register_master()注册 master 的时候,会自动展开依附于它的 slave。
4. 多个针对不同电路板的 machine,以及相关的 callback。
过去,ARM Linux 针对不同的电路板会建立由 MACHINE_START 和 MACHINE_END 包围起来的针对这个 machine 的一系列 callback,譬如:
这些不同的 machine 会有不同的 MACHINE ID,Uboot 在启动 Linux 内核时会将 MACHINE ID 存放在 r1 寄存器,Linux 启动时会匹配 Bootloader 传递的 MACHINE ID 和 MACHINE_START 声明的 MACHINE ID,然后执行相应 machine 的一系列初始化函数。
引入 Device Tree 之后,MACHINE_START 变更为 DT_MACHINE_START,其中 含有一个.dt_compat 成员,用于表明相关的 machine 与.dts 中 root 结点的 compatible 属性兼 容关系。如果 Bootloader 传递给内核的 Device Tree 中 root 结点的 compatible 属性出现在某 machine 的.dt_compat 表中,相关的 machine 就与对应的 Device Tree 匹配,从而引发这一 machine 的一系列初始化函数被执行。
Linux 倡导针对多个 SoC、多个电路板的通用 DT machine,即一个 DT machine 的.dt_compat 表含多个电路板.dts 文件的 root 结点 compatible 属性字符串。之后,如果的电 路板的初始化序列不一样,可以透过 int of_machine_is_compatible(const char *compat) API 判断具体的电路板是什么。
譬如 arch/arm/mach-exynos/mach-exynos5-dt.c 的 EXYNOS5_DT machine 同时兼容 "samsung,exynos5250"和"samsung,exynos5440":
它的.init_machine 成员函数就针对不同的 machine 进行了不同的分支处理:
使用 Device Tree 后,驱动需要与.dts 中描述的设备结点进行匹配,从而引发驱动的 probe()函数执行。对于 platform_driver 而言,需要添加一个 OF 匹配表,如前文的.dts 文件 的"acme,a1234-i2c-bus"兼容 I 2 C 控制器结点的 OF 匹配表可以是:
对于 I2C 和 SPI 从设备而言,同样也可以透过 of_match_table 添加匹配的.dts 中的 相关结点的 compatible 属性,如 sound/soc/codecs/wm8753.c 中的:
不过这边有一点需要提醒的是,I 2 C 和 SPI 外设驱动和 Device Tree 中设备结点的 compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为 ,,别名其实就是去掉 compatible 属性中逗号前的 manufacturer 前缀。 关于这一点,可查看 drivers/spi/spi.c 的源代码,函数 spi_match_device()暴露了更多的细节, 如果别名出现在设备 spi_driver 的 id_table 里面,或者别名与 spi_driver 的 name 字段相同, SPI 设备和驱动都可以匹配上:
4. 常用 OF&API
在 Linux 的 BSP 和驱动代码中,还经常会使用到 Linux 中一组 Device Tree 的 API, 这些 API 通常被冠以 of_前缀,它们的实现代码位于内核的 drivers/of 目录。这些常用的 API 包括:
int of_device_is_compatible(const struct device_node *device,const char *compat);
判断设备结点的 compatible 属性是否包含 compat 指定的字符串。当一个驱动支持 2 个或多个设备的时候,这些不同.dts 文件中设备的 compatible 属性都会进入驱动 OF 匹配 表。因此驱动可以透过 Bootloader 传递给内核的 Device Tree 中的真正结点的 compatible 属 性以确定究竟是哪一种设备,从而根据不同的设备类型进行不同的处理。如 drivers/pinctrl/pinctrl-sirf.c 即兼容于"sirf,prima2-pinctrl",又兼容于"sirf,prima2-pinctrl",在驱 动中就有相应分支处理:
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
根据 compatible 属性,获得设备结点。遍历 Device Tree 中所有的设备结点,看看 哪个结点的类型、compatible 属性与本函数的输入参数匹配,大多数情况下,from、type 为 NULL。
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz);
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz);
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);
读取设备结点 np 的属性名为 propname,类型为 8、16、32、64 位整型数组的属性。 对于 32 位处理器来讲,最常用的是 of_property_read_u32_array()。如在 arch/arm/mm/cachel2x0.c 中,透过如下语句读取 L2 cache 的"arm,data-latency"属性:
在 arch/arm/boot/dts/vexpress-v2p-ca9.dts 中,含有"arm,data-latency"属性的 L2 cache 结点如下:
有些情况下,整形属性的长度可能为 1,于是内核为了方便调用者,又在上述 API 的基础上封装出了更加简单的读单一整形属性的 API,它们为 int of_property_read_u8()、 of_property_read_u16()等,实现于 include/linux/of.h:
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
int of_property_read_string_index(struct device_node *np, const char *propname, int index, const char **output);
前者读取字符串属性,后者读取字符串数组属性中的第 index 个字符串。如 drivers/clk/clk.c 中的 of_clk_get_parent_name()透过 of_property_read_string_index()遍历 clkspec 结点的所有"clock-output-names"字符串数组属性。
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);
如果设备结点 np 含有 propname 属性,则返回 true,否则返回 false。一般用于检查 空属性是否存在。
void __iomem *of_iomap(struct device_node *node, int index);
通过设备结点直接进行设备内存区间的 ioremap(),index 是内存段的索引。若设备 结点的 reg 属性有多段,可通过 index 标示要 ioremap 的是哪一段,只有 1 段的情况,index 为 0。采用 Device Tree 后,大量的设备驱动通过 of_iomap()进行映射,而不再通过传统的 ioremap。 unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
透过 Device Tree 或者设备的中断号,实际上是从.dts 中的 interrupts 属性解析出中 断号。若设备使用了多个中断,index 指定中断的索引号。 还有一些 OF API,这里不一一列举,具体可参考 include/linux/of.h 头文件。
5. 总结
ARM 社区一贯充斥的大量垃圾代码导致 Linus 盛怒,因此社区在 2011 年到 2012 年进 行了大量的工作。ARM Linux 开始围绕 Device Tree 展开,Device Tree 有自己的独立的语 法,它的源文件为.dts,编译后得到.dtb,Bootloader 在引导 Linux 内核的时候会将.dtb 地址 告知内核。之后内核会展开 Device Tree 并创建和注册相关的设备,因此 arch/arm/mach-xxx 和 arch/arm/plat-xxx 中大量的用于注册 platform、I 2 C、SPI 板级信息的代码被删除,而驱动 也以新的方式和.dts 中定义的设备结点进行匹配。
ARM Linux 设备树详细介绍(2)共二篇《全篇结束》
标签:二篇,结点,const,Tree,machine,Linux,device,compatible,ARM From: https://blog.csdn.net/qq_42837317/article/details/139721340