Porting¶
MARTe2 has a friendly structure which allows easy porting on different platforms. Essentially, porting comes down to a sequence of basic steps, some of them are mandatory while others are optional, to accomplish the whole porting process.
Porting fundamentals¶
Starting from fundamentals, the MARTe2 structure will be again represented from the porting point-of-view. MARTe2 porting consists essentially in adaptations on the outer layers (lowermost and uppermost) of its layered structure. Specifically:
Core/BareMetal
L0Types (Architecture)
L1Portability (Architecture/Environment)
L6App (Environment)
Core/FileSystem
L1Portability (Environment)
L6App (Environment)
Core/Scheduler
L1Portability (Environment)
As described above, layers may contain one or both Architecture and Environment subdirectories. While the first contains hardware related porting code, the latter contains software and “operating system” code. The term “operating system” is intentionally quoted. Actually the term is abused as MARTe2 can also be ported on bare metal, directly on hardware without an operating system but with an hardware abstraction layer of software instead, which wraps calls to the underlying platform.
Each of the Architecture and Environment subfolders may contain other folders in turn, which represent the specific platform and operating system for the porting couple. Their naming must follow a convention and must encourage code reusation across combinations. One fundamental advice is to avoid binding Architectures to Environment and to maintain clean and streamlined versions of the two. Should this be impossible, a scheme to avoid
direct binding and leave room for code re-utilisation will be further explained (see Architecture/Environment unbinding section). This is done to avoid ARCH x ENV
combinations proliferation of (mostly) copy-paste code.
Bundled Architecture and Environment follow this principle, although specific platforms have some exceptions which are handled in a further descripted section (Architecture/Environment unbinding).
Bundled Architecture/Environment folders must reside in a specific directory, located inside MARTe2 directory tree (e.g. Source/Core/BareMetal/L0Types/Architecture/x86_gcc). In order to avoid cluttering, have clear separation and allow independent “unofficial” portings, their placement can be customised by using environment settings.
- The aforementioned mechanism works leveraging a Makefile-based structure where
The MakeDefaults directory contains definitions (MakeStdLibDefs) and rules (MakeStdLibRules) for each couple of Architecture and Environment
The MakeDefaults directory can be placed inside the MARTe2 one for bundled configuration or moved outside for customisation
Each top-level Makefile knows its own MARTe2 tier (BareMetal/FileSystem/Scheduler) and level (L0, L1, …, L6). This is particulary true in L0, where
TIER
andLAYER
symbols are needed to build include pathsThe TARGET environment variable is used to derive the Architecture and Environment couple (i.e. their directory name)
Makefile are defaulting to MARTe2 Architecture/Environment path but two environment variables allow them to be placed elsewhere
For instance, a complete workflow for an existing target configuration is (e.g. Ultrascale+ porting running with FreeRTOS)
TARGET=armv8_gcc_a53-freertos_us
This translates Architecture to armv8_gcc_a53 and Environment to freertos_us which means going into the MakeDefaults directory and search for the following two files:
MakeStdLibDefs.armv8_gcc_a53-freertos_us
MakeStdLibRules.armv8_gcc_a53-freertos_us
The MakeStdLibDefs file defines two new variables (i.e. ENVIRONMENT=FreeRTOS
and ARCHITECTURE=armv8_gcc
) which are used for the subsequent Makefiles to search and find the right Environment and Architecture subdirectories.
Please note that the files may have further naming convention, which can be used to do subdivisions. In the specific example, the _a53
suffix stands for the eventual CPU specialisation and _us
stands for Ultrascale+ environment.
Base folder customisation¶
In order to have independent portings, without affecting the cloned MARTe2 repository, the MakeDefaults directory can be customised.
The MakeDefaults directory can be configured by means of the MARTe2_MAKEDEFAULT_DIR
Once the TARGET
environment variable is set, inside that folder you must have (at least) the MakeStdLibDefs.arch-env and the MakeStdLibRules.arch-env files.
At this point, these two files may combine already existing Architecture/Environment or specify new ones. Using the MARTe2_PORTABLE_ENV_DIR
and MARTe2_PORTABLE_ARCH_DIR
environment variables, you can also move outside the MARTe2 directory tree your own porting implementation, leaving unaffected the original folder content and structure, given however they remain hierarchically structured as their bundled counterpart.
Architecture/Environment unbinding¶
In some scenarios it may be difficult a total separation between architecture and environment. When this happens, it is advisable to use the following pattern. This approach can be already found inside some of the actual MARTe2 portings.
Create a standard porting file, where instead of the real implementation you only put a
#define
guarded includeOnly in the included file, use the needed “mixed” approach
In your MakeStdLibDefs.arch-env add the
ENV_ARCH_CUSTOMIZATION
variable
For example, the CompilerTypes.h
file for the Xilinx Ultrascale+ implementation has a non-generic armv8 gcc porting. To achieve that, a standard
CompilerTypes.h is produced:
#ifdef XILINX_ULTRASCALE
#include "CompilerTypesXil.h"
#define CUSTOMIZED_COMPILERTYPES_HEADER
#endif
#ifndef CUSTOMIZED_COMPILERTYPES_HEADER
#include "CompilerTypesDefault.h"
#endif
As shown above, the ENV_ARCH_CUSTOMIZATION
is brought inside the CompilerTypes.h
file and its value is used to distinguish between clean and targeted implementation. Note also the approach allows multiple implementations and a fallback CompilerTypesDefault.h
implementation. The fallback implementation is, effectively, the default clean one.
Portable test environment¶
MARTe2 default test suite is based on GTest. Another option, which comes handy when porting MARTe2 is the availability of a portable test environment.
The portable test environment is based on a mechanism that offers an interface compatible to the one offered by GTest but way simpler
in terms of implementation and dependencies. Standard makefiles default to the GTest suite, the setting of the MARTe2_TEST_ENVIRONMENT
variable to "Portable"
instead, switches to the Portable. The portable environment, used in conjunction with the static test mode, produces a whole test executable which can be executed on the ported environment.
To reduce the size of the executable or the scope of the tests, a test link filter is available. It is enabled only when MARTe2_TEST_ENVIRONMENT
is set to "Portable"
and enables another two options: MARTe2_TEST_LINK_FILTER_MODE
and MARTe2_FILTER_ITEMS
.
The MARTe2_TEST_LINK_FILTER_MODE
environment variable controls the level at which the filter has to operate while the MARTe2_FILTER_ITEMS
controls what, also according to the mode, has to be included or excluded from the linking step in the executable generation.
The MARTe2_TEST_LINK_FILTER_MODE
can be:
- Layer
, the filtering happens at whole Tier/Layer level, meaning that the MARTe2_FILTER_ITEMS
will contain the name of the layers which have to be included/excluded;
- Library
, the filtering happens at single test object level, meaning that the MARTe2_FILTER_ITEMS
will contain the name of the test objects that have to be included/excluded;
The MARTe2_FILTER_ITEMS
environment variable must contain a colon (:) separated list of the names of the layers/library to include or exclude.
When only the name is specified (e.g. L1PortabilityBGTest.a) the linking operates in whitelist mode and includes only the specified library.
When the name is preceded by a minus sign (-) (e.g. -L1PortabilityBGTest.a) the linking operates in blacklist mode, a whole list of the available layers will be built and, if and only if the specified name is found, it will be removed from the list of the linked objects.
When operating in Layer mode, the MARTe2 naming convention helps filtering in/out the right tier as a capital letter (B for BareMetal, S for Scheduler and F for FileSystem) is appended to the layer name itself (e.g. L1PortabilityBGTest.a or L2ObjectsBGTest.a)
Note that the filter cannot operate in mixed black/whitelist mode (e.g. you cannot mix L1PortabilityBGTest.a:-L2ObjectsBGTest.a). If you both include and exclude an error will be generated.
MARTe2 startup hooks¶
Some embedded deployment scenarios require that hardware initialisation functions start before any other code is executed. This is particularly useful when deploying the full MARTe2 Application, which owns already a main() entry point. MARTe2 has currently two (2) hooks which are called during the startup phase, before the GlobalObjectsDatabase constructor begins its initialisation loop:
InitHardware() {[...]} InitPlatform() {[...]}
Their declaration stays inside BareMetal/L1Portability/HardwareI.h
and you should provide an implementation (at least an empty one if not needed).
Implementation of these methods are intended to initialise the underlying hardware platform before the MARTe initialisation process begins, like in embedded or microcontroller-based platforms, where peripherals (e.g. U[S]ART) should be initialised beforehand (e.g. console output functions).
They are called subsequently, one after the other without any other intermediate call (e.g. InitHardware(); InitPlatform();
).
Bootstrapping advices¶
If the platform initialisation does not require to be executed before the GlobalObjectsDatabase
construction, it can be
accomplished at a later stage in the ported Bootstrap.cpp
. This is the case of the current FreeRTOS implementation, where a MARTe2HardwareInitialise()
extern function is expected to be implemented from the hardware platform side.
In the specific aforementioned FreeRTOS implementation, as the platform hardware initialisation may be required to run in a task execution environment, MARTe2 waits for a vTaskNotification
to resume its operations. In that scenario, the minimal MARTe2HardwareInitialise()
call must notify the caller task to start MARTe2.
Walkthrough¶
The porting guide is supported by the following walkthrough, in order to speedup the boilerplate operations needed to begin the MARTe2 porting process.
Step 1 - Makefile Rules and Definitions¶
In the first step you will need to create a suitable directory to hold the two Makefile Rules and Definitions for your architecture. Makefile naming convention needs for these two to be named with the following scheme [architecture]-[environment] where
[architecture] can also be split into subarchitectures (e.g. armv8_gcc or x86_gcc or x86_cl)
[environment] can also be split into subenvironments (e.g. freertos_us or bare_us)
Step 2 - Change MakeStdLibDefs file¶
Inherit an existing MakeStdLibDefs with default needed compiler defines by changing the first inclusion. You can use a shipped one or start from one of them to adapt to the ported platform.
Update the ENVIRONMENT
and ARCHITECTURE
(see above) with the names your architecture and environment combination should have. Note that
these two names will match the two Architecture/Environment subdirectories under each ported layer
For tidyness, their name should be given accordingly to their internally set ARCHITECTURE``and ``ENVIRONMENT
variables.
Update the MARTe2_LINK_MODE
variable to suit your environment. Allowed values are Static or anything else (also none or undefined). This value will
affect the MARTeApp linking step.
Update the MARTe2_TEST_ENVIRONMENT
variable to suit your needs. Allowed values are Portable
and GTest
. This value will affect the MARTe2 test suite
generation. Undefined defaults to GTest.
Append, by using INCLUDES+=
, eventual additional directories which are needed during the compilation process. Keep in mind that these are
the ones where both ported architecture and environment code will source. Usually here goes the BSP (Board Support Package) path where all the includable
headers for the platform reside. To keep the code clean and independent from the path, you can use your own environment variable to point the includes to the right place.
- Update the (a)
COMPILERPREFIX
, (b)COMPILER
, (c)CCOMPILER
and (d)LINKER
with your prefix for all the names (e.g.
aarch64-none-elf
);C++ compiler (e.g.
-g++
);C compiler (e.g.
-gcc
) and (d) linker (e.g.-ld
). TheCOMPILERPREFIX
could be omitted, as its purpose is solely aestethic, avoiding repetition.
Update the CPUSPECIFICFLAG
, ADDITIONALFLAGS
, OPTIM
and CFLAGS
according to your own platform specifications. Note that the only significant variable
is CFLAGS
, which is built appending the others.
Step 3 - Change MakeStdLibRules file¶
Inherit an existing MakeStdLibRules with default needed compiler defines by changing the first inclusion. You can use a shipped one or start from one
of them to adapt to the ported platform. Please note here that rules are also named with a convention and they inherit/override settings (e.g. gcc-dynamic
vs gcc-static
).
Step 4 - Set the right environment variables¶
In contrast to standard shipped MARTe2, the MARTe2_DIR
environment variable must be set in order for the makefiles chain to become able to pick
MARTe2 and own ported files. This becomes true especially if using the external Makedefaults/Architecture/Environment directories configuration.
At a bare minimum, the following environment variables must be set:
MARTe2_MAKEDEFAULT_DIR
(where aforementioned MakeStdLibDefs and MakeStdLibRules reside)
MARTe2_PORTABLE_ENV_DIR
(where the environment-related porting files reside)
MARTe2_PORTABLE_ARCH_DIR
(where the architecture-related porting files reside)
Plus, setting the environment variable MARTe2_TEST_ENVIRONMENT
, sets the right test environment to be compiled for MARTe. As above stated, this variable
can be GTest
or Portable
. If planning to port for embedded architecture, prefer the Portable environment, unless sure that the GTest suite is able to
be compiled. Also take into account the link filter previously explained.
Note that the MARTe2_TEST_ENVIRONMENT
defaults to GTest. To avoid test compilation, which is useful in the first development phases, you can disable
their compilation by issuing
"make -f Makefile.gcc core"
After the bare minimum variables, you must also consider the BSP files location which will be needed for your own platform porting. This variable
will be used in the aforementioned INCLUDES+=
line inside the MakeStdLibDefs file previously written.
Step 5 - Create the architecture and environment files¶
Use the provided stubs to speedup the development process or take them from an existing implementation. Consider that the stubs or, alternatively, your own must provide an implementation for every function defined (no __weaks symbols) eventually returning a failure or fail-safe value.
Also consider that, given that the Architecture and Environment directories are different, porting may be only oriented to just one of them.
This could be achieved by setting accordingly the MARTe2_PORTABLE_ARCH_DIR
and MARTe2_PORTABLE_ENV_DIR
variables.
Warning
If other ancillary files are needed, aside the mandatory porting ones, keep in mind to add their .x
line in the Makefile.inc
.
Quick reference on files to change¶
Files which are needed are (1)
BareMetal - L0Types - Architecture
CompilerTypes.h
BareMetal - L1Portability - Architecture
AtomicA.h (2)
EndianityA.h (3)
FastMathA.h
HardwareIA.h (4)
HighResolutionTimerA.h (5)
ProcessorA.h (6)
BareMetal - L1Portability - Environment
BasicConsole.cpp (7)
GeneralDefinitions.h (8)
HardwareI.cpp (9)
HighResolutionTimer.h (10) + HighResolutionTimerCalibrator.h/.cpp
LoadableLibrary.cpp (11)
Sleep.cpp (12)
StandardHeap.cpp (13)
Timestamp.cpp (14)
BareMetal - L6App - Environment
Bootstrap.cpp (15)
FileSystem - L1Portability - Environment
BasicFile.cpp (16)
BasicFileProperties.h (17)
BasicSocket.cpp (18)
BasicTCPSocket.cpp
BasicUART.cpp (19)
BasicUARTProperties.h
Directory.cpp (20)
DirectoryCore.h
DirectoryScanner.cpp
InternetHost.cpp (21)
InternetHostCore.h
InternetMulticastCore.h
InternetService.cpp (22)
InternetServiceCore.h
Select.cpp (23)
SelectProperties.h
SocketCore.h (24)
Scheduler - L1Portability - Environment
EventSem.cpp
MutexSem.cpp
Processor.cpp
Threads.cpp (25)
ThreadDatabase.h (26)
Notes¶
Based on the file listing above, some notes are provided about specific files porting instructions.
(1): provided stubs are empty do-nothing implementation of the described methods.
(2): when porting AtomicA.h, prefer intrinsics over inline assembly where possible (e.g. __atomic_test_and_set)
(3): when porting EndianityA.h, ensure your platform endianess and implement accordingly the To/From functions. Some of them may result in do-nothing functions (e.g. the ToLittleEndian in a little endian architecture). Consider that some architectures may be both configured as big and little. Prefer intrinsics to achieve endiannes swap (e.g. __bswap/16/32/64).
(4): HardwareIA.h holds the function that will be called at startup, which is intended to be called as hardware initialisation.
(5): Native microseconds-granular counters are preferred, to suit internal calculations (delays, etc).
(6): Can also be implemented to return a fixed compile-time value if cpuid mechanism is unwanted/unnecessary as not fundamental.
(7): Can also be empty-implemented like the stub, if the console is unavailable (e.g. headless or unsupervised) or unneeded.
(8): Catch all container for all baseline stuff (e.g. handles and constants).
(9): Contains the code which must be run before MARTe2 GlobalObjectsDatabase construction, see HardwareIA.h and notes above.
(10): If the vanilla 32/64 bit counter is available without further initialisations, the implementation can be straightforwardly included in HighResolutionTimer.h (stateless, no class, direct inline methods). Else, if the HRT needs a stateful implementation or a peripheral initialisation beforehand, the HRT calibrator approach can be used. This means that you have an instance of the calibrator, which is a class in charge of the initialisation and preparation of the timer/counter which lies statically and externally referenced from the HighResolutionTimer.h file inside the inlined methods. This approach can be translated anywhere else, to suit similar needs.
(11): If the porting is based on dynamic linking and runtime module loading, place here suitable functions (see Linux dlopen())
(12): Sleep functions here are meant to be, if available, a non-busy sleep alternative from the OS (e.g. yielding like the FreeRTOS vTaskDelay()).
(13): Must implement own heap management function (alloc/free/realloc/…). Use OS-aware primitives when available (e.g. FreeRTOS vPortMalloc/vPortFree)
(14): Timestamp functions can be left empty if the platform has not RTC
(15): Functions can be semantically split between BareMetal/L6App and FileSystem/L6App bootstraps
(16): Handle variable, used across FileSystem implementations, stays in GeneralDefinitions.h. It assumes a POSIX style handle.
(17): Place here convenient structure to hold basic file information (see Linux implementation).
(18): Needs aformentioned Handle and InternetHost class to be defined, see below.
(19): Use BasicUARTProperties.h to define a suitable structure for UART configuration data (bps, data, stop, parity bits, …)
(20): Use DirectoryCore.h to define a suitable structure to hold directory stat.
(21): Use InternetHostCore.h to define a suitable structure to hold IP/host data.
(22): Use InternetServiceCore.h to define a suitable structure to hold IP/service data.
(23): Use SelectProperties.h to define a suitable structure to hold select data (POSIX fd_set style).
(24): Used by BasicSocket as base handle.
(25): If the OS/BM does not include a thread database, the ThreadDatabase can be used instead, see ThreadDatabase.h
(26): Defaults to a MARTe2 based GenericThreadDatabase, which can be used to store/retrieve currently spawned threads. Be aware that, if not using the database, that some informations may be different between the real number of running threads and the MARTe2 perceived one. For example, if bypassing the ThreadsDatabase, asking directly to the OS the number of threads returns a number that may be inconsistent for the MARTe2 point-of-view. This is especially true in some tests, which expect a zero (0) number of threads still running after the completion. The zero (0) value has to be intended as “MARTe2 has 0 threads running” and not as “The system has 0 threads running”. (See socket test suite).