Skip to content

Latest commit

 

History

History
738 lines (574 loc) · 34.3 KB

PORTING.md

File metadata and controls

738 lines (574 loc) · 34.3 KB

uVisor porting guide for mbed OS

This guide will help you port uVisor to your platform and to integrate it in mbed OS and CMSIS RTOS. For porting uVisor to other operating systems, please contact us.

Overview

This guide assumes that you are porting a whole device family, called ${family}, to uVisor. We strongly suggest you abstract and generalize your port as much as possible. Because a static library is generated for each of your family configurations, a more generalized uVisor port results in the generation — and maintenance — of fewer release libraries. You will also enjoy the benefit of going through the porting process only once for a large set of devices.

You will need:

  • The Launchpad GNU ARM Embedded Toolchain.
  • GNU Make.
  • Git.

Repository structure

Go to top

The uVisor is developed in the ARMmbed/uvisor repository. Most of your porting efforts will happen there.

Although uVisor is highly self-contained, it still requires some support from the target operating system. There are three files in particular that you need to change in your codebase:

  • Library glue layer. This glue-layer module compiles the uVisor in the form of a static library that contains the implementation of the uVisor APIs and the uVisor core, which is a prelinked binary component. You can also build both release and debug libraries for all family configurations by cloning the open source codebase at the ARMmbed/uvisor Makefile. The logic to generate the libraries, publish them and choose the correct library for the target at build time is OS-specific. A glue layer must provide this logic.
  • Linker script. It contains specific memory regions and symbols that uVisor relies on.
  • Startup script. uVisor boots right after the system basic initialization and before the C/C++ library initialization. The startup script needs to call uvisor_init() in between those two.

The library glue layer is already embedded in the mbed OS codebase. The linker script and startup code are also in that repository. This guide will show you how to modify those files later.

The example application in ARMmbed/mbed-os-example-uvisor shows the minimum set of uVisor features on the supported targets. You can use it during the porting process as a quick way to test that the uVisor is working on the new platform.

Porting steps

Go to top

We assume that you are developing in ~/code/. Clone the uVisor GitHub repository locally:

$ cd ~/code
$ git clone [email protected]:ARMMbed/uvisor.git

The uVisor configurations

Go to top

A single family of microcontrollers might trigger different releases of uVisor. Although uVisor is as hardware-agnostic as possible, there are still some hardware-specific features that you need to know. This table describes these features.

Symbol Description
FLASH_ORIGIN Origin of the physical flash memory where uVisor code is placed
FLASH_OFFSET Offset in flash at which uVisor is located
SRAM_ORIGIN Origin of the physical SRAM memory where uVisor .bss and stacks are placed
SRAM_OFFSET Offset in SRAM at which uVisor .bss and stacks are located
FLASH_LENGTH_MIN min( [FLASH_LENGTH(i) for i in family's devices] )
SRAM_LENGTH_MIN min( [SRAM_LENGTH(i) for i in family's devices] )
NVIC_VECTORS max( [NVIC_VECTORS(i) for i in family's devices] )
CORE_* Core version (for example, CORE_CORTEX_M3)

Table 1. Hardware-specific features that differentiate uVisor builds

Note: "SRAM" is the read/write-able memory that uVisor uses to put its own protected assets. On some platforms, this may be a different memory from the physical SRAM, like a tightly-coupled memory (TCM).

A uVisor configuration is the unique combination of the parameters of Table 1. When porting your family to uVisor, make sure that you generate as many library releases as the possible configurations.


Example

Assume for simplicity that the ${family} that you want to port consists of four devices. These have the following values from Table 1:

Symbol ${device0} ${device1} ${device2} ${device3}
FLASH_ORIGIN 0x0 0x0 0x0 0x0
FLASH_OFFSET 0x400 0x400 0x400 0x400
SRAM_ORIGIN 0x20000000 0x20000000 0x1FFF0000 0x1FFF0000
SRAM_OFFSET 0x400 0x400 0x400 0x400
FLASH_LENGTH(i) 0x100000 0x100000 0x80000 0x80000
SRAM_LENGTH(i) 0x10000 0x20000 0x10000 0x20000
NVIC_VECTORS(i) 86 122 86 122
CORE_ CORTEX_M4 CORTEX_M4 CORTEX_M3 CORTEX_M3

Table 2. Example uVisor configuration values

Following the descriptions of Table 1, some values are common among the four devices:

  • NVIC_VECTORS is the maximum NVIC_VECTORS(i), hence it is 122.
  • FLASH_LENGTH_MIN is 0x80000, and SRAM_LENGTH_MIN is 0x10000.
  • FLASH_ORIGIN, FLASH_OFFSET and SRAM_OFFSET are the same for all the devices, so they are common to all configurations.

You must combine the remaining values to form distinct configurations. In this case, you only need to combine SRAM_ORIGIN and CORE_*. If you look at the table, you will see that they appear in two out of the four possible value combinations. Hence, you have a total of two uVisor configurations. Call them after the parameters that make them unique:

CONFIGURATION_0x20000000_CORTEX_M4 = {0x0, 0x400, 0x20000000, 0x400, 0x80000, 0x10000, 122, CORE_CORTEX_M4}
CONFIGURATION_0x1FFF0000_CORTEX_M3 = {0x0, 0x400, 0x1FFF0000, 0x400, 0x80000, 0x10000, 122, CORE_CORTEX_M3}

${device0} and ${device1} belong to CONFIGURATION_0x20000000_CORTEX_M4; ${device2} and ${device3} belong to CONFIGURATION_0x1FFF0000_CORTEX_M3.


Memory architecture

Go to top

The uVisor expects a distinction between at least two memories: Flash and SRAM. If more than one memory fits this description, you should make an architectural decision about where to put uVisor. We suggest that if you have fast memories, you use those, provided they offer security features at least equal to those of the regular memories.

Platform-specific code

Go to top

Look at the uvisor folder structure:

uvisor
├── ...
│
├── core                       # uVisor main codebase; it is hardware-independent.
│
├── platform                   # Hardware-specific folder
│   ├── ...
|   └── ${family}              # NEW SUPPORTED FAMILY: You will develop here.
│       ├── [*] Makefile.configurations
│       ├── docs
│       └── inc
│           ├── [*] config.h
│           └── [*] configurations.h
│
└── api
    ├── ...
    └── lib
        ├── ...
        └── ${family}          # The release libraries end up here.

Each [*] indicates a file you must create during the porting process. See details for each of them below. The snippets refer to the configurations discussed in the previous example.


~/code/uvisor/platform/${family}/inc/configurations.h

This file contains the uVisor configurations for your family. Each configuration triggers a separate library release. This file should be autogenerated. Symbols that are common to all devices in the family should go first. Configuration-specific symbols are conditionally defined. The condition is based on a macro definition of the form CONFIGURATION_${CONFIGURATION_NAME}.

Example

#ifndef __CONFIGURATIONS_H__
#define __CONFIGURATIONS_H__

/*******************************************************************************
 * Family-wide configurations
 ******************************************************************************/

/* The symbols below *must* be calculated from values across the family. */

/* Maximum number of vectors seen across the family:
 *   NVIC_VECTORS = max(NVIC_VECTORS_i) for i in family */
#define NVIC_VECTORS 122

/* Minimum memory requirements:
 *   FLASH_LENGTH_MIN = min(FLASH_LENGTH_i) for i in family
 *   SRAM_LENGTH_MIN = min(SRAM_LENGTH_i) for i in family */
#define FLASH_LENGTH_MIN 0x80000
#define SRAM_LENGTH_MIN  0x10000

/* The symbols below can be configuration-specific or family-wide,
 * depending on your requirements. See the porting guide for more details. */

/* Memory boundaries */
#define FLASH_ORIGIN 0x0
#define FLASH_OFFSET 0x400
#define SRAM_OFFSET  0x400

/*******************************************************************************
 * Hardware-specific configurations
 *
 * Configurations are named after the parameter values, in this order:
 *   - SRAM_ORIGIN
 *   - CORE
 ******************************************************************************/

/* The symbols below are specific to each configuration. */

#if defined(CONFIGURATION_0x20000000_CORTEX_M4)

/* Memory boundaries */
#define SRAM_ORIGIN 0x20000000

/* ARM core selection */
#define CORE_CORTEX_M4

#elif defined(CONFIGURATION_0x1FFF0000_CORTEX_M3)

/* Memory boundaries */
#define SRAM_ORIGIN 0x1FFF0000

/* ARM core selection */
#define CORE_CORTEX_M3

#else /* Hardware-specific configurations */

#error "Unrecognized uVisor configuration. Check your Makefile."

#endif /* Hardware-specific configurations */

#endif /* __CONFIGURATIONS_H__ */

~/code/uvisor/platform/${family}/inc/config.h

This file contains uVisor customizations that are not hardware-specific but can be chosen by each family (for example, the default stack size).

This table lists symbols that you can specify.

Symbol Description
TOTAL_STACK_SIZE The size of uVisor's own stack, including guard bands
NDEBUG TODO
DEBUG_MAX_BUFFER TODO
CHANNEL_DEBUG TODO
MPU_MAX_PRIVATE_FUNCTIONS TODO
MPU_REGION_COUNT TODO
ARMv7M_MPU_REGIONS TODO
ARMv7M_ALIGNMENR_BITS TODO
ARMv7M_MPU_RESERVED_REGIONS TODO
UVISOR_MAX_ACLS TODO

Table 3. Optional hardware-specific config.h symbols

Note: You must always have a separate configurations.h file, even if the remaining config.h is empty. This ensures that configurations.h (which is possibly autogenerated by a script of yours) does not need to know anything more than the features of Table 1.

Example

#ifndef __CONFIG_H__
#define __CONFIG_H__

/* Your custom optional settings here. See Table 3. */
...

#include "configurations.h"

#endif /* __CONFIG_H__ */

~/code/uvisor/platform/${family}/Makefile.configurations

This file configures the build system for your device family. This table shows the symbols you can define.

Symbol Description
ARCH_MPU MPU architecture (if absent defaults to ARMv7M; alternative: KINETIS)
CONFIGURATIONS List of uVisor configurations, as defined in config.h.

Table 4. Platform-specific configuration symbols

Example

# MPU architecture
ARCH_MPU:=ARMv7M

# Family configurations
CONFIGURATIONS:=\
    CONFIGURATION_0x20000000_CORTEX_M4 \
    CONFIGURATION_0x1FFF0000_CORTEX_M3

Build

Go to top

You can finally generate all the uVisor library releases:

$ cd ~/code/uvisor
$ make

The build process generates as many static libraries (*.a files) as your family configurations, multiplied by two (debug and release builds). You can find them in ~/code/uvisor/api/lib/${family}. Please note that these libraries are not published in the uVisor repository. Instead, the glue-layer library deploys them and makes them available to the target OS.

Integrate uVisor in mbed OS

Go to top

You now need to integrate uVisor in the mbed OS codebase for your target. This requires the following steps, which we will cover in detail:

  • Add a hook to uvisor_init() in your startup script.
  • Add the uVisor-specific sections to your platforms' linker scripts.
  • Deploy the uVisor libraries for your target platforms.
  • Enable uVisor in your targets.

In the sections below, we refer to the mbed OS codebase as shown in the ARMmbed/mbed-os repository.

Startup script

Go to top

You can usually find the startup script at:

targets/TARGET_${vendor}/TARGET_${family}/TARGET_${device}/device/TOOLCHAIN_${toolchain}

The startup code must call the function uvisor_init() right after system initialization (usually called SystemInit()) and right before the C/C++ library initialization.

ResetHandler:
    ...
    ldr r0, =SystemInit
    blx r0
#if defined(FEATURE_UVISOR) && defined(TARGET_UVISOR_SUPPORTED)
    ldr r0, =uvisor_init    /* [*] Insert this. */
    blx r0                  /* [*] Insert this. */
#endif /* defined(FEATURE_UVISOR) && defined(TARGET_UVISOR_SUPPORTED) */
    ldr r0, =__start
    bx  r0
    ...

Make sure that no static initialization (zeroing the BSS section, loading data from flash to SRAM) happens before the uVisor initialization. Even setting a single global variable before uvisor_init() and then referring to it later can result in data corruption.

The conditional guards in the example above rely on the FEATURE_UVISOR and UVISOR_SUPPORTED symbols, which this guide will cover in the Library deployment section.

Linker script

Go to top

Note: Because uVisor only supports the GNU ARM Embedded Toolchain, this guide only provides instructions for the GCC ld linker script.

To enforce the security model, place uVisor at a specific location in memory and give uVisor specific information about the rest of the memory map. These symbols live in the ld linker script.

You can use the snippet below as a template for your ld linker script.

Please note that every occurrence of ... identifies part of the existing linker script that has been omitted here for clarity. You can find details and requirements for each section in a table below.

The linker script template below relies on the following assumptions:

  • The device has one flash memory, which the uVisor and the rest of the OS/app share.
  • The device has one SRAM memory.
    • The uVisor and the OS/app can share it.
    • Alternatively, you can place uVisor in a separate, faster memory (for example, a TCM).
...

/* Specify the memory areas. */
MEMORY
{
    m_interrupts (RX)  : ORIGIN = 0x00000000, LENGTH = 0x00000400
    m_text       (RX)  : ORIGIN = 0x00000400, LENGTH = 0x00100000
    m_tcm        (RW)  : ORIGIN = 0x10000000, LENGTH = 0x00010000 /* Optional */
    m_data       (RW)  : ORIGIN = 0x1FFF0000, LENGTH = 0x00040000
}

/* Define the output sections. */
SECTIONS
{
    /* The startup code goes first into the internal flash. */
    .interrupts :
    {
        ...
    } > m_interrupts

    /* The program code and other data go into the internal flash. */

    /* Note: The uVisor expects this section at a fixed location, as specified
             by the porting process configuration parameter: FLASH_OFFSET. */
    __UVISOR_FLASH_OFFSET = < your FLASH_OFFSET here >;
    __UVISOR_FLASH_START = ORIGIN(m_interrupts) + __UVISOR_FLASH_OFFSET;
    .text __UVISOR_FLASH_START :
    {
        /* uVisor code and data */
        . = ALIGN(4);
        __uvisor_main_start = .;
        *(.uvisor.main)
        __uvisor_main_end = .;

        ...
    } > m_text

    /* Constuctors, destructors, etc. go here -- but not the data section! */
    ...

    /* The following SRAM sections are placed before .data, to ensure that their
       load address is the same as the one specified here. */

    .interrupts_ram :
    {
        ...
    } > m_data

    /* uVisor own memory and private box memories
    /* If uVisor shares the SRAM with the OS/app, ensure that this section is
     * the first one after the VTOR relocation section. */
    /* Note: The uVisor expects this section at a fixed location, as specified
             by the porting process configuration parameter: SRAM_OFFSET. */
    __UVISOR_SRAM_OFFSET = < your SRAM_OFFSET here >;
    __UVISOR_SRAM_START = ORIGIN(m_data/m_tcm) + __UVISOR_SRAM_OFFSET;
    .uvisor.bss __UVISOR_SRAM_START (NOLOAD):
    {
        . = ALIGN(32);
        __uvisor_bss_start = .;

        /* Protected uVisor own BSS section */
        . = ALIGN(32);
        __uvisor_bss_main_start = .;
        KEEP(*(.keep.uvisor.bss.main))
        . = ALIGN(32);
        __uvisor_bss_main_end = .;

        /* Protected uVisor boxes' static memories */
        . = ALIGN(32);
        __uvisor_bss_boxes_start = .;
        KEEP(*(.keep.uvisor.bss.boxes))
        . = ALIGN(32);
        __uvisor_bss_boxes_end = .;

        . = ALIGN(32);
        __uvisor_bss_end = .;
    /*********************** SRAM shared with OS/app **************************/
    } > m_data
    /*********************** uVisor in its own TCM ****************************/
    } > m_tcm
    /**************************************************************************/

    /* Heap space for the page allocator
    /* If uVisor shares the SRAM with the OS/app, ensure that this section is
     * the first one after the uVisor BSS section. Otherwise, ensure it is the
     * first one after the VTOR relocation section. */
    .page_heap (NOLOAD) :
    {
        . = ALIGN(32);
        __uvisor_page_start = .;
        KEEP(*(.keep.uvisor.page_heap))
        /************************** ARMv7-M MPU only **************************/
        . = ALIGN((1 << LOG2CEIL(LENGTH(m_data))) / 8);
        /********************** Other MPU architectures ***********************/
        . = ALIGN(32);
        /**********************************************************************/
        __uvisor_page_end = .;
    } > m_data

    /* Now we can place the .data section, which will be loaded to SRAM. */
    .data :
    {
        ...
    } > m_data AT > m_text

    /* uVisor configuration section
     * This section must be located after all other flash regions. */
    .uvisor.secure :
    {
        . = ALIGN(32);
        __uvisor_secure_start = .;

        /* uVisor secure boxes configuration tables */
        . = ALIGN(32);
        __uvisor_cfgtbl_start = .;
        KEEP(*(.keep.uvisor.cfgtbl))
        . = ALIGN(32);
        __uvisor_cfgtbl_end = .;

        /* Pointers to the uVisor secure boxes configuration tables */
        /* Note: Do not add any further alignment here, as uVisor will need to
                 have access to the exact list of pointers. */
        __uvisor_cfgtbl_ptr_start = .;
        KEEP(*(.keep.uvisor.cfgtbl_ptr_first))
        KEEP(*(.keep.uvisor.cfgtbl_ptr))
        __uvisor_cfgtbl_ptr_end = .;

        /* Pointers to all boxes register gateways. These are grouped here to
           allow discoverability and firmware verification. */
        __uvisor_register_gateway_ptr_start = .;
        KEEP(*(.keep.uvisor.register_gateway_ptr))
        __uvisor_register_gateway_ptr_end = .;

        . = ALIGN(32);
        __uvisor_secure_end = .;
    } > m_text

    /* From now on you can insert any other SRAM region. */

    /* Uninitialized data section
     * This region is not initialized by the C/C++ library and can be used to
     * store state across soft reboots. */
    .uninitialized (NOLOAD):
    {
        . = ALIGN(32);
        __uninitialized_start = .;
        *(.uninitialized)
        KEEP(*(.keep.uninitialized))
        . = ALIGN(32);
        __uninitialized_end = .;
    } > m_data

    /* BSS, heap and stack go here.
     * The one below is a sample implementation. */

    .bss (NOLOAD):
    {
        . = ALIGN(4);
        __bss_start__ = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        __bss_end__ = .;
    } > m_data

    /* Note: The uVisor requires the original heap start and end addresses to be
             provided. */
    .heap (NOLOAD):
    {
        . = ALIGN(8);
        __end__ = .;
        end = .;
        __uvisor_heap_start = .;
        __HeapBase = .;
        . += HEAP_SIZE;
        __HeapLimit = .;
        __uvisor_heap_end = .;
    } > m_data

    /* Initialize the stack at the end of the memory block. */
    __StackTop = ORIGIN(m_data) + LENGTH(m_data);
    __stack = __StackTop;
    __StackLimit = __StackTop - STACK_SIZE;

    /* Provide physical memory boundaries for uVisor. */
    __uvisor_flash_start = ORIGIN(m_interrupts);
    __uvisor_flash_end = ORIGIN(m_text) + LENGTH(m_text);
    /*********************** SRAM shared with OS/app **************************/
    __uvisor_sram_start = ORIGIN(m_data);
    __uvisor_sram_end = ORIGIN(m_data) + LENGTH(m_data);
    __uvisor_public_sram_start = __uvisor_sram_start;
    __uvisor_public_sram_end = __uvisor_sram_end;
    /*********************** uVisor in its own TCM ****************************/
    __uvisor_sram_start = ORIGIN(m_tcm);
    __uvisor_sram_end = ORIGIN(m_tcm) + LENGTH(m_tcm);
    __uvisor_public_sram_start = ORIGIN(m_data);
    __uvisor_public_sram_end = ORIGIN(m_data) + LENGTH(m_data);
    /**************************************************************************/
}

As the snippet above shows, the uVisor needs the following regions:

Region Description
.uvisor.main It holds the monolithic uVisor core binary. Its location determines the API table of uVisor because uvisor_api points to the first entry in this table. This region must be located at the same offset as FLASH_OFFSET in your configuration.
.uvisor.bss It contains uVisor's memories (coming from the uVisor library, in .keep.uvisor.bss.main) and the private boxes' protected memories (in .keep.uvisor.bss.boxes). This region must be located at the same offset that you specified in SRAM_OFFSET. If uVisor shares the SRAM with the OS/app, this region must be positioned right before the page heap section and immediately after the VTOR relocation section. Otherwise, it spans the whole memory devoted to it (for example, a TCM).
.page_heap It hosts pages the uVisor secure allocator allocates. If the uVisor shares the SRAM with the OS/app, it must be immediately after the uVisor BSS section. Otherwise, it must be the first SRAM region after the VTOR relocation section.
.uvisor.secure It contains constant data in flash that describes the configuration of the secure boxes. It comprises the configuration tables (.keep.uvisor.cfgtbl) and the pointers to them (.keep.uvisor.cfgtbl_ptr[_first]), which uVisor uses for box enumeration.
.uninitialized It is not, strictly speaking, a uVisor region but should go in the linker script, so data can pass to the execution environment across soft reboots. The C/C++ library initialization never touches this section, but the testing framework uses it.

All these sections and their subregions must start and end with symbols that describe their boundaries, as the template shows. Note that all symbols prefixed with .keep must end up in the final memory map even if they are not explicitly used.

Once uVisor is active, it maintains its own vector table, which the rest of the code can only interact with through uVisor APIs. As the script above shows, we still suggest to leave the standard vector table in flash so that if uVisor is disabled, any legacy mechanism for interrupt management still works. This is the same reason we need an SRAM_OFFSET; it reserves the space for relocation of the original OS vector table.

The heap boundaries need to be provided (__uvisor_heap_start and __uvisor_heap_end) because the uVisor allocator APIs use them. The uVisor allocator APIs are available even when uVisor is not strictly supported on a platform, but in that case, there is no security feature.

Note: After making the necessary changes to your linker script, HEAP_SIZE becomes the minimum heap size. The heap will grow to fill all available RAM between the stack and data sections. If there is not enough room for the minimum heap size, the linker generates an error. The reason for this change is to ease transitioning applications from the legacy heap to the uVisor page heap; after an application is fully transitioned to the uVisor page heap, the legacy heap is no longer required and can have a HEAP_SIZE size of 0.

Finally, note that at the end of the linker script, the physical boundaries for the memories (flash/SRAM) uVisor populates are also provided.

Library deployment

Go to top

The uVisor codebase is embedded in ARMmbed/mbed-os, and you can find it at features/FEATURE_UVISOR. This module is a glue layer library that translates the ARMmbed/uvisor core and APIs into a file structure that is compatible with mbed OS:

mbed
└── features
    └── FEATURE_UVISOR
        ├── importer
        │   ├── [*] Makefile
        │   └── TARGET_IGNORE
        │       └── uvisor -> ARMmbed/uvisor
        ├── includes
        |
        ├── source
        |
        └── targets
            ├── TARGET_UVISOR_SUPPORTED
            |   ├── < your target here >
            │   ├── TARGET_EFM32
            │   ├── TARGET_MCU_K64F
            │   └── TARGET_STM32F4
            └── TARGET_UVISOR_UNSUPPORTED

The uVisor repository is a submodule in the features/FEATURE_UVISOR/importer/TARGET_IGNORE folder. As the folder name suggests, the uVisor code is never compiled directly. Instead, an importer script, Makefile, periodically builds the uVisor and publishes the resulting libraries in the source and includes directories.

If you want to add your newly ported platforms to this deployment process, you need to change the Makefile, so it knows how to translate the ${family} name that you used during the porting process into a compatible mbed OS target name. Given the generality of your uVisor port, you might need to translate the same family/configuration to multiple mbed OS targets.

The translation requires you to add an element to the following line in the Makefile:

TARGET_TRANSLATION:=MCU_K64F.kinetis EFM32.efm32 STM32F4.stm32

Following the previous examples in this porting guide, we assumed that your device ${family} contains four devices, ${device0}, ${device1}, ${device2} and ${device3}. The first two belong to the CONFIGURATION_0x20000000_CORTEX_M4 configuration, the other two to the CONFIGURATION_0x1FFF0000_CORTEX_M3 one.

If you have two targets named TARGET_${device0_or_1} and TARGET_${device2_or_3} in mbed OS, the Makefile translation will look like:

TARGET_TRANSLATION:=MCU_K64F.kinetis EFM32.efm32 STM32F4.stm32 ${device0_or_1}.${family} ${device2_or_3}.family

Once you have updated the importer script, you can run make from the importer folder. Your new libraries will show up in the targets folder.

Target configuration

Go to top

Although downloading the mbed OS repository always fetches the uVisor codebase, the uVisor libraries do not link automatically. For them to do so, the target description must explicitly specify that uVisor is a supported feature for that target.

In addition, even when the uVisor feature is set, a low-impact version of the uVisor libraries links by default, which does not enable the uVisor security features. We call this version of the libraries the unsupported libraries.

The unsupported libraries are useful when an application or the operating system wants to use uVisor APIs that are available even when a platform does not support the uVisor. At the moment, only the uVisor allocator APIs are available in the unsupported libraries.

To enable the uVisor feature in a target and trigger the linkage of the supported libraries, see this table.

Symbol How to define it Description
FEATURE_UVISOR Add UVISOR to the features field in the target description file. If a target sets it, the uVisor codebase is compiled and linked when building applications for that target. Depending on whether UVISOR_SUPPORTED is set, the linked libraries are in unsupported or supported mode. In unsupported mode, there are no security features, and only the allocator APIs are available.
UVISOR_SUPPORTED Add UVISOR_SUPPORTED to the extra_labels field in the target description file. If a target sets it together with FEATURE_UVISOR, the libraries that are linked into the application provide the full set of the uVisor security features.

Given the description above, the following combinations are possible:

FEATURE_UVISOR UVISOR_SUPPORTED What happens
Not defined Not defined uVisor is not linked in your app. The uVisor allocator APIs are not available.
Not defined Defined uVisor is not linked in your app. The uVisor allocator APIs are not available.
Defined Not defined uVisor is linked in your app in unsupported mode. Only the uVisor allocator APIs are available.
Defined Defined uVisor is linked in your app in supported mode. All uVisor APIs and security features are available.

Please note that defining both the FEATURE_UVISOR and the UVISOR_SUPPORTED symbols does not automatically enable uVisor on the application. By default, uVisor runs in disabled mode, where the uVisor initialization function is executed but returns immediately.

In disabled mode, no security feature is enabled though the uVisor binaries are still flashed to the device. To learn more about the uVisor modes of operation, please see the API documentation. If you want to know how to enable uVisor and configure an application to use the uVisor security features, please see the getting started guide.

If you do not want to enable the uVisor features immediately on your mbed target, users can still selectively override the target features and labels to enable uVisor. You can use the configuration system to set the FEATURE_UVISOR and UVISOR_SUPPORTED symbols by creating a file called mbed_app.json at the application level with the following content:

{
    "target_overrides": {
        "${original_target}": {
            "target.features_add": ["UVISOR"],
            "target.extra_labels_add":["UVISOR_SUPPORTED"],
        }
    },
    "macros": [
      "FEATURE_UVISOR",
      "TARGET_UVISOR_SUPPORTED"
    ]
}

Please note that the macros FEATURE_UVISOR and TARGET_UVISOR_SUPPORTED in the configuration file above are automatically defined for C and C++ files but not for assembly files. Because the startup script (usually in assembly) relies on those symbols, you need to define them manually.

Next steps

Go to top

It is time to test your application. We suggest that you use the example app, mbed-os-example-uvisor, but if you prefer, you can build a uVisor-enabled blinky program following the getting started guide.

In both cases, please:

  • Run uVisor at least once in debug mode to ensure all runtime sanity checks pass. You can also use this round of checks to confirm your linker script and uVisor ports are structurally correct. For more information about the uVisor debug mode, please read Debugging uVisor on mbed OS.
  • Ensure that your app has the relevant ACLs to work with uVisor enabled. This requires you to run uVisor in debug mode multiple times and find all the faulting peripherals. Read a more detailed description of this procedure in the final section of the getting started guide.