| 9120e874 Andy McCall | 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_ |