Timeline
Timeline
2026-06-22
init
This article introduces the implementation principles and core data structures of the virtio-based rpmsg bus driver in the Linux kernel. It discusses the basic communication model of rpmsg and provides a detailed analysis of rpmsg_device、rpmsg_endpoint creation and destruction logic, as well as the underlying transmission mechanisms in the virtproc_info private state, including virtqueue send/receive channels, buffer management, DMA mapping, and concurrency control.
linux 5.10.238

rpmsg structure
rpmsg has three layers:
1 | ┌────────────────────────────────────────┐ |
virtio_rpmsg_bus.cIn the middle and lower layers, its responsibilities are:
- Take over virtio rpmsg devices
- Send and receive messages using vring
- Create/destroy rpmsg_device based on name service
It producesrpmsg_device, but does not consume. The consumer isrpmsg_driver。
module_init/module_exit
1 | static struct virtio_device_id id_table[] = { |
virtio_rpmsg_bus.cis in the Linux kernel virtio-based rpmsg bus driver. Its role is:
To allow the Linux main core and remote processors to send and receive rpmsg messages via the virtio vring mechanism, and to abstract remote services into Linux’s
rpmsg_devicefor binding and use by upper-layer rpmsg drivers.
This file itself is the virtio transport implementation of the rpmsg bus. It does not care about specific business protocols, such as audio, sensors, TEE, MCU control, etc.; it is only responsible for delivering messages to the corresponding endpoint.
Basic communication model
rpmsg
The communication unit of rpmsg is:
rpmsg_devicerpmsg channel device, a communication channel (channel), which is a ‘device’rpmsg_driverThe driver that handles the business of this channelrpmsg_endpointThe actual send/receive endpoint on the channel (address + callback)
The relationship among the three:
1 | rpmsg_driver ←—— 匹配 ——→ rpmsg_device |
rpmsg_device is the ‘driven-managed device’ in the middle. When creatingrpmsg_devicewill trigger the probe function for matching, and call upon successful matchrpmsg_driver->probefunction
struct rpmsg_device
1 | struct rpmsg_device |
wherestruct rpmsg_devicedefined as follows:
1 | /** |
struct rpmsg_device_opsrepresents arpmsg_device's operation
1 | /** |
A rpmsg_device is equivalent to a logical channel, for example:
channel name: “rpmsg-demo”src: local endpoint addressdst: remote endpoint address
Invirtio_rpmsg_bus.cin, the channel is created as follows (rpmsg_create_channel()):
1 | rpdev->src = chinfo->src; |
That is: the remote end announces a service “rpmsg-demo”, Linux creates arpmsg_deviceto represent this channel; or the local end actively creates a service “rpmsg-demo”, that is, creates arpmsg_deviceto represent the local channel. After announcing, the NS service notifies the remote end to also create a channel for “rpmsg-demo”. The local and remoterpmsg_device's src and dst are opposite to each other, communicating through this channel.
Actuallyvirtio_rpmsg_bus.cthe abstracted rpmsg channel descriptor in is
1 | /** |
which is arpmsg_deviceplus the core data structurestruct virtproc_info *vrp, which is the key data structure for virtio to implement the rpmsg bus
struct rpmsg_endpoint
rpmsg_deviceis like a telephone line + an extension service, whilerpmsg_endpointrepresents the person who actually answers the phone
1 | /** |
The logic related to rpmsg_endpoint is mainly inrpmsg_core.c, among which the most important isrpmsg_rx_cb_t cbandconst struct rpmsg_endpoint_ops *opsthese two members:
1 | typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32); |
This function represents the callback function triggered when the ept receives a message
1 | /** |
represents the operation function of rpmsg_endpoint.
endpoint creation
The core function is:__rpmsg_create_ept(), which is the operation function of rpmsg_device
1 | /* for more info, see below documentation of rpmsg_create_ept() */ |
- Allocate
struct rpmsg_endpoint - Initialize reference count and callback lock
- Record callback, private data, ops
- Assign local address to endpoint
- Insert
vrp->endpointsThis idr
Key logic:
1 | if (addr == RPMSG_ADDR_ANY) { |
- If the caller does not specify an address, allocate dynamically
- Dynamic address starts from
1024Start 0 ~ 1023Reserved for predefined services
And the reserved address definition:
1 |
endpoint address is assigned viaidr_alloc()Assignment:
1 | id = idr_alloc(&vrp->endpoints, ept, id_min, id_max, GFP_KERNEL); |
endpoint destruction
Core function:__rpmsg_destroy_ept(), which is the operation function of rpmsg_endpoint
- from
idrDelete endpoint in - Set callback to
NULL - Decrement reference count, release endpoint if necessary
Key code:
1 | /** |
Pay special attention to concurrency here:
- Delete idr: prevent new RX messages from finding this endpoint
- Set
cb = NULL: prevent in-flight RX that has already obtained the endpoint from calling the callback kref: prevent the endpoint from being released while the RX path is using it
struct virtproc_info
Core data structure:struct virtproc_info
1 | struct virtproc_info { |
This is the private state of the entire virtio rpmsg device, attached tovdev->priv = vrp, divided by function
- virtio basics:
vdevunderlying virtio device
- TX/RX channels:
rvq,svqRX/TX virtqueue
- buffer management:
rbufs,sbufsRX/TX buffer virtual addressnum_bufstotal buffer countbuf_sizesingle buffer sizelast_sbufTX frontier cursorbufs_dmabuffer DMA base address
- TX synchronization:
tx_lockprotects svq/sbufs/sleeperssendqwait queue for waiting on TX buffersleeperswaiter count (controls tx-complete interrupt switch)
- endpoint management:
endpointsendpoint idr (lookup by address)endpoints_lockprotects the endpoint table
- name service:
ns_eptname service endpoint (addr 53)
struct virtio_device *vdev
Function: points to the underlying virtio device. It is the root of this rpmsg instance. Through it, you can get:
- vdev->dev device node (dev_err/dev_for dbg)
- vdev->config virtio config operations (reset/del_vqs etc.)
- vdev->priv points back to vrp itself
set in probe:
1 | vrp->vdev = vdev; |
In the callback, it relies on this to retrieve vrp:
1 | struct virtproc_info *vrp = rvq->vdev->priv; |
struct virtqueue *rvq, *svq
Function:
- rvq = receive virtqueue: remote -> Linux
- svq = send virtqueue: Linux -> remote
These are the two channels for receiving and sending. In probe:
1 | vrp->rvq = vqs[0]; /* input */ |
- Receive message:
virtqueue_get_buf(rvq)/virtqueue_add_inbuf(rvq) - Send message:
virtqueue_add_outbuf(svq)/virtqueue_get_buf(svq)
void *rbufs, *sbufs
Function:
rbufs: starting kernel virtual address of the RX buffer region
sbufs: starting kernel virtual address of the TX buffer region
In probe, a whole block of DMA memory is cut in half:
1 | vrp->rbufs = bufs_va; /* First half RX */ |
As shown in the figure
1 | bufs_va |
get_a_tx_buf()It means taking the TX buffer by index from sbufs:
1 | ret = vrp->sbufs + vrp->buf_size * vrp->last_sbuf++; |
unsigned int num_bufs
Function: total number of RX + TX buffers (each takes up half). Calculated in probe based on vring size:
1 | if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS / 2) |
Therefore:
1 | RX buffer 数 = num_bufs / 2 |
Used in many placesnum_bufs / 2as a boundary, for exampleget_a_tx_buf():
1 | if (vrp->last_sbuf < vrp->num_bufs / 2) |
unsigned int buf_size
Function: number of bytes per buffer, currently fixed at 512 (MAX_RPMSG_BUF_SIZE)。
In the probe function
1 | vrp->buf_size = MAX_RPMSG_BUF_SIZE; |
It determines:
- Address stride of each buffer:
sbufs + buf_size * i - Payload limit of a single message
1 | if (len > vrp->buf_size - sizeof(struct rpmsg_hdr)) |
int last_sbuf
Function: TX buffer’s ‘initial allocation cursor’, records which one is sequentially allocated for the first time. Inget_a_tx_buf()used in:
1 | if (vrp->last_sbuf < vrp->num_bufs / 2) |
last_sbuf < num_bufs/2: there are unused new TX buffers, take them in order- Otherwise: all TX buffers have been used, switch to recycling and reusing from the used ring
Once the upper limit is reached,last_sbufit is fixed and no longer grows, subsequently relying entirely onvirtqueue_get_buf()recycling.
dma_addr_t bufs_dma
Function: DMA base address of the entire buffer (address from the device/DMA perspective). In probe, bydma_alloc_coherent()returns:
1 | bufs_va = dma_alloc_coherent(vdev->dev.parent, |
rbufs/sbufsis the virtual address used by the CPU, bufs_dma is the physical/bus address used for releasing and DMA mapping. It is used to release during remove:
1 | dma_free_coherent(vdev->dev.parent, total_buf_space, |
struct mutex tx_lock
Function: Protects the shared state on the sending side: svq, sbufs, sleepers. Allows multiple senders to call concurrentlyrpmsg_send(). A mutex is used because sending may need to wake up a “dozing” remote processor, a process that may sleep, so a sleepable mutex must be used.
struct idr endpoints
Function: idr index table for all local endpoints, keyed by endpoint address. Used to quickly look up endpoints by address. This is the core data structure for message distribution. When creating an endpoint, an address is allocated and inserted:
1 | id = idr_alloc(&vrp->endpoints, ept, id_min, id_max, GFP_KERNEL); |
When receiving a message, look up by dst:
1 | ept = idr_find(&vrp->endpoints, virtio32_to_cpu(vrp->vdev, msg->dst)); |
So rpmsg is actuallyaddress-based dispatch, relying on this idr.
struct mutex endpoints_lock
Function: Protects concurrent access to the endpoints idr. Must be held when adding, deleting, querying, or modifying endpoints:
1 | mutex_lock(&vrp->endpoints_lock); |
Note that it and tx_lock are two different locks:
-tx_lockprotects the sending path
-endpoints_lockprotects the endpoint table
wait_queue_head_t sendq
Function: Senders waiting for TX buffers sleep on this wait queue. When there are no TX buffers, rpmsg_send() sleeps and waits:
1 | wait_event_interruptible_timeout(vrp->sendq, |
After the remote side consumes the TX buffer, the TX complete callback wakes it up:
1 | static void rpmsg_xmit_done(struct virtqueue *svq) |
atomic_t sleepers
Function: How many senders are currently waiting for TX buffers (waiter count). It coordinates with the dynamic switching of the TX complete interrupt:
1 | /* First waiter: enable tx-complete callback */ |
Design purpose:
- No one waiting for TX buffer: disable tx-complete interrupt to save overhead
- Someone waiting for TX buffer: enable tx-complete interrupt, wake up immediately after remote returns buffer
(This is the design for 5.10; in Linux 7.1.1, sleepers were removed due to the introduction of poll support.)
struct rpmsg_endpoint *ns_ept
Purpose: dedicated endpoint for name service (fixed address 53). It does not belong to any regular rpmsg channel; it is used internally by the bus to handle remote service ‘create/destroy’ notifications. Created in probe when the remote supports the NS feature:
1 | if (virtio_has_feature(vdev, VIRTIO_RPMSG_F_NS)) { |
Its callback isrpmsg_ns_cb(), responsible for creating/destroying rpmsg_device according to NS messages. Destroyed separately during remove:
1 | if (vrp->ns_ept) |
Note that rpdev is passed as NULL during creation because it is not bound to a specific channel.
Buffer design
The driver uses fixed-size buffers. Related definitions:
1 |
- Up to 512 buffers
- Half RX, half TX
- Each buffer is 512 bytes
- Maximum total memory 512 * 512 = 256 KiB
In other words:
1 | 总 buffer 区域 |
Allocated in probe:
1 | bufs_va = dma_alloc_coherent(vdev->dev.parent, |
Then split:
1 | vrp->rbufs = bufs_va; |
Each sent message has a common header:
1 | struct rpmsg_hdr { |
Meaning:
| Field | Meaning |
|---|---|
src | Source endpoint address |
dst | Destination endpoint address |
reserved | Reserved |
len | Payload length |
flags | Message flags |
data[] | Actual payload data |
After Linux receives a message, it will find the local endpoint based on thedstaddress, and then call the callback of that endpoint.
get_a_tx_buf()
Code:
1 | /* super simple buffer "allocator" that is just enough for now */ |
This function is a very simple TX buffer allocator.
It has two stages:
1 | 阶段 1:还有从未使用过的 TX buffer |
last_sbufOnly useful in the first stage.
Once:
1 | vrp->last_sbuf == vrp->num_bufs / 2 |
Then it always proceeds:
1 | virtqueue_get_buf(vrp->svq, &len); |
In other words:
1 | last_sbuf = 一次性“开荒游标” |
virtio API
| API | Function |
|---|---|
virtio_find_vqs() | Find/Create virtqueue |
virtqueue_add_inbuf() | Give the device a “writable” buffer |
virtqueue_add_outbuf() | Give the device a “readable” buffer |
virtqueue_add_sgs | The generic buffer add function includes in and out |
virtqueue_get_buf() | Retrieve the buffer processed by the device from the used ring |
virtqueue_kick() | Notify the other party: the queue has a new buffer |
virtqueue_kick_prepare() | Determine whether notification is needed |
virtqueue_notify() | Actually send the notification |
virtqueue_enable_cb() | Enable virtqueue callback/interrupt |
virtqueue_disable_cb() | Disable virtqueue callback/interrupt, used to prevent interrupt storms during polling |
virtqueue_get_vring_size() | Get ring size |
virtio_has_feature() | Check virtio feature |
virtio_device_ready() | Set DRIVER_OK, the device can start working |
probe function
1 | static struct virtio_driver virtio_ipc_driver = { |
virtio_driver.probeThe function is defined as:
1 | static int rpmsg_probe(struct virtio_device *vdev) |
The analysis is as follows
Allocatevirtproc_info
1 | vrp = kzalloc(sizeof(*vrp), GFP_KERNEL); |
Initialization:
1 | idr_init(&vrp->endpoints); |
Find virtqueue
The driver needs two virtqueues:
1 | vq_callback_t *vq_cbs[] = { rpmsg_recv_done, rpmsg_xmit_done }; |
Then:
1 | err = virtio_find_vqs(vdev, 2, vqs, vq_cbs, names, NULL); |
Two queues:
1 | vrp->rvq = vqs[0]; |
Meaning:
| virtqueue | Usage | callback |
|---|---|---|
rvq/ input | Linux receives messages from the remote end | rpmsg_recv_done() |
svq/ output | Linux sends a message to the remote end | rpmsg_xmit_done() |
Calculate the number of buffers
1 | if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS / 2) |
In other words: if the vring is small, reduce the number of buffers according to the vring’s capacity; otherwise, use a maximum of 512 buffers
Allocate coherent DMA buffer
1 | bufs_va = dma_alloc_coherent(vdev->dev.parent, |
This buffer is shared DMA memory accessible by both the main core and the virtio backend.
Pre-load RX buffers into the RX virtqueue
1 | for (i = 0; i < vrp->num_bufs / 2; i++) { |
This step is critical:
Linux first places a batch of empty RX buffers into the available ring, so that the remote processor can later write messages into these buffers.
Disable TX complete interrupt by default
1 | virtqueue_disable_cb(vrp->svq); |
Because most of the time, the sender does not need an interrupt every time a TX buffer is consumed by the remote end. The TX complete interrupt is only temporarily enabled when Linux has no TX buffers and the sender needs to sleep and wait.
Create name service endpoint
If the remote end supports it:
1 | VIRTIO_RPMSG_F_NS |
then create an endpoint with the address53as the name service endpoint:
1 | vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, rpmsg_ns_cb, |
Address definition:
1 |
This endpoint specifically handles remote service creation/destruction notifications.
Device ready and notify the remote end
1 | notify = virtqueue_kick_prepare(vrp->rvq); |
The order here is important:
- RX buffers are ready (
virtqueue_kick_prepare) - virtio device set to ready
- Notify the remote end that it can start sending messages
Finally print:
1 | dev_info(&vdev->dev, "rpmsg host is online\n"); |
remove function
1 | static int rpmsg_remove_device(struct device *dev, void *data) |
Process:
- reset virtio device
1 | vdev->config->reset(vdev); |
First, stop the device to prevent further transmission and reception.
- Delete all child rpmsg devices
1 | device_for_each_child(&vdev->dev, NULL, rpmsg_remove_device); |
Inrpmsg_remove_device()Called within:
1 | device_unregister(dev); |
- Destroy NS endpoint
1 | if (vrp->ns_ept) |
- endpoints、virtqueue、DMA buffer、vrp
1 | idr_destroy(&vrp->endpoints); |
Send message(rpmsg_send_offchannel_raw())
All sending APIs will eventually enterrpmsg_send_offchannel_raw()
1 | /** |
The call chain is roughly:
1 | rpmsg_send() |
Parameter check
First check src/dst:
1 | if (src == RPMSG_ADDR_ANY || dst == RPMSG_ADDR_ANY) |
Broadcast addresses are not allowed as actual sending addresses.
Then check the length:
1 | if (len > vrp->buf_size - sizeof(struct rpmsg_hdr)) |
Because a single buffer is fixed at 512 bytes, the payload cannot exceed512 - sizeof(struct rpmsg_hdr)
Get TX buffer
1 | msg = get_a_tx_buf(vrp); |
Its logic:
1 | if (vrp->last_sbuf < vrp->num_bufs / 2) |
Meaning:
- Initial phase: directly take an unused buffer from the TX buffer pool
- Subsequent phase: from
svqReclaim TX buffers that have been consumed by the remote end from the used ring
What to do when there is no TX buffer?
Iftrysend:
1 | if (!msg && !wait) |
If it is a normalsend, then wait, up to 15 seconds:
1 | err = wait_event_interruptible_timeout(vrp->sendq, |
If timeout:
1 | return -ERESTARTSYS; |
Before waiting, it will call:
1 | rpmsg_upref_sleepers(vrp); |
Function: If this is the first sender sleeping and waiting for a TX buffer, enable the TX complete interrupt
1 | virtqueue_enable_cb(vrp->svq); |
Called after waiting ends:
1 | rpmsg_downref_sleepers(vrp); |
If there are no more waiters, disable the TX complete interrupt:
1 | virtqueue_disable_cb(vrp->svq); |
Fill the rpmsg header and payload
1 | msg->len = cpu_to_virtio16(vrp->vdev, len); |
Note the use ofcpu_to_virtio16/32(), because virtio devices may have specific endianness requirements.
Add to TX virtqueue and kick the remote end
1 | virtqueue_add_outbuf(vrp->svq, &sg, 1, msg, GFP_KERNEL); |
Then:
1 | virtqueue_kick(vrp->svq); |
Complete meaning:
Linux fills the TX buffer → adds the buffer to the output virtqueue → kicks the remote processor → the remote end takes the message from the virtqueue
TX complete callback
Set in the probe function
1 | static int rpmsg_probe(struct virtio_device *vdev) |
i.e., the tx callback function isrpmsg_xmit_done
Note: The TX complete interrupt is usually disabled; it is only enabled when a sender is sleeping and waiting for a buffer.
1 | /* |
When the remote end consumes the TX buffer, the virtio backend puts the buffer into the used ring and triggers the TX complete callback. Linux wakes up the sending thread waiting for the TX buffer.
TX sleepers mechanism:rpmsg_upref_sleepers()
1 | static void rpmsg_upref_sleepers(struct virtproc_info *vrp) |
Meaning: Before the sending thread prepares to sleep because there is no TX buffer:sleepers++, if this is the first waiter, enable the TX complete callback
Correspondingly, there is alsorpmsg_downref_sleepers(): After the sending thread wakes up:sleepers--, if this is the last waiter, disable the TX complete callback
The purpose of this mechanism:
- No one waiting for TX buffer: disable the TX complete interrupt to reduce overhead
- Someone waiting for TX buffer: enable the TX complete interrupt to immediately wake up the waiter after the remote end returns the buffer
Complete chain:
1 | TX buffer 用完 |
Receive message (rpmsg_recv_done)
Set in the probe function
1 | static int rpmsg_probe(struct virtio_device *vdev) |
i.e., the rx callback function isrpmsg_recv_done, the function is defined as follows:
rpmsg_recv_done
1 | /* called when an rx buffer is used, and it's time to digest a message */ |
Fetch used buffer from RX virtqueue
1 | msg = virtqueue_get_buf(rvq, &len); |
If a msg is obtained, try to continue, looping to process all available messages:
1 | while (msg) { |
Single message processing:rpmsg_recv_single()
1 | static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev, |
Upon reception, distribution is not based on channel name, but on:
msg->dst, invrp->endpointslook up the local endpoint. Therefore, rpmsg actually operates more like address-based messaging at runtime; the channel name is mainly used for device discovery and driver matching.
Main steps:
- Parse payload length
1 | msg_len = virtio16_to_cpu(vrp->vdev, msg->len); |
- Check if the message length is valid
1 | if (len > vrp->buf_size || |
Prevent out-of-bounds access caused by abnormal lengths passed from the remote end.
- According to
dstFind endpoint
1 | ept = idr_find(&vrp->endpoints, virtio32_to_cpu(vrp->vdev, msg->dst)); |
This is the core of rpmsg distribution:msg->dst == local endpoint address
- Increment endpoint reference count
1 | if (ept) |
Prevent the endpoint from being released during callback execution.
- Invoke callback
1 | if (ept->cb) |
Parameters passed to the upper-layer callback include:
| Parameter | Meaning |
|---|---|
ept->rpdev | Corresponding rpmsg device |
msg->data | payload |
msg_len | Payload length |
ept->priv | Endpoint private data |
msg->src | Remote source address |
- Put the RX buffer back into the virtqueue
After processing, this buffer needs to be given back to the remote for use:
1 | rpmsg_sg_init(&sg, msg, vrp->buf_size); |
kick the remote
Finally, inrpmsg_recv_done()if messages have been processed, kick the remote:
1 | if (msgs_received) |
name service mechanism
Note: name service is an optional feature. Feature definition:
1If the remote does not support this feature, dynamic service discovery will be unavailable. In this case, the channel may need to be created statically.
rpmsg supports the remote dynamically notifying Linux: I have created a service or I have destroyed a service, this is called name service. Related structure:
1 | struct rpmsg_ns_msg { |
Fields:
| Field | Meaning |
|---|---|
name | Service name |
addr | Remote service address |
flags | Create or destroy |
flags:
1 | RPMSG_NS_CREATE = 0 |
NS endpoint
name service uses a fixed address:
1 |
Created during probe:
1 | vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, rpmsg_ns_cb, |
Note hererpdevthe parameter isNULL, because the NS endpoint does not belong to a regular rpmsg channel, but is an internal endpoint used by the bus itself.
NS callback:rpmsg_ns_cb()
1 | /* invoked when a name service announcement arrives */ |
After receiving the remote NS message:
- Check length
- Ensure the endpoint is not bound to a real
rpdev - Fix the name string ending
- Construct
rpmsg_channel_info - Create or destroy channel based on flags
Create channel:
1 | newch = rpmsg_create_channel(vrp, &chinfo); |
Destroy channel:
1 | ret = rpmsg_unregister_device(&vrp->vdev->dev, &chinfo); |
Local service announce mechanism
This driver can not only receive remote NS, but also announce local services to the remote end.
Related functions:
1 | virtio_rpmsg_announce_create() |
When an rpmsg channel that needs to be announced is created locally, the driver constructsrpmsg_ns_msg, and sends it to the remote NS address53(inrpmsg_core.cin). Creation notification:
1 | nsm.flags = cpu_to_virtio32(vrp->vdev, RPMSG_NS_CREATE); |
Destruction notification:
1 | nsm.flags = cpu_to_virtio32(vrp->vdev, RPMSG_NS_DESTROY); |
rpmsg channel creation
1 | /* |
It based on the passed-instruct rpmsg_channel_info *chinfocreates arpmsg_device。
1 | /** |
Process:
- Check if the same channel already exists
1 | tmp = rpmsg_find_device(dev, chinfo); |
If it already exists, do not create it again.
- Allocate
virtio_rpmsg_channel
1 | vch = kzalloc(sizeof(*vch), GFP_KERNEL); |
This structure:
1 | struct virtio_rpmsg_channel { |
It is a virtio transport private channel, exposing itsrpmsg_device。
- Fill
rpmsg_device
1 | rpdev->src = chinfo->src; |
Then register:
1 | ret = rpmsg_register_device(rpdev); |
After registration, the rpmsg bus will match the id table of the upper-layer rpmsg driver, and then call the corresponding driver’s probe.
Overall send/receive flow chart
Linux sends a message to the remote end
1 | 上层 rpmsg driver |
Linux receives a message from the remote end
1 | 远端处理器写入 RX buffer |
Remote end publishes service
1 | 远端发送 NS 消息到 addr 53 |
RX buffer lifecycle
RX buffer lifecycle:
1 | probe 时 add_inbuf |
So the RX buffer is recycled.
TX buffer lifecycle
TX buffer lifecycle:
1 | Linux 从 sbufs 初始池拿 buffer |
