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 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 support
sendto/send_offchannel - If the backend does not support poll, user-space write operations are still available (blocking/non-blocking mode is determined by
rpmsg_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.htypedef int (*initcall_t)(void); |
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);} |
if
rpdev->driver_overridethen it only needs to comparerpdev->driver_overrideanddrv->name, that is, specifying therpdevforce matching a driver with a corresponding nameif
rpdev->id_tableexistsFrom
struct 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 call
of_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;} |
| priority | driving constraint combination | match detail conditions | score calculation formula | final score example |
|---|---|---|---|---|
| 1 | specificcompat&&type&&name | compatmatches andindex=0;typematch;nameMatch | (highest score) | |
| 2 | specificcompat&&type | compatmatches andindex=0;typematch; nonenameconstraint | ||
| 3 | specificcompat&&name | compatmatches andindex=0; nonetypeconstraint;nameMatch | ||
| 4 | specificcompat | compatmatches andindex=0; nonetypeandnameconstraint | ||
| 5 | generalcompat&&type&&name | compatmatches andindex=1;typematch;nameMatch | ||
| 6 | generalcompat&&type | compatmatches andindex=1;typematch; nonenameconstraint | ||
| 7 | generalcompat&&name | compatmatches andindex=1; nonetypeconstraint;nameMatch | ||
| 8 | generalcompat | compatmatches andindex=1; nonetypeandnameconstraint | ||
| — | more generic compat… | compatmatches andindex=2(later compatible string) | continues to decrease as index increases | |
| 9 | type&&name | Nonecompatconstraint;typematch;nameMatch | ||
| 10 | type | Nonecompatconstraint;typematch; nonenameconstraint | ||
| 11 | name | Nonecompatconstraint; nonetypeconstraint;nameMatch | (least significant score) | |
| — | no match / eliminated | any driver-specified constraint not found in the node | return directly0 |
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;} |
- 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.
- 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 |
Note
dst = 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 *rpdev、rpdrv->callback、priv = NULL、struct rpmsg_channel_info chinfoAfter creating the ept, the key assignment:rpdev->src = ept->addrThis is a very critical operation!
- If prior to creation
rpdev->src = RPMSG_ADDR_ANYthe backend will dynamically allocate an available address ept->addris the actual local address after backend allocation- hold
ept->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.
- Call
rpdrvofprobefunction
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
- 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_bus │ ▼device_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
- 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:
- Read the compatible property of the device tree
- Generate the standard OF modalias format:
of:N<name>T<type>C<compatible> - 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 | |
Example of the final generated uevent environment variables:MODALIAS=rpmsg:rpmsg-tty
- 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。
udev→modprobeThe complete chain
Kernel:rpmsg_uevent()
│
▼ Generate
MODALIAS=rpmsg:rpmsg-tty
│
▼ Vianetlink/socketSend to user spaceudevdReceiveduevent
│
▼ Parse environment variablesMODALIAS=rpmsg:rpmsg-tty
│
▼ Execute rulesmodprobe 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-ttyManually trigger uevent
1$ echo change > /sys/bus/rpmsg/devices/virtio0.rpmsg-tty.-1.0/ueventThis 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 (possibly
RPMSG_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 through
name+src+dstPrecisely find an existing channel - You can also use
RPMSG_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 | |
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 */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’s
mod_devicetable.hstandardMODULE_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:
1driver_set_override(dev, &rpdev->driver_override, "my_drv", strlen("my_drv"));
u32 src/u32 dst
| field | meaning | When it changes |
|---|---|---|
| src | Local 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 |
| dst | Peer address. Usually a known remote service address, orRPMSG_ADDR_ANY | Set 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:
- A
rpmsg_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
| Type | meaning |
|---|---|
struct rpmsg_device * | The RPMSG device (channel) that received the message |
void * | Message data pointer (payload) |
int | Message length (payload len) |
void * | Private data (rpmsg_create_eptpassed in whenpriv) |
u32 | Message 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 │◄──────────rpdev ││ id.name = "tty" │ │ refcount ││ src = 0x401 │ │ cb = my_callback ││ dst = 0x0 │ │ addr = 0x401 ││ ept ─────────────────────────►│ priv ││ announce = true │ │ ops ││ ops │ └──────────────────────┘└─────────────────────┘ │ │ 1:N ▼ ┌──────────────┐ │ 额外的端点们 │ (驱动手动创建) └──────────────┘ |
Relationship summary:
- 1
rpmsg_deviceRepresents a logical channel (associated with a remote processor) - 1
rpmsg_deviceAt least 1 defaultrpmsg_endpoint(rpdev->ept) - 1
rpmsg_deviceCan have N additional endpoints (manually created by the driver) - each
rpmsg_endpointBinds a uniqueaddr, and triggers its own callback when receiving a message sent to that address
Address lifecycle mapping
| Stage | rpmsg_device->src | rpmsg_endpoint->addr | Description |
|---|---|---|---|
| Device just created | RPMSG_ADDR_ANY | None (not yet created) | Waiting for backend allocation |
rpmsg_dev_probe()In | is updated and set toept->addr | ept->addr | the actual address allocated by the backend |
| Runtime | remains unchanged | remains unchanged | used for message routing |
Key assignment chain:
12345678 | // rpmsg_dev_probe()ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo); │ └── 后端分配 addr(如 0x401) │ ▼rpdev->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
| callback | framework behavior | Applicable scenarios |
|---|---|---|
| non-NULL | rpmsg_dev_probe()Automatically create default endpoint, bind callback torpdev->src | Simple service, single address listening |
| NULL | Framework 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
| object | Created by | Managed by | Lifecycle |
|---|---|---|---|
rpmsg_device | Backend (virtio) receives NS message | Kernel device model | During channel existence |
rpmsg_endpoint | Framework automatically creates (rpdev->ept) or driver manually creates | krefreference count | Bound to device or driver requirements |
rpmsg_driver | Statically defined by driver author | module_init/module_exit | During 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
| Member | Whether required | Call function | Call timing | Precondition |
|---|---|---|---|---|
| create_ept | Must | rpmsg_create_ept() | ① The framework automatically creates the default endpoint; ② The driver manually creates the endpoint. | rpdev->opsNon-empty |
| announce_create | Optional | rpmsg_dev_probe() | driverprobe()After success | ept created successfully andops->announce_createNon-empty |
| announce_destroy | Optional | rpmsg_dev_remove() | At the very beginning of device removal/driver unload | ops->announce_destroyNon-empty |
rpdev->ops->create_ept()
- Framework automatically creates
rpmsg_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 name
rpdev->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
| Operation | Caller | Callee | Core semantics |
|---|---|---|---|
| create_ept | rpmsg_create_ept() | Backend | Allocate resources: bind backend buffers and interrupts to the local address. |
| announce_create | rpmsg_dev_probe() | Backend | Publish service: notify the remote end that “a service is listening on this address”. |
| announce_destroy | rpmsg_dev_remove() | Backend | Revoke 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 |+--------------------------------------------------- |
| Level | ops | Operation object | Typical operations |
|---|---|---|---|
| Rpmsg Device | rpmsg_device_ops | rpmsg_device(channel) | Create endpoint, announce channel existence |
| Rpmsg Endpoint | rpmsg_endpoint_ops | rpmsg_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
| ops | required | core behavior (when not implemented) | virtio backend |
|---|---|---|---|
| destroy_ept | ✅ required | N/A (will not be NULL, must be set during initialization) | virtio_rpmsg_destroy_ept |
| send | ✅ required | -ENXIO | virtio_rpmsg_send |
| trysend | ✅ required | -ENXIO | virtio_rpmsg_trysend |
| sendto | optional | -ENXIO | virtio_rpmsg_sendto |
| send_offchannel | optional | -ENXIO | virtio_rpmsg_send_offchannel |
| trysendto | optional | -ENXIO | virtio_rpmsg_trysendto |
| trysend_offchannel | optional | -ENXIO | virtio_rpmsg_trysend_offchannel |
| poll | optional | returns 0 | undefined |
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=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) |
