Linux设备树 - 设备兼容性与设备匹配
by 宋强
基本语法
兼容性匹配
设备树部分
设备树中的每一个设备节点都有一个compatible属性,通常形如:
.compatible = “<manufacture>, <model>”
比如ti, omap2-i2c,说明这个是ti生产设备omap2系列的i2c设备,可以使用这个系列通用的驱动。
compatible还可以提供多个字符串,匹配失败之后提供备选项。
设备驱动部分
驱动部分需要设定一个可以匹配的表之后使用MODULE_DEVICE_TABLE进行声明,这个表是一个of_device_id结构体数组,例如:
static const struct of_device_id wm8753_of_match[] = {
{ .compatible = "wlf,wm8753", },
{ }
};
MODULE_DEVICE_TABLE(of, wm8753_of_match);
然后在驱动的基类device_driver中有一个of_match_table属性,把这个表的指针赋给这个值就完成了匹配表的设定:
static struct spi_driver wm8753_spi_driver = {
.driver = {
.name = "wm8753",
.of_match_table = wm8753_of_match,
},
.probe = wm8753_spi_probe,
};
获取驱动中预置设备特定信息的结构体
使用of_match_device获取匹配的设备的结构体的指针,函数原型为:
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev);
第一个参数是id表指针,第二个参数是设备基类指针(多态)。
SPI和IIC中的特殊情况
通常情况下设备和驱动通过兼容性字符串匹配之后执行驱动的probe,但SPI和IIC比较特别,他们提供兼容性字符串无法匹配的情况下的另外两种匹配方法:
别名匹配
别名是把model部分去掉manufacture前缀得到的结果,如果别名出现在驱动的id_table中或者和驱动的name字段相同,都会匹配成功。
兼容性分辨
如果兼容性匹配成功并且执行了probe,这个时候probe中很多时候需要知道是哪种兼容性匹配成功了,有两种方式来进行分辨。
使用API检测是否匹配
内核提供了一个API用于获取这个信息:
int of_device_is_compatible(const struct device_node *device, const char *);
第一个参数是设备,第二个是字符串,匹配就返回真。
使用of_device_id的data属性
使用上一种方法的缺点在于需要大量的if else语句来对不同的设备进行处理,而内核提供了另一种方式来避免使用过多的if else。
of_device_id的data属性是一个void *指针,可以用于指向我们自定义的一个设备相关的结构体,之后在驱动的probe中使用
static inline struct device_node *of_find_matching_node(
struct device_node *from,
const struct of_device_id *matches);
const struct of_device_id *of_match_node(
const struct of_device_id *matches, const struct device_node *node);
第一个函数应该是从这个设备列表中找到第一个匹配的,第二个是通过这个获取data指针(第一个函数用法有疑问)。
具体用例可以参考 arch/arm/mm/cache-l2x0.c
遇见更加实际的例子之后进行补充。
设备信息传递
原来使用IORESOURCES_MEM和IORESOURCE_IRQ声明的资源现在直接使用设备树的reg和interrupts进行声明,例如:
interrupts = < 7 3 >;
reg = < 0 0x100 >;
指定多个中断号和一个内存段,或者使用
reg = < 0x402325 >;
来声明一个内存地址(例如一个ARM的寄存器),或者一个数字声明一个I2C总线设备地址。
platform_device绑定MEM和IRQ、I2C总线设备注册和SPI总线设备注册都有标准的设备树信息传递格式,另外也有传递自定义属性的方式。
驱动内部可能会提供标注的读取接口例如gpio按键驱动获取flag的接口:
of_get_gpio_flags();
一些常规的设备树信息的获取方式
struct clk *clk_get(struct device *dev, const char *id); //获取时钟的原型
platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取内存占用的方法
int platform_get_irq(struct platform_device *dev, unsigned int num); //获取中断号
struct dma_chan *dma_request_slave_channel(struct device *dev, const char * name); //获取DMA通道信息
通用的使用
const void *of_get_property(const struct device_node *node, const char *name, int *lenp); //最通用的
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_array(const struct device_node *np,
const char* propname, u64 *out_values, size_t sz);
内核为了方便调用者还提供了长度为1的API:
static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value);
static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value);
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value);
还有读取字符串的API:
int of_property_read_string(struct device_node *np, const char
*propname, const char **out_string); //Directly return a string.
int of_property_read_string_index(struct device_node *np, const char
*propname, int index, const char **out_string); //Return the specific string.
根节点常用属性
- cpu:描述每个cpu信息。
- memory:描述内存分布。
- chosen:设定一些硬件参数,可以用于传递kernel command line。
- alias:对某些节点的使用的别名。
- 其他总线和设备信息。
总线的一些属性
- compatible:描述总线控制器的类型,如果为简单的内存总线可以直接使用“simple-bus”。
- #address-cells:描述需要使用多少个cell来描述总线内存基址,总线地址可能不连续,就有多个起始描述地址。
- #size-cells:描述需要使用多少个cell大小来描述总线宽度(什么时候和address-cells的值不一样??)。
- ranges:通常用于描述到总线的内存地址转换,如果只定义ranges而不定义内容,说明内存地址不转换,只是“总线”概念上的转换。
中断控制
与中断控制有关的节点属性主要有三个:
- interrupt-controller:只需要声明,没有值,指示当前节点为中断控制器。
- #interrupt-cells:指示有多少空间用于描述这个中断控制器管理的中断号,为1的时候interrupts只包含一个中断号,为2的时候第二个的信息包含触发信息,例如上下沿,参考[2]。
- interrupt-parent:指向当前节点的中断控制器。
将DTS编译成DTB使用的DTC
即Source、Binary和Compiler,通常当前版本的scripts/dtc目录中会有DTC的源码并且在编译内核的时候被编译,其他系统也可以使用仓库的预编译版本,ubuntu为device-tree-compiler.
不支持设备树的bootloader
旧版的bootloader可能会不支持设备树,这个时候需要在内核中使能选项:CONFIG_ARM_APPEDED_DTB。
将DTB文件附加到内核镜像结尾需要直接使用
cat *.dtb >> zImage
cat zImage *.dtb > zImage \\等价