Skip to content

Commit 0dc0317

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

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

app/commands.hh

Lines changed: 176 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,181 @@ 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+
auto num_segments = reader.segments.size();
310+
for (auto i = 0; i < num_segments; i++) {
311+
const auto segment = reader.segments[i];
312+
if (segment->get_type() == ELFIO::PT_LOAD) {
313+
load_segments.push_back(segment);
314+
}
315+
}
316+
317+
bool addr4b = false;
318+
auto erase_size = 0;
319+
auto load_size = 0;
320+
for (auto segment : load_segments) {
321+
auto phys_start = segment->get_physical_address();
322+
auto phys_end = phys_start + segment->get_memory_size();
323+
auto sector_start = phys_start - (phys_start % flash::SectorSize);
324+
auto sector_end = phys_end + (flash::SectorSize - (phys_end % flash::SectorSize));
325+
load_size += segment->get_file_size();
326+
erase_size += (sector_end - sector_start);
327+
if (phys_start > 0xFFFFFF || phys_end > 0xFFFFFF) {
328+
addr4b = true;
329+
}
330+
}
331+
332+
if (!bootstrap) {
333+
this->flash.reset();
334+
}
335+
if (addr4b && !this->flash.enter_4b_addr()) {
336+
std::println("Enter 4-byte address mode failed");
337+
return 0;
338+
}
339+
if (quad && !this->flash.enable_quad(true)) {
340+
std::println("enable quad failed");
341+
return 0;
342+
}
343+
344+
this->flash.write_enable(true);
345+
346+
std::optional<bool> res;
347+
348+
// The length of the data to be loaded into memory (file size) may be less than
349+
// the size of the segment in memory (memory size), in which case the excess represents
350+
// zero-initialised data (e.g .bss section). Therefore, the memory size is used for
351+
// erasing the flash, and then the file resident data is loaded into it, which may be
352+
// shorter.
353+
354+
// Erase sectors containing segment data.
355+
if (!skip_erase) {
356+
auto erased = 0;
357+
auto erase_progress = ProgressBar(erase_size, 50, "Erasing").with_throughput();
358+
for (auto segment : load_segments) {
359+
auto phys_start = segment->get_physical_address();
360+
auto phys_end = phys_start + segment->get_memory_size();
361+
// Align to sector size
362+
auto sector_start = phys_start - (phys_start % flash::SectorSize);
363+
auto sector_end = phys_end + (flash::SectorSize - (phys_end % flash::SectorSize));
364+
365+
auto addr = sector_start;
366+
while (addr < sector_end) {
367+
res = addr4b ? this->flash.template erase<4, flash::Opcode::SectorErase4b>(addr)
368+
: this->flash.erase(addr);
369+
if (!res) {
370+
std::println("Failed to erase block {:#x}", addr);
371+
return 0;
372+
}
373+
374+
this->flash.wait_not_busy();
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> first_n(data.first(n).begin(), data.first(n).end());
400+
401+
if (addr4b) {
402+
res = this->flash.template single_page_program_non_blocking<4>(addr, first_n);
403+
} else if (quad) {
404+
res = this->flash.quad_page_program(addr, first_n);
405+
} else {
406+
res = this->flash.single_page_program_non_blocking(addr, first_n);
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::array<uint8_t, flash::PageSize> page{0xff};
425+
std::copy_n(data.begin(), n, page.begin());
426+
427+
if (addr4b) {
428+
res = this->flash.template single_page_program_non_blocking<4>(addr, page);
429+
} else if (quad) {
430+
res = this->flash.quad_page_program(addr, page);
431+
} else {
432+
res = this->flash.single_page_program_non_blocking(addr, page);
433+
}
434+
if (!res) {
435+
std::println("Program page {:#x} failed.", addr);
436+
return 0;
437+
}
438+
439+
this->flash.wait_not_busy();
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+
return 1;
454+
}
455+
456+
// TODO: Verifying loaded ELF files is not yet supported.
457+
std::println("Verifying loaded ELF files is not yet supported!");
458+
return 0;
459+
}
460+
};
461+
286462
template <typename T>
287463
requires embeddedpp::Gpio<T>
288464
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)