Skip to content
Draft
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
21 changes: 16 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ project(dmboot
DESCRIPTION "DMOD Bootloader"
LANGUAGES C CXX ASM)

# Python interpreter for helper scripts (cross-platform)
find_package(Python3 REQUIRED COMPONENTS Interpreter)

# ======================================================================
# For VS Code
# ======================================================================
Expand Down Expand Up @@ -115,7 +118,11 @@ set(MODULES_DMP_OBJECT "${CMAKE_BINARY_DIR}/__modules_dmp.o")
add_custom_command(
OUTPUT "${MODULES_DMP_OBJECT}"
COMMAND ${CMAKE_COMMAND} -E echo "Checking for modules.dmp..."
COMMAND bash -c "if [ -f ${MODULES_DMP_FILE} ]; then echo 'Embedding modules.dmp'; ${CMAKE_OBJCOPY} --input-target=binary --output-target=elf32-littlearm --binary-architecture=arm --rename-section .data=.embedded.modules_dmp,alloc,load,readonly,data,contents ${MODULES_DMP_FILE} ${MODULES_DMP_OBJECT}; else echo 'No modules.dmp found, creating empty object'; ${CMAKE_OBJCOPY} --input-target=binary --output-target=elf32-littlearm --binary-architecture=arm --rename-section .data=.embedded.modules_dmp,alloc,load,readonly,data,contents /dev/null ${MODULES_DMP_OBJECT}; fi"
COMMAND ${CMAKE_COMMAND}
-DINPUT_DMP="${MODULES_DMP_FILE}"
-DOUTPUT_OBJECT="${MODULES_DMP_OBJECT}"
-DOBJCOPY_EXECUTABLE="${CMAKE_OBJCOPY}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/scripts/embed_modules_dmp.cmake"
DEPENDS prepare_modules_dmp
COMMENT "Conditionally embedding modules.dmp if it exists"
VERBATIM
Expand Down Expand Up @@ -184,7 +191,7 @@ target_link_libraries(${MODULE_NAME}.elf
add_custom_command(
TARGET ${MODULE_NAME}.elf POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Generating memory usage report..."
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/scripts/memory_report.py
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/memory_report.py
${CMAKE_BINARY_DIR}/${MODULE_NAME}.map
${CMAKE_BINARY_DIR}
--lib-dir ${CMAKE_CURRENT_SOURCE_DIR}/lib
Expand All @@ -205,7 +212,11 @@ include(scripts/targets.cmake)

# Custom target to build dmlog_monitor for host architecture
set(DMLOG_HOST_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/dmlog/build_host)
set(DMLOG_MONITOR_EXECUTABLE ${DMLOG_HOST_BUILD_DIR}/tools/monitor/dmlog_monitor)
if(WIN32)
set(DMLOG_MONITOR_EXECUTABLE ${DMLOG_HOST_BUILD_DIR}/tools/monitor/dmlog_monitor.exe)
else()
set(DMLOG_MONITOR_EXECUTABLE ${DMLOG_HOST_BUILD_DIR}/tools/monitor/dmlog_monitor)
endif()

add_custom_command(
OUTPUT ${DMLOG_MONITOR_EXECUTABLE}
Expand All @@ -224,7 +235,7 @@ add_custom_target(build_dmlog_monitor
# Extract ring buffer configuration after building firmware
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/dmlog_ring_buffer.cmake
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/scripts/extract_ring_buffer_addr.py
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/extract_ring_buffer_addr.py
${CMAKE_BINARY_DIR}/${MODULE_NAME}.map
--output ${CMAKE_BINARY_DIR}/dmlog_ring_buffer.cmake
--format cmake
Expand Down Expand Up @@ -261,4 +272,4 @@ add_custom_target(monitor-gdb
DEPENDS build_dmlog_monitor extract_ring_buffer_config
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Monitoring logs from ${TARGET} via GDB server..."
)
)
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ After running the script, restart your terminal or run:
source ~/.bashrc
```

### Option 3: Native Windows Setup

Native Windows development/build (without WSL or Docker) is supported.

See [docs/windows-environment-setup.md](docs/windows-environment-setup.md) for the required toolchain and PowerShell build flow.
Quick start:

```powershell
.\scripts\setup-windows-env.ps1
```

## Building

### Building for Hardware
Expand Down
76 changes: 70 additions & 6 deletions docs/windows-environment-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,80 @@

## Overview

dmod-boot is Linux-oriented (Bash scripts, Linux toolchain, POSIX paths). On Windows, use one of the supported workflows below:
dmod-boot supports native Windows development and build workflows.

1. **WSL2 + Ubuntu (recommended)** - best for local development in VS Code
2. **Docker Desktop** - fastest way to get a ready-to-use environment
Supported workflows:

> Native (non-WSL) Windows builds are not officially supported by this repository.
1. **Native Windows (PowerShell + CMake)** - Recommended when you want to work without WSL/Docker
2. **WSL2 + Ubuntu** - Linux-like workflow on Windows
3. **Docker Desktop** - fastest way to get a ready-to-use environment

---

## Option 1: WSL2 + Ubuntu (Recommended)
## Option 1: Native Windows (Recommended)

### 1. Install required tools

Use the repository helper script (no admin-required package installation; tools are downloaded to user-writable directory):

```powershell
.\scripts\setup-windows-env.ps1
```

By default this downloads/extracts tools to:

```powershell
$env:USERPROFILE\tools\dmboot
```

Then (for the current shell session):

```powershell
. "$env:USERPROFILE\tools\dmboot\activate-dmboot-tools.ps1"
```

Downloaded by `setup-windows-env.ps1`:

- **CMake** (3.10+)
- **GNU Arm Embedded Toolchain** (`arm-none-eabi-*`)
- **Renode** (optional, for emulation workflow)

Install separately and add to `PATH`:

- **Python 3**
- **Ninja** (optional, recommended)
- **OpenOCD** (for hardware workflow)
- **dmod tools** (`dmf-get`, `todmp`, `dmod_loader`) - build/install from the dmod project

> Note: `setup-windows-env.ps1` downloads portable tools to user space and does not install tools globally.

Optional script flags:

```powershell
.\scripts\setup-windows-env.ps1 -ToolsDir D:\tools\dmboot -SkipRenode
.\scripts\setup-windows-env.ps1 -DryRun
```

### 2. Configure and build (PowerShell)

From repository root:

```powershell
cmake -DCMAKE_BUILD_TYPE=Debug -DTARGET=STM32F746xG -S . -B build
cmake --build build --config Debug
```

### 3. Optional: Renode emulation mode

```powershell
cmake -DCMAKE_BUILD_TYPE=Debug -DDMBOOT_EMULATION=ON -S . -B build
cmake --build build --config Debug
cmake --build build --target connect
```

---

## Option 2: WSL2 + Ubuntu

### 1. Install WSL2

Expand Down Expand Up @@ -93,7 +157,7 @@ cmake --build build --target monitor-gdb

---

## Option 2: Docker Desktop (Quick Start)
## Option 3: Docker Desktop (Quick Start)

### 1. Install Docker Desktop on Windows

Expand Down
53 changes: 31 additions & 22 deletions modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,24 @@ macro(append_main_module_download_commands LIST_VAR)
list(APPEND _MAIN_MODULE_ARGS ${DMBOOT_MAIN_MODULE_CONFIG})
endif()
if(DMBOOT_MANIFEST_URL)
list(APPEND ${LIST_VAR}
COMMAND ${CMAKE_COMMAND} -E echo "Downloading main module ${DMBOOT_MAIN_MODULE} (manifest: ${DMBOOT_MANIFEST_URL})..."
COMMAND ${DMF_GET} ${_MAIN_MODULE_ARGS} -m "${DMBOOT_MANIFEST_URL}" -o "${DMBOOT_MODULES_OUT_DIR}" -t "${DMOD_TOOLS_NAME}" --cpu-family "${DMBOOT_MCU_SERIES}" -y --type dmf --config-dir "${DMBOOT_CONFIG_DIR}"
COMMAND bash -c "test -f '${DMBOOT_MODULES_OUT_DIR}/${DMBOOT_MAIN_MODULE}.dmf' || { echo 'ERROR: Main module ${DMBOOT_MAIN_MODULE}.dmf was not downloaded to ${DMBOOT_MODULES_OUT_DIR}. The manifest may not contain the module entry or dmf-get encountered an error.'; exit 1; }"
)
else()
list(APPEND ${LIST_VAR}
COMMAND ${CMAKE_COMMAND} -E echo "Downloading main module ${DMBOOT_MAIN_MODULE}..."
COMMAND ${DMF_GET} ${_MAIN_MODULE_ARGS} -o "${DMBOOT_MODULES_OUT_DIR}" -t "${DMOD_TOOLS_NAME}" --cpu-family "${DMBOOT_MCU_SERIES}" -y --type dmf --config-dir "${DMBOOT_CONFIG_DIR}"
COMMAND bash -c "test -f '${DMBOOT_MODULES_OUT_DIR}/${DMBOOT_MAIN_MODULE}.dmf' || { echo 'ERROR: Main module ${DMBOOT_MAIN_MODULE}.dmf was not downloaded to ${DMBOOT_MODULES_OUT_DIR}. Check if dmf-get encountered an error.'; exit 1; }"
)
endif()
list(APPEND ${LIST_VAR}
COMMAND ${CMAKE_COMMAND} -E echo "Downloading main module ${DMBOOT_MAIN_MODULE} (manifest: ${DMBOOT_MANIFEST_URL})..."
COMMAND ${DMF_GET} ${_MAIN_MODULE_ARGS} -m "${DMBOOT_MANIFEST_URL}" -o "${DMBOOT_MODULES_OUT_DIR}" -t "${DMOD_TOOLS_NAME}" --cpu-family "${DMBOOT_MCU_SERIES}" -y --type dmf --config-dir "${DMBOOT_CONFIG_DIR}"
COMMAND ${CMAKE_COMMAND}
-DREQUIRED_FILE="${DMBOOT_MODULES_OUT_DIR}/${DMBOOT_MAIN_MODULE}.dmf"
-DERROR_MESSAGE="Main module ${DMBOOT_MAIN_MODULE}.dmf was not downloaded to ${DMBOOT_MODULES_OUT_DIR}. The manifest may not contain the module entry or dmf-get encountered an error."
-P "${CMAKE_SOURCE_DIR}/scripts/require_file.cmake"
)
else()
list(APPEND ${LIST_VAR}
COMMAND ${CMAKE_COMMAND} -E echo "Downloading main module ${DMBOOT_MAIN_MODULE}..."
COMMAND ${DMF_GET} ${_MAIN_MODULE_ARGS} -o "${DMBOOT_MODULES_OUT_DIR}" -t "${DMOD_TOOLS_NAME}" --cpu-family "${DMBOOT_MCU_SERIES}" -y --type dmf --config-dir "${DMBOOT_CONFIG_DIR}"
COMMAND ${CMAKE_COMMAND}
-DREQUIRED_FILE="${DMBOOT_MODULES_OUT_DIR}/${DMBOOT_MAIN_MODULE}.dmf"
-DERROR_MESSAGE="Main module ${DMBOOT_MAIN_MODULE}.dmf was not downloaded to ${DMBOOT_MODULES_OUT_DIR}. Check if dmf-get encountered an error."
-P "${CMAKE_SOURCE_DIR}/scripts/require_file.cmake"
)
endif()
endmacro()

if(DMBOOT_FLASH_DMD_FILES)
Expand Down Expand Up @@ -246,23 +252,26 @@ set(DMBOOT_MODULES_DMP "${CMAKE_BINARY_DIR}/modules.dmp")
if(DMBOOT_MAIN_MODULE)
add_custom_command(
OUTPUT "${DMBOOT_MODULES_DMP}"
WORKING_DIRECTORY ${DMBOOT_MODULES_OUT_DIR}
COMMAND ${CMAKE_COMMAND} -E echo "Checking if modules directory contains files..."
COMMAND ${CMAKE_COMMAND} -E echo "Contents of ${DMBOOT_MODULES_OUT_DIR}:"
COMMAND ls -la ${DMBOOT_MODULES_OUT_DIR} || ${CMAKE_COMMAND} -E echo "Directory is empty or does not exist"
COMMAND bash -c "if ls ${DMBOOT_MODULES_OUT_DIR}/*.dmf 1> /dev/null 2>&1; then ${TODMP} modules ${DMBOOT_MODULES_OUT_DIR} ${DMBOOT_MODULES_DMP} ${DMBOOT_MAIN_MODULE}; ls ${DMBOOT_MODULES_OUT_DIR}/*.dmf; else echo 'ERROR: No .dmf files found in ${DMBOOT_MODULES_OUT_DIR} but main module ${DMBOOT_MAIN_MODULE} was expected. Module download may have failed.'; exit 1; fi"
COMMAND ${CMAKE_COMMAND}
-DMODULES_DIR="${DMBOOT_MODULES_OUT_DIR}"
-DOUTPUT_DMP="${DMBOOT_MODULES_DMP}"
-DTODMP_EXECUTABLE="${TODMP}"
-DMAIN_MODULE="${DMBOOT_MAIN_MODULE}"
-DFAIL_IF_EMPTY=ON
-P "${CMAKE_SOURCE_DIR}/scripts/create_modules_dmp.cmake"
DEPENDS download_modules
COMMENT "Creating modules dmp file from flash modules (if any exist)..."
VERBATIM
)
else()
add_custom_command(
OUTPUT "${DMBOOT_MODULES_DMP}"
WORKING_DIRECTORY ${DMBOOT_MODULES_OUT_DIR}
COMMAND ${CMAKE_COMMAND} -E echo "Checking if modules directory contains files..."
COMMAND ${CMAKE_COMMAND} -E echo "Contents of ${DMBOOT_MODULES_OUT_DIR}:"
COMMAND ls -la ${DMBOOT_MODULES_OUT_DIR} || ${CMAKE_COMMAND} -E echo "Directory is empty or does not exist"
COMMAND bash -c "if ls ${DMBOOT_MODULES_OUT_DIR}/*.dmf 1> /dev/null 2>&1; then ${TODMP} modules ${DMBOOT_MODULES_OUT_DIR} ${DMBOOT_MODULES_DMP}; ls ${DMBOOT_MODULES_OUT_DIR}/*.dmf; else echo 'No .dmf files found, skipping DMP creation'; fi"
COMMAND ${CMAKE_COMMAND}
-DMODULES_DIR="${DMBOOT_MODULES_OUT_DIR}"
-DOUTPUT_DMP="${DMBOOT_MODULES_DMP}"
-DTODMP_EXECUTABLE="${TODMP}"
-DFAIL_IF_EMPTY=OFF
-P "${CMAKE_SOURCE_DIR}/scripts/create_modules_dmp.cmake"
DEPENDS download_modules
COMMENT "Creating modules dmp file from flash modules (if any exist)..."
VERBATIM
Expand Down
51 changes: 51 additions & 0 deletions scripts/create_modules_dmp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
if(NOT DEFINED MODULES_DIR)
message(FATAL_ERROR "MODULES_DIR is not defined")
endif()

if(NOT DEFINED OUTPUT_DMP)
message(FATAL_ERROR "OUTPUT_DMP is not defined")
endif()

if(NOT DEFINED TODMP_EXECUTABLE)
message(FATAL_ERROR "TODMP_EXECUTABLE is not defined")
endif()

if(NOT EXISTS "${MODULES_DIR}")
file(MAKE_DIRECTORY "${MODULES_DIR}")
endif()

file(GLOB DMF_FILES "${MODULES_DIR}/*.dmf")

if(DMF_FILES)
message(STATUS "Found DMF files in ${MODULES_DIR}:")
foreach(DMF_FILE IN LISTS DMF_FILES)
message(STATUS " ${DMF_FILE}")
endforeach()

if(DEFINED MAIN_MODULE AND NOT MAIN_MODULE STREQUAL "")
execute_process(
COMMAND "${TODMP_EXECUTABLE}" modules "${MODULES_DIR}" "${OUTPUT_DMP}" "${MAIN_MODULE}"
RESULT_VARIABLE TODMP_RESULT
)
else()
execute_process(
COMMAND "${TODMP_EXECUTABLE}" modules "${MODULES_DIR}" "${OUTPUT_DMP}"
RESULT_VARIABLE TODMP_RESULT
)
endif()

if(NOT TODMP_RESULT EQUAL 0)
message(FATAL_ERROR "todmp failed while creating ${OUTPUT_DMP} (exit code: ${TODMP_RESULT})")
endif()
else()
if(FAIL_IF_EMPTY)
if(DEFINED MAIN_MODULE AND NOT MAIN_MODULE STREQUAL "")
message(FATAL_ERROR "No .dmf files found in ${MODULES_DIR} but main module '${MAIN_MODULE}' was expected. Module download may have failed.")
else()
message(FATAL_ERROR "No .dmf files found in ${MODULES_DIR}. Module download may have failed.")
endif()
endif()

message(STATUS "No .dmf files found in ${MODULES_DIR}, creating empty modules.dmp placeholder")
file(WRITE "${OUTPUT_DMP}" "")
endif()
36 changes: 36 additions & 0 deletions scripts/embed_modules_dmp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
if(NOT DEFINED INPUT_DMP)
message(FATAL_ERROR "INPUT_DMP is not defined")
endif()

if(NOT DEFINED OUTPUT_OBJECT)
message(FATAL_ERROR "OUTPUT_OBJECT is not defined")
endif()

if(NOT DEFINED OBJCOPY_EXECUTABLE)
message(FATAL_ERROR "OBJCOPY_EXECUTABLE is not defined")
endif()

set(INPUT_BINARY_FILE "${INPUT_DMP}")
if(EXISTS "${INPUT_DMP}")
message(STATUS "Embedding modules.dmp from ${INPUT_DMP}")
else()
get_filename_component(OUTPUT_DIR "${OUTPUT_OBJECT}" DIRECTORY)
set(INPUT_BINARY_FILE "${OUTPUT_DIR}/empty_modules_dmp.bin")
file(WRITE "${INPUT_BINARY_FILE}" "")
message(STATUS "No modules.dmp found, embedding empty placeholder")
endif()

execute_process(
COMMAND "${OBJCOPY_EXECUTABLE}"
--input-target=binary
--output-target=elf32-littlearm
--binary-architecture=arm
--rename-section .data=.embedded.modules_dmp,alloc,load,readonly,data,contents
"${INPUT_BINARY_FILE}"
"${OUTPUT_OBJECT}"
RESULT_VARIABLE OBJCOPY_RESULT
)

if(NOT OBJCOPY_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to create embedded modules object (objcopy exit code: ${OBJCOPY_RESULT})")
endif()
11 changes: 11 additions & 0 deletions scripts/require_file.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
if(NOT DEFINED REQUIRED_FILE)
message(FATAL_ERROR "REQUIRED_FILE is not defined")
endif()

if(NOT EXISTS "${REQUIRED_FILE}")
if(DEFINED ERROR_MESSAGE)
message(FATAL_ERROR "${ERROR_MESSAGE}")
else()
message(FATAL_ERROR "Required file does not exist: ${REQUIRED_FILE}")
endif()
endif()
10 changes: 5 additions & 5 deletions scripts/run_monitor.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ endif()
include(${PROJECT_BINARY_DIR}/dmlog_ring_buffer.cmake)

# Set paths
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor")
if(WIN32)
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor.exe")
else()
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor")
endif()

# Check if dmlog_monitor exists
if(NOT EXISTS ${DMLOG_MONITOR_EXECUTABLE})
Expand All @@ -36,10 +40,6 @@ message(STATUS "Ring buffer size: ${DMLOG_RING_BUFFER_SIZE}")
execute_process(
COMMAND ${DMLOG_MONITOR_EXECUTABLE} --addr ${DMLOG_RING_BUFFER_ADDR}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
# Don't capture OUTPUT or ERROR - let them pass through to terminal
INPUT_FILE /dev/stdin
OUTPUT_FILE /dev/stdout
ERROR_FILE /dev/stderr
)

# Note: We intentionally don't check the exit code because:
Expand Down
10 changes: 5 additions & 5 deletions scripts/run_monitor_gdb.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ endif()
include(${PROJECT_BINARY_DIR}/dmlog_ring_buffer.cmake)

# Set paths
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor")
if(WIN32)
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor.exe")
else()
set(DMLOG_MONITOR_EXECUTABLE "${PROJECT_SOURCE_DIR}/lib/dmlog/build_host/tools/monitor/dmlog_monitor")
endif()

# Check if dmlog_monitor exists
if(NOT EXISTS ${DMLOG_MONITOR_EXECUTABLE})
Expand All @@ -38,10 +42,6 @@ message(STATUS "Connecting to GDB server at localhost:3333 (OpenOCD GDB server)"
execute_process(
COMMAND ${DMLOG_MONITOR_EXECUTABLE} --gdb --port 3333 --addr ${DMLOG_RING_BUFFER_ADDR}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
# Don't capture OUTPUT or ERROR - let them pass through to terminal
INPUT_FILE /dev/stdin
OUTPUT_FILE /dev/stdout
ERROR_FILE /dev/stderr
)

# Note: We intentionally don't check the exit code because:
Expand Down
Loading