Writing IPC applications¶
Writing IPC Applications on RTOS (PDK IPC driver)¶
IPC setup code¶
Initialise the IPC stack before starting any communication with other cores. This can usually be done by
uint32_t selfProcId = IPC_MCU2_1;
uint32_t remoteProcList[] =
{
IPC_MPU1_0, IPC_MCU1_0, IPC_MCU1_1, IPC_MCU2_0, IPC_MCU3_0, IPC_MCU3_1, IPC_C66X_1, IPC_C66X_2, IPC_C7X_1
};
uint32_t numRemoteProcs = sizeof(remoteProcList)/sizeof(uint32_t);
Ipc_init(NULL);
Ipc_mpSetConfig(selfProcId, numRemoteProcs, &remoteProcList[0]);
Note
It is required to call Ipc_mpSetConfig()
with a list of all cores (except selfProcId
) that are required to participate in the communication.
If it is required to communicate with Linux running on A72 cores, the resource table must also be loaded
Ipc_loadResourceTable((void*)&ti_ipc_remoteproc_ResourceTable);
Note
See the example resource tables (using exactly one trace
entry and one vdev
entry) in apps\/basic_demos\/app_tirtos\/tirtos_linux\/ipc_rsctable.h
After the IPC stack is setup, the core needs to initialise the virtio or VRING layer. The virtio framework can be initialised as
vqParam.vqObjBaseAddr = (void*)&sysVqBuf[0];
vqParam.vqBufSize = numRemoteProcs * Ipc_getVqObjMemoryRequiredPerCore();
vqParam.vringBaseAddr = (void*)VRING_BASE_ADDRESS;
vqParam.vringBufSize = IPC_VRING_BUFFER_SIZE;
vqParam.timeoutCnt = 100;
Ipc_initVirtIO(&vqParam);
Note
Ipc_initVirtIO()
will internally call Ipc_isRemoteReady()
and will initialise the virtio layers for only the currently online cores.
If core is not online, it skips virtio pairing with that core. This can happen when the RTOS cores are booted early, and Linux remoteproc
and virtio drivers are initialised later.
There are two possible ways to work around this problem:
Wait for all Linux cores to be ready before calling
Ipc_initVirtIO()
for(i = 0; i < numRemoteProcs; i++) while(!Ipc_isRemoteReady(remoteProcList[i])) Task_sleep(1); vqParam.vqObjBaseAddr = (void*)&sysVqBuf[0]; vqParam.vqBufSize = numRemoteProcs * Ipc_getVqObjMemoryRequiredPerCore(); vqParam.vringBaseAddr = (void*)VRING_BASE_ADDRESS; vqParam.vringBufSize = IPC_VRING_BUFFER_SIZE; vqParam.timeoutCnt = 100; Ipc_initVirtIO(&vqParam);
Poll for Linux cores to be ready and pair virtio with Linux cores late
... for(i = 0; i < numRemoteProcs; i++) { Task_Params_init(¶ms); params.arg0 = (UArg)remoteProcList[i]; params.arg1 = (UArg)lateInitFlag[i]; Task_create(rpmsg_vdevMonitorFxn, ¶ms, NULL); } ... void rpmsg_vdevMonitorFxn(UArg arg0, UArg arg1) { int32_t status; /* Is this a late init core? */ if((uint32_t) arg1 == FALSE) { return; } /* Wait for remote core to be ready ... */ while(!Ipc_isRemoteReady((uint32_t) arg0)) { Task_sleep(1); } /* Create the VRing now ... */ Ipc_lateVirtioCreate((uint32_t) arg0); ...
Note
When Linux boots the RTOS cores, Linux virtio driver is brought up in advance, and option 1 above can be used.
The next step is to initialise RPMSG stack. This can be done by
RPMessageParams_init(&cntrlParam);
cntrlParam.buf = pCntrlBuf;
cntrlParam.bufSize = rpmsgDataSize;
cntrlParam.stackBuffer = &pTaskBuf[index++ * IPC_TASK_STACKSIZE];
cntrlParam.stackSize = IPC_TASK_STACKSIZE;
RPMessage_init(&cntrlParam);
Note
For the cores that required late virtio pairing, the RPMSG stack pairing should also be done in rpmsg_vdevMonitorFxn()
...
Ipc_lateVirtioCreate((uint32_t) arg0);
RPMessage_lateInit((uint32_t) arg0);
...
IPC server (listening to service requests)¶
After the setup is complete, the server needs to open an endpoint and announce the presence of a service to all other cores.
RPMessageParams_init(¶ms);
params.requestedEndpt = ENDPOINT;
params.buf = buf;
params.bufSize = bufSize;
handle = RPMessage_create(¶ms, &myEndPt);
RPMessage_announce(RPMESSAGE_ALL, myEndPt, "rpmsg_chrdev");
Note
The ENDPOINT
is chosen so that it is unique for a given core. It can be duplicated on a different core, however.
Also, you can run multiple rpmsg_chrdev
services (each having a different user-defined service function) on a given core
by assigning different endpoint numbers to each of them.
Note
The announcement is done to all the cores with which virtio pairing has been done. If a late virtio pairing is done with
any core, the services must be re-announced to them in rpmsg_vdevMonitorFxn()
...
RPMessage_lateInit((uint32_t) arg0);
RPMessage_announce((uint32_t) arg0, RecvEndPt, "rpmsg_chrdev");
...
After the service endpoint is opened, the server can run a loop which listens to incoming messages to the endpoint and calls the service function. The sevice function is responsible for sending back any response to the sender, after the sevice has been completed.
while(TRUE)
{
RPMessage_recv(handle, (Ptr)msg, &msg_len, &remoteEndPt, &remoteProcId,
IPC_RPMESSAGE_TIMEOUT_FOREVER);
...
/* do something with message */
service_func(msg, msg_len, remoteEndPt, remoteProcId, resp, &resp_len);
...
RPMessage_send(handle, remoteProcId, remoteEndPt, myEndPt, resp, resp_len);
}
IPC client (sending sevice requests)¶
The client application also needs to run a setup code similar to a server application. After the setup is complete, the client needs to find out the remote endpoint for a service on a given core
RPMessage_getRemoteEndPt(dstProc, "rpmsg_chrdev", &remoteProcId, &remoteEndPt, BIOS_WAIT_FOREVER);
Once the remote endpoint is identified, the client application can start a loop to send messages and wait for responses
while(TRUE)
{
RPMessage_send(handle, dstProc, remoteEndPt, myEndPt, (Ptr)msg, msg_len);
/* wait a for a response message: */
RPMessage_recv(handle, (Ptr)resp, &resp_len, &remoteEndPt,
&remoteProcId, IPC_RPMESSAGE_TIMEOUT_FOREVER);
Writing IPC Applications on Linux¶
In linux, the rpmsg char driver is initialised by kernel and it creates a userspace /dev
entry for each remote rpmsg_chrdev
service.
The developer can take advantage of the “ti_rpmsg_char” library to easily connect to a remote endpoint and start communicating.
rpmsg_char_dev_t *rcdev;
char dev_name = NULL; /* remote-service-name, defaults to rpmsg_chrdev internally */
int rproc_id = R5F_MAIN0_0; /* relevant remote-proc id */
int flags = 0;
rpmsg_char_init(NULL);
rcdev = rpmsg_char_open(rproc_id, dev_name, REMOTE_SERVICE_ENDPOINT,
"local-endpoint-name", 0);
After these steps, the application has an fd
that can be used for reading and writing messages
while(1) {
write(rcdev->fd, msg, msg_len);
read(rcdev->fd, resp, resp_len);
}
rpmsg_char_close(rcdev);
rpmsg_char_exit();