Cover image for Rpmsg Core

Rpmsg Core

Words 7.9k
Views
Visitors

Timeline

Timeline

2026-06-27

init

This article introduces the bus abstraction mechanism of Rpmsg Core in Linux 5.10.23 and its backend access methods, and discusses in detail the device-driver matching scoring rules and device probe process. This article summarizes the core logic in the probe process, such as power domain association, automatic Endpoint creation, and dynamic address allocation, revealing the implementation details of the rpmsg framework in hardware power management and endpoint communication.

linux 5.10.23

rpmsg.drawio
rpmsg.drawio

rpmsg is a bus abstraction that is not bound to any specific transport. virtio is currently the most common implementation in Linux, but theoretically there can be:

  • Direct mapping implementation based on shared memory
  • Implementation based on mailbox interrupts
  • Implementation based on PCIe doorbell

Each backend only needs to provide its ownrpmsg_endpoint_opsandrpmsg_device_ops, to access the rpmsg core. And not all backends need to support all send variants. For example:

  • The simplest backend only needs to implement send and trysend.
  • If the backend does not support explicit specification of src/dst (offchannel), it may not supportsendto/send_offchannel
  • If the backend does not support poll, userspace write operations are still available (blocking/non-blocking mode is guaranteed by therpmsg_send/rpmsg_trysendsemantics).

module_init/module_exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int __init rpmsg_init(void)
{
int ret;

ret = bus_register(&rpmsg_bus);
if (ret)
pr_err("failed to register rpmsg bus: %d\n", ret);

return ret;
}
postcore_initcall(rpmsg_init);

static void __exit rpmsg_fini(void)
{
bus_unregister(&rpmsg_bus);
}
module_exit(rpmsg_fini);

MODULE_DESCRIPTION("remote processor messaging bus");
MODULE_LICENSE("GPL v2");

rpmsg_initfunction usespostcore_initcall, callingbus_registerregister a bus

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
// include/linux/init.h

#define __initcall(fn) device_initcall(fn)

#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

typedef int (*initcall_t)(void);

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ___define_initcall(fn, id, __sec) \
__ADDRESSABLE(fn) \
asm(".section \"" #__sec ".init\", \"a\" \n" \
"__initcall_" #fn #id ": \n" \
".long " #fn " - . \n" \
".previous \n");
#else
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#endif

struct bust_type rpmsg_bus

struct bus_type rpmsg_busdefined as follows:

1
2
3
4
5
6
7
8
static struct bus_type rpmsg_bus = {
.name = "rpmsg",
.match = rpmsg_dev_match,
.dev_groups = rpmsg_dev_groups,
.uevent = rpmsg_uevent,
.probe = rpmsg_dev_probe,
.remove = rpmsg_dev_remove,
};

rpmsg_dev_match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* match rpmsg channel and rpmsg driver */
static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv);
const struct rpmsg_device_id *ids = rpdrv->id_table;
unsigned int i;

if (rpdev->driver_override)
return !strcmp(rpdev->driver_override, drv->name);

if (ids)
for (i = 0; ids[i].name[0]; i++)
if (rpmsg_id_match(rpdev, &ids[i]))
return 1;

return of_driver_match_device(dev, drv);
}
  • Ifrpdev->driver_overridethen only needs to comparerpdev->driver_overrideanddrv->name, i.e., specifying that therpdevforce matching a driver with a corresponding name

  • Ifrpdev->id_talbeexists

    • fromstruct rpmsg_driver *rpdrvtake out fromconst struct rpmsg_device_id *idsThen, iterate through the ids table, throughids[i].nameMatchrpmsg_device, return 1 if the match is successful,

    • otherwise callof_driver_match_deviceMatchstruct device *devandstruct device_driver *drv

of_driver_match_device's matching is done throughstruct device_driver *drvin thedrv->of_match_tableandstruct device *devin thedev->of_nodeas a parameter, calling__of_match_nodeMatch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;

if (!matches)
return NULL;

for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}

return best_match;
}

Through the__of_device_is_compatibleCalculate the score, find the one with the highest scoreconst struct of_device_id *best_match, while__of_device_is_compatibledefined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
/**
* __of_device_is_compatible() - Check if the node matches given constraints
* @device: pointer to node
* @compat: required compatible string, NULL or "" for any match
* @type: required device_type value, NULL or "" for any match
* @name: required node name, NULL or "" for any match
*
* Checks if the given @compat, @type and @name strings match the
* properties of the given @device. A constraints can be skipped by
* passing NULL or an empty string as the constraint.
*
* Returns 0 for no match, and a positive integer on match. The return
* value is a relative score with larger values indicating better
* matches. The score is weighted for the most specific compatible value
* to get the highest score. Matching type is next, followed by matching
* name. Practically speaking, this results in the following priority
* order for matches:
*
* 1. specific compatible && type && name
* 2. specific compatible && type
* 3. specific compatible && name
* 4. specific compatible
* 5. general compatible && type && name
* 6. general compatible && type
* 7. general compatible && name
* 8. general compatible
* 9. type && name
* 10. type
* 11. name
*/
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;

/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}

/* Matching type is better than matching name */
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
score += 2;
}

/* Matching name is a bit better than not */
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}

return score;
}

Base=INT_MAX/2Base = INT\_MAX / 2

PriorityDriving constraint combinationMatching detail conditionsScore calculation formulaFinal score example
1specificcompat&&type&&namecompatMatch andindex=0typeMatch;nameMatchBase(0×4)+2+1Base - (0 \times 4) + 2 + 1Base+3Base + 3 (Highest score)
2specificcompat&&typecompatMatch andindex=0typeMatch; nonenameConstraintBase(0×4)+2+0Base - (0 \times 4) + 2 + 0Base+2Base + 2
3specificcompat&&namecompatMatch andindex=0; nonetypeConstraint;nameMatchBase(0×4)+0+1Base - (0 \times 4) + 0 + 1Base+1Base + 1
4specificcompatcompatMatch andindex=0; nonetypeandnameConstraintBase(0×4)+0+0Base - (0 \times 4) + 0 + 0BaseBase
5generalcompat&&type&&namecompatMatch andindex=1typeMatch;nameMatchBase(1×4)+2+1Base - (1 \times 4) + 2 + 1Base1Base - 1
6generalcompat&&typecompatMatch andindex=1typeMatch; nonenameConstraintBase(1×4)+2+0Base - (1 \times 4) + 2 + 0Base2Base - 2
7generalcompat&&namecompatMatch andindex=1; nonetypeConstraint;nameMatchBase(1×4)+0+1Base - (1 \times 4) + 0 + 1Base3Base - 3
8generalcompatcompatMatch andindex=1; nonetypeandnameConstraintBase(1×4)+0+0Base - (1 \times 4) + 0 + 0Base4Base - 4
More generalized compat…compatMatch andindex=2(later compatible string)Base(2×4)+Base - (2 \times 4) + \dotsContinue to decrease as index increases
9type&&namenonecompatConstraint;typeMatch;nameMatch0+2+10 + 2 + 133
10typenonecompatConstraint;typeMatch; nonenameConstraint0+2+00 + 2 + 022
11namenonecompatConstraint; nonetypeConstraint;nameMatch0+0+10 + 0 + 111 (Lowest Significant Score)
No match / EliminatedAny constraint specified by the driver was not found in the nodeReturn directly000

rpmsg_dev_probe

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
/*
* when an rpmsg driver is probed with a channel, we seamlessly create
* it an endpoint, binding its rx callback to a unique local rpmsg
* address.
*
* if we need to, we also announce about this channel to the remote
* processor (needed in case the driver is exposing an rpmsg service).
*/
static int rpmsg_dev_probe(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
struct rpmsg_channel_info chinfo = {};
struct rpmsg_endpoint *ept = NULL;
int err;

err = dev_pm_domain_attach(dev, true);
if (err)
goto out;

if (rpdrv->callback) {
strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
chinfo.src = rpdev->src;
chinfo.dst = RPMSG_ADDR_ANY;

ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
if (!ept) {
dev_err(dev, "failed to create endpoint\n");
err = -ENOMEM;
goto out;
}

rpdev->ept = ept;
rpdev->src = ept->addr;
}

err = rpdrv->probe(rpdev);
if (err) {
dev_err(dev, "%s: failed: %d\n", __func__, err);
goto destroy_ept;
}

if (ept && rpdev->ops->announce_create) {
err = rpdev->ops->announce_create(rpdev);
if (err) {
dev_err(dev, "failed to announce creation\n");
goto remove_rpdev;
}
}

return 0;

remove_rpdev:
if (rpdrv->remove)
rpdrv->remove(rpdev);
destroy_ept:
if (ept)
rpmsg_destroy_ept(ept);
out:
return err;
}
  1. Power domain association
1
2
3
err = dev_pm_domain_attach(dev, true);
if (err)
goto out;

Function: Associate the device with a Power Management Domain (genpd).

  • In modern SoCs, different peripherals may belong to different power domains and can be independently powered on/off
  • dev_pm_domain_attach(dev, true) A true value indicates: if the device tree specifies the device’spower-domainsproperty, the kernel will try to automatically attach
  • If attach fails (e.g., the power domain does not exist), subsequent initialization is meaningless, exit directly

Placed first because subsequent endpoint creation and driver initialization may all depend on the hardware power being on. If the power domain is not ready, these operations may fail or even cause hardware exceptions.


  1. Auto-create Endpoint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (rpdrv->callback) {
strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
chinfo.src = rpdev->src;
chinfo.dst = RPMSG_ADDR_ANY;

ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
if (!ept) {
dev_err(dev, "failed to create endpoint\n");
err = -ENOMEM;
goto out;
}

rpdev->ept = ept;
rpdev->src = ept->addr;
}

First, determineif (rpdrv->callback)

  • Simple driver: only needs one receive callback, provides callback during registration, the framework automatically creates an endpoint for it
  • Complex driver: may require multiple endpoints, dynamic address management, the callback for such drivers may be NULL, they will manually call it in their own proberpmsg_create_ept(),

Then constructstruct rpmsg_channel_info chipinfo

1
2
3
strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);  // Service name
chinfo.src = rpdev->src; // Local address
chinfo.dst = RPMSG_ADDR_ANY; // Any destination address

Notedst = RPMSG_ADDR_ANYMeaning: when the endpoint is created, it does not bind to a fixed peer address, and can accept messages from any remote address.

Callrpmsg_create_ept()The parameters passed in arestruct rpmsg_device *rpdevrpdrv->callbackpriv = NULLstruct rpmsg_channel_info chinfo, after creating the ept, the key assignment:rpdev->src = ept->addr, this is a very critical operation!

  • If before creationrpdev->src = RPMSG_ADDR_ANY, the backend will dynamically assign an available address
  • ept->addris the actual local address assigned by the backend
  • storeept->addrWrite backrpdev->src, ensuring the device structure records the real address

This means: the driver’s receive callback will be bound to this newly allocated address, and messages sent from the remote to this address will trigger the callback.


  1. Callrpdrvofprobefunction
1
2
3
4
5
6
err = rpdrv->probe(rpdev);
if (err) {
dev_err(dev, "%s: failed: %d\n", __func__, err);
goto destroy_ept;
}

Callstruct rpmsg_driver *rpdrv's probe function


  1. announce_create
1
2
3
4
5
6
7
if (ept && rpdev->ops->announce_create) {
err = rpdev->ops->announce_create(rpdev);
if (err) {
dev_err(dev, "failed to announce creation\n");
goto remove_rpdev;
}
}

Ifstruct rpmsg_device *rpdevofconst struct rpmsg_device_ops *ops;ofannounce_createis set, then call thisannounce_create, that is, call it after the ept is createdannounce_createthis callback

rpmsg_dev_remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int rpmsg_dev_remove(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
int err = 0;

if (rpdev->ops->announce_destroy)
err = rpdev->ops->announce_destroy(rpdev);

if (rpdrv->remove)
rpdrv->remove(rpdev);

dev_pm_domain_detach(dev, true);

if (rpdev->ept)
rpmsg_destroy_ept(rpdev->ept);

return err;
}

The remove function and probe function are in reverse order, firstannounce_destroy, and then callstruct rpmsg_driver *rpdrvthe remove function in, then calldev_pm_domain_detachseparate the power domain, and finally callrpmsg_destroy_eptDestroyrpdev->ept

rpmsg_uevent

1
2
3
4
5
6
7
8
9
10
11
12
static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
int ret;

ret = of_device_uevent_modalias(dev, env);
if (ret != -ENODEV)
return ret;

return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT,
rpdev->id.name);
}

This function is the callback when an RPMSG bus device generates a uevent (userspace event/hotplug event). It is the bridge connecting the kernel device model and userspace udev. Function location and call chain:

1
2
3
4
5
6
7
8
设备注册到 rpmsg_bus


device_add()
└── bus_add_device() / bus_probe_device()
└── kobject_uevent(KOBJ_ADD) // Trigger uevent
└── dev_uevent() // Device uevent callback
└── rpmsg_uevent() // ← Right here (via bus_type.uevent)

The uevent carries a set of environment variables to userspace, and udev decides based on these variables to:

  • Create device node
  • Automatically load driver modules
  • Execute rule scripts

Line-by-line code analysis

  1. First priority: Device tree format modalias
1
2
3
ret = of_device_uevent_modalias(dev, env);
if (ret != -ENODEV)
return ret;

If the device is associated with a device tree node (dev->of_node),of_device_uevent_modaliasit will:

  1. Read the compatible property of the device tree
  2. Generate the standard OF modalias format:of:N<name>T<type>C<compatible>
  3. Add to uevent environment variables

Meaning of the return value:

  • 0: Successfully added OF modalias, return directly
  • -ENODEV: Device has no device tree node, continue with RPMSG’s own logic

2. **Second priority: RPMSG custom modalias**
1
2
return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT,
rpdev->id.name);

If the device has no device tree node, RPMSG generates its own modalias:

RPMSG_DEVICE_MODALIAS_FMTDefinition (ininclude/linux/rpmsg.hormod_devicetable.h):

1
#define RPMSG_DEVICE_MODALIAS_FMT   "rpmsg:%s"

Example of the final generated uevent environment variable:MODALIAS=rpmsg:rpmsg-tty


  1. Complete uevent output example

When a new RPMSG device is registered, the uevent might look like this:

1
2
3
4
5
6
7
8
ACTION=add
BUS=rpmsg
SUBSYSTEM=rpmsg
MODALIAS=rpmsg:rpmsg-tty ← 这里由 rpmsg_uevent 生成
NAME=rpmsg-tty
SRC=0x401
DST=0x0
DEVPATH=/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0

How does udev use modalias?

  • Automatically loading driver modules: udev rules usually contain:
1
2
3
# /lib/udev/rules.d/80-drivers.rules

ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"

When the uevent carriesMODALIAS=rpmsg:rpmsg-tty, udev will execute:modprobe rpmsg:rpmsg-tty

But modprobe doesn’t recognize the colon format; the module itself needs to match via an alias.

  • Alias declaration in the driver module: In the driver source code:
1
2
3
4
5
6
static struct rpmsg_device_id rpmsg_tty_id_table[] = {
{ .name = "rpmsg-tty" },
{ },
}

MODULE_DEVICE_TABLE(rpmsg, rpmsg_tty_id_table); // ← Generate module alias

After compilation, the module file will contain alias information:

1
2
$ modinfo rpmsg_tty
alias: rpmsg:tty*

Note:MODULE_DEVICE_TABLEThe macro generates at compile time mod_rpmsg… symbols, depmod will write them into/lib/modules/$(uname -r)/modules.alias

udevmodprobethe complete chain of

Kernel:rpmsg_uevent()

▼ Generate

MODALIAS=rpmsg:rpmsg-tty

▼ vianetlink/socketsent to userspace
udevdReceiveuevent

▼ Parse environment variables
MODALIAS=rpmsg:rpmsg-tty

▼ Execute rules
modprobe rpmsg:rpmsg-tty

▼ Match/lib/modules/.../modules.alias
Foundrpmsg_tty.ko


insmod rpmsg_tty.ko

This way, as soon as the RPMSG channel is created, the corresponding driver module can be loaded automatically without the user needing to manually modprobe.

If userspace wants to test manually:

View device uevent

1
2
3
4
$ cat /sys/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0/uevent
BUS=rpmsg
DRIVER=rpmsg_tty
MODALIAS=rpmsg:rpmsg-tty

Manually trigger uevent

1
$ echo change > /sys/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0/uevent

This will re-invoke rpmsg_uevent(), and udev will process it again. View module aliases

1
2
3
$ grep rpmsg /lib/modules/$(uname -r)/modules.alias
alias rpmsg:* rpmsg_core
alias rpmsg:rpmsg-tty rpmsg_tty

rpmsg_dev_groups

dev_groups is a field of struct bus in the Linux device model_, its role is: to automatically create a set of sysfs attribute files for each device registered on this bus.

Inrpmsg_core.cin:

1
2
3
4
5
6
7
8
9
10
static struct attribute *rpmsg_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_modalias.attr,
&dev_attr_dst.attr,
&dev_attr_src.attr,
&dev_attr_announce.attr,
&dev_attr_driver_override.attr,
NULL,
};
ATTRIBUTE_GROUPS(rpmsg_dev);

ATTRIBUTE_GROUPS(rpmsg_dev) is a kernel macro, which expands to:

1
2
3
4
5
6
7
8
static struct attribute_group rpmsg_dev_group = {
.attrs = rpmsg_dev_attrs,
};

static struct attribute_group *rpmsg_dev_groups[] = {
&rpmsg_dev_group,
NULL,
};

Then attach it to the bus:

1
2
3
4
5
static struct bus_type rpmsg_bus = {
.name = "rpmsg",
.dev_groups = rpmsg_dev_groups, // ← here
...
};

Key data structures

struct rpmsg_channel_info

1
2
3
4
5
6
7
8
9
10
11
/**
* struct rpmsg_channel_info - channel info representation
* @name: name of service
* @src: local address
* @dst: destination address
*/
struct rpmsg_channel_info {
char name[RPMSG_NAME_SIZE]; // Service name
u32 src; // Local address (source address)
u32 dst; // Destination address
};

Common scenarios:

  • Scenario 1: Passing channel information when creating an endpoint
1
2
3
4
5
6
7
8
// rpmsg_dev_In probe()
struct rpmsg_channel_info chinfo = {};

strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE); // "rpmsg-tty"
chinfo.src = rpdev->src; // Initial address
chinfo.dst = RPMSG_ADDR_ANY; // Accept any remote

ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);

Here, chinfo tells the backend:

  • What service I am:name = "rpmsg-tty"

  • What local address I want: src (might beRPMSG_ADDR_ANY, let the backend assign)

  • Who I accept to access me:dst = RPMSG_ADDR_ANY(any remote)

  • Scenario 2: Identifying channels

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// rpmsg_find_In device()
struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo)
{
return device_find_child(parent, chinfo, rpmsg_device_match);
}

// rpmsg_device_In match()
static int rpmsg_device_match(struct device *dev, void *data)
{
struct rpmsg_channel_info *chinfo = data;
struct rpmsg_device *rpdev = to_rpmsg_device(dev);

if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src)
return 0;
if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst)
return 0;
if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE))
return 0;

return 1; // Match successful
}

Here, chinfo is a “query condition”:

  • Can be retrieved vianame+src+dstPrecisely find an existing channel
  • Can also useRPMSG_ADDR_ANYas a wildcard, ignoring src or dst for matching

struct rpmsg_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* rpmsg_device - device that belong to the rpmsg bus
* @dev: the device struct
* @id: device id (used to match between rpmsg drivers and devices)
* @driver_override: driver name to force a match; do not set directly,
* because core frees it; use driver_set_override() to
* set or clear it.
* @src: local address
* @dst: destination address
* @ept: the rpmsg endpoint of this channel
* @announce: if set, rpmsg will announce the creation/removal of this channel
*/
struct rpmsg_device {
struct device dev; // Base class of the Linux device model
struct rpmsg_device_id id; // Device identifier (for matching)
const char *driver_override; // Force bind to a specified driver
u32 src; // Local address
u32 dst; // Destination address
struct rpmsg_endpoint *ept; // Endpoint (receive callback bound here)
bool announce; // Whether to announce lifecycle to the remote end
const struct rpmsg_device_ops *ops; // Backend operation table
};
  • struct device dev

This is the embedded base class of the Linux device model.rpmsg_deviceAccess the kernel device model through composition rather than inheritance.

Key macro (inrpmsg_internal.h):

1
#define to_rpmsg_device(d) container_of(d, struct rpmsg_device, dev)

Kernel bus callback only getsstruct device *, converted to through this macrostruct rpmsg_device *

  • struct rpmsg_device_id id

include/linux/mod_devicetable.h

1
2
3
4
5
6
7
8
/* rpmsg */

#define RPMSG_NAME_SIZE 32
#define RPMSG_DEVICE_MODALIAS_FMT "rpmsg:%s"

struct rpmsg_device_id {
char name[RPMSG_NAME_SIZE];
};

This is the service name compared during bus matching. For example, “rpmsg-tty”, “rpmsg-client-sample”.

Why wrap a separate layer of structure?

  • Follow the Linux device model’smod_devicetable.hstandard
  • MODULE_DEVICE_TABLE(rpmsg, ...) Requires unifiedxxx_device_idFormat
  • Can extend fields in the future without breaking ABI
  • const char *driver_override

Force specify the driver name. When set, the bus matching logic bypasses id_table and OF matching, directly comparing the driver name.

Important: The comment says do not set directly because the kernel will kfree() this pointer when the device is destroyed. Correct usage:

1
driver_set_override(dev, &rpdev->driver_override, "my_drv", strlen("my_drv"));
  • u32 src/u32 dst
FieldMeaningChange timing
srcLocal address. When the device is created, it might beRPMSG_ADDR_ANY, and updated to the actual value after backend allocationrpmsg_dev_probe()inrpdev->src = ept->addr
dstPeer address. Usually the known remote service address, orRPMSG_ADDR_ANYset by the backend at creation time

src is the address I listen/receive on, dst is who I send to. rpmsg_device represents a logical channel, so it contains both addresses.

  • struct rpmsg_endpoint *ept

Points to the default endpoint of this channel. When the driver provides a callback,rpmsg_dev_probe() an endpoint is automatically created and assigned here.

Note: A rpmsg_device can only have one default ept, but the driver can manually create additional endpoints in probe() (e.g., when multiple listening addresses are needed).

  • bool announce

Controls whether to send to the remote end when the channel is created/destroyed**Name Service (NS)**announcement messages.announce = true: Sends “I’m online” at creation, and “I’m offline” at destruction. After receiving it, the remote processor can update its own service table, or trigger the corresponding client connection

  • const struct rpmsg_device_ops *ops

Backend operation table, defined inrpmsg_internal.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* struct rpmsg_device_ops - indirection table for the rpmsg_device operations
* @create_ept: create backend-specific endpoint, required
* @announce_create: announce presence of new channel, optional
* @announce_destroy: announce destruction of channel, optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* @announce_create and @announce_destroy are optional as the backend might
* advertise new channels implicitly by creating the endpoints.
*/
struct rpmsg_device_ops {
struct rpmsg_endpoint *(*create_ept)(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo);

int (*announce_create)(struct rpmsg_device *ept);
int (*announce_destroy)(struct rpmsg_device *ept);
};

This is the dividing line between the RPMSG core layer and the specific backend:

  • rpmsg_core.conly calls these interfaces
  • virtio_rpmsg_bus.c(or other backends) implements these interfaces
  • allows the RPMSG framework to support multiple underlying transports (although currently it’s mainly virtio)

struct rpmsg_endpoint

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
typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32);

/**
* struct rpmsg_endpoint - binds a local rpmsg address to its user
* @rpdev: rpmsg channel device
* @refcount: when this drops to zero, the ept is deallocated
* @cb: rx callback handler
* @cb_lock: must be taken before accessing/changing @cb
* @addr: local rpmsg address
* @priv: private data for the driver's use
*
* In essence, an rpmsg endpoint represents a listener on the rpmsg bus, as
* it binds an rpmsg address with an rx callback handler.
*
* Simple rpmsg drivers shouldn't use this struct directly, because
* things just work: every rpmsg driver provides an rx callback upon
* registering to the bus, and that callback is then bound to its rpmsg
* address when the driver is probed. When relevant inbound messages arrive
* (i.e. messages which their dst address equals to the src address of
* the rpmsg channel), the driver's handler is invoked to process it.
*
* More complicated drivers though, that do need to allocate additional rpmsg
* addresses, and bind them to different rx callbacks, must explicitly
* create additional endpoints by themselves (see rpmsg_create_ept()).
*/
struct rpmsg_endpoint {
struct rpmsg_device *rpdev;
struct kref refcount;
rpmsg_rx_cb_t cb;
struct mutex cb_lock;
u32 addr;
void *priv;

const struct rpmsg_endpoint_ops *ops;
};

endpoint is the first-class object for sending/receiving

rpmsg core attaches all communication operations torpmsg_endpoint, rather thanrpmsg_device. This means:

  • Arpmsg_device(channel) can have multiple endpoints.
  • Different endpoints can have different callbacks and addresses.
  • The send operation is performed via the endpoint, naturally carrying the source address.

This is similar to the design of TCP sockets: the device is like a socket fd, and the endpoint is like a specific connection endpoint.

rpmsg_rx_cb_tCallback type

1
typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32);

Parameter details

TypeMeaning
struct rpmsg_device *RPMSG device (channel) that received the message
void *Message data pointer (payload)
intMessage length (payload len)
void *Private data (rpmsg_create_eptpassed in whenpriv
u32Message source address (sender address)

Usually returns 0 to indicate successful processing. The specific meaning is defined by the backend, generally:

  • 0: Message processed, buffer can be released
  • Negative value: Processing error

Usage scenario

  • Scenario A: Simple driver, providing callback during registration
1
2
3
4
5
6
7
8
9
10
11
12
13
static int my_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
pr_info("received %d bytes from 0x%x: %.*s\n", len, src, len, (char *)data);
return 0;
}

static struct rpmsg_driver my_drv = {
.drv.name = "my_rpmsg",
.id_table = my_id_table,
.probe = my_probe,
.callback = my_rpmsg_cb, // ← here
};

The framework atrpmsg_dev_probe()automatically creates the endpoint, calling the backend-implementedrpdev->ops->create_ept()binds this callback in (virtio_rpmsg_bus.c)。

  • Scenario B: Complex driver, manually creating multiple endpoints
1
2
3
4
5
6
7
8
9
10
11
12
static int my_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_channel_info chinfo = {};
struct rpmsg_endpoint *ept2;
// Default endpoint has been created by the framework (rpdev->ept)
// Create another additional endpoint for control messages
strncpy(chinfo.name, "ctrl", RPMSG_NAME_SIZE);
chinfo.src = RPMSG_ADDR_ANY;

ept2 = rpmsg_create_ept(rpdev, ctrl_msg_cb, my_priv, chinfo);
// ctrl_msg_cb will receive messages sent to this new address
}

rpmsg_deviceandrpmsg_endpointrelationship between

This is the key to understanding the RPMSG architecture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────┐         ┌──────────────────────┐
struct rpmsg_device│ │ struct rpmsg_endpoint
│ (代表一条通道) │ │ (代表一个监听地址) │
├─────────────────────┤ ├──────────────────────┤
dev │◄──────────rpdev
id.name = "tty" │ │ refcount │
│ src = 0x401 │ │ cb = my_callback │
│ dst = 0x0 │ │ addr = 0x401
│ ept ─────────────────────────►│ priv │
│ announce = true │ │ ops │
│ ops │ └──────────────────────┘
└─────────────────────┘

1:N

┌──────────────┐
│ 额外的端点们 │ (驱动手动创建)
└──────────────┘

Relationship summary:

  • 1rpmsg_deviceRepresents a logical channel (associated with a remote processor)
  • 1rpmsg_deviceAt least 1 defaultrpmsg_endpointrpdev->ept
  • 1rpmsg_deviceCan have N additional endpoints (manually created by the driver)
  • eachrpmsg_endpointBinds a uniqueaddr, triggers its own cb when a message destined for that address is received

Address lifecycle mapping

Stagerpmsg_device->srcrpmsg_endpoint->addrDescription
Device just createdRPMSG_ADDR_ANYNone (not yet created)Waiting for backend allocation
**rpmsg_dev_probe()In **Updated, set toept->addrept->addrActual address allocated by the backend
RuntimeRemains unchangedRemains unchangedUsed for message routing

Key assignment chain:

1
2
3
4
5
6
7
8
// rpmsg_dev_probe()
ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);

└── 后端分配 addr(如 0x401


rpdev->ept = ept;
rpdev->src = ept->addr; // Synced to the device structure!

struct rpmsg_driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* struct rpmsg_driver - rpmsg driver struct
* @drv: underlying device driver
* @id_table: rpmsg ids serviced by this driver
* @probe: invoked when a matching rpmsg channel (i.e. device) is found
* @remove: invoked when the rpmsg channel is removed
* @callback: invoked when an inbound message is received on the channel
*/
struct rpmsg_driver {
struct device_driver drv;
const struct rpmsg_device_id *id_table;
int (*probe)(struct rpmsg_device *dev);
void (*remove)(struct rpmsg_device *dev);
int (*callback)(struct rpmsg_device *, void *, int, void *, u32);
};

struct rpmsg_driverIs the core structure that driver developers need to populate in the RPMSG framework. It follows the standard Linux device driver model while encapsulating RPMSG-specific messaging semantics.

  • struct device_driver drv

Linux device model base class, used to attach torpmsg_bus

  • const struct rpmsg_device_id *id_table

Device ID table supported by the driver (matched by service name)

  • int (*probe)(struct rpmsg_device *)

Called upon successful match, executes driver initialization, refer torpmsg_dev_probefunction

  • void (*remove)(struct rpmsg_device *dev);

Called upon device removal/driver unload, executes cleanup

  • int (*callback)(struct rpmsg_device *, void *, int, void *, u32);

Message receive callback (triggers the framework to automatically create a default endpoint),callbackandprobeDivision of labor

callback triggers automatic endpoint creation

callbackFramework behaviorApplicable scenarios
Non-NULLrpmsg_dev_probe()Automatically creates default endpoint, binds callback torpdev->srcSimple service, single address listening
NULLFramework does not create default endpoint, driver needs toprobe()manually call inrpmsg_create_ept()Complex service, multi-address, dynamic endpoint management

andrpmsg_device/rpmsg_endpointtriangular relationship

ObjectCreated byManaged byLifecycle
rpmsg_deviceBackend (virtio) receives NS messageKernel device modelDuring channel existence
rpmsg_endpointFramework automatically creates (rpdev->ept) or driver manually createskrefReference countBound to device or driver requirements
rpmsg_driverStatically defined by driver authormodule_init/module_exitDuring module loading

Call chain

1
2
3
4
5
6
7
8
9
10
rpmsg_bus.match() 比较
rpdev->id.name vs rpdrv->id_table[].name
↓ 匹配成功
rpmsg_bus.probe()
├── dev_pm_domain_attach()
├── rpmsg_create_ept() ← virtio_rpmsg_bus.c 中赋值 ept->cb = rpdrv->callback
├── rpdrv->probe() ← 驱动初始化
└── announce_create()
↓ 远端发消息
rpdev->ops->announce_create() ← virtio_rpmsg_bus.c 中调用ept->cb

struct rpmsg_device_ops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* struct rpmsg_device_ops - indirection table for the rpmsg_device operations
* @create_ept: create backend-specific endpoint, required
* @announce_create: announce presence of new channel, optional
* @announce_destroy: announce destruction of channel, optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* @announce_create and @announce_destroy are optional as the backend might
* advertise new channels implicitly by creating the endpoints.
*/
struct rpmsg_device_ops {
struct rpmsg_endpoint *(*create_ept)(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo);

int (*announce_create)(struct rpmsg_device *ept);
int (*announce_destroy)(struct rpmsg_device *ept);
};

Invocation timing

MemberMandatory?Call functionInvocation timingPrecondition
create_eptMustrpmsg_create_ept()① The framework automatically creates the default endpoint;
② The driver manually creates the endpoint
rpdev->opsNon-empty
announce_createOptionalrpmsg_dev_probe()driverprobe()After successept created successfully andops->announce_createNon-empty
announce_destroyOptionalrpmsg_dev_remove()Device removal/Driver unload At the beginningops->announce_destroyNon-empty

rpdev->ops->create_ept()

  • Framework automatically createsrpmsg_core.c:rpmsg_dev_probe()
1
2
3
4
5
// rpmsg_core.c: rpmsg_dev_probe()
if (rpdrv->callback) {
ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
// Internal call: rpdev->ops->create_ept(rpdev, cb, priv, chinfo)
}
  • Driver manually creates
1
2
3
4
5
6
7
8
9
10
11
12
// Driver code example
static int my_probe(struct rpmsg_device *rpdev)
{
struct rpmsg_endpoint *ept2;
struct rpmsg_channel_info chinfo = {
.name = "ctrl",
.src = RPMSG_ADDR_ANY,
.dst = RPMSG_ADDR_ANY,
};
ept2 = rpmsg_create_ept(rpdev, ctrl_cb, my_priv_data, chinfo);
// Internal call: rpdev->ops->create_ept(rpdev, ctrl_cb, my_priv_data, chinfo)
}

rpdev->ops->announce_create()

1
2
3
4
5
6
7
8
9
10
// rpmsg_core.c: rpmsg_dev_probe()
err = rpdrv->probe(rpdev); // ← ① Let the driver complete initialization first
if (err)
goto destroy_ept;

if (ept && rpdev->ops->announce_create) { // ← ② Announce after the driver is ready
err = rpdev->ops->announce_create(rpdev);
if (err)
goto remove_rpdev;
}

Backend:

  • Send a “channel creation” message to the remote processor via the RPMSG Name Service protocol
  • Message content usually includes: service namerpdev->id.name, local addressrpdev->src

rpdev->ops->announce_destroy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// rpmsg_core.c: rpmsg_dev_remove()
static int rpmsg_dev_remove(struct device *dev)
{
struct rpmsg_device *rpdev = to_rpmsg_device(dev);
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
int err = 0;

if (rpdev->ops->announce_destroy) // ← ① Execute first: notify the remote end
err = rpdev->ops->announce_destroy(rpdev);

if (rpdrv->remove) // ← ② Then call driver cleanup
rpdrv->remove(rpdev);

dev_pm_domain_detach(dev, true); // ← ③ Power separation

if (rpdev->ept)
rpmsg_destroy_ept(rpdev->ept); // ← ④ Destroy endpoint last

return err;

}

Backend:

  • Send a “channel destroy” message to the remote end via the name service protocol
  • After receiving it, the remote end will delete the channel from its own service table, and subsequent messages sent to that address will be discarded or return an error

Summary

OperationCallerCalleeCore semantics
create_eptrpmsg_create_ept()BackendAllocate resources: bind backend buffer and interrupt to the local address
announce_createrpmsg_dev_probe()BackendPublish service: notify the remote end that “a service is listening at this address”
announce_destroyrpmsg_dev_remove()BackendRevoke service: notify the remote end that “the service at this address is about to stop”

These three hooks together implement the complete lifecycle management of RPMSG “create-publish-revoke”, which is the most critical contract interface between the core layer and the backend

struct rpmsg_endpoint_ops

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
/**
* struct rpmsg_endpoint_ops - indirection table for rpmsg_endpoint operations
* @destroy_ept: see @rpmsg_destroy_ept(), required
* @send: see @rpmsg_send(), required
* @sendto: see @rpmsg_sendto(), optional
* @send_offchannel: see @rpmsg_send_offchannel(), optional
* @trysend: see @rpmsg_trysend(), required
* @trysendto: see @rpmsg_trysendto(), optional
* @trysend_offchannel: see @rpmsg_trysend_offchannel(), optional
* @poll: see @rpmsg_poll(), optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* In addition to @destroy_ept, the backend must at least implement @send and
* @trysend, while the variants sending data off-channel are optional.
*/
struct rpmsg_endpoint_ops {
void (*destroy_ept)(struct rpmsg_endpoint *ept);

int (*send)(struct rpmsg_endpoint *ept, void *data, int len);
int (*sendto)(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
int (*send_offchannel)(struct rpmsg_endpoint *ept, u32 src, u32 dst,
void *data, int len);

int (*trysend)(struct rpmsg_endpoint *ept, void *data, int len);
int (*trysendto)(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
int (*trysend_offchannel)(struct rpmsg_endpoint *ept, u32 src, u32 dst,
void *data, int len);
__poll_t (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
};

Decoupling of rpmsg core and backend

The architecture of the rpmsg subsystem can be viewed as two layers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+---------------------------------------------------+
| rpmsg client driver (user code) |
| - imx_rproc, ti_pruss, etc. |
| - rpmsg_send(), rpmsg_create_ept(), ... |
+---------------------------------------------------+
| rpmsg core (drivers/rpmsg/rpmsg_core.c) |
| - 提供 EXPORT_SYMBOL 的 API |
| - 通过 ops 表转发到 backend |
+---------------------------------------------------+
| rpmsg transport backend |
| - virtio_rpmsg_bus.c (virtio 传输) |
| - 未来可能有其他 backend |
| - 实现 rpmsg_endpoint_ops / rpmsg_device_ops |
+---------------------------------------------------
LevelopsOperation objectTypical operation
Rpmsg Devicerpmsg_device_opsrpmsg_device(channel)Create endpoint, announce channel existence
Rpmsg Endpointrpmsg_endpoint_opsrpmsg_endpoint(Communication endpoint)Send data, destroy endpoint, poll

This layering allows the core to perform name service announcement when the device registers (via rpmsg_device_ops.announce_create), while specific data transmission and reception go through the endpoint (via rpmsg_endpoint_ops.send)。

Layered relationship:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rpmsg_device (代表一个 channel)
|
|-- rpmsg_device_ops
| |
| |-- create_ept() --> rpmsg_endpoint
| |-- announce_create/destroy()
|
v
rpmsg_endpoint (代表 channel 上的一个通信端点)
|
|-- rpmsg_endpoint_ops
| |
| |-- send/sendto/send_offchannel
| |-- trysend/trysendto/trysend_offchannel
| |-- destroy_ept
| |-- poll

Forwarding logic of rpmsg core

drivers/rpmsg/rpmsg_core.cEach API is a thin wrapper, with a few typical functions as examples:

1
2
3
4
5
6
7
8
9
10
int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->send)
return -ENXIO;

return ept->ops->send(ept, data, len);

}

Because send is required, core has no fallback. If XIO.

1
2
3
4
5
6
7
8
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->sendto)
return -ENXIO;
return ept->ops->sendto(ept, data, len, dst);
}

seerpmsg_core.cAlthough the comment says sendto is optional, the semantics of optional are “the backend can choose not to support this operation”, not “core will provide compatibility for you”. If a backend only implements send and trysend, calling sendto will fail.

1
2
3
4
5
6
7
8
9
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait) {
if (WARN_ON(!ept))
return 0;
if (!ept->ops->poll)
return 0;

return ept->ops->poll(ept, filp, wait);
}

virtio_rpmsg_bus.cpoll is not implemented, so it goes throughrpmsg_poll()returns 0 when called. Anddrivers/rpmsg/rpmsg_char.c

1
mask |= rpmsg_poll(eptdev->ept, filp, wait);

rpmsg_char.cprovides user-space/dev/rpmsgXinterface.rpmsg_poll()The result is ORed intopoll mask. If the backend does not implement poll, user-space’s/dev/rpmsgXdoingpoll()can only detect read events (EPOLLIN), cannot detect viarpmsg_poll()detect write-ready status. However, because virtio’srpmsg_sendseries either block or return immediately, the write path does not actually depend on poll.

Summary

opsrequiredcore behavior (when not implemented)virtio backend
destroy_ept✅ requiredN/A (will not be NULL, must be set at initialization)virtio_rpmsg_destroy_ept
send✅ required-ENXIOvirtio_rpmsg_send
trysend✅ required-ENXIOvirtio_rpmsg_trysend
sendtooptional-ENXIOvirtio_rpmsg_sendto
send_offchanneloptional-ENXIOvirtio_rpmsg_send_offchannel
trysendtooptional-ENXIOvirtio_rpmsg_trysendto
trysend_offchanneloptional-ENXIOvirtio_rpmsg_trysend_offchannel
polloptionalreturns 0undefined

EXPORT_SYMBOLS

rpmsg_create_ept()

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
/**
* rpmsg_create_ept() - create a new rpmsg_endpoint
* @rpdev: rpmsg channel device
* @cb: rx callback handler
* @priv: private data for the driver's use
* @chinfo: channel_info with the local rpmsg address to bind with @cb
*
* Every rpmsg address in the system is bound to an rx callback (so when
* inbound messages arrive, they are dispatched by the rpmsg bus using the
* appropriate callback handler) by means of an rpmsg_endpoint struct.
*
* This function allows drivers to create such an endpoint, and by that,
* bind a callback, and possibly some private data too, to an rpmsg address
* (either one that is known in advance, or one that will be dynamically
* assigned for them).
*
* Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint
* is already created for them when they are probed by the rpmsg bus
* (using the rx callback provided when they registered to the rpmsg bus).
*
* So things should just work for simple drivers: they already have an
* endpoint, their rx callback is bound to their rpmsg address, and when
* relevant inbound messages arrive (i.e. messages which their dst address
* equals to the src address of their rpmsg channel), the driver's handler
* is invoked to process it.
*
* That said, more complicated drivers might need to allocate
* additional rpmsg addresses, and bind them to different rx callbacks.
* To accomplish that, those drivers need to call this function.
*
* Drivers should provide their @rpdev channel (so the new endpoint would belong
* to the same remote processor their channel belongs to), an rx callback
* function, an optional private data (which is provided back when the
* rx callback is invoked), and an address they want to bind with the
* callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will
* dynamically assign them an available rpmsg address (drivers should have
* a very good reason why not to always use RPMSG_ADDR_ANY here).
*
* Returns a pointer to the endpoint on success, or NULL on error.
*/
struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
rpmsg_rx_cb_t cb, void *priv,
struct rpmsg_channel_info chinfo)
{
if (WARN_ON(!rpdev))
return NULL;

return rpdev->ops->create_ept(rpdev, cb, priv, chinfo);
}
EXPORT_SYMBOL(rpmsg_create_ept);

Callrpdev->opsin thecreate_ept

rpmsg_destroy_ept()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* rpmsg_destroy_ept() - destroy an existing rpmsg endpoint
* @ept: endpoing to destroy
*
* Should be used by drivers to destroy an rpmsg endpoint previously
* created with rpmsg_create_ept(). As with other types of "free" NULL
* is a valid parameter.
*/
void rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
{
if (ept && ept->ops)
ept->ops->destroy_ept(ept);
}
EXPORT_SYMBOL(rpmsg_destroy_ept);

rpmsg_send()

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
/**
* rpmsg_send() - send a message across to the remote processor
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len on the @ept endpoint.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address and its associated rpmsg
* device destination addresses.
* In case there are no TX buffers available, the function will block until
* one becomes available, or a timeout of 15 seconds elapses. When the latter
* happens, -ERESTARTSYS is returned.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->send)
return -ENXIO;

return ept->ops->send(ept, data, len);
}
EXPORT_SYMBOL(rpmsg_send);

rpmsg_sendto()

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
/**
* rpmsg_sendto() - send a message across to the remote processor, specify dst
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
* @dst: destination address
*
* This function sends @data of length @len to the remote @dst address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source.
* In case there are no TX buffers available, the function will block until
* one becomes available, or a timeout of 15 seconds elapses. When the latter
* happens, -ERESTARTSYS is returned.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->sendto)
return -ENXIO;

return ept->ops->sendto(ept, data, len, dst);
}
EXPORT_SYMBOL(rpmsg_sendto);

rpmsg_send_offchannel

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
/**
* rpmsg_send_offchannel() - send a message using explicit src/dst addresses
* @ept: the rpmsg endpoint
* @src: source address
* @dst: destination address
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len to the remote @dst address,
* and uses @src as the source address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to.
* In case there are no TX buffers available, the function will block until
* one becomes available, or a timeout of 15 seconds elapses. When the latter
* happens, -ERESTARTSYS is returned.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->send_offchannel)
return -ENXIO;

return ept->ops->send_offchannel(ept, src, dst, data, len);
}
EXPORT_SYMBOL(rpmsg_send_offchannel);

rpmsg_trysend()

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
/**
* rpmsg_trysend() - send a message across to the remote processor
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len on the @ept endpoint.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source and its associated
* rpdev's address as destination.
* In case there are no TX buffers available, the function will immediately
* return -ENOMEM without waiting until one becomes available.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->trysend)
return -ENXIO;

return ept->ops->trysend(ept, data, len);
}
EXPORT_SYMBOL(rpmsg_trysend);

rpmsg_trysendto()

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
/**
* rpmsg_trysendto() - send a message across to the remote processor, specify dst
* @ept: the rpmsg endpoint
* @data: payload of message
* @len: length of payload
* @dst: destination address
*
* This function sends @data of length @len to the remote @dst address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to, using @ept's address as source.
* In case there are no TX buffers available, the function will immediately
* return -ENOMEM without waiting until one becomes available.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->trysendto)
return -ENXIO;

return ept->ops->trysendto(ept, data, len, dst);
}
EXPORT_SYMBOL(rpmsg_trysendto);

rpmsg_poll()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* rpmsg_poll() - poll the endpoint's send buffers
* @ept: the rpmsg endpoint
* @filp: file for poll_wait()
* @wait: poll_table for poll_wait()
*
* Returns mask representing the current state of the endpoint's send buffers
*/
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait)
{
if (WARN_ON(!ept))
return 0;
if (!ept->ops->poll)
return 0;

return ept->ops->poll(ept, filp, wait);
}
EXPORT_SYMBOL(rpmsg_poll);

rpmsg_trysend_offchannel()

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
/**
* rpmsg_trysend_offchannel() - send a message using explicit src/dst addresses
* @ept: the rpmsg endpoint
* @src: source address
* @dst: destination address
* @data: payload of message
* @len: length of payload
*
* This function sends @data of length @len to the remote @dst address,
* and uses @src as the source address.
* The message will be sent to the remote processor which the @ept
* endpoint belongs to.
* In case there are no TX buffers available, the function will immediately
* return -ENOMEM without waiting until one becomes available.
*
* Can only be called from process context (for now).
*
* Returns 0 on success and an appropriate error value on failure.
*/
int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
void *data, int len)
{
if (WARN_ON(!ept))
return -EINVAL;
if (!ept->ops->trysend_offchannel)
return -ENXIO;

return ept->ops->trysend_offchannel(ept, src, dst, data, len);
}
EXPORT_SYMBOL(rpmsg_trysend_offchannel);

rpmsg_find_device()

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
/*
* match a rpmsg channel with a channel info struct.
* this is used to make sure we're not creating rpmsg devices for channels
* that already exist.
*/
static int rpmsg_device_match(struct device *dev, void *data)
{
struct rpmsg_channel_info *chinfo = data;
struct rpmsg_device *rpdev = to_rpmsg_device(dev);

if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src)
return 0;

if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst)
return 0;

if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE))
return 0;

/* found a match ! */
return 1;
}

struct device *rpmsg_find_device(struct device *parent,
struct rpmsg_channel_info *chinfo)
{
return device_find_child(parent, chinfo, rpmsg_device_match);

}
EXPORT_SYMBOL(rpmsg_find_device);

complete flowchart

click code block to expand

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
local processors                                 remote processor
│ [dtb] virtio_device match virtio_driver │[dtb] virtio_device match virtio_driver
│ rpmsg_probe (virtio_rpmsg_bus.c) │rpmsg_probe (virtio_rpmsg_bus.c)
│ vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, │vrp->ns_ept = __rpmsg_create_ept(vrp, NULL,
│ rpmsg_ns_cb, vrp, RPMSG_NS_ADDR); │ rpmsg_ns_cb, vrp, RPMSG_NS_ADDR);
│ │
│ │
│ │
│ [dtb] rpmsg_device register rpmsg_bus │[dtb] rpmsg_device register rpmsg_bus
│ rpmsg_dev_probe(rpdev->dev) (rpmsg_core.c) │rpmsg_dev_probe(rpdev->dev) (rpmsg_core.c)
│ rpdrv->callback = NULL, don't create ept │ rpdrv->callback = NULL, don't create ept
│ rpdrv->probe = rpmsg_chrdev_probe (rpmsg_char.c) │ rpdrv->probe = rpmsg_chrdev_probe (rpmsg_char.c)
│ create cdev "rpmsg_ctrl0" │ create cdev "rpmsg_ctrl0"
│ n rpdev->ops->announce_create(rpdev); │ rpdev->ops->announce_create(rpdev);
│ = virtio_rpmsg_announce_create(rpdev) │ = virtio_rpmsg_announce_create(rpdev)
│ rpdev->ept = NULL, don't announce │ rpdev->ept = NULL, don't announce
│ │
│ │
│ open("/dev/rpmsg_ctrl0") │open("/dev/rpmsg_ctrl0")
│ ioctl(fd, RPMSG_CREATE_EPT_IOCTL, &eptinfo) │ioctl(fd, RPMSG_CREATE_EPT_IOCTL, &eptinfo)
│ eptinfo.name="tty", src=0x300, dst=0x400 │ eptinfo.name="tty", src=0x400, dst=0x300
if src = RPMSG_ADDR_ANY, ept->addr = idr_alloc()│ if src = RPMSG_ADDR_ANY, ept->addr = idr_alloc()
│ rpmsg_eptdev_create(ctrldev, chinfo); │ rpmsg_eptdev_create(ctrldev, chinfo);
│ create cdev "rpmsg%d" │ create cdev "rpmsg%d"
│ open("/dev/rpmsg0") │open("/dev/rpmsg0")
│ rpmsg_create_ept(rpdev, rpmsg_ept_cb, │ rpmsg_create_ept(rpdev, rpmsg_ept_cb,
│ eptdev, eptdev->chinfo); │ eptdev, eptdev->chinfo);
│ __rpmsg_create_ept │ __rpmsg_create_ept
│ ept->addr = 0x300 │ ept->addr = 0x400
│ ept->cb = rpmsg_ept_cb │ ept->cb = rpmsg_ept_cb
│ │
│ write_iter("/dev/rpmsg0") │read_iter("/dev/rpmsg0")
│ rpmsg_eptdev_write_iter(iocb, from) │ rpmsg_eptdev_read_iter(iocb, to)
│ rpmsg_send/trysend(eptdev->ept, kbuf, len); │ wait_event_interruptible(eptdev->readq,
│ ept->ops->send │ !skb_queue_empty(&eptdev->queue) ||
│ src=ept->addr, dst=rpdev->dst │ !eptdev->ept)
│ rpmsg_send_offchannel_raw() │
│ get_a_tx_buf() │
│ fill rpmsg_hdr │
│ virtqueue_add_outbuf() │
│ virtqueue_kick() │
│───────────────────────────────────────────────────►│
│ │rpmsg_recv_done(rvq)
│ │ rpmsg_recv_single(vrp, dev, msg, len)
│ │ ept->cb() = rpmsg_ept_cb()
│ │ skb_put_data(skb, buf, len);
│ │ skb_queue_tail(&eptdev->queue, skb);
│ │ wake_up_interruptible(&eptdev->readq);
│ │
│ │
│ │ wait_event_interruptible(eptdev->readq,
│ │ !skb_queue_empty(&eptdev->queue) ||
│ │ !eptdev->ept)
│ │ skb = skb_dequeue(&eptdev->queue)
│ │ copy_to_iter(skb->data, use, to)
│ │ kfree_skb(skb)