设备树相关接口描述

更新时间:
2024-01-09
下载文档

设备树相关接口描述

SylixOS 设备树相关源码在“libsylixos/SylixOS/devtree/”目录下。设备树头文件 devtree.h 内定义了设备树类型的结构体,以及设备树相关接口。

使用内核提供的设备树接口,必须引用 SylixOS 提供的设备树接口文件,至少应该包括:

#define  __SYLIXOS_DEVTREE_DRV
#include "devtree/devtree.h"

系统相关接口

设备树初级初始化

SylixOS 在启动时会对设备树 .dtb 文件加载在内存中的内存块进行扫描,进行一系列的检查操作,包括所提供的设备树文件是否为有效的设备树文件、是否有错误的终止(溢出边界)、是否为 SylixOS 无法读取的版本,或者含有错误的结构和无法处理的结构等。检查无误后,SylixOS 会获取设备树根节点的一些信息。设备树的初级初始化调用如下接口:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT    API_DeviceTreeLowLevelInit(PVOID  pvDevtreeMem);

函数 API_DeviceTreeLowLevelInit 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pvDevtreeMem 为设备树在内存中加载的基地址。

内核启动参数设置

chosen 节点 是 SylixOS 设备树中一种特殊的节点。它并不是一个真实的设备,一般 .dts 或 .dtsi 文件中的 chosen 节点为空或者内容很少。 chosen 节点主要是为了 bootloader 向 SylixOS 内核传递数据,重点是 bootargs 参数。比如在之前章节介绍到的内核启动参数。翼辉提供的全志 T3 板卡的 BSP 中,设备树源文件的 chosen 节点内容如下:

/ {
    ...
    chosen {
        bootargs = "ncpus=4 kdlog=no kderror=no kfpu=no heapchk=yes hz=1000 hhz=1000 rfsmap=/:/dev/ram";
    };
...
};

设置内核的启动参数需要调用 API_DeviceTreeKernelStartParamGet 函数,以获得设备树定义的系统内核启动参数。其函数原型如下:

#include <SylixOS.h>
#include <devtree/devtree.h>
ssize_t  API_DeviceTreeKernelStartParamGet(PCHAR  pcParam, size_t  stLen);

函数 API_DeviceTreeKernelStartParamGet 原型分析:

  • 此函数成功返回获得的启动参数长度,失败返回错误号。
  • 参数 pcParam 是启动参数。
  • 参数 stLen 是设置的缓冲区长度。

设备树高级初始化

系统初始化完成后,SylixOS 会对设备树进行高级初始化。与前面介绍的初级初始化不同,除了再次进行一系列检查外,SylixOS 将设备树结构展开,把每个节点转换成相应的设备树节点结构体。只有设备树高级初始化结束后,设备树提供的众多解析接口才可以正常使用,其函数原型如下:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT    API_DeviceTreeHighLevelInit(PVOID  pvDevTreeMem);

函数 API_DeviceTreeHighLevelInit 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pvDevtreeMem 为设备树在内存中加载的基地址。

获得系统内存参数

之前章节介绍到,在初始化 VMM 系统时,需要用到在 bspmap.h 中定义的物理内存描述表和虚拟内存描述表。在设备树中,需要将上述描述表转化为设备树的描述结构。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeKernelVmmParamGet(PVOID                     pvDevtreeMem,
                                     PLW_MMU_PHYSICAL_DESC     pphyOriginDesc,
                                     size_t                    stPhyOriginDesc,
                                     PLW_MMU_PHYSICAL_DESC    *ppphydesc,
                                     PLW_MMU_VIRTUAL_DESC     *ppvirdes);

函数 API_DeviceTreeKernelVmmParamGet 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pvDevtreeMem 为设备树在内存中加载的基地址。
  • 参数 pphyOriginDesc 为原始的设备树物理内存描述。
  • 参数 stPhyOriginDesc 为原始的设备树物理内存字节。
  • 参数 ppphydesc 为转化后的物理描述结构。
  • 参数 ppvirdes 为转化后的虚拟描述结构。

根据别名查找设备 ID

设备树节点中的 aliases 节点是一种特殊的节点,它允许一个节点定义一个别名,从而便于访问节点。DTC 编译过后,别名被替换为绝对路径。

/ {
    aliases {
        serial0        = &uart0;
        serial1        = &uart1;
        twi0           = &twi0;
        twi1           = &twi1;
        ir0            = &ir0;
        ir1            = &ir1;
        spi0           = &spi0;
        spi1           = &spi1;
        global_timer0  = &soc_timer0;
    };
}

在设备树高级初始化时,会解析设备树生成 aliases 节点,实际上就是将 aliases 插入一个全局的链表中,该函数遍历该链表,通过别名获取到设备的 ID。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeAliasIdGet(PLW_DEVTREE_NODE  pdtnDev, CPCHAR  pcStem);

函数 API_DeviceTreeAliasIdGet 原型分析:

  • 此函数成功返回获得的设备 ID,失败返回错误码,即设备不存在。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcStem 为设备名称,即别名。

该函数使用到的结构体 PLW_DEVTREE_NODE 描述了设备树节点的属性,其详细描述如下:

typedef struct devtree_node {
    CPCHAR                        DTN_pcName;             /*  设备树节点名称         */
    CPCHAR                        DTN_pcType;
    UINT32                        DTN_uiHandle;           /*  设备树节点的句柄值      */
    CPCHAR                        DTN_pcFullName;
    PLW_DEVTREE_PROPERTY          DTN_pdtpproperties;     /*  设备树节点的属性        */
    PLW_DEVTREE_PROPERTY          DTN_pdtpdeadpros;
    struct devtree_node          *DTN_pdtnparent;         /*  父节点                */
    struct devtree_node          *DTN_pdtnchild;          /*  孩子节点              */
    struct devtree_node          *DTN_pdtnsibling;        /*  兄弟节点              */
    ULONG                         DTN_ulFlags;
    PVOID                         DTN_pvData;             /*  最后开辟 FullName 内存 */
} LW_DEVTREE_NODE;
typedef LW_DEVTREE_NODE       *PLW_DEVTREE_NODE;

部分成员描述如下:

  • DTN_pcName :设备树的节点名。
  • DTN_uiHandle :设备树节点的句柄。
  • DTN_pdtpproperties :设备树节点的属性,该结构体包含属性的名称、长度、属性值以及指向下一个属性的指针。
  • DTN_pdtnparen t:父节点同样也是一个设备树属性结构体。

设备树节点属性的结构体定义在“libsylixos/SylixOS/system/include/s_class.h”头文件中,其详细描述如下:

typedef struct devtree_prop {
    CPCHAR                         DTP_pcName;          /*  属性名称             */
    INT                            DTP_iLength;         /*  属性长度             */
    PVOID                          DTP_pvValue;         /*  属性值               */
    struct devtree_prop           *DTP_pdtpNext;        /*  指向下一个属性的指针    */
} LW_DEVTREE_PROPERTY;

每一个节点都有若干属性,每个属性都可以抽象出属性的名称、长度、属性值等。该结构体是设备树操作的重要依据。

匹配加载设备树节点

设备树 .dts 文件最终会在 SylixOS 中转换为描述硬件的资源,SylixOS 会转换含有“ compatible ”属性的节点的子节点,且“ compatible ”为“simple-bus”、“simple-mfd”或“isa”。

/{
    soc: soc@01c00000 {
        compatible = "simple-bus";
        ...
            uart0:uart@01c28000 {...};
            uart1:uart@01c28400 {...};
            ...
};
};

SylixOS 提供设备树外设节点加载初始化入口 API_DeviceTreeDefaultPopulate ,用于在设备驱动的初始化后调用,而该函数就是调用 API_DeviceTreeDevPopulate 函数,对应的匹配表就是包含上述“ compatible ”属性的属性值。该过程会遍历每个对应节点及其子节点,如果与设备链表匹配则创建设备。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeDevPopulate(PLW_DEVTREE_NODE       pdtnDev,
                               PLW_DEVTREE_TABLE      pdttMatch);

函数 API_DeviceTreeDevPopulate 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pdttMatch 为匹配表。

该函数使用结构体 PLW_ DEVTREE_TABLE 来提供匹配表,其详细描述如下:

typedef struct devtree_table_item {
    CHAR                          DTITEM_cName[32];
    CHAR                          DTITEM_cType[32];
    CHAR                          DTITEM_cCompatible[128];
    CPVOID                        DTITEM_pvData;
} LW_DEVTREE_TABLE_ITEM;
typedef LW_DEVTREE_TABLE_ITEM     LW_DEVTREE_TABLE;
typedef LW_DEVTREE_TABLE_ITEM    *PLW_DEVTREE_TABLE;

部分成员描述:

  • DTITEM_cCompatible :设备树所兼容的板级。
  • DTITEM_pvData :设备实例函数。

SylixOS 还提供了一个默认的设备树外设节点加载初始化入口,本质上是传入默认的匹配表。

设备与驱动匹配

SylixOS 调用 API_DeviceTreeDrvMatchDev 函数,会逐个对比匹配表的表项,检查设备树节点与匹配表项是否匹配,优先匹配“compatible”属性,找到最合适的项。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeDrvMatchDev(PLW_DEV_INSTANCE   pdevInstance,
                               PLW_DRV_INSTANCE   pdrvInstance);

函数 API_DeviceTreeDrvMatchDev 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdevInstance 为设备实例指针。
  • 参数 pdrvInstance 为驱动实例指针。

驱动实体结构体 PLW_DRV_INSTANCE 的具体描述如下:

typedef struct lw_drv_instance {
    CPCHAR                            DRVHD_pcName;
    struct lw_bus_type               *DRVHD_pbustype;
    PLW_DEVTREE_TABLE                 DRVHD_pMatchTable;
    LW_LIST_LINE                      DRVHD_lineBus;
    LW_LIST_LINE_HEADER               DRVHD_plineDevList;
    LW_OBJECT_HANDLE                  DRVHD_hDevListLock;
    PVOID                             DRVHD_pvPriData;
    /*
     *  以下为驱动操作函数
     */
    INT                         (*DRVHD_pfuncProbe   )(PLW_DEV_INSTANCE  pDev);
    INT                         (*DRVHD_pfuncRemove  )(PLW_DEV_INSTANCE  pDev);
    VOID                        (*DRVHD_pfuncShutdown)(PLW_DEV_INSTANCE  pDev);
    INT                         (*DRVHD_pfuncSuspend )(PLW_DEV_INSTANCE  pDev);
    INT                         (*DRVHD_pfuncResume  )(PLW_DEV_INSTANCE  pDev);
} LW_DRV_INSTANCE;
typedef LW_DRV_INSTANCE          *PLW_DRV_INSTANCE;

部分结构体成员描述如下:

  • DRVHD_pcName :驱动名称。
  • DRVHD_pbustype :驱动使用的总线。
  • DRVHD_pMatchTable :驱动匹配表。
  • DRVHD_lineBus :驱动所在总线链表的节点。
  • DRVHD_plineDevList :匹配的设备链表头。
  • DRVHD_hDevListLock :匹配的设备链表操作锁。
  • DRVHD_pvPriData :驱动的私有数据。

设备提前初始化接口

前文提到在设备树高级初始化时,会将设备树结构展开,而系统初始化完成之前,设备树中的很多接口无法使用。但对于中断控制器等系统关键外设,需要在系统完全初始化完之前就进行“提前”的初始化,则可以调用 API_DeviceTreeDevEarlyInit 接口实现。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeDevEarlyInit(PLW_DEVTREE_TABLE  pdttMatch);

函数 API_DeviceTreeDevEarlyInit 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdttMatch 为匹配表结构。

如在中断的初始化函数中调用该接口:

static LW_DEVTREE_TABLE  _G_armGicMatch[] = {
           LW_DEVTREE_MATCH_DECLARE("arm,cortex-a15-gic",  __armGicProbe),
           LW_DEVTREE_TABLE_END,
};
static INT  __armGicProbe (PLW_DEVTREE_NODE  pdtnode)
{
    ...
}
VOID  armGicInit (VOID)
{
    API_DeviceTreeDevEarlyInit(_G_armGicMatch);      /*  注册中断控制器实例  */
}

检查节点与匹配表项是否匹配

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeIsCompatible(PLW_DEVTREE_NODE      pdtnDev,
                                PCHAR                 pcCompat);

函数 API_DeviceTreeIsCompatible 原型分析:

  • 此函数成功返回匹配的权重值,权重值越大表示越匹配。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcCompat 为匹配表项的兼容属性。

设备树设备节点获取匹配表数据

#include <SylixOS.h>
#include <devtree/devtree.h>
PVOID  API_DeviceTreeDevGetMatchData(PLW_DEV_INSTANCE  pdevInstance);

函数 API_DeviceTreeDevGetMatchData 原型分析:

  • 此函数检查节点与匹配表是否匹配,若匹配则返回匹配表数据,不匹配则返回 LW_NULL。
  • 参数 pdevInstance 为设备实例指针。

属性读取接口

在介绍设备树基本数据结构时,具体分析了属性的形式,SylixOS 提供了不同的接口,用于读取不同的数据类型,方便开发人员在 BSP 开发过程中的灵活使用。下面具体介绍这些通用的属性读取接口。

查找属性节点

SylixOS 提供了通过指定属性名称获取属性节点的方法,在设备树中比较灵活,使用很广泛。例如在之前介绍的检查节点与匹配表项是否匹配时,函数在实现的过程中就是调用了查找属性节点接口,通过属性“compatible”,查找属性节点。

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_PROPERTY  API_DeviceTreePropertyFind(PLW_DEVTREE_NODE    pdtnDev,
                                                 CPCHAR              pcPropname,
                                                 INT                *piLen);

函数 API_DeviceTreePropertyFind 原型分析:

  • 此函数成功返回属性节点,失败返回 LW_NULL ;通过遍历节点的属性结构,找到与之相匹配的字符串。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为属性名称。
  • 参数 piLen 为获取的属性值长度。

获取属性值

通过指定节点的指定属性名,获取到属性值。该函数实际上还是调用查找属性节点的接口,返回设备树节点属性值成员。

#include <SylixOS.h>
#include <devtree/devtree.h>
PVOID  API_DeviceTreePropertyGet(PLW_DEVTREE_NODE        pdtnDev,
                                 CPCHAR                  pcName,
                                 INT                    *piLen);

函数 API_DeviceTreePropertyGet 原型分析:

  • 此函数成功返回属性值,失败返回 **LW_NULL **。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcName 为需要查找的属性名称。
  • 参数 piLen 为属性长度。

读取无符号 32 位整型类型的属性值

常用 API_DeviceTreePropertyU32Read 函数获取指定的属性值。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyU32Read(PLW_DEVTREE_NODE      pdtnDev,
                                   CPCHAR                pcPropname,
                                   UINT32               *puiOutValue);

函数 API_DeviceTreePropertyU32Read 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为属性名称。
  • 参数 puiOutValue 为读取到的数据。

与 API_DeviceTreePropertyU32Read 类似的包括 API_DeviceTreePropertyU32IndexRead 和 API_DeviceTreePropertyU32ArrayRead 接口,其用法类似,只是增加了其他判断操作。例如在设备树结构中有如下串口定义:

&uart {
    ...
    tx_buf   = <512>;//发送缓冲区大小
    status   = "okay";
};

若需获取设备树中设置的串口发送缓冲区大小,可在串口驱动中调用该函数:

static INT __SioProbe (PLW_DEV_INSTANCE  pdtDev)
{
    PLW_DEVTREE_NODE  pdevNode   = pdtDev->DEVHD_pdtnDev;
    size_t            stTxBufSize;
    INT               iRet;
    /*
     *  获取串口发送缓冲区大小,若获取失败,则默认 512
     */
    iRet = API_DeviceTreePropertyU32Read(pdevNode, "tx_buf", (UINT32*)&stTxBufSize);
    if (ERROR_NONE != iRet) {
        stTxBufSize = 512;
    }
}

此外,还可以用 API_DeviceTreePropertyU32IndexRead 来按顺序读取一个 U32 类型的属性值,需要传入指定的序号,用作属性值长度的最小有效值。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyU32IndexRead(PLW_DEVTREE_NODE    pdtnDev,
                                        CPCHAR              pcPropname,
                                        UINT32              uiIndex,
                                        UINT32             *puiOutValue);

函数 API_DeviceTreePropertyU32IndexRead 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找结点的属性名称。
  • 参数 uiIndex 为指定的序号。
  • 参数 puiOutValue 为读出的数据。

读取下一个无符号 32 位整型类型属性值

该函数用于按顺序读取 U32 类型的下一个属性值。常在一个链表中使用:

#include <SylixOS.h>
#include <devtree/devtree.h>
UINT32*  API_DeviceTreePropertyU32Next(PLW_DEVTREE_PROPERTY     pdtprop,
                                       UINT32                  *puiCur,
                                       UINT32                  *puiOut);

函数 API_DeviceTreePropertyU32Next 原型分析:

  • 此函数成功返回更新后的属性地址,失败返回 LW_NULL
  • 参数 pdtprop 为当前设备树属性节点。
  • 参数 puiCur 为当前属性地址。
  • 参数 puiOut 为读出的数据。

读取 BOOL 类型属性的值

该函数与获取指定属性值接口类似,都是先查找该值,再进行判断的操作。

#include <SylixOS.h>
#include <devtree/devtree.h>
BOOL  API_DeviceTreePropertyBoolRead(PLW_DEVTREE_NODE    pdtnDev, 
                                     CPCHAR              pcPropName);

函数 API_DeviceTreePropertyBoolRead 原型分析:

  • 此函数返回 LW_TRUELW_FALSE
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找节点的属性名称。

解析和获得字符串类型的属性值

设备树可以将某一节点的属性值设置为字符串类型的值,若需获取该字符串,需要调用 API_DeviceTreePropertyStringRead 函数对字符串进行解析。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyStringRead(PLW_DEVTREE_NODE        pdtnDev,
                                      CPCHAR                  pcPropName,
                                      CPCHAR                 *ppcOutString);

函数 API_DeviceTreePropertyStringRead 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找节点属性名称。
  • 参数 ppcOutString 为读取出的 string 属性。

函数 API_DeviceTreePropertyStringRead 会调用如下函数获取属性值节点,然后对返回的节点进行解析。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyStringHelperRead(PLW_DEVTREE_NODE      pdtnDev,
                                            CPCHAR                pcPropName,
                                            CPCHAR               *ppcOutStrs,
                                            size_t                stSize,
                                            INT                   iSkip,
                                            INT                  *piCount);

函数 API_DeviceTreePropertyStringHelperRead 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找节点属性名称。
  • 参数 ppcOutStrs 为输出的字符串指针数组。
  • 参数 stSize 为读取的数组元素数量。
  • 参数 iSkip 为开头跳过的字符串数量。
  • 参数 piCount 为获取 string 类型属性长度。

注意:
API_DeviceTreePropertyStringHelperRead 函数不应被直接调用,而是通过 API_DeviceTreePropertyStringRead 读取字符串类型属性值这样的接口来间接调用该接口。
API_DeviceTreePropertyStringHelperRead 可以用于获取指定序号的 string 类型的属性值,函数 API_DeviceTreePropertyStringIndexRead 实际上调用的就是此函数。同样也可以用于获取一个由多条 string 类型组成的的属性值中的 string 个数。例如在一个节点的“compatible”属性中,可能会有多个字符串,代表了它所能支持的板卡类型,用于获取字符串数量的函数 API_DeviceTreePropertyStringCount 便是调用了这个接口。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyStringCount(const PLW_DEVTREE_NODE       pdtnDev, 
                                       CPCHAR                       pcPropName);

函数 API_DeviceTreePropertyStringCount 原型分析:

  • 此函数成功返回属性字符串的数量,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找节点属性名称。

比较属性值与指定字符串

检查某一属性值与指定的字符串是否匹配,若匹配则将该属性值的属性序号返回,它提供了获取设备树属性值序号的手段。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePropertyStringMatch(PLW_DEVTREE_NODE   pdtnDev,
                                       CPCHAR             pcPropName,
                                       CPCHAR             pcString);

函数 API_DeviceTreePropertyStringMatch 原型分析:

  • 此函数成功返回该属性值的属性序号,失败返回 PX_ERROR
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPropname 为查找节点属性名称。
  • 参数 pcString 为指定的字符串。

检查设备树节点状态

通过节点偏移值,指定查看某个设备树节点是否处于“okay”状态。

#include <SylixOS.h>
#include <devtree/devtree.h>
BOOL  API_DeviceTreeNodeIsOkayByOffset(PVOID  pvDevTree, INT  iOffset);

函数 API_DeviceTreeNodeIsOkayByOffset 原型分析:

  • 此函数返回 LW_TRUE 表示状态为“okay”,返回 LW_FALSE 表示不为“okay”。
  • 参数 pvDevTree 为设备树在内存中加载的基地址。
  • 参数 iOffset 为节点偏移量。

也可以通过更直接的方式,通过设备树的节点来获取节点状态。

#include <SylixOS.h>
#include <devtree/devtree.h>
BOOL  API_DeviceTreeNodeIsOkay(PLW_DEVTREE_NODE  pdtnDev);

该函数的返回和上述的相同,参数为设备树节点。

为设备节命名

该函数用来给一个设备找到合适的名字,实则是通过设备树节点读取“compatible”属性值,然后根据读取的值来进行命名。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeModaliasGet(PLW_DEVTREE_NODE        pdtnDev,
                               PCHAR                   pcName,
                               INT                     iLen);

函数 API_DeviceTreeModaliasGet 原型分析:

  • 此函数找到返回 ERROR_NONE ,未找到返回错误码。
  • 参数 pdtnDev 为用于获取属性的设备树节点。
  • 参数 pcName 为存放属性名的内存指针。
  • 参数 iLen 为存放属性名的内存大小。

获取节点的固定属性 cell

设备树中使用“address-cells”和“size-cells”来表示分别用多少个 32 位数表示地址和大小。例如在一个 .dts 源文件中:

/{
#address-cells = <1>;
#size-cells = <1>;
memory {
        reg = <0x80000000 0x20000000>;
    };
};

函数 API_DeviceTreeNAddrCells 和 API_DeviceTreeNSizeCells 用于获取对应的属性值,两者用法相同,实现方法类似,这里选择其中一个介绍:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeNAddrCells(PLW_DEVTREE_NODE  pdtnDev);

函数 API_DeviceTreeNAddrCells 原型分析:

  • 此函数成功返回 address cell 的值,未读取到则返回默认值。
  • 参数 pdtnDev 为用于获取 address cell 值的设备树节点。

phandle 相关接口

phandle 属性是设备树中唯一的节点指定标识符,节点中的 phandle 属性值必须是唯一的。通过 DTC 工具,将 .dtb 文件反汇编为一个.dts 文件,可以观察到一部分的节点插入了 phandle 的属性,例如:

/ {
    ...
    spi0 {
        #clock-cells = <0x00>;
        compatible = "allwinner,sunxi-periph-clock";
        clock-output-names = "spi0";
        phandle = <0x40>;
    };
};

这类接口需要用到 phandle 迭代器结构体 LW_DEVTREE_PHANDLE_ITERATOR,该结构体包含了 phandle 的一些属性,它的具体描述如下:

typedef struct devtree_phandle_iterator {
    CPCHAR                    DTPHI_pcCellsName;
    INT                       DTPHI_iCellCount;
    PLW_DEVTREE_NODE          DTPHI_pdtnParent;
    const UINT32             *DTPHI_puiListEnd;
    const UINT32             *DTPHI_puiPhandleEnd;
    const UINT32             *DTPHI_puiCurrent;
    UINT32                    DTPHI_uiCurCount;
    UINT32                    DTPHI_uiPhandle;
    PLW_DEVTREE_NODE          DTPHI_pdtnDev;
} LW_DEVTREE_PHANDLE_ITERATOR;

初始化 phandle 迭代器

该函数清空 phandle 迭代器结构体:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePhandleIteratorInit (PLW_DEVTREE_PHANDLE_ITERATOR      pdtpiItor,
                                        const PLW_DEVTREE_NODE            pdtnDev,
                                        CPCHAR                            pcListName,
                                        CPCHAR                            pcCellsName,
                                        INT                               iCellCount);

函数 API_DeviceTreePhandleIteratorInit 原型分析:

  • 该函数清空 phandle 迭代器结构体,然后将 phandle 迭代器结构体中的部分成员赋值,成功返回 ERROR_NONE,失败返回错误号。
  • 参数 pdtpiItor 为需要初始化的迭代器。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcListName 为链表名称。
  • 参数 pcCellsName 为 cell 的名称。
  • 参数 iCellCount 为 cell 的数量。
    lib_bzero(pdtpiItor, sizeof(LW_DEVTREE_PHANDLE_ITERATOR));
    pdtpiItor->DTPHI_pcCellsName   = pcCellsName;
    pdtpiItor->DTPHI_iCellCount    = iCellCount;
    pdtpiItor->DTPHI_pdtnParent    = pdtnDev;
    pdtpiItor->DTPHI_puiListEnd    = puiList + iSize / sizeof(UINT32);
    pdtpiItor->DTPHI_puiPhandleEnd = puiList;
    pdtpiItor->DTPHI_puiCurrent    = puiList;

获取下一个迭代器

通过当前的迭代器的地址,可以获取到下一个迭代器。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePhandleIteratorNext (PLW_DEVTREE_PHANDLE_ITERATOR  pdtpiItor);
  • 该函数成功返回 ERROR_NONE ,失败返回错误号。

phandle 解析

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreePhandleParse (const PLW_DEVTREE_NODE  pdtnDev,
                                              CPCHAR                  pcPhandleName,
                                              INT                     iIndex);

函数 API_DeviceTreePhandleParse 原型分析:

  • 该函数返回设备树节点或 LW_NULL
  • 参数 pdtnDev 为设备树节点。
  • 参数 pcPhandleName 为 phandle 名称。
  • 参数 iIndex 为序号。

函数 API_DeviceTreePhandleParseWithArgs 与上述函数实现方法类似,区别在于调用函数传入参数不同。

查找相关接口

SylixOS 提供以下几种通用的查找设备树节点的接口,这些接口的应用也比较灵活,使用也较为广泛。

通过路径查找设备树节点

该接口支持对以下几种节点路径的查找,并获取“冒号”分隔符之后的字符:

  • 完整的路径名,例如:/soc@03000000/spi@05010000。
  • 别名,例如:spi0。
  • 别名 + 相对路径名。
#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreeFindNodeOptsByPath(CPCHAR    pcPath, 
                                                   CPCHAR   *ppcOpts);

函数 API_DeviceTreeFindNodeOptsByPath 原型分析:

  • 该函数查找成功返回设备树节点,失败返回 LW_NULL
  • 参数 pcPath 为有效的节点路径。
  • 参数 ppcOpts 为获取的分隔符之后的字符。

通过 phandle 查找设备树节点

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreeFindNodeByPhandle(UINT32  uiPhandle);

函数 API_DeviceTreeFindNodeByPhandle 原型分析:

  • 该函数查找成功返回设备树节点,失败返回 LW_NULL
  • 参数 uiPhandle 为设备树节点的 phandle。

通过一个节点查找下一个节点

函数 API_DeviceTreeFindAllNodes 可以通过一个起始节点,查找出其下面的一个节点。

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreeFindAllNodes(PLW_DEVTREE_NODE  pdtnPrev);

函数 API_DeviceTreeFindAllNodes 原型分析:

  • 该函数返回下一个设备树节点。
  • 参数 pdtnPrev 为设备树节点。

通过一个节点查找下一个孩子节点

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreeNextChildGet(const PLW_DEVTREE_NODE  pdtnDev,
                                             PLW_DEVTREE_NODE        pdtnPrev);

函数 API_DeviceTreeNextChildGet 原型分析:

  • 该函数需要传入当前节点和前一个节点,返回下一个孩子节点或 LW_NULL
  • 参数 pdtn Dev 为当前的父设备树节点。
  • 参数 pdtnPrev 为当前的孩子设备树节点。

地址相关接口

设备树文件中,地址属性相较于属性值为字符串等,在获取上稍显复杂一些。SylixOS 提供了获取地址相关的接口,开发时除了会使用这些接口外,还需要注意这些接口的实际含义。

地址属性转换为实际地址

该接口通常用于获取地址的资源之后,将其转换为实际的 64 位地址。其他获取地址相关的接口常常会调用到此处。

#include <SylixOS.h>
#include <devtree/devtree.h>
UINT64  API_DeviceTreeAddressTranslate(PLW_DEVTREE_NODE       pdtnDev, 
                                       const UINT32          *puiInAddr)

函数 API_DeviceTreeAddressTranslate 原型分析:

  • 该函数将传入的 32 位设备树地址属性基址,转换为的实际 64 位地址并返回。
  • 参数 pdtnDev 为设备树节点。
  • 参数 puiInAddr 为地址属性基址。

地址属性转换为资源变量

在设备树中,一个“memory”属性包含了不同的内存的基址和大小,为了能方便的使用这些地址,SylixOS 提供了一种方法将他们作为资源保存在结构体 LW_DEV_RESOURCE 中。 LW_DEV_RESOURCE 的详细描述如下:

typedef struct dev_resource {
    union {
        struct {
            addr_t                DEVRES_ulStart;      /*  IO、MEM 地址资源的起始地址  */
            addr_t                DEVRES_ulEnd;        /*  IO、MEM 地址资源的结束地址  */
        } iomem;
        struct {
            addr_t                DEVRES_ulStart;     /*  BUS 资源的起始地址          */
            addr_t                DEVRES_ulEnd;       /*  BUS 资源的结束地址          */
        } bus;
        struct {
            ULONG                 DEVRES_ulIrq;       /*  中断资源的中断号            */
            ULONG                 DEVRES_ulFlags;     /*  中断资源的中断类型          */
        } irq;
    };
    CPCHAR                        DEVRES_pcName;      /*  资源的名称                */
    ULONG                         DEVRES_ulFlags;     /*  资源的类型                */
    ULONG                         DEVRES_ulReserve[16];
} LW_DEV_RESOURCE;
typedef LW_DEV_RESOURCE          *PLW_DEV_RESOURCE;

函数 API_DeviceTreeResourceGet 首先获取到地址基址和地址范围,然后将获得到的地址进行转换,最后填充到结构体当中:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeResourceGet (PLW_DEVTREE_NODE     pdtnDev, 
                                INT                  iIndex,
                                PLW_DEV_RESOURCE     pdevresource);
  • 该函数成功返回 ERROR_NONE ,失败返回错误码。
  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为指定的 cells 序号。
  • 参数 pdevresource 为存储地址范围的资源变量。

获取地址范围

在 .dts 中,设备寄存器的地址范围表示在一个属性中,例如:

/{
    ...
    uart0: uart@28000000 {
        reg = <0x0 0x28000000 0x0 0x1000>;
    };
};

通过 API_DeviceTreeRegAddressGet 接口可以获取设备寄存器的地址范围:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT API_DeviceTreeRegAddressGet(PLW_DEVTREE_NODE      pdtnDev,
                                PLW_DEV_RESOURCE      pdevresource);
  • 该函数成功返回 ERROR_NONE ,失败返回错误码。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pdevresource 为存储地址范围的资源变量。

在串口驱动中调用该函数,示例如下:

static INT __SioProbe (PLW_DEV_INSTANCE  pdtDev)
{
    PLW_DEVTREE_NODE  pdevNode   = pdtDev->DEVHD_pdtnDev;
    LW_DEV_RESOURCE   devResource;
    ULONG             ulBase;
    API_DeviceTreeRegAddressGet(pdevNode, &devResource);    /*  获取控制器资源  */
    ulBase = devResource.iomem.DEVRES_ulStart;
}

获取地址属性

SylixOS 可以通过设备树节点直接获取基址和大小

#include <SylixOS.h>
#include <devtree/devtree.h>
const UINT32*  API_DeviceTreeAddressGet(PLW_DEVTREE_NODE      pdtnDev,
                                        INT                   iIndex,
                                        UINT64               *pullSize);

函数 API_DeviceTreeAddressGet 原型分析:

  • 该函数成功返回地址属性对应的地址,失败返回 LW_NULL
  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为指定的 cells 序号。
  • 参数 pullSize 为输出的资源大小。

映射资源地址

该函数获取设备树中的资源地址,并进行映射,因此这个函数本身就包含了两个过程:先获取设备树存储地址范围资源变量,然后将物理 IO 空间指定内存映射到逻辑空间。

#include <SylixOS.h>
#include <devtree/devtree.h>
PVOID  API_DeviceTreeAddressIoremap (PLW_DEVTREE_NODE  pdtnDev, INT  iIndex);

函数 API_DeviceTreeAddressIoremap 原型分析:

  • 该函数返回映射出的虚拟地址或者 LW_NULL
  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为指定的 cells 序号。

中断相关接口

和之前介绍的设备树结构中的地址范围转换不同,中断信号可以来自设备,也可以终止在设备。与设备树中自然表示的设备寻址不同,中断信号表示为独立于树的节点之间的链接。以下四个属性用来描述中断连接:

  • interrupt-controller:将该节点声明为接收中断信号的设备的空属性。
  • interrupt-cells:中断控制器节点的属性。它说明此中断控制器的中断说明符中有多少个单元格(类似于#address-cells 和#size-cells)。
  • interrupt-parent:设备节点的属性,其中包含到它所连接的中断控制器的 phandle。没有中断父属性的节点也可以从其父节点继承该属性。
  • interrupts:包含中断说明符列表的设备节点的属性,设备上的每个中断输出信号对应一个 interrupt 属性。

设备树中断相关接口的实现在“libsylixos/SylixOS/devtree/devtreeIrq.c”文件。在进行驱动开发时,这类接口常用于中断相关资源的获取和解析。

解析一条中断资源

解析出的中断资源参数保存在 PLW_DEVTREE_PHANDLE_ARGS 结构体中,它的具体描述如下:

#define MAX_PHANDLE_ARGS            16
typedef struct devtree_phandle_args {
    PLW_DEVTREE_NODE                  DTPH_pdtnDev;
    INT                               DTPH_iArgsCount;
    UINT32                            DTPH_uiArgs[MAX_PHANDLE_ARGS];
} LW_DEVTREE_PHANDLE_ARGS;
typedef LW_DEVTREE_PHANDLE_ARGS      *PLW_DEVTREE_PHANDLE_ARGS;

成员描述如下:

  • DTPH_pdtnDev 为设备树节点。
  • DTPH_iArgsCount 表示参数个数。
  • DTPH_uiArgs 表示一个参数列表。

函数 API_DeviceTreeIrqOneParse 获取设备树节点的“reg”属性,获取“interrupt-cells”的大小,读取“interrupts”属性,最后解析为中断资源:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeIrqOneParse(PLW_DEVTREE_NODE              pdtnDev,
                               INT                           iIndex,
                               PLW_DEVTREE_PHANDLE_ARGS      pdtpaOutIrq);

若解析失败,该函数返回错误号。

  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为中断资源序号。
  • 参数 pdtpaOutIrq 为解析出的中断资源参数。

此外,该函数由 API_DeviceTreeIrqCountGet 调用,直接通过设备树节点即可获取中断资源数。

查找设备树节点中断父节点

该接口会先尝试读取“interrupt-parent”属性的值,若读取失败,则获取当前节点的父节点,再根据 phandle 查找结点。

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_DEVTREE_NODE  API_DeviceTreeIrqFindParent(PLW_DEVTREE_NODE  pdtnChild);
  • 此函数返回中断父节点或 **LW_NULL **。

解析 Irq 属性

对中断属性进行解析,获取中断属性信息,并将中断信息保存在中断资源参数结构体中。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeIrqRawParse(const  UINT32                  *puiAddr, 
                               PLW_DEVTREE_PHANDLE_ARGS        pdtpaOutIrq);

函数 API_DeviceTreeIrqRawParse 分析:

  • 传入 Irq 属性的基地址,解析失败会返回错误号。
  • 参数 puiAddr Irq 属性的基地址。
  • 参数 pdtpaOutIrq 为存储 Irq 信息的参数。

转化中断资源为表项

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeIrqToResource(PLW_DEVTREE_NODE      pdtnDev,
                                 INT                   iIndex,
                                 PLW_DEV_RESOURCE      pdevresource);

该函数被调用时,会先通过设备树节点将中断资源序号转换为 SylixOS 的中断号,当资源指针有效时,获取中断名称的属性,然后记录中断号和中断名。

函数 API_DeviceTreeIrqToResource 分析:

  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为中断资源序号。
  • 参数 pdevresource 为资源表指针。

该函数转化 SylixOS 中断号时调用的接口 API_DeviceTreeIrqGet 如下:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeIrqGet(PLW_DEVTREE_NODE     pdtnDev, 
                          INT                  iIndex, 
                          ULONG               *pulVector);
  • 参数 pdtnDev 为设备树节点。
  • 参数 iIndex 为中断资源序号。
  • 参数 pulVector 为资源表指针。

SylixOS 还提供了 API_DeviceTreeIrqToResouceTable 函数,可以获取设备树节点的中断资源,并转换为资源表,同时返回已经转换的中断资源数量,实际上还是调用了 API_DeviceTreeIrqToResource。例如在串口驱动中,需要获取设备树中断相关资源:

&uart {
    ...
    interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
};

在串口驱动中调用该函数:

static INT __SioProbe (PLW_DEV_INSTANCE  pdtDev)
{
    PLW_DEVTREE_NODE  pdevNode   = pdtDev->DEVHD_pdtnDev;
    LW_DEV_RESOURCE   irqResource;
    INT               iIrqNum;
    SIO_CHAN          SioChan    = LW_NULL;
    /*
     *  获取中断资源、转换中断号
     */
    iIrqNum = API_DeviceTreeIrqCountGet(pdevNode);
    API_DeviceTreeIrqToResouceTable(pdevNode, &irqResource, iIrqNum);
    SioChan->ulVector  = irqResource.irq.DEVRES_ulIrq;
}

在获取 SylixOS 中断号后,便可以将中断号作为设置系统指定向量中断服务的参数,来使能中断。

Pinctrl 相关接口

在本章节开头介绍到,Pinctrl 是设备树新的驱动框架,这类接口的很多实现用到了内核“libsylixos/SylixOS/system/device/pinctrl”目录下的源码。在 SylixOS 设备树引脚控制相关操作需要用到引脚控制器设备结构体,下面是该结构体的详细描述:

typedef struct lw_pinctrl_dev {
    LW_LIST_LINE                     PCTLD_lineManage;  
    LW_LIST_LINE_HEADER              PCTLD_plineDescs;    
    struct lw_pinctrl               *PCTLD_ppinctrl;
    struct lw_pinctrl_desc          *PCTLD_ppinctldesc;
    PLW_DEVTREE_NODE                 PCTLD_pdtnDev;
    PVOID                            PCTLD_pvData;
    LW_LIST_LINE_HEADER              PCTLD_plineGpioRange;
} LW_PINCTRL_DEV;
typedef LW_PINCTRL_DEV          *PLW_PINCTRL_DEV;

该结构体的详细成员描述如下:

  • PCTLD_lineManage :引脚控制器链表。
  • PCTLD_plineDescs :引脚描述的集合。
  • PCTLD_ppinctrl :引脚节点链表。
  • PCTLD_ppinctldesc :引脚控制描述。
  • PCTLD_pdtnDev :引脚控制器的设备树节点。
  • PCTLD_pvData :私有数据。
  • PCTLD_plineGpioRange :GPIO 范围。

获取引脚控制器

一般来说,一个引脚只是分给一个设备的,所以该设备的驱动会请求这个 GPIO 。SylixOS 提供根据设备树获取引脚控制器的接口:

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_PINCTRL_DEV  API_DeviceTreePinCtrlDevGet(PLW_DEVTREE_NODE  pdtnDev);

函数 API_DeviceTreePinCtrlDevGet 分析:

该函数实际上调用 API_PinCtrlDevGetByDevtreeNode,两者用法相同,返回查找到的引脚控制器或 LW_NULL

解析引脚控制器设备

该接口将引脚控制对应的设备树解析成引脚映射结构,需要传入引脚控制结构体 PLW_PINCTRL:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePinCtrlMapsCreate(PLW_PINCTRL   ppinctrl);

函数 API_DeviceTreePinCtrlMapsCreate 分析:

该函数会按照序号查找引脚控制器的属性,即“pinctrl-n”,“n”为引脚序号,并获取 phandle,根据 phandle 来找到对应的设备树节点,最后解析该引脚控制配置。引脚控制结构体的详细描述如下:

typedef struct lw_pinctrl {
    LW_LIST_LINE                     PCTL_lineManage;
    LW_LIST_LINE_HEADER              PCTL_plineStates;
    LW_LIST_LINE_HEADER              PCTL_plinemaps
    struct lw_pinctrl_state         *PCTL_ppctlstate;
    PLW_DEVTREE_NODE                 PCTL_pdtnDev;
} LW_PINCTRL;
typedef LW_PINCTRL *PLW_PINCTRL;

其具体成员描述如此下:

  • PCTL_lineManage :引脚控制管理。
  • PCTL_plineStates :引脚状态链表。
  • PCTL_plinemaps :从设备树解析出的映射表。
  • PCTL_ppctlstate :当前的引脚状态。
  • PCTL_pdtnDev :引脚控制关联的设备树节点。

释放引脚控制器资源

#include <SylixOS.h>
#include <devtree/devtree.h>
VOID  API_DeviceTreePinCtrlMapsFree (PLW_PINCTRL  ppinctrl);

函数 API_DeviceTreePinCtrlMapsFree 分析:

该函数会遍历引脚控制的映射链表,注销一个引脚控制的引脚映射结构,将引脚映射结构从全局的引脚映射链表移除,再从引脚控制链表中移除,最后释放引脚映射内存。

时钟相关接口

获取时钟

SylixOS 提供两种方法获取设备树时钟结构,第一个是通过时钟名的方式来获取时钟结构:

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_CLOCK  API_DeviceTreeClockGetByName (PLW_DEVTREE_NODE    pdtnDev, 
                                         CPCHAR              pcName);

如果没有时钟名,该函数会默认匹配“clock-names”,经过一些判断操作后,会调用另一个获取时钟的函数 API_DeviceTreeClockGet:

#include <SylixOS.h>
#include <devtree/devtree.h>
PLW_CLOCK  API_DeviceTreeClockGet (PLW_DEVTREE_NODE      pdtnDev, 
                                   INT                   iIndex);

需要传入设备树节点和设备序号,返回获取的时钟或 LW_NULL

获取父时钟

该函数用于获取指定序号的父时钟名称:

#include <SylixOS.h>
#include <devtree/devtree.h>
CPCHAR  API_DeviceTreeParentClockNameGet (PLW_DEVTREE_NODE      pdtnDev, 
                                          INT                   iIndex);

需要传入设备树节点和指定的时钟序号,返回父时钟的名称或 LW_NULL

MDIO 相关接口

接下来几个章节介绍的是总线类设备树相关接口,他们的实现思路有许多相似之处,根据总线的特性,进行一些修改。

MDIO(Management Data Input/Output),管理数据输入输出)是一种简单的双线串行接口,它包含两个管脚:MDC 和 MDIO 。SylixOS 设备树 MDIO 相关操作需要用到 MDIO 设备结构体,下面是该结构体的详细描述:

typedef struct lw_mdio_device  {
    CHAR                         MDIODEV_cName[MDIO_NAME_SIZE];
    LW_DEV_INSTANCE              MDIODEV_devinstance;
    struct lw_mdio_adapter      *MDIODEV_pmdioadapter;
    PVOID                        MDIODEV_pvPriv;
    UINT                         MDIODEV_uiAddr;
    UINT                         MDIODEV_uiFlag;
    /*
     *  总线匹配函数
     */
    INT                          (*MDIODEV_pfuncBusMatch)(PLW_DEV_INSTANCE  pDev,
                                 PLW_DRV_INSTANCE  pDrv);
} MDIO_DEVICE;
typedef MDIO_DEVICE      *PMDIO_DEVICE;

该结构体的部分成员描述如下:

  • MDIODEV_cName :MDIO 设备名称。
  • MDIODEV_devinstance :驱动模型中的设备。
  • MDIODEV_pmdioadapter :使用的控制器。
  • MDIODEV_pvPriv :私有数据。
  • MDIODEV_uiAddr :设备总线地址。
  • MDIODEV_uiFlag :相关标志。

获取 MDIO 设备

该函数用于设备树中解析 MDIO 控制器下挂载的设备:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeMdioRegister (PMDIO_ADAPTER       pmdioadapter,
                                 PLW_DEVTREE_NODE    pdtnDev);

函数 API_DeviceTreeMdioRegister 分析:

该函数会先注册 MDIO 总线适配器,然后遍历设备树节点,查找“reg”地址属性,最后注册 MDIO 设备,成功则返回 ERROR_NONE

传入的 MDIO 控制器结构体 MDIO_ADAPTER 的具体描述如下:

typedef struct lw_mdio_adapter  {
    CPCHAR                      MDIOADPT_pcName;
    PVOID                       MDIOADPT_pvPriv;
    LW_OBJECT_HANDLE            MDIOADPT_hBusLock;
    struct lw_mdio_device      *MDIOADPT_DevMap[MDIO_MAX_ADDR];
    UINT                        MDIOADPT_uiIrqMap[MDIO_MAX_ADDR];
    /*
     *  以下为操作函数,具体功能见名称
     */
    INT    (*MDIOADPT_pfuncRead)(struct lw_mdio_adapter   *pmdioadapter,
                                 UINT                      uiAddr,
                                 UINT                      uiRegNum);
    INT    (*MDIOADPT_pfuncWrite)(struct lw_mdio_adapter  *pmdioadapter,
                                  UINT                     uiAddr,
                                  UINT                     uiRegNum,
                                  UINT16                   usValue);
    INT    (*MDIOADPT_pfuncReset)(struct lw_mdio_adapter  *pmdioadapter);
} MDIO_ADAPTER;
typedef MDIO_ADAPTER      *PMDIO_ADAPTER;

该结构体的部分成员描述如下:

  • MDIOADPT_pcName :MDIO 控制器名称。
  • MDIOADPT_pvPriv :私有数据。
  • MDIOADPT_hBusLock :操作锁。
  • MDIOADPT_DevMap :挂载的设备表。
  • MDIOADPT_uiIrqMap :MDIO 设备的中断表。

注册 MDIO 设备

获取 MDIO 设备时调用了注册 MDIO 设备的接口 API_DeviceTreeMdioDevRegister,该接口的函数原型如下:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeMdioDevRegister (PMDIO_ADAPTER          pmdioadapter,
                                    PLW_DEVTREE_NODE       pdtnDev,
                                    UINT                   uiAddr);

该函数通过设备树注册 MDIO 设备,成功返回 ERROR_NONE ,失败返回错误号。

  • 参数 pmdioadapter 为 MDIO 控制器指针。
  • 参数 pdtnDev 为 MDIO 控制器的设备树节点。
  • 参数 uiAddr 为 MDIO 设备地址。

查找 MOID 设备

该函数通过设备树节点查找 MDIO 设备:

#include <SylixOS.h>
#include <devtree/devtree.h>
PMDIO_DEVICE  API_DeviceTreeMdioDevFind (PLW_DEVTREE_NODE  pdtnDev);

函数 API_DeviceTreeMdioDevFind 分析:

调用该函数时,首先会在 MDIO 总线上查找设备,然后获取 MDIO 设备结构体,成功则返回查找到的 MDIO 设备指针,失败返回 LW_NULL

I2C 相关接口

BSP I2C 开发时,需要从设备树中解析 I2C 控制器下挂载的设备:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeI2cAdapterRegister (PLW_DT_I2C_ADAPTER    pdti2cadapter,
                                       PLW_DEVTREE_NODE      pdtnDev,
                                       CPCHAR                pcName);

该函数会先注册 I2C 总线适配器,然后遍历该设备树节点的子节点,注册 I2C 设备,成功返回 ERROR_NONE , 失败返回错误号。

下面是 I2C 控制器结构体的具体描述:

typedef struct lw_dt_i2c_adapter 
{
    PLW_I2C_ADAPTER                 DTI2CADPT_pi2cadapter;
    CPCHAR                          DTI2CADPT_pcName;
    PLW_DEV_INSTANCE                DTI2CADPT_pdevinstance;
    LW_OBJECT_HANDLE                DTI2CADPT_hBusLock;
    ULONG                           DTI2CADPT_ulTimeout;
    INT                             DTI2CADPT_iRetry;
    PVOID                           DTI2CADPT_pvPriv;
    struct lw_dt_i2c_funcs         *DTI2CADPT_pi2cfuncs;
} LW_DT_I2C_ADAPTER;
typedef LW_DT_I2C_ADAPTER    *PLW_DT_I2C_ADAPTER;

该结构体的详细成员描述如下:

  • DTI2CADPT_pi2cadapter :适配器指针。
  • DTI2CADPT_pcName :I2C 控制器名称。
  • DTI2CADPT_pdevinstance :驱动模型中的设备。
  • DTI2CADPT_hBusLock :总线操作锁。
  • DTI2CADPT_ulTimeout :操作超时时间。
  • DTI2CADPT_iRetry :重试次数。
  • DTI2CADPT_pvPriv :私有数据。
  • DTI2CADPT_pi2cfuncs :总线适配器操作函数。

该函数注册 I2C 设备时调用 API_DeviceTreeI2cDevRegister 函数:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeI2cDevRegister (PLW_DT_I2C_ADAPTER      pdti2cadapter,
                                   PLW_DEVTREE_NODE        pdtnDev);

在 BSP 的 I2C 驱动开发中,一个 I2C 通道 I2cChan 会包含一个 I2C 适配器 i2cadapter ,并会在最后将该结构体作为参数传入 API_DeviceTreeI2cAdapterRegister 完成 I2C 设备的注册。

SPI 相关接口

SPI 相关接口的思想与上述的 I2C 相同,需要从设备树中解析并注册 SPI 控制器下挂载的设备:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeSpiCtrlRegister(PLW_DT_SPI_CTRL        pdtspictrl,
                                   PLW_DEVTREE_NODE       pdtnDev,
                                   CPCHAR                 pcName);

该函数会先注册 SPI 控制器,然后遍历该设备树节点的子节点,注册 SPI 设备,成功返回 ERROR_NONE ,失败返回错误号。

  • 参数 pdtspictrl 为 SPI 控制器指针。
  • 参数 pdtnDev 为 SPI 设备节点。

下面是 SPI 总线控制器结构体的具体描述:

typedef struct lw_dt_spi_ctrl {
    PLW_SPI_ADAPTER          DTSPICTRL_pspiadapter;
    PLW_DEV_INSTANCE         DTSPICTRL_pdevinstance;
    LW_OBJECT_HANDLE         DTSPICTRL_hBusLock;
    UINT16                   DTSPICTRL_usChipSelNums;
    UINT16                   DTSPICTRL_usDmaAlignment;
    UINT32                   DTSPICTRL_uiXferSizeMax;
    UINT32                   DTSPICTRL_uiSpeedMax;
    UINT32                   DTSPICTRL_uiSpeedMin;
    UINT32                   DTSPICTRL_uiMode;
    UINT16                   DTSPICTRL_usFlag;
    UINT32                  *DTSPICTRL_puiChipSelGpios;

#define LW_SPI_HALF_DUPLEX       BIT(0)
#define LW_SPI_NO_RX             BIT(1)
#define LW_SPI_NO_TX             BIT(2)
#define LW_SPI_MUST_RX           BIT(3)
#define LW_SPI_MUST_TX           BIT(4)
#define LW_SPI_GPIO_SS           BIT(5)

    PVOID                    DTSPICTRL_pvPriv;                          
    /*
     *  SPI 控制器基本操作函数
     */
    INT    (*DTSPICTRL_pfuncSetup)(struct   lw_dt_spi_dev         *pdtspidev);
    INT    (*DTSPICTRL_pfuncXferOne)(struct lw_dt_spi_dev         *pdtspidev,
                                     PLW_DT_SPI_XFER               pdtspixfer);
    VOID    (*DTSPICTRL_pfuncSetCs)(struct lw_dt_spi_dev          *pdtspidev,
                                    BOOL                           bEnable);
    INT    (*DTSPICTRL_pfuncPrepareXfer)(struct lw_dt_spi_dev     *pdtspidev);
    INT    (*DTSPICTRL_pfuncUnprepareXfer)(struct lw_dt_spi_dev   *pdtspidev);
    /*
     *  SPI 控制器其他操作函数
     */
    INT    (*DTSPICTRL_pfuncPrepareHw)(struct lw_dt_spi_ctrl         *pdtspictrl);
    INT    (*DTSPICTRL_pfuncUnprepareHw)(struct lw_dt_spi_ctrl       *pdtspictrl);
    INT     (*DTSPICTRL_pfuncPrepareMsg)(struct lw_dt_spi_ctrl       *pdtspictrl,
                                         struct lw_dt_spi_msg        *pdtspimsg);
    INT     (*DTSPICTRL_pfuncUnprepareMsg)(struct lw_dt_spi_ctrl     *pdtspictrl,
                                           struct lw_dt_spi_msg      *pdtspimsg);
    INT    (*DTSPICTRL_pfuncXferOneMsg)(struct lw_dt_spi_dev         *pdtspidev,
                                        struct lw_dt_spi_msg         *pdtspimsg);
} LW_DT_SPI_CTRL;
typedef LW_DT_SPI_CTRL     *PLW_DT_SPI_CTRL;

部分成员描述如下:

  • DTSPICTRL_pdevinstance :驱动模型中的设备。
  • DTSPICTRL_hBusLock :总线操作锁。
  • DTSPICTRL_usChipSelNums :片选信号数量。
  • DTSPICTRL_usDmaAlignmentDMA :对齐方式。
  • DTSPICTRL_uiXferSizeMax :控制器一次最多传输的字节数。
  • DTSPICTRL_uiSpeedMaxSPI :总线最高工作速率。
  • DTSPICTRL_uiSpeedMinSPI :总线最低工作速率。
  • DTSPICTRL_uiMode :工作模式。
  • DTSPICTRL_usFlag :其他功能标志。
  • DTSPICTRL_puiChipSelGpios :片选 GPIO。

该函数注册 SPI 设备时调用 API_DeviceTreeSpiDevRegister 函数:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreeSpiDevRegister (PLW_DT_SPI_CTRL        pdtspictrl,
                                   PLW_DEVTREE_NODE       pdtnDev);
  • 参数 pdtspictrl 为 SPI 控制器指针。
  • 参数 pdtnDev 为 SPI 设备节点。

PCI 相关接口

每个 PCI 总线段都有唯一编号,并且总线编号通过使用“bus-range”包含两个单元格的属性。第一个单元格给出分配给该节点的总线编号,第二个单元格给出任何从属 PCI 总线的最大总线编号。

PCI 地址空间与 CPU 地址空间完全分离,因此从 PCI 地址到 CPU 地址需要进行地址转换。所以要使用到“range”、“#address-cells”、“#size-cells”。

获取总线范围

该函数会去读取“bus-range”属性,将获得的地址范围转换为存储地址范围的资源变量。

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePciBusRangeParse(PLW_DEVTREE_NODE  pdtnDev, 
                                    PLW_DEV_RESOURCE  pdevresource);

函数 API_DeviceTreePciBusRangeParse 分析:

  • 该函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pdtnDev 为设备树节点。
  • 参数 pdevresource 为转换后的存储地址范围的资源变量。

获取桥片资源

该函数用于获取 PCI 桥片上的资源

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePciHostBridgeResourcesGet (PLW_DEVTREE_NODE         pdtnDev,
                                              UINT8                    ucBusNo,
                                              UINT8                    ucBusMax,
                                              LW_LIST_LINE_HEADER     *pplineheadResource,
                                              UINT64                  *pullIoBase);

函数 API_DeviceTreePciHostBridgeResourcesGet 分析:

  • 该函数成功返回 ERROR_NONE ,失败返回错误号;
  • 参数 pdtnDev 为指定的设备树节点。
  • 参数 ucBusNo 为起始 BUS 序号。
  • 参数 ucBusMax 为结束 BUS 序号。
  • 参数 pplineheadResource 为存储资源的链表头。
  • 参数 pullIoBase 为 IO 基地址。

PCI 资源解析

该函数用于 PCI 设备树节点获取 PCI、IO、MEM、BUS 范围:

#include <SylixOS.h>
#include <devtree/devtree.h>
INT  API_DeviceTreePciRangesParse(PLW_DEVTREE_NODE             pdtnDev,
                                  LW_LIST_LINE_HEADER         *pplineheadResource,
                                  PLW_DEV_RESOURCE            *pdevresBusRange);

函数 API_DeviceTreePciRangesParse 分析:

  • 该函数成功返回 ERROR_NONE ,失败返回错误号;
  • 参数 pdtnDev 为指定的设备树节点。
  • 参数 pplineheadResource 为存储地址范围的链表头。
  • 参数 pdevresBusRange 为 BUS 范围。

例如,在 .dts 中有如下的一段,其中“ranges”的第一个参数可以把它理解为序号,在获取地址时会按照序号顺序读取:

pcie: pcie0_port0@40100000 {
    ...    
    #address-cells = <3>;
    #size-cells = <2>;
    bus-range = <0x1 0x3>;
    ranges = <0x02000000 0x0 0x40000000 0x40000000 0x0 0x20000000       //mem
               0x01000000 0x0 0x18000000 0x18000000 0x0 0x02000000>;    //io
};

在 BSP 的 PCI 驱动中,对 PCI 资源进行解析获取:

static INT  __PciBusProbe (PLW_DEV_INSTANCE  pdtDev)
{
    INT                         iRet;
    PLW_DEVTREE_NODE            pdevNode  = pdtDev->DEVHD_pdtnDev;
    LW_LIST_LINE_HEADER         plineheadResource;
    PLW_DEV_RESOURCE            pdevres;
    /*
     * 获取PCI 资源
     */
    iRet = API_DeviceTreePciRangesParse(pdevNode, &plineheadResource, &pdevres);
    if (iRet) {
        return  (PX_ERROR);
    }
}
文档内容是否对您有所帮助?
有帮助
没帮助