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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容