{ "card":{"number":"07","slug":"isr-semaphore","title":"Przerwanie zewnętrzne i semaphore FromISR","topic":"Hazard3 MEI, xISRStack, xSemaphoreGiveFromISR i yield","status":"Gotowa","version":"v00.01"}, "front":{"goal":"Uczeń rozdziela task context od ISR context, używa API FromISR i dowodzi natychmiastowego przełączenia do wyższego taska.","scope":"Prawdziwe wejście IRQ5 testbencha wywołuje machine external interrupt. Port zapisuje pełny kontekst i przechodzi na \\texttt{xISRStack}. ISR czyści źródło, daje binary semaphore i wywołuje \\texttt{portYIELD\\_FROM\\_ISR}. Waiter priority 3 działa przed powrotem do stimulus priority 2."}, "viewpoints":[ {"id":"A1","label":"CONTEXT","status":"enabled","subtitle":"Testbench → Hazard3 → port → task"}, {"id":"A2","label":"STRUCTURE","status":"unavailable","subtitle":"Struktura","reason":"Obiekty runtime i ich relacje są pełniej pokazane w A7."}, {"id":"A3","label":"DISPATCH","status":"enabled","subtitle":"Vector, trap, FromISR i context switch"}, {"id":"A4","label":"APPLICATION","status":"unavailable","subtitle":"Eksperyment","reason":"Konkretny handoff jest kompletny w A5."}, {"id":"A5","label":"FLOW","status":"enabled","subtitle":"E01–E09: trigger do PASS"}, {"id":"A6","label":"STATE","status":"enabled","subtitle":"Semaphore empty/given/taken i task state"}, {"id":"A7","label":"RUNTIME","status":"enabled","subtitle":"Waiter, stimulus i xISRStack"}, {"id":"A8","label":"PATTERNS","status":"unavailable","subtitle":"Wzorce","reason":"Deferred interrupt handoff jest tu mechanizmem wykonania A3/A5, nie osobnym wzorcem aplikacyjnym."} ], "artifact":{"source":"src/tasks/task01_isr_semaphore.c","elf":"build/task01_isr_semaphore/prog.elf","image":"build/task01_isr_semaphore/prog.bin"}, "strategies":[ {"id":"code.irq-contract","kind":"code","prerequisites":["źródło FC07"],"layout":["vector bridge","FreeRTOS trap","application ISR","config"],"commands":["rg -n 'isr_external_irq|application_interrupt|GiveFromISR|SET_IRQ' src include"],"expected_observations":["external vector trafia do pełnego trap path","ISR API ma osobny suffix"],"assertions":["source clear poprzedza give","port ma 256-word ISR stack"],"evidence_fields":["code_ref","symbol","ABI"]}, {"id":"run.trap","kind":"run","prerequisites":["Hazard3 online","IRQ5 enabled","czysta RAM"],"layout":["mcause","mepc","xISRStack","source register"],"commands":["print/x g_fc07.mcause","print/x g_fc07.isr_sp","print g_fc07.irq_source_after_clear"],"expected_observations":["mcause=0x8000000b","ISR SP różny od task SP","source=0"],"assertions":["real MEI","full context saved"],"evidence_fields":["mcause","SP","PC","IRQ register","timestamp"]}, {"id":"run.handoff","kind":"run","prerequisites":["waiter Blocked na binary semaphore"],"layout":["semaphore","xHPW","waiter/stimulus TCB","event order"],"commands":["print g_fc07.higher_priority_task_woken","print g_fc07_events"],"expected_observations":["xHPW=pdTRUE","E07 poprzedza E08"],"assertions":["waiter value widzi stimulus_after=0","stimulus po resume widzi waiter_ran=1"],"evidence_fields":["state","actor","value","PC/SP"]}, {"id":"run.pass","kind":"run","prerequisites":["E01–E08 committed"],"layout":["nine records","stack separation","digest"],"commands":["print g_fc07_pass","print g_fc07.digest"],"expected_observations":["PASS=1","nine sequential markers"],"assertions":["three distinct stack contexts","digest stable"],"evidence_fields":["PASS","digest","source blob","ELF hash","timestamp"]} ], "checkpoints":{ "task01.config":{"event_id":"E01","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 1"},"verify":{"expressions":[{"expr":"g_fc07_event_count","equals":1},{"expr":"g_fc07.semaphore == 0","equals":1}]}}, "task01.waiter-block":{"event_id":"E02","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 2"},"verify":{"expressions":[{"expr":"g_fc07.waiter_sp != 0","equals":1},{"expr":"g_fc07.waiter_ran","equals":0}]}}, "task01.before-trigger":{"event_id":"E03","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 3"},"verify":{"expressions":[{"expr":"g_fc07.stimulus_sp != 0","equals":1},{"expr":"g_fc07.stimulus_after","equals":0}]}}, "task01.isr-entry":{"event_id":"E04","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 4"},"verify":{"expressions":[{"expr":"g_fc07.mcause","equals":2147483659},{"expr":"g_fc07.isr_sp != g_fc07.stimulus_sp","equals":1}]}}, "task01.source-clear":{"event_id":"E05","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 5"},"verify":{"expressions":[{"expr":"g_fc07.irq_source_after_clear","equals":0},{"expr":"g_fc07.waiter_ran","equals":0}]}}, "task01.give-yield":{"event_id":"E06","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 6"},"verify":{"expressions":[{"expr":"g_fc07.higher_priority_task_woken","equals":1},{"expr":"g_fc07.stimulus_after","equals":0}]}}, "task01.waiter-runs":{"event_id":"E07","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 7"},"verify":{"expressions":[{"expr":"g_fc07.waiter_ran","equals":1},{"expr":"g_fc07.stimulus_after","equals":0}]}}, "task01.stimulus-resumes":{"event_id":"E08","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 8"},"verify":{"expressions":[{"expr":"g_fc07.waiter_ran","equals":1},{"expr":"g_fc07.stimulus_after","equals":1}]}}, "task01.pass":{"event_id":"E09","stop":{"symbol":"fc07_checkpoint_committed","offset":0,"condition":"$a0 == 9"},"verify":{"expressions":[{"expr":"g_fc07_pass","equals":1},{"expr":"g_fc07_event_count","equals":9},{"expr":"g_fc07_events[6].actor","equals":2}]}} }, "sections":[ {"id":"A1","label":"CONTEXT","title":"Context","order":10,"orientation":"landscape","description":"Od fizycznej linii IRQ do odblokowanego taska.","content_tex":"Źródło testbencha, kontroler Hazard3, port FreeRTOS, ISR aplikacji i waiter są osobnymi warstwami odpowiedzialności.","assets":[{"stem":"a1-context","title":"A1 · granice interrupt path","caption":"A1 CONTEXT — IRQ5 do high-priority waitera.","label":"fig:a1-context","alt":"Warstwy realnego external IRQ.","phases":[{"id":"context","label":"SOURCE / CPU / PORT / ISR / TASK","steps":[ {"id":"source","number":1,"label":"testbench drives a real IRQ line","mode":"RUN","event_id":"E03","strategy_ref":"run.trap","svg_label":"01","code_ref":"src/tasks/task01_isr_semaphore.c:156","snapshot_ref":"task01.before-trigger","description":"MMIO store ustawia bit wejścia CPU.","evidence":"IRQ mask bit 5."}, {"id":"hazard","number":2,"label":"Hazard3 raises machine external interrupt","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"02","code_ref":"src/tasks/task01_isr_semaphore.c:103","snapshot_ref":"task01.isr-entry","description":"To nie jest tick hook.","evidence":"mcause=MEI."}, {"id":"port","number":3,"label":"port saves full task context","mode":"CODE","strategy_ref":"code.irq-contract","svg_label":"03","code_ref":"src/common/hazard3_freertos_traps.S:24","description":"Vector skacze do official trap handler.","evidence":"xISRStack."}, {"id":"isr","number":4,"label":"application ISR clears and gives","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"04","code_ref":"src/tasks/task01_isr_semaphore.c:99","snapshot_ref":"task01.give-yield","description":"API FromISR ustawia xHPW.","evidence":"source=0, xHPW=1."}, {"id":"waiter","number":5,"label":"higher waiter consumes semaphore","mode":"RUN","event_id":"E07","strategy_ref":"run.handoff","svg_label":"05","code_ref":"src/tasks/task01_isr_semaphore.c:135","snapshot_ref":"task01.waiter-runs","description":"Context restore wybiera waiter TCB.","evidence":"stimulus_after=0."} ]}]}]}, {"id":"A3","label":"DISPATCH","title":"Dispatch","order":30,"orientation":"landscape","description":"Pełny context save umożliwia yield z ISR.","content_tex":"Samo vTaskSwitchContext w zwykłym handlerze nie wystarcza. Port musi najpierw zapisać pełny kontekst przerwanego taska, przejść na ISR stack i dopiero potem odtworzyć kontekst wybranego taska.","assets":[{"stem":"a3-dispatch","title":"A3 · external interrupt dispatch","caption":"A3 DISPATCH — vector, trap, FromISR i restore.","label":"fig:a3-dispatch","alt":"Dispatch external IRQ w porcie FreeRTOS.","phases":[{"id":"dispatch","label":"VECTOR / SAVE / ISR / RESTORE","steps":[ {"id":"vector","number":1,"label":"external vector enters port","mode":"CODE","strategy_ref":"code.irq-contract","svg_label":"01","code_ref":"src/common/hazard3_freertos_traps.S:24","description":"Strong symbol zastępuje weak handler testbencha.","evidence":"isr_external_irq."}, {"id":"trap","number":2,"label":"trap saves full context","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"02","code_ref":"vendor/FreeRTOS-Kernel/portable/GCC/RISC-V/portASM.S:356","snapshot_ref":"task01.isr-entry","description":"Interrupted SP jest zapisany w TCB.","evidence":"mepc/mstatus/registers."}, {"id":"stack","number":3,"label":"handler runs on xISRStack","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"03","code_ref":"src/tasks/task01_isr_semaphore.c:104","snapshot_ref":"task01.isr-entry","description":"ISR SP różni się od obu tasków.","evidence":"isr_sp."}, {"id":"app","number":4,"label":"application handler owns device protocol","mode":"RUN","event_id":"E05","strategy_ref":"run.trap","svg_label":"04","code_ref":"src/tasks/task01_isr_semaphore.c:107","snapshot_ref":"task01.source-clear","description":"Najpierw clear source.","evidence":"register=0."}, {"id":"give","number":5,"label":"FromISR unblocks waiter","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"05","code_ref":"src/tasks/task01_isr_semaphore.c:111","snapshot_ref":"task01.give-yield","description":"Binary semaphore staje się available.","evidence":"xHPW=1."}, {"id":"yield","number":6,"label":"yield selects waiter TCB","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"06","code_ref":"src/tasks/task01_isr_semaphore.c:118","snapshot_ref":"task01.give-yield","description":"Switch następuje przed mret.","evidence":"vTaskSwitchContext."}, {"id":"restore","number":7,"label":"port restores selected task","mode":"RUN","event_id":"E07","strategy_ref":"run.handoff","svg_label":"07","code_ref":"src/tasks/task01_isr_semaphore.c:135","snapshot_ref":"task01.waiter-runs","description":"CPU nie wraca od razu do stimulus.","evidence":"E07 before E08."} ]}]}]}, {"id":"A5","label":"FLOW","title":"Flow","order":40,"orientation":"landscape","description":"Jeden realny handoff E01–E09.","content_tex":"Sekwencja dowodzi source-clear-before-give oraz waiter-before-interrupted-task.","assets":[{"stem":"a5-flow","diagram_kind":"sequence","title":"A5 · IRQ handoff replay","caption":"A5 FLOW — od Blocked przez ISR do natychmiastowego waitera.","label":"fig:a5-flow","alt":"Sekwencja ISR semaphore handoff.","phases":[{"id":"flow","label":"TRIGGER / ISR / YIELD / PASS","steps":[ {"id":"config","number":1,"label":"IRQ contract configured","mode":"RUN","event_id":"E01","strategy_ref":"run.trap","svg_label":"01","code_ref":"src/tasks/task01_isr_semaphore.c:238","snapshot_ref":"task01.config","description":"IRQ5 i priority są jawne.","evidence":"event count 1."}, {"id":"wait","number":2,"label":"waiter reaches empty take","mode":"RUN","event_id":"E02","strategy_ref":"run.handoff","svg_label":"02","code_ref":"src/tasks/task01_isr_semaphore.c:130","snapshot_ref":"task01.waiter-block","description":"Następnie przechodzi do Blocked.","evidence":"waiter_ran=0."}, {"id":"trigger","number":3,"label":"stimulus commits before trigger","mode":"RUN","event_id":"E03","strategy_ref":"run.trap","svg_label":"03","code_ref":"src/tasks/task01_isr_semaphore.c:155","snapshot_ref":"task01.before-trigger","description":"Kontrolowany punkt przed MMIO.","evidence":"stimulus_after=0."}, {"id":"entry","number":4,"label":"MEI enters xISRStack","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"04","code_ref":"src/tasks/task01_isr_semaphore.c:103","snapshot_ref":"task01.isr-entry","description":"Prawdziwy async trap.","evidence":"mcause and SP."}, {"id":"clear","number":5,"label":"ISR clears hardware source","mode":"RUN","event_id":"E05","strategy_ref":"run.trap","svg_label":"05","code_ref":"src/tasks/task01_isr_semaphore.c:107","snapshot_ref":"task01.source-clear","description":"Zapobiega retrigger.","evidence":"source=0."}, {"id":"give","number":6,"label":"give requests a switch","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"06","code_ref":"src/tasks/task01_isr_semaphore.c:111","snapshot_ref":"task01.give-yield","description":"Wyższy task stał się Ready.","evidence":"xHPW=pdTRUE."}, {"id":"waiter","number":7,"label":"waiter runs before stimulus","mode":"RUN","event_id":"E07","strategy_ref":"run.handoff","svg_label":"07","code_ref":"src/tasks/task01_isr_semaphore.c:135","snapshot_ref":"task01.waiter-runs","description":"Natychmiastowy efekt yield.","evidence":"stimulus_after=0."}, {"id":"resume","number":8,"label":"interrupted stimulus resumes","mode":"RUN","event_id":"E08","strategy_ref":"run.handoff","svg_label":"08","code_ref":"src/tasks/task01_isr_semaphore.c:158","snapshot_ref":"task01.stimulus-resumes","description":"Dopiero po zakończeniu waitera.","evidence":"waiter_ran=1."}, {"id":"pass","number":9,"label":"verifier commits PASS","mode":"RUN","event_id":"E09","strategy_ref":"run.pass","svg_label":"09","code_ref":"src/tasks/task01_isr_semaphore.c:186","snapshot_ref":"task01.pass","description":"Sprawdza order, stacks i cause.","evidence":"PASS=1, digest."} ]}]}]}, {"id":"A6","label":"STATE","title":"State","order":50,"orientation":"portrait","description":"Semaphore state i scheduler decision.","content_tex":"GiveFromISR zmienia obiekt synchronizacyjny i stan waitera, lecz natychmiastowe wykonanie wymaga dodatkowego yield request.","assets":[{"stem":"a6-state","title":"A6 · ISR handoff state","caption":"A6 STATE — empty, given, switch i taken.","label":"fig:a6-state","alt":"Maszyna stanu semaphore handoff.","phases":[{"id":"state","label":"SEMAPHORE / TASK / SWITCH","steps":[ {"id":"empty","number":1,"label":"empty semaphore blocks waiter","mode":"RUN","event_id":"E02","strategy_ref":"run.handoff","svg_label":"01","code_ref":"src/tasks/task01_isr_semaphore.c:131","snapshot_ref":"task01.waiter-block","description":"Waiter trafia na wait list.","evidence":"Blocked."}, {"id":"pending","number":2,"label":"IRQ interrupts stimulus","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"02","code_ref":"src/tasks/task01_isr_semaphore.c:99","snapshot_ref":"task01.isr-entry","description":"Stimulus context jest zachowany.","evidence":"mepc and SP."}, {"id":"given","number":3,"label":"give makes waiter Ready","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"03","code_ref":"src/tasks/task01_isr_semaphore.c:111","snapshot_ref":"task01.give-yield","description":"Semaphore nie pozostaje full.","evidence":"xHPW=true."}, {"id":"switch","number":4,"label":"yield requests context switch","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"04","code_ref":"src/tasks/task01_isr_semaphore.c:118","snapshot_ref":"task01.give-yield","description":"pxCurrentTCB wybiera waitera.","evidence":"vTaskSwitchContext."}, {"id":"taken","number":5,"label":"waiter consumes token","mode":"RUN","event_id":"E07","strategy_ref":"run.handoff","svg_label":"05","code_ref":"src/tasks/task01_isr_semaphore.c:131","snapshot_ref":"task01.waiter-runs","description":"Take wraca pdPASS.","evidence":"waiter Running."}, {"id":"resumed","number":6,"label":"stimulus eventually resumes","mode":"RUN","event_id":"E08","strategy_ref":"run.handoff","svg_label":"06","code_ref":"src/tasks/task01_isr_semaphore.c:158","snapshot_ref":"task01.stimulus-resumes","description":"Oryginalny mepc nie ginie.","evidence":"E08 after E07."} ]}]}]}, {"id":"A7","label":"RUNTIME","title":"Runtime","order":60,"orientation":"portrait","description":"Trzy stosy i dwa TCB w jednym trap replay.","content_tex":"ISR stack nie należy do żadnego taska. Port zapisuje interrupted task stack, wykonuje ISR na xISRStack i odtwarza stack wybranego waitera.","assets":[{"stem":"a7-runtime","title":"A7 · stack and context evidence","caption":"A7 RUNTIME — stimulus TCB, xISRStack i waiter TCB.","label":"fig:a7-runtime","alt":"Runtime trzech kontekstów stosu.","phases":[{"id":"runtime","label":"TCB / ISR STACK / RESTORE","steps":[ {"id":"waiter","number":1,"label":"waiter owns priority-3 stack","mode":"RUN","event_id":"E02","strategy_ref":"run.handoff","svg_label":"01","code_ref":"src/tasks/task01_isr_semaphore.c:129","snapshot_ref":"task01.waiter-block","description":"SP zapisany przed take.","evidence":"waiter_sp."}, {"id":"stimulus","number":2,"label":"stimulus owns interrupted stack","mode":"RUN","event_id":"E03","strategy_ref":"run.trap","svg_label":"02","code_ref":"src/tasks/task01_isr_semaphore.c:153","snapshot_ref":"task01.before-trigger","description":"Priority 2.","evidence":"stimulus_sp."}, {"id":"isr","number":3,"label":"trap switches to xISRStack","mode":"RUN","event_id":"E04","strategy_ref":"run.trap","svg_label":"03","code_ref":"src/tasks/task01_isr_semaphore.c:104","snapshot_ref":"task01.isr-entry","description":"ISR nie zużywa task stack.","evidence":"isr_sp distinct."}, {"id":"sem","number":4,"label":"semaphore links ISR and waiter","mode":"RUN","event_id":"E06","strategy_ref":"run.handoff","svg_label":"04","code_ref":"src/tasks/task01_isr_semaphore.c:111","snapshot_ref":"task01.give-yield","description":"Only FromISR API in ISR.","evidence":"xHPW."}, {"id":"evidence","number":5,"label":"event order proves selected return","mode":"RUN","event_id":"E09","strategy_ref":"run.pass","svg_label":"05","code_ref":"src/tasks/task01_isr_semaphore.c:195","snapshot_ref":"task01.pass","description":"E07 waiter, E08 stimulus.","evidence":"nine committed records."} ]}]}]} ], "learning":{"model_label":"Model ISR handoff","model":"Uczeń rozdziela hardware source, trap port, xISRStack, FromISR API i task scheduling.","replay_label":"Replay realnego MEI","replay":"Uczeń odtwarza E01–E09 i wiąże mcause, source clear, xHPW, trzy SP oraz kolejność context restore.","criterion":"PASS; mcause=MEI; source=0 przed give; xHPW=1; waiter E07 przed stimulus E08; trzy różne stosy; deterministyczny digest.","requirement":"Zaprojektowanie i zbadanie bezpiecznego handoff z prawdziwego przerwania do taska FreeRTOS."}, "task":{"short_label":"ISR semaphore","title":"External IRQ → FromISR semaphore → high-priority task","prompt_tex":"Przejdź po A1, A3, A5, A6 i A7. Odtwórz E01–E09 i udowodnij source-clear-before-give oraz waiter-before-stimulus.","conclusion_tex":"ISR działa na osobnym stacku i używa FromISR API. \\texttt{xHigherPriorityTaskWoken} jest informacją dla portu; dopiero \\texttt{portYIELD\\_FROM\\_ISR} powoduje restore kontekstu wyższego taska przed powrotem do przerwanego kodu.","flow":[ {"kind":"block","id":"contract","title":"A — kontrakt przerwania","content_tex":"Rozdziel source, vector, port, ISR i task.","steps":[{"id":"order","title":"Ustal kolejność ISR.","content_tex":"Najpierw clear, potem give, na końcu yield."},{"id":"predict","title":"Przewidź context restore.","content_tex":"Wskaż, czy pierwszy wykona się waiter czy stimulus."}]}, {"kind":"block","id":"replay","title":"B — replay Hazard3","content_tex":"Każdy RUN zaczyna się od czystej RAM i wyłączonego źródła.","steps":[{"id":"trap","title":"Zbadaj E03–E05.","content_tex":"Zapisz \\texttt{mcause}, \\texttt{mepc}, \\texttt{isr\\_sp} i source state."},{"id":"handoff","title":"Zbadaj E06–E08.","content_tex":"Porównaj xHPW i kolejność tasków."},{"id":"pass","title":"Zbadaj E09.","content_tex":"Sprawdź trzy stosy, markers i digest."}]}, {"kind":"exercise","id":"no-yield","title":"Ćwiczenie — brak yield request","prompt_tex":"W kontrolowanym wariancie pomiń \\texttt{portYIELD\\_FROM\\_ISR}, zachowując GiveFromISR. Zmierz, kiedy waiter zacznie działać i wskaż następny legalny scheduling point.","evidence_tex":"Dwa timestampowane ślady E07/E08 z yield i bez yield oraz opis różnicy.","criterion":"Uczeń nie myli odblokowania taska z natychmiastowym wykonaniem i przywraca poprawny yield w wersji finalnej.","based_on":["contract","replay"]} ]} }