Skip to content

Commit 3370378

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

2 files changed

Lines changed: 198 additions & 0 deletions

File tree

app/commands.hh

Lines changed: 174 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,179 @@ 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(&*segment);
312+
}
313+
}
314+
315+
bool addr4b = false;
316+
auto erase_size = 0;
317+
auto load_size = 0;
318+
for (auto segment : load_segments) {
319+
auto phys_start = segment->get_physical_address();
320+
auto phys_end = phys_start + segment->get_memory_size();
321+
auto sector_start = phys_start - (phys_start % flash::SectorSize);
322+
auto sector_end = phys_end + (flash::SectorSize - (phys_end % flash::SectorSize));
323+
load_size += segment->get_file_size();
324+
erase_size += (sector_end - sector_start);
325+
if (phys_start > 0xFFFFFF || phys_end > 0xFFFFFF) {
326+
addr4b = true;
327+
}
328+
}
329+
330+
if (!bootstrap) {
331+
this->flash.reset();
332+
}
333+
if (addr4b && !this->flash.enter_4b_addr()) {
334+
std::println("Enter 4-byte address mode failed");
335+
return 0;
336+
}
337+
if (quad && !this->flash.enable_quad(true)) {
338+
std::println("enable quad failed");
339+
return 0;
340+
}
341+
342+
this->flash.write_enable(true);
343+
344+
std::optional<bool> res;
345+
346+
// The length of the data to be loaded into memory (file size) may be less than
347+
// the size of the segment in memory (memory size), in which case the excess represents
348+
// zero-initialised data (e.g .bss section). Therefore, the memory size is used for
349+
// erasing the flash, and then the file resident data is loaded into it, which may be
350+
// shorter.
351+
352+
// Erase sectors containing segment data.
353+
if (!skip_erase) {
354+
auto erased = 0;
355+
auto erase_progress = ProgressBar(erase_size, 50, "Erasing").with_throughput();
356+
for (auto segment : load_segments) {
357+
auto phys_start = segment->get_physical_address();
358+
auto phys_end = phys_start + segment->get_memory_size();
359+
// Align to sector size
360+
auto sector_start = phys_start & ~(flash::SectorSize - 1);
361+
auto sector_end = (phys_end + flash::SectorSize - 1) & ~(flash::SectorSize - 1);
362+
363+
auto addr = sector_start;
364+
while (addr < sector_end) {
365+
res = addr4b ? this->flash.template erase<4, flash::Opcode::SectorErase4b>(addr)
366+
: this->flash.erase(addr);
367+
if (!res) {
368+
std::println("Failed to erase block {:#x}", addr);
369+
return 0;
370+
}
371+
372+
this->flash.wait_not_busy();
373+
374+
addr += flash::SectorSize;
375+
erased += flash::SectorSize;
376+
erase_progress.update(erased);
377+
}
378+
}
379+
}
380+
381+
this->flash.wait_not_busy();
382+
383+
// Then, load the segment data from the file.
384+
auto loaded = 0;
385+
auto load_progress = ProgressBar(load_size, 50, "Loading").with_throughput();
386+
for (auto segment : load_segments) {
387+
auto addr = segment->get_physical_address();
388+
std::span<const uint8_t> data(reinterpret_cast<const uint8_t*>(segment->get_data()),
389+
segment->get_file_size());
390+
391+
// Segments may start at an address that is not aligned to the flash page size.
392+
// Page program commands may start within a page, but may wrap-around or be invalid
393+
// if going over the page boundary, so only write until we are aligned to the page size.
394+
if ((addr % flash::PageSize) != 0) {
395+
auto to_next = flash::PageSize - (addr % flash::PageSize);
396+
auto n = std::min(to_next, data.size());
397+
std::vector<uint8_t> first_n(data.first(n).begin(), data.first(n).end());
398+
399+
if (addr4b) {
400+
res = this->flash.template single_page_program_non_blocking<4>(addr, first_n);
401+
} else if (quad) {
402+
res = this->flash.quad_page_program(addr, first_n);
403+
} else {
404+
res = this->flash.single_page_program_non_blocking(addr, first_n);
405+
}
406+
if (!res) {
407+
std::println("Program page {:#x} failed.", addr);
408+
return 0;
409+
}
410+
411+
this->flash.wait_not_busy();
412+
413+
addr += n;
414+
loaded += n;
415+
data = data.subspan(n);
416+
load_progress.update(loaded);
417+
}
418+
419+
// Now we are aligned to the flash page size, write pages as normal.
420+
while (data.size() > 0) {
421+
auto n = std::min(data.size(), std::size_t{flash::PageSize});
422+
std::array<uint8_t, flash::PageSize> page{0xff};
423+
std::copy_n(data.begin(), n, page.begin());
424+
425+
if (addr4b) {
426+
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
427+
} else if (quad) {
428+
res = this->flash.quad_page_program(addr, page);
429+
} else {
430+
res = this->flash.single_page_program_non_blocking(addr, page);
431+
}
432+
if (!res) {
433+
std::println("Program page {:#x} failed.", addr);
434+
return 0;
435+
}
436+
437+
this->flash.wait_not_busy();
438+
439+
addr += n;
440+
loaded += n;
441+
data = data.subspan(n);
442+
load_progress.update(loaded);
443+
}
444+
}
445+
446+
this->flash.wait_not_busy();
447+
this->flash.write_enable(false);
448+
449+
if (bootstrap) {
450+
this->flash.reset();
451+
return 1;
452+
}
453+
454+
// TODO: Verifying loaded ELF files is not yet supported.
455+
std::println("Verifying loaded ELF files is not yet supported!");
456+
return 0;
457+
}
458+
};
459+
286460
template <typename T>
287461
requires embeddedpp::Gpio<T>
288462
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)