From 9044e121f5d2804299d9bc7685fa8d89cc171787 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Tue, 7 Apr 2026 14:06:22 +0100 Subject: [PATCH 1/5] Add handling to load for files with multiple family IDs Also adds supported family IDs to models, used to check if family is supported for SRAM loads (and RP2040 flash loads) This fixes picotool load for the universal binaries --- main.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++--------- model/model.h | 21 ++++++++++++--- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/main.cpp b/main.cpp index c91e8742..33d6b8ef 100644 --- a/main.cpp +++ b/main.cpp @@ -5413,7 +5413,31 @@ bool load_guts(picoboot::connection con, iostream_memory_access &file_access) { bool load_command::execute(device_map &devices) { auto con = get_single_bootsel_device_connection(devices); picoboot_memory_access raw_access(con); - auto tmp_file_access = get_file_memory_access(0); + uint32_t next_id = 0; + vector available_family_ids; + uint32_t file_family_id = 0; + uint32_t override_family_id = settings.family_id; + auto tmp_file_access = get_file_memory_access(0, false, &next_id); + if (next_id) { + // UF2 file with multiple family IDs + settings.family_id = 0; + next_id = get_family_id(0); + while (next_id) { + available_family_ids.push_back(next_id); + auto tmp_access = get_file_memory_access(0, false, &next_id); + } + if (override_family_id) { + if (std::find(available_family_ids.begin(), available_family_ids.end(), override_family_id) == available_family_ids.end()) { + fos << "WARNING: Requested family ID " << family_name(override_family_id) << " not found in UF2 file, "; + fos << "so treating first family ID found in the UF2 file (" << family_name(available_family_ids[0]) << ") as the requested one\n"; + available_family_ids.resize(1); + } else { + available_family_ids.resize(1); + available_family_ids[0] = override_family_id; + } + } + } + if (settings.load.partition >= 0) { auto partitions = get_partitions(con); if (!partitions) { @@ -5429,28 +5453,52 @@ bool load_command::execute(device_map &devices) { settings.offset = start + FLASH_START; settings.offset_set = true; settings.partition_size = end - start; - } else if (!settings.load.ignore_pt && !settings.offset_set && tmp_file_access.get_binary_start() == FLASH_START) { - uint32_t family_id = get_family_id(0); - settings.family_id = family_id; + } else if (!settings.load.ignore_pt && !settings.offset_set) { + if (available_family_ids.size() == 0) { + uint32_t family_id = get_family_id(0); + available_family_ids.push_back(family_id); + } uint32_t start; uint32_t end; - if (raw_access.get_model()->supports_partition_table()) { - if (get_target_partition(con, &start, &end)) { - settings.offset = start + FLASH_START; - settings.offset_set = true; - settings.partition_size = end - start; + bool accepted = false; + for (auto family_id : available_family_ids) { + settings.family_id = override_family_id ? override_family_id : family_id; + if (raw_access.get_model()->supports_partition_table() && tmp_file_access.get_binary_start() == FLASH_START) { + if (get_target_partition(con, &start, &end)) { + settings.offset = start + FLASH_START; + settings.offset_set = true; + settings.partition_size = end - start; + accepted = true; + file_family_id = family_id; + break; + } } else { - // Check if partition table is present, for correct error message + // Check the family ID is supported by the model (either SRAM, or RP2040) + if (raw_access.get_model()->supports_family_id(settings.family_id)) { + if (available_family_ids.size() > 1) { + fos << "Loading family ID " << family_name(settings.family_id) << "\n"; + } + accepted = true; + file_family_id = family_id; + break; + } + } + } + if (!accepted) { + // Check if partition table is present, for correct error message + if (raw_access.get_model()->supports_partition_table() && tmp_file_access.get_binary_start() == FLASH_START) { auto partitions = get_partitions(con); if (!partitions) { - fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded onto a device with no partition table"); + fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded onto an %s device with no partition table", raw_access.get_model()->name().c_str()); } else { fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded into the partition table on the device"); } + } else { + fail(ERROR_NOT_POSSIBLE, "This file cannot be loaded onto an %s device", raw_access.get_model()->name().c_str()); } } } - auto file_access = get_file_memory_access(0); + auto file_access = get_file_memory_access(0, false, &file_family_id); if (settings.offset_set && get_file_type() != filetype::bin && raw_access.get_model()->chip() == rp2040) { fail(ERROR_ARGS, "Offset only valid for BIN files"); } diff --git a/model/model.h b/model/model.h index 3b51ec2c..023067c0 100644 --- a/model/model.h +++ b/model/model.h @@ -73,8 +73,8 @@ static std::string chip_name(chip_t chip) { // looking at the bootrom), however "stock" versions can be created from family IDs for example class model_info { public: - model_info(chip_t chip, std::string name, uint32_t rom_end, std::set picoboot_cmds = {}) : _chip(chip), _name(std::move(name)), - _rom_end(rom_end), _picoboot_cmds(std::move(picoboot_cmds)) {} + model_info(chip_t chip, std::string name, uint32_t rom_end, std::set picoboot_cmds = {}, std::set supported_family_ids = {}) : _chip(chip), _name(std::move(name)), + _rom_end(rom_end), _picoboot_cmds(std::move(picoboot_cmds)), _supported_family_ids(std::move(supported_family_ids)) {} chip_t chip() const { return _chip; } chip_revision_t chip_revision() const { return _chip_revision; } void set_chip_revision(chip_revision_t revision) { _chip_revision = revision; } @@ -89,6 +89,10 @@ class model_info { return invalid; } + virtual bool supports_family_id(uint32_t family_id) const { + return _supported_family_ids.find(family_id) != _supported_family_ids.end(); + } + virtual bool supports_picoboot_cmd(picoboot_cmd_id cmd) const { return _picoboot_cmds.find(cmd) != _picoboot_cmds.end(); } @@ -132,6 +136,7 @@ class model_info { chip_revision_t _chip_revision; uint32_t _rom_end; std::set _picoboot_cmds; + std::set _supported_family_ids; chip_t _chip; uint32_t _family_id; }; @@ -146,7 +151,7 @@ class model_unknown : public model_info { class model_rp : public model_info { protected: - model_rp(chip_t chip, std::string name, uint32_t rom_end, std::set picoboot_cmds = {}) : model_info(chip, std::move(name), rom_end, std::move(picoboot_cmds)) {} + model_rp(chip_t chip, std::string name, uint32_t rom_end, std::set picoboot_cmds = {}, std::set supported_family_ids = {}) : model_info(chip, std::move(name), rom_end, std::move(picoboot_cmds), std::move(supported_family_ids)) {} public: enum memory_type get_memory_type(uint32_t addr) override { @@ -175,7 +180,7 @@ class model_rp : public model_info { class model_rp_generic : public model_rp { public: // allow large memory regions for generic model - model_rp_generic() : model_rp(unknown, chip_name(unknown), 0x100, {}) {} + model_rp_generic() : model_rp(unknown, chip_name(unknown), 0x100) {} uint32_t xip_sram_start() override { return std::min({XIP_SRAM_START_RP2040, XIP_SRAM_START_RP2350}); @@ -210,6 +215,8 @@ class model_rp2040 : public model_rp { PC_ENTER_CMD_XIP, PC_EXEC, PC_VECTORIZE_FLASH, + }, { + RP2040_FAMILY_ID, }) { set_family_id(RP2040_FAMILY_ID); } @@ -276,6 +283,12 @@ class model_rp2350 : public model_rp { PC_GET_INFO, PC_OTP_READ, PC_OTP_WRITE, + }, { + ABSOLUTE_FAMILY_ID, + DATA_FAMILY_ID, + RP2350_ARM_S_FAMILY_ID, + RP2350_RISCV_FAMILY_ID, + RP2350_ARM_NS_FAMILY_ID, }) {} bool supports_partition_table() override { return true; } From 86e1da544f891e39de4814df817e0c5b1caba910 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Tue, 7 Apr 2026 14:55:53 +0100 Subject: [PATCH 2/5] Fix regular UF2s --- main.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/main.cpp b/main.cpp index 33d6b8ef..bf0765f0 100644 --- a/main.cpp +++ b/main.cpp @@ -5417,9 +5417,11 @@ bool load_command::execute(device_map &devices) { vector available_family_ids; uint32_t file_family_id = 0; uint32_t override_family_id = settings.family_id; + bool multi_family_uf2 = false; auto tmp_file_access = get_file_memory_access(0, false, &next_id); if (next_id) { // UF2 file with multiple family IDs + multi_family_uf2 = true; settings.family_id = 0; next_id = get_family_id(0); while (next_id) { @@ -5436,6 +5438,16 @@ bool load_command::execute(device_map &devices) { available_family_ids[0] = override_family_id; } } + } else { + // For anything else, just populate available_family_ids with the family ID from the file + uint32_t family_id = get_family_id(0); + available_family_ids.push_back(family_id); + } + + if (available_family_ids.size() > 1) { + vector family_names = {}; + for (auto family_id : available_family_ids) family_names.push_back(family_name(family_id)); + fos << "UF2 file contains multiple family IDs: " << cli::join(family_names, ", ") << "\n"; } if (settings.load.partition >= 0) { @@ -5454,10 +5466,6 @@ bool load_command::execute(device_map &devices) { settings.offset_set = true; settings.partition_size = end - start; } else if (!settings.load.ignore_pt && !settings.offset_set) { - if (available_family_ids.size() == 0) { - uint32_t family_id = get_family_id(0); - available_family_ids.push_back(family_id); - } uint32_t start; uint32_t end; bool accepted = false; @@ -5498,9 +5506,9 @@ bool load_command::execute(device_map &devices) { } } } - auto file_access = get_file_memory_access(0, false, &file_family_id); + auto file_access = multi_family_uf2 ? get_file_memory_access(0, false, &file_family_id) : get_file_memory_access(0); if (settings.offset_set && get_file_type() != filetype::bin && raw_access.get_model()->chip() == rp2040) { - fail(ERROR_ARGS, "Offset only valid for BIN files"); + fail(ERROR_ARGS, "On RP2040, offset is only valid for BIN files"); } bool ret = load_guts(con, file_access); return ret; From fff1102efe9825c99f43096038af82fcb0d74c35 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Fri, 17 Jul 2026 11:49:20 +0100 Subject: [PATCH 3/5] Fix to work with new universal binaries New ones split the last RP2040 block after all the RP2350 blocks to prevent Windows errors, so fix build_rmap_uf2 to handle that --- main.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index bf0765f0..4ec3119d 100644 --- a/main.cpp +++ b/main.cpp @@ -3306,12 +3306,32 @@ uint32_t build_rmap_uf2(std::shared_ptrfile, range_map& r uf2_block block; unsigned int pos = 0; uint32_t next_family_id = 0; + vector family_ids = {}; do { file->read((char*)&block, sizeof(uf2_block)); if (file->fail()) { if (file->eof()) { file->clear(); break; } fail(ERROR_READ_FAILED, "unexpected end of input file"); } + if( + std::find(family_ids.begin(), family_ids.end(), block.file_size) == family_ids.end() + #if SUPPORT_RP2350_A2 + && !check_abs_block(block) + #endif + ) { + // new family ID found + family_ids.push_back(block.file_size); + DEBUG_LOG("New family ID %s\n", family_name(block.file_size).c_str()); + if ( + std::find(family_ids.begin(), family_ids.end(), family_id) != family_ids.end() + && std::find(family_ids.begin(), family_ids.end(), family_id) + 1 == std::find(family_ids.begin(), family_ids.end(), block.file_size) + ) { + DEBUG_LOG("This is next family ID %s\n", family_name(block.file_size).c_str()); + next_family_id = block.file_size; + } else if (block.file_size == family_id) { + DEBUG_LOG("This is selected family ID %s\n", family_name(block.file_size).c_str()); + } + } if (block.magic_start0 == UF2_MAGIC_START0 && block.magic_start1 == UF2_MAGIC_START1 && block.magic_end == UF2_MAGIC_END) { if (block.flags & UF2_FLAG_FAMILY_ID_PRESENT && @@ -3325,20 +3345,10 @@ uint32_t build_rmap_uf2(std::shared_ptrfile, range_map& r } else { rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); family_id = block.file_size; - next_family_id = 0; } #else rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); family_id = block.file_size; - next_family_id = 0; - #endif - } else if (block.file_size != family_id && family_id && !next_family_id) { - #if SUPPORT_RP2350_A2 - if (!check_abs_block(block)) { - #endif - next_family_id = block.file_size; - #if SUPPORT_RP2350_A2 - } #endif } } From 4707ef2477ea9889c091533dc80834273aa5a036 Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Fri, 17 Jul 2026 13:45:13 +0100 Subject: [PATCH 4/5] Small fixups Add robustness for invalid UF2 files Allow loading BIN/ELF files into SRAM/RP2040 regardless of the guessed family ID, to match prior behaviour --- main.cpp | 91 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/main.cpp b/main.cpp index 4ec3119d..f9897e36 100644 --- a/main.cpp +++ b/main.cpp @@ -3313,43 +3313,44 @@ uint32_t build_rmap_uf2(std::shared_ptrfile, range_map& r if (file->eof()) { file->clear(); break; } fail(ERROR_READ_FAILED, "unexpected end of input file"); } - if( - std::find(family_ids.begin(), family_ids.end(), block.file_size) == family_ids.end() - #if SUPPORT_RP2350_A2 - && !check_abs_block(block) - #endif - ) { - // new family ID found - family_ids.push_back(block.file_size); - DEBUG_LOG("New family ID %s\n", family_name(block.file_size).c_str()); - if ( - std::find(family_ids.begin(), family_ids.end(), family_id) != family_ids.end() - && std::find(family_ids.begin(), family_ids.end(), family_id) + 1 == std::find(family_ids.begin(), family_ids.end(), block.file_size) - ) { - DEBUG_LOG("This is next family ID %s\n", family_name(block.file_size).c_str()); - next_family_id = block.file_size; - } else if (block.file_size == family_id) { - DEBUG_LOG("This is selected family ID %s\n", family_name(block.file_size).c_str()); - } - } if (block.magic_start0 == UF2_MAGIC_START0 && block.magic_start1 == UF2_MAGIC_START1 && block.magic_end == UF2_MAGIC_END) { if (block.flags & UF2_FLAG_FAMILY_ID_PRESENT && - !(block.flags & UF2_FLAG_NOT_MAIN_FLASH) && block.payload_size == PAGE_SIZE && - (!family_id || block.file_size == family_id)) { + !(block.flags & UF2_FLAG_NOT_MAIN_FLASH) && block.payload_size == PAGE_SIZE) { + if( + std::find(family_ids.begin(), family_ids.end(), block.file_size) == family_ids.end() + #if SUPPORT_RP2350_A2 + && !check_abs_block(block) + #endif + ) { + // new family ID found + family_ids.push_back(block.file_size); + DEBUG_LOG("New family ID %s\n", family_name(block.file_size).c_str()); + if ( + std::find(family_ids.begin(), family_ids.end(), family_id) != family_ids.end() + && std::find(family_ids.begin(), family_ids.end(), family_id) + 1 == std::find(family_ids.begin(), family_ids.end(), block.file_size) + ) { + DEBUG_LOG("This is next family ID %s\n", family_name(block.file_size).c_str()); + next_family_id = block.file_size; + } else if (block.file_size == family_id) { + DEBUG_LOG("This is selected family ID %s\n", family_name(block.file_size).c_str()); + } + } + if (!family_id || block.file_size == family_id) { #if SUPPORT_RP2350_A2 - // ignore the absolute block, but save the address - if (check_abs_block(block)) { - DEBUG_LOG("Ignoring RP2350-E10 absolute block\n"); - settings.uf2.abs_block_loc = block.target_addr; - } else { + // ignore the absolute block, but save the address + if (check_abs_block(block)) { + DEBUG_LOG("Ignoring RP2350-E10 absolute block\n"); + settings.uf2.abs_block_loc = block.target_addr; + } else { + rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); + family_id = block.file_size; + } + #else rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); family_id = block.file_size; - } - #else - rmap.insert(range(block.target_addr, block.target_addr + PAGE_SIZE), pos + offsetof(uf2_block, data[0])); - family_id = block.file_size; #endif + } } } pos += sizeof(uf2_block); @@ -5441,7 +5442,8 @@ bool load_command::execute(device_map &devices) { if (override_family_id) { if (std::find(available_family_ids.begin(), available_family_ids.end(), override_family_id) == available_family_ids.end()) { fos << "WARNING: Requested family ID " << family_name(override_family_id) << " not found in UF2 file, "; - fos << "so treating first family ID found in the UF2 file (" << family_name(available_family_ids[0]) << ") as the requested one\n"; + fos << "so loading the first family ID found in the UF2 file (" << family_name(available_family_ids[0]) << "), "; + fos << "into where family ID " << family_name(override_family_id) << " would go\n"; available_family_ids.resize(1); } else { available_family_ids.resize(1); @@ -5454,10 +5456,15 @@ bool load_command::execute(device_map &devices) { available_family_ids.push_back(family_id); } - if (available_family_ids.size() > 1) { - vector family_names = {}; - for (auto family_id : available_family_ids) family_names.push_back(family_name(family_id)); - fos << "UF2 file contains multiple family IDs: " << cli::join(family_names, ", ") << "\n"; + if (multi_family_uf2){ + // Default to the first available family + file_family_id = available_family_ids[0]; + + if (available_family_ids.size() > 1) { + vector family_names = {}; + for (auto family_id : available_family_ids) family_names.push_back(family_name(family_id)); + fos << "UF2 file contains multiple family IDs: " << cli::join(family_names, ", ") << "\n"; + } } if (settings.load.partition >= 0) { @@ -5491,11 +5498,10 @@ bool load_command::execute(device_map &devices) { break; } } else { - // Check the family ID is supported by the model (either SRAM, or RP2040) - if (raw_access.get_model()->supports_family_id(settings.family_id)) { - if (available_family_ids.size() > 1) { - fos << "Loading family ID " << family_name(settings.family_id) << "\n"; - } + // For UF2 files, check the family ID is supported by the model (either SRAM, or RP2040) + // Other files are automatically accepted unless the family ID has been overridden, + // because the family ID is only a guess from get_access_model + if ((get_file_type() != filetype::uf2 && !override_family_id) || raw_access.get_model()->supports_family_id(settings.family_id)) { accepted = true; file_family_id = family_id; break; @@ -5516,6 +5522,11 @@ bool load_command::execute(device_map &devices) { } } } + + if (multi_family_uf2 && available_family_ids.size() > 1){ + fos << "Loading family ID " << family_name(file_family_id) << "\n"; + } + auto file_access = multi_family_uf2 ? get_file_memory_access(0, false, &file_family_id) : get_file_memory_access(0); if (settings.offset_set && get_file_type() != filetype::bin && raw_access.get_model()->chip() == rp2040) { fail(ERROR_ARGS, "On RP2040, offset is only valid for BIN files"); From 663860cf09d5c4ae6c7dcb5acf43ffae66a1a9dd Mon Sep 17 00:00:00 2001 From: William Vinnicombe Date: Fri, 17 Jul 2026 13:52:39 +0100 Subject: [PATCH 5/5] Clarify --ignore-partitions functionality Now it also ignores UF2 compatibility checks for RP2040 and SRAM binaries too --- README.md | 2 +- main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3ec328f..6f1e0b36 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ SYNOPSIS: OPTIONS: Post load actions --ignore-partitions - When writing flash data, ignore the partition table and write to absolute space + Ignore the partition table, or whether the family ID is compatible with the device --family Specify the family ID of the file to load diff --git a/main.cpp b/main.cpp index f9897e36..9baf8661 100644 --- a/main.cpp +++ b/main.cpp @@ -1094,7 +1094,7 @@ struct load_command : public cmd { group get_cli() override { return ( ( - option("--ignore-partitions").set(settings.load.ignore_pt) % "When writing flash data, ignore the partition table and write to absolute space" + + option("--ignore-partitions").set(settings.load.ignore_pt) % "Ignore the partition table, or whether the family ID is compatible with the device" + (option("--family") % "Specify the family ID of the file to load" & family_id("family_id").set(settings.family_id) % "family ID to use for load").force_expand_help(true) + (option('p', "--partition") % "Specify the partition to load into" &