时间轴

时间轴

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

远程处理器的资源包含:

  • 远程处理器在上电之前需要的系统资源,例如分配给远程处理器的连续物理内存,还有分配给远程处理器的外围设备(这些设备可以是保留的、未使用的),这些资源在 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 的设备地址。
align 16 16 对齐大小。vring 消费者(Consumer)和生产者(Producer)部分之间的对齐字节数。
num 16 16 缓冲区数量。该 vring 支持的 buffer 数量(必须是 2 的幂)。
notifyid 0
(master→remote)
1
(remote→master)
通知 ID。整个远端处理器(rproc)范围内唯一的通知索引。给远端发送信号(kick)时,通过该 ID 让其知道是哪个 vring 被触发了。
reserved 0 0 保留字段。必须为 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 当 master GNUC && !LINUX_RPROC_MASTER
&& VIRTIO_MASTER_ONLY
GCC 不在启动时初始化带 section 属性的全局变量,所以 memset 清零后运行时逐字段填
GCC + M4 当 slave GNUC && !LINUX_RPROC_MASTER while(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

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

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

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

参考资料