美文网首页
Linux 内核学习(6)---- Linux 设备树相关API

Linux 内核学习(6)---- Linux 设备树相关API

作者: 特立独行的佩奇 | 来源:发表于2023-04-16 21:11 被阅读0次

    platform_device 的生成

    在dts/dtsi 文件中添加相应的 node 之后,linux 设备树框架会将其解析为 platform_device 结构,编写设备驱动程序时,也会向platform_bus 注册 platform_driver ;根据 node 中 compatible 属性,匹配相应的驱动,最终调用到驱动的 probe 函数
    dts/dtsi 文件中添加相应的 node,对应于内核中的 struct device_node 结构体
    /linux-5.4.6/arch/arm64/boot/dts/arm juno-motherboard.dtsi

    apbregs@10000 {
        compatible = "syscon", "simple-mfd";
        reg = <0x010000 0x1000>;
    
        led0 {
            compatible = "register-bit-led";
            offset = <0x08>;
            mask = <0x01>;
            label = "vexpress:0";
            linux,default-trigger = "heartbeat";
            default-state = "on";
        };
        led1 {
            compatible = "register-bit-led";
            offset = <0x08>;
            mask = <0x02>;
            label = "vexpress:1";
            linux,default-trigger = "mmc0";
            default-state = "off";
        };
        led2 {
            compatible = "register-bit-led";
            offset = <0x08>;
            mask = <0x04>;
            label = "vexpress:2";
            linux,default-trigger = "cpu0";
            default-state = "off";
        };
        .......
        };
    };
    
    

    每一个大括号里面的内容被抽象为一个节点,在内核中对应的数据结构如下:
    include/linux/of.h

    struct device_node {
        const char *name; //节点名称
        phandle phandle;
        const char *full_name; //带路径的节点全名
        struct fwnode_handle fwnode;
    
        struct  property *properties;
        struct  property *deadprops;    /* removed properties */
        struct  device_node *parent; //父节点名称
        struct  device_node *child; //子节点
        struct  device_node *sibling; //兄弟节点
    #if defined(CONFIG_OF_KOBJ)
        struct  kobject kobj;
    #endif
        unsigned long _flags;
        void    *data;
    #if defined(CONFIG_SPARC)
        unsigned int unique_id;
        struct of_irq_controller *irq_trans;
    #endif
    };
    

    获取节点常用函数

    of_find 相关API

    功能:通过节点的compatible属性和type类型获取设备节点

    struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat);
    

    from:指定从哪里开始寻找设备节点,为NULL时从根节点开始寻找
    type:要寻找的设备节点类型,为NULL表示忽略type
    compatible:要寻找的设备节点compatible属性字符串
    结果:成功返回设备节点结构体,失败返回NULL

    功能:根据设备节点的名字和设备类型获取设备节点

    struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
    struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
    

    功能:根据设备节点的名字和设备类型获取设备节点
    from:指定从哪里开始寻找设备节点,为NULL时从根节点开始寻找
    name:要寻找的设备节点名字
    type:要寻找的设备节点类型
    成功返回设备节点结构体,失败返回NULL
    功能:通过路径全名获取设备节点

    struct device_node *of_find_node_by_path(const char *path)
    
    获取子节点和父节点API
    struct device_node *of_get_parent(const struct device_node *node);
    struct device_node *of_get_next_parent(struct device_node *node);
    

    功能:获取当前节点的父节点
    参数:需要查找的父节点的节点
    返回值:成功返回该节点的父节点,失败返回NULL

    struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
    struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev);
    

    功能:获取当前节点的父节点 of_get_next_child函数可以循环查找子节点
    参数:node:表示当前的节点 prev:前一个子节点,也就是从哪一个子节点开始寻找,为NULL则表示从第一个开始寻找
    返回值为找到的下一个子节点
    返回值:成功返回该节点的父节点,失败返回NULL

    提取设备数属性API

    设备数的每个node 可以包含多个属性,获取的属性用下面的结构体表示
    主要包含:名称,属性的长度,属性的值

    struct property {
        char    *name; //名称
        int length; //长度
        void    *value; //属性值
        struct property *next; //下一个属性
    #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
        unsigned long _flags;
    #endif
    #if defined(CONFIG_OF_PROMTREE)
        unsigned int unique_id;
    #endif
    #if defined(CONFIG_OF_KOBJ)
        struct bin_attribute attr;
    #endif
    };
    
    of find property
    struct property *of_find_property(const struct device_node *np,const char *name,int *lenp);
    

    of_find_property 可以查找指定的属性 np:设备节点 name:属性 lenp:属性长度
    比如使用下面代码:
    of_find_property(client->dev.of_node, "linux,gpio-keymap", &proplen)
    通过of_find_property函数获取设备中"linux,gpio-keymap"这个属性的值

    of_property_read by index
    int of_property_read_u32_index(const struct device_node *np, const char *propname,u32 index, u32 *out_value);
    int of_property_read_u64_index(const struct device_node *np,const char *propname,u32 index, u64 *out_value);
    

    功能:读取设备树中属性为32/64位无符号整形的值,可以指定标号读取哪几个
    参数:np:设备节点 propname:属性名字 index:标号,表示读第几个 out_value:读出来的值
    返回值:0 读取成功, -EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小

    of_property_read array
    static inline int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
    static inline int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz)
    static inline int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_values, size_t sz)
    static inline int of_property_read_u64_array(const struct device_node *np,const char *propname, u64 *out_values, size_t sz)
    

    功能:可以一次性读出多个无符号数据,比如地址信息
    np:设备节点 propname:属性名字 out_values:读出的值 sz:要读多少个数据
    下面的接口用于读取单个的值

    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)
    static inline int of_property_read_s32(const struct device_node *np,const char *propname, s32 *out_value)
    

    读取string 值的接口

    int of_property_read_string(const struct device_node *np, const char *propname,const char **out_string);
    

    component 框架下的 DTS 写法

    component 框架下为了启动顺序的需要,区分了master 和 component 设备,对应的parent 和 child 设备也有固定写法:


    dts_component.jpg

    linux-5.4.6/Documentation/devicetree/bindings/display

    / {
    
        dp0: display@c00000 {
            #address-cells = <1>;
            #size-cells = <0>;
            compatible = "arm,mali-d71";
            reg = <0xc00000 0x20000>;
            interrupts = <0 168 4>;
            clocks = <&dpu_aclk>;
            clock-names = "aclk";
            iommus = <&smmu 0>, <&smmu 1>, <&smmu 2>, <&smmu 3>,
                <&smmu 4>, <&smmu 5>, <&smmu 6>, <&smmu 7>,
                <&smmu 8>, <&smmu 9>;
    
            dp0_pipe0: pipeline@0 {
                clocks = <&fpgaosc2>;
                clock-names = "pxclk";
                reg = <0>;
    
                port@0 {
                    reg = <0>;
                    dp0_pipe0_out: endpoint@0 {
                        reg = <0>;
                        remote-endpoint = <&db_dvi0_in>;
                    };
                    
                    dp0_pipe0_out_ext: endpoint@1 {
                        reg = <1>;
                        remote-endpoint = <&db_dvi1_in>;
                    };      
                    
                };
                
                port@1 {
                    reg = <1>;
                }
            };
    
            dp0_pipe1: pipeline@1 {
                clocks = <&fpgaosc2>;
                clock-names = "pxclk";
                reg = <1>;
    
                port {
                    dp0_pipe1_out: endpoint {
                        remote-endpoint = <&db_dvi1_in>;
                    };
                };
            };
        };
        ...
    };
    

    以 arm 的 komeda dpu 为例,一个display 设备区分多个 pipeline,一个pipeline 下可能有两个port,可能其中一个是用于输出数据的流向 比如输出到MIPI_DSI,另一个是用于添加一级后处理器,用于画质的处理

    单个port 也可能有多个输出终端,比如同时输出到 HDMI 端口和 MIPI_DSI 端口,用 endpoint 区分,其中的 remote-endpoint 字段特别用于指向 endpoint 的 device tree node

    remote-endpoint 字段引用了其他的 device node,对应了 componet 设备
    display 表示显示控制器,对应了DRM 中的 master 设备

    设备树中使用的 API

    include/linux/of_graph.h

    struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id);
    struct device_node *of_graph_get_next_endpoint(const struct device_node *parent,
                        struct device_node *previous);
    struct device_node *of_graph_get_endpoint_by_regs(
            const struct device_node *parent, int port_reg, int reg);
    struct device_node *of_graph_get_remote_endpoint(
                        const struct device_node *node);
    struct device_node *of_graph_get_port_parent(struct device_node *node);
    struct device_node *of_graph_get_remote_port_parent(
                        const struct device_node *node);
    struct device_node *of_graph_get_remote_port(const struct device_node *node);
    struct device_node *of_graph_get_remote_node(const struct device_node *node,
                             u32 port, u32 endpoint);
    

    of_graph_get_remote_node 指定 port 和 endpoint 可以返回 remote-endpoint 设备的 device tree node

    相关文章

      网友评论

          本文标题:Linux 内核学习(6)---- Linux 设备树相关API

          本文链接:https://www.haomeiwen.com/subject/nykqjdtx.html