Menu

Expand
Rate this page:

Microvisor System Calls

Microvisor Private Beta

Microvisor is in a pre-release phase and the information contained in this document is therefore provisional and subject to change. Some features referenced below may not yet be available in or supported by the current version of the Microvisor system calls or REST API.

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.

The Microvisor system calls

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.

System call design

In C, Microvisor system calls take the following form:

extern enum MvStatus mvFunctionName(...)

All function names are prefixed mv.

All calls return a case of the enum MvStatus.

Only the name and list of parameters are function-specific.

System call coverage

The Microvisor system calls initially will provide the application with functionality in the following key areas:

Device hardware tasks

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.

View device functions

Fast interrupt operations

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.

View fast interrupt functions

Networking operations

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.

View network functions

HTTP operations

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.

View HTTP functions

Notification management

The notifications calls allow the application to configure how it is informed about system events. For details, see Microvisor notifications, below.

View notification functions

Timekeeping

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.

View time functions

Handles

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.

Getters and setters

Getting values via system calls

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.

Setting values via system calls

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.

Errors and return values

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.

Microvisor notifications

Microvisor provides a notification mechanism which the application can use to be informed about the outcome of operations and system events. Microvisor issues two broad classes of notification — action completion notifications and status notifications — but both work in the same way: they are dispatched to the application by one or more notification centers.

If Microvisor is not able to attempt a requested action — the request was malformed, for example, or was configured to use memory to which the application is not permitted to access — the system call will immediately return an error code, and no notification will be issued. Notifications are only issued when Microvisor can perform a requested action. The outcome final outcome of that action may be success or failure, of course.

Action completion notifications

Many Microvisor system calls trigger asynchronous operations: the actions requested by the application take a finite but unknown length of time to complete. For this reason, the Microvisor notification system is used to inform the application when the requested operation has been completed. The notification will include the outcome of the action: whether it succeeded or failed.

Status notifications

Notifications are also used to inform the application when certain key system events take place. The application must register its interest in these events and will then receive notifications if and when they take place. Unlike action completion notifications, status notifications do not come in response to a specific operation requested by the application.

How notifications work

Microvisor dispatches notifications through one or more notification centers. You can establish as many centers as you need: you are limited only by the amount of non-secure memory available to hold the centers’ notification-storage buffers. Buffers are sized and allocated for this purpose by the application, and referenced as a pointer during center setup. Each notification center is identified by its own handle.

The memory allocated to receive notifications comprises contiguous bytes but they used as backing for a circular buffer. Each notification center maintains a write pointer to the next entry in its buffer, and when this pointer reaches past the final allocated byte, it is automatically moved back to the start of the buffer, as shown below:

Microvisor's notification buffer

With a buffer capable of holding n notifications, Microvisor at some point in the application lifecycle writes a sequence of four 16-byte notifications (red squares 1—4). Because of the circularity of the buffer, notifications 3 and 4 are written at the start of the buffer. The write pointer is set to record the next record (first grey square)

After it has written a notification, the notification center raises an interrupt to signal to the application that a new notification has been posted. When it asks Microvisor to create a notification center, the application provides a non-secure interrupt line’s IRQ number. The application may process notifications in its interrupt service routine, or it can choose to defer notification processing to its main run loop by setting a flag or some other record.

Notification center setup

To begin receiving notifications, your code first creates a notification center. It calls mvSetupNotifications() and passes a reference to a notification setup structure which contains the information the notification center will need to dispatch notifications to the application:

struct MvNotificationSetup {
  uint32_t irq;
  struct MvNotification *buffer;
  uint32_t buffer_size;
}

The application can create as many notification centers as it requires, but each one must specify a unique, valid IRQ.

In addition to a NotificationSetup structure, your code also passes in a pointer to four bytes of memory into which Microvisor will write the new notification center’s unique handle. System calls that respond with notifications require you to supply the handle of the notification center that you would like to dispatch their notifications.

The interrupt

The value of irq in the NotificationSetup structure is the number of the non-secure interrupt line that will be triggered to signal that a new notification has been posted. The call to mvSetupNotifications() will return an error if this value indicates a secure interrupt, or has already been assigned to a notification center.

The buffer

To receive any kind of notification, the application must allocate memory into which the notification center will write the notifications it posts. All notifications are 16 bytes in size, and the application is expected to allocate space for at least two of them. The first byte of the notification buffer must also be aligned to an eight-byte boundary. The buffer must be valid for the lifetime of the notification center that uses it.

Microvisor imposes no other restrictions on a notification buffer. The application may use one buffer or many: each center targets one buffer, but that buffer can be shared among notification centers. Many applications will be able to make do with a single notification center targeting one buffer. However many buffers your application allocates, each one can be any size from 32 bytes upwards — though any notification buffer’s size must be a multiple of 16 bytes, of course.

Reading notifications

The application should maintain per buffer read pointers that references where the next notification to be read from a given buffer is located. Microvisor doesn’t provide read pointers; this is the responsibility of the application. Because the application may choose to defer reading notifications, Microvisor provides a protocol that allows the application to detect when it has reached the end of a sequence of notifications. Each notification has a four-byte event_type field, and Microvisor zeros this field in the space after the notification it has just written. When the application reads a notification of type zero, it knows it has now read all currently available notifications.

Buffer overruns

If the application doesn’t read a notification in its interrupt service routine, or the interrupt is ignored — i.e., it is masked, has too low a priority, or has been disabled — there is a risk that the notification center’s write pointer will overtakes the application’s read pointer: a buffer overrun. The application can detect this, if it wishes, by zeroing the event_type of notifications as they are read, and on each interrupt checking that the entry prior to the read pointer has been zeroed.

One write pointer per buffer

Microvisor maintains a single write pointer for each unique buffer reference supplied by the application during notification center setup. When it is passed a buffer pointer, it checks if that buffer is already in use. If it is, Microvisor uses the pre-existing write pointer to ensure that notifications from action A don’t overwrite those from action B.

So if the application has multiple notification centers sharing a single buffer, and therefore provides the same pointer and buffer size through multiple notification configurations, Microvisor will maintain a single write pointer to that buffer.

Notifications

Each notification has the following 16-byte structure:

struct MvNotification { 
  uint64_t microseconds;
  enum MvEventType event_type;
  uint32_t tag; 
}

The value of microseconds is the value of the system’s monotonic microsecond counter when the notification was written. Use this to measure and assess the duration of key operations.

The event_type field indicates what event or action caused the notification to be issued, useful especially if your application uses a single buffer for a range of event types. Your code can check if this value is zero when it is iterating over a number of notifications: an event_type of zero indicates it has now read all the currently available notifications, and its read pointer is placed ready for the next notification to be posted. It can also be used to detect buffer overruns. The available event types are listed under each system call that initiates notifications.

The value of tag will match the tag set during the call that triggered the dispatch of the notification.

Tags

To help applications keep track of which notification was the result of what specific action, an application can provide a tag whenever it calls an System function which will result in notifications being dispatched

A tag is an arbitrary 32-bit value, which can be a pointer, a reference to an object, an index in a table, or whatever the application chooses.

The tag is set when the application requests an operation that may issue notifications. Microvisor makes no use of tags itself: it stores them at registration, and the center includes it in any relevant notifications it dispatches subsequently.

Tags are tied to operations or events, so even if a notification center is dispatching notifications for a variety of operations, notifications will always include the correct tag.

Rate this page:

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd by visiting Twilio's Stack Overflow Collective or browsing the Twilio tag on Stack Overflow.

Thank you for your feedback!

Please select the reason(s) for your feedback. The additional information you provide helps us improve our documentation:

Sending your feedback...
🎉 Thank you for your feedback!
Something went wrong. Please try again.

Thanks for your feedback!

Refer us and get $10 in 3 simple steps!

Step 1

Get link

Get a free personal referral link here

Step 2

Give $10

Your user signs up and upgrade using link

Step 3

Get $10

1,250 free SMSes
OR 1,000 free voice mins
OR 12,000 chats
OR more