知识杂货铺

不卖切糕

View on GitHub
5 July 2018 11:22

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.

根节点常用属性

总线的一些属性

中断控制

与中断控制有关的节点属性主要有三个:

将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       \\等价

参考

  1. Device Tree for Dummies - Bootlin
  2. Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
tags: Linux - dts