OXYGENHUB
◆ main enhanced-x16-emulator / src / cpu / irq_ctx.c
5.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — interrupt context tracking. See irq_ctx.h.
2
3
#include "irq_ctx.h"
4
5
#include <string.h>
6
7
// Past CPU_IRQ_CTX_MAX (see irq_ctx.h) the depth still counts, but there is no
8
// room to record which stack pointer each of those entries had. A return can
9
// therefore only take them off one at a time, since it has nothing to match
10
// against; an entry can still clear them wholesale, by judging them against the
11
// deepest frame that does have a recorded pointer.
12
13
// Nesting beyond this is a runaway loop, and the exact number stops meaning
14
// anything. This is reachable, not merely defensive. Once the depth passes the
15
// array, frames[] stops being written, so the pointer everything above is
16
// judged against -- frames[CPU_IRQ_CTX_MAX - 1] -- is frozen. Interrupts taken
17
// strictly below it therefore add a level every time with nothing able to
18
// retire any of them, and they need not even keep descending. A handler that
19
// BRKs on entry would climb until a signed counter overflowed, which is
20
// undefined behaviour rather than merely a wrong answer.
21
#define CPU_IRQ_DEPTH_MAX 0x10000
22
23
struct irq_frame {
24
	uint8_t  vector;
25
	uint32_t from_pc;  // 24-bit: the program bank matters on the 65816
26
	uint16_t sp;       // stack pointer before the interrupt pushed anything
27
};
28
29
static struct irq_frame frames[CPU_IRQ_CTX_MAX];
30
static int              depth;
31
static uint32_t         taken;
32
static uint8_t          last_vector;
33
static bool             just_entered;
34
35
void
36
cpu_irq_ctx_enter(int vector, uint32_t from_pc, uint16_t sp)
37
{
38
	taken++;
39
	last_vector  = (uint8_t)vector;
40
	just_entered = true;
41
42
	// Retire frames the stack has already passed. Nesting drives the pointer
43
	// DOWN, so open frames must be strictly decreasing in sp; one recorded at or
44
	// below where this interrupt is being taken cannot still be live, because
45
	// the stack has risen back above it.
46
	//
47
	// This is what clears a handler that left without an RTI -- a BRK reaching a
48
	// warm start, which resets the stack instead of returning. Nothing on the
49
	// return path can do it: the abandoned frame has no matching return, so it
50
	// would sit there for the rest of the session, and every further BRK would
51
	// add another.
52
	//
53
	// Judged here rather than on return because entry is the moment the stack
54
	// level is known to be real. A return only tells us where the stack ended
55
	// up, which a handler is free to fake.
56
	//
57
	// Levels above the array have no recorded pointer of their own, so they are
58
	// judged by the deepest one that has: an interrupt taken at or above
59
	// frames[CPU_IRQ_CTX_MAX - 1] proves everything from there up is gone,
60
	// unrecorded levels included. Skipping them instead would strand every frame
61
	// underneath as soon as the depth passed the array, which is the same
62
	// permanent staleness this rule exists to prevent.
63
	while (depth > 0) {
64
		const int top = (depth < CPU_IRQ_CTX_MAX ? depth : CPU_IRQ_CTX_MAX) - 1;
65
		if (frames[top].sp > sp)
66
			break;
67
		depth = top;
68
	}
69
70
	if (depth >= 0 && depth < CPU_IRQ_CTX_MAX) {
71
		frames[depth].vector  = (uint8_t)vector;
72
		frames[depth].from_pc = from_pc;
73
		frames[depth].sp      = sp;
74
	}
75
	if (depth < CPU_IRQ_DEPTH_MAX)
76
		depth++;
77
}
78
79
void
80
cpu_irq_ctx_leave(uint16_t sp)
81
{
82
	// An RTI outside anything we saw entered is not an error -- an interrupt
83
	// could already have been in flight when the emulator started, or a handler
84
	// could have been entered before a reset. There is simply nothing to pop.
85
	if (depth <= 0) {
86
		depth = 0;
87
		return;
88
	}
89
90
	// Nothing was recorded for entries above the array, so they come off singly.
91
	if (depth > CPU_IRQ_CTX_MAX) {
92
		depth--;
93
		return;
94
	}
95
96
	// A balanced RTI restores the stack pointer to exactly what it was when the
97
	// interrupt was taken, so the frame being returned from is the one whose
98
	// recorded pointer equals this one. Matching exactly, rather than measuring
99
	// how far the stack has moved, is what keeps this honest: a distance test
100
	// cannot tell a genuine unwind from a stack that wrapped, and guessing
101
	// wrong collapses the depth to zero while a handler is still running.
102
	//
103
	// Retirement on entry keeps the recorded pointers strictly decreasing, so
104
	// at most one frame can match and the search direction does not matter.
105
	// Searching outwards also handles a handler that returns past its own
106
	// frame, unwinding several at once.
107
	for (int i = depth - 1; i >= 0; i--) {
108
		if (frames[i].sp == sp) {
109
			depth = i;
110
			return;
111
		}
112
	}
113
114
	// No frame matches: a handler that rewrote the stack, or an RTI for an
115
	// interrupt taken before this was watching. Leave the depth alone rather
116
	// than popping a frame that may still be live -- a handler can fabricate a
117
	// frame and RTI to itself with three pushes, which lands on no recorded
118
	// pointer at all while the real interrupt is still running.
119
	//
120
	// So this errs high, and stays there until a return matches an outer frame.
121
	// Too high corrects itself at the next matching return; too low would claim
122
	// no interrupt is running while one is, and a debugger confidently
123
	// reporting that is worse than one reporting a stale depth.
124
}
125
void
126
cpu_irq_ctx_reset(void)
127
{
128
	depth        = 0;
129
	taken        = 0;
130
	last_vector  = 0;
131
	just_entered = false;
132
	memset(frames, 0, sizeof(frames));
133
}
134
135
bool
136
cpu_in_interrupt(void)
137
{
138
	return depth > 0;
139
}
140
141
int
142
cpu_irq_depth(void)
143
{
144
	return depth;
145
}
146
147
uint32_t
148
cpu_irq_count(void)
149
{
150
	return taken;
151
}
152
153
int
154
cpu_irq_last_vector(void)
155
{
156
	return last_vector;
157
}
158
159
uint32_t
160
cpu_irq_return_pc(void)
161
{
162
	if (depth <= 0)
163
		return 0;
164
	const int top = depth - 1;
165
	return top < CPU_IRQ_CTX_MAX ? frames[top].from_pc : 0;
166
}
167
168
bool
169
cpu_irq_take_entered_flag(void)
170
{
171
	const bool v = just_entered;
172
	just_entered = false;
173
	return v;
174
}