Timeline
Timeline
2026-07-04
init
This article introduces the structure and function of the resource table in the remote processor firmware image, and elaborates in detail on how the main processor allocates system resources such as physical memory, configures Virtio devices, and creates RPMsg channels to achieve inter-core communication by decoding the resource table after loading the firmware. At the same time, combined with the reference code of STM32MP15C, this article summarizes the specific definition of resource entries under the Remoteproc framework and the implementation of dynamic resource management based on OpenAMP.
Reference code:
STM32CubeMP1 MPU Firmware Package
- STM32MP157C-EV1 RevC
- STM32MP157C-DK2 RevC
Remote processor firmware image
The firmware image of a remote processor generally includes:
- Resource table (resource_table)
- User application
- RTOS or Bare Metal (abbreviated as BM) related code
- OpenAMP library.
After the main processor loads the firmware of the remote processor into the kernel of the remote processor, it decodes the firmware image to obtain the associated resources and reserves memory for the firmware code segment and data. After starting the remote processor, the main processor creates an RPMsg channel, which is used for remote communication.

The resources of the remote processor include:
- System resources required by the remote processor before power-on, such as contiguous physical memory allocated to the remote processor, as well as peripheral devices allocated to the remote processor (these devices can be reserved, unused), these resources are configured in the
stm32mp157-m4-srm.dtsiresource manager of the device tree file (i.e., them4_system_resourcesnode). - In addition to system resources, the remote processor will have a resource table (
resource_table), and the resource table may also contain resource entries that publish the functions supported by the remote processor or existing configurations, such as the vring address and vring size in the Virtio device.
Only after meeting the requirements of all resources (M-core configuration requirements, the resource table will require Linux to allocate corresponding resources, etc.) will Remoteproc start the device.
Resource table
Under the kernel source codeinclude/linux/remoteproc.hfile, find the following structure resource_table, which is the firmware resource table header:
struct resource_table defined by Linux
1 | /** |
Following this firmware resource table header are the resource entries themselves, each entry starts with astruct fw_rsc_hdrheader, and the content of the entry itself will follow this header, parsed according to the resource type. The following is the beginning of the resource entry header:
struct fw_rsc_hdr
1 | /** |
The resource type is defined as follows
1 | /** |
The above values are used asrproc_handle_rsc()function (inremoteproc_internal.hIndex of the lookup table in the file).
When registering a new remote processor, the Remoteproc framework will look up its resource table and register the Virtio devices it supports. The firmware should provide Remoteproc information about the Virtio devices it supports and their configurations,RSC_VDEVResource entries should specify the Virtio device ID (such asvirtio_ids.h), Virtio features, Virtio configuration space, vrings information, etc. We can get the RSC_VDEV information by looking at the Linux filesystem/sys/kernel/debug/remoteproc/remoteproc0/resource_tablefile. (i.e., first find the relevant attribute through therproc_handle_rsc()function, and then go to theRSC_VDEVto find the specific configuration of that attribute.)
struct fw_rsc_carveout
1 | /** |
struct fw_rsc_devmem
1 | /** |
struct fw_rsc_trace
1 | /** |
struct fw_rsc_vdev_vring
1 | /** |
struct fw_rsc_vdev
1 | /** |
struct rproc_mem_entry
1 | /** |
Implementation of rsc_table for STM32MP15C
By usingSTMicroelectronics/STM32CubeMP1repository’sSTM32MP157C-EV1as an example:
STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Src/rsc_table.c
1 | /** |
AndSTM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Inc/rsc_table.hAs follows:
1 | /* |
attribute marker
1 | volatile struct shared_resource_table __resource __attribute__((used)) resource_table; |
__resource = __attribute__((section(".resource_table")))- puts the entire resource_table variable into the
.resource_tableELF section. - This is exactly the section name that the Linux side
rproc_elf_load_rsc_table()/rproc_elf_find_loaded_rsc_table()looks for—when the A7 loads the firmware ELF, it locates the resource table by section name.
- puts the entire resource_table variable into the
resource_table_init()Pass the address and length of this table to the OpenAMP middleware, which will be used later by the M4’s virtio/rpmsg stack.
struct shared_resource_table
STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Inc/rsc_table.h
1 | struct shared_resource_table { |
struct fw_rsc_vdev
STM32CubeMP1/Middlewares/Third_Party/OpenAMP/open-amp/lib/include/openamp/remoteproc.h
1 | /** |
ST’sfw_rsc_vdevfolds the type in
There are two points here:
- ST added type at the front of the
struct fw_rsc_vdev, while Linux’s struct fw_rsc_vdev does not have type (type is in a separatestruct fw_rsc_hdr). But the two are completely identical on-wire:
1 | ST: [vdev.type | vdev.id | ... | reserved] ← type 在 vdev 内 |
ST does this so that it can initialize the entire vdev entry (including type) at once using a designated initializer.
- C trick using zero-length vring[0] + separate vring0/vring1 members
fw_rsc_vring[0] at the end of vdev occupies 0 bytes, sostruct shared_resource_tableThe vring0 and vring1 members immediately following vdev in memory fall exactly after vdev—which is where Linux expects to find the vring array. This is equivalent to Linux’s vring[] flexible array member, but ST uses separate named members for easier initialization.
struct fw_rsc_vdev_vring
STM32CubeMP1/Middlewares/Third_Party/OpenAMP/open-amp/lib/include/openamp/remoteproc.h
1 | /** |
struct fw_rsc_trace
STM32CubeMP1/Middlewares/Third_Party/OpenAMP/open-amp/lib/include/openamp/remoteproc.h
1 | /** |
rsc_table instance
STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Src/rsc_table.c
1 |
|
vdev
Because this project#define LINUX_RPROC_MASTER(openamp_conf.h), it takes the static initialization branch:
1 | .vdev= { |
vring
1 | .vring0 = {VRING_TX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0}, |
| vring fields | vring0 (TX) | vring1 (RX) | Description |
|---|---|---|---|
| da | -1 (FW_RSC_ADDR_ANY) | -1 | Device address. If -1, it indicates support for dynamic allocation of the vring device address. |
| align | 16 | 16 | Alignment size. The number of alignment bytes between the Consumer and Producer sections of the vring. |
| num | 16 | 16 | Number of buffers. The number of buffers supported by this vring (must be a power of 2). |
| notifyid | 0 (master→remote) | 1 (remote→master) | Notification ID. A unique notification index across the entire remote processor (rproc). When kicking the remote end, this ID is used to let it know which vring was triggered. |
| reserved | 0 | 0 | Reserved field. Must be 0. |
Dynamic ResMgrcore:da = -1
- Static method: firmware hardcodes
vring.da = 0x10040000, which Linux uses directly. - Dynamic approach (
STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/): firmware writesda = -1, indicating “Linux allocates”, Linux from the DTvdev0vring0/1reservedcarveoutpool to allocate vring memory, and backfills the actual address tovring->da. The firmware declares the requirement, the host allocates and backfills—exactly what the fw_rsc_vdev comment refers to as “negotiation”.
openamp_conf.hThere are two branches:
1 |
cm_trace
1 |
|
Note:
- trace’s da is not
-1 - trace buffer is allocated by M4 itself (system_log_buf is in M4 memory), the host only reads and does not allocate. After the host starts, it will create
debugfs/.../cm4_log, what it reads is the log written by M4.
resource_table_init()
1 | void resource_table_init(int RPMsgRole, void **table_ptr, int *length) |
Preprocessor branches handle three scenarios:
| Scenario | Condition | Behavior |
|---|---|---|
| Linux as master | #ifdef LINUX_RPROC_MASTER | Skip the entire#if !defined(LINUX_RPROC_MASTER)block; the table is initialized at compile time, the function just returns:table_ptr = (void *)&resource_tablelength = sizeof(sizeof(resource_table)) |
| GCC + M4 as master | GNUC&&!LINUX_RPROC_MASTER&& VIRTIO_MASTER_ONLY | GCC does not initialize global variables with the section attribute at startup, somemsetFill field by field at runtime after zeroing |
| GCC + M4 as slave | GNUC&&!LINUX_RPROC_MASTER | while(vring1.da != VRING_RX_ADDRESS){}Spin wait for the master to fill the table |
This set#ifThe nesting is for OpenAMP to be compatible withLinux-master/M4-master/M4-slavewritten for three topologies. For the STM32MP157 (Linux master), the runtime section is not compiled; it actually just returns a pointer*length = sizeof(resource_table);and*table_ptr = (void *)&resource_table;。
Three types
#ifThe branch indicates who fills the table for this M4 code under the three topologies:resource_table: Linux loading (static), M4 master fills at runtime, M4 slave waits for filling.
Summary

Thisrsc_table.cis the “resource requirement list” submitted by the M4 firmware to Linux:
- declares a
virtio-rpmsgdevice (id=7, 2 vrings, name-service feature) - a trace buffer, and sets the vring address to -1 (
FW_RSC_ADDR_ANY) to let Linux dynamically allocate—this is exactlyDynamic ResMgrthe meaning of.
The entire table is placed in the.resource_tableELF segment, initialized at compile time,resource_table_init()and just hands the pointer over to the OpenAMP middleware.
Storage and system resource allocation
reserved-memory
Instm32mp157d-atk.dtsiThis code can be seen under the device tree:
1 | reserved-memory { |
reserved-memoryindicates that the memory allocated under this node is all reserved memory,Reserved memory areas are generally used by specific drivers. They differ from the memory areas used by the Linux kernel; typically, reserved memory functionality is closely related to the Linux kernel’s DMA or CMA.
If a node’s
compatibleattribute isshared-dma-pool, it means the memory area of this node is used as a shared pool of DMA buffers for a group of devices. In this case, if the node attribute containsno-map, it means this memory cannot be mapped by the Linux kernel as part of the system memory and must be separated from the system memory. If the node attribute containsreusableproperty, it means that this memory does not need to be separated from the system memory, and when a specific driver is not using this memory, the OS can use it.Note that a node cannot have both the no-map and reusable properties at the same time, because they are logically contradictory.
We see thatreserved-memoryeach child node under the node containsno-mapproperty, indicating that this memory cannot be used by the Linux kernel as system memory, and is actually reserved for the M4 system.
mcuram2
1 | mcuram2: mcuram2@10000000 { |
0x10000000 is the starting address of SRAM1, and the size of 0x40000 is exactly 256KB. This area is the SRAM1+SRAM2 region, which is mainly used to store the code and data segments of the M4 firmware: SRAM1 (code) and SRAM2 (data). This can be seen from the linker script of the M4 project. However, the address range can also be re-divided by modifying the linker script. But it should be noted that the address range in the linker script must be consistent with the range configured in the device tree.
vdev0vring0、vdev0vring1、vdev0buffer
1 | vdev0vring0: vdev0vring0@10040000 { |
vdev0vring0、vdev0vring1andvdev0bufferThe child node is exactly at SRAM3, which is the IPC buffer. The allocation of these three nodes is as follows:
vdev0vring0child node, 0x10040000 is the starting address of vring0, with an address length of 0x1000, which is 4KB. Similarly,vdev0vring1is the starting address of vring1, with an address length of 0x1000, which is also 4KB. These two nodes are the vrings we mentioned earlier for sending and receiving messages.vdev0bufferchild node, with a starting address of 0x10042000, an address length of 0x4000, and a size of 16KB. This address falls within SRAM3, which is the set shared memory area.
vdev0vring0、vdev0vring1andvdev0bufferonly occupies the first 24KB of SRAM3. SRAM3 has 64KB and is not fully used, so if necessary, the unused addresses of SRAM3 can also be used for other functions by modifying the device tree and linker script.
mcuram
1 | mcuram: mcuram@30000000 { |
mcuramThe starting address of the child node is 0x30000000, with an address length of 0x40000 and a size of 256KB. This address is the SRAM1 and SRAM2 area in RAM aliases. Because the physical addresses of RAM aliases and SRAMs are the same, the corresponding area “visible” to the A7 also needs to be configured. This area corresponds to the area “visible” to the M4mcuram2area. Because mcuramandmcuram2 have the same physical address, the functions of these two storage areas are the same. It can be said that mcuram is the alias storage area of mcuram2, which might be the origin of “aliases” in RAM aliases. It should be noted that in the device tree,mcuramandmcuram2the memory segment definitions must be consistent.
retram
1 | retram: retram@38000000 { |
retramThe starting address of the child node is 0x38000000, with an address length of 0x10000, which is 64KB. It belongs to the RETRAM area in RAM aliases. This area corresponds to the RETRAM area in the BOOT storage area, and they are the same physical address. RETRAM is used to store the interrupt vector table of the M4 core (the interrupt vector table starts from 0x00000000). By default, the starting address of RETRAM is 0x38000000, and it will be remapped to 0x00000000 to execute the M4 code.
Summary

| Child node | Address | Size | Area |
|---|---|---|---|
| mcuram2 | 0x10000000~0x10040000 | 256KB | M4-visible SRAM1+SRAM2 |
| vdev0vring0 | 0x10040000~0x10041000 | 4KB | SRAM3 |
| vdev0vring1 | 0x10041000~0x10042000 | 4KB | SRAM3 |
| vdev0buffer | 0x10042000~0x10046000 | 16KB | SRAM3 |
| mcuram | 0x30000000~0x30040000 | 256KB | A7-visible SRAM1+SRAM2 |
| retram | 0x38000000~0x38010000 | 64KB | A7-visible RETRAM |
For the convenience of unified address management, A7 and M4 may have different address mappings for the physical address of the same real chip. For example, A7 starts from 0X38000000, and this part is called mcuram, while in the eyes of M4, it starts from 0X10000000, and this part is called mcuram2. But in fact, these two addresses and the lengths they cover ultimately point to the same real storage chip.
m4_system_resources
stm32mp151.dtsiDevice tree file
1 | mlahb { |
In the above device tree nodes, there is am4_system_resourcesThe child node (which is the resource manager of M4), is used to configure the peripheral resources of M4, wherecompatiblein the propertyrproc-srm-corewill match the kernel source code’sdrivers/remoteproc/rproc_srm_core.cdriver file, as follows is therproc_srm_core.cpartial code of the file:
1 | static const struct of_device_id rproc_srm_core_match[] = { |
After the device and driver are successfully matched, the function represented by the probe member variable of platform_driverrproc_srm_core_probe()is executed, and through this function, the rproc sub-device is registered (rproc represents a physical remote processor device, which can be considered a peripheral)
In the kernel source code’sstm32mp157-m4-srm.dtsiDevice tree file, as follows,&m4_rprocindicates appending content under the previousm4_rprocnode:
1 | &m4_rproc { |
The above codeconfigures the system resources of M4, that is, which peripherals M4 is configured with, however,statusthe properties are alldisabled, which means that although the peripherals are configured, they are not enabled.
stm32mp157d-atk.dtsiThe file includesstm32mp157-m4-srm.dtsiFile
stm32mp157d-atk.dtsThe file further includesstm32mp157d-atk.dtsiFileTherefore, you can directly
stm32mp157d-atk.dtsiorstm32mp157d-atk.dtsselect and enable a certain peripheral of M4 in the device tree file.
Some peripherals of M4 and A7 are shared, for example, GPIO is a shared resource. If this GPIO is not multiplexed for other functions and is simply used as a regular IO, then both A7 and M4 can access these resources. For example, understm32mp157d-atk.dtsi, a buzzer and two LED nodes are configured, which use the GPIO function. These nodes are for A7, but M4 can also use them:
1 | leds { |
If it is a peripheral with a single-selection function, meaning these peripherals can only be used exclusively by A7 or exclusively by M4, if both A7 and M4 use the peripheral together, there will be a resource contention issue, and one side will experience an exception (the main processor has a certain priority, usually the coprocessor side experiences the exception). For example, if A7 and M4 both occupy ADC1 to collect data, the data collected by M4 will be inaccurate at this time, and it might fail to collect data and display 0.
For peripherals with a single choice:
- If A7 wants to use this peripheral, the corresponding peripheral node for A7 must be configured under the device tree.
- If this peripheral is to be used by M4, it is not necessary to configure the M4-related node under the device tree; you only need to configure the peripheral in the firmware (i.e., configure it in the bare-metal program). After A7 loads and starts the firmware, M4 can use this peripheral.
If the peripheral node corresponding to A7 has already been configured under the device tree, and M4 wants to use this peripheral, it is best to comment out the related node occupied by A7 under the device tree. For example, instm32mp157d-atk.dtsiThere are the following nodes under the device tree:
1 | adc1_in6_pins_b: adc1-in6 { |
The above code snippet indicates that ADC1 is assigned for A7 to use. If M4 wants to use it, this code does not need to be commented out, as long as it is ensured that A7 does not operate ADC1 after the Linux system is running. Then, after loading and running the M4 firmware (ADC1 has already been configured in the firmware), M4 can use ADC1 to collect data.
However, if A7 operates ADC1 at this time, the data collected by ADC1 on the M4 side will be inaccurate. Therefore, it is recommended to comment it out:
1 | /* |
That is, comment out the ADC1 part occupied by A7. The &m4_adc node part later is manually added; this section can be added or not, but according to ST’s standards, it is best to add it. The device treestm32mp157c-dk2-m4-examples.dtsis a template file. When modifying the device tree, you can refer to the template file. The above modified&m4_adcnode is written with reference to this file.
After modifying the device tree, execute the following command to recompile the device tree:
1 | make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs |
Then copy the compiledstm32mp157d-atk.dtbfile to the development board’s file system/bootdirectory, replace the previous device tree binary file, then execute the sync command to synchronize the cache, and then restart the development board. After re-entering the Linux operating system, A7 can no longer operate ADC1. After loading and starting the M4 firmware, M4 can access ADC1 independently.
Linker script
Linker script syntax
Entry address
1 | /* Entry Point */ |
ENTRY(SYMBOL), which means setting the value of the symbol SYMBOL to the entry address, i.e., the address of the first instruction executed by the program.
Memory region definition
By default, the linker can allocate storage areas at any location for sections. Using the MEMORY command can describe which memory regions can be used by the linker and which memory regions should be avoided. A linker script can contain the MEMORY command at most once.
1 | MEMORY |
NAME is the name used to reference the memory region in the linker script; each memory region has a unique name
ORIGINis the start address
LENGTH is the length of the address range
ATTR: Optional attribute string used to restrict which input sections can be placed in this region. Supported characters include:
- r: Read-only section
- w: Read/Write section
- x: Executable section
- a: Allocatable section
- i or l: Initialized section
- !: Inverted attribute (indicates placing sections that do not satisfy any attributes following this character)
Usage: Once a memory region is defined, it can be used in section descriptions>regionto instruct the linker to place specific output sections into this region.
1 | SECTIONS |
Section Linking Definition
The SECTIONS command is a very important command in the linker script. Its function is:to tell the linker how to map the sections of the input files to the various sections of the output file, and how to place the output sections into the address space。
Like the MEMORY command, there is only one SECTIONS command in a linker script. If there is no SECTIONS command in the entire linker script, the linker will combine all input sections with the same name into one output section, and the order of the input sections will be the order in which they were discovered by the linker.
1 | SECTIONS |
Some common usages:
. = ALIGN(4): Indicates 4-byte address alignment. That is, the starting address of the section must be divisible by 4. Generally, the common ones are ALIGN(4) or ALIGN(8), which means 4-byte or 8-byte alignment.PROVIDEandPROVIDE_HIDDENkeyword: Indicates defining a symbol in the linker script file. This symbol is not defined by the object file, but is referenced by the object file.KEEP(): The function of KEEP() is when the linker’s–gc-sectionsgarbage collection option is enabled, this part cannot be collected. For example,KEEP(*(.text))indicates that not all .text sections can be treated as garbage for collection./DISCARD/: Is a special section name. If this section name is used as output, all matching sections are discarded.
For more linker script syntax, refer to this site:
stm32mp15xx_m4.ld
1 | /* |
Analysis:
1 | /* Entry Point */ |
Core configuration parsing
Program entry: Specify
Reset_HandlerAs the program entry point, this function is in the startup filestartup_stm32mp15xx.sdefined in.Stack top address: Set the highest stack address to
0x10040000. It determines the initial position of the Stack Pointer (SP), corresponding to the mapping boundary of the internal SRAM.Stack size: Specifies the minimum space. The Heap size is 512B, and the Stack size is 1KB。
Memory region (MEMORY) definition
m_interrupts(0x00000000 ~ 0x00000298): Mapped to the RETRAM region, used to store the interrupt vector table of the M4 core.m_text(0x10000000 ~ 0x10020000): Corresponds to SRAM1 (128KB), used to store the Code section.m_data(0x10020000 ~ 0x10040000): Corresponds to SRAM2 (128KB), used to store the Data section.
The linker script plans the program execution address in SRAM:
- SRAM allocation logic under dual-core collaboration
The total SRAM space available for M4 is SRAM1 ~ SRAM4 (384KB in total), address range0x10000000 ~ 0x1005FFFF. Pay attention to the operating mode during allocation:
- Single-core mode (M4 running only): SRAM1 ~ SRAM4 can be fully allocated to M4.
- Dual-core mode (A7 + M4 running):
- SRAM1 & SRAM2: Fully allocated to M4 (storing M4’s code and data).
- SRAM3: Shared by A7 and M4. Among them,
0x10040000 ~ 0x10046000by default serves asMemory exchange area for dual-core communication (IPC Shared Memory). The specific allocation ratio needs to refer to the Device Tree configuration under Linux.
1 | reserved-memory { |
And m_interrupts is actually in RETRAM, so what is the remaining SRAM4 used for?
If the Linux operating system is not running, and only M4 bare-metal programs are running, the M4 core can completely use this area, specified by the user.
If running the Linux operating system, under the Linux Device Tree, SRAM4 has already been defaulted as DMA for Linux functions. If you want to release this area, you can just delete and release the corresponding node under the Device Tree (but it is not recommended to do so, as A7 may become abnormal).

Based on the above analysis, the summary is as follows:
- If A7 is not running, and only M4 is running (M4 can run bare-metal, RTOS): SRAM1~SRAM4 can be completely allocated to M4;
- If A7 and M4 are running simultaneously (e.g., dual-core communication): SRAM1 and SRAM2 are exclusively for M4, part of SRAM3’s addresses are used by both M4 and A7, and SRAM4 is separately configured with DMA under Linux, meaning it is occupied by A7. If you want to modify the address area range in MEMORY, you must modify it in conjunction with the address range of the memory mapping table.

