mirror of https://github.com/wolfSSL/wolfBoot.git
1094 lines
30 KiB
C
1094 lines
30 KiB
C
/* app_s32k1xx.c
|
|
*
|
|
* Test bare-metal application for NXP S32K1xx
|
|
* Features:
|
|
* - LED indicator based on firmware version (Green=v1, Blue=v>1)
|
|
* - Interactive console with commands
|
|
* - XMODEM firmware update support
|
|
* - Partition and keystore information display
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include "hal.h"
|
|
#include "../hal/s32k1xx.h"
|
|
#include "wolfboot/wolfboot.h"
|
|
#include "keystore.h"
|
|
#include "target.h"
|
|
#include "image.h"
|
|
|
|
#ifdef TARGET_s32k1xx
|
|
|
|
/* RAMFUNCTION for test-app: code that runs during flash operations must be in RAM */
|
|
#ifdef RAM_CODE
|
|
#define APP_RAMFUNCTION __attribute__((used,section(".ramcode"),long_call))
|
|
#else
|
|
#define APP_RAMFUNCTION
|
|
#endif
|
|
|
|
/* ============== SysTick Timer ============== */
|
|
|
|
static volatile uint32_t jiffies = 0;
|
|
|
|
/* SysTick interrupt handler - called isr_systick to match startup_arm.c */
|
|
void isr_systick(void)
|
|
{
|
|
jiffies++;
|
|
}
|
|
|
|
static uint32_t get_time_ms(void)
|
|
{
|
|
return jiffies;
|
|
}
|
|
|
|
static void delay_ms(uint32_t ms)
|
|
{
|
|
uint32_t start = jiffies;
|
|
while ((jiffies - start) < ms) {
|
|
__asm__ volatile ("wfi");
|
|
}
|
|
}
|
|
|
|
static void systick_init(void)
|
|
{
|
|
/* Configure SysTick for 1ms tick */
|
|
SYST_RVR = (CLOCK_SPEED / 1000) - 1;
|
|
SYST_CVR = 0;
|
|
SYST_CSR = SYST_CSR_ENABLE | SYST_CSR_TICKINT | SYST_CSR_CLKSOURCE;
|
|
}
|
|
|
|
/* ============== LED Functions ============== */
|
|
|
|
static void led_init(void)
|
|
{
|
|
/* Enable clock to PORTD */
|
|
PCC_PORTD |= PCC_CGC;
|
|
|
|
/* Configure LED pins as GPIO */
|
|
PORTD_PCR0 = PORT_PCR_MUX_GPIO; /* Blue LED */
|
|
PORTD_PCR15 = PORT_PCR_MUX_GPIO; /* Red LED */
|
|
PORTD_PCR16 = PORT_PCR_MUX_GPIO; /* Green LED */
|
|
|
|
/* Set as outputs */
|
|
GPIOD_PDDR |= (1UL << LED_PIN_BLUE) | (1UL << LED_PIN_RED) | (1UL << LED_PIN_GREEN);
|
|
|
|
/* All LEDs off initially (active low) */
|
|
GPIOD_PSOR = (1UL << LED_PIN_BLUE) | (1UL << LED_PIN_RED) | (1UL << LED_PIN_GREEN);
|
|
}
|
|
|
|
static void led_green_on(void)
|
|
{
|
|
GPIOD_PCOR = (1UL << LED_PIN_GREEN); /* Active low */
|
|
}
|
|
|
|
static void led_green_off(void)
|
|
{
|
|
GPIOD_PSOR = (1UL << LED_PIN_GREEN);
|
|
}
|
|
|
|
static void led_blue_on(void)
|
|
{
|
|
GPIOD_PCOR = (1UL << LED_PIN_BLUE); /* Active low */
|
|
}
|
|
|
|
static void led_blue_off(void)
|
|
{
|
|
GPIOD_PSOR = (1UL << LED_PIN_BLUE);
|
|
}
|
|
|
|
static void APP_RAMFUNCTION led_red_on(void)
|
|
{
|
|
GPIOD_PCOR = (1UL << LED_PIN_RED); /* Active low */
|
|
}
|
|
|
|
static void APP_RAMFUNCTION led_red_off(void)
|
|
{
|
|
GPIOD_PSOR = (1UL << LED_PIN_RED);
|
|
}
|
|
|
|
static void led_toggle_version(uint32_t version)
|
|
{
|
|
if (version == 1) {
|
|
GPIOD_PTOR = (1UL << LED_PIN_GREEN);
|
|
} else {
|
|
GPIOD_PTOR = (1UL << LED_PIN_BLUE);
|
|
}
|
|
}
|
|
|
|
/* Set LED based on version: Green for v1, Blue for v>1 */
|
|
static void led_set_version(uint32_t version)
|
|
{
|
|
/* Turn off both first */
|
|
led_green_off();
|
|
led_blue_off();
|
|
|
|
if (version == 1) {
|
|
led_green_on();
|
|
} else if (version > 1) {
|
|
led_blue_on();
|
|
}
|
|
}
|
|
|
|
/* ============== System Control ============== */
|
|
|
|
void arch_reboot(void)
|
|
{
|
|
SCB_AIRCR = AIRCR_VECTKEY | AIRCR_SYSRESETREQ;
|
|
while (1) {
|
|
__asm__ volatile ("wfi");
|
|
}
|
|
}
|
|
|
|
/* ============== UART / Printf Support ============== */
|
|
|
|
#ifdef DEBUG_UART
|
|
/* UART functions are declared in s32k1xx.h and implemented in hal/s32k1xx.c */
|
|
|
|
/* Flag to block text output during XMODEM transfer */
|
|
static volatile int xmodem_active = 0;
|
|
|
|
/* ============== UART RX Interrupt Buffering ============== */
|
|
#define UART_RX_BUF_SIZE 512
|
|
static volatile uint8_t uart_rx_buf[UART_RX_BUF_SIZE];
|
|
static volatile uint32_t uart_rx_head = 0; /* Write index (ISR writes here) */
|
|
static volatile uint32_t uart_rx_tail = 0; /* Read index (app reads from here) */
|
|
|
|
/* LPUART1 RX Interrupt Handler */
|
|
void isr_lpuart1(void)
|
|
{
|
|
uint32_t stat = LPUART1_STAT;
|
|
|
|
/* Clear only the error flags (write 1 to clear) - do NOT write other bits */
|
|
uint32_t errors = stat & (LPUART_STAT_OR | LPUART_STAT_NF | LPUART_STAT_FE | LPUART_STAT_PF);
|
|
if (errors) {
|
|
LPUART1_STAT = errors; /* Write 1 to clear only error flags */
|
|
}
|
|
|
|
/* Read all available bytes from FIFO */
|
|
while (LPUART1_STAT & LPUART_STAT_RDRF) {
|
|
uint8_t c = (uint8_t)(LPUART1_DATA & 0xFF);
|
|
uint32_t next_head = (uart_rx_head + 1) % UART_RX_BUF_SIZE;
|
|
|
|
/* Store byte if buffer not full */
|
|
if (next_head != uart_rx_tail) {
|
|
uart_rx_buf[uart_rx_head] = c;
|
|
uart_rx_head = next_head;
|
|
}
|
|
/* else: buffer full, discard byte */
|
|
}
|
|
}
|
|
|
|
/* Read from RX buffer (for XMODEM) - returns number of bytes read
|
|
* Must be RAMFUNCTION since it's called during flash operations */
|
|
static int APP_RAMFUNCTION uart_rx_isr(uint8_t *buf, int max_len)
|
|
{
|
|
int count = 0;
|
|
|
|
while (count < max_len && uart_rx_tail != uart_rx_head) {
|
|
buf[count++] = uart_rx_buf[uart_rx_tail];
|
|
uart_rx_tail = (uart_rx_tail + 1) % UART_RX_BUF_SIZE;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* Check if RX data available */
|
|
static int uart_rx_available(void)
|
|
{
|
|
return (uart_rx_head != uart_rx_tail) ? 1 : 0;
|
|
}
|
|
|
|
/* Read single character from RX buffer (for console) - returns 1 if char read, 0 if none */
|
|
static int uart_getc(char *c)
|
|
{
|
|
if (uart_rx_tail != uart_rx_head) {
|
|
*c = uart_rx_buf[uart_rx_tail];
|
|
uart_rx_tail = (uart_rx_tail + 1) % UART_RX_BUF_SIZE;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Enable LPUART RX interrupt */
|
|
static void uart_rx_irq_enable(void)
|
|
{
|
|
/* Set interrupt priority lower than SysTick (higher number = lower priority)
|
|
* SysTick defaults to priority 0, so set LPUART to 2 to ensure
|
|
* jiffies keeps incrementing even during heavy UART traffic. */
|
|
NVIC_SetPriority(LPUART1_IRQn, 2);
|
|
|
|
/* Enable LPUART1 interrupt in NVIC */
|
|
NVIC_EnableIRQ(LPUART1_IRQn);
|
|
|
|
/* Enable Receiver Interrupt in LPUART */
|
|
LPUART1_CTRL |= LPUART_CTRL_RIE;
|
|
}
|
|
|
|
/* Print hex buffer (similar to stm32h5 style) */
|
|
#define LINE_LEN 16
|
|
static void print_hex(const uint8_t* buffer, uint32_t length, int dumpChars)
|
|
{
|
|
uint32_t i, sz;
|
|
|
|
if (!buffer) {
|
|
printf("\tNULL\r\n");
|
|
return;
|
|
}
|
|
|
|
while (length > 0) {
|
|
sz = length;
|
|
if (sz > LINE_LEN)
|
|
sz = LINE_LEN;
|
|
|
|
printf("\t");
|
|
for (i = 0; i < LINE_LEN; i++) {
|
|
if (i < length)
|
|
printf("%02x ", buffer[i]);
|
|
else
|
|
printf(" ");
|
|
}
|
|
if (dumpChars) {
|
|
printf("| ");
|
|
for (i = 0; i < sz; i++) {
|
|
if (buffer[i] > 31 && buffer[i] < 127)
|
|
printf("%c", buffer[i]);
|
|
else
|
|
printf(".");
|
|
}
|
|
}
|
|
printf("\r\n");
|
|
|
|
buffer += sz;
|
|
length -= sz;
|
|
}
|
|
}
|
|
#endif /* DEBUG_UART */
|
|
|
|
/* ============== Partition State Names ============== */
|
|
|
|
static const char* part_state_name(uint8_t state, int state_retval)
|
|
{
|
|
if (state_retval == 0) {
|
|
return "(no trailer)";
|
|
}
|
|
switch (state) {
|
|
case IMG_STATE_NEW: return "NEW";
|
|
case IMG_STATE_UPDATING: return "UPDATING";
|
|
case IMG_STATE_TESTING: return "TESTING";
|
|
case IMG_STATE_SUCCESS: return "SUCCESS";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/* ============== Key Type Names ============== */
|
|
|
|
static const char* key_type_name(uint32_t type)
|
|
{
|
|
switch (type) {
|
|
case AUTH_KEY_ECC256: return "ECDSA P-256 (secp256r1)";
|
|
case AUTH_KEY_ECC384: return "ECDSA P-384 (secp384r1)";
|
|
case AUTH_KEY_ECC521: return "ECDSA P-521 (secp521r1)";
|
|
case AUTH_KEY_RSA2048: return "RSA-2048";
|
|
case AUTH_KEY_RSA3072: return "RSA-3072";
|
|
case AUTH_KEY_RSA4096: return "RSA-4096";
|
|
case AUTH_KEY_ED25519: return "Ed25519";
|
|
case AUTH_KEY_ED448: return "Ed448";
|
|
case AUTH_KEY_LMS: return "LMS";
|
|
case AUTH_KEY_XMSS: return "XMSS";
|
|
case AUTH_KEY_ML_DSA: return "ML-DSA";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
static const char* hash_type_name(void)
|
|
{
|
|
#ifdef WOLFBOOT_HASH_SHA256
|
|
return "SHA-256";
|
|
#elif defined(WOLFBOOT_HASH_SHA384)
|
|
return "SHA-384";
|
|
#elif defined(WOLFBOOT_HASH_SHA512)
|
|
return "SHA-512";
|
|
#elif defined(WOLFBOOT_HASH_SHA3_384)
|
|
return "SHA3-384";
|
|
#endif
|
|
return "Unknown";
|
|
}
|
|
|
|
/* ============== Information Display ============== */
|
|
|
|
#ifdef DEBUG_UART
|
|
static void print_partition_info(void)
|
|
{
|
|
uint32_t boot_ver, update_ver;
|
|
uint8_t boot_state = 0, update_state = 0;
|
|
int boot_state_valid, update_state_valid;
|
|
|
|
boot_ver = wolfBoot_current_firmware_version();
|
|
update_ver = wolfBoot_update_firmware_version();
|
|
|
|
boot_state_valid = wolfBoot_get_partition_state(PART_BOOT, &boot_state);
|
|
update_state_valid = wolfBoot_get_partition_state(PART_UPDATE, &update_state);
|
|
|
|
printf("\r\n=== Partition Information ===\r\n");
|
|
|
|
printf("Boot Partition:\r\n");
|
|
printf(" Address: 0x%08lX\r\n", (unsigned long)WOLFBOOT_PARTITION_BOOT_ADDRESS);
|
|
printf(" Version: %lu\r\n", (unsigned long)boot_ver);
|
|
printf(" State: %s\r\n", part_state_name(boot_state, boot_state_valid));
|
|
|
|
printf("Update Partition:\r\n");
|
|
printf(" Address: 0x%08lX\r\n", (unsigned long)WOLFBOOT_PARTITION_UPDATE_ADDRESS);
|
|
printf(" Version: %lu\r\n", (unsigned long)update_ver);
|
|
printf(" State: %s\r\n", part_state_name(update_state, update_state_valid));
|
|
|
|
printf("Swap Partition:\r\n");
|
|
printf(" Address: 0x%08lX\r\n", (unsigned long)WOLFBOOT_PARTITION_SWAP_ADDRESS);
|
|
printf(" Size: %lu bytes\r\n", (unsigned long)WOLFBOOT_SECTOR_SIZE);
|
|
}
|
|
|
|
static void print_keystore_info(void)
|
|
{
|
|
#ifndef WOLFBOOT_NO_SIGN
|
|
uint32_t n_keys;
|
|
int i;
|
|
|
|
printf("\r\n=== Keystore Information ===\r\n");
|
|
|
|
n_keys = keystore_num_pubkeys();
|
|
printf("Number of public keys: %lu\r\n", (unsigned long)n_keys);
|
|
printf("Hash: %s\r\n", hash_type_name());
|
|
|
|
for (i = 0; i < (int)n_keys; i++) {
|
|
uint32_t size = keystore_get_size(i);
|
|
uint32_t type = keystore_get_key_type(i);
|
|
uint8_t* keybuf = keystore_get_buffer(i);
|
|
|
|
printf("\r\nKey #%d:\r\n", i);
|
|
printf(" Algorithm: %s\r\n", key_type_name(type));
|
|
printf(" Size: %lu bytes\r\n", (unsigned long)size);
|
|
printf(" Data:\r\n");
|
|
print_hex(keybuf, size, 0);
|
|
}
|
|
#else
|
|
printf("\r\n=== Keystore Information ===\r\n");
|
|
printf("Signing disabled (SIGN=NONE)\r\n");
|
|
#endif /* !WOLFBOOT_NO_SIGN */
|
|
}
|
|
|
|
/* ============== XMODEM Transfer ============== */
|
|
|
|
#define XSOH 0x01
|
|
#define XEOT 0x04
|
|
#define XACK 0x06
|
|
#define XNAK 0x15
|
|
#define XCAN 0x18
|
|
#define XCRC 'C' /* Request CRC mode */
|
|
|
|
#define XMODEM_PAYLOAD_SIZE 128
|
|
#define XMODEM_PACKET_SIZE_CRC (3 + XMODEM_PAYLOAD_SIZE + 2) /* SOH + blk + ~blk + data + CRC16 */
|
|
#define XMODEM_TIMEOUT_MS 1000
|
|
|
|
/* CRC-16-CCITT for XMODEM-CRC mode */
|
|
static uint16_t APP_RAMFUNCTION crc16_ccitt(const uint8_t* data, int len)
|
|
{
|
|
uint16_t crc = 0;
|
|
int i, j;
|
|
for (i = 0; i < len; i++) {
|
|
crc ^= ((uint16_t)data[i] << 8);
|
|
for (j = 0; j < 8; j++) {
|
|
if (crc & 0x8000) {
|
|
crc = (crc << 1) ^ 0x1021;
|
|
} else {
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
/* Raw byte transmit for XMODEM (declared in hal, runs from RAM) */
|
|
extern void uart_tx(uint8_t byte);
|
|
|
|
/* RAM-based memory copy for use during flash operations */
|
|
static void APP_RAMFUNCTION ram_memcpy(void *dst, const void *src, uint32_t len)
|
|
{
|
|
uint8_t *d = (uint8_t *)dst;
|
|
const uint8_t *s = (const uint8_t *)src;
|
|
while (len--) {
|
|
*d++ = *s++;
|
|
}
|
|
}
|
|
|
|
static void APP_RAMFUNCTION xmodem_cancel(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < 10; i++) {
|
|
uart_tx(XCAN);
|
|
}
|
|
}
|
|
|
|
/* XMODEM receive state - passed to RAM function */
|
|
typedef struct {
|
|
uint32_t dst_offset;
|
|
int result;
|
|
/* Debug counters */
|
|
uint32_t pkts_received;
|
|
uint32_t pkts_crc_fail;
|
|
uint32_t pkts_num_fail;
|
|
uint32_t pkts_soh_fail;
|
|
uint32_t timeouts;
|
|
} xmodem_state_t;
|
|
|
|
/* Core XMODEM-CRC receive loop - runs entirely from RAM during flash operations
|
|
* Uses XMODEM-CRC mode (133-byte packets with 16-bit CRC)
|
|
* Returns: 0 on success, -1 on error
|
|
*/
|
|
static int APP_RAMFUNCTION xmodem_receive_ram(xmodem_state_t *state)
|
|
{
|
|
uint8_t xpkt[XMODEM_PACKET_SIZE_CRC];
|
|
uint8_t payload[XMODEM_PAYLOAD_SIZE];
|
|
uint8_t pkt_num = 0, pkt_num_expected = 0xFF;
|
|
uint32_t t_size = 0;
|
|
uint32_t now;
|
|
uint32_t i = 0;
|
|
int transfer_started = 0;
|
|
int eot_expected = 0;
|
|
int ret = -1;
|
|
|
|
state->dst_offset = 0;
|
|
state->result = -1;
|
|
state->pkts_received = 0;
|
|
state->pkts_crc_fail = 0;
|
|
state->pkts_num_fail = 0;
|
|
state->pkts_soh_fail = 0;
|
|
state->timeouts = 0;
|
|
|
|
/* Send 'C' to request CRC mode (XMODEM-CRC) */
|
|
uart_tx(XCRC);
|
|
|
|
while (1) {
|
|
now = jiffies; /* Direct access to volatile - faster than function call */
|
|
i = 0;
|
|
|
|
/* Receive packet - uses interrupt-buffered RX to avoid FIFO overflow */
|
|
while (i < XMODEM_PACKET_SIZE_CRC) {
|
|
int r = uart_rx_isr(&xpkt[i], XMODEM_PACKET_SIZE_CRC - i);
|
|
if (r > 0) {
|
|
i += r;
|
|
now = jiffies;
|
|
if (i >= 1 && xpkt[0] == XEOT) {
|
|
break; /* End of transmission */
|
|
}
|
|
} else if (jiffies > (now + XMODEM_TIMEOUT_MS)) {
|
|
now = jiffies;
|
|
state->timeouts++;
|
|
if (i == 0) {
|
|
uart_tx(XCRC); /* Request CRC mode again */
|
|
}
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
/* Check for EOT */
|
|
if (xpkt[0] == XEOT) {
|
|
uart_tx(XACK);
|
|
led_red_on(); /* Indicate transfer complete */
|
|
ret = 0;
|
|
break;
|
|
} else if (eot_expected) {
|
|
uart_tx(XNAK);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
/* Validate SOH */
|
|
if (xpkt[0] != XSOH) {
|
|
state->pkts_soh_fail++;
|
|
continue;
|
|
}
|
|
state->pkts_received++;
|
|
|
|
/* Validate packet number */
|
|
pkt_num = xpkt[1];
|
|
if ((uint8_t)(~xpkt[2]) == pkt_num) {
|
|
uint16_t recv_crc, calc_crc;
|
|
|
|
if (!transfer_started) {
|
|
pkt_num_expected = pkt_num;
|
|
transfer_started = 1;
|
|
} else if (pkt_num_expected != pkt_num) {
|
|
uart_tx(XNAK);
|
|
continue;
|
|
}
|
|
|
|
/* Toggle LED to show activity */
|
|
if ((pkt_num & 0x0F) == 0) {
|
|
led_red_on();
|
|
} else if ((pkt_num & 0x0F) == 8) {
|
|
led_red_off();
|
|
}
|
|
|
|
/* Validate CRC-16 - XMODEM-CRC uses CRC over DATA bytes only */
|
|
recv_crc = ((uint16_t)xpkt[XMODEM_PACKET_SIZE_CRC - 2] << 8) |
|
|
xpkt[XMODEM_PACKET_SIZE_CRC - 1];
|
|
calc_crc = crc16_ccitt(xpkt + 3, XMODEM_PAYLOAD_SIZE);
|
|
|
|
if (recv_crc == calc_crc) {
|
|
/* Copy payload using RAM-based memcpy */
|
|
ram_memcpy(payload, xpkt + 3, XMODEM_PAYLOAD_SIZE);
|
|
|
|
/* Send ACK first, then write to flash.
|
|
* This allows sender to prepare next packet while we write.
|
|
* Risk: if write fails, we've already ACKed - but that's rare.
|
|
*/
|
|
uart_tx(XACK);
|
|
|
|
/* Write to flash */
|
|
ret = hal_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS + state->dst_offset,
|
|
payload, XMODEM_PAYLOAD_SIZE);
|
|
if (ret != 0) {
|
|
xmodem_cancel();
|
|
/* No printf here - we're in RAM */
|
|
break;
|
|
}
|
|
pkt_num_expected++;
|
|
state->dst_offset += XMODEM_PAYLOAD_SIZE;
|
|
|
|
/* Get expected size from header (offset 4 = image size) */
|
|
if (t_size == 0 && state->dst_offset >= 8) {
|
|
t_size = *(uint32_t*)(payload + 4) + IMAGE_HEADER_SIZE;
|
|
}
|
|
|
|
if (t_size > 0 && state->dst_offset >= t_size) {
|
|
eot_expected = 1;
|
|
}
|
|
} else {
|
|
state->pkts_crc_fail++;
|
|
uart_tx(XNAK);
|
|
}
|
|
} else {
|
|
state->pkts_num_fail++;
|
|
uart_tx(XNAK);
|
|
}
|
|
}
|
|
|
|
state->result = ret;
|
|
return ret;
|
|
}
|
|
|
|
static int cmd_update_xmodem(void)
|
|
{
|
|
xmodem_state_t state;
|
|
int ret;
|
|
uint32_t erase_addr;
|
|
int erase_ret;
|
|
|
|
printf("Erasing update partition...\r\n");
|
|
#ifdef DEBUG_FLASH
|
|
printf(" Address: 0x%08lX\r\n", (unsigned long)WOLFBOOT_PARTITION_UPDATE_ADDRESS);
|
|
printf(" Size: 0x%08lX (%lu bytes)\r\n",
|
|
(unsigned long)WOLFBOOT_PARTITION_SIZE,
|
|
(unsigned long)WOLFBOOT_PARTITION_SIZE);
|
|
#endif
|
|
|
|
hal_flash_unlock();
|
|
|
|
#ifdef DEBUG_FLASH
|
|
/* Erase sector by sector with debug output */
|
|
erase_addr = WOLFBOOT_PARTITION_UPDATE_ADDRESS;
|
|
while (erase_addr < WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE) {
|
|
printf(" Erasing sector at 0x%08lX...", (unsigned long)erase_addr);
|
|
fflush(stdout);
|
|
|
|
erase_ret = hal_flash_erase(erase_addr, WOLFBOOT_SECTOR_SIZE);
|
|
if (erase_ret != 0) {
|
|
printf(" FAILED (%d)\r\n", erase_ret);
|
|
hal_flash_lock();
|
|
return -1;
|
|
}
|
|
printf(" OK\r\n");
|
|
erase_addr += WOLFBOOT_SECTOR_SIZE;
|
|
}
|
|
#else
|
|
(void)erase_addr;
|
|
(void)erase_ret;
|
|
hal_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE);
|
|
#endif
|
|
|
|
printf("Done.\r\n");
|
|
printf("Waiting for XMODEM transfer...\r\n");
|
|
printf("(Send file now using XMODEM-CRC protocol)\r\n");
|
|
|
|
/* Flush all printf output before starting XMODEM */
|
|
fflush(stdout);
|
|
/* Wait for UART TX to complete */
|
|
while (!(LPUART1_STAT & LPUART_STAT_TC)) {}
|
|
|
|
/* Drain any pending RX data before starting XMODEM */
|
|
{
|
|
char c;
|
|
while (uart_getc(&c) > 0) {} /* Use ISR buffer, not hardware */
|
|
}
|
|
|
|
/* Small delay to ensure clean start */
|
|
delay_ms(100);
|
|
|
|
/* Block all printf output during XMODEM */
|
|
xmodem_active = 1;
|
|
|
|
/* Run the receive loop from RAM */
|
|
ret = xmodem_receive_ram(&state);
|
|
|
|
/* Re-enable printf output */
|
|
xmodem_active = 0;
|
|
|
|
hal_flash_lock();
|
|
|
|
/* Wait for sender to finish and drain any pending RX data.
|
|
* This prevents printf output from mixing with XMODEM retransmits. */
|
|
{
|
|
char c;
|
|
delay_ms(3000); /* Wait for sender to give up */
|
|
while (uart_read(&c) > 0) {} /* Drain RX buffer */
|
|
}
|
|
|
|
printf("\r\nTransfer %s\r\n", (ret == 0) ? "complete!" : "failed.");
|
|
printf("XMODEM stats: recv=%lu, crc_fail=%lu, num_fail=%lu, soh_fail=%lu, timeouts=%lu\r\n",
|
|
(unsigned long)state.pkts_received, (unsigned long)state.pkts_crc_fail,
|
|
(unsigned long)state.pkts_num_fail, (unsigned long)state.pkts_soh_fail,
|
|
(unsigned long)state.timeouts);
|
|
|
|
if (ret == 0) {
|
|
uint32_t update_ver = wolfBoot_update_firmware_version();
|
|
if (update_ver != 0) {
|
|
printf("New firmware version: %lu\r\n", (unsigned long)update_ver);
|
|
printf("Triggering update...\r\n");
|
|
wolfBoot_update_trigger();
|
|
printf("Reboot to apply update.\r\n");
|
|
} else {
|
|
printf("Warning: No valid image detected\r\n");
|
|
}
|
|
}
|
|
|
|
led_red_off();
|
|
return ret;
|
|
}
|
|
|
|
/* ============== Console Commands ============== */
|
|
|
|
static int cmd_help(const char *args);
|
|
static int cmd_info(const char *args);
|
|
static int cmd_success(const char *args);
|
|
static int cmd_reboot(const char *args);
|
|
static int cmd_update(const char *args);
|
|
static int cmd_timestamp(const char *args);
|
|
static int cmd_trigger(const char *args);
|
|
static int cmd_status(const char *args);
|
|
|
|
typedef struct {
|
|
int (*fn)(const char *args);
|
|
const char* name;
|
|
const char* help;
|
|
} console_cmd_t;
|
|
|
|
static const console_cmd_t commands[] = {
|
|
{cmd_help, "help", "Show this help message"},
|
|
{cmd_info, "info", "Display partition and key info"},
|
|
{cmd_status, "status", "Show partition versions and states"},
|
|
{cmd_success, "success", "Mark firmware as successful"},
|
|
{cmd_trigger, "trigger", "Trigger update (if update image in flash)"},
|
|
{cmd_update, "update", "Update firmware via XMODEM"},
|
|
{cmd_timestamp, "timestamp", "Show current system time"},
|
|
{cmd_reboot, "reboot", "Reboot the system"},
|
|
{NULL, NULL, NULL}
|
|
};
|
|
|
|
static int cmd_help(const char *args)
|
|
{
|
|
int i;
|
|
(void)args;
|
|
printf("\r\nAvailable commands:\r\n");
|
|
for (i = 0; commands[i].name != NULL; i++) {
|
|
printf(" %s - %s\r\n", commands[i].name, commands[i].help);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_info(const char *args)
|
|
{
|
|
(void)args;
|
|
print_partition_info();
|
|
print_keystore_info();
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_success(const char *args)
|
|
{
|
|
(void)args;
|
|
wolfBoot_success();
|
|
printf("Firmware marked as successful.\r\n");
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_timestamp(const char *args)
|
|
{
|
|
(void)args;
|
|
printf("Current systick: %lu ms\r\n", (unsigned long)jiffies);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_status(const char *args)
|
|
{
|
|
uint32_t boot_ver, update_ver;
|
|
uint8_t boot_state, update_state;
|
|
int ret;
|
|
(void)args;
|
|
|
|
boot_ver = wolfBoot_current_firmware_version();
|
|
update_ver = wolfBoot_update_firmware_version();
|
|
|
|
printf("\r\n=== Partition Status ===\r\n");
|
|
printf("Boot Partition: v%lu @ 0x%lX\r\n",
|
|
(unsigned long)boot_ver, (unsigned long)WOLFBOOT_PARTITION_BOOT_ADDRESS);
|
|
|
|
ret = wolfBoot_get_partition_state(PART_BOOT, &boot_state);
|
|
if (ret == 0) {
|
|
printf(" State: %s (0x%02X)\r\n",
|
|
(boot_state == IMG_STATE_SUCCESS) ? "SUCCESS" :
|
|
(boot_state == IMG_STATE_TESTING) ? "TESTING" :
|
|
(boot_state == IMG_STATE_UPDATING) ? "UPDATING" : "NEW",
|
|
boot_state);
|
|
} else {
|
|
printf(" State: (no trailer)\r\n");
|
|
}
|
|
|
|
printf("Update Partition: v%lu @ 0x%lX\r\n",
|
|
(unsigned long)update_ver, (unsigned long)WOLFBOOT_PARTITION_UPDATE_ADDRESS);
|
|
|
|
ret = wolfBoot_get_partition_state(PART_UPDATE, &update_state);
|
|
if (ret == 0) {
|
|
printf(" State: %s (0x%02X)\r\n",
|
|
(update_state == IMG_STATE_SUCCESS) ? "SUCCESS" :
|
|
(update_state == IMG_STATE_TESTING) ? "TESTING" :
|
|
(update_state == IMG_STATE_UPDATING) ? "UPDATING" : "NEW",
|
|
update_state);
|
|
} else {
|
|
printf(" State: (no trailer)\r\n");
|
|
}
|
|
|
|
if (update_ver > 0 && update_ver > boot_ver) {
|
|
printf("\r\nUpdate available! Use 'trigger' command to start update.\r\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_trigger(const char *args)
|
|
{
|
|
uint32_t update_ver;
|
|
(void)args;
|
|
|
|
update_ver = wolfBoot_update_firmware_version();
|
|
if (update_ver == 0) {
|
|
printf("No update image found in update partition.\r\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("Update image version: %lu\r\n", (unsigned long)update_ver);
|
|
printf("Triggering update...\r\n");
|
|
wolfBoot_update_trigger();
|
|
printf("Update triggered. Use 'reboot' to start update.\r\n");
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_reboot(const char *args)
|
|
{
|
|
(void)args;
|
|
printf("Rebooting...\r\n");
|
|
fflush(stdout);
|
|
delay_ms(100); /* Allow UART to flush */
|
|
arch_reboot();
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_update(const char *args)
|
|
{
|
|
(void)args;
|
|
return cmd_update_xmodem();
|
|
}
|
|
|
|
static int parse_command(const char* cmd)
|
|
{
|
|
int i;
|
|
for (i = 0; commands[i].name != NULL; i++) {
|
|
if (strcmp(cmd, commands[i].name) == 0) {
|
|
return commands[i].fn(NULL);
|
|
}
|
|
}
|
|
printf("Unknown command: %s\r\n", cmd);
|
|
printf("Type 'help' for available commands.\r\n");
|
|
return -1;
|
|
}
|
|
|
|
#define CMD_BUF_SIZE 64
|
|
|
|
static void console_loop(void)
|
|
{
|
|
char cmd[CMD_BUF_SIZE];
|
|
int idx;
|
|
char c;
|
|
|
|
while (1) {
|
|
printf("\r\ncmd> ");
|
|
fflush(stdout);
|
|
idx = 0;
|
|
|
|
while (idx < CMD_BUF_SIZE - 1) {
|
|
int ret = uart_getc(&c);
|
|
if (ret > 0) {
|
|
if (c == '\r' || c == '\n') {
|
|
printf("\r\n");
|
|
break;
|
|
} else if (c == 0x08 || c == 0x7F) { /* Backspace */
|
|
if (idx > 0) {
|
|
printf("\b \b");
|
|
fflush(stdout);
|
|
idx--;
|
|
}
|
|
} else if (c >= 32 && c < 127) {
|
|
printf("%c", c);
|
|
fflush(stdout);
|
|
cmd[idx++] = c;
|
|
}
|
|
}
|
|
/* No delay - tight polling loop for responsive input */
|
|
}
|
|
|
|
cmd[idx] = '\0';
|
|
if (idx > 0) {
|
|
parse_command(cmd);
|
|
}
|
|
}
|
|
}
|
|
#endif /* DEBUG_UART */
|
|
|
|
/* ============== Clock Functions ============== */
|
|
|
|
/* Ensure FIRC (48 MHz) is enabled for UART */
|
|
static void clock_ensure_firc(void)
|
|
{
|
|
/* Check if FIRC is valid */
|
|
if (!(SCG_FIRCCSR & SCG_FIRCCSR_FIRCVLD)) {
|
|
/* Enable FIRC if not already enabled */
|
|
SCG_FIRCDIV = (1UL << 8) | (1UL << 0); /* FIRCDIV1=/1, FIRCDIV2=/1 */
|
|
SCG_FIRCCFG = 0; /* Range 0: 48 MHz */
|
|
SCG_FIRCCSR = SCG_FIRCCSR_FIRCEN;
|
|
|
|
/* Wait for FIRC valid */
|
|
while (!(SCG_FIRCCSR & SCG_FIRCCSR_FIRCVLD)) {}
|
|
}
|
|
|
|
/* Ensure system is running from FIRC */
|
|
if ((SCG_CSR & SCG_CSR_SCS_MASK) != SCG_CSR_SCS_FIRC) {
|
|
SCG_RCCR = SCG_xCCR_SCS_FIRC |
|
|
(0UL << SCG_xCCR_DIVCORE_SHIFT) |
|
|
(0UL << SCG_xCCR_DIVBUS_SHIFT) |
|
|
(1UL << SCG_xCCR_DIVSLOW_SHIFT);
|
|
|
|
/* Wait for clock switch */
|
|
while ((SCG_CSR & SCG_CSR_SCS_MASK) != SCG_CSR_SCS_FIRC) {}
|
|
}
|
|
}
|
|
|
|
/* ============== Main Entry Point ============== */
|
|
|
|
void main(void)
|
|
{
|
|
uint32_t version;
|
|
|
|
/* Disable watchdog - bootloader may have enabled it */
|
|
WDOG_CNT = WDOG_CNT_UNLOCK;
|
|
while (!(WDOG_CS & WDOG_CS_ULK)) {}
|
|
WDOG_TOVAL = 0xFFFF;
|
|
WDOG_CS = WDOG_CS_UPDATE | WDOG_CS_CMD32EN | WDOG_CS_CLK_LPO; /* Disabled, but updatable */
|
|
while (!(WDOG_CS & WDOG_CS_RCS)) {}
|
|
|
|
/* Ensure FIRC clock is running at 48 MHz for UART */
|
|
clock_ensure_firc();
|
|
|
|
#ifdef DEBUG_UART
|
|
/* Reinitialize UART - bootloader may have changed settings in hal_prepare_boot */
|
|
uart_init();
|
|
/* Enable interrupt-based RX buffering for reliable XMODEM transfers */
|
|
uart_rx_irq_enable();
|
|
/* Disable stdout buffering to prevent delayed output during XMODEM */
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
#endif
|
|
|
|
/* Initialize test-app hardware */
|
|
systick_init();
|
|
led_init();
|
|
|
|
/* Enable interrupts */
|
|
__asm__ volatile ("cpsie i");
|
|
|
|
/* Get current firmware version */
|
|
version = wolfBoot_current_firmware_version();
|
|
|
|
/* Set LED based on version: Green for v1, Blue for v>1 */
|
|
led_set_version(version);
|
|
|
|
#ifdef DEBUG_UART
|
|
printf("\r\n");
|
|
printf("========================================\r\n");
|
|
printf("S32K1xx wolfBoot Test Application\r\n");
|
|
printf("Copyright 2025 wolfSSL Inc.\r\n");
|
|
printf("========================================\r\n");
|
|
printf("Firmware Version: %lu\r\n", (unsigned long)version);
|
|
|
|
/* Auto-mark success for testing if version > 1 */
|
|
if (version > 1) {
|
|
uint8_t state = 0;
|
|
wolfBoot_get_partition_state(PART_BOOT, &state);
|
|
if (state == IMG_STATE_TESTING) {
|
|
printf("Testing state detected, marking success...\r\n");
|
|
wolfBoot_success();
|
|
}
|
|
}
|
|
|
|
/* Show initial info */
|
|
print_partition_info();
|
|
|
|
printf("\r\nType 'help' for available commands.\r\n");
|
|
|
|
/* Enter interactive console */
|
|
console_loop();
|
|
#else
|
|
/* No UART - just blink LED */
|
|
while (1) {
|
|
led_toggle_version(version);
|
|
delay_ms(500);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ============== Syscalls for printf support ============== */
|
|
|
|
int _getpid(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int _kill(int pid, int sig)
|
|
{
|
|
(void)pid;
|
|
(void)sig;
|
|
return -1;
|
|
}
|
|
|
|
void _exit(int status)
|
|
{
|
|
_kill(status, -1);
|
|
while (1) {}
|
|
}
|
|
|
|
int _read(int file, char *ptr, int len)
|
|
{
|
|
(void)file;
|
|
(void)ptr;
|
|
(void)len;
|
|
return -1;
|
|
}
|
|
|
|
int _write(int file, char *ptr, int len)
|
|
{
|
|
(void)file;
|
|
#ifdef DEBUG_UART
|
|
/* Block text output during XMODEM to prevent protocol interference */
|
|
if (!xmodem_active) {
|
|
uart_write(ptr, len);
|
|
}
|
|
#else
|
|
(void)ptr;
|
|
#endif
|
|
return len;
|
|
}
|
|
|
|
int _close(int file)
|
|
{
|
|
(void)file;
|
|
return -1;
|
|
}
|
|
|
|
int _isatty(int file)
|
|
{
|
|
(void)file;
|
|
return 1;
|
|
}
|
|
|
|
int _lseek(int file, int ptr, int dir)
|
|
{
|
|
(void)file;
|
|
(void)ptr;
|
|
(void)dir;
|
|
return 0;
|
|
}
|
|
|
|
int _fstat(int file, struct stat *st)
|
|
{
|
|
(void)file;
|
|
st->st_mode = S_IFCHR;
|
|
return 0;
|
|
}
|
|
|
|
/* Back-end for malloc, used for printf */
|
|
extern unsigned int _end; /* From linker script: end of BSS */
|
|
extern unsigned int _end_stack; /* From linker script: end of RAM */
|
|
|
|
void *_sbrk(int incr)
|
|
{
|
|
static unsigned char *heap = NULL;
|
|
unsigned char *prev_heap;
|
|
|
|
if (heap == NULL) {
|
|
heap = (unsigned char *)&_end;
|
|
}
|
|
|
|
prev_heap = heap;
|
|
|
|
/* Align increment to 4 bytes */
|
|
if (((incr >> 2) << 2) != incr)
|
|
incr = ((incr >> 2) + 1) << 2;
|
|
|
|
/* Check we don't overflow into the stack */
|
|
if ((heap + incr) > (unsigned char *)&_end_stack) {
|
|
return (void *)-1;
|
|
}
|
|
|
|
heap += incr;
|
|
return prev_heap;
|
|
}
|
|
|
|
#endif /* TARGET_s32k1xx */
|