Skip to content

Commit bc0ac1d

Browse files
ziuziakowskaengdoreis
authored andcommitted
command: implement ELF loader command
Signed-off-by: Alice Ziuziakowska <a.ziuziakowska@lowrisc.org>
1 parent 2d0aa1a commit bc0ac1d

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

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

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