You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/api/manual/UseCases.md
+295Lines changed: 295 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -439,3 +439,298 @@ The Privileged system IRQ hooks can be used to specify the following handlers:
439
439
- RTX would register for handling SysTick (if a better periodic timer suitable for the RTX scheduler isn't available)
440
440
- SVC 0
441
441
- 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.
* `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.
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.
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. */
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.
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.
* `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`
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.
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.
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.
* `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