diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6cabb7c..99154ac 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,11 +30,20 @@ jobs: - name: Configure host run: cmake --preset linux-debug -S host + - name: Build host + run: cmake --build host/build + - name: Configure firmware/rmcs_board run: cmake --preset debug -S firmware/rmcs_board + - name: Build firmware/rmcs_board + run: cmake --build firmware/rmcs_board/build + - name: Configure firmware/c_board run: cmake --preset debug -S firmware/c_board + - name: Build firmware/c_board + run: cmake --build firmware/c_board/build --target c_board_app c_board_bootloader + - name: Run clang-tidy check run: .scripts/clang-tidy-check diff --git a/.scripts/lint-targets.yml b/.scripts/lint-targets.yml index a189ed0..c422170 100644 --- a/.scripts/lint-targets.yml +++ b/.scripts/lint-targets.yml @@ -15,8 +15,10 @@ rmcs_board: folders: - core/src - core/include - - firmware/rmcs_board/src - - firmware/rmcs_board/include + - firmware/rmcs_board/app/src + - firmware/rmcs_board/app/include + - firmware/rmcs_board/bootloader/src + - firmware/rmcs_board/bootloader/include c_board: cmake: firmware/c_board/CMakeLists.txt diff --git a/AGENTS.md b/AGENTS.md index 92faad4..d5303fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ cmake --build firmware/c_board/build --target c_board_app c_board_bootloader ```bash .scripts/clang-format-check --fix # 应用格式修复 -.scripts/clang-tidy-check # 静态分析(需在 CMake configure 之后运行) +.scripts/clang-tidy-check # 静态分析(需在 CMake build 之后运行) ``` `.scripts/clang-tidy-check --fix` 可触发 clang-tidy 自动修复,但部分修复可能不符合预期,需手动调整。 diff --git a/Dockerfile.build_firmware b/Dockerfile.build_firmware index 6334dc6..eb1ea1a 100644 --- a/Dockerfile.build_firmware +++ b/Dockerfile.build_firmware @@ -11,11 +11,10 @@ RUN --mount=type=bind,target=/src,source=.,readonly \ build_dir="/tmp/build-${board}"; \ cmake --preset release -S "firmware/${board}" -B "${build_dir}"; \ cmake --build "${build_dir}"; \ - elf_path="${build_dir}/output/librmcs-firmware.elf"; \ - if [ ! -f "${elf_path}" ]; then \ - elf_path="${build_dir}/librmcs-firmware.elf"; \ - fi; \ - cp "${elf_path}" "/output/librmcs-firmware-${board}-${LIBRMCS_PROJECT_VERSION}.elf"; \ + cp "${build_dir}/bootloader/output/rmcs_board_bootloader.elf" \ + "/output/librmcs-bootloader-${board}.elf"; \ + cp "${build_dir}/app/output/rmcs_board_app.dfu" \ + "/output/librmcs-firmware-${board}-${LIBRMCS_PROJECT_VERSION}.dfu"; \ done \ && for board in c_board; do \ build_dir="/tmp/build-${board}"; \ diff --git a/firmware/c_board/CMakeLists.txt b/firmware/c_board/CMakeLists.txt index 4003439..98e6c19 100644 --- a/firmware/c_board/CMakeLists.txt +++ b/firmware/c_board/CMakeLists.txt @@ -17,39 +17,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(librmcs-firmware LANGUAGES C CXX ASM) -set(LIBRMCS_PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") -cmake_path(ABSOLUTE_PATH LIBRMCS_PROJECT_ROOT NORMALIZE) +include("${CMAKE_CURRENT_SOURCE_DIR}/../common/cmake/librmcs_firmware.cmake") +librmcs_setup_project_context(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") - -if(NOT DEFINED LIBRMCS_PROJECT_VERSION OR LIBRMCS_PROJECT_VERSION STREQUAL "") - execute_process( - COMMAND ".scripts/generate_version" - WORKING_DIRECTORY "${LIBRMCS_PROJECT_ROOT}" - OUTPUT_VARIABLE LIBRMCS_PROJECT_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY - ) -endif() -message(STATUS "Librmcs project version: ${LIBRMCS_PROJECT_VERSION}") - - -option(HOST_DEBUGGER "Debugger runs outside Dev Container (Docker)" OFF) -set(SOURCE_PATH_MAP "" CACHE PATH "(In container) Map container source path to a host-visible path for debugger") - -if(HOST_DEBUGGER AND SOURCE_PATH_MAP STREQUAL "") - if(DEFINED ENV{HOST_WORKSPACE_FOLDER} AND NOT "$ENV{HOST_WORKSPACE_FOLDER}" STREQUAL "") - set(SOURCE_PATH_MAP "$ENV{HOST_WORKSPACE_FOLDER}") - else() - message(FATAL_ERROR "HOST_WORKSPACE_FOLDER is not set") - endif() -endif() -if(NOT SOURCE_PATH_MAP STREQUAL "") - set(SOURCE_PATH_MAP "${SOURCE_PATH_MAP}/") - cmake_path(NORMAL_PATH SOURCE_PATH_MAP) - add_compile_options( - "-fdebug-prefix-map=${LIBRMCS_PROJECT_ROOT}=${SOURCE_PATH_MAP}" - "-ffile-prefix-map=${LIBRMCS_PROJECT_ROOT}=${SOURCE_PATH_MAP}" - ) +if(LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS) + add_compile_options(${LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS}) endif() diff --git a/firmware/c_board/app/CMakeLists.txt b/firmware/c_board/app/CMakeLists.txt index 9379c58..e16649a 100644 --- a/firmware/c_board/app/CMakeLists.txt +++ b/firmware/c_board/app/CMakeLists.txt @@ -53,15 +53,13 @@ target_link_libraries(c_board_app PRIVATE c_board_apply_target_options(c_board_app "STM32F407XX_APP.ld") -add_custom_command(TARGET c_board_app POST_BUILD - COMMAND ${CMAKE_OBJCOPY} -O binary - $ - $/c_board_app.bin - COMMAND ${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash - -o $/c_board_app.dfu - $/c_board_app.bin - COMMAND dfu-suffix -v 0xA11C -p 0xD401 -d 0x0300 - -a $/c_board_app.dfu - > /dev/null +librmcs_add_dfu_image( + TARGET c_board_app + INPUT_ELF $ + OUTPUT_BINARY $/c_board_app.bin + OUTPUT_DFU $/c_board_app.dfu + VENDOR_ID 0xA11C + PRODUCT_ID 0xD401 + DEVICE_ID 0x0300 COMMENT "Generating c_board_app.dfu" ) diff --git a/firmware/c_board/app/src/timer/timer.hpp b/firmware/c_board/app/src/timer/timer.hpp index cb43447..fec4627 100644 --- a/firmware/c_board/app/src/timer/timer.hpp +++ b/firmware/c_board/app/src/timer/timer.hpp @@ -45,7 +45,9 @@ class Timer { HAL_TIM_Base_Start(kTimerHigh) == HAL_OK && HAL_TIM_Base_Start(kTimerLow) == HAL_OK); } - TimePoint timepoint() const { return TimePoint{Duration{timer_counter_low_}}; } + TimePoint timepoint() const { + return TimePoint{Duration{timer_counter_low_}}; + } TimePoint48 timepoint48() const { uint32_t hi1, hi2, lo; diff --git a/firmware/c_board/app/src/usb/usb_descriptors.hpp b/firmware/c_board/app/src/usb/usb_descriptors.hpp index 1e3d3ef..7c6fa3c 100644 --- a/firmware/c_board/app/src/usb/usb_descriptors.hpp +++ b/firmware/c_board/app/src/usb/usb_descriptors.hpp @@ -126,7 +126,7 @@ class UsbDescriptors { .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_VENDOR_SPECIFIC, + .bDeviceClass = TUSB_CLASS_UNSPECIFIED, .bDeviceSubClass = 0x00, .bDeviceProtocol = 0x00, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, diff --git a/firmware/c_board/bootloader/src/crypto/sha256.hpp b/firmware/c_board/bootloader/src/crypto/sha256.hpp index 832eb86..262624a 100644 --- a/firmware/c_board/bootloader/src/crypto/sha256.hpp +++ b/firmware/c_board/bootloader/src/crypto/sha256.hpp @@ -13,7 +13,10 @@ offered in this implementation. Algorithm specification can be found here: * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf - This implementation uses little endian byte order. + This implementation uses big-endian (network) byte order: + sha256_transform reads message words as big-endian, and + sha256_final stores the bit length and digest in big-endian + order per the SHA-256 specification. *********************************************************************/ #include @@ -22,7 +25,7 @@ namespace librmcs::firmware::crypto { -inline constexpr size_t kSha256BlockSize = 32U; +inline constexpr size_t kSha256DigestSize = 32U; struct Sha256Ctx { std::array data{}; @@ -75,7 +78,7 @@ inline uint32_t small_sigma1(uint32_t x) { } inline void sha256_transform(Sha256Ctx* ctx, const uint8_t* data) { - std::array message_schedule{}; + std::array message_schedule; for (uint32_t index = 0U; index < 16U; ++index) { const uint32_t data_offset = index * 4U; diff --git a/firmware/c_board/bootloader/src/flash/metadata.hpp b/firmware/c_board/bootloader/src/flash/metadata.hpp index 9506705..3185aae 100644 --- a/firmware/c_board/bootloader/src/flash/metadata.hpp +++ b/firmware/c_board/bootloader/src/flash/metadata.hpp @@ -31,9 +31,11 @@ class Metadata { } else if (latest_valid_slot_state_ == DataSlotState::kFlashing) { return; } else if (latest_valid_slot_state_ == DataSlotState::kReady) { - latest_valid_slot_++; - if (reinterpret_cast(latest_valid_slot_) >= kMetadataEndAddress) + auto next_addr = reinterpret_cast(latest_valid_slot_) + sizeof(DataSlot); + if (next_addr >= kMetadataEndAddress) erase_and_rescan(); + else + latest_valid_slot_ = reinterpret_cast(next_addr); } latest_valid_slot_->enter_flashing_state(); @@ -162,7 +164,8 @@ class Metadata { case DataSlotState::kFlashing: case DataSlotState::kReady: { if (!latest_valid_slot_ - || (latest_valid_slot_ + 1 == &slot + || (reinterpret_cast(latest_valid_slot_) + sizeof(DataSlot) + == reinterpret_cast(&slot) && latest_valid_slot_state_ == DataSlotState::kReady)) { latest_valid_slot_ = &slot; latest_valid_slot_state_ = state; diff --git a/firmware/c_board/bootloader/src/flash/validation.hpp b/firmware/c_board/bootloader/src/flash/validation.hpp index 85bff3b..c1e7931 100644 --- a/firmware/c_board/bootloader/src/flash/validation.hpp +++ b/firmware/c_board/bootloader/src/flash/validation.hpp @@ -10,11 +10,11 @@ namespace librmcs::firmware::flash { inline constexpr uint32_t kSramStartAddress = 0x20000000U; -inline constexpr uint32_t kSramEndAddress = 0x20020000U; // Exclusive +inline constexpr uint32_t kSramEndAddress = 0x20020000U; // Exclusive inline constexpr uint32_t kCrc32Polynomial = 0xEDB88320U; -inline constexpr uint32_t kImageHashMagic = 0x48415348U; // "HASH" +inline constexpr uint32_t kImageHashMagic = 0x48415348U; // "HASH" inline constexpr uint32_t kImageHashSuffixSize = - sizeof(uint32_t) + static_cast(crypto::kSha256BlockSize); // 36 + sizeof(uint32_t) + static_cast(crypto::kSha256DigestSize); // 36 inline bool is_vector_table_valid() { const uint32_t initial_msp = *reinterpret_cast(kAppStartAddress); @@ -78,10 +78,20 @@ inline bool validate_image_hash(uint32_t address, uint32_t size) { const uint32_t firmware_size = size - kImageHashSuffixSize; const uint8_t* expected_sha256 = suffix_ptr + sizeof(uint32_t); - uint8_t computed_sha256[crypto::kSha256BlockSize]; + uint8_t computed_sha256[crypto::kSha256DigestSize]; compute_image_sha256(address, firmware_size, computed_sha256); - return std::memcmp(computed_sha256, expected_sha256, crypto::kSha256BlockSize) == 0; + return std::memcmp(computed_sha256, expected_sha256, crypto::kSha256DigestSize) == 0; +} + +inline bool validate_candidate_image(uint32_t size) { + if (size == 0U || size > kAppMaxImageSize) + return false; + + if (!is_vector_table_valid()) + return false; + + return validate_image_hash(kAppStartAddress, size); } inline bool validate_app_image() { @@ -91,17 +101,11 @@ inline bool validate_app_image() { return false; const uint32_t size = meta.image_size(); - if (size == 0U || size > kAppMaxImageSize) - return false; - - if (!is_vector_table_valid()) + if (!validate_candidate_image(size)) return false; const uint32_t crc32 = compute_image_crc32(kAppStartAddress, size); - if (crc32 != meta.image_crc32()) - return false; - - return validate_image_hash(kAppStartAddress, size); + return crc32 == meta.image_crc32(); } } // namespace librmcs::firmware::flash diff --git a/firmware/c_board/bootloader/src/flash/writer.hpp b/firmware/c_board/bootloader/src/flash/writer.hpp index 3f5b8de..d1567a7 100644 --- a/firmware/c_board/bootloader/src/flash/writer.hpp +++ b/firmware/c_board/bootloader/src/flash/writer.hpp @@ -22,6 +22,8 @@ class Writer { void begin_session() { clear_active_sector(); } + void abort_session() { clear_active_sector(); } + void finish_session() { if (!has_active_sector_) return; @@ -51,6 +53,7 @@ class Writer { const size_t chunk_size = (data.size() - input_offset < writable) ? (data.size() - input_offset) : writable; + utility::assert_debug(offset_in_sector == buffered_size_); std::memcpy( sector_buffer_.data() + offset_in_sector, data.data() + input_offset, chunk_size); advance_buffer(offset_in_sector, chunk_size); diff --git a/firmware/c_board/bootloader/src/main.cpp b/firmware/c_board/bootloader/src/main.cpp index 55f41ea..4e5c71b 100644 --- a/firmware/c_board/bootloader/src/main.cpp +++ b/firmware/c_board/bootloader/src/main.cpp @@ -21,10 +21,8 @@ int main() { const bool force_dfu = utility::boot_mailbox.consume_enter_dfu_request(); if (!force_dfu) { - if (flash::validate_app_image()) { + if (flash::validate_app_image()) utility::jump_to_app(flash::kAppStartAddress); - utility::assert_failed_always(); - } } utility::assert_always(tusb_rhport_init(0, nullptr)); diff --git a/firmware/c_board/bootloader/src/usb/dfu.hpp b/firmware/c_board/bootloader/src/usb/dfu.hpp index b931cb0..ebe8fd9 100644 --- a/firmware/c_board/bootloader/src/usb/dfu.hpp +++ b/firmware/c_board/bootloader/src/usb/dfu.hpp @@ -62,9 +62,6 @@ class Dfu { if (block_num != expected_block_) return fail(DFU_STATUS_ERR_ADDRESS); - if ((downloaded_size_ & 0x3U) != 0U) - return fail(DFU_STATUS_ERR_PROG); - const uint64_t write_address_64 = static_cast(flash::kAppStartAddress) + static_cast(downloaded_size_); if (write_address_64 >= static_cast(flash::kAppEndAddress)) @@ -99,15 +96,18 @@ class Dfu { flash_writer_.finish_session(); - if (!flash::is_vector_table_valid()) + if (!flash::validate_candidate_image(downloaded_size_)) return fail(DFU_STATUS_ERR_FIRMWARE); const uint32_t image_crc32 = flash::compute_image_crc32(flash::kAppStartAddress, downloaded_size_); - flash::Metadata::get_instance().finish_flashing(downloaded_size_, image_crc32); + auto& metadata = flash::Metadata::get_instance(); + metadata.finish_flashing(downloaded_size_, image_crc32); - if (!flash::validate_app_image()) + if (!metadata.is_ready() || metadata.image_size() != downloaded_size_ + || metadata.image_crc32() != image_crc32) { return fail(DFU_STATUS_ERR_FIRMWARE); + } reset_transfer_state(); reset_requested_ = true; @@ -153,6 +153,7 @@ class Dfu { } void reset_transfer_state() { + flash_writer_.abort_session(); expected_block_ = 0U; downloaded_size_ = 0U; session_started_ = false; diff --git a/firmware/c_board/bootloader/src/utility/jump.hpp b/firmware/c_board/bootloader/src/utility/jump.hpp index e345f00..4884816 100644 --- a/firmware/c_board/bootloader/src/utility/jump.hpp +++ b/firmware/c_board/bootloader/src/utility/jump.hpp @@ -4,6 +4,8 @@ #include +#include "firmware/c_board/bootloader/src/utility/assert.hpp" + namespace librmcs::firmware::utility { inline void jump_to_app(uint32_t app_address) { @@ -43,7 +45,7 @@ inline void jump_to_app(uint32_t app_address) { __ISB(); app_reset_handler(); - __builtin_unreachable(); + assert_failed_always(); } } // namespace librmcs::firmware::utility diff --git a/firmware/common/cmake/librmcs_firmware.cmake b/firmware/common/cmake/librmcs_firmware.cmake new file mode 100644 index 0000000..e29e0ae --- /dev/null +++ b/firmware/common/cmake/librmcs_firmware.cmake @@ -0,0 +1,138 @@ +include_guard(GLOBAL) + +function(librmcs_setup_project_context) + set(options) + set(one_value_args PROJECT_ROOT) + cmake_parse_arguments(ARG "${options}" "${one_value_args}" "" ${ARGN}) + + if(NOT ARG_PROJECT_ROOT) + message(FATAL_ERROR "librmcs_setup_project_context requires PROJECT_ROOT") + endif() + + set(project_root "${ARG_PROJECT_ROOT}") + cmake_path(ABSOLUTE_PATH project_root NORMALIZE) + + option(HOST_DEBUGGER "Debugger runs outside Dev Container (Docker)" OFF) + set( + SOURCE_PATH_MAP + "" + CACHE PATH + "(In container) Map container source path to a host-visible path for debugger" + ) + + set(project_version "${LIBRMCS_PROJECT_VERSION}") + if("${project_version}" STREQUAL "") + execute_process( + COMMAND ".scripts/generate_version" + WORKING_DIRECTORY "${project_root}" + OUTPUT_VARIABLE project_version + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) + endif() + + set(source_path_map "${SOURCE_PATH_MAP}") + if(HOST_DEBUGGER AND "${source_path_map}" STREQUAL "") + if(DEFINED ENV{HOST_WORKSPACE_FOLDER} AND NOT "$ENV{HOST_WORKSPACE_FOLDER}" STREQUAL "") + set(source_path_map "$ENV{HOST_WORKSPACE_FOLDER}") + else() + message(FATAL_ERROR "HOST_WORKSPACE_FOLDER is not set") + endif() + endif() + + set(prefix_map_compile_options "") + if(NOT "${source_path_map}" STREQUAL "") + set(source_path_map "${source_path_map}/") + cmake_path(NORMAL_PATH source_path_map) + list( + APPEND + prefix_map_compile_options + "-fdebug-prefix-map=${project_root}=${source_path_map}" + "-ffile-prefix-map=${project_root}=${source_path_map}" + ) + endif() + + message(STATUS "Librmcs project version: ${project_version}") + + set(LIBRMCS_PROJECT_ROOT "${project_root}" PARENT_SCOPE) + set(LIBRMCS_PROJECT_VERSION "${project_version}" PARENT_SCOPE) + set(LIBRMCS_SOURCE_PATH_MAP "${source_path_map}" PARENT_SCOPE) + set(LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS "${prefix_map_compile_options}" PARENT_SCOPE) +endfunction() + +function(librmcs_add_dfu_image) + if(NOT DEFINED LIBRMCS_PROJECT_ROOT OR "${LIBRMCS_PROJECT_ROOT}" STREQUAL "") + message(FATAL_ERROR "LIBRMCS_PROJECT_ROOT must be set before calling librmcs_add_dfu_image") + endif() + + set(options) + set( + one_value_args + TARGET + INPUT_BINARY + INPUT_ELF + OUTPUT_BINARY + OUTPUT_DFU + VENDOR_ID + PRODUCT_ID + DEVICE_ID + COMMENT + ) + cmake_parse_arguments(ARG "${options}" "${one_value_args}" "" ${ARGN}) + + if(NOT ARG_TARGET) + message(FATAL_ERROR "librmcs_add_dfu_image requires TARGET") + endif() + if(NOT ARG_OUTPUT_DFU) + message(FATAL_ERROR "librmcs_add_dfu_image requires OUTPUT_DFU") + endif() + + set(input_binary "${ARG_INPUT_BINARY}") + set(objcopy_command) + if(NOT "${ARG_OUTPUT_BINARY}" STREQUAL "") + if("${ARG_INPUT_ELF}" STREQUAL "") + message(FATAL_ERROR "OUTPUT_BINARY requires INPUT_ELF") + endif() + if(NOT DEFINED CMAKE_OBJCOPY OR "${CMAKE_OBJCOPY}" STREQUAL "") + message(FATAL_ERROR "CMAKE_OBJCOPY must be set when OUTPUT_BINARY is used") + endif() + + set(input_binary "${ARG_OUTPUT_BINARY}") + set( + objcopy_command + COMMAND + ${CMAKE_OBJCOPY} + -O + binary + ${ARG_INPUT_ELF} + ${ARG_OUTPUT_BINARY} + ) + endif() + + if("${input_binary}" STREQUAL "") + message(FATAL_ERROR "librmcs_add_dfu_image requires INPUT_BINARY or OUTPUT_BINARY") + endif() + + add_custom_command( + TARGET ${ARG_TARGET} + POST_BUILD + ${objcopy_command} + COMMAND + "${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash" + -o + ${ARG_OUTPUT_DFU} + ${input_binary} + COMMAND + dfu-suffix + -v + ${ARG_VENDOR_ID} + -p + ${ARG_PRODUCT_ID} + -d + ${ARG_DEVICE_ID} + -a + ${ARG_OUTPUT_DFU} + COMMENT "${ARG_COMMENT}" + VERBATIM + ) +endfunction() diff --git a/firmware/rmcs_board/CMakeLists.txt b/firmware/rmcs_board/CMakeLists.txt index a9606ce..4f1915f 100644 --- a/firmware/rmcs_board/CMakeLists.txt +++ b/firmware/rmcs_board/CMakeLists.txt @@ -1,106 +1,77 @@ cmake_minimum_required(VERSION 3.28) set(CMAKE_CXX_SCAN_FOR_MODULES OFF) -set(APP_NAME "librmcs-firmware") +project(rmcs_board_superbuild LANGUAGES NONE) + +include("${CMAKE_CURRENT_SOURCE_DIR}/../common/cmake/librmcs_firmware.cmake") set(BOARD "hpm5300evk" CACHE STRING "Target board") set(RV_ARCH "rv32imafdcb" CACHE STRING "RISC-V arch") set(RV_ABI "ilp32d" CACHE STRING "RISC-V ABI") -set(HPM_BUILD_TYPE "flash_xip" CACHE STRING "HPM build type") - -set(CONFIG_DMA_MGR 1) -set(CONFIG_TINYUSB 1) -set(CONFIG_USB_DEVICE 1) -set(CONFIG_USB_DEVICE_VENDOR 1) -if(NOT DEFINED ENV{HPM_SDK_BASE} OR "$ENV{HPM_SDK_BASE}" STREQUAL "") - set(ENV{HPM_SDK_BASE} "${CMAKE_CURRENT_SOURCE_DIR}/bsp/hpm_sdk") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "debug") endif() -find_package(hpm-sdk REQUIRED HINTS "$ENV{HPM_SDK_BASE}") - -project(librmcs-firmware VERSION 3 LANGUAGES C CXX ASM) -set(LIBRMCS_PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") -cmake_path(ABSOLUTE_PATH LIBRMCS_PROJECT_ROOT NORMALIZE) - -if(NOT DEFINED LIBRMCS_PROJECT_VERSION OR LIBRMCS_PROJECT_VERSION STREQUAL "") - execute_process( - COMMAND ".scripts/generate_version" - WORKING_DIRECTORY "${LIBRMCS_PROJECT_ROOT}" - OUTPUT_VARIABLE LIBRMCS_PROJECT_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY +librmcs_setup_project_context(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") + +function(add_rmcs_board_subbuild target_name source_subdir) + set(source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${source_subdir}") + set(binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${source_subdir}") + set(compile_commands_file "${binary_dir}/compile_commands.json") + + set(configure_args + -G "${CMAKE_GENERATOR}" + -S "${source_dir}" + -B "${binary_dir}" + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DBOARD=${BOARD} + -DRV_ARCH=${RV_ARCH} + -DRV_ABI=${RV_ABI} + -DHOST_DEBUGGER=${HOST_DEBUGGER} + -DSOURCE_PATH_MAP=${LIBRMCS_SOURCE_PATH_MAP} + -DLIBRMCS_PROJECT_VERSION=${LIBRMCS_PROJECT_VERSION} ) -endif() -message(STATUS "Librmcs project version: ${LIBRMCS_PROJECT_VERSION}") - -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED True) - -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_C_EXTENSIONS OFF) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -sdk_compile_options(-Wall -Wextra -Wpedantic -fvisibility=hidden) -sdk_compile_options($<$:-fno-exceptions>) -sdk_compile_options($<$:-fno-rtti>) -sdk_compile_options($<$:-fno-unwind-tables>) -sdk_compile_options($<$:-fno-asynchronous-unwind-tables>) -sdk_compile_options($<$:-fno-threadsafe-statics>) -sdk_compile_options($<$:-fno-use-cxa-atexit>) - -option(HOST_DEBUGGER "Debugger runs outside Dev Container (Docker)" OFF) -set(SOURCE_PATH_MAP "" CACHE PATH "(In container) Map container source path to a host-visible path for debugger") - -if(HOST_DEBUGGER AND SOURCE_PATH_MAP STREQUAL "") - if(DEFINED ENV{HOST_WORKSPACE_FOLDER} AND NOT "$ENV{HOST_WORKSPACE_FOLDER}" STREQUAL "") - set(SOURCE_PATH_MAP "$ENV{HOST_WORKSPACE_FOLDER}") - else() - message(FATAL_ERROR "HOST_WORKSPACE_FOLDER is not set") + if(CMAKE_MAKE_PROGRAM) + list(APPEND configure_args -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}) endif() -endif() -if(NOT SOURCE_PATH_MAP STREQUAL "") - set(SOURCE_PATH_MAP "${SOURCE_PATH_MAP}/") - cmake_path(NORMAL_PATH SOURCE_PATH_MAP) - sdk_compile_options( - "-fdebug-prefix-map=${LIBRMCS_PROJECT_ROOT}=${SOURCE_PATH_MAP}" - "-ffile-prefix-map=${LIBRMCS_PROJECT_ROOT}=${SOURCE_PATH_MAP}" + add_custom_target( + ${target_name} + BYPRODUCTS "${compile_commands_file}" + COMMAND ${CMAKE_COMMAND} ${configure_args} + COMMAND ${CMAKE_COMMAND} --build "${binary_dir}" + USES_TERMINAL + VERBATIM ) -endif() - -# HPM SDK forcibly converts CMAKE_BUILD_TYPE to lowercase. God knows why. -if(CMAKE_BUILD_TYPE STREQUAL "debug") - sdk_compile_options(-Og) -endif() - -sdk_compile_definitions(-DLIBRMCS_PROJECT_VERSION_STRING="${LIBRMCS_PROJECT_VERSION}") -sdk_compile_definitions(-DCFG_TUSB_MCU=OPT_MCU_HPM) -sdk_compile_definitions(-DUSB_HOST_MCU_CORE=HPM_CORE0) - -sdk_inc("${CMAKE_CURRENT_SOURCE_DIR}/include") - -sdk_app_inc("${LIBRMCS_PROJECT_ROOT}") - -file(GLOB_RECURSE PROJECT_SOURCE CONFIGURE_DEPENDS - "${LIBRMCS_PROJECT_ROOT}/core/src/*.cpp" - "${LIBRMCS_PROJECT_ROOT}/core/src/*.c" - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" +endfunction() + +add_rmcs_board_subbuild(rmcs_board_app app) +add_rmcs_board_subbuild(rmcs_board_bootloader bootloader) + +set(RMCS_BOARD_APP_COMPILE_COMMANDS "${CMAKE_CURRENT_BINARY_DIR}/app/compile_commands.json") +set(RMCS_BOARD_BOOTLOADER_COMPILE_COMMANDS "${CMAKE_CURRENT_BINARY_DIR}/bootloader/compile_commands.json") +set(RMCS_BOARD_MERGED_COMPILE_COMMANDS "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json") + +add_custom_command( + OUTPUT "${RMCS_BOARD_MERGED_COMPILE_COMMANDS}" + COMMAND + ${CMAKE_COMMAND} + "-DOUTPUT_FILE=${RMCS_BOARD_MERGED_COMPILE_COMMANDS}" + "-DINPUT_FILES=${RMCS_BOARD_APP_COMPILE_COMMANDS};${RMCS_BOARD_BOOTLOADER_COMPILE_COMMANDS}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/merge_compile_commands.cmake" + DEPENDS + rmcs_board_app + rmcs_board_bootloader + "${RMCS_BOARD_APP_COMPILE_COMMANDS}" + "${RMCS_BOARD_BOOTLOADER_COMPILE_COMMANDS}" + COMMENT "Merging rmcs_board compile commands" + VERBATIM ) -sdk_app_src(${PROJECT_SOURCE}) -set_property(TARGET app PROPERTY CXX_STANDARD 23) -set_property(TARGET app PROPERTY CXX_STANDARD_REQUIRED ON) -set_property(TARGET app PROPERTY CXX_EXTENSIONS OFF) +add_custom_target( + rmcs_board_compile_commands + DEPENDS "${RMCS_BOARD_MERGED_COMPILE_COMMANDS}" +) -# Move SDK includes into SYSTEM to suppress third-party warnings -get_target_property(_hpm_incs ${HPM_SDK_LIB_ITF} INTERFACE_INCLUDE_DIRECTORIES) -if(_hpm_incs) - set_target_properties(${HPM_SDK_LIB_ITF} PROPERTIES - INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_hpm_incs}") -endif() +add_custom_target(rmcs_board ALL DEPENDS rmcs_board_compile_commands) diff --git a/firmware/rmcs_board/app/CMakeLists.txt b/firmware/rmcs_board/app/CMakeLists.txt new file mode 100644 index 0000000..9e020b6 --- /dev/null +++ b/firmware/rmcs_board/app/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.28) +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + +set(APP_NAME "rmcs_board_app") + +include("${CMAKE_CURRENT_SOURCE_DIR}/../../common/cmake/librmcs_firmware.cmake") + +set(BOARD "hpm5300evk" CACHE STRING "Target board") +set(RV_ARCH "rv32imafdcb" CACHE STRING "RISC-V arch") +set(RV_ABI "ilp32d" CACHE STRING "RISC-V ABI") +set(HPM_BUILD_TYPE "flash_uf2" CACHE STRING "HPM build type") + +set(CONFIG_DMA_MGR 1) +set(CONFIG_TINYUSB 1) +set(CONFIG_USB_DEVICE 1) +set(CONFIG_USB_DEVICE_VENDOR 1) + +if(NOT DEFINED ENV{HPM_SDK_BASE} OR "$ENV{HPM_SDK_BASE}" STREQUAL "") + set(ENV{HPM_SDK_BASE} "${CMAKE_CURRENT_SOURCE_DIR}/../bsp/hpm_sdk") +endif() +find_package(hpm-sdk REQUIRED HINTS "$ENV{HPM_SDK_BASE}") + +librmcs_setup_project_context(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../..") + +project(rmcs_board_app VERSION 3 LANGUAGES C CXX ASM) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +sdk_compile_options(-Wall -Wextra -Wpedantic -fvisibility=hidden) +sdk_compile_options($<$:-fno-exceptions>) +sdk_compile_options($<$:-fno-rtti>) +sdk_compile_options($<$:-fno-unwind-tables>) +sdk_compile_options($<$:-fno-asynchronous-unwind-tables>) +sdk_compile_options($<$:-fno-threadsafe-statics>) +sdk_compile_options($<$:-fno-use-cxa-atexit>) +if(LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS) + sdk_compile_options(${LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS}) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "debug") + sdk_compile_options(-Og) +endif() + +sdk_compile_definitions(-DLIBRMCS_PROJECT_VERSION_STRING="${LIBRMCS_PROJECT_VERSION}") +sdk_compile_definitions(-DCFG_TUSB_MCU=OPT_MCU_HPM) +sdk_compile_definitions(-DUSB_HOST_MCU_CORE=HPM_CORE0) + +sdk_inc("${CMAKE_CURRENT_SOURCE_DIR}/include") +sdk_app_inc("${LIBRMCS_PROJECT_ROOT}") + +file(GLOB_RECURSE PROJECT_SOURCE CONFIGURE_DEPENDS + "${LIBRMCS_PROJECT_ROOT}/core/src/*.cpp" + "${LIBRMCS_PROJECT_ROOT}/core/src/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" +) +sdk_app_src(${PROJECT_SOURCE}) +sdk_app_src("$ENV{HPM_SDK_BASE}/middleware/tinyusb/src/class/dfu/dfu_rt_device.c") + +sdk_linker_global_symbols("_uf2_bl_length=0x20000") + +set_property(TARGET app PROPERTY CXX_STANDARD 23) +set_property(TARGET app PROPERTY CXX_STANDARD_REQUIRED ON) +set_property(TARGET app PROPERTY CXX_EXTENSIONS OFF) + +get_target_property(_hpm_incs ${HPM_SDK_LIB_ITF} INTERFACE_INCLUDE_DIRECTORIES) +if(_hpm_incs) + set_target_properties(${HPM_SDK_LIB_ITF} PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_hpm_incs}") +endif() + +librmcs_add_dfu_image( + TARGET ${APP_ELF_NAME} + INPUT_BINARY "${EXECUTABLE_OUTPUT_PATH}/${APP_BIN_NAME}" + OUTPUT_DFU "${EXECUTABLE_OUTPUT_PATH}/rmcs_board_app.dfu" + VENDOR_ID 0xA11C + PRODUCT_ID 0xAF01 + DEVICE_ID 0x0300 + COMMENT "Generating rmcs_board_app.dfu" +) diff --git a/firmware/rmcs_board/include/tusb_config.h b/firmware/rmcs_board/app/include/tusb_config.h similarity index 91% rename from firmware/rmcs_board/include/tusb_config.h rename to firmware/rmcs_board/app/include/tusb_config.h index 6248d70..36cbea6 100644 --- a/firmware/rmcs_board/include/tusb_config.h +++ b/firmware/rmcs_board/app/include/tusb_config.h @@ -64,11 +64,13 @@ extern "C" { #endif //------------- CLASS -------------// -#define CFG_TUD_CDC 0 -#define CFG_TUD_MSC 0 -#define CFG_TUD_HID 0 -#define CFG_TUD_MIDI 0 -#define CFG_TUD_VENDOR 1 +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 1 +#define CFG_TUD_DFU_RUNTIME 1 +#define CFG_TUD_DFU 0 #define CFG_TUD_VENDOR_EPSIZE 512 diff --git a/firmware/rmcs_board/src/app.cpp b/firmware/rmcs_board/app/src/app.cpp similarity index 67% rename from firmware/rmcs_board/src/app.cpp rename to firmware/rmcs_board/app/src/app.cpp index 3d50719..cb4b94b 100644 --- a/firmware/rmcs_board/src/app.cpp +++ b/firmware/rmcs_board/app/src/app.cpp @@ -1,17 +1,18 @@ -#include "firmware/rmcs_board/src/app.hpp" +#include "firmware/rmcs_board/app/src/app.hpp" #include #include #include #include -#include "firmware/rmcs_board/src/can/can.hpp" -#include "firmware/rmcs_board/src/gpio/gpio.hpp" -#include "firmware/rmcs_board/src/spi/bmi088/accel.hpp" -#include "firmware/rmcs_board/src/spi/bmi088/gyro.hpp" -#include "firmware/rmcs_board/src/uart/uart.hpp" -#include "firmware/rmcs_board/src/usb/vendor.hpp" -#include "firmware/rmcs_board/src/utility/interrupt_lock.hpp" +#include "firmware/rmcs_board/app/src/can/can.hpp" +#include "firmware/rmcs_board/app/src/gpio/gpio.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/accel.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp" +#include "firmware/rmcs_board/app/src/uart/uart.hpp" +#include "firmware/rmcs_board/app/src/usb/vendor.hpp" +#include "firmware/rmcs_board/app/src/utility/boot_mailbox.hpp" +#include "firmware/rmcs_board/app/src/utility/interrupt_lock.hpp" int main() { librmcs::firmware::app.init().run(); } @@ -23,6 +24,7 @@ App::App() { board_init(); board_init_usb(HPM_USB0); dma_mgr_init(); + boot::BootMailbox::clear(); can::can0.init(); can::can1.init(); diff --git a/firmware/rmcs_board/src/app.hpp b/firmware/rmcs_board/app/src/app.hpp similarity index 83% rename from firmware/rmcs_board/src/app.hpp rename to firmware/rmcs_board/app/src/app.hpp index b344dfa..c3245a1 100644 --- a/firmware/rmcs_board/src/app.hpp +++ b/firmware/rmcs_board/app/src/app.hpp @@ -1,7 +1,7 @@ #pragma once #include "core/src/utility/immovable.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware { diff --git a/firmware/rmcs_board/src/can/can.cpp b/firmware/rmcs_board/app/src/can/can.cpp similarity index 94% rename from firmware/rmcs_board/src/can/can.cpp rename to firmware/rmcs_board/app/src/can/can.cpp index cb5e0ff..2559ec4 100644 --- a/firmware/rmcs_board/src/can/can.cpp +++ b/firmware/rmcs_board/app/src/can/can.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/can/can.hpp" +#include "firmware/rmcs_board/app/src/can/can.hpp" #include @@ -8,7 +8,7 @@ #include #include "core/include/librmcs/data/datas.hpp" -#include "firmware/rmcs_board/src/usb/vendor.hpp" +#include "firmware/rmcs_board/app/src/usb/vendor.hpp" namespace librmcs::firmware::can { diff --git a/firmware/rmcs_board/src/can/can.hpp b/firmware/rmcs_board/app/src/can/can.hpp similarity index 98% rename from firmware/rmcs_board/src/can/can.hpp rename to firmware/rmcs_board/app/src/can/can.hpp index 141c494..ca82da0 100644 --- a/firmware/rmcs_board/src/can/can.hpp +++ b/firmware/rmcs_board/app/src/can/can.hpp @@ -18,7 +18,7 @@ #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::can { diff --git a/firmware/rmcs_board/src/gpio/gpio.cpp b/firmware/rmcs_board/app/src/gpio/gpio.cpp similarity index 93% rename from firmware/rmcs_board/src/gpio/gpio.cpp rename to firmware/rmcs_board/app/src/gpio/gpio.cpp index bcdb4cd..6ebabc1 100644 --- a/firmware/rmcs_board/src/gpio/gpio.cpp +++ b/firmware/rmcs_board/app/src/gpio/gpio.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/gpio/gpio.hpp" +#include "firmware/rmcs_board/app/src/gpio/gpio.hpp" #include @@ -13,8 +13,8 @@ #include #include -#include "firmware/rmcs_board/src/spi/bmi088/accel.hpp" -#include "firmware/rmcs_board/src/spi/bmi088/gyro.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/accel.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp" namespace { diff --git a/firmware/rmcs_board/src/gpio/gpio.hpp b/firmware/rmcs_board/app/src/gpio/gpio.hpp similarity index 100% rename from firmware/rmcs_board/src/gpio/gpio.hpp rename to firmware/rmcs_board/app/src/gpio/gpio.hpp diff --git a/firmware/rmcs_board/src/spi/bmi088/accel.hpp b/firmware/rmcs_board/app/src/spi/bmi088/accel.hpp similarity index 94% rename from firmware/rmcs_board/src/spi/bmi088/accel.hpp rename to firmware/rmcs_board/app/src/spi/bmi088/accel.hpp index 0ef1be4..b1bd528 100644 --- a/firmware/rmcs_board/src/spi/bmi088/accel.hpp +++ b/firmware/rmcs_board/app/src/spi/bmi088/accel.hpp @@ -9,10 +9,10 @@ #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/spi/bmi088/base.hpp" -#include "firmware/rmcs_board/src/spi/spi.hpp" -#include "firmware/rmcs_board/src/usb/vendor.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/base.hpp" +#include "firmware/rmcs_board/app/src/spi/spi.hpp" +#include "firmware/rmcs_board/app/src/usb/vendor.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::spi::bmi088 { diff --git a/firmware/rmcs_board/src/spi/bmi088/base.hpp b/firmware/rmcs_board/app/src/spi/bmi088/base.hpp similarity index 98% rename from firmware/rmcs_board/src/spi/bmi088/base.hpp rename to firmware/rmcs_board/app/src/spi/bmi088/base.hpp index 2043f9e..e138564 100644 --- a/firmware/rmcs_board/src/spi/bmi088/base.hpp +++ b/firmware/rmcs_board/app/src/spi/bmi088/base.hpp @@ -9,7 +9,7 @@ #include #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/spi/spi.hpp" +#include "firmware/rmcs_board/app/src/spi/spi.hpp" namespace librmcs::firmware::spi::bmi088 { diff --git a/firmware/rmcs_board/src/spi/bmi088/gyro.hpp b/firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp similarity index 94% rename from firmware/rmcs_board/src/spi/bmi088/gyro.hpp rename to firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp index 2c5e3da..795a793 100644 --- a/firmware/rmcs_board/src/spi/bmi088/gyro.hpp +++ b/firmware/rmcs_board/app/src/spi/bmi088/gyro.hpp @@ -9,10 +9,10 @@ #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/spi/bmi088/base.hpp" -#include "firmware/rmcs_board/src/spi/spi.hpp" -#include "firmware/rmcs_board/src/usb/vendor.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/spi/bmi088/base.hpp" +#include "firmware/rmcs_board/app/src/spi/spi.hpp" +#include "firmware/rmcs_board/app/src/usb/vendor.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::spi::bmi088 { diff --git a/firmware/rmcs_board/src/spi/spi.cpp b/firmware/rmcs_board/app/src/spi/spi.cpp similarity index 91% rename from firmware/rmcs_board/src/spi/spi.cpp rename to firmware/rmcs_board/app/src/spi/spi.cpp index f3750be..0e7558e 100644 --- a/firmware/rmcs_board/src/spi/spi.cpp +++ b/firmware/rmcs_board/app/src/spi/spi.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/spi/spi.hpp" +#include "firmware/rmcs_board/app/src/spi/spi.hpp" #include diff --git a/firmware/rmcs_board/src/spi/spi.hpp b/firmware/rmcs_board/app/src/spi/spi.hpp similarity index 99% rename from firmware/rmcs_board/src/spi/spi.hpp rename to firmware/rmcs_board/app/src/spi/spi.hpp index c3ad6c5..2955564 100644 --- a/firmware/rmcs_board/src/spi/spi.hpp +++ b/firmware/rmcs_board/app/src/spi/spi.hpp @@ -16,7 +16,7 @@ #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::spi { diff --git a/firmware/rmcs_board/src/uart/rx_buffer.hpp b/firmware/rmcs_board/app/src/uart/rx_buffer.hpp similarity index 100% rename from firmware/rmcs_board/src/uart/rx_buffer.hpp rename to firmware/rmcs_board/app/src/uart/rx_buffer.hpp diff --git a/firmware/rmcs_board/src/uart/tx_buffer.hpp b/firmware/rmcs_board/app/src/uart/tx_buffer.hpp similarity index 99% rename from firmware/rmcs_board/src/uart/tx_buffer.hpp rename to firmware/rmcs_board/app/src/uart/tx_buffer.hpp index 1d52510..5b470ba 100644 --- a/firmware/rmcs_board/src/uart/tx_buffer.hpp +++ b/firmware/rmcs_board/app/src/uart/tx_buffer.hpp @@ -21,7 +21,7 @@ #include "core/include/librmcs/data/datas.hpp" #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/utility/ring_buffer.hpp" +#include "firmware/rmcs_board/app/src/utility/ring_buffer.hpp" namespace librmcs::firmware::uart { diff --git a/firmware/rmcs_board/src/uart/uart.cpp b/firmware/rmcs_board/app/src/uart/uart.cpp similarity index 91% rename from firmware/rmcs_board/src/uart/uart.cpp rename to firmware/rmcs_board/app/src/uart/uart.cpp index 2a9d93a..0e01f87 100644 --- a/firmware/rmcs_board/src/uart/uart.cpp +++ b/firmware/rmcs_board/app/src/uart/uart.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/uart/uart.hpp" +#include "firmware/rmcs_board/app/src/uart/uart.hpp" #include #include diff --git a/firmware/rmcs_board/src/uart/uart.hpp b/firmware/rmcs_board/app/src/uart/uart.hpp similarity index 95% rename from firmware/rmcs_board/src/uart/uart.hpp rename to firmware/rmcs_board/app/src/uart/uart.hpp index 23aab16..1815766 100644 --- a/firmware/rmcs_board/src/uart/uart.hpp +++ b/firmware/rmcs_board/app/src/uart/uart.hpp @@ -17,10 +17,10 @@ #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" -#include "firmware/rmcs_board/src/uart/rx_buffer.hpp" -#include "firmware/rmcs_board/src/uart/tx_buffer.hpp" -#include "firmware/rmcs_board/src/usb/helper.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/uart/rx_buffer.hpp" +#include "firmware/rmcs_board/app/src/uart/tx_buffer.hpp" +#include "firmware/rmcs_board/app/src/usb/helper.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::uart { diff --git a/firmware/rmcs_board/src/usb/helper.hpp b/firmware/rmcs_board/app/src/usb/helper.hpp similarity index 100% rename from firmware/rmcs_board/src/usb/helper.hpp rename to firmware/rmcs_board/app/src/usb/helper.hpp diff --git a/firmware/rmcs_board/src/usb/interrupt_safe_buffer.hpp b/firmware/rmcs_board/app/src/usb/interrupt_safe_buffer.hpp similarity index 100% rename from firmware/rmcs_board/src/usb/interrupt_safe_buffer.hpp rename to firmware/rmcs_board/app/src/usb/interrupt_safe_buffer.hpp diff --git a/firmware/rmcs_board/src/usb/usb_descriptors.cpp b/firmware/rmcs_board/app/src/usb/usb_descriptors.cpp similarity index 94% rename from firmware/rmcs_board/src/usb/usb_descriptors.cpp rename to firmware/rmcs_board/app/src/usb/usb_descriptors.cpp index 775b6b0..d94e297 100644 --- a/firmware/rmcs_board/src/usb/usb_descriptors.cpp +++ b/firmware/rmcs_board/app/src/usb/usb_descriptors.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/usb/usb_descriptors.hpp" +#include "firmware/rmcs_board/app/src/usb/usb_descriptors.hpp" #include diff --git a/firmware/rmcs_board/src/usb/usb_descriptors.hpp b/firmware/rmcs_board/app/src/usb/usb_descriptors.hpp similarity index 83% rename from firmware/rmcs_board/src/usb/usb_descriptors.hpp rename to firmware/rmcs_board/app/src/usb/usb_descriptors.hpp index 385c581..412ddd3 100644 --- a/firmware/rmcs_board/src/usb/usb_descriptors.hpp +++ b/firmware/rmcs_board/app/src/usb/usb_descriptors.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,7 @@ #include #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::usb { @@ -25,8 +26,8 @@ class UsbDescriptors { public: UsbDescriptors() { update_serial_string(); } - uint8_t const* get_device_descriptor() const { - return reinterpret_cast(&device_descriptor_); + static uint8_t const* get_device_descriptor() { + return reinterpret_cast(&kDeviceDescriptor); } static uint8_t const* get_configuration_descriptor(uint8_t index) { @@ -52,6 +53,7 @@ class UsbDescriptors { case 1: str = kManufacturerString; break; case 2: str = kProductString; break; case 3: str = std::string_view{serial_string_.data(), serial_string_.size() - 1}; break; + case 4: str = kDfuRuntimeString; break; default: return nullptr; } constexpr auto max_size = std::min( @@ -135,12 +137,12 @@ class UsbDescriptors { } private: // Device Descriptor - tusb_desc_device_t const device_descriptor_ = { + static constexpr tusb_desc_device_t kDeviceDescriptor = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, - .bDeviceClass = TUSB_CLASS_VENDOR_SPECIFIC, + .bDeviceClass = TUSB_CLASS_UNSPECIFIED, .bDeviceSubClass = 0x00, .bDeviceProtocol = 0x00, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, @@ -157,10 +159,16 @@ class UsbDescriptors { }; private: // Configuration Descriptor - static constexpr size_t kItfNumTotal = 1; + // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) + enum InterfaceNumber : uint8_t { + kItfNumVendor = 0, + kItfNumDfuRuntime, + kItfNumTotal, + }; - static constexpr size_t kConfigTotalLen = - TUD_CONFIG_DESC_LEN + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN; + static constexpr size_t kConfigTotalLen = TUD_CONFIG_DESC_LEN + + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN + + CFG_TUD_DFU_RUNTIME * TUD_DFU_RT_DESC_LEN; // Align endpoint numbering to STM32 HAL style: // EP1 OUT: data OUT, EP1 IN: data IN @@ -173,7 +181,9 @@ class UsbDescriptors { 1, kItfNumTotal, 0, kConfigTotalLen, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), // Interface number, string index, EP data address (out, in) and size. - TUD_VENDOR_DESCRIPTOR(0, 0, kEpnumCdc0DataOut, kEpnumCdc0DataIn, 64), + TUD_VENDOR_DESCRIPTOR(kItfNumVendor, 0, kEpnumCdc0DataOut, kEpnumCdc0DataIn, 64), + TUD_DFU_RT_DESCRIPTOR( + kItfNumDfuRuntime, 4, DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_WILL_DETACH, 1000, 1024), }; static_assert(sizeof(kConfigurationDescriptorFs) == kConfigTotalLen); @@ -183,7 +193,9 @@ class UsbDescriptors { 1, kItfNumTotal, 0, kConfigTotalLen, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), // Interface number, string index, EP data address (out, in) and size. - TUD_VENDOR_DESCRIPTOR(0, 0, kEpnumCdc0DataOut, kEpnumCdc0DataIn, 512), + TUD_VENDOR_DESCRIPTOR(kItfNumVendor, 0, kEpnumCdc0DataOut, kEpnumCdc0DataIn, 512), + TUD_DFU_RT_DESCRIPTOR( + kItfNumDfuRuntime, 4, DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_WILL_DETACH, 1000, 1024), }; static_assert(sizeof(kConfigurationDescriptorHs) == kConfigTotalLen); @@ -192,6 +204,7 @@ class UsbDescriptors { static constexpr std::string_view kManufacturerString = "Alliance RoboMaster Team."; static constexpr std::string_view kProductString = "RMCS Agent v" LIBRMCS_PROJECT_VERSION_STRING; + static constexpr std::string_view kDfuRuntimeString = "DFU Runtime"; std::array serial_string_{"AF-0000-0000-0000-0000-0000-0000-0000-0000"}; std::array descriptor_string_buffer_{}; diff --git a/firmware/rmcs_board/src/usb/vendor.cpp b/firmware/rmcs_board/app/src/usb/vendor.cpp similarity index 80% rename from firmware/rmcs_board/src/usb/vendor.cpp rename to firmware/rmcs_board/app/src/usb/vendor.cpp index 0d833f0..544188d 100644 --- a/firmware/rmcs_board/src/usb/vendor.cpp +++ b/firmware/rmcs_board/app/src/usb/vendor.cpp @@ -1,4 +1,4 @@ -#include "firmware/rmcs_board/src/usb/vendor.hpp" +#include "firmware/rmcs_board/app/src/usb/vendor.hpp" #include #include @@ -7,6 +7,7 @@ #include #include "core/src/protocol/serializer.hpp" +#include "firmware/rmcs_board/app/src/utility/boot_mailbox.hpp" namespace librmcs::firmware::usb { @@ -24,6 +25,8 @@ void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t size) { {reinterpret_cast(buffer), size}, size < max_packet_size); } +void tud_dfu_runtime_reboot_to_dfu_cb() { boot::BootMailbox::reboot_to_bootloader(); } + void tud_suspend_cb(bool remote_wakeup_en) { (void)remote_wakeup_en; } void tud_resume_cb() {} diff --git a/firmware/rmcs_board/src/usb/vendor.hpp b/firmware/rmcs_board/app/src/usb/vendor.hpp similarity index 94% rename from firmware/rmcs_board/src/usb/vendor.hpp rename to firmware/rmcs_board/app/src/usb/vendor.hpp index 57fb4e9..cd0e6c9 100644 --- a/firmware/rmcs_board/src/usb/vendor.hpp +++ b/firmware/rmcs_board/app/src/usb/vendor.hpp @@ -17,11 +17,11 @@ #include "core/src/protocol/serializer.hpp" #include "core/src/utility/assert.hpp" #include "core/src/utility/immovable.hpp" -#include "firmware/rmcs_board/src/can/can.hpp" -#include "firmware/rmcs_board/src/uart/uart.hpp" -#include "firmware/rmcs_board/src/usb/interrupt_safe_buffer.hpp" -#include "firmware/rmcs_board/src/usb/usb_descriptors.hpp" -#include "firmware/rmcs_board/src/utility/lazy.hpp" +#include "firmware/rmcs_board/app/src/can/can.hpp" +#include "firmware/rmcs_board/app/src/uart/uart.hpp" +#include "firmware/rmcs_board/app/src/usb/interrupt_safe_buffer.hpp" +#include "firmware/rmcs_board/app/src/usb/usb_descriptors.hpp" +#include "firmware/rmcs_board/app/src/utility/lazy.hpp" namespace librmcs::firmware::usb { diff --git a/firmware/rmcs_board/src/utility/assert.cpp b/firmware/rmcs_board/app/src/utility/assert.cpp similarity index 100% rename from firmware/rmcs_board/src/utility/assert.cpp rename to firmware/rmcs_board/app/src/utility/assert.cpp diff --git a/firmware/rmcs_board/app/src/utility/boot_mailbox.hpp b/firmware/rmcs_board/app/src/utility/boot_mailbox.hpp new file mode 100644 index 0000000..369b5d5 --- /dev/null +++ b/firmware/rmcs_board/app/src/utility/boot_mailbox.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "core/src/utility/assert.hpp" + +namespace librmcs::firmware::boot { + +class BootMailbox { +public: + static void clear() { write_pair(0U, 0U); } + + [[noreturn]] static void reboot_to_bootloader() { + write_pair(kMailboxMagic, kMailboxRequestEnterDfu); + ppor_reset_mask_set_source_enable(HPM_PPOR, ppor_reset_software); + ppor_reset_set_hot_reset_enable(HPM_PPOR, ppor_reset_software); + ppor_sw_reset(HPM_PPOR, 10U); + while (true) {} + } + +private: + static constexpr uint32_t kMailboxMagic = 0x524D4353U; // "RMCS" + static constexpr uint32_t kMailboxRequestEnterDfu = 0x44465530U; // "DFU0" + static constexpr uint8_t kMagicGprIndex = 12U; + + static void write_pair(uint32_t magic, uint32_t request) { + uint32_t values[2]{magic, request}; + core::utility::assert_always( + sysctl_cpu0_set_gpr(HPM_SYSCTL, kMagicGprIndex, 2U, values, false) == status_success); + } +}; + +} // namespace librmcs::firmware::boot diff --git a/firmware/rmcs_board/src/utility/interrupt_lock.hpp b/firmware/rmcs_board/app/src/utility/interrupt_lock.hpp similarity index 100% rename from firmware/rmcs_board/src/utility/interrupt_lock.hpp rename to firmware/rmcs_board/app/src/utility/interrupt_lock.hpp diff --git a/firmware/rmcs_board/src/utility/lazy.hpp b/firmware/rmcs_board/app/src/utility/lazy.hpp similarity index 97% rename from firmware/rmcs_board/src/utility/lazy.hpp rename to firmware/rmcs_board/app/src/utility/lazy.hpp index 1d596f0..fe04ed1 100644 --- a/firmware/rmcs_board/src/utility/lazy.hpp +++ b/firmware/rmcs_board/app/src/utility/lazy.hpp @@ -7,7 +7,7 @@ #include #include "core/src/utility/assert.hpp" -#include "firmware/rmcs_board/src/utility/interrupt_lock.hpp" +#include "firmware/rmcs_board/app/src/utility/interrupt_lock.hpp" namespace librmcs::firmware::utility { diff --git a/firmware/rmcs_board/src/utility/ring_buffer.hpp b/firmware/rmcs_board/app/src/utility/ring_buffer.hpp similarity index 100% rename from firmware/rmcs_board/src/utility/ring_buffer.hpp rename to firmware/rmcs_board/app/src/utility/ring_buffer.hpp diff --git a/firmware/rmcs_board/bootloader/CMakeLists.txt b/firmware/rmcs_board/bootloader/CMakeLists.txt new file mode 100644 index 0000000..535c436 --- /dev/null +++ b/firmware/rmcs_board/bootloader/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.28) +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + +set(APP_NAME "rmcs_board_bootloader") + +include("${CMAKE_CURRENT_SOURCE_DIR}/../../common/cmake/librmcs_firmware.cmake") + +set(BOARD "hpm5300evk" CACHE STRING "Target board") +set(RV_ARCH "rv32imafdcb" CACHE STRING "RISC-V arch") +set(RV_ABI "ilp32d" CACHE STRING "RISC-V ABI") +set(HPM_BUILD_TYPE "flash_xip" CACHE STRING "HPM build type") + +set(CONFIG_TINYUSB 1) +set(CONFIG_USB_DEVICE 1) + +if(NOT DEFINED ENV{HPM_SDK_BASE} OR "$ENV{HPM_SDK_BASE}" STREQUAL "") + set(ENV{HPM_SDK_BASE} "${CMAKE_CURRENT_SOURCE_DIR}/../bsp/hpm_sdk") +endif() +find_package(hpm-sdk REQUIRED HINTS "$ENV{HPM_SDK_BASE}") + +librmcs_setup_project_context(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../..") + +project(rmcs_board_bootloader VERSION 3 LANGUAGES C CXX ASM) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +sdk_compile_options(-Wall -Wextra -Wpedantic -fvisibility=hidden) +sdk_compile_options($<$:-fno-exceptions>) +sdk_compile_options($<$:-fno-rtti>) +sdk_compile_options($<$:-fno-unwind-tables>) +sdk_compile_options($<$:-fno-asynchronous-unwind-tables>) +sdk_compile_options($<$:-fno-threadsafe-statics>) +sdk_compile_options($<$:-fno-use-cxa-atexit>) +if(LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS) + sdk_compile_options(${LIBRMCS_SOURCE_PATH_MAP_COMPILE_OPTIONS}) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "debug") + sdk_compile_options(-Og) +endif() + +sdk_compile_definitions(-DLIBRMCS_PROJECT_VERSION_STRING="${LIBRMCS_PROJECT_VERSION}") +sdk_compile_definitions(-DCFG_TUSB_MCU=OPT_MCU_HPM) +sdk_compile_definitions(-DUSB_HOST_MCU_CORE=HPM_CORE0) + +sdk_inc("${CMAKE_CURRENT_SOURCE_DIR}/include") +sdk_app_inc("${LIBRMCS_PROJECT_ROOT}") + +file(GLOB_RECURSE PROJECT_SOURCE CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" +) +sdk_app_src(${PROJECT_SOURCE}) +sdk_app_src( + "$ENV{HPM_SDK_BASE}/middleware/tinyusb/src/device/usbd.c" + "$ENV{HPM_SDK_BASE}/middleware/tinyusb/src/device/usbd_control.c" + "$ENV{HPM_SDK_BASE}/middleware/tinyusb/src/portable/hpm/dcd_hpm.c" + "$ENV{HPM_SDK_BASE}/middleware/tinyusb/src/class/dfu/dfu_device.c" +) + +sdk_linker_global_symbols("_flash_size=0x1F000") + +set_property(TARGET app PROPERTY CXX_STANDARD 23) +set_property(TARGET app PROPERTY CXX_STANDARD_REQUIRED ON) +set_property(TARGET app PROPERTY CXX_EXTENSIONS OFF) + +get_target_property(_hpm_incs ${HPM_SDK_LIB_ITF} INTERFACE_INCLUDE_DIRECTORIES) +if(_hpm_incs) + set_target_properties(${HPM_SDK_LIB_ITF} PROPERTIES + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_hpm_incs}") +endif() diff --git a/firmware/rmcs_board/bootloader/include/tusb_config.h b/firmware/rmcs_board/bootloader/include/tusb_config.h new file mode 100644 index 0000000..42133cf --- /dev/null +++ b/firmware/rmcs_board/bootloader/include/tusb_config.h @@ -0,0 +1,53 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CFG_TUSB_MCU +# error CFG_TUSB_MCU must be defined +#endif + +#ifndef BOARD_DEVICE_RHPORT_NUM +# define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +#ifndef BOARD_DEVICE_RHPORT_SPEED +# define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED +#endif + +#if BOARD_DEVICE_RHPORT_NUM == 0 +# define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 +# define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else +# error "Incorrect RHPort configuration" +#endif + +#define CFG_TUSB_OS OPT_OS_NONE + +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION __attribute__((section(".noncacheable.non_init"))) +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +# define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +# define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_DFU_RUNTIME 0 +#define CFG_TUD_DFU 1 + +#define CFG_TUD_DFU_XFER_BUFSIZE 1024 + +#ifdef __cplusplus +} +#endif diff --git a/firmware/rmcs_board/bootloader/src/crypto/sha256.hpp b/firmware/rmcs_board/bootloader/src/crypto/sha256.hpp new file mode 100644 index 0000000..262624a --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/crypto/sha256.hpp @@ -0,0 +1,202 @@ +#pragma once + +/********************************************************************* +* Filename: sha256.hpp +* Original: Brad Conte (brad AT bradconte.com) +* https://github.com/B-Con/crypto-algorithms (public domain) +* Modified: Refactored to header-only C++ with namespace, std types, +* and named helper functions. +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Header-only adaptation of the SHA-256 hashing algorithm. + SHA-256 is one of the three algorithms in the SHA2 + specification. The others, SHA-384 and SHA-512, are not + offered in this implementation. + Algorithm specification can be found here: + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf + This implementation uses big-endian (network) byte order: + sha256_transform reads message words as big-endian, and + sha256_final stores the bit length and digest in big-endian + order per the SHA-256 specification. +*********************************************************************/ + +#include +#include +#include + +namespace librmcs::firmware::crypto { + +inline constexpr size_t kSha256DigestSize = 32U; + +struct Sha256Ctx { + std::array data{}; + uint32_t datalen = 0U; + uint64_t bitlen = 0U; + std::array state{}; +}; + +namespace detail { + +inline constexpr uint32_t kChunkBytes = 64U; +inline constexpr uint32_t kPaddingBytes = 56U; +inline constexpr uint32_t kBitLengthIncrement = 512U; + +inline constexpr std::array kRoundConstants = { + 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, + 0xab1c5ed5U, 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, + 0x9bdc06a7U, 0xc19bf174U, 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, + 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, + 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, + 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, 0xa2bfe8a1U, 0xa81a664bU, + 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, 0x19a4c116U, + 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, + 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, + 0xc67178f2U, +}; + +inline uint32_t rotate_right(uint32_t value, uint32_t bits) { + return (value >> bits) | (value << (32U - bits)); +} + +inline uint32_t choose(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ ((~x) & z); } + +inline uint32_t majority(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); } + +inline uint32_t big_sigma0(uint32_t x) { + return rotate_right(x, 2U) ^ rotate_right(x, 13U) ^ rotate_right(x, 22U); +} + +inline uint32_t big_sigma1(uint32_t x) { + return rotate_right(x, 6U) ^ rotate_right(x, 11U) ^ rotate_right(x, 25U); +} + +inline uint32_t small_sigma0(uint32_t x) { + return rotate_right(x, 7U) ^ rotate_right(x, 18U) ^ (x >> 3U); +} + +inline uint32_t small_sigma1(uint32_t x) { + return rotate_right(x, 17U) ^ rotate_right(x, 19U) ^ (x >> 10U); +} + +inline void sha256_transform(Sha256Ctx* ctx, const uint8_t* data) { + std::array message_schedule; + + for (uint32_t index = 0U; index < 16U; ++index) { + const uint32_t data_offset = index * 4U; + message_schedule[index] = (static_cast(data[data_offset]) << 24U) + | (static_cast(data[data_offset + 1U]) << 16U) + | (static_cast(data[data_offset + 2U]) << 8U) + | static_cast(data[data_offset + 3U]); + } + + for (uint32_t index = 16U; index < 64U; ++index) { + message_schedule[index] = + small_sigma1(message_schedule[index - 2U]) + message_schedule[index - 7U] + + small_sigma0(message_schedule[index - 15U]) + message_schedule[index - 16U]; + } + + uint32_t a = ctx->state[0]; + uint32_t b = ctx->state[1]; + uint32_t c = ctx->state[2]; + uint32_t d = ctx->state[3]; + uint32_t e = ctx->state[4]; + uint32_t f = ctx->state[5]; + uint32_t g = ctx->state[6]; + uint32_t h = ctx->state[7]; + + for (uint32_t index = 0U; index < 64U; ++index) { + const uint32_t temporary_1 = + h + big_sigma1(e) + choose(e, f, g) + kRoundConstants[index] + message_schedule[index]; + const uint32_t temporary_2 = big_sigma0(a) + majority(a, b, c); + h = g; + g = f; + f = e; + e = d + temporary_1; + d = c; + c = b; + b = a; + a = temporary_1 + temporary_2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +} // namespace detail + +inline void sha256_init(Sha256Ctx* ctx) { + ctx->datalen = 0U; + ctx->bitlen = 0U; + ctx->state[0] = 0x6a09e667U; + ctx->state[1] = 0xbb67ae85U; + ctx->state[2] = 0x3c6ef372U; + ctx->state[3] = 0xa54ff53aU; + ctx->state[4] = 0x510e527fU; + ctx->state[5] = 0x9b05688cU; + ctx->state[6] = 0x1f83d9abU; + ctx->state[7] = 0x5be0cd19U; +} + +inline void sha256_update(Sha256Ctx* ctx, const uint8_t* data, size_t len) { + for (size_t index = 0U; index < len; ++index) { + ctx->data[ctx->datalen] = data[index]; + ++ctx->datalen; + + if (ctx->datalen == detail::kChunkBytes) { + detail::sha256_transform(ctx, ctx->data.data()); + ctx->bitlen += detail::kBitLengthIncrement; + ctx->datalen = 0U; + } + } +} + +inline void sha256_final(Sha256Ctx* ctx, uint8_t* hash) { + uint32_t data_index = ctx->datalen; + + if (ctx->datalen < detail::kPaddingBytes) { + ctx->data[data_index++] = 0x80U; + while (data_index < detail::kPaddingBytes) { + ctx->data[data_index++] = 0x00U; + } + } else { + ctx->data[data_index++] = 0x80U; + while (data_index < detail::kChunkBytes) { + ctx->data[data_index++] = 0x00U; + } + detail::sha256_transform(ctx, ctx->data.data()); + + for (uint32_t index = 0U; index < detail::kPaddingBytes; ++index) { + ctx->data[index] = 0x00U; + } + } + + ctx->bitlen += static_cast(ctx->datalen) * 8U; + ctx->data[63] = static_cast(ctx->bitlen); + ctx->data[62] = static_cast(ctx->bitlen >> 8U); + ctx->data[61] = static_cast(ctx->bitlen >> 16U); + ctx->data[60] = static_cast(ctx->bitlen >> 24U); + ctx->data[59] = static_cast(ctx->bitlen >> 32U); + ctx->data[58] = static_cast(ctx->bitlen >> 40U); + ctx->data[57] = static_cast(ctx->bitlen >> 48U); + ctx->data[56] = static_cast(ctx->bitlen >> 56U); + detail::sha256_transform(ctx, ctx->data.data()); + + for (uint32_t index = 0U; index < 4U; ++index) { + const uint32_t shift = 24U - (index * 8U); + hash[index] = static_cast((ctx->state[0] >> shift) & 0xFFU); + hash[index + 4U] = static_cast((ctx->state[1] >> shift) & 0xFFU); + hash[index + 8U] = static_cast((ctx->state[2] >> shift) & 0xFFU); + hash[index + 12U] = static_cast((ctx->state[3] >> shift) & 0xFFU); + hash[index + 16U] = static_cast((ctx->state[4] >> shift) & 0xFFU); + hash[index + 20U] = static_cast((ctx->state[5] >> shift) & 0xFFU); + hash[index + 24U] = static_cast((ctx->state[6] >> shift) & 0xFFU); + hash[index + 28U] = static_cast((ctx->state[7] >> shift) & 0xFFU); + } +} + +} // namespace librmcs::firmware::crypto diff --git a/firmware/rmcs_board/bootloader/src/flash/layout.hpp b/firmware/rmcs_board/bootloader/src/flash/layout.hpp new file mode 100644 index 0000000..137e377 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/flash/layout.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include + +namespace librmcs::firmware::flash { + +inline constexpr uintptr_t kFlashBaseAddress = BOARD_FLASH_BASE_ADDRESS; +inline constexpr uintptr_t kMetadataStartAddress = 0x8001F000U; +inline constexpr uintptr_t kMetadataEndAddress = 0x80020000U; +inline constexpr uintptr_t kAppStartAddress = 0x80020000U; +inline constexpr uintptr_t kAppEntryAddress = kAppStartAddress + sizeof(uint32_t); +inline constexpr uintptr_t kAppEndAddress = BOARD_FLASH_BASE_ADDRESS + BOARD_FLASH_SIZE; +inline constexpr size_t kAppMaxImageSize = kAppEndAddress - kAppStartAddress; +inline constexpr uint32_t kFlashSectorSize = 4096U; + +static_assert(kMetadataEndAddress == kAppStartAddress); +static_assert((kMetadataStartAddress % kFlashSectorSize) == 0U); +static_assert((kAppStartAddress % kFlashSectorSize) == 0U); +static_assert((kAppEndAddress % kFlashSectorSize) == 0U); + +} // namespace librmcs::firmware::flash diff --git a/firmware/rmcs_board/bootloader/src/flash/metadata.hpp b/firmware/rmcs_board/bootloader/src/flash/metadata.hpp new file mode 100644 index 0000000..14d6703 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/flash/metadata.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include + +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/xpi_nor.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::flash { + +class Metadata { +public: + static Metadata& get_instance() { + static Metadata image_metadata; + return image_metadata; + } + + bool is_ready() const { return latest_valid_slot_state_ == DataSlotState::kReady; } + bool is_flashing() const { return latest_valid_slot_state_ == DataSlotState::kFlashing; } + + uint32_t image_size() const { return latest_valid_slot_->image_size; } + uint32_t image_crc32() const { return latest_valid_slot_->image_crc32; } + + void begin_flashing() { + if (!latest_valid_slot_ || latest_valid_slot_state_ == DataSlotState::kFatal) { + erase_and_rescan(); + } else if (latest_valid_slot_state_ == DataSlotState::kEmpty) { + + } else if (latest_valid_slot_state_ == DataSlotState::kFlashing) { + return; + } else if (latest_valid_slot_state_ == DataSlotState::kReady) { + auto next_addr = reinterpret_cast(latest_valid_slot_) + sizeof(DataSlot); + if (next_addr >= kMetadataEndAddress) + erase_and_rescan(); + else + latest_valid_slot_ = reinterpret_cast(next_addr); + } + + latest_valid_slot_->enter_flashing_state(); + latest_valid_slot_state_ = DataSlotState::kFlashing; + } + + void finish_flashing(uint32_t size, uint32_t crc32) { + utility::assert_debug(latest_valid_slot_state_ == DataSlotState::kFlashing); + + latest_valid_slot_->enter_ready_state(size, crc32); + latest_valid_slot_state_ = DataSlotState::kReady; + } + +private: + Metadata() { scan_latest_valid_slot(); } + + static constexpr uint32_t kFlashWordErased = 0xFFFFFFFFU; + static constexpr uint32_t kImageMetadataMagic = 0x524D4353U; // "RMCS" + static constexpr uint32_t kImageStateReady = 0x494D5244U; // "IMRD" + + enum class DataSlotState : uint8_t { + kFatal, + kEmpty, + kFlashing, + kReady, + }; + + struct DataSlot { + volatile uint32_t magic; + volatile uint32_t image_state; + volatile uint32_t image_size; + volatile uint32_t image_crc32; + + DataSlotState read_state() const { + switch (magic) { + case kFlashWordErased: + return (image_state == kFlashWordErased && image_size == kFlashWordErased + && image_crc32 == kFlashWordErased) + ? DataSlotState::kEmpty + : DataSlotState::kFatal; + + case kImageMetadataMagic: + switch (image_state) { + case kFlashWordErased: + return (image_size == kFlashWordErased && image_crc32 == kFlashWordErased) + ? DataSlotState::kFlashing + : DataSlotState::kFatal; + case kImageStateReady: + return (image_size <= kAppMaxImageSize) ? DataSlotState::kReady + : DataSlotState::kFatal; + default: return DataSlotState::kFatal; + } + + default: return DataSlotState::kFatal; + } + } + + void enter_flashing_state() { + utility::assert_debug(read_state() == DataSlotState::kEmpty); + + XpiNor::instance().program_word( + reinterpret_cast(&magic), kImageMetadataMagic); + + utility::assert_always(read_state() == DataSlotState::kFlashing); + } + + void enter_ready_state(uint32_t size, uint32_t crc32) { + utility::assert_debug(read_state() == DataSlotState::kFlashing); + + auto& xpi_nor = XpiNor::instance(); + xpi_nor.program_word(reinterpret_cast(&image_size), size); + xpi_nor.program_word(reinterpret_cast(&image_crc32), crc32); + xpi_nor.program_word(reinterpret_cast(&image_state), kImageStateReady); + + utility::assert_always(read_state() == DataSlotState::kReady); + } + }; + + void scan_latest_valid_slot() { + static_assert(kMetadataStartAddress % sizeof(DataSlot) == 0U); + static_assert(kMetadataEndAddress % sizeof(DataSlot) == 0U); + + latest_valid_slot_ = nullptr; + latest_valid_slot_state_ = DataSlotState::kFatal; + + for (uintptr_t addr = kMetadataStartAddress; addr < kMetadataEndAddress; + addr += sizeof(DataSlot)) { + auto& slot = *reinterpret_cast(addr); + switch (const auto state = slot.read_state()) { + case DataSlotState::kFatal: + latest_valid_slot_ = nullptr; + latest_valid_slot_state_ = DataSlotState::kFatal; + return; + + case DataSlotState::kEmpty: + if (!latest_valid_slot_) { + latest_valid_slot_ = &slot; + latest_valid_slot_state_ = DataSlotState::kEmpty; + } + break; + + case DataSlotState::kFlashing: + case DataSlotState::kReady: + if (!latest_valid_slot_ + || (reinterpret_cast(latest_valid_slot_) + sizeof(DataSlot) + == reinterpret_cast(&slot) + && latest_valid_slot_state_ == DataSlotState::kReady)) { + latest_valid_slot_ = &slot; + latest_valid_slot_state_ = state; + } else { + latest_valid_slot_ = nullptr; + latest_valid_slot_state_ = DataSlotState::kFatal; + return; + } + break; + } + } + } + + void erase_and_rescan() { + XpiNor::instance().erase_sector(kMetadataStartAddress); + scan_latest_valid_slot(); + utility::assert_always( + latest_valid_slot_ != nullptr && latest_valid_slot_state_ == DataSlotState::kEmpty); + } + + DataSlot* latest_valid_slot_ = nullptr; + DataSlotState latest_valid_slot_state_ = DataSlotState::kFatal; +}; + +} // namespace librmcs::firmware::flash diff --git a/firmware/rmcs_board/bootloader/src/flash/validation.hpp b/firmware/rmcs_board/bootloader/src/flash/validation.hpp new file mode 100644 index 0000000..28e9084 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/flash/validation.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include + +#include "firmware/rmcs_board/bootloader/src/crypto/sha256.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/metadata.hpp" + +namespace librmcs::firmware::flash { + +inline constexpr uint32_t kCrc32Polynomial = 0xEDB88320U; +inline constexpr uint32_t kImageHashMagic = 0x48415348U; // "HASH" +inline constexpr uint32_t kImageHashSuffixSize = + sizeof(uint32_t) + static_cast(crypto::kSha256DigestSize); + +inline bool has_valid_app_signature() { + return *reinterpret_cast(kAppStartAddress) == BOARD_UF2_SIGNATURE; +} + +inline uint32_t compute_image_crc32(uintptr_t address, uint32_t size) { + const auto* data = reinterpret_cast(address); + uint32_t crc = 0xFFFFFFFFU; + + for (uint32_t i = 0U; i < size; ++i) { + crc ^= data[i]; + for (uint8_t bit = 0U; bit < 8U; ++bit) { + const bool lsb_set = (crc & 0x1U) != 0U; + crc >>= 1U; + if (lsb_set) + crc ^= kCrc32Polynomial; + } + } + + return ~crc; +} + +inline void compute_image_sha256(uintptr_t address, uint32_t size, uint8_t* hash) { + const auto* data = reinterpret_cast(address); + crypto::Sha256Ctx ctx; + crypto::sha256_init(&ctx); + crypto::sha256_update(&ctx, data, size); + crypto::sha256_final(&ctx, hash); +} + +inline bool validate_image_hash(uintptr_t address, uint32_t size) { + if (size <= kImageHashSuffixSize) + return false; + + const auto* suffix_ptr = + reinterpret_cast(address + size - kImageHashSuffixSize); + + uint32_t suffix_magic = 0U; + std::memcpy(&suffix_magic, suffix_ptr, sizeof(suffix_magic)); + if (suffix_magic != kImageHashMagic) + return false; + + const uint32_t firmware_size = size - kImageHashSuffixSize; + const uint8_t* expected_sha256 = suffix_ptr + sizeof(uint32_t); + + uint8_t computed_sha256[crypto::kSha256DigestSize]; + compute_image_sha256(address, firmware_size, computed_sha256); + + return std::memcmp(computed_sha256, expected_sha256, crypto::kSha256DigestSize) == 0; +} + +inline bool validate_candidate_image(uint32_t size) { + if (size == 0U || size > kAppMaxImageSize) + return false; + + if (!has_valid_app_signature()) + return false; + + return validate_image_hash(kAppStartAddress, size); +} + +inline bool validate_app_image() { + auto& meta = Metadata::get_instance(); + if (!meta.is_ready()) + return false; + + const uint32_t size = meta.image_size(); + if (!validate_candidate_image(size)) + return false; + + const uint32_t crc32 = compute_image_crc32(kAppStartAddress, size); + return crc32 == meta.image_crc32(); +} + +} // namespace librmcs::firmware::flash diff --git a/firmware/rmcs_board/bootloader/src/flash/writer.hpp b/firmware/rmcs_board/bootloader/src/flash/writer.hpp new file mode 100644 index 0000000..cd79290 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/flash/writer.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/include/tusb_config.h" +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/xpi_nor.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::flash { + +class Writer { +public: + static constexpr uint32_t kTransferBlockSize = CFG_TUD_DFU_XFER_BUFSIZE; + + void begin_session() { clear_active_sector(); } + + void finish_session() { + if (!has_active_sector_) + return; + + commit_active_sector_if_needed(); + clear_active_sector(); + } + + void abort_session() { clear_active_sector(); } + + void write(uintptr_t address, std::span data) { + utility::assert_debug(!data.empty()); + utility::assert_debug(address >= kAppStartAddress && address < kAppEndAddress); + utility::assert_debug( + static_cast(address) + data.size() <= static_cast(kAppEndAddress)); + + size_t input_offset = 0U; + while (input_offset < data.size()) { + const uintptr_t write_address = address + input_offset; + const uintptr_t sector_address = + write_address - (write_address % XpiNor::instance().sector_size()); + activate_sector(sector_address); + + const uintptr_t sector_offset = write_address - active_sector_address_; + const size_t writable = + static_cast(XpiNor::instance().sector_size() - sector_offset); + const size_t chunk_size = std::min(writable, data.size() - input_offset); + + utility::assert_debug(sector_offset == buffered_size_); + std::memcpy( + writable_sector_buffer_bytes().data() + sector_offset, data.data() + input_offset, + chunk_size); + advance_buffer(sector_offset, chunk_size); + + input_offset += chunk_size; + if ((sector_offset + chunk_size) == XpiNor::instance().sector_size()) { + commit_active_sector_if_needed(); + clear_active_sector(); + } + } + } + +private: + static constexpr size_t kSectorBufferCapacity = kFlashSectorSize; + static constexpr size_t kSectorBufferWordCount = kSectorBufferCapacity / sizeof(uint32_t); + static constexpr uintptr_t kInvalidAddress = ~static_cast(0U); + + static std::span writable_sector_buffer_bytes() { + return std::as_writable_bytes(std::span(sector_buffer_)); + } + + static std::span sector_buffer_bytes() { + return std::as_bytes(std::span(sector_buffer_)); + } + + void activate_sector(uintptr_t sector_address) { + if (has_active_sector_ && active_sector_address_ == sector_address) + return; + + if (has_active_sector_) { + commit_active_sector_if_needed(); + clear_active_sector(); + } + + utility::assert_debug((sector_address % kSectorBufferCapacity) == 0U); + has_active_sector_ = true; + active_sector_address_ = sector_address; + buffered_size_ = 0U; + } + + void advance_buffer(uintptr_t offset, size_t size) { + utility::assert_debug(has_active_sector_); + utility::assert_debug(size > 0U); + utility::assert_debug(offset <= static_cast(SIZE_MAX) - size); + + buffered_size_ = std::max(buffered_size_, static_cast(offset) + size); + } + + bool has_buffered_data() const { return has_active_sector_ && buffered_size_ > 0U; } + + void clear_active_sector() { + has_active_sector_ = false; + active_sector_address_ = kInvalidAddress; + buffered_size_ = 0U; + } + + void commit_active_sector_if_needed() { + utility::assert_debug(has_active_sector_); + if (!has_buffered_data()) + return; + + const auto* flash_ptr = reinterpret_cast(active_sector_address_); + const bool is_same = + std::memcmp(flash_ptr, sector_buffer_bytes().data(), buffered_size_) == 0; + if (!is_same) { + auto& xpi_nor = XpiNor::instance(); + xpi_nor.erase_sector(active_sector_address_); + program_buffer(active_sector_address_, buffered_size_); + } + + buffered_size_ = 0U; + } + + static void program_buffer(uintptr_t address, size_t size) { + utility::assert_debug((address & 0x3U) == 0U); + + const size_t full_word_count = size / sizeof(uint32_t); + if (full_word_count > 0U) { + XpiNor::instance().program_words( + address, std::span(sector_buffer_.data(), full_word_count)); + } + + const size_t full_word_bytes = full_word_count * sizeof(uint32_t); + const size_t tail_size = size - full_word_bytes; + if (tail_size == 0U) + return; + + uint32_t tail_word = 0xFFFFFFFFU; + std::memcpy(&tail_word, sector_buffer_bytes().data() + full_word_bytes, tail_size); + XpiNor::instance().program_word(address + full_word_bytes, tail_word); + } + + static_assert((kSectorBufferCapacity % sizeof(uint32_t)) == 0U); + alignas(4) static inline std::array sector_buffer_{}; + + bool has_active_sector_ = false; + uintptr_t active_sector_address_ = kInvalidAddress; + size_t buffered_size_ = 0U; +}; + +} // namespace librmcs::firmware::flash diff --git a/firmware/rmcs_board/bootloader/src/flash/xpi_nor.hpp b/firmware/rmcs_board/bootloader/src/flash/xpi_nor.hpp new file mode 100644 index 0000000..32c537d --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/flash/xpi_nor.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::flash { + +class XpiNor { +public: + static XpiNor& instance() { + static XpiNor flash; + return flash; + } + + uint32_t sector_size() const { return sector_size_; } + + void erase_sector(uintptr_t address) { + utility::assert_debug((address % sector_size_) == 0U); + + const uint32_t irq_flags = disable_global_irq(CSR_MSTATUS_MIE_MASK); + const hpm_stat_t status = rom_xpi_nor_erase_sector( + BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &nor_config_, + to_flash_offset(address)); + restore_global_irq(irq_flags & CSR_MSTATUS_MIE_MASK); + utility::assert_always(status == status_success); + + invalidate_range(address, sector_size_); + } + + void program_word(uintptr_t address, uint32_t data) { + program_words(address, std::span(&data, 1U)); + } + + void program_words(uintptr_t address, std::span data) { + if (data.empty()) + return; + + utility::assert_debug((address & 0x3U) == 0U); + utility::assert_debug( + (reinterpret_cast(data.data()) & (alignof(uint32_t) - 1U)) == 0U); + + writeback_range(reinterpret_cast(data.data()), data.size_bytes()); + + const uint32_t irq_flags = disable_global_irq(CSR_MSTATUS_MIE_MASK); + const hpm_stat_t status = rom_xpi_nor_program( + BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &nor_config_, data.data(), + to_flash_offset(address), static_cast(data.size_bytes())); + restore_global_irq(irq_flags & CSR_MSTATUS_MIE_MASK); + utility::assert_always(status == status_success); + + invalidate_range(address, data.size_bytes()); + } + +private: + static uint32_t to_flash_offset(uintptr_t address) { + utility::assert_debug(address >= kFlashBaseAddress); + return static_cast(address - BOARD_FLASH_BASE_ADDRESS); + } + + static void invalidate_range(uintptr_t address, size_t size) { + const uintptr_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN(address); + const uintptr_t aligned_end = HPM_L1C_CACHELINE_ALIGN_UP(address + size); + l1c_dc_invalidate( + static_cast(aligned_start), + static_cast(aligned_end - aligned_start)); + } + + static void writeback_range(uintptr_t address, size_t size) { + const uintptr_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN(address); + const uintptr_t aligned_end = HPM_L1C_CACHELINE_ALIGN_UP(address + size); + l1c_dc_writeback( + static_cast(aligned_start), + static_cast(aligned_end - aligned_start)); + } + + XpiNor() { + xpi_nor_config_option_t option{}; + option.header.U = BOARD_APP_XPI_NOR_CFG_OPT_HDR; + option.option0.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT0; + option.option1.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT1; + + utility::assert_always( + rom_xpi_nor_auto_config(BOARD_APP_XPI_NOR_XPI_BASE, &nor_config_, &option) + == status_success); + utility::assert_always( + rom_xpi_nor_get_property( + BOARD_APP_XPI_NOR_XPI_BASE, &nor_config_, xpi_nor_property_sector_size, + §or_size_) + == status_success); + utility::assert_always(sector_size_ == kFlashSectorSize); + } + + xpi_nor_config_t nor_config_{}; + uint32_t sector_size_ = 0U; +}; + +} // namespace librmcs::firmware::flash diff --git a/firmware/rmcs_board/bootloader/src/main.cpp b/firmware/rmcs_board/bootloader/src/main.cpp new file mode 100644 index 0000000..e844ab2 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/main.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/flash/validation.hpp" +#include "firmware/rmcs_board/bootloader/src/usb/dfu.hpp" +#include "firmware/rmcs_board/bootloader/src/usb/usb_descriptors.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/boot_mailbox.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/jump.hpp" + +int main() { + using namespace librmcs::firmware; // NOLINT(google-build-using-namespace) + + // Reset-time clocks already run CPU0 at 360 MHz, so board_init() would only bump it to + // 480 MHz while adding avoidable startup latency on the direct-to-app path. + const bool force_dfu = boot::BootMailbox::consume_enter_dfu_request(); + if (!force_dfu && flash::validate_app_image()) + utility::jump_to_app(); + + board_init(); + board_init_usb(HPM_USB0); + (void)usb::get_usb_descriptors(); + + const tusb_rhport_init_t init_config{ + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_FULL, + }; + utility::assert_always(tusb_rhport_init(0, &init_config)); + + while (true) { + tud_task(); + usb::Dfu::instance().poll(); + } +} diff --git a/firmware/rmcs_board/bootloader/src/usb/dfu.cpp b/firmware/rmcs_board/bootloader/src/usb/dfu.cpp new file mode 100644 index 0000000..3e3a2b9 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/usb/dfu.cpp @@ -0,0 +1,29 @@ +#include "firmware/rmcs_board/bootloader/src/usb/dfu.hpp" + +#include + +#include + +namespace librmcs::firmware::usb { + +extern "C" { + +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state) { + return Dfu::get_timeout_ms(alt, state); +} + +void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, uint16_t length) { + tud_dfu_finish_flashing(Dfu::instance().download(alt, block_num, data, length)); +} + +void tud_dfu_manifest_cb(uint8_t alt) { tud_dfu_finish_flashing(Dfu::instance().manifest(alt)); } + +uint16_t tud_dfu_upload_cb(uint8_t, uint16_t, uint8_t*, uint16_t) { return 0; } + +void tud_dfu_detach_cb() { Dfu::detach(); } + +void tud_dfu_abort_cb(uint8_t alt) { Dfu::instance().abort(alt); } + +} // extern "C" + +} // namespace librmcs::firmware::usb diff --git a/firmware/rmcs_board/bootloader/src/usb/dfu.hpp b/firmware/rmcs_board/bootloader/src/usb/dfu.hpp new file mode 100644 index 0000000..4420411 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/usb/dfu.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/metadata.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/validation.hpp" +#include "firmware/rmcs_board/bootloader/src/flash/writer.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/boot_mailbox.hpp" + +namespace librmcs::firmware::usb { + +class Dfu { +public: + static Dfu& instance() { + static Dfu dfu; + return dfu; + } + + static uint32_t get_timeout_ms(uint8_t alt, uint8_t state) { + if (alt != kDfuAltFlash) + return 0U; + + if (state == DFU_DNBUSY) + return 1U; + + if (state == DFU_MANIFEST) + return 100U; + + return 0U; + } + + uint8_t download(uint8_t alt, uint16_t block_num, const uint8_t* data, uint16_t length) { + if (alt != kDfuAltFlash) + return DFU_STATUS_ERR_TARGET; + + if (length == 0U) + return DFU_STATUS_ERR_NOTDONE; + + if (session_started_ && block_num == 0U) + reset_transfer_state(); + + if (!session_started_) { + if (block_num != 0U) + return DFU_STATUS_ERR_ADDRESS; + + flash_writer_.begin_session(); + flash::Metadata::get_instance().begin_flashing(); + + session_started_ = true; + expected_block_ = 0U; + downloaded_size_ = 0U; + reset_requested_ = false; + } + + if (block_num != expected_block_) + return fail(DFU_STATUS_ERR_ADDRESS); + + const uint64_t write_address_64 = static_cast(flash::kAppStartAddress) + + static_cast(downloaded_size_); + const uint64_t write_end_64 = write_address_64 + static_cast(length); + const uint64_t downloaded_size_64 = + static_cast(downloaded_size_) + static_cast(length); + + if (write_address_64 >= static_cast(flash::kAppEndAddress)) + return fail(DFU_STATUS_ERR_ADDRESS); + if (write_end_64 > static_cast(flash::kAppEndAddress)) + return fail(DFU_STATUS_ERR_ADDRESS); + if (downloaded_size_64 > static_cast(flash::kAppMaxImageSize)) + return fail(DFU_STATUS_ERR_ADDRESS); + + const auto payload = std::span( + reinterpret_cast(data), static_cast(length)); + flash_writer_.write(static_cast(write_address_64), payload); + + downloaded_size_ = static_cast(downloaded_size_64); + expected_block_ = static_cast(expected_block_ + 1U); + return DFU_STATUS_OK; + } + + uint8_t manifest(uint8_t alt) { + if (alt != kDfuAltFlash) + return DFU_STATUS_ERR_TARGET; + + if (!session_started_ || downloaded_size_ == 0U) + return DFU_STATUS_ERR_NOTDONE; + + flash_writer_.finish_session(); + + if (!flash::validate_candidate_image(downloaded_size_)) + return fail(DFU_STATUS_ERR_FIRMWARE); + + const uint32_t image_crc32 = + flash::compute_image_crc32(flash::kAppStartAddress, downloaded_size_); + auto& metadata = flash::Metadata::get_instance(); + metadata.finish_flashing(downloaded_size_, image_crc32); + + if (!metadata.is_ready() || metadata.image_size() != downloaded_size_ + || metadata.image_crc32() != image_crc32) { + return fail(DFU_STATUS_ERR_FIRMWARE); + } + + reset_transfer_state(); + reset_requested_ = true; + reset_requested_tick_ = mchtmr_get_count(HPM_MCHTMR); + return DFU_STATUS_OK; + } + + void abort(uint8_t alt) { + if (alt != kDfuAltFlash) + return; + + reset_transfer_state(); + } + + [[noreturn]] static void detach() { + boot::BootMailbox::clear(); + boot::BootMailbox::reboot(); + } + + void poll() const { + if (!reset_requested_) + return; + + const uint64_t elapsed = mchtmr_get_count(HPM_MCHTMR) - reset_requested_tick_; + const uint64_t reset_delay_ticks = + (static_cast(clock_get_frequency(clock_mchtmr0)) * kResetDelayMs) / 1000U; + if (elapsed < reset_delay_ticks) + return; + + detach(); + } + +private: + static constexpr uint8_t kDfuAltFlash = 0U; + static constexpr uint32_t kResetDelayMs = 1500U; + + Dfu() = default; + + uint8_t fail(uint8_t status) { + reset_transfer_state(); + return status; + } + + void reset_transfer_state() { + flash_writer_.abort_session(); + expected_block_ = 0U; + downloaded_size_ = 0U; + session_started_ = false; + } + + flash::Writer flash_writer_{}; + uint16_t expected_block_ = 0U; + uint32_t downloaded_size_ = 0U; + bool session_started_ = false; + bool reset_requested_ = false; + uint64_t reset_requested_tick_ = 0U; +}; + +} // namespace librmcs::firmware::usb diff --git a/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.cpp b/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.cpp new file mode 100644 index 0000000..927205e --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.cpp @@ -0,0 +1,28 @@ +#include "firmware/rmcs_board/bootloader/src/usb/usb_descriptors.hpp" + +#include + +namespace librmcs::firmware::usb { + +UsbDescriptors& get_usb_descriptors() { + static UsbDescriptors descriptors; + return descriptors; +} + +extern "C" { + +uint8_t const* tud_descriptor_device_cb(void) { + return get_usb_descriptors().get_device_descriptor(); +} + +uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { + return get_usb_descriptors().get_configuration_descriptor(index); +} + +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + return get_usb_descriptors().get_string_descriptor(index, langid); +} + +} // extern "C" + +} // namespace librmcs::firmware::usb diff --git a/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.hpp b/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.hpp new file mode 100644 index 0000000..460c929 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/usb/usb_descriptors.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::usb { + +class UsbDescriptors { +public: + UsbDescriptors() { update_serial_string(); } + + static uint8_t const* get_device_descriptor() { + return reinterpret_cast(&kDeviceDescriptor); + } + + static uint8_t const* get_configuration_descriptor(uint8_t index) { + (void)index; + return kConfigurationDescriptorFs; + } + + uint16_t const* get_string_descriptor(uint8_t index, uint16_t langid) { + (void)langid; + uint8_t str_size = 0U; + + if (index == 0U) { + std::memcpy(&descriptor_string_buffer_[1], kLanguageId.data(), kLanguageId.size()); + str_size = 1U; + } else { + std::string_view str; + switch (index) { + case 1: str = kManufacturerString; break; + case 2: str = kProductString; break; + case 3: + str = std::string_view{serial_string_.data(), serial_string_.size() - 1U}; + break; + case 4: str = kAlt0String; break; + default: return nullptr; + } + + constexpr auto max_size = std::min( + std::tuple_size_v - 1U, + (std::numeric_limits::max() - 2U) / 2U); + str_size = static_cast(std::min(str.size(), max_size)); + + for (uint8_t i = 0U; i < str_size; ++i) + descriptor_string_buffer_[i + 1U] = static_cast(str[i]); + } + + descriptor_string_buffer_[0] = + (TUSB_DESC_STRING << 8) | static_cast((2U * str_size) + 2U); + return descriptor_string_buffer_.data(); + } + +private: + static constexpr size_t kUuidWordCount = OTP_SOC_UUID_LEN / sizeof(uint32_t); + static_assert((OTP_SOC_UUID_LEN % sizeof(uint32_t)) == 0U); + + void update_serial_string() { + std::array uuid{}; + + for (size_t i = 0U; i < uuid.size(); ++i) + uuid[i] = otp_read_from_shadow(OTP_SOC_UUID_IDX + static_cast(i)); + + mix_uid_entropy(uuid); + + auto* cursor = serial_string_.data() + 3; + for (const auto& word : uuid) { + cursor = write_hex_u16(static_cast(word >> 16U), cursor) + 1; + cursor = write_hex_u16(static_cast(word), cursor) + 1; + } + utility::assert_debug(cursor == serial_string_.data() + serial_string_.size()); + } + + static constexpr void mix_uid_entropy(std::array& uid) { + static_assert(kUuidWordCount == 4U); + + auto& [a, b, c, d] = uid; + + const auto mix_step = [](uint32_t v) { + v *= 0x9E3779B9U; + return v ^ (v >> 16U); + }; + + a ^= mix_step(b ^ c ^ d); + b ^= mix_step(a ^ c ^ d); + c ^= mix_step(a ^ b ^ d); + d ^= mix_step(a ^ b ^ c); + + a ^= mix_step(b + c + d); + b ^= mix_step(a + c + d); + c ^= mix_step(a + b + d); + d ^= mix_step(a + b + c); + } + + static char* write_hex_u16(uint16_t value, char* buffer) { + static constexpr char hex_lut[] = "0123456789ABCDEF"; + + *buffer++ = hex_lut[(value >> 12U) & 0xFU]; + *buffer++ = hex_lut[(value >> 8U) & 0xFU]; + *buffer++ = hex_lut[(value >> 4U) & 0xFU]; + *buffer++ = hex_lut[value & 0xFU]; + return buffer; + } + +private: // Device Descriptor + static constexpr tusb_desc_device_t kDeviceDescriptor = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0xA11C, + .idProduct = 0xAF01, + .bcdDevice = 0x0300, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01, + }; + +private: // Configuration Descriptor + static constexpr uint8_t kItfNumDfu = 0U; + static constexpr uint8_t kItfNumTotal = 1U; + static constexpr size_t kConfigTotalLen = TUD_CONFIG_DESC_LEN + TUD_DFU_DESC_LEN(1); + + static constexpr uint8_t kConfigurationDescriptorFs[] = { + TUD_CONFIG_DESCRIPTOR(1, kItfNumTotal, 0, kConfigTotalLen, 0, 100), + TUD_DFU_DESCRIPTOR(kItfNumDfu, 1, 4, DFU_ATTR_CAN_DOWNLOAD, 1000, CFG_TUD_DFU_XFER_BUFSIZE), + }; + static_assert(sizeof(kConfigurationDescriptorFs) == kConfigTotalLen); + +private: // String Descriptor + static constexpr std::array kLanguageId = {0x09, 0x04}; + static constexpr std::string_view kManufacturerString = "Alliance RoboMaster Team."; + static constexpr std::string_view kProductString = "RMCS DFU Bootloader"; + static constexpr std::string_view kAlt0String = "Internal Flash"; + std::array serial_string_{"AF-0000-0000-0000-0000-0000-0000-0000-0000"}; + std::array descriptor_string_buffer_{}; +}; + +UsbDescriptors& get_usb_descriptors(); + +} // namespace librmcs::firmware::usb diff --git a/firmware/rmcs_board/bootloader/src/utility/assert.hpp b/firmware/rmcs_board/bootloader/src/utility/assert.hpp new file mode 100644 index 0000000..b99eeba --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/utility/assert.hpp @@ -0,0 +1,20 @@ +#pragma once + +namespace librmcs::firmware::utility { + +[[noreturn, gnu::always_inline]] inline void assert_failed_always() { __builtin_trap(); } + +[[gnu::always_inline]] inline void assert_always(bool condition) { + if (!condition) [[unlikely]] + assert_failed_always(); +} + +[[gnu::always_inline]] inline void assert_debug(bool condition) { +#ifdef NDEBUG + [[assume(condition)]]; +#else + assert_always(condition); +#endif +} + +} // namespace librmcs::firmware::utility diff --git a/firmware/rmcs_board/bootloader/src/utility/boot_mailbox.hpp b/firmware/rmcs_board/bootloader/src/utility/boot_mailbox.hpp new file mode 100644 index 0000000..b6135c2 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/utility/boot_mailbox.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::boot { + +class BootMailbox { +public: + static void clear() { write_pair(0U, 0U); } + + static bool consume_enter_dfu_request() { + const auto values = read_pair(); + clear(); + return values[0] == kMailboxMagic && values[1] == kMailboxRequestEnterDfu; + } + + [[noreturn]] static void reboot() { + ppor_reset_mask_set_source_enable(HPM_PPOR, ppor_reset_software); + ppor_reset_set_hot_reset_enable(HPM_PPOR, ppor_reset_software); + ppor_sw_reset(HPM_PPOR, 10U); + while (true) + ; + } + +private: + static constexpr uint32_t kMailboxMagic = 0x524D4353U; // "RMCS" + static constexpr uint32_t kMailboxRequestEnterDfu = 0x44465530U; // "DFU0" + static constexpr uint8_t kMagicGprIndex = 12U; + + static std::array read_pair() { + std::array values{}; + utility::assert_always( + sysctl_cpu0_get_gpr(HPM_SYSCTL, kMagicGprIndex, values.size(), values.data()) + == status_success); + return values; + } + + static void write_pair(uint32_t magic, uint32_t request) { + uint32_t values[2]{magic, request}; + utility::assert_always( + sysctl_cpu0_set_gpr(HPM_SYSCTL, kMagicGprIndex, 2U, values, false) == status_success); + } +}; + +} // namespace librmcs::firmware::boot diff --git a/firmware/rmcs_board/bootloader/src/utility/jump.hpp b/firmware/rmcs_board/bootloader/src/utility/jump.hpp new file mode 100644 index 0000000..4f0e9b5 --- /dev/null +++ b/firmware/rmcs_board/bootloader/src/utility/jump.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +#include "firmware/rmcs_board/bootloader/src/flash/layout.hpp" +#include "firmware/rmcs_board/bootloader/src/utility/assert.hpp" + +namespace librmcs::firmware::utility { + +[[noreturn]] inline void jump_to_app() { + disable_global_irq(CSR_MSTATUS_MIE_MASK); + l1c_dc_disable(); + l1c_fence_i(); + + using AppEntry = void (*)(); + reinterpret_cast(flash::kAppEntryAddress)(); + + assert_failed_always(); +} + +} // namespace librmcs::firmware::utility diff --git a/firmware/rmcs_board/bsp/hpm_sdk b/firmware/rmcs_board/bsp/hpm_sdk index 132349c..a82a535 160000 --- a/firmware/rmcs_board/bsp/hpm_sdk +++ b/firmware/rmcs_board/bsp/hpm_sdk @@ -1 +1 @@ -Subproject commit 132349c9ddad3cbeca7211345d5d6086355f20b9 +Subproject commit a82a53544d5679a43b70f390c8bbbb8e91f6b8c1 diff --git a/firmware/rmcs_board/cmake/merge_compile_commands.cmake b/firmware/rmcs_board/cmake/merge_compile_commands.cmake new file mode 100644 index 0000000..ee073f6 --- /dev/null +++ b/firmware/rmcs_board/cmake/merge_compile_commands.cmake @@ -0,0 +1,41 @@ +if(NOT DEFINED OUTPUT_FILE OR "${OUTPUT_FILE}" STREQUAL "") + message(FATAL_ERROR "OUTPUT_FILE is required") +endif() + +if(NOT DEFINED INPUT_FILES) + message(FATAL_ERROR "INPUT_FILES is required") +endif() + +set(has_entries OFF) +file(WRITE "${OUTPUT_FILE}" "[\n") + +foreach(input_file IN LISTS INPUT_FILES) + if(NOT EXISTS "${input_file}") + message(FATAL_ERROR "Compile commands file not found: ${input_file}") + endif() + + file(READ "${input_file}" content) + string(STRIP "${content}" content) + if("${content}" STREQUAL "") + continue() + endif() + + string(REGEX REPLACE "^[ \t\r\n]*\\[" "" body "${content}") + string(REGEX REPLACE "\\][ \t\r\n]*$" "" body "${body}") + string(STRIP "${body}" body) + if("${body}" STREQUAL "") + continue() + endif() + + if(has_entries) + file(APPEND "${OUTPUT_FILE}" ",\n") + endif() + file(APPEND "${OUTPUT_FILE}" "${body}") + set(has_entries ON) +endforeach() + +if(has_entries) + file(APPEND "${OUTPUT_FILE}" "\n]\n") +else() + file(WRITE "${OUTPUT_FILE}" "[]\n") +endif()