|
12 | 12 | #include <iostream> |
13 | 13 | #include <picosha2.h> |
14 | 14 | #include <print> |
| 15 | +#include <elfio/elfio.hpp> |
15 | 16 |
|
16 | 17 | namespace commands { |
17 | 18 |
|
@@ -282,6 +283,178 @@ struct LoadFile : public Commands<T> { |
282 | 283 | } |
283 | 284 | }; |
284 | 285 |
|
| 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 | + |
285 | 458 | template <typename T> |
286 | 459 | requires embeddedpp::Gpio<T> |
287 | 460 | struct GpioWrite { |
|
0 commit comments