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_<n> from the board’s EMB_VECTOR_<name>: 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_<name>(), 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.<name> 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 passed at their stated scope. The layout-marker correction 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.