The "Holy Bible" for embedded engineers
Firmware update mechanisms are essential for maintaining and improving embedded systems throughout their lifecycle. This guide covers various update strategies including Over-the-Air (OTA) updates, wired updates, and recovery mechanisms, with a focus on reliability, security, and user experience.
Firmware updates are processes that:
Update Methods:
├── Wired Updates
│ ├── JTAG/SWD Programming
│ ├── UART/USB Bootloader
│ └── CAN/Ethernet Updates
├── Wireless Updates
│ ├── Bluetooth/BLE
│ ├── WiFi Updates
│ ├── Cellular (3G/4G/5G)
│ └── LoRa/Satellite
└── Hybrid Approaches
├── Local + Remote
└── Incremental + Full
Update Process Flow:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Update │───▶│ Download │───▶│ Validation │───▶│ Installation│
│ Detection │ │ & Store │ │ & Security │ │ & Activation│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Rollback │ │ Progress │ │ Integrity │ │ Success/ │
│ Protection │ │ Monitoring │ │ Checking │ │ Failure │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Dual-Bank Firmware Update Architecture
┌─────────────────────────────────────────────────────────────┐
│ Bank A (Active) │ Bank B (Inactive) │
│ 0x08000000 │ 0x08040000 │
│ ┌─────────────┐ │ ┌─────────────┐ │
│ │ Firmware │ │ │ Update │ │
│ │ Header │ │ │ Buffer │ │
│ ├─────────────┤ │ ├─────────────┤ │
│ │ Application │ │ │ New │ │
│ │ Code │ │ │ Firmware │ │
│ ├─────────────┤ │ ├─────────────┤ │
│ │ Application │ │ │ (or old │ │
│ │ Data │ │ │ firmware) │ │
│ └─────────────┘ │ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Firmware Update Process
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Update │───▶│ Download │───▶│ Validation │
│ Detection │ │ New FW │ │ & Security │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Rollback │ │ Progress │ │ Integrity │
│ Protection │ │ Monitoring │ │ Checking │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│Installation │
│& Activation │
└─────────────┘
Firmware Update Security
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Transport Security │
│ ├── TLS/SSL encryption │
│ ├── Certificate validation │
│ └── Secure communication channels │
├─────────────────────────────────────────────────────────────┤
│ Layer 2: Firmware Integrity │
│ ├── Checksum verification │
│ ├── CRC validation │
│ └── Hash verification │
├─────────────────────────────────────────────────────────────┤
│ Layer 3: Cryptographic Authentication │
│ ├── Digital signatures │
│ ├── Public key validation │
│ └── Certificate chain verification │
├─────────────────────────────────────────────────────────────┤
│ Layer 4: Access Control │
│ ├── User authentication │
│ ├── Authorization checks │
│ └── Update permissions │
└─────────────────────────────────────────────────────────────┘
Update Recovery Strategies
┌─────────────────────────────────────────────────────────────┐
│ Primary Recovery: Automatic Rollback │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Update │ │ Validation │ │ Rollback to │ │
│ │ Failure │ │ Failed │ │ Previous Firmware │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Secondary Recovery: Safe Mode │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Rollback │ │ Safe Mode │ │ Manual Recovery │ │
│ │ Failed │ │ Entry │ │ via External Interface │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Tertiary Recovery: Factory Reset │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ All Recovery│ │ Factory │ │ System Restored │ │
│ │ Failed │ │ Reset │ │ to Default State │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
The update system should be designed with clear separation of concerns:
// Update system architecture
typedef struct {
update_detection_t detection; // Update detection mechanisms
update_download_t download; // Data transfer and storage
update_validation_t validation; // Integrity and security checks
update_installation_t install; // Flash programming and activation
update_recovery_t recovery; // Rollback and error recovery
update_monitoring_t monitoring; // Progress and status tracking
} update_system_t;
// Update detection interface
typedef struct {
bool (*check_available)(void); // Check for available updates
bool (*check_required)(void); // Check if update is required
update_info_t (*get_info)(void); // Get update information
} update_detection_t;
// Update download interface
typedef struct {
int (*start_download)(void); // Begin download process
int (*download_chunk)(uint8_t *data, uint32_t size); // Download data chunk
bool (*is_complete)(void); // Check if download complete
int (*verify_download)(void); // Verify downloaded data
} update_download_t;
Update Memory Layout:
┌─────────────────────────────────────────────────────────────────┐
│ Application Bank A │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Header │ │ Code │ │ Data │ │
│ │ (256B) │ │ (Variable) │ │ (Variable) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Application Bank B │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Header │ │ Code │ │ Data │ │
│ │ (256B) │ │ (Variable) │ │ (Variable) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Update Buffer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Header │ │ Code │ │ Data │ │
│ │ (256B) │ │ (Variable) │ │ (Variable) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
// OTA update protocol structure
typedef struct {
uint32_t update_id; // Unique update identifier
uint32_t firmware_size; // Total firmware size
uint32_t firmware_version; // New firmware version
uint32_t checksum; // CRC32 checksum
uint32_t signature_length; // Length of signature
uint8_t signature[64]; // Cryptographic signature
uint32_t chunk_size; // Size of each data chunk
uint32_t total_chunks; // Total number of chunks
uint8_t update_data[]; // Variable length data
} ota_update_t;
// OTA update header
typedef struct {
uint32_t magic_number; // Magic number for identification
uint32_t protocol_version; // Protocol version
uint32_t update_type; // Type of update (full/incremental)
uint32_t compression_type; // Compression algorithm used
uint32_t encryption_type; // Encryption method
uint32_t timestamp; // Update timestamp
uint32_t crc32; // Header CRC32
} ota_header_t;
#define OTA_MAGIC_NUMBER 0x4F544100 // "OTA\0"
#define OTA_PROTOCOL_VERSION 0x00010000 // v1.0.0
#define OTA_UPDATE_TYPE_FULL 0x00000001
#define OTA_UPDATE_TYPE_INCREMENTAL 0x00000002
// OTA update states
typedef enum {
OTA_STATE_IDLE,
OTA_STATE_DOWNLOADING,
OTA_STATE_VALIDATING,
OTA_STATE_INSTALLING,
OTA_STATE_ACTIVATING,
OTA_STATE_COMPLETE,
OTA_STATE_ERROR,
OTA_STATE_ROLLBACK
} ota_state_t;
// OTA update state machine
ota_state_t ota_state_machine(ota_state_t current_state, ota_event_t event) {
switch (current_state) {
case OTA_STATE_IDLE:
if (event == OTA_EVENT_UPDATE_AVAILABLE) {
return OTA_STATE_DOWNLOADING;
}
break;
case OTA_STATE_DOWNLOADING:
if (event == OTA_EVENT_DOWNLOAD_COMPLETE) {
return OTA_STATE_VALIDATING;
} else if (event == OTA_EVENT_DOWNLOAD_ERROR) {
return OTA_STATE_ERROR;
}
break;
case OTA_STATE_VALIDATING:
if (event == OTA_EVENT_VALIDATION_SUCCESS) {
return OTA_STATE_INSTALLING;
} else if (event == OTA_EVENT_VALIDATION_FAILED) {
return OTA_STATE_ERROR;
}
break;
case OTA_STATE_INSTALLING:
if (event == OTA_EVENT_INSTALLATION_COMPLETE) {
return OTA_STATE_ACTIVATING;
} else if (event == OTA_EVENT_INSTALLATION_ERROR) {
return OTA_STATE_ROLLBACK;
}
break;
case OTA_STATE_ACTIVATING:
if (event == OTA_EVENT_ACTIVATION_SUCCESS) {
return OTA_STATE_COMPLETE;
} else if (event == OTA_EVENT_ACTIVATION_FAILED) {
return OTA_STATE_ROLLBACK;
}
break;
}
return current_state;
}
// Chunk download management
typedef struct {
uint32_t chunk_id; // Current chunk identifier
uint32_t chunk_size; // Size of current chunk
uint32_t total_chunks; // Total number of chunks
uint32_t received_chunks; // Number of received chunks
uint32_t chunk_timeout_ms; // Timeout for chunk reception
uint32_t retry_count; // Retry attempts for failed chunks
uint8_t *chunk_buffer; // Buffer for chunk data
} chunk_download_t;
// Download chunk with retry mechanism
int download_chunk_with_retry(chunk_download_t *chunk_info) {
int result;
uint32_t retries = 0;
while (retries < MAX_CHUNK_RETRIES) {
// Request chunk from server
result = request_chunk(chunk_info->chunk_id);
if (result != 0) {
retries++;
delay_ms(CHUNK_RETRY_DELAY_MS);
continue;
}
// Wait for chunk data with timeout
result = wait_for_chunk_data(chunk_info->chunk_buffer,
chunk_info->chunk_size,
chunk_info->chunk_timeout_ms);
if (result == 0) {
// Verify chunk integrity
if (verify_chunk_integrity(chunk_info->chunk_id,
chunk_info->chunk_buffer,
chunk_info->chunk_size) == 0) {
return 0; // Success
}
}
retries++;
log_warning("Chunk %d download failed, retry %d",
chunk_info->chunk_id, retries);
delay_ms(CHUNK_RETRY_DELAY_MS);
}
log_error("Chunk %d download failed after %d retries",
chunk_info->chunk_id, MAX_CHUNK_RETRIES);
return -1;
}
// UART bootloader protocol
typedef struct {
uint8_t command; // Command byte
uint8_t length; // Data length
uint8_t data[256]; // Command data
uint16_t checksum; // Simple checksum
} uart_command_t;
// UART bootloader commands
#define UART_CMD_ERASE_FLASH 0x01
#define UART_CMD_WRITE_FLASH 0x02
#define UART_CMD_READ_FLASH 0x03
#define UART_CMD_VERIFY_FLASH 0x04
#define UART_CMD_JUMP_APP 0x05
#define UART_CMD_GET_VERSION 0x06
#define UART_CMD_GET_DEVICE_ID 0x07
// UART bootloader command handler
int handle_uart_command(uart_command_t *cmd) {
// Verify checksum
if (!verify_uart_checksum(cmd)) {
send_uart_response(UART_RESP_ERROR, "Checksum error");
return -1;
}
switch (cmd->command) {
case UART_CMD_ERASE_FLASH:
return handle_erase_flash(cmd);
case UART_CMD_WRITE_FLASH:
return handle_write_flash(cmd);
case UART_CMD_READ_FLASH:
return handle_read_flash(cmd);
case UART_CMD_VERIFY_FLASH:
return handle_verify_flash(cmd);
case UART_CMD_JUMP_APP:
return handle_jump_app(cmd);
case UART_CMD_GET_VERSION:
return handle_get_version(cmd);
case UART_CMD_GET_DEVICE_ID:
return handle_get_device_id(cmd);
default:
send_uart_response(UART_RESP_ERROR, "Unknown command");
return -1;
}
}
// Flash write command handler
int handle_write_flash(uart_command_t *cmd) {
uint32_t address = *(uint32_t*)&cmd->data[0];
uint32_t length = cmd->length - 4;
// Validate address range
if (address < APPLICATION_START_ADDRESS ||
address + length > FLASH_END_ADDRESS) {
send_uart_response(UART_RESP_ERROR, "Invalid address range");
return -1;
}
// Unlock flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
// Write data to flash
int result = write_flash_data(address, &cmd->data[4], length);
// Lock flash
FLASH->CR |= FLASH_CR_LOCK;
if (result == 0) {
send_uart_response(UART_RESP_SUCCESS, "Flash write successful");
} else {
send_uart_response(UART_RESP_ERROR, "Flash write failed");
}
return result;
}
// CAN update message structure
typedef struct {
uint32_t message_id; // CAN message identifier
uint8_t message_type; // Type of message
uint8_t sequence_number; // Sequence number for ordering
uint8_t data_length; // Length of data payload
uint8_t data[8]; // Data payload
} can_update_message_t;
// CAN update message types
#define CAN_MSG_UPDATE_START 0x01
#define CAN_MSG_UPDATE_DATA 0x02
#define CAN_MSG_UPDATE_END 0x03
#define CAN_MSG_UPDATE_ACK 0x04
#define CAN_MSG_UPDATE_NACK 0x05
#define CAN_MSG_UPDATE_STATUS 0x06
// CAN update message handler
int handle_can_update_message(can_update_message_t *msg) {
static uint8_t expected_sequence = 0;
static uint32_t update_address = 0;
switch (msg->message_type) {
case CAN_MSG_UPDATE_START:
// Initialize update process
if (initialize_update_process(msg) == 0) {
update_address = *(uint32_t*)&msg->data[0];
expected_sequence = 0;
send_can_ack(msg->sequence_number);
} else {
send_can_nack(msg->sequence_number, "Update init failed");
}
break;
case CAN_MSG_UPDATE_DATA:
// Handle data chunk
if (msg->sequence_number == expected_sequence) {
if (write_update_chunk(update_address, msg->data, msg->data_length) == 0) {
update_address += msg->data_length;
expected_sequence++;
send_can_ack(msg->sequence_number);
} else {
send_can_nack(msg->sequence_number, "Write failed");
}
} else {
send_can_nack(msg->sequence_number, "Sequence mismatch");
}
break;
case CAN_MSG_UPDATE_END:
// Finalize update process
if (finalize_update_process() == 0) {
send_can_ack(msg->sequence_number);
} else {
send_can_nack(msg->sequence_number, "Finalization failed");
}
break;
}
return 0;
}
// Cryptographic signature verification
typedef struct {
uint8_t public_key[64]; // Public key for verification
uint8_t signature[64]; // ECDSA signature
uint8_t hash[32]; // SHA256 hash of firmware
uint32_t timestamp; // Signature timestamp
uint32_t nonce; // Random nonce for replay protection
} firmware_signature_t;
// Verify firmware signature
bool verify_firmware_signature(firmware_signature_t *sig, uint8_t *firmware, uint32_t size) {
// 1. Check timestamp freshness
uint32_t current_time = get_system_time();
if (current_time - sig->timestamp > MAX_SIGNATURE_AGE) {
log_error("Signature too old: %d seconds", current_time - sig->timestamp);
return false;
}
// 2. Calculate firmware hash
uint8_t calculated_hash[32];
if (calculate_sha256(firmware, size, calculated_hash) != 0) {
log_error("Failed to calculate firmware hash");
return false;
}
// 3. Compare calculated hash with signature hash
if (memcmp(calculated_hash, sig->hash, 32) != 0) {
log_error("Firmware hash mismatch");
return false;
}
// 4. Verify ECDSA signature
if (!verify_ecdsa_signature(sig->hash, sig->signature, sig->public_key)) {
log_error("ECDSA signature verification failed");
return false;
}
// 5. Verify nonce uniqueness (implement replay protection)
if (!verify_nonce_uniqueness(sig->nonce)) {
log_error("Nonce reuse detected");
return false;
}
return true;
}
// Secure update channel implementation
typedef struct {
uint8_t session_key[32]; // Session encryption key
uint8_t iv[16]; // Initialization vector
uint32_t sequence_number; // Sequence number for ordering
bool encryption_enabled; // Encryption status
} secure_channel_t;
// Encrypt update data
int encrypt_update_data(secure_channel_t *channel, uint8_t *data,
uint32_t size, uint8_t *encrypted_data) {
if (!channel->encryption_enabled) {
memcpy(encrypted_data, data, size);
return 0;
}
// Generate new IV for each encryption
if (generate_random_iv(channel->iv) != 0) {
return -1;
}
// Encrypt data using AES-256-GCM
uint8_t tag[16];
if (aes_256_gcm_encrypt(channel->session_key, channel->iv,
data, size, encrypted_data, tag) != 0) {
return -1;
}
// Append authentication tag
memcpy(encrypted_data + size, tag, 16);
return 0;
}
// Decrypt update data
int decrypt_update_data(secure_channel_t *channel, uint8_t *encrypted_data,
uint32_t size, uint8_t *decrypted_data) {
if (!channel->encryption_enabled) {
memcpy(decrypted_data, encrypted_data, size);
return 0;
}
// Extract authentication tag
uint8_t tag[16];
memcpy(tag, encrypted_data + size - 16, 16);
// Decrypt data
if (aes_256_gcm_decrypt(channel->session_key, channel->iv,
encrypted_data, size - 16, decrypted_data, tag) != 0) {
return -1;
}
return 0;
}
// Dual-bank update management
typedef struct {
uint32_t active_bank; // Currently active bank
uint32_t update_bank; // Bank being updated
uint32_t bank_size; // Size of each bank
bool update_in_progress; // Update status
uint32_t update_version; // Version being updated to
} dual_bank_manager_t;
// Initialize dual-bank system
int init_dual_bank_system(dual_bank_manager_t *manager) {
// Determine active bank from boot configuration
manager->active_bank = get_active_bank();
manager->update_bank = (manager->active_bank == BANK_A) ? BANK_B : BANK_A;
manager->bank_size = get_bank_size();
manager->update_in_progress = false;
// Validate both banks
if (!validate_bank(BANK_A) || !validate_bank(BANK_B)) {
log_error("Bank validation failed");
return -1;
}
return 0;
}
// Perform dual-bank update
int perform_dual_bank_update(dual_bank_manager_t *manager,
uint8_t *firmware, uint32_t size) {
if (manager->update_in_progress) {
log_error("Update already in progress");
return -1;
}
manager->update_in_progress = true;
// 1. Erase update bank
if (erase_bank(manager->update_bank) != 0) {
log_error("Failed to erase update bank");
manager->update_in_progress = false;
return -1;
}
// 2. Write firmware to update bank
if (write_bank(manager->update_bank, firmware, size) != 0) {
log_error("Failed to write to update bank");
manager->update_in_progress = false;
return -1;
}
// 3. Verify update bank
if (verify_bank(manager->update_bank, firmware, size) != 0) {
log_error("Update bank verification failed");
manager->update_in_progress = false;
return -1;
}
// 4. Switch to update bank
if (switch_active_bank(manager->update_bank) != 0) {
log_error("Failed to switch active bank");
manager->update_in_progress = false;
return -1;
}
// 5. Update complete
manager->active_bank = manager->update_bank;
manager->update_bank = (manager->active_bank == BANK_A) ? BANK_B : BANK_A;
manager->update_in_progress = false;
return 0;
}
// Rollback protection implementation
typedef struct {
uint32_t current_version; // Current firmware version
uint32_t minimum_version; // Minimum allowed version
uint32_t rollback_counter; // Rollback attempt counter
uint32_t max_rollback_attempts; // Maximum rollback attempts
bool rollback_protection; // Rollback protection status
} rollback_protection_t;
// Check rollback protection
int check_rollback_protection(rollback_protection_t *protection, uint32_t new_version) {
// 1. Check if new version is lower than current
if (new_version < protection->current_version) {
log_warning("Rollback attempt detected: current=%d, new=%d",
protection->current_version, new_version);
// 2. Check if rollback is allowed
if (new_version < protection->minimum_version) {
log_error("Rollback to version %d not allowed (minimum=%d)",
new_version, protection->minimum_version);
return -1;
}
// 3. Check rollback attempt counter
if (protection->rollback_counter >= protection->max_rollback_attempts) {
log_error("Maximum rollback attempts exceeded");
return -1;
}
// 4. Increment rollback counter
protection->rollback_counter++;
save_rollback_counter(protection->rollback_counter);
log_info("Rollback allowed (attempt %d/%d)",
protection->rollback_counter, protection->max_rollback_attempts);
} else {
// Reset rollback counter for forward updates
protection->rollback_counter = 0;
save_rollback_counter(0);
}
return 0;
}
// OTA update system implementation
typedef struct {
ota_state_t state; // Current update state
ota_header_t header; // Update header information
chunk_download_t chunk_info; // Chunk download management
secure_channel_t secure_channel; // Secure communication channel
dual_bank_manager_t bank_manager; // Dual-bank management
update_progress_t progress; // Update progress tracking
} ota_update_system_t;
// Main OTA update function
int perform_ota_update(ota_update_system_t *ota_system) {
int result = 0;
// 1. Initialize update system
if (init_ota_system(ota_system) != 0) {
log_error("Failed to initialize OTA system");
return -1;
}
// 2. Download update header
if (download_update_header(ota_system) != 0) {
log_error("Failed to download update header");
return -1;
}
// 3. Validate update header
if (validate_update_header(ota_system) != 0) {
log_error("Update header validation failed");
return -1;
}
// 4. Download firmware in chunks
ota_system->state = OTA_STATE_DOWNLOADING;
while (!ota_system->chunk_info.is_complete) {
result = download_chunk_with_retry(&ota_system->chunk_info);
if (result != 0) {
log_error("Chunk download failed");
ota_system->state = OTA_STATE_ERROR;
break;
}
// Update progress
update_progress(&ota_system->progress,
ota_system->chunk_info.received_chunks,
ota_system->chunk_info.total_chunks);
}
// 5. Validate downloaded firmware
if (result == 0) {
ota_system->state = OTA_STATE_VALIDATING;
if (validate_downloaded_firmware(ota_system) != 0) {
log_error("Firmware validation failed");
ota_system->state = OTA_STATE_ERROR;
result = -1;
}
}
// 6. Install firmware if validation successful
if (result == 0) {
ota_system->state = OTA_STATE_INSTALLING;
if (install_firmware(ota_system) != 0) {
log_error("Firmware installation failed");
ota_system->state = OTA_STATE_ERROR;
result = -1;
}
}
// 7. Activate new firmware
if (result == 0) {
ota_system->state = OTA_STATE_ACTIVATING;
if (activate_new_firmware(ota_system) != 0) {
log_error("Firmware activation failed");
ota_system->state = OTA_STATE_ERROR;
result = -1;
}
}
// 8. Finalize update
if (result == 0) {
ota_system->state = OTA_STATE_COMPLETE;
log_info("OTA update completed successfully");
} else {
log_error("OTA update failed, entering recovery mode");
enter_recovery_mode(ota_system);
}
return result;
}
Firmware update mechanisms are critical for maintaining embedded systems throughout their lifecycle. A well-designed update system provides:
The key to successful update mechanism implementation lies in:
By following these principles and implementing the techniques discussed in this guide, developers can create robust, secure, and user-friendly firmware update systems for their embedded products.