diff --git a/.gitignore b/.gitignore index 65b8a8dce7..dbff48f90d 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ tools/tpm/policy_create tools/tpm/policy_sign config/*.ld test-lib +lib-fs # Elf preprocessing tools tools/squashelf/** @@ -255,3 +256,8 @@ lib/r_tsip_rx Debug/ Release/ language.settings.xml + +# Eclipse +.cproject +.project +.settings/ diff --git a/Makefile b/Makefile index 534e7dba29..e9819fd40e 100644 --- a/Makefile +++ b/Makefile @@ -164,6 +164,10 @@ ifeq ($(TARGET),library) MAIN_TARGET:=libwolfboot.a endif +ifeq ($(TARGET),library_fs) + MAIN_TARGET:=libwolfboot.a +endif + ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif @@ -214,6 +218,10 @@ test-lib: libwolfboot.a hal/library.o @echo "\t[BIN] $@" $(Q)$(CC) $(CFLAGS) -o $@ hal/library.o libwolfboot.a +lib-fs: libwolfboot.a hal/library_fs.o + @echo "\t[BIN] $@" + $(Q)$(CC) $(CFLAGS) -o $@ hal/library_fs.o libwolfboot.a + wolfboot.efi: wolfboot.elf @echo "\t[BIN] $@" $(Q)$(OBJCOPY) -j .rodata -j .text -j .sdata -j .data \ @@ -440,6 +448,8 @@ clean: $(Q)rm -f tools/keytools/otp/otp-keystore-gen $(Q)rm -f .stack_usage $(Q)rm -f $(WH_NVM_BIN) $(WH_NVM_HEX) + $(Q)rm -f test-lib + $(Q)rm -f lib-fs $(Q)$(MAKE) -C test-app clean V=$(V) $(Q)$(MAKE) -C tools/check_config -s clean $(Q)$(MAKE) -C stage1 -s clean diff --git a/config/examples/library_fs.config b/config/examples/library_fs.config new file mode 100644 index 0000000000..0a49287858 --- /dev/null +++ b/config/examples/library_fs.config @@ -0,0 +1,29 @@ +ARCH=sim +TARGET=library_fs + +SIGN?=ED25519 +HASH?=SHA256 +IMAGE_HEADER_SIZE?=256 +DEBUG=0 +SPMATH?=0 +SPMATHALL?=0 + +# Required for library (libwolfboot.a) +NO_LOADER=1 +USE_GCC_HEADLESS=0 + +# Flash Partition Filename +WOLFBOOT_PARTITION_FILENAME=\"/dev/mtd0\" + +# Flash Sector Size +WOLFBOOT_SECTOR_SIZE=0x2000 +# Application Partition Size +WOLFBOOT_PARTITION_SIZE=0xA000 +# Location in flash for wolfBoot +WOLFBOOT_ORIGIN=0x0 +# Location in flash for boot partition +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x10000 +# Location in flash for update partition +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x8000 +# Location in flash for swap +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0xFFE00 diff --git a/config/examples/zynqmp.config b/config/examples/zynqmp.config index d04a233fc6..70dc8cb241 100644 --- a/config/examples/zynqmp.config +++ b/config/examples/zynqmp.config @@ -63,6 +63,8 @@ WOLFBOOT_SECTOR_SIZE=0x20000 # Application Partition Size WOLFBOOT_PARTITION_SIZE=0x2A00000 # Location in Flash for wolfBoot +WOLFBOOT_ORIGIN=0x0 +# Location in Flash for Primary Boot Partition WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x800000 # Load Partition to RAM Address WOLFBOOT_LOAD_ADDRESS?=0x10000000 diff --git a/docs/lib.md b/docs/lib.md index a86e9010cd..af847865b1 100644 --- a/docs/lib.md +++ b/docs/lib.md @@ -21,7 +21,7 @@ of the manifest header. On success, zero is returned. If the image does not contain a valid 'magic number' at the beginning -of the manifest, or if the size of the image is bigger than `WOLFBOOT_PARTITION_SIZE`, -1 is returned. +of the manifest, or if the size of the image is bigger than `WOLFBOOT_PARTITION_SIZE`, -1 is returned. If the `open_image_address` operation is successful, two other functions can be invoked: @@ -123,3 +123,62 @@ Firmware Valid booting 0x5609e3526590(actually exiting) ``` +## Library mode: Partition Manager CLI Example + +An example application using filesystem access is provided in `hal/library_fs.c`. + +The CLI application `lib-fs` allow querying partition states, triggering updates, and marking the boot partition as successful. + +### Building the lib-fs example + +Step 1: use the example configuration to compile wolfBoot in library mode: + +``` +cp config/examples/library_fs.config .config +``` + +Step 2: Adjust the configuration to fit your partition layout and file path. + +Step 3: Build the CLI application: + +``` +make lib-fs +``` + +This will produce the `lib-fs` executable. + +### Using the Partition Manager CLI + +The example configuration points the binary to access `/dev/mtd0` for partition data. You can simulate this file path with `modprobe mtdram total_size=16384 erase_size=128`. You may need to adjust the file permissions to allow read/write access. + +Run the application with one of the supported commands: + +``` +./lib-fs +``` + +Available commands: + +- `status` : Show state of all partitions +- `get-boot` : Get BOOT partition state +- `get-update` : Get UPDATE partition state +- `update-trigger` : Trigger an update (sets UPDATE partition to UPDATING) +- `success` : Mark BOOT partition as SUCCESS +- `help` : Show usage information + +#### Example usage + +Show all partition states: +``` +./lib-fs status +``` + +Trigger an update: +``` +./lib-fs update-trigger +``` + +Mark the boot partition as successful: +``` +./lib-fs success +``` diff --git a/hal/library.c b/hal/library.c index 82e5a8b55c..ca7f83827d 100644 --- a/hal/library.c +++ b/hal/library.c @@ -142,7 +142,8 @@ int wolfBoot_start(void) exit: if (ret < 0) { wolfBoot_printf("Failure %d: Hdr %d, Hash %d, Sig %d\n", ret, - os_image.hdr_ok, os_image.sha_ok, os_image.signature_ok); + (int)os_image.hdr_ok, (int)os_image.sha_ok, + (int)os_image.signature_ok); } return 0; diff --git a/hal/library_fs.c b/hal/library_fs.c new file mode 100644 index 0000000000..5c3ac83af5 --- /dev/null +++ b/hal/library_fs.c @@ -0,0 +1,172 @@ +/* library_fs.c + * + * Copyright (C) 2025 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 +#include +#include + +#include "image.h" +#include "printf.h" +#include "wolfboot/wolfboot.h" + +/* Helper function to convert partition ID to string */ +static const char* partition_name(uint8_t part) +{ + switch (part) { + case PART_BOOT: + return "BOOT"; + case PART_UPDATE: + return "UPDATE"; + default: + return "UNKNOWN"; + } +} + +/* Helper function to convert state value to string */ +static const char* state_name(uint8_t state) +{ + switch (state) { + case IMG_STATE_NEW: + return "NEW"; + case IMG_STATE_UPDATING: + return "UPDATING"; + case IMG_STATE_SUCCESS: + return "SUCCESS"; + default: + return "UNKNOWN"; + } +} + +/* Print partition state */ +static int cmd_get_state(uint8_t part) +{ + uint8_t state; + int ret; + + ret = wolfBoot_get_partition_state(part, &state); + if (ret != 0) { + wolfBoot_printf("Error: Failed to get state for %s partition (error: %d)\n", + partition_name(part), ret); + return -1; + } + + wolfBoot_printf("%s partition state: %s (0x%02X)\n", + partition_name(part), state_name(state), state); + return 0; +} + +/* Print all partition states */ +static int cmd_get_all_states(void) +{ + int ret = 0; + + wolfBoot_printf("=== Partition States ===\n"); + + if (cmd_get_state(PART_BOOT) != 0) + ret = -1; + + if (cmd_get_state(PART_UPDATE) != 0) + ret = -1; + + return ret; +} + +/* Trigger an update */ +static int cmd_update_trigger(void) +{ + wolfBoot_printf("Triggering update...\n"); + wolfBoot_update_trigger(); + wolfBoot_printf("Update triggered successfully. UPDATE partition set to UPDATING state.\n"); + return 0; +} + +/* Mark current boot as successful */ +static int cmd_success(void) +{ + wolfBoot_printf("Marking BOOT partition as SUCCESS...\n"); + wolfBoot_success(); + wolfBoot_printf("BOOT partition marked as SUCCESS.\n"); + return 0; +} + +/* Print usage information */ +static void print_usage(const char* prog_name) +{ + wolfBoot_printf("wolfBoot Partition Manager CLI\n"); + wolfBoot_printf("\nUsage: %s [options]\n\n", prog_name); + wolfBoot_printf("Commands:\n"); + wolfBoot_printf(" status - Show state of all partitions\n"); + wolfBoot_printf(" get-boot - Get BOOT partition state\n"); + wolfBoot_printf(" get-update - Get UPDATE partition state\n"); + wolfBoot_printf(" update-trigger - Trigger an update (sets UPDATE partition to UPDATING)\n"); + wolfBoot_printf(" success - Mark BOOT partition as SUCCESS\n"); + wolfBoot_printf(" help - Show this help message\n"); + wolfBoot_printf("\nPartitions:\n"); + wolfBoot_printf(" BOOT - Currently running firmware partition\n"); + wolfBoot_printf(" UPDATE - Staging partition for new firmware\n"); + wolfBoot_printf("\nExamples:\n"); + wolfBoot_printf(" %s status - Display all partition states\n", prog_name); + wolfBoot_printf(" %s update-trigger - Stage an update for next boot\n", prog_name); + wolfBoot_printf(" %s success - Confirm current firmware is working\n", prog_name); + wolfBoot_printf("\n"); +} + +int main(int argc, const char* argv[]) +{ + int ret = 0; + + /* Check for argument count */ + if (argc != 2) { + print_usage(argv[0]); + return 1; + } + + const char* command = argv[1]; + + /* Process commands */ + if (strcmp(command, "status") == 0) { + ret = cmd_get_all_states(); + } + else if (strcmp(command, "get-boot") == 0) { + ret = cmd_get_state(PART_BOOT); + } + else if (strcmp(command, "get-update") == 0) { + ret = cmd_get_state(PART_UPDATE); + } + else if (strcmp(command, "update-trigger") == 0) { + ret = cmd_update_trigger(); + } + else if (strcmp(command, "success") == 0) { + ret = cmd_success(); + } + else if (strcmp(command, "help") == 0 || strcmp(command, "--help") == 0 || + strcmp(command, "-h") == 0) { + print_usage(argv[0]); + ret = 0; + } + else { + wolfBoot_printf("Error: Unknown command '%s'\n\n", command); + print_usage(argv[0]); + ret = 1; + } + + return ret; +} diff --git a/include/target.h.in b/include/target.h.in index 7fce6ff97d..7d5f8c3af3 100644 --- a/include/target.h.in +++ b/include/target.h.in @@ -36,7 +36,7 @@ #ifdef WOLFBOOT_FIXED_PARTITIONS -#ifdef ARCH_SIM +#if defined(ARCH_SIM) && !defined(WOLFBOOT_PARTITION_FILENAME) #include /* use runtime ram base for simulator */ extern uint8_t *sim_ram_base; diff --git a/include/user_settings.h b/include/user_settings.h index 379488ee78..0c6d56ab1d 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -472,7 +472,9 @@ extern int tolower(int c); #define NO_HC128 #define NO_DES3 #define NO_WRITEV +#ifndef WOLFBOOT_PARTITION_FILENAME #define NO_FILESYSTEM +#endif #define NO_MAIN_DRIVER #define NO_OLD_RNGNAME #define NO_WOLFSSL_DIR diff --git a/options.mk b/options.mk index 7efb49d100..0fbf53a0ae 100644 --- a/options.mk +++ b/options.mk @@ -1035,3 +1035,7 @@ endif ifeq ($(USE_UART3),1) CFLAGS += -DUSE_UART3=1 endif + +ifneq ($(WOLFBOOT_PARTITION_FILENAME),) + CFLAGS += -DWOLFBOOT_PARTITION_FILENAME=$(WOLFBOOT_PARTITION_FILENAME) +endif diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 6c08380daf..abd2333455 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -408,7 +408,7 @@ static void RAMFUNCTION set_partition_magic(uint8_t part) return; } -#else +#elif !defined(WOLFBOOT_PARTITION_FILENAME) /** * @brief Get the trailer at a specific address @@ -548,8 +548,114 @@ static void RAMFUNCTION set_partition_magic(uint8_t part) #endif /* !MOCK_PARTITION_TRAILER */ +#ifdef WOLFBOOT_PARTITION_FILENAME -#ifdef WOLFBOOT_FIXED_PARTITIONS +#ifndef WOLFBOOT_FIXED_PARTITIONS +#error Using WOLFBOOT_PARTITION_FILENAME requires WOLFBOOT_FIXED_PARTITIONS +#endif + +static void set_partition_state_fs(uint8_t st, uint8_t part) +{ + const char* fileName = WOLFBOOT_PARTITION_FILENAME; + XFILE partFile = XBADFILE; + union { + uint32_t num; + byte buf[sizeof(uint32_t)]; + } magic; + long offset; + + if (part == PART_BOOT) + offset = PART_BOOT_ENDFLAGS; + else if (part == PART_UPDATE) + offset = PART_UPDATE_ENDFLAGS; + else + return; + + partFile = XFOPEN(fileName, "r+b"); + if (partFile == XBADFILE) + goto cleanup; + + if (XFSEEK(partFile, offset - sizeof(uint32_t), XSEEK_SET) != 0) + goto cleanup; + + if (XFREAD(magic.buf, 1, sizeof(magic.buf), partFile) != sizeof(magic.buf)) + goto cleanup; + + if (magic.num != WOLFBOOT_MAGIC_TRAIL) { + magic.num = WOLFBOOT_MAGIC_TRAIL; + if (XFSEEK(partFile, offset - sizeof(uint32_t), XSEEK_SET) != 0) + goto cleanup; + if (XFWRITE(magic.buf, 1, sizeof(magic.buf), partFile) != + sizeof(magic.buf)) + goto cleanup; + } + + if (XFSEEK(partFile, offset - sizeof(uint32_t) - 1, XSEEK_SET) != 0) + goto cleanup; + + if (XFWRITE(&st, 1, sizeof(st), partFile) != sizeof(st)) + goto cleanup; + +cleanup: + if (partFile != XBADFILE) + XFCLOSE(partFile); +} + +void RAMFUNCTION wolfBoot_update_trigger(void) +{ + set_partition_state_fs(IMG_STATE_UPDATING, PART_UPDATE); +} + +void RAMFUNCTION wolfBoot_success(void) +{ + set_partition_state_fs(IMG_STATE_SUCCESS, PART_BOOT); +} + +int RAMFUNCTION wolfBoot_get_partition_state(uint8_t part, uint8_t *st) +{ + const char* fileName = WOLFBOOT_PARTITION_FILENAME; + XFILE partFile = XBADFILE; + union { + uint32_t num; + byte buf[sizeof(uint32_t)]; + } magic; + long offset; + int ret = -1; + + if (part == PART_BOOT) + offset = PART_BOOT_ENDFLAGS; + else if (part == PART_UPDATE) + offset = PART_UPDATE_ENDFLAGS; + else + return -1; + + partFile = XFOPEN(fileName, "rb"); + if (partFile == XBADFILE) + goto cleanup; + + if (XFSEEK(partFile, offset - sizeof(uint32_t), XSEEK_SET) != 0) + goto cleanup; + + if (XFREAD(magic.buf, 1, sizeof(magic.buf), partFile) != sizeof(magic.buf)) + goto cleanup; + + if (magic.num != WOLFBOOT_MAGIC_TRAIL) + goto cleanup; + + if (XFSEEK(partFile, offset - sizeof(uint32_t) - 1, XSEEK_SET) != 0) + goto cleanup; + + if (XFREAD(st, 1, sizeof(*st), partFile) != sizeof(*st)) + goto cleanup; + + ret = 0; +cleanup: + if (partFile != XBADFILE) + XFCLOSE(partFile); + return ret; +} + +#elif defined(WOLFBOOT_FIXED_PARTITIONS) /* WOLFBOOT_PARTITION_FILENAME */ /** * @brief Get the magic trailer of a partition. * @@ -1248,7 +1354,7 @@ uint16_t wolfBoot_get_image_type(uint8_t part) } #endif /* WOLFBOOT_FIXED_PARTITIONS */ -#if defined(WOLFBOOT_DUALBOOT) +#if defined(WOLFBOOT_DUALBOOT) && !defined(WOLFBOOT_PARTITION_FILENAME) #if defined(WOLFBOOT_FIXED_PARTITIONS) /** diff --git a/tools/scripts/boot_status.py b/tools/scripts/boot_status.py new file mode 100755 index 0000000000..e72bd29c22 --- /dev/null +++ b/tools/scripts/boot_status.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +import argparse +import sys + +# Declare config_vars as a global variable +config_vars: dict[str, int] = {} + +def set_status(status_file: str, partition: str, value: str) -> None: + with open(status_file, "r+b") as f: + if partition == "BOOT": + addr = config_vars["WOLFBOOT_PARTITION_BOOT_ADDRESS"] + elif partition == "UPDATE": + addr = config_vars["WOLFBOOT_PARTITION_UPDATE_ADDRESS"] + else: + print(f"Error: Invalid partition: {partition}") + sys.exit(1) + f.seek(addr + config_vars["WOLFBOOT_PARTITION_SIZE"] - 4) + magic: bytes = f.read(4) + if magic != b"BOOT": + f.seek(addr + config_vars["WOLFBOOT_PARTITION_SIZE"] - 4) + f.write(b"BOOT") + if value == "NEW": + status_byte = b"\xFF" + elif value == "UPDATING": + status_byte = b"\x70" + elif value == "SUCCESS": + status_byte = b"\x00" + else: + print(f"Error: Invalid value: {value}") + sys.exit(1) + # Write status byte at correct address + f.seek(addr + config_vars["WOLFBOOT_PARTITION_SIZE"] - 5) + f.write(status_byte) + +def get_status(status_file: str, partition: str) -> None: + with open(status_file, "rb") as f: + if partition == "BOOT": + addr = config_vars["WOLFBOOT_PARTITION_BOOT_ADDRESS"] + elif partition == "UPDATE": + addr = config_vars["WOLFBOOT_PARTITION_UPDATE_ADDRESS"] + else: + print(f"Error: Invalid partition: {partition}") + sys.exit(1) + f.seek(addr + config_vars["WOLFBOOT_PARTITION_SIZE"] - 4) + magic: bytes = f.read(4) + if magic != b"BOOT": + print(f"Error: Missing magic at expected address {hex(addr)}") + sys.exit(1) + f.seek(addr + config_vars["WOLFBOOT_PARTITION_SIZE"] - 5) + status_byte: bytes = f.read(1) + if status_byte == b"\xFF": + print("NEW") + elif status_byte == b"\x70": + print("UPDATING") + elif status_byte == b"\x00": + print("SUCCESS") + else: + print("INVALID") + +def read_config(config_path: str) -> dict[str, str]: + """ + Reads a config file and returns a dictionary of variables. + Supports lines of the form KEY=VALUE, KEY:=VALUE, KEY::=VALUE, KEY:::=VALUE, and KEY?=VALUE. + Ignores comments and blank lines. + """ + config: dict[str, str] = {} + assignment_ops = [":::= ", "::=", ":=", "="] + + with open(config_path, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + + for op in assignment_ops: + if op in line: + parts = line.split(op, 1) + if len(parts) == 2: + key = parts[0].rstrip("?").strip() + value = parts[1].strip() + config[key] = value + break # Stop after first matching operator + return config + +def main() -> None: + parser = argparse.ArgumentParser(description="Manage boot status") + parser.add_argument( + "file", + type=str, + help="Path to the boot status file" + ) + parser.add_argument( + "config", + type=str, + help="Path to the .config file" + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + partitions = ["BOOT", "UPDATE"] + states = ["SUCCESS", "UPDATING", "NEW"] + + set_parser = subparsers.add_parser("set") + set_parser.add_argument("slot", choices=partitions, type=str) + set_parser.add_argument("value", choices=states, type=str) + + get_parser = subparsers.add_parser("get") + get_parser.add_argument("slot", choices=partitions, type=str) + + args = parser.parse_args() + + read_vars = read_config(args.config) + + # Check required config variables using a for loop + required_vars = [ + "WOLFBOOT_PARTITION_SIZE", + "WOLFBOOT_PARTITION_BOOT_ADDRESS", + "WOLFBOOT_PARTITION_UPDATE_ADDRESS", + ] + for var in required_vars: + if var not in read_vars: + print(f"Error: Missing required config variable: {var}") + sys.exit(1) + try: + config_vars[var] = int(read_vars[var], 16) + except ValueError: + print(f"Error: Config variable {var} value '{read_vars[var]}' is not a valid hex number") + sys.exit(1) + + command: str = str(args.command) + if command == "set": + set_status(str(args.file), str(args.slot), str(args.value)) + elif command == "get": + get_status(str(args.file), str(args.slot)) + else: + parser.print_help() + +if __name__ == "__main__": + main()