Files
lab-rv32i-freertos-c-first-…/doc/task01-a1-a8-plan.md
T

15 KiB
Raw Blame History

FC02 Plan — First Task, TCB and Task Stack

Status: implemented locally and under validation. The repository exists at series/freertos/lab-rv32i-freertos-c-first-task, but it remains outside the active series manifest until all build, simulator, replay and layout gates pass.

The paired experiment and common E01E12 vocabulary are defined in freertos-first-task-a1-a8.md.

Goal

The student creates a FreeRTOS task through the raw C API, follows pvParameters into a typed context, separates that context from the TCB and task stack, observes equal-priority time slicing, and proves deterministic completion in Hazard3/GDB.

The card is the C baseline for K02. It must not imitate a C++ wrapper in C.

Scope

Included:

  • TaskFunction_t and void *pvParameters;
  • xTaskCreate, TaskHandle_t, scheduler start and self-delete;
  • two equal-priority CPU-bound workers and one lower-priority supervisor;
  • static application contexts, dynamic TCB/task-stack allocation in heap_4;
  • TCB, stack, PC/SP, callback ABI and task-state observations;
  • the same sum and scheduling invariants as K02.

Excluded:

  • C++ classes, templates, RAII or virtual dispatch;
  • queues, mutexes, notifications, timers, ISR APIs and peripherals;
  • proof that the idle task has already reclaimed deleted tasks;
  • report/PDF/Teams generation and the later book/matura lesson payload.

Planned program shape

typedef enum
{
    TRACE_PREPARED,
    TRACE_STARTING,
    TRACE_READY,
    TRACE_RUNNING,
    TRACE_COMPLETED,
    TRACE_CREATE_FAILED
} WorkerTraceState;

typedef struct
{
    uint32_t id;
    volatile uint32_t result;
    volatile uint32_t iterations;
    volatile WorkerTraceState trace_state;
    TaskHandle_t handle;
    TickType_t start_tick;
    TickType_t end_tick;
} WorkerContext;

Planned source responsibilities:

src/tasks/task01_first_task.c
├── two static WorkerContext instances
├── trace/checkpoint state
├── create_worker(WorkerContext*, ...)
├── sum_task_entry(void *pvParameters)
├── supervisor_task_entry(void *pvParameters)
├── task01_debug_checkpoint(event, subject)
└── main()

trace_state is an application diagnostic. Its name deliberately avoids pretending that it is eTaskState.

Applicable viewpoint profile

View Status FC02 purpose
A1 CONTEXT enabled raw application → C API/kernel/port → Hazard3
A2 STRUCTURE enabled contexts, handles, entry functions and storage domains
A3 DISPATCH enabled function pointer + erased parameter → typed C body
A4 APPLICATION unavailable the raw task module already is the concrete application; no generic application layer exists
A5 FLOW enabled E01E12 creation and execution scenario
A6 STATE enabled static context/diagnostic state versus kernel state
A7 RUNTIME enabled context, TCB, stack, registers, PC and ticks
A8 PATTERNS unavailable a standalone pattern view would be decorative; idioms are labelled in A2/A3 and compared in K02 A8

Unavailable A4/A8 tabs remain readable in the viewer and print index with the reasons above.

Canonical tree

Series · FreeRTOS C
└── Card FC02 · First Task, TCB and Task Stack
    └── Task01 · Raw C task and typed context
        ├── Block · A1 CONTEXT
        │   └── Phase · SYSTEM BOUNDARY
        │       ├── Step 01 · application module requests tasks          [CODE]
        │       ├── Step 02 · FreeRTOS C API owns scheduling             [CODE]
        │       └── Step 03 · kernel/port execute on Hazard3              [CODE]
        ├── Block · A2 STRUCTURE
        │   └── Phase · C DATA / FUNCTIONS
        │       ├── Step 01 · WorkerContext is application state          [CODE]
        │       ├── Step 02 · TaskHandle_t is an opaque kernel handle     [CODE]
        │       ├── Step 03 · two static worker contexts                  [CODE]
        │       ├── Step 04 · sum_task_entry has TaskFunction_t shape     [CODE]
        │       ├── Step 05 · SupervisorContext observes both workers     [CODE]
        │       └── Step 06 · common sum/scheduling invariants            [CODE]
        ├── Block · A3 DISPATCH · C FUNCTION POINTER
        │   └── Phase · CALLBACK / CONTEXT
        │       ├── Step 01 · TaskFunction_t erases the context type      [CODE]
        │       ├── Step 02 · xTaskCreate receives entry and context      [RUN E03]
        │       ├── Step 03 · pvParameters arrives in RV32 a0 at entry    [RUN E06]
        │       ├── Step 04 · cast recovers WorkerContext*                [RUN E06]
        │       ├── Step 05 · entry executes the typed calculation        [RUN E07]
        │       └── Step 06 · wrong context cast is not rejected by C     [CODE]
        ├── Block · A5 FLOW
        │   ├── Phase · CREATE / START
        │   │   ├── Step 01 · static contexts prepared                    [RUN E01]
        │   │   ├── Step 02 · first worker create request                 [RUN E02]
        │   │   ├── Step 03 · xTaskCreate(entry, &workerA)                [RUN E03]
        │   │   ├── Step 04 · handle != null; diagnostic ready            [RUN E04]
        │   │   └── Step 05 · vTaskStartScheduler()                       [RUN E05]
        │   └── Phase · DISPATCH / FINISH
        │       ├── Step 06 · sum_task_entry(&workerA)                    [RUN E06]
        │       ├── Step 07 · diagnostic running; calculation loop        [RUN E07]
        │       ├── Step 08 · tick/time slice A -> B -> A                 [RUN E08]
        │       ├── Step 09 · result 200010000 published                  [RUN E09]
        │       ├── Step 10 · diagnostic completed; public handle null    [RUN E10]
        │       ├── Step 11 · vTaskDelete(NULL)                           [RUN E11]
        │       └── Step 12 · supervisor verifies PASS                    [RUN E12]
        ├── Block · A6 STATE
        │   ├── Phase · CONTEXT / DIAGNOSTIC
        │   │   ├── Step 01 · static WorkerContext lifetime               [CODE]
        │   │   ├── Step 02 · PREPARED -> STARTING -> READY               [RUN E01E04]
        │   │   └── Step 03 · RUNNING -> COMPLETED                        [RUN E07E10]
        │   └── Phase · KERNEL
        │       ├── Step 04 · Ready -> Running through scheduler          [RUN E04E07]
        │       ├── Step 05 · equal-priority time slicing                 [RUN E08]
        │       └── Step 06 · Deleted does not destroy static context     [RUN E10E11]
        ├── Block · A7 RUNTIME
        │   ├── Phase · MEMORY
        │   │   ├── Step 01 · context address is not a TCB address        [RUN E04/E06]
        │   │   ├── Step 02 · TCB and task stack come from heap_4         [RUN E04/E07]
        │   │   └── Step 03 · SP lies inside worker stack interval        [RUN E06/E07]
        │   └── Phase · CPU / SCHEDULER
        │       ├── Step 04 · pvParameters in a0 only at callback entry   [RUN E06]
        │       ├── Step 05 · PC, tick and switch trace                   [RUN E08]
        │       └── Step 06 · result, source and checkpoint identity      [RUN E12]
        └── Exercise · Timestamped raw-C task trace

Planned checkpoint table

FC02 snapshots are local to the FC02 binary even though their event_id matches K02.

Event Planned snapshot Stop condition Required verification
E01 task01.contexts point 1, worker A trace prepared, handle null, result zero
E02 task01.create-request point 2, worker A, previous point 1 first launch request only
E03 task01.create-call point 3, worker A trace starting; callback/context arguments correct
E04 task01.ready point 4, worker A trace ready; handle non-null
E05 task01.scheduler point 5 all three tasks created; startup stack active
E06 task01.entry point 6, worker A pvParameters == &workerA; callback-entry a0 matches
E07 task01.work point 7, worker A typed context recovered; trace running
E08 task01.timeslice point 8, worker A only A/B/A trace; >=2 switches and elapsed ticks
E09 task01.result point 9, worker A result 200010000, iterations 20000
E10 task01.completed point 10, worker A trace completed; public handle null
E11 task01.delete point 11, worker A published state persists before non-returning delete
E12 task01.pass point 12, supervisor two results, contexts, handles, stacks and scheduler invariants pass

The checkpoint sink is noinline and used, writes volatile point/subject state, and ends with a compiler memory barrier. E08 is emitted once by worker A after the trace has proved A -> B -> A.

A3 dispatch evidence

The exact boundary is:

xTaskCreate(sum_task_entry, ..., &workerA, ..., &workerA.handle)
                │                    │
                │ TaskFunction_t     │ void* context
                ▼                    ▼
sum_task_entry(void *pvParameters)
                │ callback-entry a0 == &workerA
                ▼
WorkerContext *worker = (WorkerContext *)pvParameters
                │
                ▼
typed calculation and published result

There is one C callback dispatch. The cast does not cause a second dynamic dispatch. At callback entry, source + disassembly + a0 prove the ABI. After ordinary instructions execute, the card stops claiming that a0 retains pvParameters.

A compile-only negative exhibit demonstrates that C can accept a wrong void*-to-structure cast that the K02 C++ contract rejects. The unsafe fixture is never linked into or executed by the teaching program.

Per-view debugging strategies

View/anchor Mode Layout/action Expected evidence
A1 dependency boundary CODE include/link/config excerpts application calls public API; kernel/port/target boundary
A2 context layout CODE source excerpt + ptype/DWARF or bounded layout output static context fields and no hidden C++ object model
A2 handle/storage CODE declaration + memory-region map handle is opaque; context is static; TCB/stack are dynamic
A3 entry/context RUN source + entry disassembly + a0 + context memory callback ABI and exact worker identity
A3 wrong-cast exhibit CODE deterministic compile output and source blob C compiler does not encode the desired context type
A5 E01E12 RUN per-event minimal source/asm/register/stack/memory set ordered scenario and non-empty assertions
A6 diagnostic state RUN context fields beside public eTaskGetState/vTaskGetInfo result correlated domains remain explicitly distinct
A7 TCB/stack RUN context address, public handle, TCB address, heap range, SP interval context != TCB; SP belongs to the selected task stack
A7 scheduling RUN PC, tick, trace buffer and current-task observation computation crosses real ticks and task changes

Application code uses public task APIs. pxCurrentTCB may appear only in a clearly labelled debugger-internal observation, never in a C assertion or source dependency.

State model

static context lifetime:  initialized -------------------------- program end
diagnostic trace state:   PREPARED -> STARTING -> READY -> RUNNING -> COMPLETED
kernel task state:        Ready <-> Running <-> Blocked; Deleted

The lanes can be correlated by an event and handle, but they are not equal. Clearing the public handle before vTaskDelete(NULL) is an application diagnostic choice. It does not prove immediate heap reclamation.

Page and spread plan

  1. goal/scope, paired invariants and viewpoint index;
  2. compact A1 plus main A2 C structure/storage map;
  3. A3 callback/parameter dispatch;
  4. A5 CREATE/START (E01E05);
  5. A5 DISPATCH/FINISH (E06E12), joined with sheet 4 in the viewer;
  6. A6 diagnostic and kernel state lanes;
  7. A7 runtime map plus exercise/rubric.

The A5 split occurs at the scheduler boundary. Both assets share a spread_group, stable anchor numbering and common participant alignment.

Acceptance contract for future implementation

  • freestanding C11 build, same pinned kernel/port/config as K02;
  • no _Z*, .init_array, _GLOBAL__sub_I, _Unwind*, __cxa_* or hosted C++ runtime symbols; positive C/FreeRTOS/checkpoint symbols must exist;
  • two independent results equal 200010000;
  • two workers each execute 20000 loop iterations;
  • work crosses at least two ticks and proves at least two task changes with an A -> B -> A subsequence;
  • callback-entry a0, pvParameters and worker A address match;
  • static context, TCB and task stack are shown as distinct objects/domains;
  • all twelve RUN checkpoints have assertions and replay from clean RAM;
  • every CODE step has a deterministic source/artifact query and no snapshot;
  • all enabled/disabled views, anchors and full tree keys validate;
  • plain cursor movement never runs or resets the simulator.

Future implementation order

  1. Create the FC02 repository from the current card template only after explicit authorization.
  2. Pin the same kernel, port, config and Hazard3 profile as K02.
  3. Implement the raw C workload and E01E12 checkpoint sink.
  4. Add host/build parity tests and C-only ABI checks.
  5. Author A1/A2/A3/A5/A6/A7 PlantUML and text fallbacks.
  6. Add viewpoint manifest, anchors, CODE/RUN strategies and assertions.
  7. Validate simulator results, replay determinism, navigation and print geometry.
  8. Add FC02 to the active series manifest only after all gates pass.

No PDF or commit is performed by this implementation pass.

Implementation decisions after Claude review

Accepted:

  • E03 stops at xTaskCreate+0 and checks the actual RV32 argument registers;
  • E06 stops at sum_task_entry+0, before any checkpoint call can overwrite callback register a0;
  • all other replay events stop at task01_debug_checkpoint_committed, after volatile evidence has been published;
  • E08 records an actual A,B,A subsequence and its ticks; it does not assume that the complete trace starts with worker A;
  • g_observed_worker_changes names the measured application trace honestly;
  • created_handle preserves the historical opaque handle before the public handle is cleared, while TCB and task-stack addresses become forensic after self-delete;
  • config/paired-experiment.json makes kernel/config/workload parity machine readable and explicitly declares the C-versus-C++ evidence representation as the lesson variable;
  • A3 is landscape because it visualizes an invocation chain; A1/A6 remain portrait and A2/A5/A7 landscape.

Not copied from K02:

  • C++ start(), duplicate-start guard, CRTP, friend, copy/move rules and ownership vocabulary;
  • nullptr, static_cast, RTTI/vtable tests and C++ symbol requirements;
  • checkpoint stops at a sink before its evidence writes;
  • claims that a0 still carries callback context inside the checkpoint sink;
  • a forced A4 or A8 diagram with no independent teaching question.