Skip to content

Commit bfe935e

Browse files
committed
feat: add MCUboot image auto-detection to status command
Signed-off-by: Vincent Jardin <vjardin@free.fr>
1 parent 0bedd28 commit bfe935e

File tree

2 files changed

+290
-2
lines changed

2 files changed

+290
-2
lines changed

HARDWARE.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,16 @@ $ radfu status
151151
║ ║
152152
║ ┌──────────────────────────────────────────────────────────────────────┐ ║
153153
║ │ CODE FLASH 512 KB 0x00000000 - 0x0007FFFF │ ║
154-
║ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0% used │ ║
154+
║ │ █████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 23% used │ ║
155+
║ │ │ ║
156+
║ │ MCUboot Partitions: │ ║
157+
║ │ 0x00000000 64 KB bootloader (40 KB used, 63%) │ ║
158+
║ │ 0x00010000 80 KB v0.1.0 [Bank0] (39 KB used, 48%) │ ║
159+
║ │ 0x00024000 320 KB v1.0.0 [Bank1] (40 KB used, 12%) │ ║
160+
║ │ 0x00074000 48 KB storage (0 bytes used, 0%) │ ║
155161
║ ├──────────────────────────────────────────────────────────────────────┤ ║
156162
║ │ DATA FLASH 8 KB 0x08000000 - 0x08001FFF │ ║
157-
║ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0% used │ ║
163+
║ │ (usage unknown - erased data reads undefined per HW spec) │ ║
158164
║ ├──────────────────────────────────────────────────────────────────────┤ ║
159165
║ │ CONFIG AREA 512 B 0x0100A100 - 0x0100A2FF │ ║
160166
║ └──────────────────────────────────────────────────────────────────────┘ ║

src/radfu.c

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2745,6 +2745,169 @@ status_scan_flash_usage(ra_device_t *dev, uint32_t sad, uint32_t ead, uint32_t r
27452745
return used;
27462746
}
27472747

2748+
/*
2749+
* MCUboot image header structure
2750+
* See: https://docs.mcuboot.com/design.html#image-format
2751+
*/
2752+
#define MCUBOOT_IMAGE_MAGIC 0x96f3b83d
2753+
#define MCUBOOT_HEADER_SIZE 32
2754+
2755+
typedef struct {
2756+
uint32_t ih_magic;
2757+
uint32_t ih_load_addr;
2758+
uint16_t ih_hdr_size;
2759+
uint16_t ih_protect_tlv_size;
2760+
uint32_t ih_img_size;
2761+
uint32_t ih_flags;
2762+
uint8_t iv_major;
2763+
uint8_t iv_minor;
2764+
uint16_t iv_revision;
2765+
uint32_t iv_build_num;
2766+
} mcuboot_image_header_t;
2767+
2768+
/* Detected MCUboot partition info */
2769+
typedef struct {
2770+
uint32_t start;
2771+
uint32_t end;
2772+
bool has_image;
2773+
uint8_t ver_major;
2774+
uint8_t ver_minor;
2775+
uint16_t ver_rev;
2776+
uint32_t ver_build;
2777+
uint32_t img_size;
2778+
} mcuboot_partition_t;
2779+
2780+
#define MAX_MCUBOOT_PARTITIONS 8
2781+
2782+
/*
2783+
* Read a small chunk of flash (for header detection)
2784+
* Returns 0 on success, -1 on error
2785+
*/
2786+
static int
2787+
status_read_flash_chunk(ra_device_t *dev, uint32_t addr, uint8_t *buf, size_t len) {
2788+
uint8_t pkt[MAX_PKT_LEN];
2789+
uint8_t resp[CHUNK_SIZE + 6];
2790+
uint8_t data[8];
2791+
ssize_t pkt_len, n;
2792+
2793+
if (len > CHUNK_SIZE)
2794+
len = CHUNK_SIZE;
2795+
2796+
uint32_to_be(addr, &data[0]);
2797+
uint32_to_be(addr + len - 1, &data[4]);
2798+
2799+
pkt_len = ra_pack_pkt(pkt, sizeof(pkt), REA_CMD, data, 8, false);
2800+
if (pkt_len < 0)
2801+
return -1;
2802+
2803+
if (ra_send(dev, pkt, pkt_len) < 0)
2804+
return -1;
2805+
2806+
n = ra_recv(dev, resp, len + 6, 2000);
2807+
if (n < 7)
2808+
return -1;
2809+
2810+
size_t chunk_len;
2811+
uint8_t cmd;
2812+
if (ra_unpack_pkt(resp, n, buf, &chunk_len, &cmd) < 0)
2813+
return -1;
2814+
if (cmd & STATUS_ERR)
2815+
return -1;
2816+
2817+
return 0;
2818+
}
2819+
2820+
/*
2821+
* Check if address contains MCUboot image header
2822+
* Returns 1 if valid MCUboot header found, 0 otherwise
2823+
*/
2824+
static int
2825+
status_check_mcuboot_header(ra_device_t *dev, uint32_t addr, mcuboot_partition_t *part) {
2826+
uint8_t buf[MCUBOOT_HEADER_SIZE];
2827+
2828+
if (status_read_flash_chunk(dev, addr, buf, MCUBOOT_HEADER_SIZE) < 0)
2829+
return 0;
2830+
2831+
/* MCUboot uses little-endian format */
2832+
uint32_t magic = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
2833+
2834+
if (magic != MCUBOOT_IMAGE_MAGIC)
2835+
return 0;
2836+
2837+
/* Parse header fields (little-endian) */
2838+
part->start = addr;
2839+
part->has_image = true;
2840+
part->img_size = buf[12] | (buf[13] << 8) | (buf[14] << 16) | (buf[15] << 24);
2841+
part->ver_major = buf[20];
2842+
part->ver_minor = buf[21];
2843+
part->ver_rev = buf[22] | (buf[23] << 8);
2844+
part->ver_build = buf[24] | (buf[25] << 8) | (buf[26] << 16) | (buf[27] << 24);
2845+
2846+
/* Calculate end address: header + protected TLV + image + TLV area */
2847+
uint16_t hdr_size = buf[8] | (buf[9] << 8);
2848+
part->end = addr + hdr_size + part->img_size;
2849+
2850+
return 1;
2851+
}
2852+
2853+
/*
2854+
* Scan flash region to find last non-0xFF byte (actual used size)
2855+
* Returns the used size in bytes, or 0 if region is empty
2856+
*/
2857+
static uint32_t
2858+
status_scan_region_usage(ra_device_t *dev, uint32_t start, uint32_t end) {
2859+
uint8_t buf[CHUNK_SIZE];
2860+
uint32_t last_used = 0;
2861+
uint32_t size = end - start + 1;
2862+
uint32_t nr_chunks = (size + CHUNK_SIZE - 1) / CHUNK_SIZE;
2863+
2864+
for (uint32_t i = 0; i < nr_chunks; i++) {
2865+
uint32_t chunk_start = start + i * CHUNK_SIZE;
2866+
uint32_t remaining = end - chunk_start + 1;
2867+
uint32_t chunk_size = (remaining > CHUNK_SIZE) ? CHUNK_SIZE : remaining;
2868+
2869+
if (status_read_flash_chunk(dev, chunk_start, buf, chunk_size) < 0)
2870+
continue;
2871+
2872+
/* Find last non-0xFF byte in this chunk */
2873+
for (uint32_t j = chunk_size; j > 0; j--) {
2874+
if (buf[j - 1] != 0xFF) {
2875+
uint32_t offset = chunk_start - start + j;
2876+
if (offset > last_used)
2877+
last_used = offset;
2878+
break;
2879+
}
2880+
}
2881+
}
2882+
2883+
return last_used;
2884+
}
2885+
2886+
/*
2887+
* Scan code flash for MCUboot images
2888+
* Scans at 4KB boundaries (common minimum slot alignment)
2889+
* Returns number of images found
2890+
*/
2891+
static int
2892+
status_scan_mcuboot_images(
2893+
ra_device_t *dev, uint32_t sad, uint32_t ead, mcuboot_partition_t *parts, int max_parts) {
2894+
int count = 0;
2895+
uint32_t scan_step = 4096; /* 4KB alignment */
2896+
2897+
fprintf(stderr, "Scanning for MCUboot images...\n");
2898+
2899+
for (uint32_t addr = sad; addr < ead && count < max_parts; addr += scan_step) {
2900+
if (status_check_mcuboot_header(dev, addr, &parts[count])) {
2901+
count++;
2902+
/* Skip past this image to avoid finding overlapping headers */
2903+
if (parts[count - 1].end > addr)
2904+
addr = ((parts[count - 1].end + scan_step - 1) / scan_step) * scan_step - scan_step;
2905+
}
2906+
}
2907+
2908+
return count;
2909+
}
2910+
27482911
/*
27492912
* Get CPU core name from product series
27502913
*/
@@ -3216,6 +3379,34 @@ ra_status(ra_device_t *dev) {
32163379
* reads undefined values (not 0xFF) per Renesas RA hardware spec.
32173380
* The bootloader protocol does not expose a reliable blank-check method. */
32183381

3382+
/* Scan for MCUboot images in code flash */
3383+
mcuboot_partition_t mcuboot_parts[MAX_MCUBOOT_PARTITIONS];
3384+
int mcuboot_count = 0;
3385+
uint32_t bl_used = 0, storage_used = 0;
3386+
uint32_t storage_start = 0, storage_size = 0;
3387+
3388+
if (code_size > 0)
3389+
mcuboot_count =
3390+
status_scan_mcuboot_images(dev, code_sad, code_ead, mcuboot_parts, MAX_MCUBOOT_PARTITIONS);
3391+
3392+
/* Scan bootloader and storage regions if MCUboot images found */
3393+
if (mcuboot_count > 0) {
3394+
/* Scan bootloader region */
3395+
if (mcuboot_parts[0].start > code_sad) {
3396+
fprintf(stderr, "Scanning bootloader region...\n");
3397+
bl_used = status_scan_region_usage(dev, code_sad, mcuboot_parts[0].start - 1);
3398+
}
3399+
3400+
/* Scan storage region (last 48KB) */
3401+
uint32_t storage_reserve = 48 * 1024;
3402+
storage_start = code_ead - storage_reserve + 1;
3403+
if (storage_start > mcuboot_parts[mcuboot_count - 1].start) {
3404+
storage_size = code_ead - storage_start + 1;
3405+
fprintf(stderr, "Scanning storage region...\n");
3406+
storage_used = status_scan_region_usage(dev, storage_start, code_ead);
3407+
}
3408+
}
3409+
32193410
/* Print status display */
32203411
printf("\n");
32213412
status_print_hline(BOX_TL, BOX_H, BOX_TR, STATUS_WIDTH);
@@ -3327,6 +3518,97 @@ ra_status(ra_device_t *dev) {
33273518
status_format_inner(line, sizeof(line), content, INNER_WIDTH);
33283519
status_print_line(line, STATUS_WIDTH);
33293520

3521+
/* MCUboot partitions display */
3522+
if (mcuboot_count > 0) {
3523+
status_format_inner(line, sizeof(line), "", INNER_WIDTH);
3524+
status_print_line(line, STATUS_WIDTH);
3525+
status_format_inner(line, sizeof(line), " MCUboot Partitions:", INNER_WIDTH);
3526+
status_print_line(line, STATUS_WIDTH);
3527+
3528+
/* Display bootloader region (before first MCUboot image) */
3529+
if (mcuboot_parts[0].start > code_sad) {
3530+
uint32_t bl_region_size = mcuboot_parts[0].start - code_sad;
3531+
int bl_pct = (bl_region_size > 0) ? (int)((uint64_t)bl_used * 100 / bl_region_size) : 0;
3532+
char used_str[16];
3533+
format_size(bl_region_size, size_str, sizeof(size_str));
3534+
format_size(bl_used, used_str, sizeof(used_str));
3535+
snprintf(content,
3536+
sizeof(content),
3537+
" 0x%08X %6s bootloader (%s used, %d%%)",
3538+
code_sad,
3539+
size_str,
3540+
used_str,
3541+
bl_pct);
3542+
status_format_inner(line, sizeof(line), content, INNER_WIDTH);
3543+
status_print_line(line, STATUS_WIDTH);
3544+
}
3545+
3546+
/* Display detected MCUboot images */
3547+
for (int i = 0; i < mcuboot_count; i++) {
3548+
/* Determine slot boundaries based on next image or flash layout */
3549+
uint32_t slot_start = mcuboot_parts[i].start;
3550+
uint32_t slot_end;
3551+
if (i + 1 < mcuboot_count) {
3552+
slot_end = mcuboot_parts[i + 1].start - 1;
3553+
} else {
3554+
/* Last image: estimate slot end based on typical storage reservation
3555+
* Common layouts reserve 48-64KB at end for storage/settings */
3556+
uint32_t storage_reserve = 48 * 1024;
3557+
if (code_ead > storage_reserve + mcuboot_parts[i].start)
3558+
slot_end = code_ead - storage_reserve;
3559+
else
3560+
slot_end = code_ead;
3561+
}
3562+
3563+
uint32_t slot_size = slot_end - slot_start + 1;
3564+
uint32_t img_used = mcuboot_parts[i].img_size;
3565+
int slot_pct = (slot_size > 0) ? (int)((uint64_t)img_used * 100 / slot_size) : 0;
3566+
3567+
/* Determine slot label based on position */
3568+
const char *slot_label;
3569+
if (i == 0 && mcuboot_count >= 2)
3570+
slot_label = "Bank0";
3571+
else if (i == 1)
3572+
slot_label = "Bank1";
3573+
else
3574+
slot_label = "Image";
3575+
3576+
char used_str[16];
3577+
format_size(slot_size, size_str, sizeof(size_str));
3578+
format_size(img_used, used_str, sizeof(used_str));
3579+
snprintf(content,
3580+
sizeof(content),
3581+
" 0x%08X %6s v%u.%u.%u [%s] (%s used, %d%%)",
3582+
mcuboot_parts[i].start,
3583+
size_str,
3584+
mcuboot_parts[i].ver_major,
3585+
mcuboot_parts[i].ver_minor,
3586+
mcuboot_parts[i].ver_rev,
3587+
slot_label,
3588+
used_str,
3589+
slot_pct);
3590+
status_format_inner(line, sizeof(line), content, INNER_WIDTH);
3591+
status_print_line(line, STATUS_WIDTH);
3592+
}
3593+
3594+
/* Display storage region at end of flash */
3595+
if (storage_size > 0) {
3596+
int rem_pct = (storage_size > 0) ? (int)((uint64_t)storage_used * 100 / storage_size) : 0;
3597+
char used_str[16];
3598+
format_size(storage_size, size_str, sizeof(size_str));
3599+
format_size(storage_used, used_str, sizeof(used_str));
3600+
snprintf(content,
3601+
sizeof(content),
3602+
" 0x%08X %6s storage (%s used, %d%%)",
3603+
storage_start,
3604+
size_str,
3605+
used_str,
3606+
rem_pct);
3607+
status_format_inner(line, sizeof(line), content, INNER_WIDTH);
3608+
status_print_line(line, STATUS_WIDTH);
3609+
}
3610+
}
3611+
33303612
/* Data Flash */
33313613
if (data_size > 0) {
33323614
/* Separator */

0 commit comments

Comments
 (0)