# Port: AVR (ATmega328P, `arch/avr`) **Specification:** SPEC-012 (port), SPEC-011 (contract), SPEC-002 §13 and SPEC-003 §13 (AVR rows). **Status (M1):** periodic tick, context switch, `EMB_ISR` prologue and epilogue, critical sections, interrupt mask table, idle, faults by halt or watchdog reset. Tickless mode, the `.noinit` crash record, and the generated linker script come with later milestones; the configuration refuses `CONFIG_EMB_TICKLESS` on this port until then. **Boards:** `arduino_uno` (ATmega328P at 16 MHz, USART0 console at 115200, LED on PB5, Optiboot keeps the top 512 bytes of flash). **Verified on:** QEMU `arduino-uno` machine (SIM-002): the first-execution sequence and the conformance suite. No hardware run yet: every figure below marked *measured* comes from the toolchain's output, not from a board, and no latency number is published (SIM-003). ## Context frame One layout serves the switch, the interrupt epilogue, and the initial frame (SPEC-012 §4.1), 35 bytes from the saved stack pointer upward: | offset from saved SP | content | |---|---| | +1 .. +30 | r31 down to r2 (the push order is r2 first, r31 last) | | +31 | r1 (zero register) | | +32 | SREG (I bit: the context's own state; forced set by the ISR prologue) | | +33 | r0 | | +34 | return address high byte | | +35 | return address low byte | The manifest budgets 37 bytes per context (`stack.context_frame`), two more than the frame for the return address of the call into the switch routine. The initial frame returns into `embk_thread_launch(entry, arg)` with `entry` in r24:r25 and `arg` in r22:r23, which calls `entry(arg)` and exits the thread with code 0 (KRN-THR-005). ## Switch and interrupt exit - `emb_arch_switch_to(next)` is a naked routine: push the frame with `cli` after reading SREG, store SP into the outgoing thread's `arch.sp`, make `next` current, load its SP, restore, `ret`. Masked for its whole length: 33 software pushes, 33 pops, the two-byte return address and the pointer exchange, cycle cost not yet established on a board; push/pop instruction counts must not be equated with cycles. - `EMB_ISR(name)` defines `__vector_` from the board's `EMB_VECTOR_`: the same frame push with the I bit forced set in the saved SREG (`set; bld r0,7`, since `ori` needs r16 or above), `embk_isr_enter()`, the C body `embk_isr_body_()`, `embk_isr_exit()`, an unconditional SP exchange with the thread the kernel named (the running one when nothing changed: no branch, no label, per SPEC-011 §15), restore, `ret`. - **Deviation from SPEC-012 §4.4, recorded here:** the epilogue ends with `ret`, not `reti`. The restored SREG carries the resumed context's own I bit; `reti` would set I unconditionally and enable interrupts inside a switched-to thread's critical section. The interrupted context's saved SREG has I forced set, so the actual `out SREG; pop r0; ret` tail restores its interrupt state. The intervening pop prevents inferring that a one-instruction enable delay postpones every pending IRQ until after ret; physical validation of coherent tail entry remains outstanding (A27) (SPEC-012 §14 decision 2 relies on the same delay for the launch). - `EMB_ISR_RAW(name)` is a plain `signal` handler that may not call the kernel. - The kernel's interrupt-exit hook returns the context to resume, never NULL (an addition to SPEC-011 §6 made for this port's branch-free epilogue). ## Kernel start and idle `emb_arch_kernel_start(first, idle)` makes the pre-kernel context the idle context: it switches to the first thread, saving `main()`'s context into the idle control block; when the scheduler later picks the idle context it resumes there, enables interrupts, and runs the kernel's idle loop (`sei; sleep` per SPEC-011 §9). The pre-kernel stack is the idle stack (SPEC-012 §3). The port passes both threads to the start function, a deviation from SPEC-011 §4's bare stack pointer recorded in `include/emb/arch.h`. ## Interrupts and critical sections - `emb_arch_irq_lock()`: `in r, SREG; cli`; the key is the saved SREG. `unlock`: `out SREG, key`. Both always-inline with a `"memory"` clobber. Under QEMU a pending interrupt is taken after the unlock only when the emulator returns to its main loop (amendment A14); the conformance suite synchronizes on the handler through `emb_test_irq_settle()` where the order of events around an unlock is asserted. - Kernel-aware handlers run with I clear (no nesting); `CONFIG_EMB_AVR_ISR_STACK_RESERVE` (64 bytes) is the per-thread reserve for the 35-byte frame plus the handler's C frame. - The mask table (`arch/avr/avr_port.c`) maps each of the 26 vectors to its interrupt-enable register and bit; `emb_irq_set_level` is a no-op (vector order is the priority), `emb_irq_pend` is `EMB_ENOTSUP`, and `emb_irq_clear_pending` covers the external, pin-change and timer flags. ## Time Periodic tick on Timer1 CTC: prescaler `CONFIG_EMB_AVR_TIMER1_PRESCALER` (64), `OCR1A = F_CPU / prescaler / tick_hz - 1` (249 at 16 MHz and 1 ms), vector `TIMER1_COMPA`. The clock is read inside the critical section (SPEC-003 §3.1 AVR row). `emb_arch_cycles()` returns prescaled Timer1 counts rebuilt from the tick count and `TCNT1` (4 µs resolution at 16 MHz): coarse statistics only; hard timing is taken with a logic analyzer on a GPIO (SPEC-012 §14 decision 9). ## Memory and linking - Registration tables (`emb_init_table`, `emb_test_table`) live in flash in `.progmem.` sections, bracketed by `__start_`/`__stop_` symbols in the board's linker script (`boards/arduino/arduino_uno/arduino_uno.ld`, derived from the toolchain's avr5 script with the flash region ending below Optiboot). GCC for AVR lets the `progmem` attribute override an explicit section name, so table entries are declared with `EMB_TABLE_CONST` (plain `const` plus the section) and read with `EMB_FLASH_READ_*`. - `const` tables of the kernel are `EMB_FLASH_CONST` (`progmem`) and read with `pgm_read_*`; string literals of a checked build (`__func__` locations) stay in RAM on avr-gcc 7, so the tiny profile sets `CONFIG_EMB_FAULT_WHERE=n`. - Stack checks: the kernel's guard words (8 bytes at the stack limit) at every switch; no hardware limit, no MPU. ## Footprint (avr-gcc 7.3 `-Os -mrelax -mcall-prologues`, tiny profile) Measured with `tools/footprint/footprint.py` from the link map; the kernel total counts the `kernel/` objects only. The R-003 reference is `samples/footprint_t1` under `configs/footprint-t1.conf` (the T1 feature set: scheduler, notifications, sleep and timeouts, one semaphore; no mutex, no trace), gated per commit on the `avr-uno-t1` preset with the targets as hard limits. The two `first_execution` rows link the mutex as well and are gated at their baseline plus 10 percent. | preset | program | kernel text | kernel static RAM | thread control block | R-003 target | |---|---|---:|---:|---:|---| | avr-uno-t1 (release) | `footprint_t1` | 3805 | 63 | 32 | 4096 / 64 / 32 (T1, T2): met | | avr-uno-release | `first_execution` | 3909 | 66 | 34 | — | | avr-uno (checked) | `first_execution` | 4654 | 68 | 35 | — | What the pass changed (amendments A15 to A17): linker relaxation and shared prologues in the reference build; no padding byte in the object header; the exit code in `wake_data`; the wake result and notification mode in the thread's `obj.flags` bits; a singly linked timeout list without a generation field on the tiny profile; the clock sequence counter only where the port reads the clock through it; the raised-priority count only with mutexes; one table scan shared by the scheduler and the wait queues. The first M1 baseline, for the record, was 4577 / 84 / 42 (release, `first_execution`). ## Skipped conformance tests Tests needing fork-based fault expectation (`EMB_ASSERT_FAULTS`, `EMB_ASSERT_MISUSE` in checked builds) are skipped on this port and reported as skips; the release build asserts the returned statuses instead. The 32-bit wrap test (2^31 ticks) runs in virtual time only. The dynamic-connect validation test needs `CONFIG_EMB_IRQ_DYNAMIC`. ## Toolchain notes - avr-gcc cannot compute `-fstack-usage` for naked functions and errors out, so the flag is dropped for AVR builds; stack figures come from the call graph. - EmbCC: full RTOS execution remains unqualified (`cmake/toolchains/embcc-avr.cmake` exists). AVR scripts, program-memory support, DWARF, remote register layout and masking atomics are implemented upstream; [focused fixture/probe checks](../engineering/embcc-capability-review.md) passed at their stated scope. The [layout-marker correction](../engineering/embcc-layout-marker.md) now passes AVR storage/assertion/kernel-archive compilation. Flash-macro/registration wiring, linking and executed conformance still need a joint session; SPEC-012 §8/§10 retain these distinctions.