This guide describes the port interface used by HardRT v0.5. The authoritative non-installed declaration surface is src/internal/hardrt_port_contract.h.
HardRT separates the portable scheduler and synchronization code from architecture-specific tick, critical-section, stack, idle, and context-switch operations.
- A port includes
hardrt_port_contract.h; it does not redeclare kernel-private functions locally. - The contract header declares every hook a port must implement and identifies the core-private services a port is allowed to call.
- Private port/kernel headers live under
src/internaland are not part of an installed HardRT package. tests/port_contract_fixture.cis a compile-only skeleton implementation. Changing a required hook without updating the fixture is a build failure.- Cortex-M ports intended for hard-real-time qualification must give every timing-sensitive hook bounded behavior under the supported configuration. POSIX follows the logical contract but is not timing-qualified.
- The core owns task state and scheduling decisions.
- A native port owns stack-frame construction and context transfer. A hosted port may map HardRT tasks onto host execution objects as long as the common core remains the sole scheduling authority.
hrt_init()initializes all core state and the idle representation before the port configures its tick mechanism.- Tick configuration during
hrt_init()must remain inactive. A port activates its internal periodic tick only whenhrt_start()crosses the scheduler-start boundary. - Tick handlers update kernel state and request rescheduling; they do not directly run application task code.
- Blocking APIs transfer control back to the scheduler from task context.
- Kernel runtime storage is static. A hosted port may additionally consume operating-system execution resources; those resources are port implementation details and must be documented separately.
The core owns the outgoing READY-task transition exactly once before a successor is selected. A port must not separately enqueue, rotate, or refresh a task merely because it is performing the architecture-specific context switch.
The v0.5 semantics are:
- blocked, sleeping, deleted, or returned tasks are not requeued;
- explicit
hrt_yield()rotates the current RUNNING task to the READY tail once and refreshes its quantum; - RR quantum expiry rotates the task to the READY tail once and refreshes the next quantum;
- higher-priority asynchronous preemption preserves the interrupted task's queue precedence and remaining quantum;
- ISR/tick code requests a switch but never directly enters application task context.
Wake paths use one scheduler-aware decision. Under HRT_SCHED_PRIORITY and HRT_SCHED_PRIORITY_RR, a newly READY task requests immediate scheduling only when it has strictly higher priority than the current RUNNING application task, or when no normal application task is running. Equal- and lower-priority wakes do not force a context switch solely because they became READY.
This rule is also the meaning of public ISR need_switch outputs. The ISR API itself requests the switch when required; applications do not call a second ISR-yield hook.
See SCHEDULING.md for the complete contract and the retained-quantum validation trace.
Caller: hrt_init() after core scheduler storage and the idle representation are complete. Context: non-ISR. It must not block, dispatch a task, activate a periodic interrupt, or globally enable interrupts.
For HRT_TICK_SYSTICK, this hook configures the port-owned timer and context-switch priorities but leaves the timer inactive. For HRT_TICK_EXTERNAL, it configures only the scheduler mechanism required by the port and leaves timer ownership entirely with the application. It returns zero on success and a negative value when the requested internal tick cannot be represented by the port.
Caller: hrt_start(). This hook owns the ordered scheduler-start boundary. Architecture state required before task execution must be completed first; the configured internal periodic tick is then activated, the first scheduling decision is requested, and task execution is allowed to begin.
On Cortex-M, startup is briefly protected with interrupts masked while FP context support is configured, the initial PendSV is pended, and SysTick is armed. Interrupts are enabled only after that sequence is complete. On the v0.5.1 hosted POSIX port, scheduler entry starts the internal timer pthread when selected, makes the first common-core scheduling decision, dispatches the corresponding task pthread, and then remains in the controller/scheduler loop. The null port returns without executing tasks.
Caller: task-context sleep, yield, delete, and blocking synchronization paths. Task-context-only. It may transfer control immediately or pend an architecture-specific switch, but it is never the ISR reschedule API. The port must not add an additional ready-queue transition; the core scheduler owns that decision.
Caller: task paths and supported ISR/tick paths after scheduler startup. It must be non-blocking, ISR-safe for the configured kernel-aware interrupt range, and safe to invoke repeatedly. Cortex-M sets PENDSVSET; POSIX records a pending scheduler request and, when required, interrupts the currently running hosted task so the controller can regain ownership. It requests a switch but does not make the ISR/signal context the scheduler owner.
The initial scheduler handoff is owned by hrt_port_enter_scheduler() rather than by a separate core-side pend before port startup. This prevents the first Cortex-M PendSV from running before architecture startup state is ready.
Caller: scheduler/idle context. Cortex-M executes WFI; POSIX uses a short sleep. It must not alter scheduler queues.
Caller: hrt_create_task() before the task becomes READY. It builds or records the initial execution context. The supplied stack storage remains application-owned for the HardRT task lifetime and participates in the common-core live-stack contract. The operation must not block. It returns zero only when the execution context is fully usable; a negative return causes task creation to roll the slot back to HRT_UNUSED.
On native Cortex-M, the supplied storage is the task execution stack. On the v0.5.1 hosted POSIX port, the storage remains part of HardRT's public task/lifetime validation but is not the native pthread execution stack. The host pthread owns a separate stack whose sizing and allocation are host-port implementation details. New ports must document which model they implement rather than silently changing the meaning of application-owned stack storage.
Runs on first task entry. It invokes the current task function with its stored argument and calls hrt_task_delete() if that function returns.
Caller: scheduler/synchronization code in task context and supported kernel-aware ISR APIs. The pair must be non-blocking and nestable. The outermost exit restores the previous protection state. No partially updated scheduler state may be exposed.
The Cortex-M reference port uses BASEPRI with current defaults:
HARDRT_NVIC_PRIO_BITS = 4
HARDRT_MAX_SYSCALL_IRQ_PRIO = 5Any IRQ calling HardRT ISR APIs must obey the configured syscall-priority ceiling. The duration of these masked regions is part of the hard-real-time qualification work in #53.
The public hrt_tick_from_isr() path enters the same port critical-section contract before mutating common tick/scheduler state. On Cortex-M this nests through the existing BASEPRI-preserving implementation and does not widen the set of IRQ priorities permitted to call HardRT. On hosted POSIX it serializes an application-owned external-tick caller with the controller and task-side kernel paths.
Port-specific stack-pointer validation. Cortex-M checks RAM range and architecture-required alignment; POSIX currently accepts the value. It must not block.
Initializes the port's idle representation during hrt_init(). It is completed before hrt_port_configure_tick() is called, so no configured or external tick path can observe partially initialized idle state as a consequence of HardRT initialization ordering.
The allowed current port-to-core surface is declared through the contract header and includes:
uint32_t hrt__cfg_core_hz(void);
hrt_tick_source_t hrt__cfg_tick_src(void);
uint32_t hrt__cfg_tick_hz(void);
void hrt__tick_isr(void);
int hrt__get_current(void);
void hrt__set_current(int id);
int hrt__pick_next_ready(void);
void hrt__on_scheduler_entry(void);
uintptr_t hrt__schedule(uintptr_t old_sp);The current reference ports also require private TCB/context helpers for task trampoline and stack setup. They are deliberately private and may be narrowed further without changing the application ABI.
With HRT_TICK_SYSTICK, hrt_init() configures but does not activate the port timer. hrt_start() crosses the activation boundary through hrt_port_enter_scheduler(). Once active, the timer handler calls private hrt__tick_isr(). That increments time, wakes expired sleepers, accounts for RR slices, and requests a reschedule when required. A tick handler never directly switches application context.
On hosted POSIX, an internal monotonic timer pthread requests ticks. The controller processes those requests only after it has regained exclusive hosted scheduler ownership; the timer pthread is not allowed to run the common scheduler concurrently with an application task.
With HRT_TICK_EXTERNAL, HardRT never starts a timer. The application timer ISR calls public hrt_tick_from_isr(), which reaches the same core tick path through the port critical-section contract. Calling that public API while HRT_TICK_SYSTICK is selected does not advance time and records ERR_TICK_SOURCE_MISMATCH through the kernel diagnostic path.
Because the external timer is application-owned, applications are responsible for not invoking its HardRT tick path before hrt_init() has completed. HardRT itself does not enable global interrupts as a side effect of hrt_init().
The reference Cortex-M port uses PSP task stacks, PendSV, hardware exception frames, software save/restore of r4-r11, per-context EXC_RETURN on hard-float builds, and conditional save/restore of s16-s31 when an extended FP frame is active. PendSV is lower priority than SysTick. FP context support is configured before the first PendSV can dispatch a task.
The physically qualified PRIORITY_RR ordering is low-A -> ISR/wake -> high -> low-A -> low-B, with the interrupted low-A retaining its unused quantum across the higher-priority dispatch.
The v0.5.1 POSIX port is intentionally a Linux hosted execution backend, not a generic native-port template and not a hard-real-time target.
- each live HardRT application task is represented by one pthread;
- the thread running
hrt_start()remains the hosted scheduler/controller; - an internal monotonic timer uses a separate pthread;
- targeted process signals park/resume the selected application pthread so CPU-bound task code cannot indefinitely retain execution merely because it does not call a HardRT API;
- the common core still owns all READY/RUNNING/BLOCKED/SLEEP/EXITED transitions and scheduling policy;
- a scheduling/tick request does not permit the timer thread or signal handler to execute application scheduling policy concurrently with the active task;
- the host operating system determines actual pthread and signal latency, so POSIX results are semantic/regression evidence, not Cortex-M WCET evidence.
The implementation currently reserves process-wide SIGALRM for the hosted preemption/park mechanism and SIGUSR2 for wake/resume handling. An embedding process must not install incompatible handlers or use these signals for unrelated protocols while HardRT is active. Signal configurability and handler restoration are host-integration concerns, not part of the v0.5.1 public API.
Before adding a port, verify at least:
- the port compiles using only
hardrt_port_contract.hplus platform headers; -
hrt_init()cannot activate the periodic tick or globally enable interrupts; - idle/core state is complete before tick configuration;
- scheduler entry activates the internal tick and performs the first dispatch in a defined order;
- internal and external tick modes advance time exactly once per tick;
- wrong-source use of
hrt_tick_from_isr()is observable and cannot double-count a port-owned tick; - no task context switch occurs directly in a hardware tick handler or hosted signal handler;
- task-context yield reaches the scheduler safely and rotates exactly once;
- an asynchronously preempted task cannot continue executing concurrently with its scheduler-selected successor;
- duplicate/stale hosted wake notifications cannot create an extra task-dispatch token;
- task exit publishes port-side non-executability before the common core can reclaim the application slot;
- higher-priority preemption does not rotate the interrupted task behind equal-priority peers;
- nested critical sections preserve prior protection state;
- ISR-facing APIs are called only from supported interrupt priorities;
- initial native stacks satisfy the target ABI and alignment requirements;
- task return reaches
hrt_task_delete(); - idle does not enter an application ready queue;
- repeated wake, yield, block, and delete operations preserve unique READY membership.
src/port/posix/src/port/cortex_m/src/port/null/