Skip to content

Commit f5d46f6

Browse files
committed
[bootloader] Add recovery mode feature and version checking
Signed-off-by: George Pappas <pappasgeorge12@gmail.com>
1 parent f028524 commit f5d46f6

File tree

11 files changed

+169
-39
lines changed

11 files changed

+169
-39
lines changed

projects/bootloader/CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ set(SRC_FILES
5555
${GIT_ROOT_DIR}/projects/bootloader/boards/stm32f401re/startup_stm32f401xe.s
5656
# --- Actual application ---
5757
${GIT_ROOT_DIR}/projects/bootloader/src/main.c
58+
${GIT_ROOT_DIR}/projects/bootloader/src/drivers/user_input.c
5859
${GIT_ROOT_DIR}/projects/bootloader/src/drivers/sys/sys_init.c
5960
${GIT_ROOT_DIR}/projects/bootloader/src/drivers/sys/sys.c
6061
${GIT_ROOT_DIR}/projects/bootloader/src/drivers/uart/uart_driver.c
@@ -103,7 +104,7 @@ target_compile_options(${EXECUTABLE} PRIVATE
103104
-ffunction-sections
104105
-fstack-usage
105106
# Optimise for size
106-
#-Os
107+
-Os
107108
)
108109

109110
get_target_property(COMPILE_OPTIONS ${EXECUTABLE} COMPILE_OPTIONS)

projects/bootloader/boards/stm32f401re/STM32F401RETx_FLASH.ld

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ _estack = 0x20018000; /* end of RAM */
5858
_Min_Heap_Size = 0x200; /* required amount of heap */
5959
_Min_Stack_Size = 0x400; /* required amount of stack */
6060

61-
/* Image header info */
61+
/* Image header info; This should be used as is */
6262
__header_size_bytes__ = 40;
6363
__header_crc_size_bytes__ = 4;
6464
__header_fw_ver_size_bytes__ = 4;
6565
__header_hash_size_bytes__ = 32;
6666

67-
/* Primary app info */
67+
/* Primary app info; This should be adjusted based on the app needs */
6868
__flash_app_start__ = 0x08008000;
6969
__flash_app_end__ = 0x0803FFFF;
7070
__header_app_start__ = __flash_app_end__ - __header_size_bytes__ + 1;
@@ -73,7 +73,7 @@ __header_app_crc_start__ = __header_app_start__;
7373
__header_app_fw_version_start__ = __header_app_start__ + __header_crc_size_bytes__;
7474
__header_app_hash_start__ = __header_app_start__ + __header_crc_size_bytes__ + __header_fw_ver_size_bytes__;
7575

76-
/* Secondary app info */
76+
/* Secondary app info; This should be adjusted based on the app needs */
7777
__flash_app_secondary_start__ = 0x08040000;
7878
__flash_app_secondary_end__ = 0x08077FFF;
7979
__header_app_secondary_start__ = __flash_app_secondary_end__ - __header_size_bytes__ + 1;

projects/bootloader/src/drivers/flash/flash_apis.c

+47-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ flash_api_erase_secondary_space(void)
123123
/**
124124
* @brief Function to write a firmware packet to the secondary space. Will be used by the firmware update process.
125125
* NOTE: it is a responsibility of the caller to make sure that starting address offset is correct.
126-
* This function will receive an offset and write the packet data to that offset, starting from the secondary space.
126+
* This function will receive an offset and write the packet data to that offset, starting from the secondary
127+
* space.
127128
*
128129
* @param packet_data Pointer to the packet data
129130
* @param packet_size Size of the packet data
@@ -155,4 +156,49 @@ flash_api_write_firmware_update_packet(uint8_t *packet_data, uint32_t packet_siz
155156
}
156157

157158
return ret;
159+
}
160+
161+
/**
162+
* @brief Function that compares the primary and secondary firmware versions and returns true if the secondary is newer.
163+
*
164+
* @return true if the secondary firmware is newer, false otherwise.
165+
*/
166+
bool
167+
flash_api_is_secondary_newer(void)
168+
{
169+
// Get the primary and secondary firmware versions
170+
uint8_t primary_version_major = *((uint8_t *)(&__header_app_fw_version_start__));
171+
uint16_t primary_version_minor = *((uint16_t *)(&__header_app_fw_version_start__) + 1);
172+
uint8_t primary_version_patch = *((uint8_t *)(&__header_app_fw_version_start__) + 3);
173+
174+
uint8_t secondary_version_major = *((uint8_t *)(&__header_app_secondary_fw_version_start__));
175+
uint16_t secondary_version_minor = *((uint16_t *)(&__header_app_secondary_fw_version_start__) + 1);
176+
uint8_t secondary_version_patch = *((uint8_t *)(&__header_app_secondary_fw_version_start__) + 3);
177+
#ifdef DEBUG_LOG
178+
printf("Primary version: %d.%d.%d\r\n", primary_version_major, primary_version_minor, primary_version_patch);
179+
printf(
180+
"Secondary version: %d.%d.%d\r\n", secondary_version_major, secondary_version_minor, secondary_version_patch);
181+
#endif
182+
183+
// Compare the versions
184+
if (secondary_version_major > primary_version_major)
185+
{
186+
return true;
187+
}
188+
else if (secondary_version_major == primary_version_major)
189+
{
190+
if (secondary_version_minor > primary_version_minor)
191+
{
192+
return true;
193+
}
194+
else if (secondary_version_minor == primary_version_minor)
195+
{
196+
if (secondary_version_patch > primary_version_patch)
197+
{
198+
return true;
199+
}
200+
}
201+
}
202+
203+
return false;
158204
}

projects/bootloader/src/drivers/flash/flash_apis.h

+1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919
bool flash_api_transfer_secondary_to_primary(void);
2020
bool flash_api_erase_secondary_space(void);
2121
bool flash_api_write_firmware_update_packet(uint8_t *packet_data, uint32_t packet_size, uint32_t addr_offset);
22+
bool flash_api_is_secondary_newer(void);
2223

2324
#endif // FLASH_APIS_H

projects/bootloader/src/drivers/sys/sys_init.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,6 @@ sys_prepare_for_application(void)
113113
mpu_config_lock();
114114
// TODO: GPA: It is important to set the unprivileged mode after the MPU is enabled, to protect bootloader.
115115
// Right now, setting the unprivileged mode is commented out, because it will cause the bootloader to crash.
116-
//set_unprivileged_mode();
116+
// set_unprivileged_mode();
117117
__enable_irq();
118118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @file user_input.c
3+
* @brief This source file is about creating an api to get a user input. In the current implementation, the input is the
4+
* default button on the stm32f401re nucleo board.
5+
* @version 0.1
6+
* @date 2024-06-24
7+
*
8+
* @copyright Copyright (c) 2024
9+
*
10+
*/
11+
12+
// --- includes --------------------------------------------------------------------------------------------------------
13+
#include "user_input.h"
14+
15+
#include "stm32f4xx_hal.h"
16+
#include <stdbool.h>
17+
18+
// --- defines ---------------------------------------------------------------------------------------------------------
19+
#define BUTTON_GPIO_PORT GPIOC
20+
#define BUTTON_GPIO_PIN GPIO_PIN_13
21+
22+
// --- static function declarations ------------------------------------------------------------------------------------
23+
static void user_input_init(void);
24+
25+
// --- static function definitions -------------------------------------------------------------------------------------
26+
static void
27+
user_input_init(void)
28+
{
29+
static bool is_init = false;
30+
31+
if (is_init) {
32+
return;
33+
}
34+
// Enable the GPIOA clock
35+
__HAL_RCC_GPIOC_CLK_ENABLE();
36+
37+
// Configure the GPIO pin as input with pull-up
38+
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
39+
GPIO_InitStruct.Pin = BUTTON_GPIO_PIN;
40+
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
41+
GPIO_InitStruct.Pull = GPIO_PULLUP;
42+
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
43+
HAL_GPIO_Init(BUTTON_GPIO_PORT, &GPIO_InitStruct);
44+
45+
is_init = true;
46+
}
47+
48+
// --- function definitions --------------------------------------------------------------------------------------------
49+
/**
50+
* @brief This function checks if the user input is pressed.
51+
*
52+
* @return true
53+
* @return false
54+
*/
55+
bool
56+
user_input_is_pressed(void)
57+
{
58+
user_input_init();
59+
60+
return HAL_GPIO_ReadPin(BUTTON_GPIO_PORT, BUTTON_GPIO_PIN) == GPIO_PIN_RESET;
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @file user_input.h
3+
* @brief This source file is about creating an api to get a user input. In the current implementation, the input is the
4+
* default button on the stm32f401re nucleo board.
5+
* @version 0.1
6+
* @date 2024-06-24
7+
*
8+
* @copyright Copyright (c) 2024
9+
*
10+
*/
11+
12+
#ifndef USER_INPUT_H
13+
#define USER_INPUT_H
14+
15+
// --- includes --------------------------------------------------------------------------------------------------------
16+
#include <stdbool.h>
17+
18+
// --- function declarations -------------------------------------------------------------------------------------------
19+
bool user_input_is_pressed(void);
20+
21+
#endif // USER_INPUT_H

projects/bootloader/src/main.c

+6-11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "flash_apis.h"
2323
#include "common.h"
2424
#include "com_protocol.h"
25+
#include "user_input.h"
2526

2627
// --- typedefs --------------------------------------------------------------------------------------------------------
2728
/**
@@ -62,12 +63,8 @@ typedef struct bl_fsm_ctx_t
6263
bl_fsm_evts_e evt_produced : 3;
6364

6465
// Status flags
65-
uint8_t is_button_pressed : 1;
6666
uint8_t newer_ver_on_backup : 1;
6767
uint8_t recover_main_img : 1;
68-
69-
// Unused bits
70-
uint8_t unused : 7;
7168
} bl_fsm_ctx_s;
7269

7370
/**
@@ -93,10 +90,10 @@ static bl_fsm_evts_e fsm_bootloop_hdl(bl_fsm_ctx_s * const ctx);
9390
* E.g. At first, the (current_state == BL_FSM_NONE_STATE and the hdl_output(evt) == BL_FSM_NONE_STATE) -> fsm_init_hdl will be executed.
9491
* The fsm_init_hdl, will set the current_state == BL_FSM_INIT_STATE and if (for example) hdl_output(evt) == BL_FSM_ERR_OR_NONE_EVT,
9592
* -> fsm_crc_check_hdl will be called on the next state machine cycle.
96-
*
93+
*
9794
* The purpose of this fsm is to follow a path, from the init (boot) phase to either booting the application, or entering the recovery
9895
* mode.
99-
*
96+
*
10097
*/
10198
static const bl_fsm_handler bl_fsm_map[BL_FSM_STATE_COUNT][BL_FSM_EVT_COUNT] = {
10299
/* | BL_FSM_ERR_OR_NONE_EVT | BL_FSM_CHECK_PASS_EVT | BL_FSM_CHECK_FAIL_EVT | BL_FSM_BUTTON_PRESSED_EVT */
@@ -178,15 +175,13 @@ fsm_init_hdl(bl_fsm_ctx_s * const ctx)
178175
#ifdef DEBUG_LOG
179176
printf(" --- BOOTLOADER Start --- \r\n");
180177
#endif
181-
// TODO: GPA: we need to check if button is pressed or if there is a newer version in the backup region
182-
// Button has always higher priority. If pressed, the init handler returnes and bootloader enters recovery mode.
183-
if (/*button is pressed*/ 0)
178+
179+
if (user_input_is_pressed())
184180
{
185-
ctx->is_button_pressed = true;
186181
return BL_FSM_BUTTON_PRESSED_EVT;
187182
}
188183

189-
if (/*newer version in backup*/ 0)
184+
if (flash_api_is_secondary_newer())
190185
{
191186
ctx->newer_ver_on_backup = true;
192187
}

scripts/build_tools/build.sh

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ create_dfu_image() {
5757
echo "Creating DFU image..."
5858
pwd
5959
python create_dfu_image.py "$GIT_ROOT/$binary_path" "$GIT_ROOT/$linker_script" "$version_major" "$version_minor" "$version_patch"
60+
#if python fails, try with python3
61+
if [ $? -ne 0 ]; then
62+
python3 create_dfu_image.py "$GIT_ROOT/$binary_path" "$GIT_ROOT/$linker_script" "$version_major" "$version_minor" "$version_patch"
63+
fi
6064
#TODO: GPA: don't fail the rest of the script if the python script fails.
6165
}
6266

scripts/build_tools/build_info.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ projects:
2222
linker_script: "projects/app/boards/stm32f401re/STM32F401RETx_FLASH.ld"
2323
version:
2424
major: 1
25-
minor: 1
25+
minor: 2
2626
patch: 0
2727
- project_name: "bootloader"
2828
build_all: true

scripts/firmware_update_tools/bootloader_tool.py

+22-21
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def compute_crc16(data):
2424
class FirmwareUpdateFactory:
2525
def __init__(self):
2626
self.buffer = bytearray(256)
27-
27+
2828
def create_fwug_start_msg(self):
2929
msg_len = 2 + 2 # Total length: 2 bytes header + 2 bytes footer
3030
msg_header = struct.pack('BB', COM_PROTO_MSG_TYPE_FWUG_START, msg_len)
@@ -34,61 +34,61 @@ def create_fwug_start_msg(self):
3434
message = msg_header + struct.pack('>H', crc16)
3535
self.buffer[:len(message)] = message
3636
return self.buffer[:len(message)]
37-
37+
3838
def create_fwug_data_msg(self, packet_number, payload):
3939
if len(payload) != FIRMWARE_UPDATE_PACKET_SIZE:
4040
raise ValueError(f"Payload must be {FIRMWARE_UPDATE_PACKET_SIZE} bytes")
41-
41+
4242
msg_len = 2 + 2 + FIRMWARE_UPDATE_PACKET_SIZE + 2 # Header + packet number + payload + footer
4343
msg_header = struct.pack('BB', COM_PROTO_MSG_TYPE_FWUG_DATA, msg_len)
4444
packet_num = struct.pack('<H', packet_number) # Big-endian packet number
4545
msg_footer = struct.pack('H', 0) # Placeholder for CRC16
46-
46+
4747
message = msg_header + packet_num + payload + msg_footer
4848
crc16 = compute_crc16(message[:-2])
4949
message = msg_header + packet_num + payload + struct.pack('>H', crc16)
50-
50+
5151
self.buffer[:len(message)] = message
5252
return self.buffer[:len(message)]
53-
53+
5454
def create_fwug_cancel_msg(self):
5555
msg_len = 2 + 2 # 2 bytes header + 2 bytes footer
5656
msg_header = struct.pack('BB', COM_PROTO_MSG_TYPE_FWUG_CANCEL, msg_len)
5757
msg_footer = struct.pack('H', 0)
5858
message = msg_header + msg_footer
5959
crc16 = compute_crc16(message[:-2])
6060
message = msg_header + struct.pack('>H', crc16)
61-
61+
6262
self.buffer[:len(message)] = message
6363
return self.buffer[:len(message)]
6464

65-
def send_message_via_serial(message, port='COM9', baudrate=115200):
65+
def send_message_via_serial(message, port='/dev/ttyACM0', baudrate=115200):
6666
# Open serial port
6767
ser = serial.Serial(port, baudrate)
68-
68+
6969
# Create the buffer of 256 bytes
7070
buffer_size = 256
7171
buffer = bytearray(buffer_size)
72-
72+
7373
# Copy message into the buffer
7474
buffer[:len(message)] = message
75-
75+
7676
# Fill the rest of the buffer with 0xFF
7777
for i in range(len(message), buffer_size):
7878
buffer[i] = 0xFF
79-
79+
8080
# Convert buffer to hex string
8181
hex_buffer = buffer.hex()
82-
82+
8383
# Print hex buffer for verification
8484
#print("Hex Buffer:", hex_buffer)
8585
ser.write(bytes.fromhex(hex_buffer))
86-
86+
8787
# Initialize variables for response handling
8888
max_wait_time = 15 # Maximum wait time in seconds
89-
check_interval = 0.001 # Interval to check for response in seconds
89+
check_interval = 0.01 # Interval to check for response in seconds
9090
total_wait_time = 0
91-
91+
9292
# Wait for the response
9393
response = bytearray()
9494
while total_wait_time < max_wait_time:
@@ -97,15 +97,16 @@ def send_message_via_serial(message, port='COM9', baudrate=115200):
9797
break
9898
time.sleep(check_interval)
9999
total_wait_time += check_interval
100-
100+
101101
# Print the response in hex format for verification
102102
# if response:
103103
# print("Response (Hex):", response.hex())
104104
# else:
105105
# print("No response received within the maximum wait time")
106-
106+
107107
# Close serial port
108108
ser.close()
109+
return response
109110

110111
# Function to send firmware update data in chunks
111112
def send_firmware_update(file_path, factory):
@@ -116,12 +117,12 @@ def send_firmware_update(file_path, factory):
116117
data_chunk = f.read(FIRMWARE_UPDATE_PACKET_SIZE)
117118
if not data_chunk:
118119
break # No more data to send
119-
120+
120121
# Create FWUG_DATA message
121122
data_msg = factory.create_fwug_data_msg(packet_number, data_chunk)
122123
print(f"Sending FWUG_DATA Message {packet_number}:")
123-
send_message_via_serial(data_msg)
124-
124+
response = send_message_via_serial(data_msg)
125+
print("Response:", response.hex())
125126
packet_number += 1
126127

127128
if __name__ == "__main__":

0 commit comments

Comments
 (0)