feat: publish FreeRTOS C FC02 card
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
# 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 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.
|
||||
|
||||
## Planned 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;
|
||||
```
|
||||
|
||||
Planned 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
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```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 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 E01–E12 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.
|
||||
Reference in New Issue
Block a user