Browse Source

Update to cmake buildsystem

master
MG-5 2 months ago
parent
commit
de6f8a5b58
  1. 22
      .vscode/c_cpp_properties.json
  2. 19
      CMakeLists.txt
  3. 24
      Makefile
  4. 83
      core/CMakeLists.txt
  5. 48
      core/README.md
  6. 26
      core/chip/chip.h
  7. 13
      core/chip/hal_header.h
  8. 48
      core/cmake/compilerSetup.cmake
  9. 10
      core/cmake/detectCompilerType.cmake
  10. 75
      core/cmake/extractBuildinfoFromMakefile.cmake
  11. 69
      core/cmake/generateCubeMXTargets.cmake
  12. 63
      core/cmake/setupEmbeddedBuild.cmake
  13. 39
      core/cmake/setupEmbeddedExtraFiles.cmake
  14. 61
      core/inc/core/BuildConfiguration.hpp
  15. 24
      core/inc/core/BuildConfigurationTypes.h
  16. 38
      core/inc/core/SafeAssert.h
  17. 82
      core/inc/core/build_information.hpp
  18. 24
      core/inc/core/fault_handler.h
  19. 14
      core/inc/core/hash.hpp
  20. 6
      core/mk/CubeMXMakefileInstrumentation.mk
  21. 36
      core/mk/gcc-config.mk
  22. 34
      core/mk/include.mk
  23. 100
      core/mk/rules.mk
  24. 1
      core/requirements.txt
  25. 75
      core/scripts/checkStaticLibraries.py
  26. 60
      core/scripts/generateVersionHeader.py
  27. 34
      core/src/SafeAssert.cpp
  28. 28
      core/src/abi.cpp
  29. 76
      core/src/fault_handler.cpp
  30. 55
      core/src/hash.cpp
  31. 5
      core/src/std.cpp
  32. 100
      core/templates/CMakeLists.txt.FirmwareTemplate.cmake
  33. 66
      core/templates/CMakeLists.txt.LibraryTemplate.cmake
  34. 20
      core/templates/CMakeLists.txt.SplitterTemplate.cmake
  35. 60
      core/templates/CMakeLists.txt.TestTemplate.cmake
  36. 22
      core/templates/c_cpp_properties.json
  37. 5
      core/templates/flash_commands.jlink
  38. 7
      core/templates/gitignore
  39. 42
      core/templates/launch.json
  40. 61
      core/templates/tasks.json
  41. 8
      core/templates/testMain.cpp
  42. 55
      core/tests/src/HashTest.cpp
  43. 11
      core/tests/src/SafeAssertTest.cpp
  44. 8
      core/tests/src/main.cpp
  45. 23
      cubemx/Makefile
  46. 62
      src/CMakeLists.txt

22
.vscode/c_cpp_properties.json vendored

@ -2,28 +2,12 @@
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/",
"${workspaceFolder}/cubemx/Core/Inc",
"${workspaceFolder}/cubemx/Drivers/STM32L4xx_HAL_Driver/Inc",
"${workspaceFolder}/cubemx/Drivers/STM32L4xx_HAL_Driver/Inc/Legacy",
"${workspaceFolder}/cubemx/Middlewares/Third_Party/FreeRTOS/Source/include",
"${workspaceFolder}/cubemx/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2",
"${workspaceFolder}/cubemx/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
"${workspaceFolder}/cubemx/Drivers/CMSIS/Device/ST/STM32L4xx/Include",
"${workspaceFolder}/cubemx/Drivers/CMSIS/Include",
"${workspaceFolder}/src",
"${workspaceFolder}/src/oled-driver/include",
"${workspaceFolder}/src/oled-driver"
],
"defines": [
"USE_HAL_DRIVER",
"STM32L432xx"
],
"includePath": [],
"defines": [],
"compilerPath": "/usr/bin/arm-none-eabi-gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"compileCommands": "${workspaceFolder}/compile_commands.json"
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4

19
CMakeLists.txt

@ -0,0 +1,19 @@
# CMake splitter decides if firmware or test application is built depending on
# how the compiler is named. arm-none-eabi compiler will build firmware. Everything else tests.
# This is necessary as cmake is not built to easily invoke different compilers for different
# Build-targets.
cmake_minimum_required(VERSION 3.15)
include("core/cmake/compilerSetup.cmake")
project(cmake_splitter VERSION 0.0.1 LANGUAGES CXX C)
if (NOT DEFINED isEmbeddedCompiler)
message(FATAL_ERROR "compiler type not known")
endif()
if(${isEmbeddedCompiler})
include("src/CMakeLists.txt")
else()
include("test/CMakeLists.txt")
endif()

24
Makefile

@ -1,24 +0,0 @@
TARGET := solder_workspace_lighting
DEVICE := stm32l432
DEFS += FW_USE_RTOS
INCDIRS := \
src/oled-driver/include \
src/oled-driver/ \
src
SOURCES := \
src/Button/Button.cxx \
src/Button/buttonHandler.cxx \
src/adc.cxx \
src/oled-driver/fonts/mono.cxx \
src/oled-driver/src/Display.cxx \
src/oled-driver/src/Image.cxx \
src/oled-driver/src/Renderer.cxx \
src/encoder.cxx \
src/leds.cxx \
src/OledDisplay.cxx
# Actual build engine
include core/mk/include.mk

83
core/CMakeLists.txt

@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.0)
project(core VERSION 0.0.1 LANGUAGES CXX C)
# generate build information every time compile is started
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/core)
add_custom_target(generateVersionHeader
${PROJECT_SOURCE_DIR}/scripts/generateVersionHeader.py
${PROJECT_SOURCE_DIR}/../
${PROJECT_BINARY_DIR}/include/core/Version.h
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_library(${PROJECT_NAME} STATIC
src/abi.cpp
src/fault_handler.cpp
src/hash.cpp
src/SafeAssert.cpp
src/std.cpp
)
target_include_directories(${PROJECT_NAME} PUBLIC
inc
chip
${PROJECT_BINARY_DIR}/include
)
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} generateVersionHeader)
target_link_libraries(${PROJECT_NAME} freertos hal_headers)
# embedded / testing / fuzzing / emulator
if (NOT DEFINED core_BUILDCONFIGURATION)
message(FATAL_ERROR "Required variable core_BUILDCONFIGURATION is not defined")
endif()
if (core_BUILDCONFIGURATION STREQUAL "embedded")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDTYPE=2)
message("You chose embedded build configuration.")
elseif (core_BUILDCONFIGURATION STREQUAL "testing")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDTYPE=3)
message("You chose testing build configuration.")
elseif (core_BUILDCONFIGURATION STREQUAL "fuzzing")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDTYPE=4)
message("You chose fuzzing build configuration.")
elseif (core_BUILDCONFIGURATION STREQUAL "emulator")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDTYPE=5)
message("You chose emulator build configuration.")
else ()
message(FATAL_ERROR "Please define a macro for your core_BUILDCONFIGURATION. You chose '${core_BUILDTYPE}'")
endif ()
# release / debug
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDCONFIG_DEBUG)
message("Buildtype is DEBUG.")
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
target_compile_definitions(${PROJECT_NAME} PUBLIC BUILDCONFIG_RELEASE)
message("Buildtype is RELEASE.")
else ()
message(FATAL_ERROR "Please define a macro for your CMAKE_BUILD_TYPE. You chose '${CMAKE_BUILD_TYPE}' which is not implemented yet. Did you forget to add -DCMAKE_BUILD_TYPE=Release or -DCMAKE_BUILD_TYPE=Debug?")
endif ()
if (NOT DEFINED isEmbeddedCompiler)
message(FATAL_ERROR "Required variable isEmbeddedCompiler is not in scope")
endif ()
if (NOT ${isEmbeddedCompiler})
include(CTest)
find_package(PkgConfig)
pkg_search_module(GTEST REQUIRED gtest)
pkg_search_module(GMOCK REQUIRED gmock)
add_executable(${PROJECT_NAME}_test
tests/src/main.cpp
tests/src/SafeAssertTest.cpp
tests/src/HashTest.cpp
)
target_include_directories(${PROJECT_NAME}_test PRIVATE
tests/include)
target_link_libraries(${PROJECT_NAME}_test PRIVATE
${GTEST_LDFLAGS}
${GMOCK_LDFLAGS}
gcov
${PROJECT_NAME})
target_link_options(${PROJECT_NAME}_test PRIVATE --coverage)
target_compile_options(${PROJECT_NAME}_test PRIVATE ${GTEST_CFLAGS} ${GMOCK_CFLAGS} --coverage)
add_test(${PROJECT_NAME} ${PROJECT_NAME}_test)
endif ()

48
core/README.md

@ -0,0 +1,48 @@
# core
TODO DESCRIBE NEW BUILDSYSTEM BASED ON CMAKE
TODO DESCRIBE SETUP OF NEW PROJECT
TODO DESCRIBE BUILDSYSTEMS ASSUMPTIONS ABOUT COMPILER
TODO RECOMMEND NINJA BUILD GENERATOR
TODO DESCRIBE PROJECT SPLITTER
```cmake
cmake_minimum_required(VERSION 3.15)
include("libraries/core/cmake/compilerSetup.cmake")
project(cmake_splitter VERSION 0.0.1 LANGUAGES CXX C)
if(${isEmbeddedCompiler})
include("src/CMakeLists.txt")
else()
include("test/CMakeLists.txt")
endif()
```
Core library containing the build system, common functionalities required by all firmwares.
TODO UPDATE, MOVE OVER CUBEMX SETUP OF CORE
### CMake setup
Expects *isEmbeddedCompiler* variable from *core/cmake/detectCompilerType.cmake* in parent scope.
Requires following libraries:
- freertos
- hal_headers
Exports following libraries:
- core
Core features automatic CMake translation of CubeMX Makefiles when the following is added to the Makefile
```makefile
export:
@printf 'MakeExport_SOURCES $(C_SOURCES) $(ASM_SOURCES)\n'
@printf 'MakeExport_MCU_Flags $(MCU)\n'
@printf 'MakeExport_DEFS $(AS_DEFS) $(C_DEFS)\n'
@printf 'MakeExport_INCLUDES $(AS_INCLUDES) $(C_INCLUDES)\n'
@printf 'MakeExport_LDSCRIPT $(LDSCRIPT)\n'
```

26
core/chip/chip.h

@ -0,0 +1,26 @@
#pragma once
#include "hal_header.h"
/**
* @brief Defines that are necessary for uavcan stm32 driver's compile checks
* to pass.
*/
#if defined(UAVCAN_STM32_NUM_IFACES)
#if defined(STM32F4)
#define CAN1_TX_IRQHandler CAN1_TX_IRQHandler
#define CAN1_RX0_IRQHandler CAN1_RX0_IRQHandler
#define CAN1_RX1_IRQHandler CAN1_RX1_IRQHandler
#if UAVCAN_STM32_NUM_IFACES == 2
#define CAN2_TX_IRQHandler CAN2_TX_IRQHandler
#define CAN2_RX0_IRQHandler CAN2_RX0_IRQHandler
#define CAN2_RX1_IRQHandler CAN2_RX1_IRQHandler
#endif
#endif
#if defined(STM32F1)
#define CAN1_RX1_IRQHandler CAN1_RX1_IRQHandler
#endif
#endif

13
core/chip/hal_header.h

@ -0,0 +1,13 @@
#pragma once
/**
* @brief Chip "independant" cubeHAL include
*
*/
#if __has_include("stm32f4xx_hal.h")
#include "stm32f4xx_hal.h"
#define STM32F4
#else
#include "stm32f1xx_hal.h"
#define STM32F1
#endif

48
core/cmake/compilerSetup.cmake

@ -0,0 +1,48 @@
include_guard(GLOBAL)
include(${CMAKE_CURRENT_LIST_DIR}/detectCompilerType.cmake)
DETECT_COMPILER_TYPE()
add_compile_options(-fdiagnostics-color)
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
if (NOT DEFINED isEmbeddedCompiler)
message(FATAL_ERROR "Required variable isEmbeddedCompiler is not in scope")
endif ()
if (isEmbeddedCompiler)
# fixes compiler detection with arm-none-eabi-gcc as cmake tries to
# build an executable but bare metal doesn't work like this
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
endif ()
if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
message(FATAL_ERROR "In-source build detected! Generate cmake in an extra folder to avoid a mess of files generated in your folder")
endif ()
if (${isEmbeddedCompiler})
message("arm compiler detected")
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
# cmake and the programs orchestrating it are stupid sometimes and only detect the C compiler
# using the same path, set CXX/objcopy/objdump paths explicitly
# this of course breakes when you pull apart your compiler programs
# but why would you do that?
get_filename_component(CompilerPath "${CMAKE_C_COMPILER}" PATH)
if (CompilerPath STREQUAL "")
# some ci compilers dont include the full path to compiler
# assume default names for everything
set(CMAKE_CXX_COMPILER "arm-none-eabi-g++" CACHE INTERNAL "")
set(CMAKE_OBJCOPY "arm-none-eabi-objcopy" CACHE INTERNAL "")
set(CMAKE_OBJDUMP "arm-none-eabi-objdump" CACHE INTERNAL "")
else()
set(CMAKE_CXX_COMPILER ${CompilerPath}/arm-none-eabi-g++ CACHE INTERNAL "")
set(CMAKE_OBJCOPY ${CompilerPath}/arm-none-eabi-objcopy CACHE INTERNAL "")
set(CMAKE_OBJDUMP ${CompilerPath}/arm-none-eabi-objdump CACHE INTERNAL "")
endif()
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
else ()
message("non-arm compiler detected")
endif ()

10
core/cmake/detectCompilerType.cmake

@ -0,0 +1,10 @@
include_guard(GLOBAL)
# Sets isEmbeddedCompiler true or false in parent scope
function(DETECT_COMPILER_TYPE)
if (${CMAKE_C_COMPILER} MATCHES "arm-none-eabi-")
set(isEmbeddedCompiler true PARENT_SCOPE)
else()
set(isEmbeddedCompiler false PARENT_SCOPE)
endif()
endfunction()

75
core/cmake/extractBuildinfoFromMakefile.cmake

@ -0,0 +1,75 @@
include_guard(GLOBAL)
# call specially created make file that prints out variables
# expecting output from the makefile code in mk/CubeMXMakefileInstrumentation.mk
# Sets the following variables in parent scope
# MakeExport_SOURCES, MakeExport_MCU_Flags, MakeExport_DEFS, MakeExport_INCLUDES, MakeExport_LDSCRIPT
# prefixDirectory will be applied to sources, includes and ldscript
function(GET_CUBEMX_VARIABLES prefixDirectory)
execute_process(COMMAND make --no-print-directory -C ${PROJECT_SOURCE_DIR}/${prefixDirectory}/
OUTPUT_VARIABLE out)
# split output into list
# a list is a string separated by ; for cmake btw
string(REPLACE "\n" ";" out "${out}")
list(LENGTH out length)
if (NOT length MATCHES 6)
# 5 entries + one empty line
message(FATAL_ERROR "Unable to extract information from makefile: list of lists is too short. Did you properly prepare/instrument it?")
endif ()
set(foundSources false)
set(foundMCUFlags false)
set(foundDefs false)
set(foundIncludes false)
set(foundLDScript false)
foreach (entry IN LISTS out)
# strip away any -I or -D
string(REPLACE "-I" "" entry "${entry}")
#string(REPLACE "-D" "" entry "${entry}")
# split into list, entries are expected to be whitespace separated
string(REPLACE " " ";" entryList "${entry}")
list(LENGTH entryList length)
if (${length} LESS 2)
continue()
endif ()
# get first element (name of entry), save it and remove it from the main list
list(GET entryList 0 entryName)
list(REMOVE_AT entryList 0)
if (entryName MATCHES "^MakeExport_SOURCES")
list(TRANSFORM entryList PREPEND "${prefixDirectory}/")
set(MakeExport_SOURCES ${entryList} PARENT_SCOPE)
set(foundSources true)
continue()
endif ()
if (entryName MATCHES "^MakeExport_MCU_Flags")
set(MakeExport_MCU_Flags ${entryList} PARENT_SCOPE)
set(foundMCUFlags true)
continue()
endif ()
if (entryName MATCHES "^MakeExport_DEFS")
set(MakeExport_DEFS ${entryList} PARENT_SCOPE)
set(foundDefs true)
continue()
endif ()
if (entryName MATCHES "^MakeExport_INCLUDES")
list(TRANSFORM entryList PREPEND "${prefixDirectory}/")
set(MakeExport_INCLUDES ${entryList} PARENT_SCOPE)
set(foundIncludes true)
continue()
endif ()
if (entryName MATCHES "^MakeExport_LDSCRIPT")
list(TRANSFORM entryList PREPEND "${prefixDirectory}/")
set(MakeExport_LDSCRIPT ${entryList} PARENT_SCOPE)
set(foundLDScript true)
continue()
endif ()
endforeach ()
if (NOT foundLDScript OR NOT foundIncludes OR NOT foundDefs OR NOT foundMCUFlags OR NOT foundSources)
message(FATAL_ERROR "CubeMX is not properly prepared with export statement")
endif ()
endfunction()

69
core/cmake/generateCubeMXTargets.cmake

@ -0,0 +1,69 @@
include_guard(GLOBAL)
include(${CMAKE_CURRENT_LIST_DIR}/extractBuildinfoFromMakefile.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/detectCompilerType.cmake)
# Extracts C and H files from cubemx makefile and creates three targets
# freertos - realtime os complete with heap, and port
# hel_headers - all cubemx generated headers without source files
# hal - complete hal with sources and headers
function(GENERATE_CUBEMX_TARGETS halDirectory generateFreertosTarget)
GET_CUBEMX_VARIABLES(${halDirectory})
DETECT_COMPILER_TYPE()
set(HalSources)
set(FreertosSources)
foreach (entry IN LISTS MakeExport_SOURCES)
if (entry MATCHES "Middlewares/Third_Party/FreeRTOS/Source/")
if (entry MATCHES "CMSIS_RTOS_V2")
# cmsis_os2.c is part of hal, but hides inside freertos sources
list(APPEND HalSources ${entry})
else ()
list(APPEND FreertosSources ${entry})
endif ()
else ()
list(APPEND HalSources ${entry})
endif ()
endforeach ()
set(HalIncludes)
set(FreertosIncludes)
foreach (entry IN LISTS MakeExport_INCLUDES)
if (entry MATCHES "Middlewares/Third_Party/FreeRTOS/Source")
if (entry MATCHES "CMSIS_RTOS_V2")
# cmsis_os2.c is part of hal, but hides inside freertos sources
list(APPEND HalIncludes ${entry})
else ()
list(APPEND FreertosIncludes ${entry})
endif ()
else ()
list(APPEND HalIncludes ${entry})
endif ()
endforeach ()
add_library(hal_headers INTERFACE)
target_include_directories(hal_headers INTERFACE ${HalIncludes})
target_compile_definitions(hal_headers INTERFACE ${MakeExport_DEFS})
# in testing a custom freertos version is built
# this must be excluded to avoid duplicate target issues
if (${generateFreertosTarget})
# freertos
add_library(freertos STATIC
${FreertosSources})
target_include_directories(freertos PUBLIC ${FreertosIncludes})
target_link_libraries(freertos PUBLIC hal_headers core)
endif ()
# complete hal
# following target doesn't compile on non embedded builds
# explicitly exclude from "All" build triggered by CTest
add_library(hal OBJECT
${HalSources}
)
target_link_libraries(hal PUBLIC hal_headers freertos)
set_target_properties(hal PROPERTIES
EXCLUDE_FROM_ALL TRUE
EXCLUDE_FROM_DEFAULT_BUILD TRUE)
endfunction()

63
core/cmake/setupEmbeddedBuild.cmake

@ -0,0 +1,63 @@
include_guard(GLOBAL)
include(${CMAKE_CURRENT_LIST_DIR}/extractBuildinfoFromMakefile.cmake)
# Invokes GET_CUBEMX_VARIABLES to retrieve defines and options from cubemx makefile
# assumes halDirectory is given as relative path seen from folder of top level script
# (linker needs full path to .ld file, so correct path assembly is critical)
function(SETUP_BUILD halDirectory firmwareName Cstandard CXXstandard)
GET_CUBEMX_VARIABLES(${halDirectory})
if (NOT DEFINED MakeExport_SOURCES OR NOT DEFINED MakeExport_MCU_Flags OR NOT DEFINED MakeExport_DEFS OR NOT DEFINED MakeExport_INCLUDES
OR NOT DEFINED MakeExport_LDSCRIPT)
message(FATAL_ERROR "Required variables are not in scope")
endif ()
set(Optimisaiton)
if (${CMAKE_BUILD_TYPE} STREQUAL "Release")
message("SETUP_BUILD configuring for Release.")
set(Optimisation -O2 -g)
elseif (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
message("SETUP_BUILD configuring for Debug.")
set(Optimisation -Og -g)
else ()
message(FATAL_ERROR "Unknown Buildtype")
endif ()
set(Specs --specs=nano.specs --specs=nosys.specs)
set(CFlags -std=${Cstandard} ${Optimisation} -fno-builtin-log)
set(CppFlags -ffunction-sections -fdata-sections -fno-common
-pedantic -Wall -Wextra
-Wno-unused-parameter -Wno-unused-variable
-fexec-charset=cp1252
${Specs})
set(CxxFlags -std=${CXXstandard} ${Optimisation}
-fno-exceptions -fno-rtti -fno-unwind-tables -Wno-register -fno-math-errno)
# generator expressions have some quirky behaviour when multiple lists separated by spaces
# are directly used, to avoid it just concat all lists beforehand
set(c_lang_options ${MakeExport_MCU_Flags} ${CFlags} ${CppFlags})
set(cpp_lang_options ${MakeExport_MCU_Flags} ${CxxFlags} ${CppFlags})
set(asm_lang_options -x assembler-with-cpp ${MakeExport_MCU_Flags} ${CFlags} ${CppFlags})
# must come before any target has been defined or else options won't stick
# all targets must be built with the same compile options or bad things happen
add_compile_options(
"$<$<COMPILE_LANGUAGE:C>:${c_lang_options}>"
"$<$<COMPILE_LANGUAGE:CXX>:${cpp_lang_options}>"
"$<$<COMPILE_LANGUAGE:ASM>:${asm_lang_options}>"
)
add_definitions(${MakeExport_DEFS})
# -u _printf_float removes a bunch of dependencies on various
# internal libc functions. Adding nosys specs would achieve the same thing
add_link_options(${Specs}
--static
-Wl,--gc-sections
-Wl,--print-memory-usage
-Wl,-Map=${firmwareName}.map
-u _printf_float
-Wl,--start-group -lc_nano -lgcc -lnosys -Wl,--end-group
-T${CMAKE_CURRENT_SOURCE_DIR}/${MakeExport_LDSCRIPT}
${MakeExport_MCU_Flags})
endfunction()

39
core/cmake/setupEmbeddedExtraFiles.cmake

@ -0,0 +1,39 @@
include_guard(GLOBAL)
cmake_minimum_required(VERSION 3.17)
# Adds generation of .bin and .list files
# Makes target generate as an .elf file
# Invokes static library analyzer see core issue #3
# assumes CMAKE_OBJCOPY, CMAKE_OBJDUMP is correctly setup globally
# TargetName: your target from add_executable
function(SETUP_EXTRA_FILE_GENERATION TargetName)
# make it so firmware generates with .elf file ending
set_target_properties(
${TargetName}
PROPERTIES
OUTPUT_NAME ${TargetName}
SUFFIX ".elf"
)
add_custom_command(
TARGET ${TargetName}
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} ARGS -O ihex ${CMAKE_BINARY_DIR}/${TargetName}.elf ${CMAKE_BINARY_DIR}/${TargetName}.bin
BYPRODUCTS ${CMAKE_BINARY_DIR}/${TargetName}.bin
)
add_custom_command(
TARGET ${TargetName}
POST_BUILD
COMMAND ${CMAKE_OBJDUMP} ARGS -S ${CMAKE_BINARY_DIR}/${TargetName}.elf > ${CMAKE_BINARY_DIR}/${TargetName}.list
BYPRODUCTS ${CMAKE_BINARY_DIR}/${TargetName}.list
)
add_custom_command(
TARGET ${TargetName}
POST_BUILD
COMMAND ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../scripts/checkStaticLibraries.py ARGS ${CMAKE_BINARY_DIR}
)
endfunction()

61
core/inc/core/BuildConfiguration.hpp

@ -0,0 +1,61 @@
#pragma once
#include "core/BuildConfigurationTypes.h"
namespace core
{
/**
* @brief Provides constexpr bools for usage in a bit more macro-free code
* Using the bools and if constexpr () is a more modern way of splitting
* code paths between the different builds.
* Macros are provided via compiler -D command.
* Multiple definitions are caught at compile time.
*
*/
struct BuildConfiguration
{
#if IS_EMBEDDED_BUILD()
static constexpr bool IsEmbeddedBuild = true;
#else
static constexpr bool IsEmbeddedBuild = false;
#endif
#if IS_TESTING_BUILD()
static constexpr bool IsTestingBuild = true;
#else
static constexpr bool IsTestingBuild = false;
#endif
#if IS_FUZZING_BUILD()
static constexpr bool IsFuzzingBuild = true;
#else
static constexpr bool IsFuzzingBuild = false;
#endif
#if IS_EMULATOR_BUILD()
static constexpr bool IsEmulatorBuild = true;
#else
static constexpr bool IsEmulatorBuild = false;
#endif
#if defined(BUILDCONFIG_DEBUG)
static constexpr bool IsDebugBuild = true;
#else
static constexpr bool IsDebugBuild = false;
#endif
#if defined(BUILDCONFIG_RELEASE)
static constexpr bool IsReleaseBuild = true;
#else
static constexpr bool IsReleaseBuild = false;
#endif
static constexpr void checkBuild()
{
// sanity checks for macros
static_assert(((IsEmbeddedBuild ? 1 : 0) + (IsTestingBuild ? 1 : 0) +
(IsFuzzingBuild ? 1 : 0) + (IsEmulatorBuild ? 1 : 0)) == 1);
static_assert(((IsDebugBuild ? 1 : 0) + (IsReleaseBuild ? 1 : 0)) == 1);
}
};
} // namespace core

24
core/inc/core/BuildConfigurationTypes.h

@ -0,0 +1,24 @@
#pragma once
#define BUILDTYPE_EMBEDDED 2
#define BUILDTYPE_TESTING 3
#define BUILDTYPE_FUZZING 4
#define BUILDTYPE_EMULATOR 5
#ifndef BUILDTYPE
#error "BUILDTYPE not defined"
#endif
#if !((BUILDTYPE == BUILDTYPE_EMBEDDED) || (BUILDTYPE == BUILDTYPE_TESTING) || \
(BUILDTYPE == BUILDTYPE_FUZZING) || (BUILDTYPE == BUILDTYPE_EMULATOR))
#error "Unknown BUILDTYPE"
#endif
// please don't use BUILDTYPE == ... by yourself but rather the following macros
// the preprocessor doesn't consider a missing BUILDTYPE in a comparison about BUILDTYPE an
// error..... so when you can use the following functions, you can be sure that BUILDTYPE existance
// and validity is checked
#define IS_EMBEDDED_BUILD() (BUILDTYPE == BUILDTYPE_EMBEDDED)
#define IS_TESTING_BUILD() (BUILDTYPE == BUILDTYPE_TESTING)
#define IS_FUZZING_BUILD() (BUILDTYPE == BUILDTYPE_FUZZING)
#define IS_EMULATOR_BUILD() (BUILDTYPE == BUILDTYPE_EMULATOR)

38
core/inc/core/SafeAssert.h

@ -0,0 +1,38 @@
#pragma once
#include "BuildConfigurationTypes.h"
#include <stdbool.h> // NOLINT
/**
* @brief Build configuration independant assert
*
*/
/**
* @brief Asserts the given condition, aborts programm if false
* When compiled for embedded, disables interrupts and enters endless loop
* locking up the scheduler until the watchdog resets the device.
* Throws std::runtime_exception in testing and fuzzing builds
*
* May be used in C or C++ Code.
* @param condition
*/
#ifdef __cplusplus
extern "C"
{
#endif
void SafeAssert_Aux(bool condition, int line, const char *file);
#ifdef __cplusplus
}
#endif
#if !IS_EMBEDDED_BUILD()
#define SafeAssert(cond) SafeAssert_Aux(cond, __LINE__, __FILE__) // NOLINT
#else
// avoids compiling in a bunch of path strings for embedded
#define SafeAssert(cond) SafeAssert_Aux(cond, 0, "NOVAL") // NOLINT
#endif
#define UAVCAN_ASSERT(cond) SafeAssert(cond)

82
core/inc/core/build_information.hpp

@ -0,0 +1,82 @@
#pragma once
#include "BuildConfiguration.hpp"
#include "core/Version.h" // generated
#include <cstdint>
namespace core
{
struct BuildInformationTestingValues
{
static constexpr uint32_t CommitHashShort = 42424242;
static constexpr bool IsDirty = true;
static constexpr bool IsDebugBuild = false;
static constexpr char BuildTime[] = "testing_value_time";
static constexpr char CommitHashLong[] = "testing_value_commit";
static constexpr char BranchName[] = "testing_value_brach";
};
class BuildInformationHelper
{
public:
static constexpr uint32_t getCommitHashShort()
{
if constexpr (BuildConfiguration::isEmbeddedBuild)
{
return BUILD_INFO_COMMIT_SHORT;
}
if constexpr (BuildConfiguration::isTestingBuild || BuildConfiguration::isFuzzingBuild)
{
return BuildInformationTestingValues::CommitHashShort;
}
}
static constexpr bool getIsDirty()
{
if constexpr (BuildConfiguration::isEmbeddedBuild)
{
return BUILD_INFO_IS_DIRTY > 0;
}
if constexpr (BuildConfiguration::isTestingBuild || BuildConfiguration::isFuzzingBuild)
{
return BuildInformationTestingValues::IsDirty;
}
}
// string literals can't easily be used as template parameters
// so secondary step is neccessary
static constexpr const char MacroBuildTime[] = BUILD_INFO_TIME;
static constexpr const char MacroCommitHashLong[] = BUILD_INFO_COMMIT_LONG;
static constexpr const char MacroBranchName[] = BUILD_INFO_BRANCH;
template <const char *MacroValue, const char *FixedValue>
static constexpr const char *get()
{
if constexpr (BuildConfiguration::isEmbeddedBuild)
{
return MacroValue;
}
if constexpr (BuildConfiguration::isTestingBuild || BuildConfiguration::isFuzzingBuild)
{
return FixedValue;
}
}
};
class BuildInformation
{
public:
static constexpr uint32_t CommitHashShort = BuildInformationHelper::getCommitHashShort();
static constexpr bool IsDirty = BuildInformationHelper::getIsDirty();
static constexpr bool IsDebugBuild = BuildConfiguration::isDebugBuild;
static constexpr const char *BuildTime =
BuildInformationHelper::get<BuildInformationHelper::MacroBuildTime,
BuildInformationTestingValues::BuildTime>();
static constexpr const char *CommitHashLong =
BuildInformationHelper::get<BuildInformationHelper::MacroCommitHashLong,
BuildInformationTestingValues::CommitHashLong>();
static constexpr const char *BranchName =
BuildInformationHelper::get<BuildInformationHelper::MacroBranchName,
BuildInformationTestingValues::BranchName>();
};
} // namespace core

24
core/inc/core/fault_handler.h

@ -0,0 +1,24 @@
#pragma once
#include "core/BuildConfigurationTypes.h"
/**
* @brief Walks up the stack and looks for the last executed code's address.
* Used to retrieve from where the chip hardfaults. Do not use outside of
* crash handlers. __attribute__((naked)) is going to shoot you into the foot.
*
*/
#ifdef __cplusplus
extern "C"
{
#endif
#if IS_EMBEDDED_BUILD()
void faultHandler(void) __attribute__((naked));
#else
void faultHandler(void);
#endif
#ifdef __cplusplus
}
#endif

14
core/inc/core/hash.hpp

@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace core::hash
{
static constexpr uint64_t HASH_SEED = 0xcbf29ce484222325;
static constexpr uint64_t MagicPrime = 0x00000100000001b3;
uint64_t fnvWithSeed(uint64_t hash, const uint8_t *data, const uint8_t *const dataEnd);
static constexpr uint64_t FirmwareHashTestingValue{0xDEADBEEFC0FFEE};
uint64_t computeFirmwareHash();
} // namespace bus_node_base

6
core/mk/CubeMXMakefileInstrumentation.mk

@ -0,0 +1,6 @@
export:
@printf 'MakeExport_SOURCES $(C_SOURCES) $(ASM_SOURCES)\n'
@printf 'MakeExport_MCU_Flags $(MCU)\n'
@printf 'MakeExport_DEFS $(AS_DEFS) $(C_DEFS)\n'
@printf 'MakeExport_INCLUDES $(AS_INCLUDES) $(C_INCLUDES)\n'
@printf 'MakeExport_LDSCRIPT $(LDSCRIPT)\n'

36
core/mk/gcc-config.mk

@ -1,36 +0,0 @@
##
## This file is part of the libopencm3 project.
##
## Copyright (C) 2014 Frantisek Burian <BuFran@seznam.cz>
##
## This library is free software: you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This library is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this library. If not, see <http://www.gnu.org/licenses/>.
##
###############################################################################
# The support makefile for GCC compiler toolchain, the rules part.
#
# please read mk/README for specification how to use this file in your project
PREFIX ?= arm-none-eabi
CC := $(PREFIX)-gcc
CXX := $(PREFIX)-g++
LD := $(PREFIX)-gcc
ar := $(PREFIX)-ar
as := $(PREFIX)-as
OBJCOPY := $(PREFIX)-objcopy
OBJDUMP := $(PREFIX)-objdump
GDB := $(PREFIX)-gdb
SIZE := $(PREFIX)-size

34
core/mk/include.mk

@ -1,34 +0,0 @@
ifeq ($(RELEASE),1)
CONFIGURATION := release
RELEASE_OPT ?= 3
OPTIMIZATION := -O$(RELEASE_OPT) -g
else
CONFIGURATION := debug
OPTIMIZATION := -Og -g
DEFS += DEBUG
endif
ROOTDIR := $(dir $(firstword $(MAKEFILE_LIST)))
BASEDIR := $(dir $(lastword $(MAKEFILE_LIST)))../
MXDIR := $(ROOTDIR)cubemx/
OUTPUT_DIR := $(ROOTDIR)build/$(CONFIGURATION)/
OBJDIR := $(OUTPUT_DIR)/obj
INCDIRS += \
$(BASEDIR)include \
SOURCES += \
$(BASEDIR)src/abi.cpp \
$(BASEDIR)src/std.cpp
# CubeMX
include cubemx/Makefile
SOURCES += $(foreach source,$(C_SOURCES) $(ASM_SOURCES),$(MXDIR)$(source))
INCDIRS += $(C_INCLUDES:-I%=$(MXDIR)%)
INCDIRS += $(AS_INCLUDES:-I%=$(MXDIR)%)
LDSCRIPT := $(MXDIR)$(LDSCRIPT)
# Include actual rules
include $(BASEDIR)mk/rules.mk

100
core/mk/rules.mk

@ -1,100 +0,0 @@
STFLASH = $(shell which st-flash)
JFLASH = $(shell which JLinkExe)
# Object files
TMP1 := $(patsubst %.c, $(OBJDIR)/%.o, $(SOURCES))
TMP2 := $(patsubst %.cpp, $(OBJDIR)/%.o, $(TMP1))
TMP3 := $(patsubst %.cxx, $(OBJDIR)/%.o, $(TMP2))
OBJS := $(patsubst %.s, $(OBJDIR)/%.o, $(TMP3))
BINARY := $(OUTPUT_DIR)$(TARGET)
# C/C++ standards
CSTD ?= c11
CXXSTD ?= c++17
# Compiler flags
SPECS ?= --specs=nano.specs
CFLAGS += -std=$(CSTD) $(OPTIMIZATION) -fno-builtin-log
CPPFLAGS += -ffunction-sections -fdata-sections -fno-common
CPPFLAGS += -pedantic -Wall -Wextra
CPPFLAGS += -Wno-unused-parameter -Wno-unused-variable
CPPFLAGS += -fexec-charset=cp1252
CPPFLAGS += $(DEFS:%=-D%)
CPPFLAGS += $(C_DEFS) $(AS_DEFS)
CPPFLAGS += $(INCDIRS:%=-I%)
CPPFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
CPPFLAGS += $(SPECS)
CXXFLAGS += -std=$(CXXSTD) $(OPTIMIZATION)
CXXFLAGS += -fno-exceptions -fno-rtti -fno-unwind-tables -Wno-register -fno-math-errno
# Linker flags
LDFLAGS += $(SPECS)
LDFLAGS += --static
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,--print-memory-usage
LDFLAGS += -Wl,-Map=$(BINARY).map
LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
# Be silent per default, but 'make V=1' will show all compiler calls.
ifneq ($(V),1)
Q := @
endif
# Toolchain stuff
include $(BASEDIR)mk/gcc-config.mk
# Rules
.PHONY: clean all stflash jflash
.SECONDARY:
all: $(BINARY).bin $(BINARY).list
print-%:
@echo $*=$($*)
%.bin: %.elf
@printf " OBJCOPY $@\n"
$(Q)$(OBJCOPY) -Obinary $< $@
%.list: %.elf
@printf " OBJDUMP $@\n"
$(Q)$(OBJDUMP) -S $< > $@
%.elf: $(OBJS) $(LIBDEPS)
@printf " LD $@\n"
$(Q)$(LD) $(OBJS) $(LDLIBS) $(LDFLAGS) -T$(LDSCRIPT) $(MCU) -o $@
$(OBJDIR)/%.o: %.s
@printf " AS $<\n"
@mkdir -p $(dir $@)
$(Q)$(CC) -x assembler-with-cpp $(CFLAGS) $(CPPFLAGS) $(MCU) -c $< -o $@
$(OBJDIR)/%.o: %.c
@printf " CC $<\n"
@mkdir -p $(dir $@)
$(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(MCU) -c $< -o $@
$(OBJDIR)/%.o: %.cpp
@printf " CXX $<\n"
@mkdir -p $(dir $@)
$(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(MCU) -c $< -o $@
$(OBJDIR)/%.o: %.cxx
@printf " CXX $<\n"
@mkdir -p $(dir $@)
$(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(MCU) -c $< -o $@
clean:
@$(RM) -Rf build dsdlc_generated
stflash:$(BINARY).bin
$(STFLASH) $(FLASHSIZE) write $(BINARY).bin 0x8000000
jflash: $(BINARY).bin
$(JFLASH) -Autoconnect 1 -Device $(DEVICE) -If SWD -Speed 4000 -CommandFile flash_commands.jlink
-include $(OBJS:.o=.d)

1
core/requirements.txt

@ -0,0 +1 @@
gitpython

75
core/scripts/checkStaticLibraries.py

@ -0,0 +1,75 @@
#!/usr/bin/python3
from subprocess import Popen, PIPE
import re
import glob
import os
import sys
def removeNoneAcii(str):
return ''.join([i if ord(i) < 128 else ' ' for i in str])
def analyzeAndCheckStaticLib(filepath):
# start nm and let it retrieve the names of all symbols
process = Popen(["nm", filepath], stdout=PIPE)
(output, err) = process.communicate()
exit_code = process.wait()
if exit_code != 0:
print("error calling nm command")
exit(1)
output = removeNoneAcii(str(output))
entries = output.split(sep="\\n")
# extract letter (type) and function name from entries
# e.g. '00000001 T Error_Handler' first match is T, second one is Error_Handler
# all undefined functions without number are not matched and don't matter
# entry names starting with $ also dont matter
extracted = []
for e in entries:
res = re.findall(r"\d+?\s(\w)\s([\w\d_]+)\b", e)
if len(res) == 1:
type = res[0][0]
name = res[0][1]
extracted.append({'type': type, 'name': name})
# else:
# print(e)
# extract all duplicate symbols and their type
duplicates = {}
for i in extracted:
for j in extracted:
if i is j:
continue
if i['name'] == j['name']:
if not i['name'] in duplicates:
duplicates[i['name']] = set()
duplicates[i['name']].add(str(i['type']).lower())
duplicates[i['name']].add(str(j['type']).lower())
foundDuplicatesWithDifferentTypes = False
for e in duplicates:
if len(duplicates[e]) > 1:
if not foundDuplicatesWithDifferentTypes:
print("Analyzing: " + str(os.path.basename(filepath)))
print("Symbol " + str(e) + "is defined multiple times with types: " + str(duplicates[e]))
foundDuplicatesWithDifferentTypes = True
if foundDuplicatesWithDifferentTypes:
print("-------------------\n"
"The Linker will not necessarily select the appropriate function from a static library "
"without you defining --whole-archive.\nAs CMake doesn't have a convenient way of doing this. Please "
"change your library type from STATIC to OBJECT so you don't bugs due to wrongly linked applications. "
"See core issue #3.\n"
"-----------------")
exit(1)
if __name__ == "__main__":
if len(sys.argv) != 2:
raise "not enough parameters"
path = sys.argv[1]
text_files = glob.glob(path + "/**/*.a", recursive=True)
for file in text_files:
analyzeAndCheckStaticLib(file)
print("Checked " + str(len(text_files)) + " static libraries, all ok.")

60
core/scripts/generateVersionHeader.py

@ -0,0 +1,60 @@
#!/usr/bin/python3
import sys
import datetime
import git # pip3 install gitpython
if len(sys.argv) != 3:
raise "not enough parameters"
repoPath = sys.argv[1]
versionHeaderPath = sys.argv[2]
repo = git.Repo(repoPath, search_parent_directories=True)
#if len(repo.submodules) == 0:
# print("Tested git repository has no submodules, path is not pointing to the main repository")
# exit(1)
commitHashLong = str(repo.head.object.hexsha)
commitHashShort = commitHashLong[:8]
isDirty = repo.is_dirty()
branch = "detached_head"
try:
branch = repo.active_branch.name
except:
print("git repository is in detached head state")
# build time in 15 minute intervals to avoid new compilation every time
now = datetime.datetime.now()
currentTime = "%d-%02d-%02dT%02d:%02d:00" % (now.year, now.month, now.day, now.hour, now.minute - now.minute % 15)
fileContent = '#define BUILD_INFO_COMMIT_SHORT 0x' + commitHashShort + '\n'
if isDirty:
fileContent += '#define BUILD_INFO_IS_DIRTY true\n'
else:
fileContent += '#define BUILD_INFO_IS_DIRTY false\n'
fileContent += '#define BUILD_INFO_TIME "' + currentTime + '"\n'
fileContent += '#define BUILD_INFO_COMMIT_LONG "' + commitHashLong + '"\n'
fileContent += '#define BUILD_INFO_BRANCH "' + branch + '"\n'
# read back file and compare, don't overwrite if nothing changes
# due to time being included, something always changes but in case
# this goes away, we can save some compile runs
doRegen = True
try:
with open(versionHeaderPath, mode='r') as oldFile:
content = oldFile.read()
if content == fileContent:
print("No change detected")
doRegen = False
else:
print("Version info changed")
except:
print("Old file not found")
if doRegen:
with open(versionHeaderPath, mode='w+') as newFile:
newFile.write(fileContent)
print("Regenerated")
exit(0)

34
core/src/SafeAssert.cpp

@ -0,0 +1,34 @@
#include "core/SafeAssert.h"
#include "core/BuildConfiguration.hpp"
#if IS_EMBEDDED_BUILD()
#include <FreeRTOS.h>
#include <task.h>
void SafeAssert_Aux(bool condition, int line, const char *file)
{
if (!condition)
{
taskDISABLE_INTERRUPTS();
if constexpr (core::BuildConfiguration::IsDebugBuild)
{
__asm("bkpt");
}
for (;;)
{
}
}
}
#else
#include <sstream>
#include <stdexcept>
void SafeAssert_Aux(bool condition, int line, const char *file)
{
if (!condition)
{
std::stringstream ss;
ss << "Assertion Failed " << file << ":" << line;
throw std::runtime_error(ss.str());
}
}
#endif

28
core/src/abi.cpp

@ -1,20 +1,23 @@
#include "core/BuildConfiguration.hpp"
#include <cstddef>
#if FW_USE_RTOS
/**
* @brief Links together C++ and FreeRTOS heap handler. Also sets up C++'s guts for embedded
* environment. WARNING Can compile on amd64 without the "#if" guard but will cause
* SEGFAULT when a class is used. So keep them!
*
*/
#if IS_EMBEDDED_BUILD()
extern "C"
{
#include <FreeRTOS.h>
#include <task.h>
}
#endif
void *operator new(std::size_t size)
{
#if FW_USE_RTOS
return pvPortMalloc(size);
#else
return reinterpret_cast<void *>(0xffffffff);
#endif
}
void *operator new[](std::size_t size)
@ -24,13 +27,7 @@ void *operator new[](std::size_t size)
void operator delete(void *ptr)
{
#if FW_USE_RTOS
vPortFree(ptr);
#else
while (1)
{
}
#endif
}
void operator delete(void *ptr, unsigned int)
@ -110,7 +107,8 @@ extern "C"
// some functions in cmath follow the compiler flag -fno-math-errno
// but not so for std::pow...
void __errno() {
void __errno()
{
}
}
}
#endif

76
core/src/fault_handler.cpp

@ -0,0 +1,76 @@
#include "core/fault_handler.h"
#include "core/BuildConfiguration.hpp"
#include <stdint.h>
#if IS_EMBEDDED_BUILD()
/* The fault handler implementation calls a function called
prvGetRegistersFromStack(). */
void __attribute__((aligned(4))) faultHandler(void)
{
__asm volatile(" mrs r0, msp \n"
" mov r1, lr \n"
" mov r2, #4 \n"
" tst r1, r2 \n"
" beq prvGetRegistersFromStack \n"
" mrs r0, psp \n"