Skip to content

Commit ce0c271

Browse files
Patatermeriac
authored andcommitted
Add a section on RPC
1 parent d8ac067 commit ce0c271

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

docs/api/manual/UseCases.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,298 @@ The Privileged system IRQ hooks can be used to specify the following handlers:
439439
- RTX would register for handling SysTick (if a better periodic timer suitable for the RTX scheduler isn't available)
440440
- SVC 0
441441
- RTX would register for handling SVC 0, with which RTX handles its own syscalls
442+
443+
444+
### Remote Procedure Calls
445+
446+
uVisor provides a Remote Procedure Call (RPC) API to call functions that execute in the context of another box.
447+
448+
#### A General Overview of the RPC API
449+
450+
The RPC API provides a structured way for a caller box to perform actions in a callee box. By default, boxes can't call functions in other box's contexts. A callee box declares RPC gateways to designate functions as callable by other boxes.
451+
452+
uVisor strictly controls the information passed between boxes, verifying that the callee box is OK with being called. This verification is done via an RPC gateway. An RPC gateway is a verifiable, in-flash data structure that the callee box uses to nominate a function as a suitable RPC target.
453+
454+
If an RPC gateway exists in a callee box, then any other box can call that target function in callee box context. No other target functions can be called in a callee box other than those designated as callable via an RPC gateway.
455+
456+
##### RPC Macros
457+
458+
Two macros are provided to implement RPC gateways: `UVISOR_BOX_RPC_GATEWAY_SYNC` and `UVISOR_BOX_RPC_GATEWAY_ASYNC`.
459+
- `UVISOR_BOX_RPC_GATEWAY_SYNC` creates a callable *synchronous* RPC gateway
460+
- `UVISOR_BOX_RPC_GATEWAY_ASYNC` creates a callable *asynchronous* RPC gateway
461+
462+
RPC gateways can be created for any function that accepts up to four, 4-byte parameters and returns up to one 4-byte value.
463+
464+
Calling a synchronous RPC gateway is simple. A synchronous RPC gateway is called in the same manner that the original target function would have been called. `UVISOR_BOX_RPC_GATEWAY_SYNC` creates a gateway with an identical function signature.
465+
466+
Calling an asynchronous RPC gateway is a bit more involved, as `UVISOR_BOX_RPC_GATEWAY_ASYNC` creates a gateway with a different function signature. Compared to the original target function, the return type is changed. The return type of the gateway is a token that can be used to wait for the asynchronous call. Sometimes this token may be invalid, in cases where the asynchronous call couldn't be initiated.
467+
468+
#### Porting a library to uVisor
469+
470+
To enable uVisor for a pre-existing library:
471+
1. Make a box configuration file, `secure_libraryname.cpp`
472+
1. Configure the box
473+
1. Create secure RPC gateways to designate library functions as securely callable within the newly created box
474+
1. Write incoming RPC handlers for all RPC target functions
475+
476+
#### Creating a secure gateway for a library function
477+
478+
We'll now work through a short example, creating both a synchronous RPC gateway and an asynchronous RPC gateway for a single library function.
479+
480+
Here is the imaginary function we want to make callable through RPC.
481+
482+
```C++
483+
/* unicorn.h */
484+
typedef enum {
485+
UNICORN_BARFABLE_NOTHING = 0,
486+
UNICORN_BARFABLE_GRASS,
487+
UNICORN_BARFABLE_WEEDS,
488+
UNICORN_BARFABLE_FLOWERS,
489+
UNICORN_BARFABLE_RAINBOW,
490+
} unicorn_barfable_t;
491+
492+
void unicorn_barf(unicorn_barfable_t thing);
493+
```
494+
495+
The function causes a unicorn to barf up some barfable thing (imaginarily, of course).
496+
497+
##### Creating a synchronous RPC gateway
498+
499+
To make a synchronous RPC gateway, we use the `UVISOR_BOX_RPC_GATEWAY_SYNC` macro.
500+
501+
```C++
502+
#define UVISOR_BOX_RPC_GATEWAY_SYNC(box_name, gw_name, fn_name, fn_ret, ...)
503+
```
504+
505+
These are the parameters.
506+
* `box_name` - The name of the target box as declared in `UVISOR_BOX_CONFIG`
507+
* `gw_name` - The new, callable function pointer for performing RPC
508+
* `fn_name` - The function being designated as an RPC target
509+
* `fn_ret` - The return type of the function being designated as an RPC target
510+
* `__VA_ARGS__` - The type of each parameter passed to the target function. There can be up to 4 parameters in a target function. Each parameter must be no more than uint32_t in size. If the target function accepts no arguments, pass `void` here.
511+
512+
Here's how to designate the function as a synchronously callable RPC target.
513+
514+
```C++
515+
/* secure_unicorn.cpp */
516+
#include "unicorn.h"
517+
518+
UVISOR_BOX_RPC_GATEWAY_SYNC(unicorn_box, unicorn_barf_sync, unicorn_barf, void, unicorn_barfable_t);
519+
```
520+
521+
We also need to declare the gateway's function prototype, so that clients can call the freshly-created RPC gateway. Notice that the gateway creating macro made a function pointer and not a function, so we declare the gateway's function prototype as a function pointer. We also need to extern the function pointer, to let the compiler know that the gateway creating macro already created the function pointer for us.
522+
```C++
523+
/* secure_unicorn.h */
524+
UVISOR_EXTERN void (*unicorn_barf_sync)(unicorn_barfable_t thing);
525+
```
526+
527+
##### Creating an asynchronous RPC gateway
528+
529+
Creating the asynchronous RPC gateway is just about as easy as creating an synchronous gateway.
530+
531+
To make an asynchronous RPC gateway, we use the `UVISOR_BOX_RPC_GATEWAY_ASYNC` macro.
532+
533+
```C++
534+
#define UVISOR_BOX_RPC_GATEWAY_ASYNC(box_name, gw_name, fn_name, fn_ret, ...)
535+
```
536+
537+
The parameters are the same as the `UVISOR_BOX_RPC_GATEWAY_SYNC` macro.
538+
* `box_name` - The name of the target box as declared in `UVISOR_BOX_CONFIG`
539+
* `gw_name` - The new, callable function pointer for performing RPC
540+
* `fn_name` - The function being designated as an RPC target
541+
* `fn_ret` - The return type of the function being designated as an RPC target
542+
* `__VA_ARGS__` - The type of each parameter passed to the target function. There can be up to 4 parameters in a target function. Each parameter must be no more than uint32_t in size. If the target function accepts no arguments, pass `void` here.
543+
544+
Here's how to make the function an asynchronously callable RPC target.
545+
546+
```C++
547+
/* secure_unicorn.cpp */
548+
#include "unicorn.h"
549+
550+
/* Both of these declarations are independent; one is not necessary for the
551+
* other. Both declarations are listed here for illustrative purposes only. */
552+
UVISOR_BOX_RPC_GATEWAY_SYNC(unicorn_box, unicorn_barf_sync, unicorn_barf, void, unicorn_barfable_t);
553+
UVISOR_BOX_RPC_GATEWAY_ASYNC(unicorn_box, unicorn_barf_async, unicorn_barf, void, unicorn_barfable_t);
554+
```
555+
556+
Just like in the synchronous case, we again declare the the gateway's function prototype. This time, however, notice that the return value is of type `uvisor_rpc_result_t`. The return value is a token that facilitates the asynchronous calling of our gateway.
557+
```C++
558+
/* secure_unicorn.h */
559+
UVISOR_EXTERN void (*unicorn_barf_sync)(unicorn_barfable_t thing);
560+
UVISOR_EXTERN uvisor_result_t (*unicorn_barf_async)(unicorn_barfable_t thing);
561+
```
562+
563+
That's all there is to it. That's all it takes to create an RPC gateway to a target function.
564+
565+
#### Handling incoming RPC
566+
567+
Each box has a single queue for handling incoming RPC calls. uVisor will verify the secure RPC gateways and then place calls into the target box's queue; an RPC call won't be added to the queue if the gateway isn't valid.
568+
569+
Making a box capable of handling incoming RPC requires two steps.
570+
1. Specify the maximum number of incoming RPC calls for the box
571+
1. Call `rpc_fncall_waitfor` from at least one thread
572+
573+
##### Limiting the maximum number of incoming RPC calls
574+
575+
To specify the maximum number of incoming RPC calls for a box, the following macro is used.
576+
577+
```C++
578+
UVISOR_BOX_RPC_MAX_INCOMING(max_num_incoming_rpc)
579+
```
580+
581+
Before the box configuration, use the `UVISOR_BOX_RPC_MAX_INCOMING` macro to specify how many RPC calls can be queued up at once.
582+
```C++
583+
/* secure_unicorn.cpp */
584+
585+
UVISOR_BOX_RPC_MAX_INCOMING(10);
586+
```
587+
With the above configuration, up to 10 RPC calls can be queued up for all RPC executors. If the executors can't execute incoming RPC calls fast enough, uVisor will prevent new RPC calls from getting queued up until space allows.
588+
589+
So, what happens on the caller side when a callee can't handle their call? Asynchronous callers will receive a timeout if the call can't be completed quickly enough. Synchronous callers will block forever until space is available.
590+
591+
##### Executing RPC calls
592+
593+
An RPC needs some context in which to execute. The context in which an RPC runs is designated by a call to `rpc_fncall_waitfor`. This function handles RPC calls, and then performs the RPC within its context. `rpc_fncall_waitfor` will either execute one RPC before returning or return with a status code indicating that something else happened.
594+
595+
Let's have a look at this function.
596+
597+
```C++
598+
int rpc_fncall_waitfor(const TFN_Ptr fn_ptr_array[], size_t fn_count, uint32_t timeout_ms);
599+
```
600+
601+
There are not so many parameters.
602+
* `fn_ptr_array` - an array of RPC function targets that this call to `rpc_fncall_waitfor` should handle RPC to
603+
* `fn_count` - the number of function targets in this array
604+
* `timeout_ms` - specifies how long to wait (in ms) for an incoming RPC calls before returning
605+
606+
And finally, the return value specifies the status of the wait (whether it timed out, or if the pool is too small, or if an RPC was handled).
607+
608+
Let's see what this would look like in practice for the unicorn library. Let's implement the body of a new thread to run in `unicorn_box` that will be used to handle RPC to the `unicorn_barf` target function. We'll have it wait forever for an incoming RPC call, handle it, and then wait for the next item.
609+
610+
```C++
611+
static void unicorn_barf_rpc_thread(const void *)
612+
{
613+
/* The list of functions we are interested in handling RPC requests for */
614+
const TFN_Ptr my_fn_array[] = {
615+
(TFN_Ptr) unicorn_barf, // Note use of `unicorn_barf`, not `unicorn_barf_async`
616+
};
617+
618+
while (1) {
619+
int status;
620+
static const uint32_t timeout_ms = UVISOR_WAIT_FOREVER;
621+
622+
status = rpc_fncall_waitfor(my_fn_array, ARRAY_COUNT(my_fn_array), timeout_ms);
623+
if (!status) {
624+
/* ... Handle unsuccessful status ... */
625+
}
626+
}
627+
}
628+
```
629+
630+
If we wanted to handle incoming RPC calls for additional RPC targets in this same thread, we could add them to `my_fn_array`. Also, if we so desired, we could create multiple threads to wait for the same RPC targets, as might be useful in a multi-core system to handle incoming RPC calls in parallel.
631+
632+
So, that about sums it up for library authors. Get out there and uVisor-enable your libraries.
633+
634+
Next up is a description of how to call these gateways.
635+
636+
#### Calling a secure gateway
637+
638+
Continuing with our previous example, we'll now work through how to call both a synchronous RPC gateway and an asynchronous RPC gateway.
639+
640+
##### Calling a synchronous RPC gateway
641+
642+
As a convenient reminder, this is the function prototype of the synchronous RPC gateway we created in the previous section.
643+
```C++
644+
/* secure_unicorn.h */
645+
UVISOR_EXTERN void (*unicorn_barf_sync)(unicorn_barfable_t thing);
646+
```
647+
648+
Calling this synchronous RPC gateway is really easy. The call looks exactly like a non-RPC to the target function. Ready?
649+
650+
```C++
651+
/* example.cpp */
652+
/* ... */
653+
void example_sync(void)
654+
{
655+
unicorn_barf_sync(UNICORN_BARFABLE_RAINBOW);
656+
}
657+
```
658+
659+
Yup, that's it. That's all there is. The call will block until the target function executes and returns. If you want to only wait for a certain amount of time for the target function to return, then you'll want to use the asynchronous RPC gateway (which we'll conveniently cover right now.)
660+
661+
##### Calling an asynchronous RPC gateway
662+
663+
As another convenient reminder, this is the function prototype of the asynchronous RPC gateway we created in the previous section.
664+
```C++
665+
/* secure_unicorn.h */
666+
UVISOR_EXTERN uvisor_result_t (*unicorn_barf_async)(unicorn_barfable_t thing);
667+
```
668+
669+
Now, to make the call. This isn't so different from the synchronous case, but we don't yet get the return value of the target function by calling an asynchronous RPC gateway. We instead get a `uvisor_rpc_result_t` token.
670+
671+
```C++
672+
/* example.cpp */
673+
/* ... */
674+
void example(void)
675+
{
676+
uvisor_rpc_result_t result;
677+
678+
result = unicorn_barf_async(UNICORN_BARFABLE_RAINBOW);
679+
if (result == UVISOR_INVALID_RESULT) {
680+
/* The asynchronous call failed. */
681+
}
682+
683+
/* ... Do anything asynchronously here ... */
684+
685+
/* ... */
686+
```
687+
688+
Now the asynchronous call is initiated and we are free to do stuff asynchronously. Eventually, we'll want to wait for the call to complete. Before we get to actually waiting for the call to complete, let's introduce the function that will do the waiting for us.
689+
690+
```C++
691+
int rpc_fncall_wait(uvisor_result_t result, uint32_t timeout_ms, uint32_t * ret);
692+
```
693+
694+
To use `rpc_fncall_wait`, pass in:
695+
* `result` - the result token previously received from an asynchronous call
696+
* `timeout_ms` - a timeout in milliseconds of how long to wait for a result to come back from the RPC target function
697+
* `ret` - a pointer to a `uint32_t`-sized return value
698+
699+
In our case, `unicorn_barf` has a void return value, so we can pass in `NULL` for `ret`.
700+
701+
Now that we understand how to use `rpc_fncall_wait`, let's spin in a loop, waiting for the result to come back for up to 500 ms. If we don't get a result by then, we can consider the unicorn to have ran out of barf. Any unicorn worth their salt should be able to barf within 500 ms.
702+
703+
```C++
704+
/* example.cpp */
705+
/* ... */
706+
void example(void)
707+
{
708+
uvisor_rpc_result_t result;
709+
710+
result = unicorn_barf_async(UNICORN_BARFABLE_RAINBOW);
711+
if (result == UVISOR_INVALID_RESULT) {
712+
/* The asynchronous call failed. */
713+
}
714+
715+
/* ... Do anything asynchronously here ... */
716+
717+
/* Wait for a non-error result synchronously.
718+
* Note that this wait could potentially be from a different thread. */
719+
while (1) {
720+
int status;
721+
static const uint32_t timeout_ms = 500;
722+
723+
status = rpc_fncall_wait(&result, timeout_ms, NULL);
724+
if (!status) {
725+
break;
726+
}
727+
}
728+
}
729+
```
730+
731+
Calling the asynchronous RPC gateway is as tough as it gets, and it isn't really that bad, is it?
732+
733+
That about wraps it up for the RPC API. We've covered both how to uVisor-enable a library and how to use a uVisor-enabled library.
734+
735+
#### More Information on the RPC API
736+
For more information on the RPC API, please refer to [the well-commented RPC API C header file which is available at https://github.com/ARMmbed/uvisor/blob/master/api/inc/rpc.h](https://github.com/ARMmbed/uvisor/blob/master/api/inc/rpc.h).

0 commit comments

Comments
 (0)