| 1 | // Commander X16 Emulator — interrupt context tracking. |
| 2 | // |
| 3 | // The CPU core has no notion of "currently servicing an interrupt": it vectors |
| 4 | // through, and the only trace is a return address on the stack. A debugger |
| 5 | // needs more than that. It wants to say whether you are inside a handler and |
| 6 | // how deep, to stop at the first instruction of one instead of stepping over |
| 7 | // the whole thing invisibly, and to show where the handler will return to. |
| 8 | // |
| 9 | // interrupt6502() reports an entry and rti() reports a leave; the two are |
| 10 | // paired by matching the stack pointer at entry exactly, which keeps nested |
| 11 | // handlers straight and unwinds several at once when a handler returns past |
| 12 | // its own frame. |
| 13 | // |
| 14 | // A handler that leaves by some other route -- discarding its frame and exiting |
| 15 | // through RTS or JMP, or a BRK that lands in a warm start which resets the |
| 16 | // stack -- has no matching RTI. Those entries are retired the next time an |
| 17 | // interrupt is taken at or above where they were recorded, since the stack |
| 18 | // having risen back past a frame proves it is gone. Until then the depth reads |
| 19 | // high rather than low, which is the safe direction: claiming no interrupt is |
| 20 | // running while one is would be worse than being stale. |
| 21 | // |
| 22 | // What this cannot survive is the stack pointer moving for a reason other than |
| 23 | // nesting. Both rules -- frames decreasing in sp, and a balanced RTI landing |
| 24 | // exactly on the recorded pointer -- read position as evidence of depth. A |
| 25 | // guest that pushes past the bottom of page 1 wraps, and a handler that |
| 26 | // relocates the stack with TXS or TCS moves it outright; either can make a live |
| 27 | // outer frame look like one the stack has passed, and it will be retired. |
| 28 | // |
| 29 | // No rule based on the stack pointer alone can tell those apart. What decides |
| 30 | // it is not which case is commoner but how each fails: retiring wrongly drops |
| 31 | // however many outer handlers the bad comparison covered, but only until the |
| 32 | // next interrupt taken at a sane stack level, whereas never retiring leaves a |
| 33 | // frame open for the rest of the session and adds another every time it |
| 34 | // happens. A bounded, self-healing error beats an unbounded permanent one. |
| 35 | // |
| 36 | // A consumer that acts on cpu_irq_return_pc() -- setting a breakpoint there to |
| 37 | // step over a handler, say -- therefore wants an explicit validity signal, so |
| 38 | // it can fall back to single-stepping rather than trusting a stale address. |
| 39 | // That belongs with the consumer, which does not exist yet. |
| 40 | // |
| 41 | // Kept apart from fake6502.c so that the CPU core carries only the two calls, |
| 42 | // and so the bookkeeping can be tested without standing up a machine. |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | extern "C" |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | |
| 82 | __cplusplus |
| 83 | } |
| 84 | |
| 85 | |
| 86 | // _CPU_IRQ_CTX_H_ |