Reserved memory
To reserve a memory range from system address space, the reserved-memory node can be used in the device-tree configuration. Each child node defines a specific memory space and can be configured according the different parameters available for the reserved memory nodes as described in the kernel docs. The reserved memory spaces can then be assigned to a specific device driver through the memory-region parameter.
Device-tree nodes within system-top.dts file for a 64-bit Cortex-A53 MPSoC:
Code Block |
---|
|
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>;
}; |
Or similar device-tree nodes for customization in the more recent Yocto-based Petalinux
project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi for a 32-bit Cortex-A9 Zynq:
Code Block |
---|
|
/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>;
};
}; |
In the device driver, the properties of the memory region can be handled parsing the device tree nodes, and once the physical address and size are known the memory region can be mapped using memremap/ioremap calls. Below the code referred to the reserved memory allocation:
Code Block |
---|
|
/* 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); |
As the reserved memory region has been excluded for the common usage by the kernel and marked as no-map, the iomem information (/proc/iomem) shows that the System RAM is less than the amount of memory in the board.
Code Block |
---|
|
root@plnx_aarch64:~# cat /proc/iomem
00000000-6fffffff : System RAM
00080000-00b37fff : Kernel code
011c9000-012b8fff : Kernel data |
Once the device is loaded, the allocation can be confirmed:
Code Block |
---|
|
[ 126.191774] reserved-memory reserved-driver@0: Device Tree Probing
[ 126.198595] reserved-memory reserved-driver@0: Allocated reserved memory, vaddr: 0xFFFFFF8020000000, paddr: 0x70000000 |
Reserved memory through DMA API
Commonly the reserved memory spaces are being used with DMA engines, so integrating both frameworks can be useful from the device driver point of view. For that particular purspose the compatible property can be set as shared-dma-pool, generating a DMA memory pool reserved for a particular device driver.
Code Block |
---|
|
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
reserved: buffer@0 {
compatible = "shared-dma-pool";
no-map;
reg = <0x0 0x70000000 0x0 0x10000000>;
};
};
reserved-driver@0 {
compatible = "xlnx,reserved-memory";
memory-region = <&reserved>;
}; |
This way the device driver only need to use the DMA API in the regular way, but instead of using the default CMA memory pool it will use the reserved memory region for this particular device.
Code Block |
---|
|
/* Initialize reserved memory resources */
rc = of_reserved_mem_device_init(dev);
if(rc) {
dev_err(dev, "Could not get reserved memory\n");
goto error1;
}
/* Allocate memory */
dma_set_coherent_mask(dev, 0xFFFFFFFF);
lp->vaddr = dma_alloc_coherent(dev, ALLOC_SIZE, &lp->paddr, GFP_KERNEL);
dev_info(dev, "Allocated coherent memory, vaddr: 0x%0llX, paddr: 0x%0llX\n", (u64)lp->vaddr, lp->paddr); |
Kernel bootlog:
Code Block |
---|
|
[ 0.000000] Reserved memory: created DMA memory pool at 0x0000000070000000, size 256 MiB
[ 0.000000] Reserved memory: initialized node buffer@0, compatible id shared-dma-pool
[ 0.000000] cma: Reserved 128 MiB at 0x0000000068000000
|
Kernel log after loading the device driver :
Code Block |
---|
|
root@plnx_aarch64:~# insmod /lib/modules/4.6.0-xilinx/extra/reserved-memory.ko
[ 80.745166] reserved-memory reserved-driver@0: Device Tree Probing
[ 80.750183] reserved-memory reserved-driver@0: assigned reserved memory node buffer@0
[ 81.220878] reserved-memory reserved-driver@0: Allocated coherent memory, vaddr: 0xFFFFFF8020000000, paddr: 0x70000000
|
Reserved memory for CMA
Sometimes the reserved memory region does not require to be assigned to an specific device driver and is only
intendeed intended to have a bigger CMA memory pool than the default one. For that particular use case, an extra property can be used to point to the kernel to use the reserved memory region as the default CMA memory pool.
Code Block |
---|
|
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
reserved: buffer@0 {
compatible = "shared-dma-pool";
reusable;
reg = <0x0 0x70000000 0x0 0x10000000>;
linux,cma-default;
};
}; |
The kernel bootlog confirms the custom CMA memory pool allocation:
Code Block |
---|
|
[ 0.000000] Reserved memory: created CMA memory pool at 0x0000000070000000, size 256 MiB
[ 0.000000] Reserved memory: initialized node buffer@0, compatible id shared-dma-pool |
Petalinux example
These use cases can be tested using the Petalinux build tool following the steps above:
- Generate a petalinux project using a BSP package (ZC702 for Zynq-7000 or ZCU102 for Zynq MPSoC)
- Modify the dts file to include the reserved memory node
- Create a driver module and modify the default content of the driver
- Build the project and launch it on QEMU