To write an OpenAMP application there a few necessary pieces as follows:
1. 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.
2. Create remoteproc struct using resource table.
3. Define RPMsg callback functions.
4. Create RPMsg virtio device.
5. Create an RPMsg endpint and associate the RPMsg device with the callback functions.
6. Use rpmsg_send() to send message across to the remote processor.
7. 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.
X-Ref Target - Figure 3-4 |
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);
}