Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(argparse QUIET)
find_package(magic_enum QUIET)
find_package(picosha2 QUIET)
find_package(elfio QUIET)
# Check if nix is not providing these dependencies.
if (NOT argparse_FOUND)
include(FetchContent)
Expand All @@ -36,12 +37,24 @@ if (NOT argparse_FOUND)
)
FetchContent_MakeAvailable(picosha2)

FetchContent_Declare(
elfio
GIT_REPOSITORY https://github.com/serge1/ELFIO
GIT_TAG Release_3.12
)
FetchContent_MakeAvailable(elfio)

else()
# This is a hack becase picosha2 does not define an install phase in CMakefile.
add_library(picosha2 INTERFACE)
target_include_directories(picosha2 INTERFACE
${CMAKE_PREFIX_PATH}/include
)
# This is a hack becase elfio does not define an install phase in CMakefile.
add_library(elfio INTERFACE)
target_include_directories(elfio INTERFACE
${CMAKE_PREFIX_PATH}/elfio
)
endif()


Expand All @@ -53,6 +66,5 @@ add_subdirectory(lib/visuals)

add_executable(ftditool app/main.cc)
target_include_directories(ftditool PUBLIC "./src/lib" "./src/")
target_link_libraries(ftditool PRIVATE visuals picosha2 argparse::argparse flash ftdipp)
target_link_libraries(ftditool PRIVATE visuals picosha2 elfio argparse::argparse flash ftdipp)
install(TARGETS ftditool DESTINATION bin)

173 changes: 173 additions & 0 deletions app/commands.hh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <iostream>
#include <picosha2.h>
#include <print>
#include <elfio/elfio.hpp>

namespace commands {

Expand Down Expand Up @@ -283,6 +284,178 @@ struct LoadFile : public Commands<T> {
}
};

template <typename T>
struct LoadFileElf : public Commands<T> {
std::string& filename;
bool quad;
bool bootstrap;
bool skip_erase;
LoadFileElf(flash::Generic<T> f, std::string& filename, bool bootstrap = false,
bool skip_erase = false, bool quad = false)
: Commands<T>(f),
filename(filename),
quad(quad),
bootstrap(bootstrap),
skip_erase(skip_erase) {}

int run() override {
ELFIO::elfio reader;
if (!reader.load(filename)) {
std::println("Invalid ELF file");
return 0;
}

std::vector<ELFIO::segment*> load_segments;
for (auto& segment : reader.segments) {
if (segment->get_type() == ELFIO::PT_LOAD) {
load_segments.push_back(std::to_address(segment));
}
}

// Given a start and end interval, return the interval in sectors containing them,
// by rounding the start address down and end address up to the next sector-aligned
// address.
auto containing_sectors = [](auto start, auto end) {
return std::make_pair(start & ~(flash::SectorSize - 1),
(end + flash::SectorSize - 1) & ~(flash::SectorSize - 1));
};

bool addr4b = false;
auto erase_size = 0;
auto load_size = 0;
for (auto segment : load_segments) {
auto phys_start = segment->get_physical_address();
auto phys_end = phys_start + segment->get_memory_size();
auto sectors = containing_sectors(phys_start, phys_end);
load_size += segment->get_file_size();
erase_size += (sectors.second - sectors.first);
if (phys_start > 0xFFFFFF || phys_end > 0xFFFFFF) {
addr4b = true;
}
}

if (!bootstrap) {
this->flash.reset();
}
if (addr4b && !this->flash.enter_4b_addr()) {
std::println("Enter 4-byte address mode failed");
return 0;
}
if (quad && !this->flash.enable_quad(true)) {
std::println("enable quad failed");
return 0;
}

this->flash.write_enable(true);

std::optional<bool> res;

// The length of the data to be loaded into memory (file size) may be less than
// the size of the segment in memory (memory size), in which case the excess represents
// zero-initialised data (e.g .bss section). Therefore, the memory size is used for
// erasing the flash, and then the file resident data is loaded into it, which may be
// shorter.

// Erase sectors containing segment data.
if (!skip_erase) {
auto erased = 0;
auto erase_progress = ProgressBar(erase_size, 50, "Erasing").with_throughput();
for (auto segment : load_segments) {
auto phys_start = segment->get_physical_address();
auto phys_end = phys_start + segment->get_memory_size();
auto sectors = containing_sectors(phys_start, phys_end);

auto addr = sectors.first;
while (addr < sectors.second) {
res = addr4b ? this->flash.template erase<4, flash::Opcode::SectorErase4b>(addr)
: this->flash.erase(addr);
if (!res) {
std::println("Failed to erase block {:#x}", addr);
return 0;
}

addr += flash::SectorSize;
erased += flash::SectorSize;
erase_progress.update(erased);
}
}
}

this->flash.wait_not_busy();

// Then, load the segment data from the file.
auto loaded = 0;
auto load_progress = ProgressBar(load_size, 50, "Loading").with_throughput();
for (auto segment : load_segments) {
auto addr = segment->get_physical_address();
std::span<const uint8_t> data(reinterpret_cast<const uint8_t*>(segment->get_data()),
segment->get_file_size());

// Segments may start at an address that is not aligned to the flash page size.
// Page program commands may start within a page, but may wrap-around or be invalid
// if going over the page boundary, so only write until we are aligned to the page size.
if ((addr % flash::PageSize) != 0) {
auto to_next = flash::PageSize - (addr % flash::PageSize);
auto n = std::min(to_next, data.size());
std::vector<uint8_t> page(data.first(n).begin(), data.first(n).end());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this copy due to the type difference?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the page program commands expect a std::span<uint8_t>, I think because the underlying transfer operations use a RW span and use an enum for the transfer direction type.


if (addr4b) {
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
} else if (quad) {
res = this->flash.quad_page_program(addr, page);
} else {
res = this->flash.single_page_program_non_blocking(addr, page);
}
if (!res) {
std::println("Program page {:#x} failed.", addr);
return 0;
}

this->flash.wait_not_busy();

addr += n;
loaded += n;
data = data.subspan(n);
load_progress.update(loaded);
}

// Now we are aligned to the flash page size, write pages as normal.
while (data.size() > 0) {
auto n = std::min(data.size(), std::size_t{flash::PageSize});
std::vector<uint8_t> page(data.first(n).begin(), data.first(n).end());

this->flash.wait_not_busy();

if (addr4b) {
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
} else if (quad) {
res = this->flash.quad_page_program(addr, page);
} else {
res = this->flash.single_page_program_non_blocking(addr, page);
}
if (!res) {
std::println("Program page {:#x} failed.", addr);
return 0;
}

addr += n;
loaded += n;
data = data.subspan(n);
load_progress.update(loaded);
}
}

this->flash.wait_not_busy();
this->flash.write_enable(false);

if (bootstrap) {
this->flash.reset();
}

return 1;
}
};

template <typename T>
requires embeddedpp::Gpio<T>
struct GpioWrite {
Expand Down
24 changes: 24 additions & 0 deletions app/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,30 @@ int main(int argc, char* argv[]) {
return 0;
};

auto bootstrap_elf_cmd = new_flash_command(
"bootstrap-elf", "Write the loadable contents of an ELF file and reset the target.");
bootstrap_elf_cmd->add_argument("filename").help("The file path.");
bootstrap_elf_cmd->add_argument("--quad")
.help("Use qSPI")
.default_value(false)
.implicit_value(true);
bootstrap_elf_cmd->add_argument("--skip-erase")
.help("Don't issue erase commands")
.default_value(false)
.implicit_value(true);
program.add_subparser(*bootstrap_elf_cmd);
commands["bootstrap-elf"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto filename = bootstrap_elf_cmd->get<std::string>("filename");
auto quad = bootstrap_elf_cmd->get<bool>("--quad");
auto skip_erase = bootstrap_elf_cmd->get<bool>("--skip-erase");
auto spih = handle_flash_command(bootstrap_elf_cmd, pid);
commands::LoadFileElf(flash::Generic(*spih), filename, true, skip_erase, quad).run();

spih->close();
return 0;
};

auto gpio_write_cmd = new_command("gpio-write", "Write a value to an FTDI GPIO pin.");
gpio_write_cmd->add_argument("pin").help("GPIO pin number (0-3)").required().scan<'d', int>();
gpio_write_cmd->add_argument("value").help("Value to write (0 or 1)").required().scan<'d', int>();
Expand Down
Binary file modified doc/img/deps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 10 additions & 4 deletions lib/flash/flash.hh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "jedec.hh"
#include "sfdp.hh"
#include <cstdint>
#include <cassert>

namespace flash {

Expand Down Expand Up @@ -135,8 +136,9 @@ class Generic {

Option<Sector> quad_read_sector(uint32_t address) { return quad_read<4096>(address); }

Option<bool>
quad_page_program(uint32_t address, std::span<uint8_t, 256> data, uint8_t upcode = 0x32) {
Option<bool> quad_page_program(uint32_t address, std::span<uint8_t> data, uint8_t upcode = 0x32) {
// Page program commands should not cross page boundaries.
assert((address % flash::PageSize) + data.size() <= flash::PageSize);
write_enable();
std::array<uint8_t, 1> cmd = {upcode};

Expand Down Expand Up @@ -250,7 +252,9 @@ class Generic {
}

template <size_t ADDR_SIZE = 3>
Option<bool> single_page_program_non_blocking(uint32_t address, std::span<uint8_t, 256> data) {
Option<bool> single_page_program_non_blocking(uint32_t address, std::span<uint8_t> data) {
// Page program commands should not cross page boundaries.
assert((address % flash::PageSize) + data.size() <= flash::PageSize);
static_assert(ADDR_SIZE == 3 || ADDR_SIZE == 4, "Only 3 or 4 byte addresses supported");

Opcode op;
Expand All @@ -274,7 +278,9 @@ class Generic {
}

template <size_t ADDR_SIZE = 3>
Option<bool> single_page_program(uint32_t address, std::span<uint8_t, 256> data) {
Option<bool> single_page_program(uint32_t address, std::span<uint8_t> data) {
// Page program commands should not cross page boundaries.
assert((address % flash::PageSize) + data.size() <= flash::PageSize);
write_enable();
single_page_program_non_blocking(address, data);
wait_not_busy();
Expand Down
34 changes: 34 additions & 0 deletions nix/elfio.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

{
stdenv,
lib,
fetchFromGitHub,
}:
stdenv.mkDerivation rec {
pname = "elfio";
version = "Release_3.12";

src = fetchFromGitHub {
owner = "serge1";
repo = "ELFIO";
rev = "${version}";
sha256 = "sha256-tDRBscs2L/3gYgLQvb1+8nNxqkr8v1xBkeDXuOqShX4=";
};

dontBuild = true;

installPhase = ''
mkdir -p $out/include
cp -r elfio $out/include
'';

meta = with lib; {
description = "Header-only ELF implementation for C++";
homepage = "https://github.com/serge1/ELFIO";
license = licenses.mit;
platforms = platforms.all;
};
}
2 changes: 2 additions & 0 deletions nix/ftditool.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
argparse,
magic-enum,
picosha2,
elfio,
libusb1,
systemd,
makeWrapper,
Expand All @@ -34,6 +35,7 @@ stdenv.mkDerivation {
argparse
magic-enum
picosha2
elfio
ftd2xx
libusb1
systemd
Expand Down