# FC02 Plan — First Task, TCB and Task Stack Status: implemented and validated locally. The card exists at `series/freertos/lab-rv32i-freertos-c-first-task`, is visible in the FreeRTOS C catalog and remains outside the deploy manifest until its own repository is committed and published. The paired experiment and common E01–E12 vocabulary are defined in [`freertos-first-task-a1-a8.md`](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. ## Implemented program shape ```c 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; ``` Source responsibilities: ```text 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 | E01–E12 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 ```text 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 E01–E04] │ │ └── Step 03 · RUNNING -> COMPLETED [RUN E07–E10] │ └── Phase · KERNEL │ ├── Step 04 · Ready -> Running through scheduler [RUN E04–E07] │ ├── Step 05 · equal-priority time slicing [RUN E08] │ └── Step 06 · Deleted does not destroy static context [RUN E10–E11] ├── 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 ``` ## 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: ```text 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 E01–E12 | 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 ```text 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 (E01–E05); 5. A5 DISPATCH/FINISH (E06–E12), 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 - 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. ## Validation result 1. The raw C workload and A1/A2/A3/A5/A6/A7 views are implemented. 2. Kernel/config/workload parity is pinned in `config/paired-experiment.json` and passes `make check-pair`. 3. C-only ABI, context contract and host workload checks pass. 4. Hazard3 finishes with `PASS`; two results equal `200010000`, the trace contains `A -> B -> A`, and three clean simulator processes produce identical evidence. 5. HTML and TeX render with A1/A6 portrait, A2/A3/A7 landscape and A5 as a two-page landscape spread. No PDF was generated. 6. The catalog marks FC02 as existing. The deploy manifest is unchanged until a published card repository exists. HTML and TeX may be rendered during validation. PDF generation and commit remain outside this pass.