Init repository

This commit is contained in:
Loïc Guégan 2025-03-24 09:33:49 +01:00
parent d3ecfe3498
commit 7741f01445
35 changed files with 2067 additions and 90 deletions

1
tools/elf2uf2/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
elf2uf2

21
tools/elf2uf2/LICENSE.TXT Normal file
View file

@ -0,0 +1,21 @@
Copyright 220 (c) 2020 Raspberry Pi (Trading) Ltd.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.0

3
tools/elf2uf2/README.md Normal file
View file

@ -0,0 +1,3 @@
# Raspberry Pi Pico elf2uf2 Utility
THIS CODE COMES FROM THE FOLLOWING REPOSITORY: https://github.com/rej696/elf2uf2

60
tools/elf2uf2/elf.h Normal file
View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _ELF_H
#define _ELF_H
#include <stdint.h>
#define ELF_MAGIC 0x464c457fu
#define EM_ARM 0x28u
#define EF_ARM_ABI_FLOAT_HARD 0x00000400u
#define PT_LOAD 0x00000001u
#pragma pack(push, 1)
struct elf_header {
uint32_t magic;
uint8_t arch_class;
uint8_t endianness;
uint8_t version;
uint8_t abi;
uint8_t abi_version;
uint8_t _pad[7];
uint16_t type;
uint16_t machine;
uint32_t version2;
};
struct elf32_header {
struct elf_header common;
uint32_t entry;
uint32_t ph_offset;
uint32_t sh_offset;
uint32_t flags;
uint16_t eh_size;
uint16_t ph_entry_size;
uint16_t ph_num;
uint16_t sh_entry_size;
uint16_t sh_num;
uint16_t sh_str_index;
};
struct elf32_ph_entry {
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filez;
uint32_t memsz;
uint32_t flags;
uint32_t align;
};
#pragma pack(pop)
#endif

353
tools/elf2uf2/main.cpp Normal file
View file

@ -0,0 +1,353 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <cstdarg>
#include <algorithm>
#include "uf2.h"
#include "elf.h"
typedef unsigned int uint;
#define ERROR_ARGS -1
#define ERROR_FORMAT -2
#define ERROR_INCOMPATIBLE -3
#define ERROR_READ_FAILED -4
#define ERROR_WRITE_FAILED -5
static char error_msg[512];
static bool verbose;
static int fail(int code, const char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(error_msg, sizeof(error_msg), format, args);
va_end(args);
return code;
}
static int fail_read_error() {
return fail(ERROR_READ_FAILED, "Failed to read input file");
}
static int fail_write_error() {
return fail(ERROR_WRITE_FAILED, "Failed to write output file");
}
// we require 256 (as this is the page size supported by the device)
#define LOG2_PAGE_SIZE 8u
#define PAGE_SIZE (1u << LOG2_PAGE_SIZE)
struct address_range {
enum type {
CONTENTS, // may have contents
NO_CONTENTS, // must be uninitialized
IGNORE // will be ignored
};
address_range(uint32_t from, uint32_t to, type type) : from(from), to(to), type(type) {}
address_range() : address_range(0, 0, IGNORE) {}
type type;
uint32_t to;
uint32_t from;
};
typedef std::vector<address_range> address_ranges;
#define MAIN_RAM_START 0x20000000u
#define MAIN_RAM_END 0x20042000u
#define FLASH_START 0x10000000u
#define FLASH_END 0x15000000u
#define XIP_SRAM_START 0x15000000u
#define XIP_SRAM_END 0x15004000u
#define MAIN_RAM_BANKED_START 0x21000000u
#define MAIN_RAM_BANKED_END 0x21040000u
const address_ranges rp2040_address_ranges_flash {
address_range(FLASH_START, FLASH_END, address_range::type::CONTENTS),
address_range(MAIN_RAM_START, MAIN_RAM_END, address_range::type::NO_CONTENTS),
address_range(MAIN_RAM_BANKED_START, MAIN_RAM_BANKED_END, address_range::type::NO_CONTENTS)
};
const address_ranges rp2040_address_ranges_ram {
address_range(MAIN_RAM_START, MAIN_RAM_END, address_range::type::CONTENTS),
address_range(XIP_SRAM_START, XIP_SRAM_END, address_range::type::CONTENTS),
address_range(0x00000000u, 0x00004000u, address_range::type::IGNORE) // for now we ignore the bootrom if present
};
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;
};
static int usage() {
fprintf(stderr, "Usage: elf2uf2 (-v) <input ELF file> <output UF2 file>\n");
return ERROR_ARGS;
}
static int read_and_check_elf32_header(FILE *in, elf32_header& eh_out) {
if (1 != fread(&eh_out, sizeof(eh_out), 1, in)) {
return fail(ERROR_READ_FAILED, "Unable to read ELF header");
}
if (eh_out.common.magic != ELF_MAGIC) {
return fail(ERROR_FORMAT, "Not an ELF file");
}
if (eh_out.common.version != 1 || eh_out.common.version2 != 1) {
return fail(ERROR_FORMAT, "Unrecognized ELF version");
}
if (eh_out.common.arch_class != 1 || eh_out.common.endianness != 1) {
return fail(ERROR_INCOMPATIBLE, "Require 32 bit little-endian ELF");
}
if (eh_out.eh_size != sizeof(struct elf32_header)) {
return fail(ERROR_FORMAT, "Invalid ELF32 format");
}
if (eh_out.common.machine != EM_ARM) {
return fail(ERROR_FORMAT, "Not an ARM executable");
}
if (eh_out.common.abi != 0) {
return fail(ERROR_INCOMPATIBLE, "Unrecognized ABI");
}
if (eh_out.flags & EF_ARM_ABI_FLOAT_HARD) {
return fail(ERROR_INCOMPATIBLE, "HARD-FLOAT not supported");
}
return 0;
}
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) {
return fail(ERROR_INCOMPATIBLE, "ELF contains memory contents for uninitialized memory");
}
ar = range;
if (verbose) {
printf("%s segment %08x->%08x (%08x->%08x)\n", uninitialized ? "Uninitialized" : "Mapped", addr,
addr + size, vaddr, vaddr+size);
}
return 0;
}
}
return fail(ERROR_INCOMPATIBLE, "Memory segment %08x->%08x is outside of valid address range for device", addr, addr+size);
}
int read_and_check_elf32_ph_entries(FILE *in, const elf32_header &eh, const address_ranges& valid_ranges, std::map<uint32_t, std::vector<page_fragment>>& pages) {
if (eh.ph_entry_size != sizeof(elf32_ph_entry)) {
return fail(ERROR_FORMAT, "Invalid ELF32 program header");
}
if (eh.ph_num) {
std::vector<elf32_ph_entry> entries(eh.ph_num);
if (fseek(in, eh.ph_offset, SEEK_SET)) {
return fail_read_error();
}
if (eh.ph_num != fread(&entries[0], sizeof(struct elf32_ph_entry), eh.ph_num, in)) {
return fail_read_error();
}
for(uint i=0;i<eh.ph_num;i++) {
elf32_ph_entry& entry = entries[i];
if (entry.type == PT_LOAD && entry.memsz) {
address_range ar;
int rc;
uint 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;
}
uint addr = entry.paddr;
uint remaining = mapped_size;
uint file_offset = entry.offset;
while (remaining) {
uint off = addr & (PAGE_SIZE - 1);
uint len = std::min(remaining, 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(FILE *in, const std::vector<page_fragment> &fragments, uint8_t *buf, uint buf_len) {
assert(buf_len >= PAGE_SIZE);
for(auto& frag : fragments) {
assert(frag.page_offset >= 0 && frag.page_offset < PAGE_SIZE && frag.page_offset + frag.bytes <= PAGE_SIZE);
if (fseek(in, frag.file_offset, SEEK_SET)) {
return fail_read_error();
}
if (1 != fread(buf + frag.page_offset, frag.bytes, 1, in)) {
return fail_read_error();
}
}
return 0;
}
static bool is_address_valid(const address_ranges& valid_ranges, uint32_t addr) {
for(const auto& range : valid_ranges) {
if (range.from <= addr && range.to > addr) {
return true;
}
}
return false;
}
static bool is_address_initialized(const address_ranges& valid_ranges, uint32_t addr) {
for(const auto& range : valid_ranges) {
if (range.from <= addr && range.to > addr) {
return address_range::type::CONTENTS == range.type;
}
}
return false;
}
static bool is_address_mapped(const std::map<uint32_t, std::vector<page_fragment>>& pages, uint32_t addr) {
uint32_t page = addr & ~(PAGE_SIZE - 1);
if (!pages.count(page)) return false;
// todo check actual address within page
return true;
}
int elf2uf2(FILE *in, FILE *out) {
elf32_header eh;
std::map<uint32_t, std::vector<page_fragment>> pages;
int rc = read_and_check_elf32_header(in, eh);
bool ram_style = false;
address_ranges valid_ranges = {};
if (!rc) {
ram_style = is_address_initialized(rp2040_address_ranges_ram, eh.entry);
if (verbose) {
if (ram_style) {
printf("Detected RAM binary\n");
} else {
printf("Detected FLASH binary\n");
}
}
valid_ranges = ram_style ? rp2040_address_ranges_ram : rp2040_address_ranges_flash;
rc = read_and_check_elf32_ph_entries(in, eh, valid_ranges, pages);
}
if (rc) return rc;
if (pages.empty()) {
return fail(ERROR_INCOMPATIBLE, "The input file has no memory pages");
}
uint page_num = 0;
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 >= MAIN_RAM_START) && (page_entry.first < MAIN_RAM_END)) && (page_entry.first < expected_ep_main_ram) ) {
expected_ep_main_ram = page_entry.first | 0x1;
} else if ( ((page_entry.first >= XIP_SRAM_START) && (page_entry.first < XIP_SRAM_END)) && (page_entry.first < expected_ep_xip_sram) ) {
expected_ep_xip_sram = page_entry.first | 0x1;
}
}
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) {
return fail(ERROR_INCOMPATIBLE, "B0/B1 Boot ROM does not support direct entry into XIP_SRAM\n");
} else if (eh.entry != expected_ep) {
return fail(ERROR_INCOMPATIBLE, "A RAM binary should have an entry point at the beginning: %08x (not %08x)\n", expected_ep, eh.entry);
}
static_assert(0 == (MAIN_RAM_START & (PAGE_SIZE - 1)), "");
// currently don't require this as entry point is now at the start, we don't know where reset vector is
#if 0
uint8_t buf[PAGE_SIZE];
rc = realize_page(in, pages[MAIN_RAM_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)) {
return fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: reset vector %08x is not in mapped memory",
MAIN_RAM_START, ip);
}
if (!is_address_valid(valid_ranges, sp - 4)) {
return fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: stack pointer %08x is not in RAM",
MAIN_RAM_START, sp);
}
#endif
}
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 = PAGE_SIZE;
block.num_blocks = (uint32_t)pages.size();
block.file_size = RP2040_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\n", block.block_no, block.num_blocks, block.target_addr);
}
memset(block.data, 0, sizeof(block.data));
rc = realize_page(in, page_entry.second, block.data, sizeof(block.data));
if (rc) return rc;
if (1 != fwrite(&block, sizeof(uf2_block), 1, out)) {
return fail_write_error();
}
}
return 0;
}
int main(int argc, char **argv) {
int arg = 1;
if (arg < argc && !strcmp(argv[arg], "-v")) {
verbose = true;
arg++;
}
if (argc < arg + 2) {
return usage();
}
const char *in_filename = argv[arg++];
FILE *in = fopen(in_filename, "rb");
if (!in) {
fprintf(stderr, "Can't open input file '%s'\n", in_filename);
return ERROR_ARGS;
}
const char *out_filename = argv[arg++];
FILE *out = fopen(out_filename, "wb");
if (!out) {
fprintf(stderr, "Can't open output file '%s'\n", out_filename);
return ERROR_ARGS;
}
int rc = elf2uf2(in, out);
fclose(in);
fclose(out);
if (rc) {
remove(out_filename);
if (error_msg[0]) {
fprintf(stderr, "ERROR: %s\n", error_msg);
}
}
return rc;
}

4
tools/elf2uf2/makefile Normal file
View file

@ -0,0 +1,4 @@
all: build
build: main.cpp
g++ -o elf2uf2 main.cpp

46
tools/elf2uf2/uf2.h Normal file
View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT_UF2_H
#define _BOOT_UF2_H
#include <stdint.h>
#include <assert.h>
/** \file uf2.h
* \defgroup boot_uf2 boot_uf2
*
* Header file for the UF2 format supported by an RP2040 in BOOTSEL mode.
*/
#define UF2_MAGIC_START0 0x0A324655u
#define UF2_MAGIC_START1 0x9E5D5157u
#define UF2_MAGIC_END 0x0AB16F30u
#define UF2_FLAG_NOT_MAIN_FLASH 0x00000001u
#define UF2_FLAG_FILE_CONTAINER 0x00001000u
#define UF2_FLAG_FAMILY_ID_PRESENT 0x00002000u
#define UF2_FLAG_MD5_PRESENT 0x00004000u
#define RP2040_FAMILY_ID 0xe48bff56
struct uf2_block {
// 32 byte header
uint32_t magic_start0;
uint32_t magic_start1;
uint32_t flags;
uint32_t target_addr;
uint32_t payload_size;
uint32_t block_no;
uint32_t num_blocks;
uint32_t file_size; // or familyID;
uint8_t data[476];
uint32_t magic_end;
};
static_assert(sizeof(struct uf2_block) == 512, "uf2_block not sector sized");
#endif

55
tools/pad_checksum.py Executable file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
import argparse
import binascii
import struct
import sys
def any_int(x):
try:
return int(x, 0)
except:
raise argparse.ArgumentTypeError("expected an integer, not '{!r}'".format(x))
def bitrev(x, width):
return int("{:0{w}b}".format(x, w=width)[::-1], 2)
parser = argparse.ArgumentParser()
parser.add_argument("ifile", help="Input file (binary)")
parser.add_argument("ofile", help="Output file (assembly)")
parser.add_argument("-p", "--pad", help="Padded size (bytes), including 4-byte checksum, default 256",
type=any_int, default=256)
parser.add_argument("-s", "--seed", help="Checksum seed value, default 0",
type=any_int, default=0)
args = parser.parse_args()
try:
idata = open(args.ifile, "rb").read()
except:
sys.exit("Could not open input file '{}'".format(args.ifile))
if len(idata) > args.pad - 4:
sys.exit("Input file size ({} bytes) too large for final size ({} bytes)".format(len(idata), args.pad))
idata_padded = idata + bytes(args.pad - 4 - len(idata))
# Our bootrom CRC32 is slightly bass-ackward but it's best to work around for now (FIXME)
# 100% worth it to save two Thumb instructions
checksum = bitrev(
(binascii.crc32(bytes(bitrev(b, 8) for b in idata_padded), args.seed ^ 0xffffffff) ^ 0xffffffff) & 0xffffffff, 32)
odata = idata_padded + struct.pack("<L", checksum)
try:
with open(args.ofile, "w") as ofile:
ofile.write("// Padded and checksummed version of: {}\n\n".format(args.ifile))
ofile.write(".cpu cortex-m0plus\n")
ofile.write(".thumb\n\n")
ofile.write(".section .boot2, \"ax\"\n\n")
for offs in range(0, len(odata), 16):
chunk = odata[offs:min(offs + 16, len(odata))]
ofile.write(".byte {}\n".format(", ".join("0x{:02x}".format(b) for b in chunk)))
except:
sys.exit("Could not open output file '{}'".format(args.ofile))