Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions .scripts/lint-targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 自动修复,但部分修复可能不符合预期,需手动调整。
Expand Down
9 changes: 4 additions & 5 deletions Dockerfile.build_firmware
Original file line number Diff line number Diff line change
Expand Up @@ -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}"; \
Expand Down
36 changes: 4 additions & 32 deletions firmware/c_board/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand Down
18 changes: 8 additions & 10 deletions firmware/c_board/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
$<TARGET_FILE:c_board_app>
$<TARGET_FILE_DIR:c_board_app>/c_board_app.bin
COMMAND ${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash
-o $<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
$<TARGET_FILE_DIR:c_board_app>/c_board_app.bin
COMMAND dfu-suffix -v 0xA11C -p 0xD401 -d 0x0300
-a $<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
> /dev/null
librmcs_add_dfu_image(
TARGET c_board_app
INPUT_ELF $<TARGET_FILE:c_board_app>
OUTPUT_BINARY $<TARGET_FILE_DIR:c_board_app>/c_board_app.bin
OUTPUT_DFU $<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
VENDOR_ID 0xA11C
PRODUCT_ID 0xD401
DEVICE_ID 0x0300
COMMENT "Generating c_board_app.dfu"
)
4 changes: 3 additions & 1 deletion firmware/c_board/app/src/timer/timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion firmware/c_board/app/src/usb/usb_descriptors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions firmware/c_board/bootloader/src/crypto/sha256.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <array>
Expand All @@ -22,7 +25,7 @@

namespace librmcs::firmware::crypto {

inline constexpr size_t kSha256BlockSize = 32U;
inline constexpr size_t kSha256DigestSize = 32U;

struct Sha256Ctx {
std::array<uint8_t, 64> data{};
Expand Down Expand Up @@ -75,7 +78,7 @@ inline uint32_t small_sigma1(uint32_t x) {
}

inline void sha256_transform(Sha256Ctx* ctx, const uint8_t* data) {
std::array<uint32_t, 64> message_schedule{};
std::array<uint32_t, 64> message_schedule;

for (uint32_t index = 0U; index < 16U; ++index) {
const uint32_t data_offset = index * 4U;
Expand Down
9 changes: 6 additions & 3 deletions firmware/c_board/bootloader/src/flash/metadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uintptr_t>(latest_valid_slot_) >= kMetadataEndAddress)
auto next_addr = reinterpret_cast<uintptr_t>(latest_valid_slot_) + sizeof(DataSlot);
if (next_addr >= kMetadataEndAddress)
erase_and_rescan();
else
latest_valid_slot_ = reinterpret_cast<DataSlot*>(next_addr);
}

latest_valid_slot_->enter_flashing_state();
Expand Down Expand Up @@ -162,7 +164,8 @@ class Metadata {
case DataSlotState::kFlashing:
case DataSlotState::kReady: {
if (!latest_valid_slot_
|| (latest_valid_slot_ + 1 == &slot
|| (reinterpret_cast<uintptr_t>(latest_valid_slot_) + sizeof(DataSlot)
== reinterpret_cast<uintptr_t>(&slot)
&& latest_valid_slot_state_ == DataSlotState::kReady)) {
latest_valid_slot_ = &slot;
latest_valid_slot_state_ = state;
Expand Down
30 changes: 17 additions & 13 deletions firmware/c_board/bootloader/src/flash/validation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t>(crypto::kSha256BlockSize); // 36
sizeof(uint32_t) + static_cast<uint32_t>(crypto::kSha256DigestSize); // 36

inline bool is_vector_table_valid() {
const uint32_t initial_msp = *reinterpret_cast<volatile const uint32_t*>(kAppStartAddress);
Expand Down Expand Up @@ -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() {
Expand All @@ -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
3 changes: 3 additions & 0 deletions firmware/c_board/bootloader/src/flash/writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 1 addition & 3 deletions firmware/c_board/bootloader/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
13 changes: 7 additions & 6 deletions firmware/c_board/bootloader/src/usb/dfu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t>(flash::kAppStartAddress)
+ static_cast<uint64_t>(downloaded_size_);
if (write_address_64 >= static_cast<uint64_t>(flash::kAppEndAddress))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -153,6 +153,7 @@ class Dfu {
}

void reset_transfer_state() {
flash_writer_.abort_session();
expected_block_ = 0U;
downloaded_size_ = 0U;
session_started_ = false;
Expand Down
4 changes: 3 additions & 1 deletion firmware/c_board/bootloader/src/utility/jump.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <main.h>

#include "firmware/c_board/bootloader/src/utility/assert.hpp"

namespace librmcs::firmware::utility {

inline void jump_to_app(uint32_t app_address) {
Expand Down Expand Up @@ -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
Loading
Loading