设备树相关接口描述
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_TRUE 或 LW_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);
}
}