test: the device runners speak the C++ the rest of the repo does
pureboot_device and the tsb device, C until now, rewritten in C++23 with every modeled behavior intact — the PGERS Z-mask and m48-discard ioctl wraps, the GPIO bridge's timing and pacing, the tiny NVM's write-once buffer, pin ownership, and the PB_PTY/TSB_PTY lines the harnesses parse. The one linkage fact worth a comment: simavr's parts headers (uart_pty.h) carry no C++ guards where its core headers do, so those includes sit in an extern "C" block. Warning-clean at -Wall -Wextra on the build line; the full protocol suites on all four sim-driven chips prove the conversion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <print>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
// 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 <tsb.elf> <boot_base_hex> [flash_dump.bin]\n", argv[0]);
|
||||
std::println(stderr, "usage: {} <tsb.elf> <boot_base_hex> [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::uint32_t>(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::uint8_t>(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;
|
||||
}
|
||||
@@ -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 <device_bin> <pureboot_elf> <mcu> <hz> <base_hex> <page>
|
||||
<baud> <app_bin> <tool_py> <workdir> <link>
|
||||
|
||||
@@ -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 <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <print>
|
||||
#include <string_view>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pty.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// 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<std::uint16_t>(io->avr->data[30] | (io->avr->data[31] << 8));
|
||||
auto masked = static_cast<std::uint16_t>(z & ~(mega_flash->spm_pagesize - 1));
|
||||
io->avr->data[30] = static_cast<std::uint8_t>(masked);
|
||||
io->avr->data[31] = static_cast<std::uint8_t>(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<std::uint8_t>(z);
|
||||
io->avr->data[31] = static_cast<std::uint8_t>(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<avr_flash_t *>(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<tiny_nvm_t *>(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<std::uint16_t>(mcu->data[30] | (mcu->data[31] << 8));
|
||||
std::uint32_t page_base = static_cast<std::uint32_t>(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<std::uint8_t>(~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<std::uint8_t>((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<avr_uart_t *>(io)->name == sw_tx_owner) {
|
||||
tx_owner = reinterpret_cast<avr_uart_t *>(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] <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
||||
" [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] <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
||||
" [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::uint32_t>(std::strtoul(argv[4], nullptr, 0));
|
||||
auto page = static_cast<unsigned>(std::atoi(argv[5]));
|
||||
auto baud = static_cast<unsigned>(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::uint32_t>(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::uint32_t>(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<unsigned>(sw_rx_bit));
|
||||
avr_irq_register_notify(
|
||||
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), static_cast<unsigned>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user