Skip to content

Commit 703e33e

Browse files
committed
command: implement ELF loader command
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
1 parent 1615488 commit 703e33e

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

app/commands.hh

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <iostream>
1313
#include <picosha2.h>
1414
#include <print>
15+
#include <elfio/elfio.hpp>
1516

1617
namespace commands {
1718

@@ -283,6 +284,178 @@ struct LoadFile : public Commands<T> {
283284
}
284285
};
285286

287+
template <typename T>
288+
struct LoadFileElf : public Commands<T> {
289+
std::string& filename;
290+
bool quad;
291+
bool bootstrap;
292+
bool skip_erase;
293+
LoadFileElf(flash::Generic<T> f, std::string& filename, bool bootstrap = false,
294+
bool skip_erase = false, bool quad = false)
295+
: Commands<T>(f),
296+
filename(filename),
297+
quad(quad),
298+
bootstrap(bootstrap),
299+
skip_erase(skip_erase) {}
300+
301+
int run() override {
302+
ELFIO::elfio reader;
303+
if (!reader.load(filename)) {
304+
std::println("Invalid ELF file");
305+
return 0;
306+
}
307+
308+
std::vector<ELFIO::segment*> load_segments;
309+
for (auto& segment : reader.segments) {
310+
if (segment->get_type() == ELFIO::PT_LOAD) {
311+
load_segments.push_back(std::to_address(segment));
312+
}
313+
}
314+
315+
// Given a start and end interval, return the interval in sectors containing them,
316+
// by rounding the start address down and end address up to the next sector-aligned
317+
// address.
318+
auto containing_sectors = [](auto start, auto end) {
319+
return std::make_pair(start & ~(flash::SectorSize - 1),
320+
(end + flash::SectorSize - 1) & ~(flash::SectorSize - 1));
321+
};
322+
323+
bool addr4b = false;
324+
auto erase_size = 0;
325+
auto load_size = 0;
326+
for (auto segment : load_segments) {
327+
auto phys_start = segment->get_physical_address();
328+
auto phys_end = phys_start + segment->get_memory_size();
329+
auto sectors = containing_sectors(phys_start, phys_end);
330+
load_size += segment->get_file_size();
331+
erase_size += (sectors.second - sectors.first);
332+
if (phys_start > 0xFFFFFF || phys_end > 0xFFFFFF) {
333+
addr4b = true;
334+
}
335+
}
336+
337+
if (!bootstrap) {
338+
this->flash.reset();
339+
}
340+
if (addr4b && !this->flash.enter_4b_addr()) {
341+
std::println("Enter 4-byte address mode failed");
342+
return 0;
343+
}
344+
if (quad && !this->flash.enable_quad(true)) {
345+
std::println("enable quad failed");
346+
return 0;
347+
}
348+
349+
this->flash.write_enable(true);
350+
351+
std::optional<bool> res;
352+
353+
// The length of the data to be loaded into memory (file size) may be less than
354+
// the size of the segment in memory (memory size), in which case the excess represents
355+
// zero-initialised data (e.g .bss section). Therefore, the memory size is used for
356+
// erasing the flash, and then the file resident data is loaded into it, which may be
357+
// shorter.
358+
359+
// Erase sectors containing segment data.
360+
if (!skip_erase) {
361+
auto erased = 0;
362+
auto erase_progress = ProgressBar(erase_size, 50, "Erasing").with_throughput();
363+
for (auto segment : load_segments) {
364+
auto phys_start = segment->get_physical_address();
365+
auto phys_end = phys_start + segment->get_memory_size();
366+
auto sectors = containing_sectors(phys_start, phys_end);
367+
368+
auto addr = sectors.first;
369+
while (addr < sectors.second) {
370+
res = addr4b ? this->flash.template erase<4, flash::Opcode::SectorErase4b>(addr)
371+
: this->flash.erase(addr);
372+
if (!res) {
373+
std::println("Failed to erase block {:#x}", addr);
374+
return 0;
375+
}
376+
377+
addr += flash::SectorSize;
378+
erased += flash::SectorSize;
379+
erase_progress.update(erased);
380+
}
381+
}
382+
}
383+
384+
this->flash.wait_not_busy();
385+
386+
// Then, load the segment data from the file.
387+
auto loaded = 0;
388+
auto load_progress = ProgressBar(load_size, 50, "Loading").with_throughput();
389+
for (auto segment : load_segments) {
390+
auto addr = segment->get_physical_address();
391+
std::span<const uint8_t> data(reinterpret_cast<const uint8_t*>(segment->get_data()),
392+
segment->get_file_size());
393+
394+
// Segments may start at an address that is not aligned to the flash page size.
395+
// Page program commands may start within a page, but may wrap-around or be invalid
396+
// if going over the page boundary, so only write until we are aligned to the page size.
397+
if ((addr % flash::PageSize) != 0) {
398+
auto to_next = flash::PageSize - (addr % flash::PageSize);
399+
auto n = std::min(to_next, data.size());
400+
std::vector<uint8_t> page(data.first(n).begin(), data.first(n).end());
401+
402+
if (addr4b) {
403+
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
404+
} else if (quad) {
405+
res = this->flash.quad_page_program(addr, page);
406+
} else {
407+
res = this->flash.single_page_program_non_blocking(addr, page);
408+
}
409+
if (!res) {
410+
std::println("Program page {:#x} failed.", addr);
411+
return 0;
412+
}
413+
414+
this->flash.wait_not_busy();
415+
416+
addr += n;
417+
loaded += n;
418+
data = data.subspan(n);
419+
load_progress.update(loaded);
420+
}
421+
422+
// Now we are aligned to the flash page size, write pages as normal.
423+
while (data.size() > 0) {
424+
auto n = std::min(data.size(), std::size_t{flash::PageSize});
425+
std::vector<uint8_t> page(data.first(n).begin(), data.first(n).end());
426+
427+
this->flash.wait_not_busy();
428+
429+
if (addr4b) {
430+
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
431+
} else if (quad) {
432+
res = this->flash.quad_page_program(addr, page);
433+
} else {
434+
res = this->flash.single_page_program_non_blocking(addr, page);
435+
}
436+
if (!res) {
437+
std::println("Program page {:#x} failed.", addr);
438+
return 0;
439+
}
440+
441+
addr += n;
442+
loaded += n;
443+
data = data.subspan(n);
444+
load_progress.update(loaded);
445+
}
446+
}
447+
448+
this->flash.wait_not_busy();
449+
this->flash.write_enable(false);
450+
451+
if (bootstrap) {
452+
this->flash.reset();
453+
}
454+
455+
return 1;
456+
}
457+
};
458+
286459
template <typename T>
287460
requires embeddedpp::Gpio<T>
288461
struct GpioWrite {

app/main.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,30 @@ int main(int argc, char* argv[]) {
241241
return 0;
242242
};
243243

244+
auto bootstrap_elf_cmd = new_flash_command(
245+
"bootstrap-elf", "Write the loadable contents of an ELF file and reset the target.");
246+
bootstrap_elf_cmd->add_argument("filename").help("The file path.");
247+
bootstrap_elf_cmd->add_argument("--quad")
248+
.help("Use qSPI")
249+
.default_value(false)
250+
.implicit_value(true);
251+
bootstrap_elf_cmd->add_argument("--skip-erase")
252+
.help("Don't issue erase commands")
253+
.default_value(false)
254+
.implicit_value(true);
255+
program.add_subparser(*bootstrap_elf_cmd);
256+
commands["bootstrap-elf"] = [&]() -> int {
257+
auto pid = program.get<uint16_t>("--pid");
258+
auto filename = bootstrap_elf_cmd->get<std::string>("filename");
259+
auto quad = bootstrap_elf_cmd->get<bool>("--quad");
260+
auto skip_erase = bootstrap_elf_cmd->get<bool>("--skip-erase");
261+
auto spih = handle_flash_command(bootstrap_elf_cmd, pid);
262+
commands::LoadFileElf(flash::Generic(*spih), filename, true, skip_erase, quad).run();
263+
264+
spih->close();
265+
return 0;
266+
};
267+
244268
auto gpio_write_cmd = new_command("gpio-write", "Write a value to an FTDI GPIO pin.");
245269
gpio_write_cmd->add_argument("pin").help("GPIO pin number (0-3)").required().scan<'d', int>();
246270
gpio_write_cmd->add_argument("value").help("Value to write (0 or 1)").required().scan<'d', int>();

0 commit comments

Comments
 (0)