To write an OpenAMP application there a few necessary pieces as follows:
- A firmware resource table.
The resource table defines the necessary firmware entries for the OpenAMP application. It is a list of system resources required by the remote_proc.
- Create remoteproc struct using resource table.
- Define RPMsg callback functions.
- Create RPMsg virtio device.
- Create an RPMsg endpint and associate the RPMsg device with the callback functions.
- Use rpmsg_send() to send message across to the remote processor.
- After initializing the framework, the flow of an OpenAMP application consists of the RPMsg channel acting as communication between the master and remote processor via the RPMsg send() and I/O callback functions. The following is a flow diagram to show this.
Figure 1. Flow Diagram
Following is a sample OpenAMP set up and flow with a resource table, Remoteproc instance and RPMsg callback functions:
struct resource_table table = {
/* Version number. If the structure changes in the future, this acts as
* reference to what the structure is.
*/
.ver = 1,
* Number of resources; Matches number of offsets in array */
.num = 2,
/* reserved (must be zero) */
.reserved = 0,
{ /* array of offsets pointing at various resource entries */
/* This RSC_RPROC_MEM entry set the shared memory address range. It is required to tell the Linux kernel range of the shared memory the remote can access. */
*/
{RSC_RPROC_MEM, 0x3ed40000, 0x3ed40000, 0x100000, 0},
/* virtio device header */
{
RSC_VDEV, VIRTIO_ID_RPMSG_, 0, RPMSG_IPU_C0_FEATURES, 0, 0, 0,
NUM_VRINGS, {0, 0},
}
}
};
#include <openamp/remoteproc.h>
#include <openamp/rpmsg.h>
#include <openamp/rpmsg_virtio.h>
/* User defined remoteproc operations for communication */
sturct remoteproc rproc_ops = {
.init = local_rproc_init;
.mmap = local_rproc_mmap;
.notify = local_rproc_notify;
.remove = local_rproc_remove;
};
/* Remoteproc instance. If you don't use Remoteproc VirtIO backend,
* you don't need to define the remoteproc instance.
*/
struct remoteproc rproc;
/* RPMsg VirtIO device instance. */
struct rpmsg_virtio_device rpmsg_vdev;
/* RPMsg device */
struct rpmsg_device *rpmsg_dev;
/* Resource Table. Resource table is used by remoteproc to describe
* the shared resources such as vdev(VirtIO device) and other shared memory.
* Resource table resources definition is in the remoteproc.h.
* Examples of the resource table can be found in the OpenAMP repo:
* - apps/machine/zynqmp/rsc_table.c
* - apps/machine/zynqmp_r5/rsc_table.c
* - apps/machine/zynq7/rsc_table.c
*/
void *rsc_table = &resource_table;
/* Size of the resource table */
int rsc_size = sizeof(resource_table);
/* Shared memory metal I/O region. It will be used by OpenAMP library
* to access the memory. You can have more than one shared memory regions
* in your application.
*/
struct metal_io_region *shm_io;
/* VirtIO device */
struct virtio_device *vdev;
/* RPMsg shared buffers pool */
struct rpmsg_virtio_shm_pool shpool;
/* Shared buffers */
void *shbuf;
/* RPMsg endpoint */
struct rpmsg_endpoint ept;
/* User defined RPMsg name service callback. This callback is called
* when there is no registered RPMsg endpoint is found for this name
* service. User can create RPMsg endpoint in this callback. */
void ns_bind_cb(struct rpmsg_device *rdev, const char *name, uint32_t dest);
/* User defined RPMsg endpoint received message callback */
void rpmsg_ept_cb(struct rpmsg_endpoint *ept, void *data, size_t len,
uint32_t src, void *priv);
/* User defined RPMsg name service unbind request callback */
void ns_unbind_cb(struct rpmsg_device *rdev, const char *name, uint32_t dest);
void main(void)
{
/* Instantiate remoteproc instance */
remoteproc_init(&rproc, &rproc_ops);
/* Mmap shared memories so that they can be used */
remoteproc_mmap(&rproc, &physical_address, NULL, size,
<memory_attributes>, &shm_io);
/* Parse resource table to remoteproc */
remoteproc_set_rsc_table(&rproc, rsc_table, rsc_size);
/* Create VirtIO device from remoteproc.
* VirtIO device master will initiate the VirtIO rings, and assign
* shared buffers. If you running the application as VirtIO slave, you
* set the role as VIRTIO_DEV_SLAVE.
* If you don't use remoteproc, you will need to define your own VirtIO
* device.
*/
vdev = remoteproc_create_virtio(&rproc, 0, VIRTIO_DEV_MASTER, NULL);
/* This step is only required if you are VirtIO device master.
* Initialize the shared buffers pool.
*/
shbuf = metal_io_phys_to_virt(shm_io, SHARED_BUF_PA);
rpmsg_virtio_init_shm_pool(&shpool, shbuf, SHARED_BUFF_SIZE);
/* Initialize RPMsg VirtIO device with the VirtIO device */
/* If it is VirtIO device slave, it will not return until the master
* side set the VirtIO device DRIVER OK status bit.
*/
rpmsg_init_vdev(&rpmsg_vdev, vdev, ns_bind_cb, io, shm_io, &shpool);
/* Get RPMsg device from RPMsg VirtIO device */
rpmsg_dev = rpmsg_virtio_get_rpmsg_device(&rpmsg_vdev);
/* Create RPMsg endpoint. */
rpmsg_create_ept(&ept, rdev, RPMSG_SERVICE_NAME, RPMSG_ADDR_ANY,
rpmsg_ept_cb, ns_unbind_cb);
/* If it is VirtIO device master, it sends the first message */
while (!is_rpmsg_ept_read(&ept)) {
/* check if the endpoint has binded.
* If not, wait for notification. If local endpoint hasn't
* been bound with the remote endpoint, it will fail to
* send the message to the remote.
*/
/* If you prefer to use interrupt, you can wait for
* interrupt here, and call the VirtIO notified function
* in the interrupt handling task.
*/
rproc_virtio_notified(vdev, RSC_NOTIFY_ID_ANY);
}
/* Send RPMsg */
rpmsg_send(&ept, data, size);
do {
/* If you prefer to use interrupt, you can wait for
* interrupt here, and call the VirtIO notified function
* in the interrupt handling task.
* If vdev is notified, the endpoint callback will be
* called.
*/
rproc_virtio_notified(vdev, RSC_NOTIFY_ID_ANY);
} while(!ns_unbind_cb_is_called && !user_decided_to_end_communication);
/* End of communication, destroy the endpoint */
rpmsg_destroy_ept(&ept);
rpmsg_deinit_vdev(&rpmsg_vdev);
remoteproc_remove_virtio(&rproc, vdev);
remoteproc_remove(&rproc);
}