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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
@page page_device_rpmsg RPMsg
@subpage page_device_rpmsg_char
@subpage page_device_rpmsg_ns
# RPMsg subsystem
Header: **`components/drivers/include/drivers/rpmsg.h`**. Core bus: **`components/drivers/rpmsg/rpmsg.c`**.
RPMsg is an **endpoint-oriented IPC bus** on top of a **transport** (VirtIO + shared memory + mailbox in the in-tree example). The core matches **`struct rt_rpmsg_device`** to **`struct rt_rpmsg_driver`** by **`id.name`**, then manages **endpoints** (name + src/dst addresses) and **`send`** via **`struct rt_rpmsg_ops`**.
| Layer | File | Role |
| --- | --- | --- |
| **RPMsg bus** | **`rpmsg/rpmsg.c`** | Endpoints, match **`rpmsg-char`** / **`rpmsg-name-service`** |
| **VirtIO RPMsg device** | **`virtio/virtio-rpmsg.c`** | **`rt_rpmsg_ops`**, virtqueues, NS announce |
| **AMP transport** | **`rpmsg/rpmsg-rt-thread-virtio.c`** | Shared mem, mailbox doorbell, VirtIO transport |
| **Class drivers** | **`rpmsg_char.c`**, **`rpmsg_ns.c`** | Char devices + name service |
**Kconfig**: **`RT_USING_RPMSG`**, **`RT_RPMSG_RT_THREAD_VIRTIO`** (default y), **`RT_RPMSG_CHAR_*`**.
---
## Complete stack (VirtIO example)
This is the **reference integration** in-tree: **`rpmsg-rt-thread-virtio.c`** + **`virtio-rpmsg.c`**.
```
DT: rt-thread,virtio-rpmsg (master or slave)
|
v
rpmsg_virtio_probe (platform)
|-- mbox: rt_mbox_request_by_index
|-- shmem: ioremap from "shmem" phandle
|-- slab DMA pool in shared RAM
|-- rt_virtio_device_register (device_id = VIRTIO_DEVICE_ID_RPMSG)
|
v
virtio_rpmsg_probe (virtio driver, RT_VIRTIO_DRIVER_EXPORT)
|-- virtio_rpmsg_vq_init: rx + tx virtqueues
|-- rt_rpmsg_device_register(id.name = "rpmsg-char")
|-- optional: second device "rpmsg-name-service" if VIRTIO_RPMSG_F_NS
|
v
rpmsg bus: match rpmsg_char_driver / rpmsg_ns_driver
|-- rpmsg_char_probe: control dev rpmsg_charN
|-- rpmsg_ns_probe: endpoint name-service @ 0x35
|
v
Application: RT_DEVICE_CTRL_RPMSG_CREATE_EPT or NS_CREATE from remote
|
v
rt_rpmsg_send -> virtio_rpmsg_send -> virtqueue kick -> mbox notify peer
```
### Physical pieces
| Piece | DT / code | Role |
| --- | --- | --- |
| **Shared memory** | **`shmem = <&soc_sram>`** | Descriptor rings + virtio buffers + link registers at base |
| **Mailbox** | **`mboxes = <&mbox N>`** | **`RPMSG_VIRTIO_EVENT_LINK`** (exchange queue pointers) and **`EVENT_QUEUE`** (kick) |
| **`queue-max`** | Both sides **must match** | Virtqueue depth |
| **`master` / `slave`** | Property on node | Master publishes queue offsets; slave reads peer TX ring for RX |
**Boot role**: **`rpmsg.mode=master`** or **`slave`** in **`/chosen` bootargs** (`INIT_CORE_EXPORT rpmsg_mode_setup` in **`rpmsg.c`**). Must match firmware on the other core. **`virtio_rpmsg_probe`** swaps RX/TX queue indices for slave vs master.
---
## Device tree example (master + slave)
```dts
/ {
chosen {
bootargs = "rpmsg.mode=master"; /* or slave on the other OS */
};
soc_sram: memory@60000000 {
reg = <0x60000000 0x100000>;
};
soc_mailbox: mailbox@ ... { /* provider */ };
rpmsg_virtio0: rpmsg-virtio@0 {
compatible = "rt-thread,virtio-rpmsg";
shmem = <&soc_sram>;
mboxes = <&soc_mailbox 0>; /* master uses index 0 */
queue-max = <64>;
master;
};
};
```
Remote side (conceptually):
```dts
rpmsg_virtio1: rpmsg-virtio@0 {
compatible = "rt-thread,virtio-rpmsg";
shmem = <&soc_sram>; /* same region */
mboxes = <&soc_mailbox 1>; /* slave uses different mbox index */
queue-max = <64>;
slave;
};
```
After both sides probe: master writes **`MASTER_QUEUE_*`** offsets; **`EVENT_LINK`** makes slave map **`peer_tx_virtq`** and process inbound RPMsg frames.
---
## Layer 1 — Register transport (`rpmsg-rt-thread-virtio`)
**`RT_PLATFORM_DRIVER_EXPORT(rpmsg_virtio_driver)`** — does **not** register **`rt_rpmsg_device`** itself.
**`rpmsg_virtio_probe`** checklist:
1. **`rt_mbox_request_by_index(&mbox_client, 0)`** — **`rx_callback = rpmsg_virtio_rx_callback`**
2. Parse **`shmem`**, **`rt_ioremap_cached`**
3. **`master`** property → **`is_master`**, feature **`VIRTIO_RPMSG_F_NS`** only on master
4. **`rt_slab_init`** on half of shmem (DMA buffers for virtqueues)
5. **`rt_dma_device_set_ops`** — copy to/from shmem for non-coherent AMP
6. Fill **`rt_virtio_device`**: **`VIRTIO_DEVICE_ID_RPMSG`**, **`rpmsg_virtio_trans`**
7. **`rt_virtio_device_register(vdev)`** → triggers **`virtio_rpmsg_probe`**
**Mailbox ISR** (`rpmsg_virtio_rx_callback`):
- **`EVENT_LINK`**: peer published descriptor ring addresses — wire **`peer_tx_virtq`**
- **`EVENT_QUEUE`**: **`rt_virtqueue_isr`** for local virtqueues
- Peer TX avail ring progress → DMA tag fixup → **`used`** index update (feeds virtio RX path)
---
## Layer 2 — Register RPMsg device (`virtio-rpmsg.c`)
**`RT_VIRTIO_DRIVER_EXPORT(virtio_rpmsg_driver)`**
**`virtio_rpmsg_probe`**:
```c
vrpmsg->parent.id.name = "rpmsg-char";
vrpmsg->parent.ops = &virtio_rpmsg_ops; /* create / destroy / send */
rt_rpmsg_device_register(&vrpmsg->parent);
if (VIRTIO_RPMSG_F_NS) {
vrpmsg->ns->id.name = "rpmsg-name-service";
rt_rpmsg_device_register(vrpmsg->ns);
}
```
### `struct rt_rpmsg_ops` (backend contract)
| Op | **`virtio_rpmsg` behavior** |
| --- | --- |
| **`create_endpoint`** | Local: optional **NS CREATE** message to **`RT_RPMSG_NS_ADDR`**. Remote-created: **`src == RT_RPMSG_ADDR_ANY`** |
| **`destroy_endpoint`** | Optional **NS DESTROY** |
| **`send`** | Build **`struct virtio_rpmsg_hdr`** (src, dst, len), **`rt_virtqueue_add_outbuf`**, **`kick`** |
Wire header (**`virtio-rpmsg.h`**):
```c
struct virtio_rpmsg_hdr {
le32 src, dst, reserved;
le16 len, flags;
u8 data[];
}; /* max payload VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(hdr) */
```
### RX path (`virtio_rpmsg_rx_done`)
1. **`rt_virtqueue_read_buf`** on **RX** queue
2. Find endpoint: **`find_endpoint(rdev, dst=hdr->dst, src=ANY)`**
If **`dst == RT_RPMSG_NS_ADDR`** → use **`vrpmsg->ns`** device
3. **`ept->rx_callback(rdev, hdr->src, data, len)`**
4. Re-queue buffer with **`rt_virtqueue_add_inbuf`**
---
## Layer 3 — RPMsg bus (`rpmsg.c`)
### Register a **class driver** (char / NS / custom)
```c
static const struct rt_rpmsg_device_id my_ids[] = {
{ .name = "rpmsg-char" }, /* must match rdev->id.name from backend */
{ },
};
static struct rt_rpmsg_driver my_driver = {
.ids = my_ids,
.probe = my_probe,
.remove = my_remove,
.rx_callback = my_default_rx, /* fallback if create_endpoint(..., NULL) */
};
RT_RPMSG_DRIVER_EXPORT(my_driver);
```
**`RT_RPMSG_DRIVER_EXPORT`** → **`INIT_DEVICE_EXPORT`** → **`rt_rpmsg_driver_register`** → probes all **`rt_rpmsg_device`** on bus with matching **`id.name`**.
### Register an **`rt_rpmsg_device`** (custom backend)
If you implement a non-VirtIO link:
```c
rdev->id.name = "rpmsg-char"; /* or your id, but char driver won't bind */
rdev->ops = &my_rpmsg_ops;
rt_rpmsg_device_register(rdev);
```
Bus **`probe`**: **`rt_dm_power_domain_attach`** then **`rdrv->probe(rdev)`**.
---
## Endpoints and addressing
```c
struct rt_rpmsg_endpoint_info info;
rt_strncpy(info.name, "tty", RT_RPMSG_NAME_SIZE);
info.src = 0x400; /* local address (assigned by you or NS) */
info.dst = 0x401; /* remote peer */
ept = rt_rpmsg_create_endpoint(rdev, &info, my_rx_cb);
if (rt_is_err(ept))
return rt_ptr_err(ept);
rt_rpmsg_send(ept, buf, len);
```
| API | Role |
| --- | --- |
| **`rt_rpmsg_create_endpoint`** | **`ops->create_endpoint`**, insert on **`ept_nodes`** |
| **`rt_rpmsg_destroy_endpoint`** | Backend destroy + **`rt_free`** |
| **`rt_rpmsg_find_endpoint`** | Match src/dst/name wildcards (**`RT_RPMSG_ADDR_ANY`**) |
| **`rt_rpmsg_send` / `sendto` / `send_wait`** | **`ops->send`** under **`ept->lock`** |
**RX callback**: per-endpoint **`rx_cb`**, or driver-wide **`struct rt_rpmsg_driver::rx_callback`** if **`rx_cb == NULL`**.
---
## Name service (summary)
Well-known address **`RT_RPMSG_NS_ADDR` (0x35)**. Remote **CREATE** can auto-create endpoints (driver default RX logs only). Local **CREATE** from **`virtio_rpmsg_create_endpoint`** announces name+addr to peer when **`VIRTIO_RPMSG_F_NS`**.
Details: @ref page_device_rpmsg_ns.
---
## Userspace / driver channel (summary)
1. Open control device **`rpmsg_charN`** (from **`rpmsg_char` probe**)
2. **`RT_DEVICE_CTRL_RPMSG_CREATE_EPT`** with **`struct rt_rpmsg_endpoint_info`**
3. Open **`rpmsg_<src>x<dst>`**, **`read`/`write`**
Details: @ref page_device_rpmsg_char.
---
## Bring-up checklist
| Step | Check |
| --- | --- |
| 1 | **`RT_USING_RPMSG`**, **`RT_RPMSG_RT_THREAD_VIRTIO`**, mailbox + shmem drivers |
| 2 | Same **`queue-max`**, same **`shmem`** region, correct **`mboxes`** index per role |
| 3 | **`rpmsg.mode=`** bootarg matches remote |
| 4 | Master has **`master;`** property; slave has **`slave;`** |
| 5 | **`virtio_rpmsg_probe`** succeeds → **`rpmsg_char`** / **`rpmsg-name-service`** on bus |
| 6 | Create endpoint or wait for NS CREATE; verify **`rt_rpmsg_send`** returns OK |
| 7 | @ref page_device_hwspinlock / @ref page_device_mailbox ordering if shared with other AMP IPC |
---
## Custom backend (non-VirtIO)
Implement **`struct rt_rpmsg_ops`** and register **`rt_rpmsg_device`** with an **`id.name`** your class driver matches (or reuse **`rpmsg-char`**):
- **`create_endpoint`**: book-keep addresses / shared memory slots
- **`send`**: push frame with src/dst header to remote
- On RX interrupt: parse header → **`rt_rpmsg_find_endpoint`** → call **`ept->rx_callback`**
Do **not** call **`rt_rpmsg_*`** from ISR if your **`send`** sleeps on virtqueue completion — use workqueue.
---
## Pitfalls
| Issue | Mitigation |
| --- | --- |
| **`id.name` mismatch** | Backend **`rpmsg-char`** vs driver table **`rpmsg-char`** / **`rpmsg-raw`** |
| **Master/slave queue swap** | Wrong **`rpmsg.mode`** → no RX |
| **`queue-max` mismatch** | Corrupt rings |
| **NS without feature** | Master must advertise **`VIRTIO_RPMSG_F_NS`** |
| **Endpoint without RX cb** | **`create_endpoint(..., NULL)`** needs driver **`rx_callback`** |
| **Buffer size** | Payload ≤ **`VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(hdr)`** (512 default) |
| **NULL `send` addr** | **`RT_RPMSG_ADDR_ANY`** rejected in **`virtio_rpmsg_send`** |
| **Power domain** | Bus probe attaches domain; **`-RT_EEMPTY`** OK |
---
## See also
- @ref page_device_rpmsg_char
- @ref page_device_rpmsg_ns
- @ref page_device_mailbox
- @ref page_device_hwspinlock
- @ref page_device_platform
- `components/drivers/rpmsg/rpmsg-rt-thread-virtio.c`
- `components/drivers/virtio/virtio-rpmsg.c`
- `components/drivers/virtio/virtio_config/virtio-rpmsg.h`
|