diff --git a/src/BUILD.bazel b/src/BUILD.bazel index 58d564ba2..a397b486507 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -19,6 +19,7 @@ alias( "//src/rp2_common/boot_bootrom_headers:__pkg__", "//src/rp2_common/hardware_boot_lock:__pkg__", "//src/rp2_common/pico_flash:__pkg__", + "//src/rp2_common/hardware_rcp:__pkg__", ], ) diff --git a/src/rp2_common/hardware_rcp/BUILD.bazel b/src/rp2_common/hardware_rcp/BUILD.bazel index 750ae819e..6f8be1be3 100644 --- a/src/rp2_common/hardware_rcp/BUILD.bazel +++ b/src/rp2_common/hardware_rcp/BUILD.bazel @@ -2,6 +2,19 @@ load("//bazel:defs.bzl", "compatible_with_rp2") package(default_visibility = ["//visibility:public"]) +# Picotool needs this (transitively through +# //src/rp2_common/pico_bootrom:pico_bootrom_headers), so we can't strictly +# constrain compatibility. +cc_library( + name = "hardware_rcp_headers", + hdrs = ["include/hardware/rcp.h"], + includes = ["include"], + visibility = ["//src/rp2_common/pico_bootrom:__pkg__"], + deps = [ + "//src:pico_platform_internal", + ], +) + cc_library( name = "hardware_rcp", hdrs = ["include/hardware/rcp.h"], diff --git a/src/rp2_common/hardware_rcp/include/hardware/rcp.h b/src/rp2_common/hardware_rcp/include/hardware/rcp.h index b75f182d9..64c88c5a1 100644 --- a/src/rp2_common/hardware_rcp/include/hardware/rcp.h +++ b/src/rp2_common/hardware_rcp/include/hardware/rcp.h @@ -15,13 +15,15 @@ */ // ---------------------------------------------------------------------------- -// RCP instructions (this header is Arm-only) -#if defined(PICO_RP2350) && !defined(__riscv) +// RCP masks +#if !PICO_RP2040 #define RCP_MASK_TRUE _u(0xa500a500) #define RCP_MASK_FALSE _u(0x00c300c3) #define RCP_MASK_INTXOR _u(0x96009600) +// RCP instructions (these instructions are Arm-only) +#if HAS_REDUNDANCY_COPROCESSOR // ---------------------------------------------------------------------------- // Macros and inline functions for use in C files #ifndef __ASSEMBLER__ @@ -994,7 +996,8 @@ rcp_switch_u8_to_ch_cl rcp_canary_check_nodelay_impl \tag, \x cdp p7, #0, c0, c0, c0, #1 .endm -#endif // !__riscv +#endif // HAS_REDUNDANCY_COPROCESSOR +#endif // !PICO_RP2040 #endif // __ASSEMBLER__ // ---------------------------------------------------------------------------- diff --git a/src/rp2_common/pico_bootrom/BUILD.bazel b/src/rp2_common/pico_bootrom/BUILD.bazel index 53a24b105..1463216d3 100644 --- a/src/rp2_common/pico_bootrom/BUILD.bazel +++ b/src/rp2_common/pico_bootrom/BUILD.bazel @@ -18,6 +18,7 @@ cc_library( "//src/rp2_common/boot_bootrom_headers", "//src/rp2_common/hardware_boot_lock:hardware_boot_lock_headers", "//src/rp2_common/pico_flash:pico_flash_headers", + "//src/rp2_common/hardware_rcp:hardware_rcp_headers", ] + select({ "//bazel/constraint:host": ["//src/host/hardware_sync"], "//conditions:default": ["//src/rp2_common/hardware_sync"], diff --git a/src/rp2_common/pico_bootrom/bootrom.c b/src/rp2_common/pico_bootrom/bootrom.c index 1120f006a..cedc27fc8 100644 --- a/src/rp2_common/pico_bootrom/bootrom.c +++ b/src/rp2_common/pico_bootrom/bootrom.c @@ -7,6 +7,9 @@ #include "pico/bootrom.h" #include "boot/picoboot.h" #include "boot/picobin.h" +#if !PICO_RP2040 +#include "hardware/rcp.h" +#endif /// \tag::table_lookup[] @@ -108,4 +111,89 @@ int rom_add_flash_runtime_partition(uint32_t start_offset, uint32_t size, uint32 } return PICO_ERROR_INSUFFICIENT_RESOURCES; } + +int rom_pick_ab_update_partition(uint32_t *workarea_base, uint32_t workarea_size, uint partition_a_num) { +#if PICO_RP2350 + // Generated from adding the following code into the bootrom + // scan_workarea_t* scan_workarea = (scan_workarea_t*)workarea; + // printf("VERSION_DOWNGRADE_ERASE_ADDR %08x\n", &(always->zero_init.version_downgrade_erase_flash_addr)); + // printf("TBYB_FLAG_ADDR %08x\n", &(always->zero_init.tbyb_flag_flash_addr)); + // printf("IMAGE_DEF_VERIFIED %08x\n", (uint32_t)&(scan_workarea->parsed_block_loops[0].image_def.core.verified) - (uint32_t)scan_workarea); + // printf("IMAGE_DEF_TBYB_FLAGGED %08x\n", (uint32_t)&(scan_workarea->parsed_block_loops[0].image_def.core.tbyb_flagged) - (uint32_t)scan_workarea); + // printf("IMAGE_DEF_BASE %08x\n", (uint32_t)&(scan_workarea->parsed_block_loops[0].image_def.core.enclosing_window.base) - (uint32_t)scan_workarea); + // printf("IMAGE_DEF_REL_BLOCK_OFFSET %08x\n", (uint32_t)&(scan_workarea->parsed_block_loops[0].image_def.core.window_rel_block_offset) - (uint32_t)scan_workarea); + #define VERSION_DOWNGRADE_ERASE_ADDR *(uint32_t*)0x400e0338 + #define TBYB_FLAG_ADDR *(uint32_t*)0x400e0348 + #define IMAGE_DEF_VERIFIED(scan_workarea) *(uint32_t*)(0x64 + (uint32_t)scan_workarea) + #define IMAGE_DEF_TBYB_FLAGGED(scan_workarea) *(bool*)(0x4c + (uint32_t)scan_workarea) + #define IMAGE_DEF_BASE(scan_workarea) *(uint32_t*)(0x54 + (uint32_t)scan_workarea) + #define IMAGE_DEF_REL_BLOCK_OFFSET(scan_workarea) *(uint32_t*)(0x5c + (uint32_t)scan_workarea) +#else + // Prevent linting errors + #define VERSION_DOWNGRADE_ERASE_ADDR *(uint32_t*)NULL + #define TBYB_FLAG_ADDR *(uint32_t*)NULL + #define IMAGE_DEF_VERIFIED(scan_workarea) *(uint32_t*)(NULL + (uint32_t)scan_workarea) + #define IMAGE_DEF_TBYB_FLAGGED(scan_workarea) *(bool*)(NULL + (uint32_t)scan_workarea) + #define IMAGE_DEF_BASE(scan_workarea) *(uint32_t*)(NULL + (uint32_t)scan_workarea) + #define IMAGE_DEF_REL_BLOCK_OFFSET(scan_workarea) *(uint32_t*)(NULL + (uint32_t)scan_workarea) + + panic_unsupported(); +#endif + + uint32_t flash_update_base = 0; + bool tbyb_boot = false; + uint32_t saved_erase_addr = 0; + if (rom_get_last_boot_type() == BOOT_TYPE_FLASH_UPDATE) { + // For a flash update boot, get the flash update base + boot_info_t boot_info = {}; + int ret = rom_get_boot_info(&boot_info); + if (ret) { + flash_update_base = boot_info.reboot_params[0]; + if (boot_info.tbyb_and_update_info & BOOT_TBYB_AND_UPDATE_FLAG_BUY_PENDING) { + // A buy is pending, so the main software has not been bought + tbyb_boot = true; + // Save the erase address, as this will be overwritten by rom_pick_ab_partition + saved_erase_addr = VERSION_DOWNGRADE_ERASE_ADDR; + } + } + } + + int rc = rom_pick_ab_partition((uint8_t*)workarea_base, workarea_size, partition_a_num, flash_update_base); + + if (IMAGE_DEF_VERIFIED(workarea_base) != RCP_MASK_TRUE) { + // Chosen partition failed verification + return BOOTROM_ERROR_NOT_FOUND; + } + + if (IMAGE_DEF_TBYB_FLAGGED(workarea_base)) { + // The chosen partition is TBYB + if (tbyb_boot) { + // The boot partition is also TBYB - cannot update both, so prioritise boot partition + // Restore the erase address saved earlier + VERSION_DOWNGRADE_ERASE_ADDR = saved_erase_addr; + return BOOTROM_ERROR_NOT_PERMITTED; + } else { + // Update the tbyb flash address, so that explicit_buy will clear the flag for the chosen partition + TBYB_FLAG_ADDR = + IMAGE_DEF_BASE(workarea_base) + + IMAGE_DEF_REL_BLOCK_OFFSET(workarea_base) + 4; + } + } else { + // The chosen partition is not TBYB + if (tbyb_boot && saved_erase_addr) { + // The boot partition was TBYB, and requires an erase + if (VERSION_DOWNGRADE_ERASE_ADDR) { + // But both the chosen partition requires an erase too + // As before, prioritise the boot partition, and restore it's saved erase_address + VERSION_DOWNGRADE_ERASE_ADDR = saved_erase_addr; + return BOOTROM_ERROR_NOT_PERMITTED; + } else { + // The chosen partition doesn't require an erase, so we're fine + VERSION_DOWNGRADE_ERASE_ADDR = saved_erase_addr; + } + } + } + + return rc; +} #endif \ No newline at end of file diff --git a/src/rp2_common/pico_bootrom/include/pico/bootrom.h b/src/rp2_common/pico_bootrom/include/pico/bootrom.h index bb5b8ba2f..76ef30300 100644 --- a/src/rp2_common/pico_bootrom/include/pico/bootrom.h +++ b/src/rp2_common/pico_bootrom/include/pico/bootrom.h @@ -749,6 +749,9 @@ static inline int rom_load_partition_table(uint8_t *workarea_base, uint32_t work * * NOTE: This method does not look at owner partitions, only the A partition passed and it's corresponding B partition. * + * NOTE: You should not call this method directly when performing a Flash Update Boot before calling `explicit_buy`, as it may prevent + * any version downgrade from occuring - instead \see rom_pick_ab_update_partition() which wraps this function. + * * \param workarea_base base address of work area * \param workarea_size size of work area * \param partition_a_num the A partition of the pair @@ -1091,6 +1094,30 @@ static inline int rom_get_last_boot_type(void) { */ int rom_add_flash_runtime_partition(uint32_t start_offset, uint32_t size, uint32_t permissions); +/*! \brief Pick A/B partition without disturbing any in progress update or TBYB boot + * \ingroup pico_bootrom + * + * This will call `rom_pick_ab_partition` using the `flash_update_boot_window_base` from the current boot, while performing extra checks to prevent disrupting + * a main image TBYB boot. It requires the same minimum workarea size as `rom_pick_ab_partition`. + * \see rom_pick_ab_partition() + * + * For example, if an `explicit_buy` is pending then calling `pick_ab_partition` would normally clear the saved flash erase address for the version downgrade, + * so the required erase of the other partition would not occur when `explicit_buy` is called - this function saves and restores that address to prevent this + * issue, and returns `BOOTROM_ERROR_NOT_PERMITTED` if the partition chosen by `pick_ab_partition` also requires a flash erase version downgrade (as you can't + * erase 2 partitions with one `explicit_buy` call). + * + * It also checks that the chosen partition contained a valid image (e.g. a signed image when using secure boot), and returns `BOOTROM_ERROR_NOT_FOUND` + * if it does not. + * + * \param workarea_base base address of work area + * \param workarea_size size of work area + * \param partition_a_num the A partition of the pair + * \return >= 0 the partition number picked + * BOOTROM_ERROR_NOT_PERMITTED if not possible to do an update correctly, e.g. if both main image and data image are TBYB + * BOOTROM_ERROR_NOT_FOUND if the chosen partition failed verification + */ +int rom_pick_ab_update_partition(uint32_t *workarea_base, uint32_t workarea_size, uint partition_a_num); + #endif #ifdef __cplusplus