zynq pl访问ps ddr
1. 背景
在 xilinx mpsoc 平台上进行 Linux 软件开发,不可避免的会涉及到 PS 与 PL 之间的数据交互。这个系列介绍一种基于 DDR 的信息交互方式。
这篇文章首先介绍下如何从系统中“偷”内存。
2. 交互框图
交互流程:
- PS 写入数据到 DDR 中,使用中断通知 PL,PL 从协商好的 DDR 中读取数据;
- PL 写入数据到 DDR 中,使用中断通知 PS,PS 从协商好的 DDR 中读取数据;
3. reserved memory
如果 PS 与 PL 要基于 DDR 进行交互,那么,在 PS 端必须将内存空间从系统中“拿”出来,让系统无法知晓或无法使用这个空间。然后,应用程序要想办法操作 DDR 的物理地址进行数据读写。 如何做呢?需要借助预留内存。实现预留内存的简单方法是在设备树中增加 reserved-memory 设备节点,在该节点中定义预留内存的起始地址及大小。
3.1 reserve 1
比如,我要使用一块内存空间作为 PS 与 PL 信息交互的空间,该空间以 0x30000000 为起始地址,大小为 256MB。那么可以在设备树中定义如下节点信息。
project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi:
/include/ "system-conf.dtsi"
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
buffer@0x30000000 {
no-map;
reg = <0 0x30000000 0 0x10000000>;
};
};
}
按照上述方式修改设备树并编译内核,系统启动后通过命令 cat /proc/iomem
即可查看系统的内存分配:
cat /proc/iomem
从下图中可以看到系统使用的内存空间被分为了两部分,0~0x2fffffff 和 0x40000000~0x7fefffff。中间丢失的部分 0x30000000~0x3fffffff 即为我们的预留内存。
3.2 reserve more
如果需要预留多块内存,可以添加多个节点。比如预留两块 256MB 的空间,起始地址分别是 0x30000000 和 0x50000000,如下:
/include/ "system-conf.dtsi"
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
res1:buffer@0x30000000 {
no-map;
reg = <0 0x30000000 0 0x10000000>;
};
res2:buffer@0x50000000 {
no-map;
reg = <0 0x50000000 0 0x10000000>;
};
};
};
从下图中可以看到,0x30000000~0x3fffffff 和 0x50000000~0x5fffffff 这两个地址空间系统无法使用。
4. 总结
基于 DDR 的 PS 与 PL 交互,PS 需要操作 DDR 的物理地址。此时就需要通过预留内存的手段让 Linux 无法使用进行交互的空间。那么,如何在用户空间中使用这块内存呢?答案是借助 UIO。
自己的路径:versions/2020.2/project-spec/meta-user/recipes-bsp/device-tree/files
PS 4G 内存,去掉保留以后,可用内存为3506280 KB = 3.5GB
下图就能看出,0x30000000 -0x4fffffff 这段512G空间被默认保留了。
5. 参考
https://xilinx-wiki.atlassian.net
设备树中保留内存的定义方式
以vexpress-v2p-ca9.dts
中保留内存的定义方式为例,说明dts文件中如何定义保留内存。
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
/* Chipselect 3 is physically at 0x4c000000 */
-
vram: vram@4c000000 {
-
/* 8 MB of designated video RAM */
-
compatible = "shared-dma-pool";
-
reg = <0x4c000000 0x00800000>;
-
no-map;
-
};
-
};
保留内存由根节点和1个或多个子结点组成。
根节点包括如下信息:
- #address-cells、#size-cells
必须项,需要同dts根节点中相关属性保持一致。
-
/dts-v1/;
-
#include "vexpress-v2m.dtsi"
-
/ {
-
model = "V2P-CA9";
-
arm,hbi = <0x191>;
-
arm,vexpress,site = <0xf>;
-
compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
-
interrupt-parent = <&gic>;
-
#address-cells = <1>;
-
#size-cells = <1>;
-
...
-
- ranges
必须项,且定义为空
子结点包括如下信息:
- 空间大小
可以通过reg
或size
来指定保留内存空间大小,若二者同时存在,以reg
属性为准。通过size
的方式如下:
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
mfc_left: region_mfc_left {
-
compatible = "shared-dma-pool";
-
no-map;
-
size = <0x2400000>;
-
-
...
- alignment
可选项 - alloc-ranges
可选项,通常可以和size同时使用。
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
default-pool {
-
compatible = "shared-dma-pool";
-
size = <0x6000000>;
-
alloc-ranges = <0x40000000 0x10000000>;
...
-
- compatible
可能包括shared-dma-pool
或者shared-dma-pool
。
主要关注shared-dma-pool
,当驱动程序需要申请DMA空间时,可以从这里进行申请内存空间。 - no-map
该属性意为不会为这段内存创建地址映射,在使用之前,需要调用者通过ioremap
创建页表映射关系才可以正常访问。这个属性与reusable
是互斥的。 - no-map-fixup
保持内存映射。 - reusable
当驱动程序不使用这些内存的时候,OS可以使用这些内存。 - linux,cma-default
定义该段保留内存空间是默认的CMA内存池。
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
default-pool {
-
compatible = "shared-dma-pool";
-
size = <0x6000000>;
-
alloc-ranges = <0x40000000 0x10000000>;
-
reusable;
-
linux,cma-default;
-
-
};
-
};
4. 保留内存的使用
4.1 设备树编码
定义保留内存:
-
memory@60000000 {
-
device_type = "memory";
-
reg = <0x60000000 0x40000000>;
-
-
};
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
-
/* test reserve memory */
-
test_reserve: test_reserve@90000000 {
-
/* 1 MB reserve memory */
-
compatible = "shared-dma-pool";
-
reg = <0x90000000 0x00100000>;
-
no-map;
-
-
};
-
};
定义memory-region
,将保留内存指定给特定设备,如下:
-
driver-test@8000 {
-
/* compatible = "test_driver_0", "simple_bus"; */
-
compatible = "test_driver_0";
-
reg = <0x80008000 0x1000>;
-
interrupt-parent = <&gic>;
-
interrupts= <0 89 4>, <0 90 4444>;
-
interrupt-names = "first_irq", "second_irq";
-
clocks = <&oscclk2>;
-
clock-names = "apb_pclk";
-
memory-region = <&test_reserve>;
-
status = "okay";
-
-
simple_bus_test{
-
compatile = "simple_bus_test";
-
};
-
-
};
加载kernel后,保留内存相关的打印信息如下:
-
Reserved memory: created DMA memory pool at 0x90000000, size 1 MiB
-
OF: reserved mem: initialized node test_reserve@90000000, compatible id shared-dma-pool
参考链接:
Linux设备树学习(一)基本知识点 - solonj - 博客园
基于Xilinx Zynq SoC / MPSoC的系统的常见要求之一是为特殊用途预留内存。预留的内存区域需要从linux内核的使用区域中分离出来,仅给特定的驱动程序使用。
reserved-memory 架构包含了预留内存的功能。预留内存的功能又与内核中的 DMA-API 和 CMA 框架密切相关。
本文旨在展示和解释一些可用的用例,并且已经使用Petalinux构建工具进行了测试。由于本文中的修改仅涉及DTS文件定制和设备驱动程序中分配内存位置的改动,还是可以将其导出到Yocto或OSL工作流程中的。
预留内存给设备驱动
为了从系统地址空间预留内存,设备树须配置预留内存的节点。每个节点定义一个特定的内存空间,并且可以根据内核文档中关于可用于预留内存节点的说明配置不同的参数。然后就可以通过memory-region参数将预留的内存空间分配给特定的设备驱动程序使用。
用于64位Cortex-A53 MPSoC的system-top.dts文件中的设备树节点:
-
reserved-memory {
-
#address-cells = <2>;
-
#size-cells = <2>;
-
ranges;
-
reserved: buffer@0 {
-
no-map;
-
reg = <0x0 0x70000000 0x0 0x10000000>;
-
};
-
};
-
reserved-driver@0 {
-
compatible = "xlnx,reserved-memory";
-
memory-region = <&reserved>;
-
};
或32位Cortex-A9 Zynq上,最新的基于 Yocto的Petalinux 的自定义的类似的设备树节点:
-
/include/ "system-conf.dtsi"
-
/ {
-
reserved-memory {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges;
-
reserved: buffer@0x38000000 {
-
no-map;
-
reg = <0x38000000 0x08000000>;
-
};
-
};
-
reserved-driver@0 {
-
compatible = "xlnx,reserved-memory";
-
memory-region = <&reserved>;
-
};
-
};
在设备驱动程序中,可以通过解析设备树节点来处理内存区域的属性,并且一旦知道了物理地址和大小,就可以使用 memremap / ioremap 调用来映射内存区域。
下面的代码使用了预留的内存分配:
-
/* Get reserved memory region from Device-tree */
-
np = of_parse_phandle(dev->of_node, "memory-region", 0);
-
if (!np) {
-
dev_err(dev, "No %s specified\n", "memory-region");
-
goto error1;
-
}
-
rc = of_address_to_resource(np, 0, &r);
-
if (rc) {
-
dev_err(dev, "No memory address assigned to the region\n");
-
goto error1;
-
}
-
lp->paddr = r.start;
-
lp->vaddr = memremap(r.start, resource_size(&r), MEMREMAP_WB);
-
dev_info(dev, "Allocated reserved memory, vaddr: 0x%0llX, paddr: 0x%0llX\n", (u64)lp->vaddr, lp->paddr);
由于保留的内存区域已被内核排除,并标记为no-map,因此 iomem 信息(/ proc / iomem)显示系统RAM小于主板中的内存量。
-
root@plnx_aarch64:~# cat /proc/iomem
-
00000000-6fffffff : System RAM
-
00080000-00b37fff : Kernel code
-
011c9000-012b8fff : Kernel data
加载设备后,可以确认内存分配:
-
[ 126.191774] reserved-memory reserved-driver@0: Device Tree Probing
-
[ 126.198595] reserved-memory reserved-driver@0: Allocated reserved memory, vaddr: 0xFFFFFF8020000000, paddr: 0x7000
-
Linux Reserved Memory 预留内存【转】 - sky-heaven - 博客园
-
Linux设备树学习(一)基本知识点 - solonj - 博客园
-