Install
npx skillscat add while-one/caffeine-hal Install via the SkillsCat registry.
Caffeine-HAL Context & Agent Guidelines
1. Project Context
Caffeine-HAL is a modern, strictly C11-compliant, Header-Defined Interface Hardware Abstraction Layer (HAL) library for embedded systems (primarily ARM Cortex-M microcontrollers).
Its core philosophy is to provide a consistent, polymorphic interface across different microcontrollers via a Virtual Method Table (VMT) architecture, while completely decoupling the generic API from the highly vendor-specific "Board Support Package" (BSP) initialization logic.
Core Architecture
- Header-Defined Interface (
INTERFACE): The entire generic HAL is defined in header files (include/cfn_hal_*.h) asstatic inlinewrappers around function pointers. - Base Driver (
cfn_hal_base.h/cfn_hal_base_impl.h): All peripherals inherit from a common base logic. Standard operations likeinit,deinit,config_set,callback_register, andpower_state_setare handled by centralized base functions. The implementation is separated intocfn_hal_base_impl.hto allow for:- Header-Only (Default): Functions are
static inline. - Library-Linked: Functions are
externby definingCFN_HAL_USE_LIB_BASE. - Library-Compile: Functions are compiled into a library by defining
CFN_HAL_COMPILE_BASE.
- Header-Only (Default): Functions are
- VMT Pattern: Every peripheral (e.g.,
cfn_hal_uart_t) contains a genericcfn_hal_driver_tbase struct and a pointer to an API struct (e.g.,cfn_hal_uart_api_t). Thevmtmember in the base struct is strictly typed asconst struct cfn_hal_api_base_s *for compiler-enforced safety. Hardware-specific C files implement these API structures. - VMT Safety: To prevent memory corruption, the
cfn_hal_api_base_tMUST be the first member of every peripheral-specific API struct. This is enforced at compile-time via theCFN_HAL_VMT_CHECKmacro and at initialization via type-checking. - Board-Level Hook (
on_config): Thecfn_hal_driver_tbase struct contains anon_configcallback. The generic_initand_deinitwrappers automatically call this hook before/after touching hardware registers. - Error Preservation: The
cfn_hal_base_initlogic ensures that the primary initialization error code from the hardware-specific driver is preserved and returned even if the board-level rollback (DEINIT phase) fails.
2. Agent Coding Rules & Constraints
When contributing to, modifying, or generating code for Caffeine-HAL, you must adhere to the following rules:
A. C Standard & Syntax
- Strict C11: The library is strictly C11. Do not use C++ features, GNU extensions, or MSVC-specific attributes.
- No Dynamic Memory: Never use
malloc,free, ornew. - Type Safety: Use
<stdint.h>types exclusively (uint32_t,uint8_t, etc.). - Generic Callbacks: Generic callbacks in the base layer must use
cfn_hal_callback_t(function pointer).- Port implementers: You MUST cast the type-erased
cfn_hal_callback_tback to its original signature (e.g.,cfn_hal_uart_callback_t) before execution to avoid Undefined Behavior.
- Port implementers: You MUST cast the type-erased
B. HAL Conventions
- Static Initializers: Every peripheral MUST provide a static initializer macro:
CFN_HAL_<PERIPHERAL>_INITIALIZER(api_ptr, phy_ptr, config_ptr). Use the genericCFN_HAL_DRIVER_INITIALIZERmacro fromcfn_hal.h. - VMT Validation: Every peripheral API struct definition MUST be followed immediately by a
CFN_HAL_VMT_CHECK(struct cfn_hal_<p>_api_s)call to ensure structural compatibility with the base layer. - Error Handling: Every API function must return a
cfn_hal_error_code_t. - Event/Error Splitting: Status flags and interrupts must be split into nominal events (
cfn_hal_<p>_event_t) and exception errors (cfn_hal_<p>_error_t). - VMT Macro Usage: Use
CFN_HAL_CHECK_AND_CALL_FUNCorCFN_HAL_CHECK_AND_CALL_FUNC_VARGinside the inline wrappers. - Layered FourCC Identification: Use
CFN_HAL_PERIPHERAL_PREFIX(defined as 'A') for all HAL peripheral types. - Naming Conventions:
- Types:
cfn_hal_<peripheral>_t,cfn_hal_<peripheral>_api_t - Enums:
cfn_hal_<peripheral>_event_t,cfn_hal_<peripheral>_error_t - Physical Mapping:
cfn_hal_<peripheral>_phy_t - Porting Member:
void *instance(Except for GPIO which usesvoid *port).
- Types:
- GPIO Integration: Use
cfn_hal_gpio_pin_handle_tfor any peripheral member that depends on a GPIO pin. - GPIO Bitmasking: GPIO pins are identified by
cfn_hal_gpio_pin_tbitmasks. - GPIO Port Controller: The GPIO driver represents a physical PORT (e.g., GPIOA). It does not have a global configuration.
- GPIO Pin Configuration: Individual pins are configured using the
cfn_hal_gpio_pin_configfunction with a configuration payload. - GPIO API Symmetry: Pin-level operations (
pin_read,pin_write,pin_toggle) must take a single pin. Port-level operations (port_read,port_write) take raw bitmasks and values.
C. Concurrency & Asynchronous Design
- Locking Policy: Static inline functions should not call
CFN_HAL_LOCKinternally. Concurrency protection is the responsibility of the caller using theCFN_HAL_WITH_LOCKmacro. - Asynchronous Semantics: Functions suffixed with
_irqor_dmaare fundamentally non-blocking. They must not contain atimeoutparameter. - Polling Semantics: Functions suffixed with
_pollingor_read/_write(if blocking) are blocking and must contain atimeoutparameter.
D. Formatting & Style
- Column Limit: 120 columns.
- Braces (Mandatory): Mandatory for all control blocks.
- Style: Use the Allman style (opening brace on a new line).
- Indentation: 4 spaces. No tabs.
- Documentation: All
static inlinefunctions must have proper Doxygen documentation.
3. Testing Requirements
- Test Location:
tests/cfn_hal_test_<peripheral>.cpp. - The "Big 4" Negative Tests: Every peripheral test suite must include:
NullDriverReturnsBadParamWrongPeripheralTypeReturnsBadParamUnimplementedApiReturnsNotSupportedOnConfigFailureAbortsInit
4. CMake & Build System
- Target Type: Caffeine-HAL is an
INTERFACEtarget. - Header Discovery: Headers in
include/*.hare automatically discovered viafile(GLOB ... CONFIGURE_DEPENDS). No manual registry is required.
5. Build Environment & CI
The build process for Caffeine-HAL is highly optimized for consistency across local development and GitHub Actions CI.
A. Dockerized Toolchain
A multi-stage Dockerfile (at the repository root) defines the entire build environment. This includes:
basestage: Installscmake,ninja-build,clang-format,clang-tidy,cppcheck,doxygen, and pre-buildsgoogletest.build-arm,build-riscv,build-nativestages: Add architecture-specific toolchains as needed.
B. CI Workflow (.github/workflows/ci.yml)
The main CI pipeline runs all checks (linting, static analysis, builds, tests) inside the appropriate Docker image.
- Fast & Consistent Builds: Jobs run within a pre-built Docker container, eliminating redundant
apt-get installcommands and ensuring a consistent environment. - Containerized Execution: Jobs are configured with
container: image: ghcr.io/${{ github.repository }}/build-native:latest(orbuild-arm, etc.). - Offline Dependencies: CMake configuration steps include
-DFETCHCONTENT_FULLY_DISCONNECTED=ONto leverage pre-installed dependencies and prevent network access.
C. Docker Image Publishing (.github/workflows/docker-publish.yml)
A dedicated workflow automatically builds and pushes updated Docker images to the GitHub Container Registry (ghcr.io).
- Trigger: Activated when the
Dockerfilechanges on themainbranch. - Multi-Stage Build & Push: Builds and tags images for all
build-*stages (e.g.,ghcr.io/your-org/your-repo/build-native:latest).
D. Local Development with Docker (scripts/build-local.sh)
An optional helper script (scripts/build-local.sh) allows developers to execute builds inside the Docker environment locally, ensuring perfect parity with CI.
# Example: Build natively inside Docker (default: builds all targets)
./scripts/build-local.sh native
# Example: Run a specific CMake target (e.g., 'caffeine-hal-format')
./scripts/build-local.sh native caffeine-hal-format
# Example: Run code coverage inside Docker
./scripts/build-local.sh native caffeine-hal-coverageE. Native Host Builds
Developers can still build the project directly on their host machine without Docker. The CMake Find-then-Fetch logic for GoogleTest (tests/CMakeLists.txt) ensures that if GTest is not found on the host, it will be automatically downloaded, maintaining native build capability.
# Standard CMake build on your host
mkdir build && cd build
cmake ..
cmake --build .
ctest --output-on-failure