Skip to content
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fd4174c
Refactor protections around a static rule engine
jorgesg82 Apr 5, 2026
eb732a8
Decouple diagnostics and fault broadcasting
jorgesg82 Apr 5, 2026
33b66be
Harden simulator and time service support
jorgesg82 Apr 5, 2026
a20de40
Hooked to Board init
jorgesg82 Apr 5, 2026
c0fb7dd
Harden fault, diagnostic, and reporter subsystems
jorgesg82 Apr 8, 2026
8d91098
Update peripheral callsites to PANIC/FAULT/WARNING/INFO macros
jorgesg82 Apr 8, 2026
d30db85
Update build, includes, and infrastructure for PM-no-ETH refactoring
jorgesg82 Apr 8, 2026
ee51f2d
Add diagnostics and fault controller test coverage
jorgesg82 Apr 8, 2026
17a7862
Add protections-and-diagnostics architecture guide
jorgesg82 Apr 8, 2026
5b4278e
Constrain global fault entry API
jorgesg82 Apr 17, 2026
6979532
Make Board take an explicit fault policy type
jorgesg82 Apr 17, 2026
7a29c2c
Make time_accumulation use scheduler time
jorgesg82 Apr 17, 2026
0db347c
applied formatter
jorgesg82 Apr 22, 2026
a4a2070
added changeset
jorgesg82 Apr 22, 2026
89cd0e8
Remove legacy bootstrap entry points and compatibility shims
jorgesg82 Apr 22, 2026
b9445da
Preserve early faults across bootstrap and replay retained diagnostics
jorgesg82 Apr 22, 2026
dba8cea
Document the updated protections and fault contract
jorgesg82 Apr 22, 2026
4a479ec
Fix CI regressions and address Copilot review feedback
jorgesg82 Apr 22, 2026
5c4e941
Make simulator MPU support portable on macOS
jorgesg82 Apr 22, 2026
b3fdede
fix RTC
jorgesg82 Apr 23, 2026
fc2ad77
feat(Protections)!:Make protections declared in compile time
FoniksFox Apr 25, 2026
922ef0a
fix(SPI Tests): Reset SPI entirely on each init so that tests can be …
FoniksFox Apr 25, 2026
5309421
feat(Protections)!: integrate compile-time protections with Board
jorgesg82 Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changesets/pm-no-eth-major.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
release: major
summary: Redesign fault handling, protections, and Board bootstrap around explicit fault policies

This PR changes the public integration contract for applications built on ST-LIB.

Breaking changes:

- `Board` now takes the fault policy type as its first template parameter.
- The global `FAULT` runtime is owned exclusively by `FaultController`.
- User state machines are now nested under the global `OPERATIONAL` state through `FaultPolicy` or `FaultPolicyNoMachine`.
- Protections now use `ProtectionEngine` and `Protections::Rules::*`; the previous `ProtectionManager` and boundary split is no longer the active model.
- Runtime reporting is unified under `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`.
- The real bootstrap path is `Board::init()`. Legacy `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, and `STLIB_HIGH::start()` must not be used as the integration path.

Migration notes:

- Declare the board as `Board<YourFaultPolicy, ...>`.
- Use `FaultPolicy<app_machine, on_fault_enter>` when you want an operational state machine nested under the global runtime.
- Use `FaultPolicyNoMachine<on_fault_enter>` when you only need a fault-entry callback.
- Use `DefaultFaultPolicy` when you want neither an operational machine nor a fault-entry callback.
- In the main loop, drive the runtime through `FaultController::check_transitions()`, `ProtectionEngine::evaluate()`, and `Diagnostics::Hub::flush()`.
18 changes: 8 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ set(HALAL_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MDMA/MDMA.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/SPIOrder.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/SPI/SPI2.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/DMA/DMA2.cpp
Expand All @@ -289,6 +290,10 @@ set(HALAL_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/UART/UART.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/EXTI/EXTI.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/FMAC/FMAC.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Flash/Flash.cpp
Expand All @@ -306,7 +311,6 @@ set(HALAL_C_ETH_CORE)
set(HALAL_CPP_ETH_CORE
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/IPV4/IPV4.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MAC/MAC.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/SNTP/SNTP.cpp
)

Expand Down Expand Up @@ -368,7 +372,6 @@ set(STLIB_LOW_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/DigitalOutput/DigitalOutput.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Math/Math.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ST-LIB_LOW.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sd/Sd.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/DigitalSensor/DigitalSensor.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/SensorInterrupt/SensorInterrupt.cpp
Expand All @@ -380,17 +383,15 @@ set(STLIB_LOW_CPP_NO_ETH

set(STLIB_HIGH_C_ETH)

set(STLIB_HIGH_CPP_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/Boundary.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp
)
set(STLIB_HIGH_CPP_ETH)

set(STLIB_HIGH_C_NO_ETH)

set(STLIB_HIGH_CPP_NO_ETH
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashStorer.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp
${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp
)

# ============================
Expand Down Expand Up @@ -426,9 +427,6 @@ add_library(${STLIB_LIBRARY} OBJECT
$<$<BOOL:${CMAKE_CROSSCOMPILING}>:${STLIB_HIGH_CPP_NO_ETH}>
$<$<AND:$<BOOL:${CMAKE_CROSSCOMPILING}>,$<BOOL:${USE_ETHERNET}>>:${STLIB_HIGH_C_ETH}>
$<$<AND:$<BOOL:${CMAKE_CROSSCOMPILING}>,$<BOOL:${USE_ETHERNET}>>:${STLIB_HIGH_CPP_ETH}>

$<$<BOOL:${CMAKE_CROSSCOMPILING}>:${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB.cpp>

$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/Scheduler.cpp>
$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/TimerDomain/TimerDomain.cpp>
$<$<NOT:$<BOOL:${CMAKE_CROSSCOMPILING}>>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp>
Expand Down
7 changes: 7 additions & 0 deletions Inc/C++Utilities/CppImports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@
#include <ranges>
#include <cstdarg>
#include <stdarg.h>
#include <bit>
#include <concepts>
#include <expected>
#include <new>
#include <optional>
#include <type_traits>
#include <variant>
8 changes: 8 additions & 0 deletions Inc/C++Utilities/CppUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ namespace chrono = std::chrono;
namespace placeholders = std::placeholders;

using std::array;
using std::byte;
using std::construct_at;
using std::destroy_at;
using std::expected;
using std::function;
using std::hash;
using std::integral_constant;
Expand All @@ -17,6 +21,7 @@ using std::make_unique;
using std::map;
using std::move;
using std::nullopt;
using std::optional;
using std::pair;
using std::queue;
using std::reference_wrapper;
Expand All @@ -29,6 +34,9 @@ using std::stack;
using std::string;
using std::stringstream;
using std::to_string;
using std::unexpected;
using std::unique_ptr;
using std::unordered_map;
using std::variant;
using std::vector;
using std::visit;
2 changes: 1 addition & 1 deletion Inc/C++Utilities/StaticVector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ template <typename T, size_t Capacity> class StaticVector {

constexpr void push_back(const T& value) {
if (size_ >= Capacity) {
ErrorHandler("StaticVector capacity exceeded");
PANIC("StaticVector capacity exceeded");
return;
}
data[size_] = value;
Expand Down
10 changes: 0 additions & 10 deletions Inc/HALAL/HALAL.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,3 @@
#include "HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp"
#include "HALAL/Services/Communication/SNTP/SNTP.hpp"
#endif

namespace HALAL {

#ifdef STLIB_ETH
void start(MAC mac, IPV4 ip, IPV4 subnet_mask, IPV4 gateway, UART::Peripheral& printf_peripheral);
#else
void start(UART::Peripheral& printf_peripheral);
#endif

} // namespace HALAL
4 changes: 2 additions & 2 deletions Inc/HALAL/Models/DMA/DMA2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ struct DMADomain {

instances[i].dma = {};
if (stream == Stream::none) {
ErrorHandler("DMA stream must be selected before init");
PANIC("DMA stream must be selected before init");
continue;
}

Expand All @@ -507,7 +507,7 @@ struct DMADomain {

if (HAL_DMA_Init(&instances[i].dma) != HAL_OK) {
instances[i].dma = {};
ErrorHandler("DMA Init failed");
PANIC("DMA Init failed");
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions Inc/HALAL/Models/MDMA/MDMA.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class MDMA {

void init_node(void* src, void* dst, size_t size) {
if (size == 0) {
ErrorHandler("MDMA: zero-length transfer is invalid");
PANIC("MDMA: zero-length transfer is invalid");
return;
}

Expand Down Expand Up @@ -220,7 +220,7 @@ class MDMA {
nodeConfig.Init.BufferTransferLength = buf_len;

if (HAL_MDMA_LinkedList_CreateNode(&node, &nodeConfig) != HAL_OK) {
ErrorHandler("Error creating linked list in MDMA");
PANIC("Error creating linked list in MDMA");
}

// HAL_MDMA_LinkedList_CreateNode only sets the request field in CTBR;
Expand Down
74 changes: 55 additions & 19 deletions Inc/HALAL/Models/MPU.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
// Defines for attributes
// Note1: Variables declared with these attributes will likely not be initialized by the startup
// Note2: These attributes can only be used for static/global variables
#ifdef SIM_ON
#define D1_NC
#define D2_NC
#define D3_NC
#define D1_C
#define D2_C
#define D3_C
#define RAM_CODE
#else
#define D1_NC __attribute__((section(".mpu_ram_d1_nc.user")))
#define D2_NC __attribute__((section(".mpu_ram_d2_nc.user")))
#define D3_NC __attribute__((section(".mpu_ram_d3_nc.user")))
Expand All @@ -53,6 +62,7 @@

// Define for RAM code
#define RAM_CODE __attribute__((section(".ram_code")))
#endif

// Memory Bank Symbols from Linker
extern "C" const char __itcm_base;
Expand All @@ -78,6 +88,17 @@ extern "C" const char __mpu_d2_nc_end;
extern "C" const char __mpu_d3_nc_start;
extern "C" const char __mpu_d3_nc_end;

inline constexpr std::array<std::size_t, 6> mpu_supported_alignments = {32, 16, 8, 4, 2, 1};

consteval bool is_supported_mpu_alignment(std::size_t alignment) {
for (std::size_t candidate : mpu_supported_alignments) {
if (candidate == alignment) {
return true;
}
}
return false;
}

template <typename T>
concept mpu_buffer_request = requires(typename T::domain d) {
typename T::buffer_type;
Expand Down Expand Up @@ -133,7 +154,7 @@ struct MPUDomain {
"Requested type has alignment greater than cache line size (32 bytes)."
);
static_assert(
std::ranges::find(alignments, alignof(T)) != std::ranges::end(alignments),
is_supported_mpu_alignment(alignof(T)),
"Requested type has alignment not supported by MPU buffer system."
);
}
Expand All @@ -143,14 +164,12 @@ struct MPUDomain {
* @param entry The Entry with all buffer requirements specified.
*/
consteval Buffer(Entry entry) : e(entry) {
static_assert(
entry.alignment <= 32,
"Requested alignment greater than cache line size (32 bytes)."
);
static_assert(
std::ranges::find(alignments, entry.alignment) != std::ranges::end(alignments),
"Requested alignment not supported by MPU buffer system."
);
if (entry.alignment > 32) {
compile_error("Requested alignment greater than cache line size (32 bytes).");
}
if (!is_supported_mpu_alignment(entry.alignment)) {
compile_error("Requested alignment not supported by MPU buffer system.");
}
// Verify size matches sizeof(T)
if (entry.size_in_bytes != sizeof(T)) {
compile_error("Entry size_in_bytes must match sizeof(T)");
Expand Down Expand Up @@ -227,7 +246,7 @@ struct MPUDomain {
uint32_t offsets_c[3] = {}; // D1, D2, D3
uint32_t assigned_offsets[N];

for (size_t align : alignments) {
for (size_t align : mpu_supported_alignments) {
for (size_t i = 0; i < N; i++) {
if (entries[i].alignment == align) {
size_t d_idx = static_cast<size_t>(entries[i].memory_domain) - 1;
Expand Down Expand Up @@ -260,26 +279,33 @@ struct MPUDomain {
void* ptr;
std::size_t size;

template <mpu_buffer_request auto& Target, typename... Args>
auto& construct(Args&&... args) {
using T = typename std::remove_cvref_t<decltype(Target)>::buffer_type;
template <auto& Target, typename... Args> auto& construct(Args&&... args) {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
using T = typename Request::buffer_type;
return *new (ptr) T(std::forward<Args>(args)...);
}

template <mpu_buffer_request auto& Target> auto* as() {
using T = typename std::remove_cvref_t<decltype(Target)>::buffer_type;
template <auto& Target> auto* as() {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
using T = typename Request::buffer_type;
return static_cast<T*>(ptr);
}
};

template <typename Board, mpu_buffer_request auto& Target, typename... Args>
template <typename Board, auto& Target, typename... Args>
static auto& construct(Args&&... args) {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
return Board::template instance_of<Target>().template construct<Target>(
std::forward<Args>(args)...
);
}

template <typename Board, mpu_buffer_request auto& Target> static auto* as() {
template <typename Board, auto& Target> static auto* as() {
using Request = std::remove_cvref_t<decltype(Target)>;
static_assert(mpu_buffer_request<Request>, "Target must be a valid MPUDomain buffer");
return Board::template instance_of<Target>().template as<Target>();
}

Expand All @@ -289,6 +315,17 @@ struct MPUDomain {
static constexpr auto Sizes = calculate_total_sizes(cfgs);

// Sections defined in Linker Script (aligned to 32 bytes just in case)
#ifdef SIM_ON
alignas(32
) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1];
alignas(32) static inline uint8_t d1_c_buffer[Sizes.d1_c_total > 0 ? Sizes.d1_c_total : 1];
alignas(32
) static inline uint8_t d2_nc_buffer[Sizes.d2_nc_total > 0 ? Sizes.d2_nc_total : 1];
alignas(32) static inline uint8_t d2_c_buffer[Sizes.d2_c_total > 0 ? Sizes.d2_c_total : 1];
alignas(32
) static inline uint8_t d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1];
alignas(32) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1];
#else
__attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32
) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1];
__attribute__((section(".ram_d1.buffer"))) alignas(32
Expand All @@ -303,6 +340,7 @@ struct MPUDomain {
) static inline uint8_t d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1];
__attribute__((section(".ram_d3.buffer"))) alignas(32
) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1];
#endif

static void init() {
HAL_MPU_Disable();
Expand Down Expand Up @@ -353,8 +391,6 @@ struct MPUDomain {
};

private:
static constexpr std::size_t alignments[6] = {32, 16, 8, 4, 2, 1};

static void configure_dynamic_region(uintptr_t start, uintptr_t end, uint8_t region_num) {
if (end <= start)
return;
Expand Down
2 changes: 1 addition & 1 deletion Inc/HALAL/Models/MPUManager/MPUManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MPUManager {
no_cached_ram_occupied_bytes = no_cached_ram_occupied_bytes + size;
if (no_cached_ram_occupied_bytes > NO_CACHED_RAM_MAXIMUM_SPACE) {
uint32_t excess_bytes = no_cached_ram_occupied_bytes - NO_CACHED_RAM_MAXIMUM_SPACE;
ErrorHandler("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes);
PANIC("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes);
return nullptr;
}
return buffer;
Expand Down
9 changes: 2 additions & 7 deletions Inc/HALAL/Models/Packets/SPIOrder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,15 @@ class SPIBaseOrder {
SPIBaseOrder(uint16_t id, uint16_t master_data_size, uint16_t slave_data_size)
: id(id), master_data_size(master_data_size), slave_data_size(slave_data_size) {
if (id == 0) {
ErrorHandler(
"Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal"
);
PANIC("Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal");
}
if (master_data_size > slave_data_size) {
payload_size = master_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL;
} else {
payload_size = slave_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL;
}
if (payload_size > SPI_MAXIMUM_PAYLOAD_SIZE_BYTES) {
ErrorHandler(
"Cannot declare SPIOrder %d as its size surpasses the maximum data size",
id
);
PANIC("Cannot declare SPIOrder %d as its size surpasses the maximum data size", id);
}
MISO_payload = new uint8_t[payload_size]{0};
MOSI_payload = new uint8_t[payload_size]{0};
Expand Down
Loading
Loading