Cover image for Remoteproc resource table

Remoteproc resource table

Words 8.9k
Views
Visitors

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.

remoteproc_image.drawio
remoteproc_image.drawio

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 thestm32mp157-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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* struct resource_table - firmware resource table header
* @ver: version number
* @num: number of resource entries
* @reserved: reserved (must be zero)
* @offset: array of offsets pointing at the various resource entries
*
* A resource table is essentially a list of system resources required
* by the remote processor. It may also include configuration entries.
* If needed, the remote processor firmware should contain this table
* as a dedicated ".resource_table" ELF section.
*
* Some resources entries are mere announcements, where the host is informed
* of specific remoteproc configuration. Other entries require the host to
* do something (e.g. allocate a system resource). Sometimes a negotiation
* is expected, where the firmware requests a resource, and once allocated,
* the host should provide back its details (e.g. address of an allocated
* memory region).
*
* The header of the resource table, as expressed by this structure,
* contains a version number (should we need to change this format in the
* future), the number of available resource entries, and their offsets
* in the table.
*
* Immediately following this header are the resource entries themselves,
* each of which begins with a resource entry header (as described below).
*/
struct resource_table {
u32 ver;
u32 num;
u32 reserved[2];
u32 offset[];
} __packed;

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
2
3
4
5
6
7
8
9
10
11
12
13
/**
* struct fw_rsc_hdr - firmware resource entry header
* @type: resource type
* @data: resource data
*
* Every resource entry begins with a 'struct fw_rsc_hdr' header providing
* its @type. The content of the entry itself will immediately follow
* this header, and it should be parsed according to the resource type.
*/
struct fw_rsc_hdr {
u32 type;
u8 data[];
} __packed;

The resource type is defined as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* enum fw_resource_type - types of resource entries
*
* @RSC_CARVEOUT: request for allocation of a physically contiguous
* memory region.
* @RSC_DEVMEM: request to iommu_map a memory-based peripheral.
* @RSC_TRACE: announces the availability of a trace buffer into which
* the remote processor will be writing logs.
* @RSC_VDEV: declare support for a virtio device, and serve as its
* virtio header.
* @RSC_LAST: just keep this one at the end of standard resources
* @RSC_VENDOR_START: start of the vendor specific resource types range
* @RSC_VENDOR_END: end of the vendor specific resource types range
*
* For more details regarding a specific resource type, please see its
* dedicated structure below.
*
* Please note that these values are used as indices to the rproc_handle_rsc
* lookup table, so please keep them sane. Moreover, @RSC_LAST is used to
* check the validity of an index before the lookup table is accessed, so
* please update it as needed.
*/
enum fw_resource_type {
RSC_CARVEOUT = 0,
RSC_DEVMEM = 1,
RSC_TRACE = 2,
RSC_VDEV = 3,
RSC_LAST = 4,
RSC_VENDOR_START = 128,
RSC_VENDOR_END = 512,
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* struct fw_rsc_carveout - physically contiguous memory request
* @da: device address
* @pa: physical address
* @len: length (in bytes)
* @flags: iommu protection flags
* @reserved: reserved (must be zero)
* @name: human-readable name of the requested memory region
*
* This resource entry requests the host to allocate a physically contiguous
* memory region.
*
* These request entries shonuld precede other firmware resource entries,
* as other entries might request placing other data objects inside
* these memory regions (e.g. data/code segments, trace resource entries, ...).
*
* Allocating memory this way helps utilizing the reserved physical memory
* (e.g. CMA) more efficiently, and also minimizes the number of TLB entries
* needed to map it (in case @rproc is using an IOMMU). Reducing the TLB
* pressure is important; it may have a substantial impact on performance.
*
* If the firmware is compiled with static addresses, then @da should specify
* the expected device address of this memory region. If @da is set to
* FW_RSC_ADDR_ANY, then the host will dynamically allocate it, and then
* overwrite @da with the dynamically allocated address.
*
* We will always use @da to negotiate the device addresses, even if it
* isn't using an iommu. In that case, though, it will obviously contain
* physical addresses.
*
* Some remote processors needs to know the allocated physical address
* even if they do use an iommu. This is needed, e.g., if they control
* hardware accelerators which access the physical memory directly (this
* is the case with OMAP4 for instance). In that case, the host will
* overwrite @pa with the dynamically allocated physical address.
* Generally we don't want to expose physical addresses if we don't have to
* (remote processors are generally _not_ trusted), so we might want to
* change this to happen _only_ when explicitly required by the hardware.
*
* @flags is used to provide IOMMU protection flags, and @name should
* (optionally) contain a human readable name of this carveout region
* (mainly for debugging purposes).
*/
struct fw_rsc_carveout {
u32 da;
u32 pa;
u32 len;
u32 flags;
u32 reserved;
u8 name[32];
} __packed;

struct fw_rsc_devmem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* struct fw_rsc_devmem - iommu mapping request
* @da: device address
* @pa: physical address
* @len: length (in bytes)
* @flags: iommu protection flags
* @reserved: reserved (must be zero)
* @name: human-readable name of the requested region to be mapped
*
* This resource entry requests the host to iommu map a physically contiguous
* memory region. This is needed in case the remote processor requires
* access to certain memory-based peripherals; _never_ use it to access
* regular memory.
*
* This is obviously only needed if the remote processor is accessing memory
* via an iommu.
*
* @da should specify the required device address, @pa should specify
* the physical address we want to map, @len should specify the size of
* the mapping and @flags is the IOMMU protection flags. As always, @name may
* (optionally) contain a human readable name of this mapping (mainly for
* debugging purposes).
*
* Note: at this point we just "trust" those devmem entries to contain valid
* physical addresses, but this isn't safe and will be changed: eventually we
* want remoteproc implementations to provide us ranges of physical addresses
* the firmware is allowed to request, and not allow firmwares to request
* access to physical addresses that are outside those ranges.
*/
struct fw_rsc_devmem {
u32 da;
u32 pa;
u32 len;
u32 flags;
u32 reserved;
u8 name[32];
} __packed;

struct fw_rsc_trace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* struct fw_rsc_trace - trace buffer declaration
* @da: device address
* @len: length (in bytes)
* @reserved: reserved (must be zero)
* @name: human-readable name of the trace buffer
*
* This resource entry provides the host information about a trace buffer
* into which the remote processor will write log messages.
*
* @da specifies the device address of the buffer, @len specifies
* its size, and @name may contain a human readable name of the trace buffer.
*
* After booting the remote processor, the trace buffers are exposed to the
* user via debugfs entries (called trace0, trace1, etc..).
*/
struct fw_rsc_trace {
u32 da;
u32 len;
u32 reserved;
u8 name[32];
} __packed;

struct fw_rsc_vdev_vring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* struct fw_rsc_vdev_vring - vring descriptor entry
* @da: device address
* @align: the alignment between the consumer and producer parts of the vring
* @num: num of buffers supported by this vring (must be power of two)
* @notifyid is a unique rproc-wide notify index for this vring. This notify
* index is used when kicking a remote processor, to let it know that this
* vring is triggered.
* @pa: physical address
*
* This descriptor is not a resource entry by itself; it is part of the
* vdev resource type (see below).
*
* Note that @da should either contain the device address where
* the remote processor is expecting the vring, or indicate that
* dynamically allocation of the vring's device address is supported.
*/
struct fw_rsc_vdev_vring {
u32 da;
u32 align;
u32 num;
u32 notifyid;
u32 pa;
} __packed;

struct fw_rsc_vdev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* struct fw_rsc_vdev - virtio device header
* @id: virtio device id (as in virtio_ids.h)
* @notifyid is a unique rproc-wide notify index for this vdev. This notify
* index is used when kicking a remote processor, to let it know that the
* status/features of this vdev have changes.
* @dfeatures specifies the virtio device features supported by the firmware
* @gfeatures is a place holder used by the host to write back the
* negotiated features that are supported by both sides.
* @config_len is the size of the virtio config space of this vdev. The config
* space lies in the resource table immediate after this vdev header.
* @status is a place holder where the host will indicate its virtio progress.
* @num_of_vrings indicates how many vrings are described in this vdev header
* @reserved: reserved (must be zero)
* @vring is an array of @num_of_vrings entries of 'struct fw_rsc_vdev_vring'.
*
* This resource is a virtio device header: it provides information about
* the vdev, and is then used by the host and its peer remote processors
* to negotiate and share certain virtio properties.
*
* By providing this resource entry, the firmware essentially asks remoteproc
* to statically allocate a vdev upon registration of the rproc (dynamic vdev
* allocation is not yet supported).
*
* Note: unlike virtualization systems, the term 'host' here means
* the Linux side which is running remoteproc to control the remote
* processors. We use the name 'gfeatures' to comply with virtio's terms,
* though there isn't really any virtualized guest OS here: it's the host
* which is responsible for negotiating the final features.
* Yeah, it's a bit confusing.
*
* Note: immediately following this structure is the virtio config space for
* this vdev (which is specific to the vdev; for more info, read the virtio
* spec). the size of the config space is specified by @config_len.
*/
struct fw_rsc_vdev {
u32 id;
u32 notifyid;
u32 dfeatures;
u32 gfeatures;
u32 config_len;
u8 status;
u8 num_of_vrings;
u8 reserved[2];
struct fw_rsc_vdev_vring vring[];
} __packed;

struct rproc_mem_entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* struct rproc_mem_entry - memory entry descriptor
* @va: virtual address
* @dma: dma address
* @len: length, in bytes
* @da: device address
* @release: release associated memory
* @priv: associated data
* @name: associated memory region name (optional)
* @node: list node
* @rsc_offset: offset in resource table
* @flags: iommu protection flags
* @of_resm_idx: reserved memory phandle index
* @alloc: specific memory allocator function
*/
struct rproc_mem_entry {
void *va;
dma_addr_t dma;
size_t len;
u32 da;
void *priv;
char name[32];
struct list_head node;
u32 rsc_offset;
u32 flags;
u32 of_resm_idx;
int (*alloc)(struct rproc *rproc, struct rproc_mem_entry *mem);
int (*release)(struct rproc *rproc, struct rproc_mem_entry *mem);
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
******************************************************************************
* @file rsc_table.c
* @author MCD Application Team
* @brief Ressource table
*
* This file provides a default resource table requested by remote proc to
* load the elf file. It also allows to add debug trace using a shared buffer.
*
******************************************************************************
* @attention
*
* Copyright (c) 2021 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/

/** @addtogroup RSC_TABLE
* @{
*/

/** @addtogroup resource_table
* @{
*/

/** @addtogroup resource_table_Private_Includes
* @{
*/

#if defined(__ICCARM__) || defined (__CC_ARM)
#include <stddef.h> /* needed for offsetof definition*/
#endif
#include "rsc_table.h"
#include "openamp/open_amp.h"

/**
* @}
*/

/** @addtogroup resource_table_Private_TypesDefinitions
* @{
*/

/**
* @}
*/

/** @addtogroup resource_table_Private_Defines
* @{
*/

/* Place resource table in special ELF section */
#if defined(__GNUC__)
#define __section_t(S) __attribute__((__section__(#S)))
#define __resource __section_t(.resource_table)
#endif

#if defined (LINUX_RPROC_MASTER)
#ifdef VIRTIO_MASTER_ONLY
#define CONST
#else
#define CONST const
#endif
#else
#define CONST
#endif

#define RPMSG_IPU_C0_FEATURES 1
#define VRING_COUNT 2

/* VirtIO rpmsg device id */
#define VIRTIO_ID_RPMSG_ 7

#if defined (__LOG_TRACE_IO_)
extern char system_log_buf[];
#endif

#if defined(__GNUC__)
#if !defined (__CC_ARM) && !defined (LINUX_RPROC_MASTER)

/* Since GCC is not initializing the resource_table at startup, it is declared as volatile to avoid compiler optimization
* for the CM4 (see resource_table_init() below)
*/
volatile struct shared_resource_table __resource __attribute__((used)) resource_table;
#else
CONST struct shared_resource_table __resource __attribute__((used)) resource_table = {
#endif
#elif defined(__ICCARM__)
__root CONST struct shared_resource_table resource_table @ ".resource_table" = {
#endif

#if defined(__ICCARM__) || defined (__CC_ARM) || defined (LINUX_RPROC_MASTER)
.version = 1,
#if defined (__LOG_TRACE_IO_)
.num = 2,
#else
.num = 1,
#endif
.reserved = {0, 0},
.offset = {
offsetof(struct shared_resource_table, vdev),
offsetof(struct shared_resource_table, cm_trace),
},

/* Virtio device entry */
.vdev= {
RSC_VDEV, VIRTIO_ID_RPMSG_, 0, RPMSG_IPU_C0_FEATURES, 0, 0, 0,
VRING_COUNT, {0, 0},
},

/* Vring rsc entry - part of vdev rsc entry */
.vring0 = {VRING_TX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0},
.vring1 = {VRING_RX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING1_ID, 0},

#if defined (__LOG_TRACE_IO_)
.cm_trace = {
RSC_TRACE,
(uint32_t)system_log_buf, SYSTEM_TRACE_BUF_SZ, 0, "cm4_log",
},
#endif
} ;
#endif

void resource_table_init(int RPMsgRole, void **table_ptr, int *length)
{

#if !defined (LINUX_RPROC_MASTER)
#if defined (__GNUC__) && ! defined (__CC_ARM)
#ifdef VIRTIO_MASTER_ONLY

/*
* Currently the GCC linker doesn't initialize the resource_table global variable at startup
* it is done here by the master application.
*/
memset(&resource_table, '\0', sizeof(struct shared_resource_table));
resource_table.num = 1;
resource_table.version = 1;
resource_table.offset[0] = offsetof(struct shared_resource_table, vdev);

resource_table.vring0.da = VRING_TX_ADDRESS;
resource_table.vring0.align = VRING_ALIGNMENT;
resource_table.vring0.num = VRING_NUM_BUFFS;
resource_table.vring0.notifyid = VRING0_ID;

resource_table.vring1.da = VRING_RX_ADDRESS;
resource_table.vring1.align = VRING_ALIGNMENT;
resource_table.vring1.num = VRING_NUM_BUFFS;
resource_table.vring1.notifyid = VRING1_ID;


resource_table.vdev.type = RSC_VDEV;
resource_table.vdev.id = VIRTIO_ID_RPMSG_;
resource_table.vdev.num_of_vrings=VRING_COUNT;
resource_table.vdev.dfeatures = RPMSG_IPU_C0_FEATURES;
#else

/* For the slave application let's wait until the resource_table is correctly initialized */
while(resource_table.vring1.da != VRING_RX_ADDRESS)
{

}
#endif
#endif
#endif

(void)RPMsgRole;
*length = sizeof(resource_table);
*table_ptr = (void *)&resource_table;
}

AndSTM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Inc/rsc_table.hAs follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
* Copyright (c) 2021 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
*/

/* This file populates resource table for BM remote
* for use by the Linux Master */

#ifndef RSC_TABLE_H_
#define RSC_TABLE_H_

#include "openamp/open_amp.h"
#include "openamp_conf.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */


/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* Resource table for the given remote */
struct shared_resource_table {
unsigned int version;
unsigned int num;
unsigned int reserved[2];
unsigned int offset[NUM_RESOURCE_ENTRIES];
/* text carveout entry */

/* rpmsg vdev entry */
struct fw_rsc_vdev vdev;
struct fw_rsc_vdev_vring vring0;
struct fw_rsc_vdev_vring vring1;
struct fw_rsc_trace cm_trace;
};

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

void resource_table_init(int RPMsgRole, void **table_ptr, int *length);

#endif /* RSC_TABLE_H_ */

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 siderproc_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.
  • 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
2
3
4
5
6
7
8
9
10
struct shared_resource_table {
unsigned int version;
unsigned int num;
unsigned int reserved[2];
unsigned int offset[NUM_RESOURCE_ENTRIES]; // NUM_RESOURCE_ENTRIES = 2
struct fw_rsc_vdev vdev;
struct fw_rsc_vdev_vring vring0;
struct fw_rsc_vdev_vring vring1;
struct fw_rsc_trace cm_trace;
};

struct fw_rsc_vdev

STM32CubeMP1/Middlewares/Third_Party/OpenAMP/open-amp/lib/include/openamp/remoteproc.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* struct fw_rsc_vdev - virtio device header
* @id: virtio device id (as in virtio_ids.h)
* @notifyid is a unique rproc-wide notify index for this vdev. This notify
* index is used when kicking a remote remoteproc, to let it know that the
* status/features of this vdev have changes.
* @dfeatures specifies the virtio device features supported by the firmware
* @gfeatures is a place holder used by the host to write back the
* negotiated features that are supported by both sides.
* @config_len is the size of the virtio config space of this vdev. The config
* space lies in the resource table immediate after this vdev header.
* @status is a place holder where the host will indicate its virtio progress.
* @num_of_vrings indicates how many vrings are described in this vdev header
* @reserved: reserved (must be zero)
* @vring is an array of @num_of_vrings entries of 'struct fw_rsc_vdev_vring'.
*
* This resource is a virtio device header: it provides information about
* the vdev, and is then used by the host and its peer remote remoteprocs
* to negotiate and share certain virtio properties.
*
* By providing this resource entry, the firmware essentially asks remoteproc
* to statically allocate a vdev upon registration of the rproc (dynamic vdev
* allocation is not yet supported).
*
* Note: unlike virtualization systems, the term 'host' here means
* the Linux side which is running remoteproc to control the remote
* remoteprocs. We use the name 'gfeatures' to comply with virtio's terms,
* though there isn't really any virtualized guest OS here: it's the host
* which is responsible for negotiating the final features.
* Yeah, it's a bit confusing.
*
* Note: immediately following this structure is the virtio config space for
* this vdev (which is specific to the vdev; for more info, read the virtio
* spec). the size of the config space is specified by @config_len.
*/
METAL_PACKED_BEGIN
struct fw_rsc_vdev {
uint32_t type; // ← ST added type at the very front!
uint32_t id;
uint32_t notifyid;
uint32_t dfeatures;
uint32_t gfeatures;
uint32_t config_len;
uint8_t status;
uint8_t num_of_vrings;
uint8_t reserved[2];
struct fw_rsc_vdev_vring vring[0]; // zero-length flexible array
} METAL_PACKED_END;

ST’sfw_rsc_vdevfolds the type in

There are two points here:

  • ST added type at the front of thestruct 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
2
3
ST:    [vdev.type | vdev.id | ... | reserved]  ← type 在 vdev 内
Linux: [hdr.type | vdev.id | ... | reserved] ← type 在 hdr 里,vdev 从 id 开始
byte0 byte4

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* struct fw_rsc_vdev_vring - vring descriptor entry
* @da: device address
* @align: the alignment between the consumer and producer parts of the vring
* @num: num of buffers supported by this vring (must be power of two)
* @notifyid is a unique rproc-wide notify index for this vring. This notify
* index is used when kicking a remote remoteproc, to let it know that this
* vring is triggered.
* @reserved: reserved (must be zero)
*
* This descriptor is not a resource entry by itself; it is part of the
* vdev resource type (see below).
*
* Note that @da should either contain the device address where
* the remote remoteproc is expecting the vring, or indicate that
* dynamically allocation of the vring's device address is supported.
*/
METAL_PACKED_BEGIN
struct fw_rsc_vdev_vring {
uint32_t da;
uint32_t align;
uint32_t num;
uint32_t notifyid;
uint32_t reserved;
} METAL_PACKED_END;

struct fw_rsc_trace

STM32CubeMP1/Middlewares/Third_Party/OpenAMP/open-amp/lib/include/openamp/remoteproc.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* struct fw_rsc_trace - trace buffer declaration
* @da: device address
* @len: length (in bytes)
* @reserved: reserved (must be zero)
* @name: human-readable name of the trace buffer
*
* This resource entry provides the host information about a trace buffer
* into which the remote remoteproc will write log messages.
*
* @da specifies the device address of the buffer, @len specifies
* its size, and @name may contain a human readable name of the trace buffer.
*
* After booting the remote remoteproc, the trace buffers are exposed to the
* user via debugfs entries (called trace0, trace1, etc..).
*/
METAL_PACKED_BEGIN
struct fw_rsc_trace {
uint32_t type;
uint32_t da;
uint32_t len;
uint32_t reserved;
uint8_t name[RPROC_MAX_NAME_LEN];
} METAL_PACKED_END;

rsc_table instance

STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Src/rsc_table.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#if defined(__GNUC__)
#if !defined (__CC_ARM) && !defined (LINUX_RPROC_MASTER)

/* Since GCC is not initializing the resource_table at startup, it is declared as volatile to avoid compiler optimization
* for the CM4 (see resource_table_init() below)
*/
volatile struct shared_resource_table __resource __attribute__((used)) resource_table;
#else
CONST struct shared_resource_table __resource __attribute__((used)) resource_table = {
#endif
#elif defined(__ICCARM__)
__root CONST struct shared_resource_table resource_table @ ".resource_table" = {
#endif

#if defined(__ICCARM__) || defined (__CC_ARM) || defined (LINUX_RPROC_MASTER)
.version = 1,
#if defined (__LOG_TRACE_IO_)
.num = 2,
#else
.num = 1,
#endif
.reserved = {0, 0},
.offset = {
offsetof(struct shared_resource_table, vdev),
offsetof(struct shared_resource_table, cm_trace),
},

/* Virtio device entry */
.vdev= {
RSC_VDEV, VIRTIO_ID_RPMSG_, 0, RPMSG_IPU_C0_FEATURES, 0, 0, 0,
VRING_COUNT, {0, 0},
},

/* Vring rsc entry - part of vdev rsc entry */
.vring0 = {VRING_TX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0},
.vring1 = {VRING_RX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING1_ID, 0},

#if defined (__LOG_TRACE_IO_)
.cm_trace = {
RSC_TRACE,
(uint32_t)system_log_buf, SYSTEM_TRACE_BUF_SZ, 0, "cm4_log",
},
#endif
} ;
#endif

vdev

Because this project#define LINUX_RPROC_MASTERopenamp_conf.h), it takes the static initialization branch:

1
2
3
4
5
6
7
8
9
10
11
.vdev= {
RSC_VDEV, // type = 3
VIRTIO_ID_RPMSG_, // id = 7 (RPMsg device)
0, // notifyid = 0 (filled back by host)
RPMSG_IPU_C0_FEATURES, // dfeatures = 1 (bit0 = VIRTIO_RPMSG_F_NS, name service)
0, // gfeatures = 0 (host backfills negotiation result)
0, // config_len = 0 (RPMsg has no config space)
0, // status = 0 (filled back by host)
VRING_COUNT, // num_of_vrings = 2
{0, 0}, // reserved[2]
},

vring

1
2
.vring0 = {VRING_TX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING0_ID, 0},
.vring1 = {VRING_RX_ADDRESS, VRING_ALIGNMENT, VRING_NUM_BUFFS, VRING1_ID, 0},
vring fieldsvring0 (TX)vring1 (RX)Description
da-1 (FW_RSC_ADDR_ANY)-1Device address. If -1, it indicates support for dynamic allocation of the vring device address.
align1616Alignment size. The number of alignment bytes between the Consumer and Producer sections of the vring.
num1616Number of buffers. The number of buffers supported by this vring (must be a power of 2).
notifyid0
(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.
reserved00Reserved field. Must be 0.

Dynamic ResMgrcore:da = -1

  • Static method: firmware hardcodesvring.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
2
3
4
5
6
7
8
9
10
11
#if defined LINUX_RPROC_MASTER          // ← This project takes this branch
#define VRING_RX_ADDRESS ((unsigned int)-1) // FW_RSC_ADDR_ANY
#define VRING_TX_ADDRESS ((unsigned int)-1)
#define VRING_ALIGNMENT 16
#define VRING_NUM_BUFFS 16
#else // Static allocation with M4 as master
#define VRING_RX_ADDRESS SHM_START_ADDRESS // Fixed address
#define VRING_TX_ADDRESS (SHM_START_ADDRESS + 0x400)
#define VRING_ALIGNMENT 4
#define VRING_NUM_BUFFS 4
#endif

cm_trace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if defined (__LOG_TRACE_IO_)
.num = 2, // resource entry num
#else
.num = 1,
#endif

...

#if defined (__LOG_TRACE_IO_)
.cm_trace = {
RSC_TRACE, // type = 2
(uint32_t)system_log_buf, // da = M4 log buffer address (note: it's the actual address, not -1)
SYSTEM_TRACE_BUF_SZ, // len = 2048 (openamp_log.h)
0, // reserved
"cm4_log", // name → host debugfs filename
},
#endif

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 createdebugfs/.../cm4_log, what it reads is the log written by M4.

resource_table_init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void resource_table_init(int RPMsgRole, void **table_ptr, int *length)
{

#if !defined (LINUX_RPROC_MASTER)
#if defined (__GNUC__) && ! defined (__CC_ARM)
#ifdef VIRTIO_MASTER_ONLY

/*
* Currently the GCC linker doesn't initialize the resource_table global variable at startup
* it is done here by the master application.
*/
memset(&resource_table, '\0', sizeof(struct shared_resource_table));
resource_table.num = 1;
resource_table.version = 1;
resource_table.offset[0] = offsetof(struct shared_resource_table, vdev);

resource_table.vring0.da = VRING_TX_ADDRESS;
resource_table.vring0.align = VRING_ALIGNMENT;
resource_table.vring0.num = VRING_NUM_BUFFS;
resource_table.vring0.notifyid = VRING0_ID;

resource_table.vring1.da = VRING_RX_ADDRESS;
resource_table.vring1.align = VRING_ALIGNMENT;
resource_table.vring1.num = VRING_NUM_BUFFS;
resource_table.vring1.notifyid = VRING1_ID;


resource_table.vdev.type = RSC_VDEV;
resource_table.vdev.id = VIRTIO_ID_RPMSG_;
resource_table.vdev.num_of_vrings=VRING_COUNT;
resource_table.vdev.dfeatures = RPMSG_IPU_C0_FEATURES;
#else

/* For the slave application let's wait until the resource_table is correctly initialized */
while(resource_table.vring1.da != VRING_RX_ADDRESS)
{

}
#endif
#endif
#endif

(void)RPMsgRole;
*length = sizeof(resource_table);
*table_ptr = (void *)&resource_table;
}

Preprocessor branches handle three scenarios:

ScenarioConditionBehavior
Linux as master
#ifdef LINUX_RPROC_MASTERSkip the entire#if !defined(LINUX_RPROC_MASTER)block; the table is initialized at compile time, the function just returns:
table_ptr = (void *)&resource_table
length = sizeof(sizeof(resource_table))
GCC + M4 as masterGNUC&&!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 slaveGNUC&&!LINUX_RPROC_MASTERwhile(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

resource_table
resource_table

Thisrsc_table.cis the “resource requirement list” submitted by the M4 firmware to Linux:

  • declares avirtio-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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;

mcuram2: mcuram2@10000000 {
compatible = "shared-dma-pool";
reg = <0x10000000 0x40000>;
no-map;
};

vdev0vring0: vdev0vring0@10040000 {
compatible = "shared-dma-pool";
reg = <0x10040000 0x1000>;
no-map;
};

vdev0vring1: vdev0vring1@10041000 {
compatible = "shared-dma-pool";
reg = <0x10041000 0x1000>;
no-map;
};

vdev0buffer: vdev0buffer@10042000 {
compatible = "shared-dma-pool";
reg = <0x10042000 0x4000>;
no-map;
};

mcuram: mcuram@30000000 {
compatible = "shared-dma-pool";
reg = <0x30000000 0x40000>;
no-map;
};

retram: retram@38000000 {
compatible = "shared-dma-pool";
reg = <0x38000000 0x10000>;
no-map;
};
};

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’scompatibleattribute 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
2
3
4
5
mcuram2: mcuram2@10000000 {
compatible = "shared-dma-pool";
reg = <0x10000000 0x40000>;
no-map;
};

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.

vdev0vring0vdev0vring1vdev0buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vdev0vring0: vdev0vring0@10040000 {
compatible = "shared-dma-pool";
reg = <0x10040000 0x1000>;
no-map;
};

vdev0vring1: vdev0vring1@10041000 {
compatible = "shared-dma-pool";
reg = <0x10041000 0x1000>;
no-map;
};

vdev0buffer: vdev0buffer@10042000 {
compatible = "shared-dma-pool";
reg = <0x10042000 0x4000>;
no-map;
};

vdev0vring0vdev0vring1andvdev0bufferThe 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.

vdev0vring0vdev0vring1andvdev0bufferonly 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
2
3
4
5
mcuram: mcuram@30000000 {
compatible = "shared-dma-pool";
reg = <0x30000000 0x40000>;
no-map;
};

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
2
3
4
5
retram: retram@38000000 {
compatible = "shared-dma-pool";
reg = <0x38000000 0x10000>;
no-map;
};

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

SRAM address allocation
SRAM address allocation

Child nodeAddressSizeArea
mcuram20x10000000~0x10040000256KBM4-visible SRAM1+SRAM2
vdev0vring00x10040000~0x100410004KBSRAM3
vdev0vring10x10041000~0x100420004KBSRAM3
vdev0buffer0x10042000~0x1004600016KBSRAM3
mcuram0x30000000~0x30040000256KBA7-visible SRAM1+SRAM2
retram0x38000000~0x3801000064KBA7-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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
mlahb { 
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
dma-ranges = <0x00000000 0x38000000 0x10000>,
<0x10000000 0x10000000 0x60000>,
<0x30000000 0x30000000 0x60000>;

m4_rproc: m4@10000000 {
compatible = "st,stm32mp1-m4";
reg = <0x10000000 0x40000>,
<0x30000000 0x40000>,
<0x38000000 0x10000>;
resets = <&scmi0_reset RST_SCMI0_MCU>;
st,syscfg-holdboot = <&rcc 0x10C 0x1>;
st,syscfg-tz = <&rcc 0x000 0x1>;
st,syscfg-rsc-tbl = <&tamp 0x144 0xFFFFFFFF>;
st,syscfg-copro-state = <&tamp 0x148 0xFFFFFFFF>;
st,syscfg-pdds = <&pwr_mcu 0x0 0x1>;
status = "disabled";

m4_system_resources {
compatible = "rproc-srm-core";
status = "disabled";
};
};
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const struct of_device_id rproc_srm_core_match[] = { 
{ .compatible = "rproc-srm-core", },
{},
};

MODULE_DEVICE_TABLE(of, rproc_srm_core_match);

static struct platform_driver rproc_srm_core_driver = {
.probe = rproc_srm_core_probe,
.remove = rproc_srm_core_remove,
.driver = {
.name = "rproc-srm-core",
.of_match_table = of_match_ptr(rproc_srm_core_match),
},
};

module_platform_driver(rproc_srm_core_driver);

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
&m4_rproc { 
m4_system_resources {
#address-cells = <1>;
#size-cells = <0>;

m4_timers2: timer@40000000 {
compatible = "rproc-srm-dev";
reg = <0x40000000 0x400>;
clocks = <&rcc TIM2_K>;
clock-names = "int";
status = "disabled";
};
/* Omit some code */
m4_adc: adc@48003000 {
compatible = "rproc-srm-dev";
reg = <0x48003000 0x400>;
clocks = <&rcc ADC12>, <&rcc ADC12_K>;
clock-names = "bus", "adc";
status = "disabled";
};

/* Omit some code */
m4_ethernet0: ethernet@5800a000 {
compatible = "rproc-srm-dev";
reg = <0x5800a000 0x2000>;
clock-names = "stmmaceth",
"mac-clk-tx",
"mac-clk-rx",
"ethstp",
"syscfg-clk";
clocks = <&rcc ETHMAC>,
<&rcc ETHTX>,
<&rcc ETHRX>,
<&rcc ETHSTP>,
<&rcc SYSCFG>;
status = "disabled";
};
};
};

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.dtsiFile

Therefore, you can directlystm32mp157d-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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
leds { 
compatible = "gpio-leds";

led1 {
label = "sys-led";
gpios = <&gpioi 0 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
default-state = "on";
status = "okay";
};

led2 {
label = "user-led";
gpios = <&gpiof 3 GPIO_ACTIVE_LOW>;
linux,default-trigger = "none";
default-state = "on";
status = "okay";
};

beep {
label = "beep";
gpios = <&gpioc 7 GPIO_ACTIVE_LOW>;
default-state = "off";
};
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
adc1_in6_pins_b: adc1-in6 { 
pins {
pinmux = <STM32_PINMUX('A', 5, ANALOG)>;
};
};

&adc {
/* ADC1 & ADC2 common resources */
pinctrl-names = "default";
pinctrl-0 = <&adc1_in6_pins_b>;
vdd-supply = <&vdd>;
vdda-supply = <&vdd>;
vref-supply = <&vdd>;

status = "okay";

adc1: adc@0 {
/* private resources for ADC1 */
st,adc-channels = <19>;
st,min-sample-time-nsecs = <10000>;
status = "okay";
};
};

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 
adc1_in6_pins_b: adc1-in6 {
pins {
pinmux = <STM32_PINMUX('A', 5, ANALOG)>;
};
};
*/
/*
&adc {
// * ADC1 & ADC2 common resources *
pinctrl-names = "default";
pinctrl-0 = <&adc1_in6_pins_b>;
vdd-supply = <&vdd>;
vdda-supply = <&vdd>;
vref-supply = <&vdd>;

status = "okay";

adc1: adc@0 {
// * private resources for ADC1 *
st,adc-channels = <19>;
st,min-sample-time-nsecs = <10000>;
status = "okay";
};
};
*/
&m4_adc {
vref-supply = <&vrefbuf>;
status = "okay"; /* Enable M4's ADC */
};

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
2
/* Entry Point */
ENTRY(Reset_Handler)

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
2
3
4
5
6
MEMORY 
{
/* Name Attributes Start Address Length */
NAME [(ATTR)] : ORIGIN = ORIGIN, LENGTH = LEN
}

  • 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
2
3
4
5
6
7
SECTIONS
{
.text :
{
*(.text)
} > mem
}

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
2
3
4
5
6
7
SECTIONS 
{
.text :
{
start.o (.text) 6 *(.text*)
} >region
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
******************************************************************************
**
** File : LinkerScript.ld
**
** Abstract : Linker script for STM32MP1 series
**
** Set heap size, stack size and stack location according
** to application requirements.
**
** Set memory bank area and size if external memory is used.
**
** Target : STMicroelectronics STM32
**
** Distribution: The file is distributed “as is,” without any warranty
** of any kind.
**
*****************************************************************************
** @attention

** <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
** All rights reserved.</center></h2>
**
** This software component is licensed by ST under BSD 3-Clause license,
** the "License"; You may not use this file except in compliance with the
** License. You may obtain a copy of the License at:
** opensource.org/licenses/BSD-3-Clause
**
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x10040000; /* end of RAM */

_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Memories definition */
MEMORY
{
m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000298
m_text (RX) : ORIGIN = 0x10000000, LENGTH = 0x00020000
m_data (RW) : ORIGIN = 0x10020000, LENGTH = 0x00020000
m_ipc_shm (RW) : ORIGIN = 0x10040000, LENGTH = 0x00008000
}

/* Symbols needed for OpenAMP to enable rpmsg */
__OPENAMP_region_start__ = ORIGIN(m_ipc_shm);
__OPENAMP_region_end__ = ORIGIN(m_ipc_shm)+LENGTH(m_ipc_shm);

/* Sections */
SECTIONS
{
/* The startup code into ROM memory */
/* Interrupt vector table, placed in RETRAM */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} > m_interrupts


/* The program code and other data into ROM memory */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)

KEEP (*(.init))
KEEP (*(.fini))

. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} > m_text

/* Constant data into ROM memory*/
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} > m_text

.ARM.extab : {
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} > m_text

.ARM : {
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} > m_text

.preinit_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} > m_text

.init_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} > m_text

.fini_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} > m_text

/* Used by the startup to initialize data */
__DATA_ROM = .;
_sidata = LOADADDR(.data);

/* Initialized data sections */
.data : AT(__DATA_ROM)
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */

. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} > m_data

__DATA_END = __DATA_ROM + (_edata - _sdata);
text_end = ORIGIN(m_text) + LENGTH(m_text);
ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")

.resource_table :
{
. = ALIGN(4);
KEEP (*(.resource_table*))
. = ALIGN(4);
} > m_data


/* Uninitialized data section into RAM memory */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)

. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} > m_data

/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} > m_data



/* Remove information from the compiler libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}

.ARM.attributes 0 : { *(.ARM.attributes) }

}

Analysis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x10040000; /* end of RAM */

_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Memories definition */
MEMORY
{
m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000298
m_text (RX) : ORIGIN = 0x10000000, LENGTH = 0x00020000
m_data (RW) : ORIGIN = 0x10020000, LENGTH = 0x00020000
m_ipc_shm (RW) : ORIGIN = 0x10040000, LENGTH = 0x00008000
}

/* Symbols needed for OpenAMP to enable rpmsg */
__OPENAMP_region_start__ = ORIGIN(m_ipc_shm);
__OPENAMP_region_end__ = ORIGIN(m_ipc_shm)+LENGTH(m_ipc_shm);
  1. Core configuration parsing

    • Program entry: SpecifyReset_HandlerAs the program entry point, this function is in the startup filestartup_stm32mp15xx.sdefined in.

    • Stack top address: Set the highest stack address to0x10040000. 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

  2. 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:

  1. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;

mcuram2: mcuram2@10000000 {
compatible = "shared-dma-pool";
reg = <0x10000000 0x40000>;
no-map;
};

vdev0vring0: vdev0vring0@10040000 {
compatible = "shared-dma-pool";
reg = <0x10040000 0x1000>;
no-map;
};

vdev0vring1: vdev0vring1@10041000 {
compatible = "shared-dma-pool";
reg = <0x10041000 0x1000>;
no-map;
};

vdev0buffer: vdev0buffer@10042000 {
compatible = "shared-dma-pool";
reg = <0x10042000 0x4000>;
no-map;
};

mcuram: mcuram@30000000 {
compatible = "shared-dma-pool";
reg = <0x30000000 0x40000>;
no-map;
};

retram: retram@38000000 {
compatible = "shared-dma-pool";
reg = <0x38000000 0x10000>;
no-map;
};
};

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).

Several SRAM areas
Several SRAM areas

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.

Reference materials