Microvisor Public Beta
Microvisor is in a pre-release phase and the information contained in this document is subject to change. Some features referenced below may not be fully available until Microvisor’s General Availability (GA) release.
If you are looking for the Microvisor REST API for cloud interaction, please see the Microvisor API.
Applications communicate with Microvisor through the Microvisor system calls. Interaction with Microvisor is transactional: the application calls functions to request the operations it would like Microvisor to perform on its behalf because each action involves access to ‘secure’ resources that the ‘non-secure’ application doesn’t have permission to work with directly.
Many operations that are requested via the system calls take place asynchronously. Other system calls register the application’s interest in being informed when key system events take place. Both of these types of call trigger the issue of notifications from Microvisor to the application. Microvisor notifications are the only way the application can learn about the outcome of an operation, or that a certain system event has taken place. The application is alerted to the presence of a new notification by a non-secure interrupt that it specifies when setting up notifications.
A number of other system calls are akin to traditional getters and setters: the application provides Microvisor with data it wants written to, say, a specified microcontroller register, or requests the current contents of that register. These functions are not executed asynchronously. In the case of the getters, they return the requested data by writing it to memory specified in the call.
In fact, no system call returns data directly. Instead they return a value which indicates whether Microvisor accepted the requested operation, or rejected it. Reasons for rejection include not only mis-formed action requests — pointers to memory the application is not permitted to access, for example, or attempts to open more network data channels that is allowed — but also situations in which Microvisor is unable to comply because overriding circumstances prevent it from doing so. A case in point: Microvisor may refuse a request from the application to reboot the host microcontroller because its is downloading a software update chunk.
In C, Microvisor system calls take the following form:
extern enum MvStatus mvFunctionName(...)
All function names are prefixed
All calls return a case of the enum
Only the name and list of parameters are function-specific.
The Microvisor system calls initially will provide the application with functionality in the following key areas:
- Application logging
- Device hardware tasks
- Fast interrupt operations
- Network operations
- HTTP operations
- Notification management
These functions allow applications to configure application logging and to issue log messages. Logged messages are accessed using the Microvisor Twilio CLI plugin.
Application logging is intended for coarse-level feedback and basic debugging. Posted log messages are not guaranteed to be received, and no claim is made as to the order in which they may be delivered. You should not use application logging as a data-delivery mechanism.
The device hardware calls provide access to peripheral registers that are owned by Microvisor but provide access to resources that may be used by the application. The application has unmediated access to all other peripherals, i.e., those it does not share with Microvisor.
The remainder of the hardware calls is given over to functions that provide the application with information about its host: for example, the unique ID that identifies the device in the Twilio cloud and console.
The fast interrupt calls allow developers to minimize the latency of a number of interrupts by instructing Microvisor to permit those interrupts to pre-empt secure threads. This typically reduces latency to under 20μs, but with some other important limitations.
- Move interrupts into low-latency mode, or move them out of it.
- Activate and deactivate specific low-latency interrupts.
- Activate and deactivate all low-latency interrupts at once.
Microvisor’s initial networking calls are geared towards the establishment of a network connection between the device and the Twilio cloud, and the formation and use of bi-directional data channels routed over this connection. This includes the following network operations:
- Request and relinquish a network connection.
- Open and close a data channel.
- Request to be notified when data has been received through a channel and placed in its receive buffer.
- Request to be notified when a channel’s transmit buffer has space for new data to be written to it.
- Read and write data from or to a channel via its buffers.
These tasks are asynchronous and make extensive use of Microvisor notifications. As part of the process of requesting a network connection, the application is therefore expected to provide notification storage.
As part of the process of establishing a data channel, the application is also expected to provide storage for the channel’s read and write buffers.
Microvisor supports the issue of HTTP requests and processing of the responses they generate. These calls require the establishment of suitable network connections and data channels. Then the application can:
- Issue HTTP requests.
- Determine the status of a completed request.
- Read back any of the headers included in the response.
- Read back all or part of any body data included in the response.
The notifications calls allow the application to configure how it is informed about system events. For details, see Microvisor notifications, below.
The timekeeping calls focus on providing the application with timing signals from the host microcontroller’s clocks, which are of necessity owned by Microvisor. This includes:
- Request the current value of the system’s monotonic microseconds counter.
- Request the current value of the system’s RTC.
Network and other resources mediated by Microvisor are identified in application-Microvisor interactions by ‘handles’. A handle is a 32-bit non-zero integer which is randomly generated and remains unique for the lifetime of the resource to which it has been assigned.
For example, when the application requests a network connection, Microvisor will provide it with handle that identifies the new connection. The application references the connection by its handle when it makes use of the connection — to open data channels over it, for instance — and to subsequently relinquish access to it.
To request a network connection, the application supplies a handle that identifies a notification center that has already been instantiated and which the application wants to dispatch the notifications generated by the new connection.
Before using a handle, you should always check that it is not zero. Microvisor defines an invalid handle as any handle with a value of zero. All extant handles are zeroed when the host microcontroller restarts, and specific handles are zeroed when the resource they refer to is relinquished or closed by the application. If you attempt to make use of an invalid handle, Microvisor will report it as an error.
System calls which ask Microvisor to provide the application with values take a pointer which indicates where the returning value will be written and the number of bytes the application expects to be written. It is the application’s responsibility to allocate memory for such storage and to do so in memory to which it has access: Microvisor will reject actions whose configurations require it to write results to memory that is inaccessible to the application.
System calls which ask Microvisor to set values take the value itself. For bulk data, the function takes a pointer to the data in application memory, and the number of bytes to be read.
Microvisor system calls do not return requested data or values directly — they are instead written to memory specified by the application. However, system calls do return a value which indicates whether the action requested by the function call was accepted or rejected by Microvisor.
If Microvisor accepts the request, it returns zero. Any non-zero value therefore represents an error, and the actual value indicates the cause of the error. The application should always check the return value from any call it makes. The final system calls design will enumerate each of the error values a system call may return; this enumeration will be included in the header files for ready access to error values in code.
Example errors include:
- Supplying an IRQ line that is not targeted to non-secure mode.
- Supplying an IRQ line that is already in use.
- Passing a pointer to data held in memory not accessible by the application.
- Passing a pointer to a buffer that references memory not accessible by the application.
- A specified buffer size is too small to hold the data that will be written to it.
- A requested time can not be provided because the RTC has not yet been set.
- A register operation targets an illegal or unavailable register.
Microvisor checks each supplied parameter — and the fields in supplied structures — and will return an error on the first of these that is mis-set. Applications should not assume that an error generated by a single mis-configured parameter means that all other parameters are valid: re-calling a function after correcting one parameter will return another error if a further parameter is found to be bad.