外设分区管理¶
总体介绍¶
受益于硬件技术的快速发展,先进封装技术带来了更高的集成度,一个片上系统(SoC)中能够封装各种丰富的外设组件,为部署混合关键性系统提供了坚实的物理基础,但同时也带来了一个挑战:Linux、RTOS或其他操作系统在运行时有各自的资源访问需求,需要能够直接访问 SoC 的不同部分,并排运行并且不会相互干扰。针对该场景,用户可以使用外设分区管理配置各OS需要的外设资源。
通过外设分区管理,能够在集成构建的过程中,根据用户的配置文件,对完整的设备树进行解析和操作,最终生成针对不同OS的设备树。
本章主要介绍如何基于yocto-meta-openeuler使用外设分区管理,以及如何编写自定义的配置文件。
如何使用外设分区管理¶
在对应的BSP层添加
lopper-ops.bbappend
和lops
文件夹,用于承载用户的配置文件。例如:. └── meta-openeuler-bsp └── recipes-kernel └── lopper ├── lopper-ops.bbappend └── lops └── lop-user-config.dts
lopper-ops.bbappend
主要是将当前目录的lops文件夹传递给yocto,内容如下:$ cat lopper-ops.bbappend # Use the operation files from current layer FILESEXTRAPATHS:prepend := "${THISDIR}/:"
lops
用于存放用户自定义的配置文件,这些配置文件统一用device-tree格式描述,但是有单独的语法, 用于描述关于输入设备树的操作,包括:修改(删除)某个设备节点;添加自定义节点;提取部分节点并生成新的设备树等。 具体如何编写lop-user-config.dts
,请参考下一小节。在使用设备树文件的配方中(假设为
foo-A.bb
),调用 lopper-devicetree.bbclass ,并配置输入和输出,如:# foo-A.bb inherit lopper-devicetree INPUT_DT = "${B}/input-sample.dtb" OUTPUT_DT = "${B}/output-sample.dtb"
输入:input-sample.dtb
,完整的设备树。输出:output-sample.dtb
,处理后的设备树。此外,还会输出根据配置文件生成的RTOS侧设备树。即:┌──────► output-sample.dtb -- for Linux │ │ lop-user-config.dts │ input-sample.dtb ──────────────────────┤ │ │ │ └──────► serial-for-zephyr.dts -- for zephyr
默认会通过 lopper-devicetree 中的 do_mkdts 完成设备树的处理,这个任务默认的依赖关系为
before do_install after do_compile
,因此,用户可以在do_install
中使用处理后的设备树output-sample.dtb
。另外,用户可以通过配置文件生成RTOS侧的设备树,例如,指明提取某个 serial 节点,并生成
serial-for-zephyr.dts
,经过处理后,这份 dts 默认会被安装到foo-A
的 SYSROOT_DIR 中, 因此,在其它的配方中(foo-B.bb
)可以通过以下方法获取该 dts 文件:# foo-B.bb # add dependence DEPENDS += "foo-A" # get the serial-for-zephyr.dts do_configure:append() { cp ${WORKDIR}/recipe-sysroot/lop_dts/serial-for-zephyr.dts ${B}/ }
See also
yocto-meta-openeuler 中为树莓派添加了一个使用案例,将 serial@7e201a00
提取给zephyr使用,
参考提交 rpi4: extract a serial for zephyr。
如何编写外设分区配置文件¶
外设分区管理是基于开源工具 lopper 完成的,用户可以自定义配置文件(lopper operations),以实现设备树节点的修改、删除、提取等操作。 关于 lopper operations 详细完整的语法配置,可以阅读学习 lopper/README-architecture。 下面,会介绍一些较为常用的功能接口。
lopper operations 的格式:
lopper operations(lops) 的结构类似于标准的dts文件,设备树的根必须指定
compatible = "system-device-tree-v1,lop";
,以便识别为 lop 文件。 所有的操作都需要定义在lops/lop_<number>
中(number仅用于标识,lop的解析顺序是其出现在文件中的顺序)。/dts-v1/; / { compatible = "system-device-tree-v1"; lops { lop_<number> { compatible = "system-device-tree-v1,lop,<lop type>"; // compatible 注明 lop 的类型 <lop specific properties>; }; lop_<number> { compatible = "system-device-tree-v1,lop,<lop type>"; <lop specific properties>; }; }; };
不同类型的 lop 有各自的 compatible 和 properties,包括:
modify
用于修改指定路径的设备节点,包括修改property、添加property、删除或移动设备节点。
对应的 compatible 为:
"system-device-tree-v1,lop,modify"
对应的 property 格式为:
modify = "<path to node>:<property>:<replacement>"
具体用法可参考:
// 重命名 /cpus_r5 节点为 /cpus lop_1 { compatible = "system-device-tree-v1,lop,modify"; modify = "/cpus_r5/::/cpus/"; }; // 删除 /cpus_r5 节点 lop_2 { compatible = "system-device-tree-v1,lop,modify"; modify = "/cpus_r5/::"; }; // 移动 /cpus_r5 节点到 /soc/cpus_r5 lop_3 { compatible = "system-device-tree-v1,lop,modify"; modify = "/cpus_r5::/soc/cpus_r5"; }; // 将 memory@800000000 中的 reg 修改为 modify_val 中的 reg lop_4 { compatible = "system-device-tree-v1,lop,modify"; modify = "/memory@800000000:reg:&modify_val#reg"; modify_val { reg = <0x0 0x00000000 0x0 0x200000>; }; }; // 为 /soc/serial@7e201000 节点添加 my-prop = okay lop_5 { compatible = "system-device-tree-v1,lop,modify"; modify = "/soc/serial@7e201000:my-prop:okay"; }; // 修改 /soc/serial@7e201000 的 my-prop 为 disable lop_6 { compatible = "system-device-tree-v1,lop,modify"; modify = "/soc/serial@7e201000:my-prop:disable"; };
node add
添加节点,例如添加
mcs@70000000
到 /reserved-memory/ 中:lop_1 { compatible = "system-device-tree-v1,lop,add"; node_src = "mcs@70000000"; node_dest = "/reserved-memory/"; mcs@70000000 { reg = <0x00 0x70000000 0x00 0x10000000>; compatible = "mcs_mem"; no-map; }; };
output
将指定的 nodes 输出到 output 中(源文件不会被修改),output 可以指定为 dts 也可以指定为 dtb,而且 nodes 支持正则匹配。
// 将 "reserved-memory", "cpu1", "ipi1" 节点输出到 test.dtb lop_1 { compatible = "system-device-tree-v1,lop,output"; outfile = "test.dtb"; nodes = "reserved-memory", "cpu1", "ipi1"; }; // 所有的节点都输出到 test.dtb lop_2 { compatible = "system-device-tree-v1,lop,output"; outfile = "test.dtb"; // * is "all nodes" nodes = "*"; }; // 所有 testprop 为 testvalue 的 axi 节点都输出到 test.dts lop_3 { compatible = "system-device-tree-v1,lop,output"; outfile = "test.dts"; nodes = "axi.*:testprop:testvalue"; };
select
select lop 通常用于选择一个满足 select_* 的节点。可以同时有多条 select 语句,并且这些 select 是全局的,在 lop_1 定义的 select,可以沿用到 lop_2 中。
select 的语法跟 modify 操作的语法相同:
select_* = "<path to node>:<property>:<value>"
示例一:选择满足 select_1 或满足 select_2 的节点,类似于
if (node1 || node2)
:// for node1 select_1 = "/path/or/regex/to/nodes:prop:val"; // for node2 select_2 = "/path/or/2nd/node/regex:prop2:val2";
示例二:选择同时满足 select_1 和 select_2 的节点,类似于
if (node1.prop1.val1 && node1.prop2.val2)
:// for node1.prop1.val1 select_1 = "/path/or/regex/to/nodes:prop1:val1"; // for node1.prop2.val2 select_2 = ":prop2:val2";
由于 select 是全局的,因此用法十分灵活,也略微复杂,一些用法示例:
lop_1 { compatible = "system-device-tree-v1,lop,select-v1"; // 清除前面选择的节点 select_1; // 选择 compatible 为 ".*arm,cortex-a72.*",并且 cpu-idle-states 为 3 的 cpu 节点 select_2 = "/cpus/.*:compatible:.*arm,cortex-a72.*"; select_3 = ":cpu-idle-states:3"; }; lop_2 { // modify 的 path 是空,所以对之前 select 的节点进行 modify 操作 compatible = "system-device-tree-v1,lop,modify"; modify = ":testprop:testvalue"; }; lop_4 { compatible = "system-device-tree-v1,lop,select-v1"; // 清空之前的select select_1; // 选择 compatible 为 ".*arm,cortex-a72.*" 的 cpu 节点 // 或 phy-handle 为 0x9 的 axi 节点 select_2 = "/cpus/.*:compatible:.*arm,cortex-a72.*"; select_3 = "/axi/.*:phy-handle:0x9"; };
cond
可以在任意的 lop 中使用 cond 指定一个 select lop,并进行条件判断,若 select lop 为真,则 lop 会继续解析并执行;若 select lop 为假,则 lop 会被忽略。
lops { // 定义一个 select lop, // 判断 device-tree 的 compatible 是否为 "raspberrypi,4-model-b\0brcm,bcm2711" lop_1: lop_1 { compatible = "system-device-tree-v1,lop,select-v1"; select_1; select_2 = "/:compatible:raspberrypi,4-model-b\0brcm,bcm2711"; }; // 通过 cond 进行条件判断 // 若 select 为真,则进行 modify 操作,删除 /soc/serial@7e201a00/ 节点 lop_1_1: lop_1_1 { lop_1_1_1 { compatible = "system-device-tree-v1,lop,modify"; cond = <&lop_1>; modify = "/soc/serial@7e201a00/::"; }; }; };
tree
可以通过 tree 指定一个 node 集,并且对该 node 集进行 modify,output 等操作。
modify 等 lop,默认会对输入的设备树文件进行操作,如果指定了 tree 就会在对应的 node 集中进行操作,如:
// 创建一棵 tree(test-tree),包含 nodes 中指定的节点 lop_1 { compatible = "system-device-tree-v1,lop,tree"; tree = "test-tree"; nodes = "reserved-memory", "cpu1", "ipi1"; nodes = "reserved-memory", "zynqmp-rpu", "zynqmp_ipi1"; }; // 针对 test-tree 做 modify lop_2 { compatible = "system-device-tree-v1,lop,modify"; tree = "test-tree"; modify = "/reserved-memory:#size-cells:3"; }; // 针对 test-tree 进行 output 操作,输出 nodes 中指定的节点 lop_3 { compatible = "system-device-tree-v1,lop,output"; tree = "test-tree"; outfile = "output.dts"; nodes = "reserved-memory"; };
print
print lop 可以用于增加调试打印,任何以 print 开头的property在被处理时,都会输出到 stdout。
lop_1 { compatible = "system-device-tree-v1,lop,print-v1"; print = "print_test: print 1"; print2 = "print_test: print2"; };
Note
应用示例: rpi4: extract a serial for zephyr
除本文介绍的接口外,lopper 还支持一些其它的 lops,比如 conditional, exec, code 等,还支持通过 assists 加载自定义的 python 代码进行 dts 的解析处理。
更多用法探索,请阅读学习 lopper/README-architecture。