Cover image for Rpmsg Core

Rpmsg Core

Words 8k
Views
Visitors

Timeline

Timeline

2026-06-27

init

This article introduces the design and implementation of the rpmsg core in the Linux kernel. As a bus abstraction, rpmsg is not bound to any specific transport; it can support multiple backend implementations based on shared memory, mailbox interrupts, or PCIe doorbells. Each backend only needs to provide corresponding operations to connect to the core. The article elaborates on the rpmsg device matching mechanism (rpmsg_dev_match) priority scoring rules, by comparing specific/general driver constraints, compat matching, and of_node matching and other conditions to calculate scores, so as to determine the optimal driver. At the same time, the article summarizes the rpmsg device probe process (rpmsg_dev_probe), including key steps such as power domain association (genpd), automatic endpoint creation, and calling the driver probe function, and explains the mechanism of dynamic endpoint address allocation and callback binding.

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:

  • a direct mapping implementation based on shared memory
  • an implementation based on mailbox interrupts
  • an implementation based on PCIe doorbell

Each backend only needs to provide its ownrpmsg_endpoint_opsandrpmsg_device_ops, and it can connect to the rpmsg core. Moreover, 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 explicitly specifying src/dst (offchannel), it can choose not to supportsendto/send_offchannel
  • If the backend does not support poll, user-space write operations are still available (blocking/non-blocking mode is determined byrpmsg_send/rpmsg_trysendsemantic guarantee).

module_init/module_exit

1234567891011121314151617181920
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_initThe function usespostcore_initcall, callsbus_registerregister a bus

1234567891011121314151617181920212223242526272829303132333435363738
// 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:

12345678
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

123456789101112131415161718
/* 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 it only needs to comparerpdev->driver_overrideanddrv->name, that is, specifying therpdevforce matching a driver with a corresponding name

  • ifrpdev->id_tableexists

    • Fromstruct rpmsg_driver *rpdrvextract fromconst struct rpmsg_device_id *ids, then traverse the ids table, throughids[i].nameMatchrpmsg_device, if the match succeeds, return 1,

    • otherwise callof_driver_match_deviceMatchstruct device *devandstruct device_driver *drv

of_driver_match_devicethe match passesstruct device_driver *drvindrv->of_match_tableandstruct device *devindev->of_nodeas a parameter, call__of_match_nodeMatch:

12345678910111213141516171819202122
staticconst 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, and__of_device_is_compatibledefined as follows:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
/** * __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 combinationmatch detail conditionsscore calculation formulafinal score example
1specificcompat&&type&&namecompatmatches andindex=0typematch;nameMatchBase(0×4)+2+1Base - (0 \times 4) + 2 + 1Base+3Base + 3 (highest score)
2specificcompat&&typecompatmatches andindex=0typematch; nonenameconstraintBase(0×4)+2+0Base - (0 \times 4) + 2 + 0Base+2Base + 2
3specificcompat&&namecompatmatches andindex=0; nonetypeconstraint;nameMatchBase(0×4)+0+1Base - (0 \times 4) + 0 + 1Base+1Base + 1
4specificcompatcompatmatches andindex=0; nonetypeandnameconstraintBase(0×4)+0+0Base - (0 \times 4) + 0 + 0BaseBase
5generalcompat&&type&&namecompatmatches andindex=1typematch;nameMatchBase(1×4)+2+1Base - (1 \times 4) + 2 + 1Base1Base - 1
6generalcompat&&typecompatmatches andindex=1typematch; nonenameconstraintBase(1×4)+2+0Base - (1 \times 4) + 2 + 0Base2Base - 2
7generalcompat&&namecompatmatches andindex=1; nonetypeconstraint;nameMatchBase(1×4)+0+1Base - (1 \times 4) + 0 + 1Base3Base - 3
8generalcompatcompatmatches andindex=1; nonetypeandnameconstraintBase(1×4)+0+0Base - (1 \times 4) + 0 + 0Base4Base - 4
more generic compat…compatmatches andindex=2(later compatible string)Base(2×4)+Base - (2 \times 4) + \dotscontinues 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 (least significant score)
no match / eliminatedany driver-specified constraint not found in the nodereturn directly000

rpmsg_dev_probe

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
/* * 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
123
err = dev_pm_domain_attach(dev, true); if (err)     goto out;

Purpose: associate the device with a power management domain (Power Management Domain, genpd).

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

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


  1. Automatically create endpoint
123456789101112131415
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. Provide the callback during registration, and the framework automatically creates an endpoint for it.
  • Complex driver: may need multiple endpoints and dynamic address management. The callback of such drivers may be NULL, and they will manually call it in their own probe.rpmsg_create_ept(),

Then constructstruct rpmsg_channel_info chipinfo

123
strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);  // Service namechinfo.src = rpdev->src;                                  // Local addresschinfo.dst = RPMSG_ADDR_ANY;                              // Any destination address

Notedst = RPMSG_ADDR_ANYThe meaning: when the endpoint is created, it is not bound 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 chinfoAfter creating the ept, the key assignment:rpdev->src = ept->addrThis is a very critical operation!

  • If prior to creationrpdev->src = RPMSG_ADDR_ANYthe backend will dynamically allocate an available address
  • ept->addris the actual local address after backend allocation
  • holdept->addrWrite backrpdev->srcto ensure 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 by the remote end to this address will trigger the callback.


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

Callstruct rpmsg_driver *rpdrvthe probe function


  1. announce_create
1234567
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 *opsofannounce_createis set, then call theannounce_create, i.e., called after ept is createdannounce_createthis callback

rpmsg_dev_remove

12345678910111213141516171819
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, then callstruct rpmsg_driver *rpdrvthe remove function in it, then calldev_pm_domain_detachdetach the power domain, and finally callrpmsg_destroy_eptDestroyrpdev->ept

rpmsg_uevent

123456789101112
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 the userspace udev. Function location and call chain:

12345678
设备注册到 rpmsg_busdevice_add()    └── bus_add_device() / bus_probe_device()        └── kobject_uevent(KOBJ_ADD)   // Trigger uevent            └── dev_uevent()            // Device's 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:

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

Line-by-line code analysis

  1. First priority: device tree format modalias
123
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 return value:

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

2. **Second priority: RPMSG custom modalias**
12
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 variables:MODALIAS=rpmsg:rpmsg-tty


  1. Complete uevent output example

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

12345678
ACTION=addBUS=rpmsgSUBSYSTEM=rpmsgMODALIAS=rpmsg:rpmsg-tty       ← 这里由 rpmsg_uevent 生成NAME=rpmsg-ttySRC=0x401DST=0x0DEVPATH=/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0

How does udev use modalias?

  • Automatically load driver modules: udev rules usually contain:
123
# /lib/udev/rules.d/80-drivers.rulesENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"

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

But modprobe does not recognize the format with colons; the module itself needs to match via an alias.

  • Alias declaration in the driver module: In the driver source code:
123456
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:

12
$ modinfo rpmsg_tty  alias:          rpmsg:tty*

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

udevmodprobeThe complete chain

Kernel:rpmsg_uevent()

▼ Generate

MODALIAS=rpmsg:rpmsg-tty

▼ Vianetlink/socketSend to user space
udevdReceiveduevent

▼ Parse environment variables
MODALIAS=rpmsg:rpmsg-tty

▼ Execute rules
modprobe rpmsg:rpmsg-tty

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


insmod rpmsg_tty.ko

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

If user space wants to manually test:

View device uevent

1234
$ cat /sys/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0/ueventBUS=rpmsgDRIVER=rpmsg_ttyMODALIAS=rpmsg:rpmsg-tty

Manually trigger uevent

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

This will call rpmsg_uevent() again, and udev will process it again. View module alias

123
$ grep rpmsg /lib/modules/$(uname -r)/modules.aliasalias rpmsg:* rpmsg_corealias rpmsg:rpmsg-tty rpmsg_tty

rpmsg_dev_groups

dev_groups is, in the Linux device model, struct bus_a field of type, and its purpose is: for each device registered on this bus, automatically create a set of sysfs attribute files.

Beforerpmsg_core.cin:

12345678910
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:

12345678
static struct attribute_group rpmsg_dev_group = {    .attrs = rpmsg_dev_attrs,};static struct attribute_group *rpmsg_dev_groups[] = {    &rpmsg_dev_group,    NULL,};

Then attach to the bus:

12345
static struct bus_type rpmsg_bus = {    .name       = "rpmsg",    .dev_groups = rpmsg_dev_groups,   // ← Here    ...};

Key Data Structures

struct rpmsg_channel_info

1234567891011
/** * 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
12345678
// 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 addresschinfo.dst = RPMSG_ADDR_ANY;                              // Accept any remoteept = 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 (possiblyRPMSG_ADDR_ANY, let the backend allocate)

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

  • Scenario 2: Identifying the channel

12345678910111213141516171819202122
// 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 succeeded}

Here chinfo is a “query condition”:

  • Can pass throughname+src+dstPrecisely find an existing channel
  • You can also useRPMSG_ADDR_ANYas a wildcard, ignoring src or dst for matching

struct rpmsg_device

12345678910111213141516171819202122
/** * 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 the 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    const struct rpmsg_device_ops *ops; // Backend operation table};
  • struct device dev

This is the embedded base class of the Linux device model.rpmsg_deviceIntegrate with 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)

The kernel bus callback only getsstruct device *, and this macro converts it tostruct rpmsg_device *

  • struct rpmsg_device_id id

include/linux/mod_devicetable.h

12345678
/* 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 structure?

  • Follow the Linux device model’smod_devicetable.hstandard
  • MODULE_DEVICE_TABLE(rpmsg, ...) requires a unifiedxxx_device_idFormat
    Fields can be extended in the future without breaking the ABI.
  • const char *driver_override

Forcefully specify the driver name. When set, the bus matching logic bypasses id_table and OF matching and directly compares 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
fieldmeaningWhen it changes
srcLocal address. When the device is created, it may beRPMSG_ADDR_ANY, and after the backend allocates it, it is updated to the actual value.rpmsg_dev_probe()inrpdev->src = ept->addr
dstPeer address. Usually a known remote service address, orRPMSG_ADDR_ANYSet by the backend at creation time.

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

  • struct rpmsg_endpoint *ept

Points to the default endpoint of the channel. When the driver provides a callback,rpmsg_dev_probe() it will automatically create an endpoint and assign it here.

Note: a rpmsg_device can only have one default ept, but the driver can manually create additional endpoints in probe() (for example, 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 message.announce = true: when created, send “I’m online”; when destroyed, send “I’m offline”. 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

123456789101112131415161718
/** * 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 boundary between the RPMSG core layer and the specific backend:

  • rpmsg_core.cOnly calls these interfaces.
  • virtio_rpmsg_bus.c(or other backends) implement these interfaces.
  • Allows the RPMSG framework to support multiple underlying transports (although currently mainly virtio).

struct rpmsg_endpoint

1234567891011121314151617181920212223242526272829303132333435
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 a first-class object for sending/receiving.

rpmsg core attaches all communication operations torpmsg_endpointrather thanrpmsg_deviceto. This means:

  • Arpmsg_device(channel) can have multiple endpoints.
  • Different endpoints can have different callbacks and addresses.
  • Send operations are performed through the endpoint, naturally carrying the source address.

This is similar to the TCP socket design: 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 *The 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

Use case

  • Scenario A: Simple driver, provide callback during registration
12345678910111213
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, whenrpmsg_dev_probe()automatically creates an endpoint, calling the backend-implementedrpdev->ops->create_ept()bind this callback in (virtio_rpmsg_bus.c)。

  • Scenario B: Complex driver, manually create multiple endpoints
123456789101112
static int my_probe(struct rpmsg_device *rpdev){    struct rpmsg_channel_info chinfo = {};    struct rpmsg_endpoint *ept2;	// The default endpoint has been created by the framework (rpdev->ept)  	// Create an 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_The cb will receive messages sent to this new address}

rpmsg_deviceandrpmsg_endpointthe relationship

This is the key to understanding the RPMSG architecture:

123456789101112131415161718
┌─────────────────────┐         ┌──────────────────────┐struct rpmsg_device│         │ struct rpmsg_endpoint│  (代表一条通道)       │         │ (代表一个监听地址)      │├─────────────────────┤         ├──────────────────────┤dev                 │◄──────────rpdevid.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, and triggers its own callback when receiving a message sent to that address

Address lifecycle mapping

Stagerpmsg_device->srcrpmsg_endpoint->addrDescription
Device just createdRPMSG_ADDR_ANYNone (not yet created)Waiting for backend allocation
rpmsg_dev_probe()Inis updated and set toept->addrept->addrthe actual address allocated by the backend
Runtimeremains unchangedremains unchangedused for message routing

Key assignment chain:

12345678
// rpmsg_dev_probe()ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);       └── 后端分配 addr(如 0x401rpdev->ept = ept;rpdev->src = ept->addr;   // Synchronized to the device structure!

struct rpmsg_driver

123456789101112131415
/** * 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_driverIt is the core structure that driver developers need to fill in the RPMSG framework. It follows the Linux standard device driver model and encapsulates the RPMSG-specific message sending/receiving 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 when a match succeeds, performs driver initialization, refer torpmsg_dev_probefunction

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

Called on device removal/driver unload to perform 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 create default endpoint, bind callback torpdev->srcSimple service, single address listening
NULLFramework does not create default endpoint, driver mustprobe()manually call inrpmsg_create_ept()Complex service, multi-address, dynamic endpoint management

withrpmsg_device/rpmsg_endpointthe triangular 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

12345678910
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

123456789101112131415161718
/** * 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);};

Call timing

MemberWhether requiredCall functionCall 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()At the very beginning of device removal/driver unloadops->announce_destroyNon-empty

rpdev->ops->create_ept()

  • Framework automatically createsrpmsg_core.c:rpmsg_dev_probe()
12345
// 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
123456789101112
// Driver code examplestatic 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()

12345678910
// rpmsg_core.c: rpmsg_dev_probe()err = rpdrv->probe(rpdev);          // ← ① First let the driver complete initializationif (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.
  • The message content usually includes: service namerpdev->id.name, local addressrpdev->src

rpdev->ops->announce_destroy()

123456789101112131415161718192021
// 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);      // ← ④ Finally destroy the endpoint      return err;}

Backend:

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

Summary

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

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

struct rpmsg_endpoint_ops

12345678910111213141516171819202122232425262728293031
/** * 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);};

rpmsg core and backend decoupling

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

1234567891011121314
+---------------------------------------------------+|  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 operations
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 make name service announcements during device registration (via rpmsg_device_ops.announce_create), while actual data sending and receiving goes through the endpoint (via rpmsg_endpoint_ops.send)。

Layered relationship:

12345678910111213141516
rpmsg_device (代表一个 channel)    |    |-- rpmsg_device_ops    |       |    |       |-- create_ept() --> rpmsg_endpoint    |       |-- announce_create/destroy()    |    vrpmsg_endpoint (代表 channel 上的一个通信端点)    |    |-- rpmsg_endpoint_ops    |       |    |       |-- send/sendto/send_offchannel    |       |-- trysend/trysendto/trysend_offchannel    |       |-- destroy_ept    |       |-- poll

rpmsg core’s forwarding logic

drivers/rpmsg/rpmsg_core.cIn it, each API is a thin layer of encapsulation, taking several typical functions as examples:

12345678910
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, the core has no fallback. If XIO.

12345678
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 is “the backend can choose not to support this operation”, not “the core will help you be compatible”. If a backend only implements send and trysend, calling sendto will fail.

123456789
__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.cdoes not implement poll, so viarpmsg_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 maskin. If the backend does not implement poll, user space for/dev/rpmsgXdopoll()can only detect read events (EPOLLIN), cannot viarpmsg_poll()detect write-ready status. However, due to virtio’srpmsg_sendseries either blocks or returns immediately, so in practice the write path does not depend on poll.

Summary

opsrequiredcore behavior (when not implemented)virtio backend
destroy_ept✅ requiredN/A (will not be NULL, must be set during 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()

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
/** * 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->opsincreate_ept

rpmsg_destroy_ept()

1234567891011121314
/** * 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()

12345678910111213141516171819202122232425262728
/** * 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()

12345678910111213141516171819202122232425262728
/** * 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

12345678910111213141516171819202122232425262728293031
/** * 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()

123456789101112131415161718192021222324252627
/** * 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()

123456789101112131415161718192021222324252627
/** * 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()

12345678910111213141516171819
/** * 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()

123456789101112131415161718192021222324252627282930
/** * 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()

123456789101112131415161718192021222324252627282930
/* * 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 the code block to expand

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
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=0x300if 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)
Loading comments…