nRF5 SDK v17.1.0
Supervisor call interface

The Supervisor call interface is a thread-safe method to call into the current application or into an external application using a Supervisor instruction. The Supervisor interface is split between the caller that uses a Supervisor instruction and the handler that implements a Supervisor exception handler to process the request. Both the caller and the handler must agree on the shared data passed between them through the Supervisor call.

The shorthand for a Supervisor call is SVC.

Supervisor calls

Supervisor calls are calls using the Thumb Supervisor instruction. Calling this instruction from code raises and exception on the device and branches into the SVC vector. During the exception, the following registers are stacked in the exception frame: R0-R3, R12, R14, LR, PC and xPSP.

The Supervisor exception passes control to the Supervisor handler inside the Master Boot Record, which will forward the Supervisor call to the SVC vector that is configured in the Master Boot Record. These exceptions are forwarded to the main application by default, with the exception of SVC call numbers that are reserved for use in the SoftDevice, which will always be handled inside the SoftDevice SVC vector (see Reserved SVC numbers).

When an SVC instruction is called, an exception is raised and the SVC exception handler is executed immediately, unless an exception with the same or higher priority is currently being handled.

SVC instructions will give access to an external functionality without knowing absolute addresses for the functions that are being reached. Supervisor calls are normally limited to functions with 0 to 4 arguments, as R0 - R3 are the only parameters that are stacked in the Supervisor exception frame.

There is no standardized way of calling the SVC instruction in C code, so the implementation depends on the compiler in use.

Indirect Supervisor calls

Indirect Supervisor calls is an extension to the Supervisor call interface, where the stacked R12 register is reserved to provide extra information about the Supervisor call. When R12 is used, it is possible to extend the number of call types beyond the limited range of the SVC number (being 8-bit in size). When indirect Supervisor calls are used, the SVC number is zero by default.

The shorthand for indirect Supervisor calls is SVCI.

Limitations

It is not possible to execute nested SVC instructions, i.e. executing an SVC instruction from inside of an SVC handler function. This is due to the fact that a new SVC instruction raises an exception which will be blocked because it will have the same priority as the ongoing operation. This means that it is impossible for an SVC handler to directly call any SoftDevice APIs.

This limitation can be circumvented by implementing a scheme of using Supervisor calls that return function pointers for the functions that require using the Supervisor call functionality. The main application can then call these functions directly to prevent using a nested Supervisor call.

Warning
When the SVC handler is implemented in an external application, the handler function and any functions called by it cannot use static memory unless the external application is compiled to use dedicated and reserved memory that is not in use by the main application.
When the SVC handler is implemented in an external application, Logger module cannot be used by the handler function or any functions called by it.
SVC instructions cannot be used in a Reset, NMI, or Hard Fault handler as it can cause a lockup due to the fact that the SVC priority is always lower than these handlers' priority and therefore it is automatically blocked.
SVC instructions can only be used in a lower interrupt priority level (higher priority value for the priority level) than the SVC priority. Using the SVC instruction from a context that has the same or higher interrupt priority causes a Hard Fault or a locked state. The interupt priority level for the SVC handler is 4 if there is a Master Boot Record present on the device.

See Interrupt model and processor availability for more information about the exception and interrupt model in case the device uses a SoftDevice and/or Master Boot Record.

Redirecting Supervisor calls to an external application

The Master Boot Record will forward the SVC exception to the main application by default. It is possible to control where the SVC exceptions are forwarded by setting the vector table base address through the SoftDevice APIs. This can be done by calling sd_softdevice_vector_table_base_set. The same functionality is also available in an alternative API in sd_mbr_command.

Warning
When vector table base address is changed, all SoftDevice events will be forwarded to the new address. Due care must be taken to prevent loss of critical events from the SoftDevice when changing the vector table base address.

Calling an external application by changing the vector table base address

// Set the Vector table base address to EXTERNAL_APP_START_ADDR to forward SVC exceptions
// to EXTERNAL_APP_START_ADDR instead of main application.
err_code = sd_softdevice_vector_table_base_set(EXTERNAL_APP_START_ADDR);
VERIFY_SUCCESS(err_code);
// Your Supervisor call
err_code = some_svc_call(param_0, param_1);
VERIFY_SUCCESS(err_code);
// Set the vector table base address back to main application.
err_code = sd_softdevice_vector_table_base_set(APP_START_ADDR);
VERIFY_SUCCESS(err_code);
Note
Setting a new vector table base address is only possible from the main application as the API call to do so is a Supervisor call (See Limitations).

Reserved SVC numbers

The following is a table of reserved SVC numbers:

Number Owner Description
0x00 No owner Reserved for indirect Supervisor calls.
0x01 - 0x0F No owner Available range of Supervisor calls.
0x10 - 0xFF SoftDevice Reserved numbers for Supervisor calls to the Nordic Semiconductor BLE stack.
Note
All Supervisor calls with SVC numbers that are not reserved will be forwarded to the main application SVC vector or to a redirected address (see Redirecting Supervisor calls to an external application).
Warning
The reserved ranges for the SoftDevice will always be forwarded to the SoftDevice SVC vector, regardless of user configuration. These should never be used outside of the context of the SoftDevice APIs.

Reserved SVC numbers in the Secure DFU Bootloader

The following is a table of reserved SVC numbers when calling into the Secure DFU bootloader:

Number Owner Description
0x00 Bootloader Reserved for indirect Supervisor calls.
0x01 - 0x0F No owner Not in use.
0x10 -0xFF SoftDevice Reserved number for SoftDevice Supervisor calls (Will never reach the bootloader).
Note
Vector table base address must be set to the bootloader start address for Supervisor calls to be redirected to the bootloader (see Redirecting Supervisor calls to an external application).
Warning
The reserved ranges for the SoftDevice will always be forwarded to the SoftDevice SVC vector, regardless of user configuration. These should never be used outside of the context of the SoftDevice APIs.

Supervisor call interface

The Supervisor call interface can be implemented in the main or external app by using the macros SVCALL or SVCI to declare a function that will use an SVC instruction instead of a direct function call.

Declaring an SVC function

// Reserve an SVC number for the API.
#define SVC_NUM (3)
// This declares an SVC function with the following signature:
// uint32_t svc_func(int a, char b);
SVCALL(SVC_NUM, uint32_t, svc_func, int a, char b);
// Calling this function from code
uint32_t ret_val;
ret_val = svc_func(32, 2);

Declaring an indirect SVC function

// Reserve an indirect SVC number for the API.
#define SVCI_NUM (0x3000)
// This declares an SVC function with the following signature:
// uint32_t svc_func(int a, char b);
SVCI(SVCI_NUM, uint32_t, svc_func, int a, char b);
// Calling this function from code
uint32_t ret_val;
ret_val = svc_func(32, 2);

Supervisor call handler interface

The Supervisor call handler interface can be implemented in the main or external application by adding the file nrf_svc_handler.c to the project and compiling it. This will allow for handling of the SVC exception in your application.

To handle SVC instructions, you must perform registration using Experimental: Section variables. This can be achieved by using the convenience macros NRF_SVC_FUNCTION_REGISTER or NRF_SVCI_FUNCTION_REGISTER. The macro NRF_SVCI_FUNCTION_REGISTER will use SVC number 0 and and an indirect number stored in R12.

Registering an SVC function in the SVC handler

Example registation of an SVC function in the SVC handler:

// In header file:
// Example function for Supervisor call.
typedef uint32_t (*svc_func_t)(int a, char b);
#define SVC_NUMBER (3)
// In source file:
//
// svc_func_inst is the name of the nrf_section variable in code. It must be unique
// in the code unit, but it will not be accessed directly.
NRF_SVC_FUNCTION_REGISTER(SVC_NUMBER, svc_func_inst, svc_func_t);

Example registration

// In header file:
//
// Example function for an indirect Supervisor call.
typedef uint32_t (*svc_func_t)(int a, char b);
#define SVCI_NUMBER (0x4000)
// In source file:
//
// svc_func_inst is the name of the nrf_section variable in code. It must be unique
// in the code unit, but it will not be accessed directly.
NRF_SVCI_FUNCTION_REGISTER(SVCI_NUMBER, svc_func_inst, svc_func_t);
Note
Both the SVC caller and handler must know the signature of the function and the corresponding SVC or SVCI number. This can be done by declaring them in a shared header file.
Warning
The macros to register a Supervisor caller or an indirect Supervisor caller must be invoked from a source file (and not a header). This is a limitation in Experimental: Section variables. Invoking NRF_SVC_FUNCTION_REGISTER or NRF_SVCI_FUNCTION_REGISTER from a header file will lead to compiler errors.

Asynchronous Supervisor interface

The asynchronous Supervisor interface is meant to facilitate asynchronous calls into an external application that require memory management and/or rely on asynchronous operations to complete. The memory for the exchanged parameter and the ongoing state is held in the main application. System events required to complete the asynchronous operation must be forwarded through the interface.

Note
By default, the asynchronous Supervisor interface uses an indirect Supervisor call when initializing the interface. After successful initialization, all calls through the interface happen by directly calling function pointers. This is done to prevent using nested Supervisor calls (see Limitations).

Limitations

The asynchronous Supervisor interface is limited to forwarding one parameter of data, which can be a structure type when multiple parameters are required for the call. The lifetime of the data and the state must be kept for the entirety of the asynchronous operation.

The asynchronous Supervisor interface is limited to forwarding system events in case of ongoing progress information. If custom event types are required for progressing the operation, add them to the parameter type for the SVCI interface, and call the request mechanism multiple times to progress the operation.

The asynchronous Supervisor interface does not come with predefined ways of handling multiple instances. If multiple instances are required for the asynchronous Supervisor interface calls, instance information must be added to the parameter exchanged and the state type.

For other limitations, see Limitations.

Shared data

The caller and handler must know the parameter type and the type to hold the state through a shared header file.

Warning
The Asynchronous SVC handler has no method of detecting changes in either the parameter type or state type. If the parameter or state type change, use a different indirect Supervisor call number to keep API compatibility.

Asynchronous Supervisor interface caller

Registering a caller requires a declaration of the shared data in accordance with Shared data.

To register the interface for a caller, run the macro NRF_SVCI_ASYNC_FUNC_DEFINE inside a source file.

The parameters for this macro are as follows:

Name Description
svci_num Supervisor indirect number (SVC number will be zero).
name Name of the interface. This is used as a base for all convenience functions.
param_type User-defined parameter type to exchange through the asynchronous Supervisor interface.

Registration of a caller

Example of asynchronous Supervisor caller registration:

#define SVCI_NUM (5)
// This macro invocation created an asynchronous Supervisor caller interface.
// The SVCI_NUM, function name, and parameter are used as input
NRF_SVCI_ASYNC_FUNC_DEFINE(SVCI_NUM, my_func, my_func_data_t);

Asynchronous Supervisor interface handler

Registering a handler requires a declaration of the shared data in accordance with Shared data.

To register the interface for a handler, run the macro NRF_SVCI_ASYNC_HANDLER_CREATE and implement the required handler functions.

The parameters for this macro are as follows:

Name Description
svci_num Supervisor indirect number (SVC number will be zero).
name Name of the interface. This is used as a base for all required handler functions.
param_type Type of parameter to exchange through the interface.
state_type Type of the ongoing progress state when calling the interface.

Registration of a handler

Example implementation of asynchronous Supervisor interface handler functions, given that the function interface is my_func, the parameter is my_func_data_t and the state is my_func_state_t:

#define SVCI_NUM (0x1000)
NRF_SVCI_ASYNC_HANDLER_CREATE(SVCI_NUM, my_func, my_func_data_t, my_func_state_t);
// The handler function for the Supervisor call.
// This call initializes the async interface and resets the state.
static uint32_t my_func_handler(my_func_data_svci_async_t * p_async)
{
p_async->async_func = my_func_on_call;
p_async->sys_evt_handler = my_func_on_sys_evt;
p_async->state = SOME_USER_DEFINED_INITIALIZED_STATE;
return NRF_SUCCESS;
}
// Function called when the user calls through the asynchronous Supervisor interface.
static uint32_t my_func_on_call(my_func_data_t * p_data, my_func_state_t * p_state)
{
// Return value can be customized to user-specific needs
uint32_t ret_val = NRF_ERROR_BUSY;
// State can be customized to user specific needs
(*p_state) = SOME_USER_DEFINED_ONGOING_STATE;
// Call some asynchronous functionality in the handler
ret_val = some_async_call(p_data);
return ret_val;
}
// Function called when sys events are forwarded to the asynchronous Supervisor interface.
static uint32_t my_func_on_sys_evt(uint32_t sys_evt, my_func_state_t * p_state)
{
uint32_t ret_val = NRF_ERROR_BUSY;
if (*p_state == SOME_USER_DEFINED_ONGOING_STATE)
{
// Handle the sys-event.
}
return ret_val;
}

Convenience functions

There are some static convenience functions that get created when running NRF_SVCI_ASYNC_FUNC_DEFINE. These can be used when calling the asynchronous Supervisor interface from the main application. Given that the defined function is named my_func, then the available convenience functions are as follows:

Name Parameter Description
my_func_init N/A Function to initialize the asynchronous interface. Intended to be run once.
my_fync User defined Function to request asynchronous operation. A user-defined type is exchanged in the call.
my_func_on_sys_evt System event Function to forward system events required for completing the asynchronous operation.
my_func_is_initialized N/A Function to check if the asynchronous interface is initialized.

Convenience functions example

// There must be a declaration in a header file for the
// asynchronous Supervisor interface.
//
// The example declaration contains the following:
// my_func - The function to be called
// my_func_data_t - The type of data to be exchanged
// my_func_state_t - The state to be held between calls.
// NRF_SVCI_ASYNC_FUNC_DECLARE(SVCI_NUM, my_func, my_func_data_t, my_func_state_t);
//
// There must be a definition in a source file where the convenience
// function is to be called.
//
// The example registration for these convenience functions is as follows:
// NRF_SVCI_ASYNC_FUNC_DEFINE(SVCI_NUM, my_func, my_func_state_t);
uint32_t err_code;
my_func_data_t my_data;
my_func_state_t my_state;
// Convenience function to initialize the interface
err_code = my_func_init();
VERIFY_SUCCESS(err_code);
// Convenience function to check if the interface is already initialized.
if (my_func_is_initialized())
{
//Interface was initialized.
}
// Convenience function to call the interface
err_code = my_func(my_data);
VERIFY_SUCCESS(err_code);
// Convenience function to forward system events
err_code = my_func_on_sys_evt(sys_evt);
if (err_code == NRF_ERROR_BUSY)
{
// Operation is still ongoing
}
else if (err_code == NRF_SUCCESS)
{
// Operation finished successfully
}
else
{
// Any other error reported by the async interface
}

Documentation feedback | Developer Zone | Subscribe | Updated