Cover image for Remoteproc resource table

Remoteproc resource table

字数 10.2k
阅读
访客

时间轴

时间轴

2026-07-04

init

本文介绍了远程处理器固件映像中资源表的结构与作用,详细阐述了主处理器在加载固件后如何通过解码资源表来分配物理内存等系统资源、配置Virtio设备,并创建RPMsg通道以实现核间通信。同时,本文结合STM32MP15C的参考代码,总结了Remoteproc框架下资源条目的具体定义及基于OpenAMP的动态资源管理实现方式。

参考代码:

STM32CubeMP1 MPU Firmware Package

  • STM32MP157C-EV1 RevC
  • STM32MP157C-DK2 RevC

远程处理器固件映像

远程处理器的固件映像一般包括:

  • 资源表(resource_table)
  • 用户应用程序
  • RTOS 或者裸机(Bare Metal ,简称 BM)相关代码
  • OpenAMP 的库。

主处理器将远程处理器的固件加载到远程处理器的内核中后,会去解码固件映像以获取关联的资源,并为固件代码段和数据留出内存,在启动远程处理器后,主处理器创建 RPMsg 通道,RPMsg 通道用于远程通信。

remoteproc_image.drawio
remoteproc_image.drawio

远程处理器的资源包含:

  • 远程处理器在上电之前需要的系统资源,例如分配给远程处理器的连续物理内存,还有分配给远程处理器的外围设备(这些设备可以是保留的、未使用的),这些资源在stm32mp157-m4-srm.dtsi设备树文件的资源管理器(即m4_system_resources节点)中配置。
  • 除了系统资源之外,远程处理器都会有一个资源表(resource_table),资源表还可能包含资源条目,这些条目发布远程处理器支持的功能或存在的配置,如 Virtio 设备中的 vring 地址、vring 的大小等。

只有在满足所有资源的要求后(M核端配置要求,资源表会要求linux分配相应的资源等),Remotecore 才会启动设备。

资源表

内核源码 下的include/linux/remoteproc.h文件,找到如下结构体 resource_table,它是固件资源表头:

linux 定义的 struct resource_table

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;

紧随此固件资源表头的是资源条目本身,每个条目都以struct fw_rsc_hdr标头开头,条目本身的内容会紧跟在这个头之后,根据资源类型来解析。以下是资源条目标头开头:

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;

其中 resource type定义如下

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,
};

以上这些值用作rproc_handle_rsc()函数(在remoteproc_internal.h文件中)查找表的索引。

当注册一个新的远程处理器时,Remoteproc 框架将查找其资源表并注册它支持的 Virtio 设备,固件应提供有关其支持的 Virtio 设备及其配置的 Remoteproc 信息,RSC_VDEV资源条目应指定 Virtio 设备 ID(如virtio_ids.h)、Virtio 功能、Virtio 配置空间、vrings 信息等,我们可以通过查看 Linux 文件系统/sys/kernel/debug/remoteproc/remoteproc0/resource_table文件得到 RSC_VDEV 信息。(即通过rproc_handle_rsc()函数先查找相关属性,之后到RSC_VDEV中寻找该属性的具体配置。)

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);
};

STM32MP15C 的 rsc_table 实现

STMicroelectronics/STM32CubeMP1仓库的STM32MP157C-EV1为例:

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;
}

STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/Inc/rsc_table.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
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 标记

1
volatile struct shared_resource_table __resource __attribute__((used)) resource_table;
  • __resource = __attribute__((section(".resource_table")))

    • 把整个 resource_table 变量放进.resource_table的 ELF 段。
    • 这正是 Linux 侧rproc_elf_load_rsc_table()/rproc_elf_find_loaded_rsc_table() 查找的段名——A7 加载固件 ELF 时按段名定位资源表。
  • resource_table_init() 把这张表的地址和长度交给 OpenAMP 中间件,后续 M4 的 virtio/rpmsg 栈要用。

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 在最前面加了 type!
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]; // 零长度柔性数组
} METAL_PACKED_END;

ST 的fw_rsc_vdev把 type 折叠进来了

这里有两点:

  • ST在struct fw_rsc_vdev的最前面加了 type 而 Linux 的 struct fw_rsc_vdev 没有 type(type 在独立的struct fw_rsc_hdr里)。但两者 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 这样做是为了能用一个 designated initializer 一次性初始化整个 vdev 条目(含 type)。

  • vring[0] 零长度 + 独立 vring0/vring1 成员的 C 技巧

fw_rsc_vdev 末尾的 vring[0] 占 0 字节,所以struct shared_resource_table里紧跟在 vdev 后面的 vring0、vring1 成员在内存里正好落在 vdev 之后——也就是 Linux 期望找到 vring 数组的位置。等价于 Linux 的 vring[] 柔性数组,但 ST 用分开的命名成员方便初始化。

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 实例

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

因为本工程#define LINUX_RPROC_MASTERopenamp_conf.h),走的静态初始化分支:

1
2
3
4
5
6
7
8
9
10
11
.vdev= {
RSC_VDEV, // type = 3
VIRTIO_ID_RPMSG_, // id = 7 (RPMsg 设备)
0, // notifyid = 0 (host 回填)
RPMSG_IPU_C0_FEATURES, // dfeatures = 1 (bit0 = VIRTIO_RPMSG_F_NS, name service)
0, // gfeatures = 0 (host 回填协商结果)
0, // config_len = 0 (RPMsg 无 config space)
0, // status = 0 (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 字段vring0 (TX)vring1 (RX)Description
da-1 (FW_RSC_ADDR_ANY)-1设备地址。如果是 -1,表示支持动态分配 vring 的设备地址。
align1616对齐大小。vring 消费者(Consumer)和生产者(Producer)部分之间的对齐字节数。
num1616缓冲区数量。该 vring 支持的 buffer 数量(必须是 2 的幂)。
notifyid0
(master→remote)
1
(remote→master)
通知 ID。整个远端处理器(rproc)范围内唯一的通知索引。给远端发送信号(kick)时,通过该 ID 让其知道是哪个 vring 被触发了。
reserved00保留字段。必须为 0。

Dynamic ResMgr的核心:da = -1

  • 静态方式:固件写死vring.da = 0x10040000,Linux 直接用。
  • 动态方式(STM32CubeMP1/Projects/STM32MP157C-EV1/Applications/OpenAMP/OpenAMP_Dynamic_ResMgr/):固件写da = -1,表示"Linux 来分配",Linux 从 DT 里vdev0vring0/1预留的carveout池中分配 vring 内存,把真实地址回填到vring->da。固件声明需求、host 分配并回填——正是 fw_rsc_vdev 注释里说的 “negotiation”。

openamp_conf.h有两条分支:

1
2
3
4
5
6
7
8
9
10
11
#if defined LINUX_RPROC_MASTER          // ← 本工程走这条
#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 // M4 当 master 的静态分配
#define VRING_RX_ADDRESS SHM_START_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 日志缓冲区地址(注意:是真实地址,不是 -1)
SYSTEM_TRACE_BUF_SZ, // len = 2048 (openamp_log.h)
0, // reserved
"cm4_log", // name → host debugfs 文件名
},
#endif

注意:

  • trace 的 da 不是-1
  • trace 缓冲区是 M4 自己分配的(system_log_buf 在 M4 内存里),host 只读不分配。host 启动后会建debugfs/.../cm4_log,读到的就是 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;
}

预处理器分支处理三种场景:

场景条件行为
Linux 当 master
#ifdef LINUX_RPROC_MASTER跳过整个#if !defined(LINUX_RPROC_MASTER)块;表已在编译期初始化好,函数只返回:
table_ptr = (void *)&resource_table
length = sizeof(sizeof(resource_table))
GCC + M4 当 masterGNUC&&!LINUX_RPROC_MASTER
&&VIRTIO_MASTER_ONLY
GCC 不在启动时初始化带 section 属性的全局变量,所以memset清零后运行时逐字段填
GCC + M4 当 slaveGNUC&&!LINUX_RPROC_MASTERwhile(vring1.da != VRING_RX_ADDRESS){}自旋等 master 把表填好

这套#if嵌套是 OpenAMP 为兼容Linux-master/M4-master/M4-slave三种拓扑写的。对 STM32MP157(Linux master)来说,运行时那段都不编译,实际只返回指针*length = sizeof(resource_table);*table_ptr = (void *)&resource_table;

三种#if分支表示这份 M4 代码在三种拓扑下,表由谁来填resource_table:Linux 加载(静态)、M4 master 运行时填、M4 slave 等填。

总结

resource_table
resource_table

这份rsc_table.c就是 M4 固件向 Linux 提交的"资源需求清单":

  • 声明一个virtio-rpmsg设备(id=7,2 个 vring,name-service 特性)
  • 一个 trace 缓冲区,并把 vring 地址设成 -1(FW_RSC_ADDR_ANY)让 Linux 动态分配——这正是Dynamic ResMgr的含义。

整张表放在.resource_tableELF 段,编译期初始化好,resource_table_init()只是把指针交给 OpenAMP 中间件。

存储和系统资源分配

reserved-memory

stm32mp157d-atk.dtsi设备树下可以看到这段代码:

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-memory表示该节点下分配的内存都是预留的内存,预留的内存区域一般是给特定的 驱动程序 使用的,它和 Linux 内核使用的内存区域不同,一般预留的内存功能和Linux 内核的 DMA 或者 CMA 紧密相关

如果某个节点的compatible属性为shared-dma-pool,则表示该节点内存区域用作一组设备的 DMA 缓冲区共享池,此时,如果看到节点属性中有no-map,则表示该内存不能被 Linux 内核映射为系统内存的一部分,需要从系统内存中分离出来,如果看到节点属性中有reusable属性,则表示该内存不用从系统内存分离出来,当特定驱动不使用这些内存的时候,OS 可以使用这些内存。

注意的是,一个节点不能同时有 no-map 和 reusable 属性,因为它们是逻辑上的矛盾关系。

我们看到reserved-memory节点下的每个子节点都包含no-map属性,说明这些内存不能被Linux 内核用作系统内存,实际上是留给 M4 的系统使用的。

mcuram2

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

0x10000000 是 SRAM1 的起始地址,0x40000 的大小刚好 256KB,这段区域是 SRAM1+SRAM2 区域,这段区域主要用来保存M4固件的代码段和数据段:SRAM1(代码)和SRAM2(数据),可以从M4工程的链接脚本看出,不过也可以通过修改链接脚本,重新划分地址范围,但是要注意的是,链接脚本的地址范围要和设备树配置的范围一致。

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;
};

vdev0vring0vdev0vring1vdev0buffer子节点刚好在 SRAM3 处,即 IPC 缓冲区,这三个节点分配如下:

  • vdev0vring0子节点,0x10040000 是 vring0 的起始地址,地址长度为0x1000,即 4KB,同样的。
  • vdev0vring1是 vring1 的起始地址,地址长度也是0x1000,即 4KB。这两个节点就是我们前面说的用于发送和接收消息的vring。
  • vdev0buffer子节点,起始地址为 0x10042000,地址长度为 0x4000,大小为 16KB,这段地址落在SRAM3中,这就是设置的共享的内存区域。

vdev0vring0vdev0vring1vdev0buffer只占用了 SRAM3 的前 24KB,SRAM3 有 64KB,并未使用完,所以如果有需要,也可以通过修改设备树和链接脚本来将SRAM3未使用到的地址用作其它功能。

mcuram

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

mcuram子节点起始地址是 0x30000000,地址长度为 0x40000,大小为 256KB,这段地址是RAM aliases 里的SRAM1和SRAM2区域,因为RAM aliases和SRAMs的物理地址是一样的,所以也需要配置对于A7“可见”的对应区域,这段区域对应的是M4“可见”的mcuram2区域。因为 mcurammcuram2 的物理地址一样,所以这两段存储区域的功能是一样的,可以说mcuram 是mcuram2 的别名存储区域 ,这可能就是RAM aliases中 aliases 的由来,要注意的是,在设备树中,mcurammcuram2内存段定义必须是一致的。

retram

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

retram子节点的起始地址是 0x38000000,地址长度 0x10000 为 64KB,属于 RAM aliases里的RETRAM区域,此区域和BOOT存储区域的RETRAM区域对应,它们是同一个物理地址。RETRAM 用于存放 M4 内核的中断向量表(中断向量表从0x00000000 开始),默认情况下,RETRAM起始地址为0x38000000,它会重新映射到0x00000000以执行M4的代码。

总结

SRAM的地址分配情况
SRAM的地址分配情况

子节点地址大小区域
mcuram20x10000000~0x10040000256KBM4 可见的 SRAM1+SRAM2
vdev0vring00x10040000~0x100410004KBSRAM3
vdev0vring10x10041000~0x100420004KBSRAM3
vdev0buffer0x10042000~0x1004600016KBSRAM3
mcuram0x30000000~0x30040000256KBA7 可见的 SRAM1+SRAM2
retram0x38000000~0x3801000064KBA7 可见的 RETRAM

A7和M4为了方便地址的同一管理,对于同一个真实芯片的物理地址,在A7和M4中可能有不同的地址映射,比如A7是从0X38000000开始,且这部分叫做mcuram,而在M4眼中是从0X10000000开始,这一部分叫做mcuram2,但是其实这两个地址和其涵盖的长度最后都指向同一个真实的存储芯片。

m4_system_resources

stm32mp151.dtsi设备树文件

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";
};
};
};

以上设备树节点中有个m4_system_resources子节点(也就是M4的资源管理器),它是用于配置M4的外设资源的,其中compatible属性中的rproc-srm-core会匹配到内核源码源码的drivers/remoteproc/rproc_srm_core.c驱动文件,如下是rproc_srm_core.c文件的部分代码:

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

当设备和驱动匹配成功以后,platform_driver 的probe 成员变量所代表的函数rproc_srm_core_probe()被执行,通过该函数实现注册 rproc 子设备(rproc代表一个物理远程处理器设备,可以说是一个外设)

内核源码的stm32mp157-m4-srm.dtsi设备树文件,如下,&m4_rproc表示在前面的m4_rproc节点下追加内容:

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";
};
/* 省略部分代码 */
m4_adc: adc@48003000 {
compatible = "rproc-srm-dev";
reg = <0x48003000 0x400>;
clocks = <&rcc ADC12>, <&rcc ADC12_K>;
clock-names = "bus", "adc";
status = "disabled";
};

/* 省略部分代码 */
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";
};
};
};

以上代码配置的就是M4的系统资源,即M4配置了哪些外设,不过status属性都是disabled,也就是虽然配置了外设,但是不使能外设。

stm32mp157d-atk.dtsi文件 include 了stm32mp157-m4-srm.dtsi文件

stm32mp157d-atk.dts文件又 include 了stm32mp157d-atk.dtsi文件

所以可以直接在stm32mp157d-atk.dtsistm32mp157d-atk.dts设备树文件中选择使能M4的某个外设。

M4和A7有些外设是共享的,例如GPIO是共享的资源,如果此GPIO没有复用做其它功能,只是单纯当做普通的IO使用,那么A7和M4都可以访问这些资源。例如在stm32mp157d-atk.dtsi下有配置了一个蜂鸣器和两个led节点,它们使用的是GPIO功能,这些节点是给A7用的,但M4也可以使用:

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";
};
};

如果是具有单选功能的外设,也就是这些外设要么只能单独给A7使用,要么只能单独给M4使用,如果A7和M4都一起使用该外设,就会存在资源争用问题,某一方就会出现异常(主处理器有一定的优先权,一般是协处理器这边出现异常),例如,如果A7和M4一起占用ADC1来采集数据,这个时候M4这边采集到的数据会不准确,可能采集不到数据而显示0。

对于单选的外设:

  • 如果A7要使用该外设的话,在设备树下一定要配置A7对应的外设节点
  • 如果该外设要给M4使用的话,设备树下可以不必配置M4相关的节点,只需要在固件中配置该外设即可(也就是在裸机程序中配置)当A7加载和启动固件后,M4就可以使用该外设了。

如果已经在设备树下配置A7对应的某个外设节点,M4想使用此外设的话,最好将设备树下A7占用的相关节点注释掉。例如在stm32mp157d-atk.dtsi设备树下有如下节点:

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";
};
};

以上代码段中表示将ADC1分配给A7使用,如果M4要使用的话,这段代码可以不用注释掉,只要确保 Linux系统 运行以后A7不去操作ADC1,那么,当加载和运行M4的固件后(固件中已经配置了ADC1),M4就可以使用ADC1来采集数据了。

但是,如果此时A7去操作ADC1的话,M4这边ADC1采集到的数据就会不准确。因此建议注释掉:

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"; /* 使能M4的ADC */
};

也就是将A7占用的ADC1部分注释掉。后面&m4_adc节点部分是手动添加的,这段可以添加也可以不添加,不过按照ST的标准来,最好要添加,设备树stm32mp157c-dk2-m4-examples.dts是模板文件,修改设备树的时候,可以参考模板文件,以上修改的&m4_adc节点就是参考此文件来写的。

修改好设备树以后,执行如下指令重新编译设备树:

1
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs

再将编译出来的stm32mp157d-atk.dtb文件拷贝到开发板文件系统的/boot目录下,替换掉以前的设备树二进制文件,再执行 sync 指令以同步缓存,然后重启开发板,重新进入Linux操作系统后,A7就不能再去操作ADC1了,当加载和启动M4固件后,M4可单独访问ADC1。

链接脚本

链接脚本语法

入口地址

1
2
/* Entry Point */
ENTRY(Reset_Handler)

ENTRY(SYMBOL),表示将符号 SYMBOL 的值设置成入口地址,即程序执行的第一条指令的地址。

内存区域定义

链接器在默认状态下可以为 section 分配任意位置的存储区域,使用 MEMORY 命令可以用它来描述哪些内存区域可以被链接器使用,哪些内存区域避免使用。一个链接脚本最多可以包含一次 MEMORY 命令。

1
2
3
4
5
6
MEMORY 
{
/* 名称 权限 起始地址 长度 */
NAME [(ATTR)] : ORIGIN = ORIGIN, LENGTH = LEN
}

  • NAME 是在链接脚本中引用内存区域的名字,每块内存区域有一个唯一的名字

  • ORIGIN是起始地址

  • LENGTH 是地址的长度

  • ATTR:可选的属性字符串,用于限制哪些输入段可以放入该区域。支持的字符包括:

    • r:只读段(Read-only section)
    • w:可读写段(Read/Write section)
    • x:可执行代码段(Executable section)
    • a:需要分配内存的段(Allocatable section)
    • il:已初始化的段(Initialized section)
    • !:反转属性(表示放入不满足该字符之后任何属性的段)

使用:一旦定义了一个内存区域,就可以在段描述中使用>region指示链接器将特定的输出段放入该区域中。

1
2
3
4
5
6
7
SECTIONS
{
.text :
{
*(.text)
} > mem
}

段链接定义

SECTIONS 命令是链接脚本里非常重要的命令,它的作用是:告诉链接器,如何把输入文件的 sections 映射到输出文件的各个 section,如何把输出 section 放入地址空间

跟 MEMORY 命令一样,一个链接脚本里只有一个 SECTIONS 命令。如果整个链接脚本内没有 SECTIONS 命令, 那么链接器将所有同名输入 section 合成一个输出 section 内, 各输入 section 的顺序为它们被链接器发现的顺序。

1
2
3
4
5
6
7
SECTIONS 
{
.text :
{
start.o (.text) 6 *(.text*)
} >region
}

一些常见的用法:

  • . = ALIGN(4):表示 4 字节地址对齐。也就是说段的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。

  • PROVIDEPROVIDE_HIDDEN关键字:表示在链接脚本文件中定义一个符号,这个符号没有被目标文件定义,但是被目标文件引用。

  • KEEP():KEEP() 的作用是当启用连接器的–gc-sections垃圾回收选项时,这部分不能被回收。如KEEP(*(.text))表示不能将所有的.text 段当做垃圾回收。

  • /DISCARD/:是一个特殊的段名,如果使用这个段名作为输出,那么所有符合条件的段都被丢弃。

关于更多链接脚本语法,参考本站:

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 */
/* 中断向量表,放在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) }

}

分析:

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. 核心配置解析

    • 程序入口:指定Reset_Handler为程序入口,该函数在启动文件startup_stm32mp15xx.s中定义。

    • 堆栈顶地址:设置堆栈最高地址为0x10040000。它决定了栈指针(SP)的初始位置,对应内部 SRAM 的映射边界。

    • 堆栈大小:指定最小空间。堆(Heap)大小为 512B,栈(Stack)大小为 1KB

  2. 内存区域(MEMORY)定义

    • m_interrupts(0x00000000 ~ 0x00000298):映射至 RETRAM 区域,用于存放 M4 内核的中断向量表。

    • m_text(0x10000000 ~ 0x10020000):对应 SRAM1(128KB),用于存放代码段(Code)。

    • m_data(0x10020000 ~ 0x10040000):对应 SRAM2(128KB),用于存放数据段(Data)。

链接脚本将程序执行地址规划在 SRAM 中:

  1. 双核协同下的 SRAM 分配逻辑

M4 可用的 SRAM 总空间为 SRAM1 ~ SRAM4(共 384KB),地址范围0x10000000 ~ 0x1005FFFF。分配时需注意运行模式:

  • 单核模式(仅 M4 运行):SRAM1 ~ SRAM4 可完全分配给 M4。
  • 双核模式(A7 + M4 运行)
    • SRAM1 & SRAM2:完全分配给 M4(存放 M4 的代码和数据)。
    • SRAM3:由 A7 和 M4 共享。其中0x10040000 ~ 0x10046000默认作为双核通信的内存交换区(IPC Shared Memory)。具体分配比例需参考 Linux 下的设备树(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
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;
};
};

而 m_interrupts 其实是在 RETRAM 里,剩下的 SRAM4 用于做什么呢?

  • 如果不跑 Linux 操作系统,只是跑 M4 裸机程序的话,M4 内核完全可以使用这部分区域,由用户来指定。

  • 如果跑 Linux操作系统,在 Linux 设备树下已经默认将 SRAM4 当做了 Linux 功能的 DMA 了,如果要释放这部分区域,在设备树下将对应节点删除释放即可(但不建议这么做,A7 可能会异常)

几个SRAM区域
几个SRAM区域

综合以上分析,总结如下:

  • 如果不跑 A7,只运行 M4(M4 可以跑裸机、RTOS):SRAM1~SRAM4 可以完全分配给M4;
  • 如果同时跑 A7 和 M4(例如双核通信):SRAM1 和 SRAM2 是单独给 M4 用的,SRAM3的部分地址是 M4 和 A7 一起使用的,SRAM4 在 Linux 下单独配置了 DMA,即被 A7 占用了。 如果要修改 MEMORY 中的地址区域范围,一定要联系内存映射表的地址范围来修改。

参考资料