162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * remote processor messaging bus 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2011 Texas Instruments, Inc. 662306a36Sopenharmony_ci * Copyright (C) 2011 Google, Inc. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Ohad Ben-Cohen <ohad@wizery.com> 962306a36Sopenharmony_ci * Brian Swetland <swetland@google.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define pr_fmt(fmt) "%s: " fmt, __func__ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/rpmsg.h> 1762306a36Sopenharmony_ci#include <linux/of_device.h> 1862306a36Sopenharmony_ci#include <linux/pm_domain.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include "rpmsg_internal.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct class *rpmsg_class; 2462306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_class); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/** 2762306a36Sopenharmony_ci * rpmsg_create_channel() - create a new rpmsg channel 2862306a36Sopenharmony_ci * using its name and address info. 2962306a36Sopenharmony_ci * @rpdev: rpmsg device 3062306a36Sopenharmony_ci * @chinfo: channel_info to bind 3162306a36Sopenharmony_ci * 3262306a36Sopenharmony_ci * Return: a pointer to the new rpmsg device on success, or NULL on error. 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_cistruct rpmsg_device *rpmsg_create_channel(struct rpmsg_device *rpdev, 3562306a36Sopenharmony_ci struct rpmsg_channel_info *chinfo) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci if (WARN_ON(!rpdev)) 3862306a36Sopenharmony_ci return NULL; 3962306a36Sopenharmony_ci if (!rpdev->ops || !rpdev->ops->create_channel) { 4062306a36Sopenharmony_ci dev_err(&rpdev->dev, "no create_channel ops found\n"); 4162306a36Sopenharmony_ci return NULL; 4262306a36Sopenharmony_ci } 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci return rpdev->ops->create_channel(rpdev, chinfo); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_create_channel); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/** 4962306a36Sopenharmony_ci * rpmsg_release_channel() - release a rpmsg channel 5062306a36Sopenharmony_ci * using its name and address info. 5162306a36Sopenharmony_ci * @rpdev: rpmsg device 5262306a36Sopenharmony_ci * @chinfo: channel_info to bind 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * Return: 0 on success or an appropriate error value. 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_ciint rpmsg_release_channel(struct rpmsg_device *rpdev, 5762306a36Sopenharmony_ci struct rpmsg_channel_info *chinfo) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci if (WARN_ON(!rpdev)) 6062306a36Sopenharmony_ci return -EINVAL; 6162306a36Sopenharmony_ci if (!rpdev->ops || !rpdev->ops->release_channel) { 6262306a36Sopenharmony_ci dev_err(&rpdev->dev, "no release_channel ops found\n"); 6362306a36Sopenharmony_ci return -ENXIO; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci return rpdev->ops->release_channel(rpdev, chinfo); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_release_channel); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/** 7162306a36Sopenharmony_ci * rpmsg_create_ept() - create a new rpmsg_endpoint 7262306a36Sopenharmony_ci * @rpdev: rpmsg channel device 7362306a36Sopenharmony_ci * @cb: rx callback handler 7462306a36Sopenharmony_ci * @priv: private data for the driver's use 7562306a36Sopenharmony_ci * @chinfo: channel_info with the local rpmsg address to bind with @cb 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * Every rpmsg address in the system is bound to an rx callback (so when 7862306a36Sopenharmony_ci * inbound messages arrive, they are dispatched by the rpmsg bus using the 7962306a36Sopenharmony_ci * appropriate callback handler) by means of an rpmsg_endpoint struct. 8062306a36Sopenharmony_ci * 8162306a36Sopenharmony_ci * This function allows drivers to create such an endpoint, and by that, 8262306a36Sopenharmony_ci * bind a callback, and possibly some private data too, to an rpmsg address 8362306a36Sopenharmony_ci * (either one that is known in advance, or one that will be dynamically 8462306a36Sopenharmony_ci * assigned for them). 8562306a36Sopenharmony_ci * 8662306a36Sopenharmony_ci * Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint 8762306a36Sopenharmony_ci * is already created for them when they are probed by the rpmsg bus 8862306a36Sopenharmony_ci * (using the rx callback provided when they registered to the rpmsg bus). 8962306a36Sopenharmony_ci * 9062306a36Sopenharmony_ci * So things should just work for simple drivers: they already have an 9162306a36Sopenharmony_ci * endpoint, their rx callback is bound to their rpmsg address, and when 9262306a36Sopenharmony_ci * relevant inbound messages arrive (i.e. messages which their dst address 9362306a36Sopenharmony_ci * equals to the src address of their rpmsg channel), the driver's handler 9462306a36Sopenharmony_ci * is invoked to process it. 9562306a36Sopenharmony_ci * 9662306a36Sopenharmony_ci * That said, more complicated drivers might need to allocate 9762306a36Sopenharmony_ci * additional rpmsg addresses, and bind them to different rx callbacks. 9862306a36Sopenharmony_ci * To accomplish that, those drivers need to call this function. 9962306a36Sopenharmony_ci * 10062306a36Sopenharmony_ci * Drivers should provide their @rpdev channel (so the new endpoint would belong 10162306a36Sopenharmony_ci * to the same remote processor their channel belongs to), an rx callback 10262306a36Sopenharmony_ci * function, an optional private data (which is provided back when the 10362306a36Sopenharmony_ci * rx callback is invoked), and an address they want to bind with the 10462306a36Sopenharmony_ci * callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will 10562306a36Sopenharmony_ci * dynamically assign them an available rpmsg address (drivers should have 10662306a36Sopenharmony_ci * a very good reason why not to always use RPMSG_ADDR_ANY here). 10762306a36Sopenharmony_ci * 10862306a36Sopenharmony_ci * Return: a pointer to the endpoint on success, or NULL on error. 10962306a36Sopenharmony_ci */ 11062306a36Sopenharmony_cistruct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev, 11162306a36Sopenharmony_ci rpmsg_rx_cb_t cb, void *priv, 11262306a36Sopenharmony_ci struct rpmsg_channel_info chinfo) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci if (WARN_ON(!rpdev)) 11562306a36Sopenharmony_ci return NULL; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return rpdev->ops->create_ept(rpdev, cb, priv, chinfo); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_create_ept); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/** 12262306a36Sopenharmony_ci * rpmsg_destroy_ept() - destroy an existing rpmsg endpoint 12362306a36Sopenharmony_ci * @ept: endpoing to destroy 12462306a36Sopenharmony_ci * 12562306a36Sopenharmony_ci * Should be used by drivers to destroy an rpmsg endpoint previously 12662306a36Sopenharmony_ci * created with rpmsg_create_ept(). As with other types of "free" NULL 12762306a36Sopenharmony_ci * is a valid parameter. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_civoid rpmsg_destroy_ept(struct rpmsg_endpoint *ept) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci if (ept && ept->ops) 13262306a36Sopenharmony_ci ept->ops->destroy_ept(ept); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_destroy_ept); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci/** 13762306a36Sopenharmony_ci * rpmsg_send() - send a message across to the remote processor 13862306a36Sopenharmony_ci * @ept: the rpmsg endpoint 13962306a36Sopenharmony_ci * @data: payload of message 14062306a36Sopenharmony_ci * @len: length of payload 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * This function sends @data of length @len on the @ept endpoint. 14362306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 14462306a36Sopenharmony_ci * endpoint belongs to, using @ept's address and its associated rpmsg 14562306a36Sopenharmony_ci * device destination addresses. 14662306a36Sopenharmony_ci * In case there are no TX buffers available, the function will block until 14762306a36Sopenharmony_ci * one becomes available, or a timeout of 15 seconds elapses. When the latter 14862306a36Sopenharmony_ci * happens, -ERESTARTSYS is returned. 14962306a36Sopenharmony_ci * 15062306a36Sopenharmony_ci * Can only be called from process context (for now). 15162306a36Sopenharmony_ci * 15262306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ciint rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci if (WARN_ON(!ept)) 15762306a36Sopenharmony_ci return -EINVAL; 15862306a36Sopenharmony_ci if (!ept->ops->send) 15962306a36Sopenharmony_ci return -ENXIO; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return ept->ops->send(ept, data, len); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_send); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci/** 16662306a36Sopenharmony_ci * rpmsg_sendto() - send a message across to the remote processor, specify dst 16762306a36Sopenharmony_ci * @ept: the rpmsg endpoint 16862306a36Sopenharmony_ci * @data: payload of message 16962306a36Sopenharmony_ci * @len: length of payload 17062306a36Sopenharmony_ci * @dst: destination address 17162306a36Sopenharmony_ci * 17262306a36Sopenharmony_ci * This function sends @data of length @len to the remote @dst address. 17362306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 17462306a36Sopenharmony_ci * endpoint belongs to, using @ept's address as source. 17562306a36Sopenharmony_ci * In case there are no TX buffers available, the function will block until 17662306a36Sopenharmony_ci * one becomes available, or a timeout of 15 seconds elapses. When the latter 17762306a36Sopenharmony_ci * happens, -ERESTARTSYS is returned. 17862306a36Sopenharmony_ci * 17962306a36Sopenharmony_ci * Can only be called from process context (for now). 18062306a36Sopenharmony_ci * 18162306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_ciint rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci if (WARN_ON(!ept)) 18662306a36Sopenharmony_ci return -EINVAL; 18762306a36Sopenharmony_ci if (!ept->ops->sendto) 18862306a36Sopenharmony_ci return -ENXIO; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return ept->ops->sendto(ept, data, len, dst); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_sendto); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci/** 19562306a36Sopenharmony_ci * rpmsg_send_offchannel() - send a message using explicit src/dst addresses 19662306a36Sopenharmony_ci * @ept: the rpmsg endpoint 19762306a36Sopenharmony_ci * @src: source address 19862306a36Sopenharmony_ci * @dst: destination address 19962306a36Sopenharmony_ci * @data: payload of message 20062306a36Sopenharmony_ci * @len: length of payload 20162306a36Sopenharmony_ci * 20262306a36Sopenharmony_ci * This function sends @data of length @len to the remote @dst address, 20362306a36Sopenharmony_ci * and uses @src as the source address. 20462306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 20562306a36Sopenharmony_ci * endpoint belongs to. 20662306a36Sopenharmony_ci * In case there are no TX buffers available, the function will block until 20762306a36Sopenharmony_ci * one becomes available, or a timeout of 15 seconds elapses. When the latter 20862306a36Sopenharmony_ci * happens, -ERESTARTSYS is returned. 20962306a36Sopenharmony_ci * 21062306a36Sopenharmony_ci * Can only be called from process context (for now). 21162306a36Sopenharmony_ci * 21262306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 21362306a36Sopenharmony_ci */ 21462306a36Sopenharmony_ciint rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst, 21562306a36Sopenharmony_ci void *data, int len) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci if (WARN_ON(!ept)) 21862306a36Sopenharmony_ci return -EINVAL; 21962306a36Sopenharmony_ci if (!ept->ops->send_offchannel) 22062306a36Sopenharmony_ci return -ENXIO; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return ept->ops->send_offchannel(ept, src, dst, data, len); 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_send_offchannel); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci/** 22762306a36Sopenharmony_ci * rpmsg_trysend() - send a message across to the remote processor 22862306a36Sopenharmony_ci * @ept: the rpmsg endpoint 22962306a36Sopenharmony_ci * @data: payload of message 23062306a36Sopenharmony_ci * @len: length of payload 23162306a36Sopenharmony_ci * 23262306a36Sopenharmony_ci * This function sends @data of length @len on the @ept endpoint. 23362306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 23462306a36Sopenharmony_ci * endpoint belongs to, using @ept's address as source and its associated 23562306a36Sopenharmony_ci * rpdev's address as destination. 23662306a36Sopenharmony_ci * In case there are no TX buffers available, the function will immediately 23762306a36Sopenharmony_ci * return -ENOMEM without waiting until one becomes available. 23862306a36Sopenharmony_ci * 23962306a36Sopenharmony_ci * Can only be called from process context (for now). 24062306a36Sopenharmony_ci * 24162306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 24262306a36Sopenharmony_ci */ 24362306a36Sopenharmony_ciint rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci if (WARN_ON(!ept)) 24662306a36Sopenharmony_ci return -EINVAL; 24762306a36Sopenharmony_ci if (!ept->ops->trysend) 24862306a36Sopenharmony_ci return -ENXIO; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return ept->ops->trysend(ept, data, len); 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_trysend); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/** 25562306a36Sopenharmony_ci * rpmsg_trysendto() - send a message across to the remote processor, specify dst 25662306a36Sopenharmony_ci * @ept: the rpmsg endpoint 25762306a36Sopenharmony_ci * @data: payload of message 25862306a36Sopenharmony_ci * @len: length of payload 25962306a36Sopenharmony_ci * @dst: destination address 26062306a36Sopenharmony_ci * 26162306a36Sopenharmony_ci * This function sends @data of length @len to the remote @dst address. 26262306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 26362306a36Sopenharmony_ci * endpoint belongs to, using @ept's address as source. 26462306a36Sopenharmony_ci * In case there are no TX buffers available, the function will immediately 26562306a36Sopenharmony_ci * return -ENOMEM without waiting until one becomes available. 26662306a36Sopenharmony_ci * 26762306a36Sopenharmony_ci * Can only be called from process context (for now). 26862306a36Sopenharmony_ci * 26962306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 27062306a36Sopenharmony_ci */ 27162306a36Sopenharmony_ciint rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci if (WARN_ON(!ept)) 27462306a36Sopenharmony_ci return -EINVAL; 27562306a36Sopenharmony_ci if (!ept->ops->trysendto) 27662306a36Sopenharmony_ci return -ENXIO; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci return ept->ops->trysendto(ept, data, len, dst); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_trysendto); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci/** 28362306a36Sopenharmony_ci * rpmsg_poll() - poll the endpoint's send buffers 28462306a36Sopenharmony_ci * @ept: the rpmsg endpoint 28562306a36Sopenharmony_ci * @filp: file for poll_wait() 28662306a36Sopenharmony_ci * @wait: poll_table for poll_wait() 28762306a36Sopenharmony_ci * 28862306a36Sopenharmony_ci * Return: mask representing the current state of the endpoint's send buffers 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp, 29162306a36Sopenharmony_ci poll_table *wait) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci if (WARN_ON(!ept)) 29462306a36Sopenharmony_ci return 0; 29562306a36Sopenharmony_ci if (!ept->ops->poll) 29662306a36Sopenharmony_ci return 0; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci return ept->ops->poll(ept, filp, wait); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_poll); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci/** 30362306a36Sopenharmony_ci * rpmsg_trysend_offchannel() - send a message using explicit src/dst addresses 30462306a36Sopenharmony_ci * @ept: the rpmsg endpoint 30562306a36Sopenharmony_ci * @src: source address 30662306a36Sopenharmony_ci * @dst: destination address 30762306a36Sopenharmony_ci * @data: payload of message 30862306a36Sopenharmony_ci * @len: length of payload 30962306a36Sopenharmony_ci * 31062306a36Sopenharmony_ci * This function sends @data of length @len to the remote @dst address, 31162306a36Sopenharmony_ci * and uses @src as the source address. 31262306a36Sopenharmony_ci * The message will be sent to the remote processor which the @ept 31362306a36Sopenharmony_ci * endpoint belongs to. 31462306a36Sopenharmony_ci * In case there are no TX buffers available, the function will immediately 31562306a36Sopenharmony_ci * return -ENOMEM without waiting until one becomes available. 31662306a36Sopenharmony_ci * 31762306a36Sopenharmony_ci * Can only be called from process context (for now). 31862306a36Sopenharmony_ci * 31962306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 32062306a36Sopenharmony_ci */ 32162306a36Sopenharmony_ciint rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst, 32262306a36Sopenharmony_ci void *data, int len) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci if (WARN_ON(!ept)) 32562306a36Sopenharmony_ci return -EINVAL; 32662306a36Sopenharmony_ci if (!ept->ops->trysend_offchannel) 32762306a36Sopenharmony_ci return -ENXIO; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return ept->ops->trysend_offchannel(ept, src, dst, data, len); 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_trysend_offchannel); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci/** 33462306a36Sopenharmony_ci * rpmsg_set_flow_control() - request remote to pause/resume transmission 33562306a36Sopenharmony_ci * @ept: the rpmsg endpoint 33662306a36Sopenharmony_ci * @pause: pause transmission 33762306a36Sopenharmony_ci * @dst: destination address of the endpoint 33862306a36Sopenharmony_ci * 33962306a36Sopenharmony_ci * Return: 0 on success and an appropriate error value on failure. 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_ciint rpmsg_set_flow_control(struct rpmsg_endpoint *ept, bool pause, u32 dst) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci if (WARN_ON(!ept)) 34462306a36Sopenharmony_ci return -EINVAL; 34562306a36Sopenharmony_ci if (!ept->ops->set_flow_control) 34662306a36Sopenharmony_ci return -EOPNOTSUPP; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return ept->ops->set_flow_control(ept, pause, dst); 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(rpmsg_set_flow_control); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci/** 35362306a36Sopenharmony_ci * rpmsg_get_mtu() - get maximum transmission buffer size for sending message. 35462306a36Sopenharmony_ci * @ept: the rpmsg endpoint 35562306a36Sopenharmony_ci * 35662306a36Sopenharmony_ci * This function returns maximum buffer size available for a single outgoing message. 35762306a36Sopenharmony_ci * 35862306a36Sopenharmony_ci * Return: the maximum transmission size on success and an appropriate error 35962306a36Sopenharmony_ci * value on failure. 36062306a36Sopenharmony_ci */ 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cissize_t rpmsg_get_mtu(struct rpmsg_endpoint *ept) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci if (WARN_ON(!ept)) 36562306a36Sopenharmony_ci return -EINVAL; 36662306a36Sopenharmony_ci if (!ept->ops->get_mtu) 36762306a36Sopenharmony_ci return -ENOTSUPP; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci return ept->ops->get_mtu(ept); 37062306a36Sopenharmony_ci} 37162306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_get_mtu); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci/* 37462306a36Sopenharmony_ci * match a rpmsg channel with a channel info struct. 37562306a36Sopenharmony_ci * this is used to make sure we're not creating rpmsg devices for channels 37662306a36Sopenharmony_ci * that already exist. 37762306a36Sopenharmony_ci */ 37862306a36Sopenharmony_cistatic int rpmsg_device_match(struct device *dev, void *data) 37962306a36Sopenharmony_ci{ 38062306a36Sopenharmony_ci struct rpmsg_channel_info *chinfo = data; 38162306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src) 38462306a36Sopenharmony_ci return 0; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst) 38762306a36Sopenharmony_ci return 0; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE)) 39062306a36Sopenharmony_ci return 0; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* found a match ! */ 39362306a36Sopenharmony_ci return 1; 39462306a36Sopenharmony_ci} 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cistruct device *rpmsg_find_device(struct device *parent, 39762306a36Sopenharmony_ci struct rpmsg_channel_info *chinfo) 39862306a36Sopenharmony_ci{ 39962306a36Sopenharmony_ci return device_find_child(parent, chinfo, rpmsg_device_match); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci} 40262306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_find_device); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci/* sysfs show configuration fields */ 40562306a36Sopenharmony_ci#define rpmsg_show_attr(field, path, format_string) \ 40662306a36Sopenharmony_cistatic ssize_t \ 40762306a36Sopenharmony_cifield##_show(struct device *dev, \ 40862306a36Sopenharmony_ci struct device_attribute *attr, char *buf) \ 40962306a36Sopenharmony_ci{ \ 41062306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); \ 41162306a36Sopenharmony_ci \ 41262306a36Sopenharmony_ci return sprintf(buf, format_string, rpdev->path); \ 41362306a36Sopenharmony_ci} \ 41462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(field); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci#define rpmsg_string_attr(field, member) \ 41762306a36Sopenharmony_cistatic ssize_t \ 41862306a36Sopenharmony_cifield##_store(struct device *dev, struct device_attribute *attr, \ 41962306a36Sopenharmony_ci const char *buf, size_t sz) \ 42062306a36Sopenharmony_ci{ \ 42162306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); \ 42262306a36Sopenharmony_ci const char *old; \ 42362306a36Sopenharmony_ci char *new; \ 42462306a36Sopenharmony_ci \ 42562306a36Sopenharmony_ci new = kstrndup(buf, sz, GFP_KERNEL); \ 42662306a36Sopenharmony_ci if (!new) \ 42762306a36Sopenharmony_ci return -ENOMEM; \ 42862306a36Sopenharmony_ci new[strcspn(new, "\n")] = '\0'; \ 42962306a36Sopenharmony_ci \ 43062306a36Sopenharmony_ci device_lock(dev); \ 43162306a36Sopenharmony_ci old = rpdev->member; \ 43262306a36Sopenharmony_ci if (strlen(new)) { \ 43362306a36Sopenharmony_ci rpdev->member = new; \ 43462306a36Sopenharmony_ci } else { \ 43562306a36Sopenharmony_ci kfree(new); \ 43662306a36Sopenharmony_ci rpdev->member = NULL; \ 43762306a36Sopenharmony_ci } \ 43862306a36Sopenharmony_ci device_unlock(dev); \ 43962306a36Sopenharmony_ci \ 44062306a36Sopenharmony_ci kfree(old); \ 44162306a36Sopenharmony_ci \ 44262306a36Sopenharmony_ci return sz; \ 44362306a36Sopenharmony_ci} \ 44462306a36Sopenharmony_cistatic ssize_t \ 44562306a36Sopenharmony_cifield##_show(struct device *dev, \ 44662306a36Sopenharmony_ci struct device_attribute *attr, char *buf) \ 44762306a36Sopenharmony_ci{ \ 44862306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); \ 44962306a36Sopenharmony_ci \ 45062306a36Sopenharmony_ci return sprintf(buf, "%s\n", rpdev->member); \ 45162306a36Sopenharmony_ci} \ 45262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(field) 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */ 45562306a36Sopenharmony_cirpmsg_show_attr(name, id.name, "%s\n"); 45662306a36Sopenharmony_cirpmsg_show_attr(src, src, "0x%x\n"); 45762306a36Sopenharmony_cirpmsg_show_attr(dst, dst, "0x%x\n"); 45862306a36Sopenharmony_cirpmsg_show_attr(announce, announce ? "true" : "false", "%s\n"); 45962306a36Sopenharmony_cirpmsg_string_attr(driver_override, driver_override); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic ssize_t modalias_show(struct device *dev, 46262306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 46562306a36Sopenharmony_ci ssize_t len; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci len = of_device_modalias(dev, buf, PAGE_SIZE); 46862306a36Sopenharmony_ci if (len != -ENODEV) 46962306a36Sopenharmony_ci return len; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name); 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(modalias); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_cistatic struct attribute *rpmsg_dev_attrs[] = { 47662306a36Sopenharmony_ci &dev_attr_name.attr, 47762306a36Sopenharmony_ci &dev_attr_modalias.attr, 47862306a36Sopenharmony_ci &dev_attr_dst.attr, 47962306a36Sopenharmony_ci &dev_attr_src.attr, 48062306a36Sopenharmony_ci &dev_attr_announce.attr, 48162306a36Sopenharmony_ci &dev_attr_driver_override.attr, 48262306a36Sopenharmony_ci NULL, 48362306a36Sopenharmony_ci}; 48462306a36Sopenharmony_ciATTRIBUTE_GROUPS(rpmsg_dev); 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci/* rpmsg devices and drivers are matched using the service name */ 48762306a36Sopenharmony_cistatic inline int rpmsg_id_match(const struct rpmsg_device *rpdev, 48862306a36Sopenharmony_ci const struct rpmsg_device_id *id) 48962306a36Sopenharmony_ci{ 49062306a36Sopenharmony_ci return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0; 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci/* match rpmsg channel and rpmsg driver */ 49462306a36Sopenharmony_cistatic int rpmsg_dev_match(struct device *dev, struct device_driver *drv) 49562306a36Sopenharmony_ci{ 49662306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 49762306a36Sopenharmony_ci struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv); 49862306a36Sopenharmony_ci const struct rpmsg_device_id *ids = rpdrv->id_table; 49962306a36Sopenharmony_ci unsigned int i; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (rpdev->driver_override) 50262306a36Sopenharmony_ci return !strcmp(rpdev->driver_override, drv->name); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (ids) 50562306a36Sopenharmony_ci for (i = 0; ids[i].name[0]; i++) 50662306a36Sopenharmony_ci if (rpmsg_id_match(rpdev, &ids[i])) { 50762306a36Sopenharmony_ci rpdev->id.driver_data = ids[i].driver_data; 50862306a36Sopenharmony_ci return 1; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci return of_driver_match_device(dev, drv); 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_cistatic int rpmsg_uevent(const struct device *dev, struct kobj_uevent_env *env) 51562306a36Sopenharmony_ci{ 51662306a36Sopenharmony_ci const struct rpmsg_device *rpdev = to_rpmsg_device(dev); 51762306a36Sopenharmony_ci int ret; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci ret = of_device_uevent_modalias(dev, env); 52062306a36Sopenharmony_ci if (ret != -ENODEV) 52162306a36Sopenharmony_ci return ret; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT, 52462306a36Sopenharmony_ci rpdev->id.name); 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci/* 52862306a36Sopenharmony_ci * when an rpmsg driver is probed with a channel, we seamlessly create 52962306a36Sopenharmony_ci * it an endpoint, binding its rx callback to a unique local rpmsg 53062306a36Sopenharmony_ci * address. 53162306a36Sopenharmony_ci * 53262306a36Sopenharmony_ci * if we need to, we also announce about this channel to the remote 53362306a36Sopenharmony_ci * processor (needed in case the driver is exposing an rpmsg service). 53462306a36Sopenharmony_ci */ 53562306a36Sopenharmony_cistatic int rpmsg_dev_probe(struct device *dev) 53662306a36Sopenharmony_ci{ 53762306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 53862306a36Sopenharmony_ci struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); 53962306a36Sopenharmony_ci struct rpmsg_channel_info chinfo = {}; 54062306a36Sopenharmony_ci struct rpmsg_endpoint *ept = NULL; 54162306a36Sopenharmony_ci int err; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci err = dev_pm_domain_attach(dev, true); 54462306a36Sopenharmony_ci if (err) 54562306a36Sopenharmony_ci goto out; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci if (rpdrv->callback) { 54862306a36Sopenharmony_ci strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE); 54962306a36Sopenharmony_ci chinfo.src = rpdev->src; 55062306a36Sopenharmony_ci chinfo.dst = RPMSG_ADDR_ANY; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo); 55362306a36Sopenharmony_ci if (!ept) { 55462306a36Sopenharmony_ci dev_err(dev, "failed to create endpoint\n"); 55562306a36Sopenharmony_ci err = -ENOMEM; 55662306a36Sopenharmony_ci goto out; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci rpdev->ept = ept; 56062306a36Sopenharmony_ci rpdev->src = ept->addr; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci ept->flow_cb = rpdrv->flowcontrol; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci err = rpdrv->probe(rpdev); 56662306a36Sopenharmony_ci if (err) { 56762306a36Sopenharmony_ci dev_err(dev, "%s: failed: %d\n", __func__, err); 56862306a36Sopenharmony_ci goto destroy_ept; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci if (ept && rpdev->ops->announce_create) { 57262306a36Sopenharmony_ci err = rpdev->ops->announce_create(rpdev); 57362306a36Sopenharmony_ci if (err) { 57462306a36Sopenharmony_ci dev_err(dev, "failed to announce creation\n"); 57562306a36Sopenharmony_ci goto remove_rpdev; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci } 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci return 0; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ciremove_rpdev: 58262306a36Sopenharmony_ci if (rpdrv->remove) 58362306a36Sopenharmony_ci rpdrv->remove(rpdev); 58462306a36Sopenharmony_cidestroy_ept: 58562306a36Sopenharmony_ci if (ept) 58662306a36Sopenharmony_ci rpmsg_destroy_ept(ept); 58762306a36Sopenharmony_ciout: 58862306a36Sopenharmony_ci return err; 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic void rpmsg_dev_remove(struct device *dev) 59262306a36Sopenharmony_ci{ 59362306a36Sopenharmony_ci struct rpmsg_device *rpdev = to_rpmsg_device(dev); 59462306a36Sopenharmony_ci struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci if (rpdev->ops->announce_destroy) 59762306a36Sopenharmony_ci rpdev->ops->announce_destroy(rpdev); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci if (rpdrv->remove) 60062306a36Sopenharmony_ci rpdrv->remove(rpdev); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci dev_pm_domain_detach(dev, true); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci if (rpdev->ept) 60562306a36Sopenharmony_ci rpmsg_destroy_ept(rpdev->ept); 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_cistatic struct bus_type rpmsg_bus = { 60962306a36Sopenharmony_ci .name = "rpmsg", 61062306a36Sopenharmony_ci .match = rpmsg_dev_match, 61162306a36Sopenharmony_ci .dev_groups = rpmsg_dev_groups, 61262306a36Sopenharmony_ci .uevent = rpmsg_uevent, 61362306a36Sopenharmony_ci .probe = rpmsg_dev_probe, 61462306a36Sopenharmony_ci .remove = rpmsg_dev_remove, 61562306a36Sopenharmony_ci}; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci/* 61862306a36Sopenharmony_ci * A helper for registering rpmsg device with driver override and name. 61962306a36Sopenharmony_ci * Drivers should not be using it, but instead rpmsg_register_device(). 62062306a36Sopenharmony_ci */ 62162306a36Sopenharmony_ciint rpmsg_register_device_override(struct rpmsg_device *rpdev, 62262306a36Sopenharmony_ci const char *driver_override) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci struct device *dev = &rpdev->dev; 62562306a36Sopenharmony_ci int ret; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (driver_override) 62862306a36Sopenharmony_ci strscpy_pad(rpdev->id.name, driver_override, RPMSG_NAME_SIZE); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci dev_set_name(dev, "%s.%s.%d.%d", dev_name(dev->parent), 63162306a36Sopenharmony_ci rpdev->id.name, rpdev->src, rpdev->dst); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci dev->bus = &rpmsg_bus; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci device_initialize(dev); 63662306a36Sopenharmony_ci if (driver_override) { 63762306a36Sopenharmony_ci ret = driver_set_override(dev, &rpdev->driver_override, 63862306a36Sopenharmony_ci driver_override, 63962306a36Sopenharmony_ci strlen(driver_override)); 64062306a36Sopenharmony_ci if (ret) { 64162306a36Sopenharmony_ci dev_err(dev, "device_set_override failed: %d\n", ret); 64262306a36Sopenharmony_ci put_device(dev); 64362306a36Sopenharmony_ci return ret; 64462306a36Sopenharmony_ci } 64562306a36Sopenharmony_ci } 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci ret = device_add(dev); 64862306a36Sopenharmony_ci if (ret) { 64962306a36Sopenharmony_ci dev_err(dev, "device_add failed: %d\n", ret); 65062306a36Sopenharmony_ci kfree(rpdev->driver_override); 65162306a36Sopenharmony_ci rpdev->driver_override = NULL; 65262306a36Sopenharmony_ci put_device(dev); 65362306a36Sopenharmony_ci } 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci return ret; 65662306a36Sopenharmony_ci} 65762306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_register_device_override); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ciint rpmsg_register_device(struct rpmsg_device *rpdev) 66062306a36Sopenharmony_ci{ 66162306a36Sopenharmony_ci return rpmsg_register_device_override(rpdev, NULL); 66262306a36Sopenharmony_ci} 66362306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_register_device); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci/* 66662306a36Sopenharmony_ci * find an existing channel using its name + address properties, 66762306a36Sopenharmony_ci * and destroy it 66862306a36Sopenharmony_ci */ 66962306a36Sopenharmony_ciint rpmsg_unregister_device(struct device *parent, 67062306a36Sopenharmony_ci struct rpmsg_channel_info *chinfo) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct device *dev; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci dev = rpmsg_find_device(parent, chinfo); 67562306a36Sopenharmony_ci if (!dev) 67662306a36Sopenharmony_ci return -EINVAL; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci device_unregister(dev); 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci put_device(dev); 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci return 0; 68362306a36Sopenharmony_ci} 68462306a36Sopenharmony_ciEXPORT_SYMBOL(rpmsg_unregister_device); 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci/** 68762306a36Sopenharmony_ci * __register_rpmsg_driver() - register an rpmsg driver with the rpmsg bus 68862306a36Sopenharmony_ci * @rpdrv: pointer to a struct rpmsg_driver 68962306a36Sopenharmony_ci * @owner: owning module/driver 69062306a36Sopenharmony_ci * 69162306a36Sopenharmony_ci * Return: 0 on success, and an appropriate error value on failure. 69262306a36Sopenharmony_ci */ 69362306a36Sopenharmony_ciint __register_rpmsg_driver(struct rpmsg_driver *rpdrv, struct module *owner) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci rpdrv->drv.bus = &rpmsg_bus; 69662306a36Sopenharmony_ci rpdrv->drv.owner = owner; 69762306a36Sopenharmony_ci return driver_register(&rpdrv->drv); 69862306a36Sopenharmony_ci} 69962306a36Sopenharmony_ciEXPORT_SYMBOL(__register_rpmsg_driver); 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci/** 70262306a36Sopenharmony_ci * unregister_rpmsg_driver() - unregister an rpmsg driver from the rpmsg bus 70362306a36Sopenharmony_ci * @rpdrv: pointer to a struct rpmsg_driver 70462306a36Sopenharmony_ci * 70562306a36Sopenharmony_ci * Return: 0 on success, and an appropriate error value on failure. 70662306a36Sopenharmony_ci */ 70762306a36Sopenharmony_civoid unregister_rpmsg_driver(struct rpmsg_driver *rpdrv) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci driver_unregister(&rpdrv->drv); 71062306a36Sopenharmony_ci} 71162306a36Sopenharmony_ciEXPORT_SYMBOL(unregister_rpmsg_driver); 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_cistatic int __init rpmsg_init(void) 71562306a36Sopenharmony_ci{ 71662306a36Sopenharmony_ci int ret; 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci rpmsg_class = class_create("rpmsg"); 71962306a36Sopenharmony_ci if (IS_ERR(rpmsg_class)) { 72062306a36Sopenharmony_ci pr_err("failed to create rpmsg class\n"); 72162306a36Sopenharmony_ci return PTR_ERR(rpmsg_class); 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci ret = bus_register(&rpmsg_bus); 72562306a36Sopenharmony_ci if (ret) { 72662306a36Sopenharmony_ci pr_err("failed to register rpmsg bus: %d\n", ret); 72762306a36Sopenharmony_ci class_destroy(rpmsg_class); 72862306a36Sopenharmony_ci } 72962306a36Sopenharmony_ci return ret; 73062306a36Sopenharmony_ci} 73162306a36Sopenharmony_cipostcore_initcall(rpmsg_init); 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_cistatic void __exit rpmsg_fini(void) 73462306a36Sopenharmony_ci{ 73562306a36Sopenharmony_ci bus_unregister(&rpmsg_bus); 73662306a36Sopenharmony_ci class_destroy(rpmsg_class); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_cimodule_exit(rpmsg_fini); 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ciMODULE_DESCRIPTION("remote processor messaging bus"); 74162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 742