diff --git a/CMakeLists.txt b/CMakeLists.txt index ec55f29..1390bba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,15 +22,17 @@ if(PROJECT_IS_TOP_LEVEL) # The behavioral tests drive the real wire protocols over a simavr pty # (as the host tools do) and actually flash the device. The runners are - # host programs built at configure time against libsimavr; if they or - # Python are missing, only the size tests run. - find_program(_host_cc NAMES cc gcc) + # host programs built at configure time against libsimavr (C++23 — what + # the distribution's compiler speaks in full); if they or Python are + # missing, only the size tests run. + find_program(_host_cxx NAMES c++ g++) find_package(Python3 COMPONENTS Interpreter) - if(_host_cc AND Python3_FOUND) + if(_host_cxx AND Python3_FOUND) set(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device) execute_process( - COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts - -o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.c + COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2 + -I/usr/include/simavr -I/usr/include/simavr/parts + -o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.cpp -lsimavr -lsimavrparts -lelf -lutil RESULT_VARIABLE _pbdev_res ERROR_VARIABLE _pbdev_err) if(NOT _pbdev_res EQUAL 0) @@ -40,8 +42,9 @@ if(PROJECT_IS_TOP_LEVEL) if(LIBAVR_MCU STREQUAL "atmega328p") set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device) execute_process( - COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts - -o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.c + COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2 + -I/usr/include/simavr -I/usr/include/simavr/parts + -o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.cpp -lsimavr -lsimavrparts -lelf RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err) if(NOT _dev_res EQUAL 0) diff --git a/pureboot/README.md b/pureboot/README.md index d3b8e7a..ac90704 100644 --- a/pureboot/README.md +++ b/pureboot/README.md @@ -551,7 +551,7 @@ Per chip preset, `ctest` runs: (`tools/make_presets.py --check`), so a hand edit or a generator change cannot drift the pair apart; - `pureboot.protocol` — end to end against a simavr device - (`test/pureboot_device.c`: a hardware USART as a pty, or a cycle-timed + (`test/pureboot_device.cpp`: a hardware USART as a pty, or a cycle-timed GPIO⇄pty bridge for a software-UART build, plus the SPM/NVM module simavr's tiny cores lack) driven by the real host tool through knock-from-reset, program + verify of both memories, session reconnect, an external reset diff --git a/test/device.c b/test/device.cpp similarity index 64% rename from test/device.c rename to test/device.cpp index ecfc164..ed047d9 100644 --- a/test/device.c +++ b/test/device.cpp @@ -7,62 +7,71 @@ // SPM genuinely writes avr->flash on the mega cores, so on exit (or SIGTERM) // we dump the flash image to a file for a ground-truth cross-check against // what the client read back through the bootloader. -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include + #include +// The parts headers (uart_pty.h) carry no C++ linkage guards of their own, +// unlike simavr's core headers — the block covers both harmlessly. +extern "C" { #include "avr_uart.h" #include "sim_avr.h" #include "sim_elf.h" #include "uart_pty.h" +} -static avr_t *avr; -static uart_pty_t uart_pty; -static const char *dump_path; +namespace { -static void finish(int sig) +avr_t *avr; +uart_pty_t uart_pty; +const char *dump_path; + +[[noreturn]] void finish(int) { - (void)sig; if (dump_path) { - FILE *f = fopen(dump_path, "wb"); + std::FILE *f = std::fopen(dump_path, "wb"); if (f) { - fwrite(avr->flash, 1, avr->flashend + 1, f); - fclose(f); + std::fwrite(avr->flash, 1, avr->flashend + 1, f); + std::fclose(f); } } uart_pty_stop(&uart_pty); _exit(0); } +} // namespace + int main(int argc, char *argv[]) { if (argc < 3) { - fprintf(stderr, "usage: %s [flash_dump.bin]\n", argv[0]); + std::println(stderr, "usage: {} [flash_dump.bin]", argv[0]); return 2; } - uint32_t boot_base = (uint32_t)strtoul(argv[2], NULL, 0); - dump_path = argc >= 4 ? argv[3] : NULL; + auto boot_base = static_cast(std::strtoul(argv[2], nullptr, 0)); + dump_path = argc >= 4 ? argv[3] : nullptr; avr = avr_make_mcu_by_name("atmega328p"); if (!avr) { - fprintf(stderr, "device: no ATmega328P core\n"); + std::println(stderr, "device: no ATmega328P core"); return 1; } avr_init(avr); avr->frequency = 16000000; // Real flash powers up erased (0xff); the app region must look erased // before the bootloader programs it. - memset(avr->flash, 0xff, avr->flashend + 1); + std::memset(avr->flash, 0xff, avr->flashend + 1); // simavr's ELF loader flattens the flash base to 0 (it expects an app at // 0x0), but it hands back the boot code in fw.flash; place it at the boot // section base ourselves and enter there (BOOTRST is not modelled). - elf_firmware_t fw = {0}; + elf_firmware_t fw{}; if (elf_read_firmware(argv[1], &fw) != 0) { - fprintf(stderr, "device: cannot read %s\n", argv[1]); + std::println(stderr, "device: cannot read {}", argv[1]); return 1; } // An image that runs past flash end cannot execute on hardware, and a @@ -70,23 +79,23 @@ int main(int argc, char *argv[]) // the simulation misbehaves in ways that point everywhere but here. // Refuse it loudly instead. if (boot_base + fw.flashsize > avr->flashend + 1) { - fprintf(stderr, "device: %u B at 0x%x runs past flash end 0x%x — image does not fit its slot\n", - (unsigned)fw.flashsize, boot_base, avr->flashend); + std::println(stderr, "device: {} B at {:#x} runs past flash end {:#x} — image does not fit its slot", + fw.flashsize, boot_base, avr->flashend); return 1; } - memcpy(avr->flash + boot_base, fw.flash, fw.flashsize); + std::memcpy(avr->flash + boot_base, fw.flash, fw.flashsize); avr->pc = boot_base; avr->codeend = avr->flashend; // Optional: seed the config page (one page below the boot section) with a // hex byte string, so the password gate and emergency erase can be tested. // Layout: [appjump lo][appjump hi][timeout][password...][0xff]. - const char *cfg = getenv("TSB_CONFIG"); + const char *cfg = std::getenv("TSB_CONFIG"); if (cfg) { - uint32_t app_end = boot_base - 128; // config page sits directly below the boot code + std::uint32_t app_end = boot_base - 128; // config page sits directly below the boot code for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) { char b[3] = {cfg[i], cfg[i + 1], 0}; - avr->flash[app_end + i / 2] = (uint8_t)strtoul(b, NULL, 16); + avr->flash[app_end + i / 2] = static_cast(std::strtoul(b, nullptr, 16)); } } @@ -95,18 +104,18 @@ int main(int argc, char *argv[]) // tight-polling loader (one that releases TX between bytes, as one-wire does) // in real time, distorting protocol timing. Clear it so the loader runs at // true cycle speed. - uint32_t uflags = 0; + std::uint32_t uflags = 0; avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS('0'), &uflags); uflags &= ~AVR_UART_FLAG_POLL_SLEEP; avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS('0'), &uflags); uart_pty_init(avr, &uart_pty); uart_pty_connect(&uart_pty, '0'); - printf("TSB_PTY %s\n", uart_pty.pty.slavename); - fflush(stdout); + std::println("TSB_PTY {}", uart_pty.pty.slavename); + std::fflush(stdout); - signal(SIGTERM, finish); - signal(SIGINT, finish); + std::signal(SIGTERM, finish); + std::signal(SIGINT, finish); for (;;) { int state = avr_run(avr); @@ -114,5 +123,4 @@ int main(int argc, char *argv[]) break; } finish(0); - return 0; } diff --git a/test/pbmute.py b/test/pbmute.py index 97121a4..7d54835 100644 --- a/test/pbmute.py +++ b/test/pbmute.py @@ -10,7 +10,7 @@ The state is reached the way silicon reaches it — an application that sets up its USART and jumps in with no reset between, so nothing clears UCSRnB for it. The pin ownership itself is modelled by the device runner: simavr wires a USART through IRQs alone and never takes the pin from the port, so without -that the mute could not happen here at all (test/pureboot_device.c). +that the mute could not happen here at all (test/pureboot_device.cpp). Usage: pbmute.py diff --git a/test/pureboot_device.c b/test/pureboot_device.cpp similarity index 61% rename from test/pureboot_device.c rename to test/pureboot_device.cpp index ff37a37..5b73b95 100644 --- a/test/pureboot_device.c +++ b/test/pureboot_device.cpp @@ -23,16 +23,22 @@ // // On exit (or SIGTERM) the flash and EEPROM are dumped to files for a // ground-truth cross-check against what the host read back. +#include +#include +#include +#include +#include +#include +#include + #include #include -#include -#include -#include -#include -#include #include #include +// The parts headers (uart_pty.h) carry no C++ linkage guards of their own, +// unlike simavr's core headers — the block covers both harmlessly. +extern "C" { #include "avr_eeprom.h" #include "avr_flash.h" #include "avr_ioport.h" @@ -41,31 +47,35 @@ #include "sim_elf.h" #include "sim_io.h" #include "uart_pty.h" +} -static avr_t *avr; -static uart_pty_t uart_pty; -static int link_software; -static char uart_digit = '0'; -static char sw_rx_port = 'B', sw_tx_port = 'B'; -static int sw_rx_bit = 0, sw_tx_bit = 1; -static char sw_tx_owner = 0; // the USART whose TXD the software link sits on -static const char *dump_path; -static uint32_t reset_pc; -static volatile sig_atomic_t reset_requested; +namespace { -static int parse_link(const char *spec) +avr_t *avr; +uart_pty_t uart_pty; +bool link_software; +char uart_digit = '0'; +char sw_rx_port = 'B', sw_tx_port = 'B'; +int sw_rx_bit = 0, sw_tx_bit = 1; +char sw_tx_owner = 0; // the USART whose TXD the software link sits on +const char *dump_path; +std::uint32_t reset_pc; +volatile std::sig_atomic_t reset_requested; + +int parse_link(std::string_view spec) { - if (strcmp(spec, "usart0") == 0 || strcmp(spec, "usart1") == 0) { - link_software = 0; + if (spec == "usart0" || spec == "usart1") { + link_software = false; uart_digit = spec[5]; return 0; } - if (strncmp(spec, "sw", 2) == 0) { - link_software = 1; - if (spec[2] == '\0') + if (spec.starts_with("sw")) { + link_software = true; + if (spec.size() == 2) return 0; char owner = 0; - int fields = sscanf(spec + 2, ":%c%d,%c%d@%c", &sw_rx_port, &sw_rx_bit, &sw_tx_port, &sw_tx_bit, &owner); + int fields = + std::sscanf(spec.data() + 2, ":%c%d,%c%d@%c", &sw_rx_port, &sw_rx_bit, &sw_tx_port, &sw_tx_bit, &owner); if (fields == 4 || fields == 5) { sw_tx_owner = owner; return 0; @@ -87,19 +97,19 @@ static int parse_link(const char *spec) // core — so the discard store falls through into the buffer-fill branch and // plants whatever Z/R1:R0 happen to hold. Perform the silicon's discard // here instead. -static avr_flash_t *mega_flash; -static int (*mega_flash_ioctl)(avr_io_t *io, uint32_t ctl, void *param); +avr_flash_t *mega_flash; +int (*mega_flash_ioctl)(avr_io_t *io, std::uint32_t ctl, void *param); -static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param) +int fixed_flash_ioctl(avr_io_t *io, std::uint32_t ctl, void *param) { if (ctl == AVR_IOCTL_FLASH_SPM && avr_regbit_get(io->avr, mega_flash->pgers)) { - uint16_t z = (uint16_t)(io->avr->data[30] | (io->avr->data[31] << 8)); - uint16_t masked = (uint16_t)(z & ~(mega_flash->spm_pagesize - 1)); - io->avr->data[30] = (uint8_t)masked; - io->avr->data[31] = (uint8_t)(masked >> 8); + auto z = static_cast(io->avr->data[30] | (io->avr->data[31] << 8)); + auto masked = static_cast(z & ~(mega_flash->spm_pagesize - 1)); + io->avr->data[30] = static_cast(masked); + io->avr->data[31] = static_cast(masked >> 8); int result = mega_flash_ioctl(io, ctl, param); - io->avr->data[30] = (uint8_t)z; - io->avr->data[31] = (uint8_t)(z >> 8); + io->avr->data[30] = static_cast(z); + io->avr->data[31] = static_cast(z >> 8); return result; } if (ctl == AVR_IOCTL_FLASH_SPM && !(mega_flash->flags & AVR_SELFPROG_HAVE_RWW) && @@ -114,46 +124,44 @@ static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param) return mega_flash_ioctl(io, ctl, param); } -static void fix_mega_flash_erase(void) +void fix_mega_flash_erase() { for (avr_io_t *io = avr->io_port; io; io = io->next) { - if (io->kind && strcmp(io->kind, "flash") == 0) { - mega_flash = (avr_flash_t *)io; + if (io->kind && std::string_view{io->kind} == "flash") { + mega_flash = reinterpret_cast(io); mega_flash_ioctl = io->ioctl; io->ioctl = fixed_flash_ioctl; return; } } - fprintf(stderr, "device: no flash module to fix — SPM page erases may misalign\n"); + std::println(stderr, "device: no flash module to fix — SPM page erases may misalign"); } -static void request_reset(int sig) +void request_reset(int) { - (void)sig; reset_requested = 1; } // ------------------------------------------------------------- tiny NVM --- -typedef struct { +struct tiny_nvm_t { avr_io_t io; - uint8_t buffer[128]; - uint8_t used[128]; // a buffer word loads once until erased — like silicon + std::uint8_t buffer[128]; + std::uint8_t used[128]; // a buffer word loads once until erased — like silicon unsigned page; -} tiny_nvm_t; +}; -static tiny_nvm_t nvm; +tiny_nvm_t nvm; -static int nvm_ioctl(avr_io_t *io, uint32_t ctl, void *param) +int nvm_ioctl(avr_io_t *io, std::uint32_t ctl, void *) { - (void)param; if (ctl != AVR_IOCTL_FLASH_SPM) return -1; - tiny_nvm_t *n = (tiny_nvm_t *)io; + auto *n = reinterpret_cast(io); avr_t *mcu = io->avr; - uint8_t command = mcu->data[0x57] & 0x1f; // SPMCSR, both tinies - uint16_t z = (uint16_t)(mcu->data[30] | (mcu->data[31] << 8)); - uint32_t page_base = (uint32_t)(z & ~(n->page - 1)) % (mcu->flashend + 1); + std::uint8_t command = mcu->data[0x57] & 0x1f; // SPMCSR, both tinies + auto z = static_cast(mcu->data[30] | (mcu->data[31] << 8)); + std::uint32_t page_base = static_cast(z & ~(n->page - 1)) % (mcu->flashend + 1); if (command == 0x01) { // SPMEN alone: buffer fill from r1:r0 unsigned offset = z & (n->page - 1) & ~1u; if (!n->used[offset]) { // first write wins until the buffer clears @@ -162,46 +170,44 @@ static int nvm_ioctl(avr_io_t *io, uint32_t ctl, void *param) n->used[offset] = 1; } } else if (command == 0x03) { // PGERS - memset(mcu->flash + page_base, 0xff, n->page); + std::memset(mcu->flash + page_base, 0xff, n->page); } else if (command == 0x05) { // PGWRT: programming only clears bits for (unsigned i = 0; i < n->page; i++) mcu->flash[page_base + i] &= n->buffer[i]; - memset(n->buffer, 0xff, n->page); - memset(n->used, 0, n->page); + std::memset(n->buffer, 0xff, n->page); + std::memset(n->used, 0, n->page); } else if (command == 0x11) { // CTPB - memset(n->buffer, 0xff, n->page); - memset(n->used, 0, n->page); + std::memset(n->buffer, 0xff, n->page); + std::memset(n->used, 0, n->page); } - mcu->data[0x57] &= (uint8_t)~0x1f; // the operation completes instantly + mcu->data[0x57] &= static_cast(~0x1f); // the operation completes instantly return 0; } // ----------------------------------------------------------- GPIO bridge --- -static int pty_master = -1; -static avr_irq_t *rx_pin; // the loader's RX (PB0), driven from the pty -static avr_cycle_count_t bit_cycles; +int pty_master = -1; +avr_irq_t *rx_pin; // the loader's RX (PB0), driven from the pty +avr_cycle_count_t bit_cycles; -static int tx_level = 1, tx_active, tx_bit; -static uint8_t tx_shift; +int tx_level = 1, tx_active, tx_bit; +std::uint8_t tx_shift; -static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *param) +avr_cycle_count_t tx_sample(avr_t *, avr_cycle_count_t when, void *) { - (void)mcu; - (void)param; if (tx_bit < 8) { - tx_shift = (uint8_t)((tx_shift >> 1) | (tx_level ? 0x80 : 0)); + tx_shift = static_cast((tx_shift >> 1) | (tx_level ? 0x80 : 0)); if (++tx_bit < 8) return when + bit_cycles; - /* The byte is delivered at the stop bit's sampling point (9.5 bit - * times), where a hardware receiver raises its RXC — not sooner: a - * host answering before the stop bit would put its start bit on the - * wire while the device is still driving, which the device, - * transmitting, is not watching for. */ + // The byte is delivered at the stop bit's sampling point (9.5 bit + // times), where a hardware receiver raises its RXC — not sooner: a + // host answering before the stop bit would put its start bit on the + // wire while the device is still driving, which the device, + // transmitting, is not watching for. return when + bit_cycles; } if (write(pty_master, &tx_shift, 1) != 1) - fprintf(stderr, "device: pty write lost a byte\n"); + std::println(stderr, "device: pty write lost a byte"); tx_active = 0; return 0; } @@ -213,9 +219,9 @@ static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *par // model, so the ownership does not exist there and the mute cannot happen: // supply it, or the very state this models is untestable. The link spec's // trailing @n names the USART; without one the pins are nobody's. -static avr_uart_t *tx_owner; +avr_uart_t *tx_owner; -static int tx_pin_taken(void) +bool tx_pin_taken() { return tx_owner && avr_regbit_get(avr, tx_owner->txen); } @@ -225,27 +231,26 @@ static int tx_pin_taken(void) // enabled, making a freshly reset chip mute for reasons hardware does not // have. Reset it the way the datasheet does, so the ownership starts from // nobody's and only an application that really enables the USART takes it. -static void reset_tx_owner(void) +void reset_tx_owner() { if (tx_owner) avr_regbit_clear(avr, tx_owner->txen); } -static void find_tx_owner(void) +void find_tx_owner() { for (avr_io_t *io = avr->io_port; io; io = io->next) - if (io->kind && strcmp(io->kind, "uart") == 0 && ((avr_uart_t *)io)->name == sw_tx_owner) { - tx_owner = (avr_uart_t *)io; + if (io->kind && std::string_view{io->kind} == "uart" && + reinterpret_cast(io)->name == sw_tx_owner) { + tx_owner = reinterpret_cast(io); reset_tx_owner(); return; } - fprintf(stderr, "device: no USART%c to own the software link's TX pin\n", sw_tx_owner); + std::println(stderr, "device: no USART{} to own the software link's TX pin", sw_tx_owner); } -static void tx_hook(avr_irq_t *irq, uint32_t value, void *param) +void tx_hook(avr_irq_t *, std::uint32_t value, void *) { - (void)irq; - (void)param; if (tx_pin_taken()) { // the USART holds the line; the port write goes nowhere tx_level = 1; return; @@ -254,22 +259,20 @@ static void tx_hook(avr_irq_t *irq, uint32_t value, void *param) if (!tx_active && tx_level == 1 && level == 0) { // start edge tx_active = 1; tx_bit = 0; - avr_cycle_timer_register(avr, bit_cycles + bit_cycles / 2, tx_sample, NULL); + avr_cycle_timer_register(avr, bit_cycles + bit_cycles / 2, tx_sample, nullptr); } tx_level = level; } -static uint8_t rx_queue[8192]; -static unsigned rx_head, rx_tail; // ring: head = next to send -static int rx_active, rx_bit; -static uint8_t rx_byte; +std::uint8_t rx_queue[8192]; +unsigned rx_head, rx_tail; // ring: head = next to send +int rx_active, rx_bit; +std::uint8_t rx_byte; -static void rx_start_next(void); +void rx_start_next(); -static avr_cycle_count_t rx_step(avr_t *mcu, avr_cycle_count_t when, void *param) +avr_cycle_count_t rx_step(avr_t *, avr_cycle_count_t when, void *) { - (void)mcu; - (void)param; if (rx_bit < 8) { avr_raise_irq(rx_pin, (rx_byte >> rx_bit) & 1); rx_bit++; @@ -285,7 +288,7 @@ static avr_cycle_count_t rx_step(avr_t *mcu, avr_cycle_count_t when, void *param return 0; } -static void rx_start_next(void) +void rx_start_next() { if (rx_active || rx_head == rx_tail) return; @@ -294,7 +297,7 @@ static void rx_start_next(void) rx_active = 1; rx_bit = 0; avr_raise_irq(rx_pin, 0); // start bit - avr_cycle_timer_register(avr, bit_cycles, rx_step, NULL); + avr_cycle_timer_register(avr, bit_cycles, rx_step, nullptr); } // A reset abandons whatever the bridge was mid-transfer: bytes still queued @@ -304,10 +307,10 @@ static void rx_start_next(void) // output latch, whose falling edge starts a spurious decode before this // runs, and a stale tx_sample would then interleave with the loader's first // real answer through the shared shift state, corrupting it. -static void bridge_reset(void) +void bridge_reset() { - avr_cycle_timer_cancel(avr, tx_sample, NULL); - avr_cycle_timer_cancel(avr, rx_step, NULL); + avr_cycle_timer_cancel(avr, tx_sample, nullptr); + avr_cycle_timer_cancel(avr, rx_step, nullptr); rx_head = rx_tail = 0; rx_active = 0; tx_active = 0; @@ -315,9 +318,9 @@ static void bridge_reset(void) avr_raise_irq(rx_pin, 1); // idle line } -static void poll_pty(void) +void poll_pty() { - uint8_t chunk[256]; + std::uint8_t chunk[256]; ssize_t got = read(pty_master, chunk, sizeof(chunk)); for (ssize_t i = 0; i < got; i++) { unsigned next = (rx_tail + 1) % sizeof(rx_queue); @@ -332,23 +335,22 @@ static void poll_pty(void) // ------------------------------------------------------------------ main --- -static void finish(int sig) +[[noreturn]] void finish(int) { - (void)sig; if (dump_path) { - FILE *f = fopen(dump_path, "wb"); + std::FILE *f = std::fopen(dump_path, "wb"); if (f) { - fwrite(avr->flash, 1, avr->flashend + 1, f); - fclose(f); + std::fwrite(avr->flash, 1, avr->flashend + 1, f); + std::fclose(f); } - avr_eeprom_desc_t ee = {.ee = NULL, .offset = 0, .size = 0}; + avr_eeprom_desc_t ee = {.ee = nullptr, .offset = 0, .size = 0}; if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &ee) == 0 && ee.ee && ee.size) { char path[512]; - snprintf(path, sizeof(path), "%s.eeprom", dump_path); - f = fopen(path, "wb"); + std::snprintf(path, sizeof(path), "%s.eeprom", dump_path); + f = std::fopen(path, "wb"); if (f) { - fwrite(ee.ee, 1, ee.size, f); - fclose(f); + std::fwrite(ee.ee, 1, ee.size, f); + std::fclose(f); } } } @@ -357,85 +359,87 @@ static void finish(int sig) _exit(0); } +} // namespace + int main(int argc, char *argv[]) { - int link_given = 0; + bool link_given = false; for (int opt; (opt = getopt(argc, argv, "l:")) != -1;) { if (opt != 'l' || parse_link(optarg) != 0) { - fprintf(stderr, "device: bad link spec (usart0, usart1, sw, or sw:B0,B1 as RX,TX)\n"); + std::println(stderr, "device: bad link spec (usart0, usart1, sw, or sw:B0,B1 as RX,TX)"); return 2; } - link_given = 1; + link_given = true; } int args = argc - optind; if (args < 7 || args > 9) { - fprintf(stderr, - "usage: %s [-l link] " - " [reset_hex] [resume_flash]\n" - " -l link: usart0 | usart1 | sw[:B0,B1[@0]] (RX,TX, then the USART owning\n" - " them); default: the chip's own\n" - " reset_hex: reset vector (default: base with a boot section, else 0)\n" - " resume_flash: raw full-flash image loaded instead of the ELF — a prior\n" - " run's dump, for power-fail resume tests\n", - argv[0]); + std::print(stderr, + "usage: {} [-l link] " + " [reset_hex] [resume_flash]\n" + " -l link: usart0 | usart1 | sw[:B0,B1[@0]] (RX,TX, then the USART owning\n" + " them); default: the chip's own\n" + " reset_hex: reset vector (default: base with a boot section, else 0)\n" + " resume_flash: raw full-flash image loaded instead of the ELF — a prior\n" + " run's dump, for power-fail resume tests\n", + argv[0]); return 2; } argv += optind - 1; // argv[1] is the ELF again, whatever was parsed - const char *mcu_name = argv[2]; - uint32_t base = (uint32_t)strtoul(argv[4], NULL, 0); - unsigned page = (unsigned)atoi(argv[5]); - unsigned baud = (unsigned)atoi(argv[6]); + const std::string_view mcu_name = argv[2]; + auto base = static_cast(std::strtoul(argv[4], nullptr, 0)); + auto page = static_cast(std::atoi(argv[5])); + auto baud = static_cast(std::atoi(argv[6])); dump_path = argv[7]; - int is_mega = strncmp(mcu_name, "atmega", 6) == 0; + const bool is_mega = mcu_name.starts_with("atmega"); if (!link_given) link_software = !is_mega; // the chips' natural links: USART0, or PB0/PB1 - avr = avr_make_mcu_by_name(mcu_name); + avr = avr_make_mcu_by_name(mcu_name.data()); if (!avr) { - fprintf(stderr, "device: no %s core\n", mcu_name); + std::println(stderr, "device: no {} core", mcu_name); return 1; } avr_init(avr); - avr->frequency = (uint32_t)strtoul(argv[3], NULL, 0); - memset(avr->flash, 0xff, avr->flashend + 1); // real flash powers up erased + avr->frequency = static_cast(std::strtoul(argv[3], nullptr, 0)); + std::memset(avr->flash, 0xff, avr->flashend + 1); // real flash powers up erased if (args > 8) { // Resume: the full flash image of an interrupted prior run. - FILE *f = fopen(argv[9], "rb"); - if (!f || fread(avr->flash, 1, avr->flashend + 1, f) == 0) { - fprintf(stderr, "device: cannot read %s\n", argv[9]); + std::FILE *f = std::fopen(argv[9], "rb"); + if (!f || std::fread(avr->flash, 1, avr->flashend + 1, f) == 0) { + std::println(stderr, "device: cannot read {}", argv[9]); return 1; } - fclose(f); + std::fclose(f); } else { - elf_firmware_t fw = {0}; + elf_firmware_t fw{}; if (elf_read_firmware(argv[1], &fw) != 0) { - fprintf(stderr, "device: cannot read %s\n", argv[1]); + std::println(stderr, "device: cannot read {}", argv[1]); return 1; } // An image past flash end would smash the simulator's heap and turn // into phantom peripheral behavior (lessons: believe the size gate // first) — refuse it loudly instead. if (base + fw.flashsize > avr->flashend + 1) { - fprintf(stderr, "device: %u B at 0x%x runs past flash end 0x%x — image does not fit its slot\n", - (unsigned)fw.flashsize, base, avr->flashend); + std::println(stderr, "device: {} B at {:#x} runs past flash end {:#x} — image does not fit its slot", + fw.flashsize, base, avr->flashend); return 1; } - memcpy(avr->flash + base, fw.flash, fw.flashsize); + std::memcpy(avr->flash + base, fw.flash, fw.flashsize); } // The boot-sectioned megas enter the loader in hardware (BOOTRST, not // modeled — the argument picks the modeled fuse's target); the tinies // and the boot-section-less m48s reset to word 0 like silicon — erased // flash walks up into the loader, and after the host's surgery the // patched vector routes there. - int boot_section = is_mega && strncmp(mcu_name, "atmega48", 8) != 0; - reset_pc = args > 7 ? (uint32_t)strtoul(argv[8], NULL, 0) : (boot_section ? base : 0); + const bool boot_section = is_mega && !mcu_name.starts_with("atmega48"); + reset_pc = args > 7 ? static_cast(std::strtoul(argv[8], nullptr, 0)) : (boot_section ? base : 0); avr->pc = reset_pc; avr->codeend = avr->flashend; // Erased EEPROM, as hardware powers up (simavr zeroes it). - uint8_t blank[1024]; - memset(blank, 0xff, sizeof(blank)); + std::uint8_t blank[1024]; + std::memset(blank, 0xff, sizeof(blank)); avr_eeprom_desc_t seed = {.ee = blank, .offset = 0, .size = 0}; if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &seed) == 0 && seed.size <= sizeof(blank)) { seed.ee = blank; @@ -449,7 +453,7 @@ int main(int argc, char *argv[]) fix_mega_flash_erase(); } else { nvm.page = page; - memset(nvm.buffer, 0xff, sizeof(nvm.buffer)); + std::memset(nvm.buffer, 0xff, sizeof(nvm.buffer)); nvm.io.kind = "tiny_nvm"; nvm.io.ioctl = nvm_ioctl; avr_register_io(avr, &nvm.io); @@ -458,37 +462,38 @@ int main(int argc, char *argv[]) if (!link_software) { // POLL_SLEEP paces an idle-polling loader in host real time (a // no-hardware CPU-saving hack); clear it so cycles run free. - uint32_t flags = 0; + std::uint32_t flags = 0; avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags); flags &= ~AVR_UART_FLAG_POLL_SLEEP; avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags); uart_pty_init(avr, &uart_pty); uart_pty_connect(&uart_pty, uart_digit); - printf("PB_PTY %s\n", uart_pty.pty.slavename); + std::println("PB_PTY {}", uart_pty.pty.slavename); } else { bit_cycles = (avr->frequency + baud / 2) / baud; // matches uart.hpp's own rounding exactly if (sw_tx_owner) find_tx_owner(); - rx_pin = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_rx_port), (unsigned)sw_rx_bit); - avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), (unsigned)sw_tx_bit), tx_hook, - NULL); + rx_pin = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_rx_port), static_cast(sw_rx_bit)); + avr_irq_register_notify( + avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), static_cast(sw_tx_bit)), tx_hook, + nullptr); avr_raise_irq(rx_pin, 1); // idle line int slave; struct termios raw; cfmakeraw(&raw); - if (openpty(&pty_master, &slave, NULL, &raw, NULL) != 0) { - fprintf(stderr, "device: openpty failed\n"); + if (openpty(&pty_master, &slave, nullptr, &raw, nullptr) != 0) { + std::println(stderr, "device: openpty failed"); return 1; } fcntl(pty_master, F_SETFL, O_NONBLOCK); - printf("PB_PTY %s\n", ttyname(slave)); + std::println("PB_PTY {}", ttyname(slave)); } - fflush(stdout); + std::fflush(stdout); - signal(SIGTERM, finish); - signal(SIGINT, finish); - signal(SIGUSR1, request_reset); // an external reset line, for the tests + std::signal(SIGTERM, finish); + std::signal(SIGINT, finish); + std::signal(SIGUSR1, request_reset); // an external reset line, for the tests long since_poll = 0; for (;;) { @@ -500,7 +505,7 @@ int main(int argc, char *argv[]) avr_reset(avr); avr->pc = reset_pc; if (!link_software) { // reset restores the pacing hack; re-clear it - uint32_t flags = 0; + std::uint32_t flags = 0; avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags); flags &= ~AVR_UART_FLAG_POLL_SLEEP; avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags); @@ -524,5 +529,4 @@ int main(int argc, char *argv[]) } } finish(0); - return 0; }