release 2.0.0 - RP2350 and SDK2.0.0 changes
diff --git a/elf2uf2/BUILD.bazel b/elf2uf2/BUILD.bazel new file mode 100644 index 0000000..ef3288c --- /dev/null +++ b/elf2uf2/BUILD.bazel
@@ -0,0 +1,20 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "elf2uf2", + srcs = ["elf2uf2.cpp"], + hdrs = ["elf2uf2.h"], + copts = select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "-Wno-unused-function", + "-Wno-unused-variable", + ], + }), + includes = ["."], + deps = [ + "//elf", + "//errors", + "@pico-sdk//src/common/boot_uf2_headers", + ], +)
diff --git a/elf2uf2/CMakeLists.txt b/elf2uf2/CMakeLists.txt new file mode 100644 index 0000000..7ced994 --- /dev/null +++ b/elf2uf2/CMakeLists.txt
@@ -0,0 +1,6 @@ +add_library(elf2uf2 STATIC + elf2uf2.cpp) + +target_include_directories(elf2uf2 PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(elf2uf2 PRIVATE boot_uf2_headers elf errors)
diff --git a/elf2uf2/elf2uf2.cpp b/elf2uf2/elf2uf2.cpp new file mode 100644 index 0000000..5a0e495 --- /dev/null +++ b/elf2uf2/elf2uf2.cpp
@@ -0,0 +1,338 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <cstdio> +#include <map> +#include <set> +#include <vector> +#include <cstring> +#include <cstdarg> +#include <algorithm> +#include <cstring> +#include <memory> + +#include "elf2uf2.h" +#include "errors.h" + +#define FLASH_SECTOR_ERASE_SIZE 4096u + +static bool verbose; + +static void fail_read_error() { + fail(ERROR_READ_FAILED, "Failed to read input file"); +} + +static void fail_write_error() { + fail(ERROR_WRITE_FAILED, "Failed to write output file"); +} + +struct page_fragment { + page_fragment(uint32_t file_offset, uint32_t page_offset, uint32_t bytes) : file_offset(file_offset), page_offset(page_offset), bytes(bytes) {} + uint32_t file_offset; + uint32_t page_offset; + uint32_t bytes; +}; + +int check_address_range(const address_ranges& valid_ranges, uint32_t addr, uint32_t vaddr, uint32_t size, bool uninitialized, address_range &ar) { + for(const auto& range : valid_ranges) { + if (range.from <= addr && range.to >= addr + size) { + if (range.type == address_range::type::NO_CONTENTS && !uninitialized) { + fail(ERROR_INCOMPATIBLE, "ELF contains memory contents for uninitialized memory at %p", addr); + } + ar = range; + if (verbose) { + printf("%s segment %08x->%08x (%08x->%08x)\n", uninitialized ? "Uninitialized" : "Mapped", addr, + addr + size, vaddr, vaddr+size); + } + return 0; + } + } + fail(ERROR_INCOMPATIBLE, "Memory segment %08x->%08x is outside of valid address range for device", addr, addr+size); + return ERROR_INCOMPATIBLE; +} + +int check_elf32_ph_entries(const std::vector<elf32_ph_entry>& entries, const address_ranges& valid_ranges, std::map<uint32_t, std::vector<page_fragment>>& pages) { + for(const auto & entry : entries) { + if (entry.type == PT_LOAD && entry.memsz) { + address_range ar; + int rc; + unsigned int mapped_size = std::min(entry.filez, entry.memsz); + if (mapped_size) { + rc = check_address_range(valid_ranges, entry.paddr, entry.vaddr, mapped_size, false, ar); + if (rc) return rc; + // we don't download uninitialized, generally it is BSS and should be zero-ed by crt0.S, or it may be COPY areas which are undefined + if (ar.type != address_range::type::CONTENTS) { + if (verbose) printf(" ignored\n"); + continue; + } + unsigned int addr = entry.paddr; + unsigned int remaining = mapped_size; + unsigned int file_offset = entry.offset; + while (remaining) { + unsigned int off = addr & (UF2_PAGE_SIZE - 1); + unsigned int len = std::min(remaining, UF2_PAGE_SIZE - off); + auto &fragments = pages[addr - off]; // list of fragments + // note if filesz is zero, we want zero init which is handled because the + // statement above creates an empty page fragment list + // check overlap with any existing fragments + for (const auto &fragment : fragments) { + if ((off < fragment.page_offset + fragment.bytes) != + ((off + len) <= fragment.page_offset)) { + fail(ERROR_FORMAT, "In memory segments overlap"); + } + } + fragments.push_back( + page_fragment{file_offset,off,len}); + addr += len; + file_offset += len; + remaining -= len; + } + } + if (entry.memsz > entry.filez) { + // we have some uninitialized data too + rc = check_address_range(valid_ranges, entry.paddr + entry.filez, entry.vaddr + entry.filez, entry.memsz - entry.filez, true, + ar); + if (rc) return rc; + } + } + } + return 0; +} + +int realize_page(std::shared_ptr<std::iostream> in, const std::vector<page_fragment> &fragments, uint8_t *buf, unsigned int buf_len) { + assert(buf_len >= UF2_PAGE_SIZE); + for(auto& frag : fragments) { + assert(frag.page_offset < UF2_PAGE_SIZE && frag.page_offset + frag.bytes <= UF2_PAGE_SIZE); + in->seekg(frag.file_offset, in->beg); + if (in->fail()) { + fail_read_error(); + } + in->read((char*)buf + frag.page_offset, frag.bytes); + if (in->fail()) { + fail_read_error(); + } + } + return 0; +} + +static bool is_address_mapped(const std::map<uint32_t, std::vector<page_fragment>>& pages, uint32_t addr) { + uint32_t page = addr & ~(UF2_PAGE_SIZE - 1); + if (!pages.count(page)) return false; + // todo check actual address within page + return true; +} + +uf2_block gen_abs_block(uint32_t abs_block_loc) { + uf2_block block; + block.magic_start0 = UF2_MAGIC_START0; + block.magic_start1 = UF2_MAGIC_START1; + block.flags = UF2_FLAG_FAMILY_ID_PRESENT; + block.payload_size = UF2_PAGE_SIZE; + block.num_blocks = 2; + block.file_size = ABSOLUTE_FAMILY_ID; + block.magic_end = UF2_MAGIC_END; + block.target_addr = abs_block_loc; + block.block_no = 0; + memset(block.data, 0, sizeof(block.data)); + memset(block.data, 0xef, UF2_PAGE_SIZE); + return block; +} + +bool check_abs_block(uf2_block block) { + return std::all_of(block.data, block.data + UF2_PAGE_SIZE, [](uint8_t i) { return i == 0xef; }) && + block.magic_start0 == UF2_MAGIC_START0 && + block.magic_start1 == UF2_MAGIC_START1 && + block.flags == UF2_FLAG_FAMILY_ID_PRESENT && + block.payload_size == UF2_PAGE_SIZE && + block.num_blocks == 2 && + block.file_size == ABSOLUTE_FAMILY_ID && + block.magic_end == UF2_MAGIC_END && + block.block_no == 0; +} + +int pages2uf2(std::map<uint32_t, std::vector<page_fragment>>& pages, std::shared_ptr<std::iostream> in, std::shared_ptr<std::iostream> out, uint32_t family_id, uint32_t abs_block_loc=0) { + // RP2350-E9: add absolute block to start, targeting end of flash by default + if (family_id != ABSOLUTE_FAMILY_ID && family_id != RP2040_FAMILY_ID && abs_block_loc) { + uf2_block block = gen_abs_block(abs_block_loc); + out->write((char*)&block, sizeof(uf2_block)); + if (out->fail()) { + fail_write_error(); + } + } + uf2_block block; + unsigned int page_num = 0; + block.magic_start0 = UF2_MAGIC_START0; + block.magic_start1 = UF2_MAGIC_START1; + block.flags = UF2_FLAG_FAMILY_ID_PRESENT; + block.payload_size = UF2_PAGE_SIZE; + block.num_blocks = (uint32_t)pages.size(); + block.file_size = family_id; + block.magic_end = UF2_MAGIC_END; + for(auto& page_entry : pages) { + block.target_addr = page_entry.first; + block.block_no = page_num++; + if (verbose) { + printf("Page %d / %d %08x%s\n", block.block_no, block.num_blocks, block.target_addr, + page_entry.second.empty() ? " (padding)": ""); + } + memset(block.data, 0, sizeof(block.data)); + int rc = realize_page(in, page_entry.second, block.data, sizeof(block.data)); + if (rc) return rc; + out->write((char*)&block, sizeof(uf2_block)); + if (out->fail()) { + fail_write_error(); + } + } + return 0; +} + +int bin2uf2(std::shared_ptr<std::iostream> in, std::shared_ptr<std::iostream> out, uint32_t address, uint32_t family_id, uint32_t abs_block_loc) { + std::map<uint32_t, std::vector<page_fragment>> pages; + + in->seekg(0, in->end); + if (in->fail()) { + fail_read_error(); + } + int size = in->tellg(); + if (size <= 0) { + fail_read_error(); + } + + unsigned int addr = address; + unsigned int remaining = size; + unsigned int file_offset = 0; + while (remaining) { + unsigned int off = addr & (UF2_PAGE_SIZE - 1); + unsigned int len = std::min(remaining, UF2_PAGE_SIZE - off); + auto &fragments = pages[addr - off]; // list of fragments + // note if filesz is zero, we want zero init which is handled because the + // statement above creates an empty page fragment list + // check overlap with any existing fragments + for (const auto &fragment : fragments) { + if ((off < fragment.page_offset + fragment.bytes) != + ((off + len) <= fragment.page_offset)) { + fail(ERROR_FORMAT, "In memory segments overlap"); + } + } + fragments.push_back( + page_fragment{file_offset,off,len}); + addr += len; + file_offset += len; + remaining -= len; + } + + return pages2uf2(pages, in, out, family_id, abs_block_loc); +} + +int elf2uf2(std::shared_ptr<std::iostream> in, std::shared_ptr<std::iostream> out, uint32_t family_id, uint32_t package_addr, uint32_t abs_block_loc) { + elf_file elf; + std::map<uint32_t, std::vector<page_fragment>> pages; + + int rc = elf.read_file(in); + bool ram_style = false; + address_ranges valid_ranges = {}; + address_ranges flash_range; address_ranges ram_range; + if (family_id == RP2040_FAMILY_ID) { + flash_range = rp2040_address_ranges_flash; + ram_range = rp2040_address_ranges_ram; + } else { + flash_range = rp2350_address_ranges_flash; + ram_range = rp2350_address_ranges_ram; + } + if (!rc) { + rc = rp_determine_binary_type(elf.header(), elf.segments(), flash_range, ram_range, &ram_style); + if (!rc) { + if (verbose) { + if (ram_style) { + printf("Detected RAM binary\n"); + } else { + printf("Detected FLASH binary\n"); + } + } + valid_ranges = ram_style ? ram_range : flash_range; + rc = check_elf32_ph_entries(elf.segments(), valid_ranges, pages); + } + } + if (rc) return rc; + if (pages.empty()) { + fail(ERROR_INCOMPATIBLE, "The input file has no memory pages"); + } + // No Thumb bit on RISC-V + elf32_header eh = elf.header(); + uint32_t thumb_bit = eh.common.machine == EM_ARM ? 0x1u : 0x0u; + if (ram_style) { + uint32_t expected_ep_main_ram = UINT32_MAX; + uint32_t expected_ep_xip_sram = UINT32_MAX; + for(auto& page_entry : pages) { + if ( ((page_entry.first >= SRAM_START) && (page_entry.first < ram_range[0].to)) && (page_entry.first < expected_ep_main_ram) ) { + expected_ep_main_ram = page_entry.first | thumb_bit; + } else if ( ((page_entry.first >= ram_range[1].from) && (page_entry.first < ram_range[1].to)) && (page_entry.first < expected_ep_xip_sram) ) { + expected_ep_xip_sram = page_entry.first | thumb_bit; + } + } + uint32_t expected_ep = (UINT32_MAX != expected_ep_main_ram) ? expected_ep_main_ram : expected_ep_xip_sram; + if (eh.entry == expected_ep_xip_sram && family_id == RP2040_FAMILY_ID) { + fail(ERROR_INCOMPATIBLE, "RP2040 B0/B1/B2 Boot ROM does not support direct entry into XIP_SRAM\n"); + } else if (eh.entry != expected_ep && family_id == RP2040_FAMILY_ID) { + fail(ERROR_INCOMPATIBLE, "A RP2040 RAM binary should have an entry point at the beginning: %08x (not %08x)\n", expected_ep, eh.entry); + } + static_assert(0 == (SRAM_START & (UF2_PAGE_SIZE - 1)), ""); + // currently don't require this as entry point is now at the start, we don't know where reset vector is + // todo can be re-enabled for RP2350 +#if 0 + uint8_t buf[UF2_PAGE_SIZE]; + rc = realize_page(in, pages[SRAM_START], buf, sizeof(buf)); + if (rc) return rc; + uint32_t sp = ((uint32_t *)buf)[0]; + uint32_t ip = ((uint32_t *)buf)[1]; + if (!is_address_mapped(pages, ip)) { + fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: reset vector %08x is not in mapped memory", + SRAM_START, ip); + } + if (!is_address_valid(valid_ranges, sp - 4)) { + fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: stack pointer %08x is not in RAM", + SRAM_START, sp); + } +#endif + } else { + // Fill in empty dummy uf2 pages to align the binary to flash sectors (except for the last sector which we don't + // need to pad, and choose not to to avoid making all SDK UF2s bigger) + // That workaround is required because the bootrom uses the block number for erase sector calculations: + // https://github.com/raspberrypi/pico-bootrom/blob/c09c7f08550e8a36fc38dc74f8873b9576de99eb/bootrom/virtual_disk.c#L205 + + std::set<uint32_t> touched_sectors; + for (auto& page_entry : pages) { + uint32_t sector = page_entry.first / FLASH_SECTOR_ERASE_SIZE; + touched_sectors.insert(sector); + } + + uint32_t last_page = pages.rbegin()->first; + for (uint32_t sector : touched_sectors) { + for (uint32_t page = sector * FLASH_SECTOR_ERASE_SIZE; page < (sector + 1) * FLASH_SECTOR_ERASE_SIZE; page += UF2_PAGE_SIZE) { + if (page < last_page) { + // Create a dummy page, if it does not exist yet. note that all present pages are first + // zeroed before they are filled with any contents, so a dummy page will be all zeros. + auto &dummy = pages[page]; + } + } + } + } + + if (package_addr) { + // Package binary at address + uint32_t base_addr = pages.begin()->first; + int32_t package_delta = package_addr - base_addr; + if (verbose) printf("Base %x\n", base_addr); + + auto copy_pages = pages; + pages.clear(); + for (auto page : copy_pages) { + pages[page.first + package_delta] = page.second; + } + } + + return pages2uf2(pages, in, out, family_id, abs_block_loc); +}
diff --git a/elf2uf2/elf2uf2.h b/elf2uf2/elf2uf2.h new file mode 100644 index 0000000..2478373 --- /dev/null +++ b/elf2uf2/elf2uf2.h
@@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _ELF2UF2_H +#define _ELF2UF2_H + + +#include <cstdio> +#include <fstream> + +#include "boot/uf2.h" + +#include "elf_file.h" + +// we require 256 (as this is the page size supported by the device) +#define LOG2_PAGE_SIZE 8u +#define UF2_PAGE_SIZE (1u << LOG2_PAGE_SIZE) + + +bool check_abs_block(uf2_block block); +int bin2uf2(std::shared_ptr<std::iostream> in, std::shared_ptr<std::iostream> out, uint32_t address, uint32_t family_id, uint32_t abs_block_loc=0); +int elf2uf2(std::shared_ptr<std::iostream> in, std::shared_ptr<std::iostream> out, uint32_t family_id, uint32_t package_addr=0, uint32_t abs_block_loc=0); + + +#endif \ No newline at end of file