OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / src / io_trace.c
4.4 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — I/O event trace ring buffer.
2
//
3
// See io_trace.h for what this is and why it has no emulator dependencies.
4
5
#include "io_trace.h"
6
7
#include <stdarg.h>
8
#include <stdio.h>
9
#include <stdlib.h>
10
#include <string.h>
11
12
#define IO_TRACE_CAPACITY_DEFAULT 4096
13
#define IO_TRACE_CAPACITY_MIN     64
14
#define IO_TRACE_CAPACITY_MAX     262144
15
16
bool     io_trace_enabled     = false;
17
uint32_t io_trace_device_mask = IO_TRACE_MASK_DEFAULT;
18
19
static io_event_t *s_ring  = NULL;
20
static int         s_cap   = 0;
21
static int         s_count = 0;
22
static int         s_head  = 0; // where the next event lands
23
static uint32_t    s_seq   = 0;
24
static uint32_t    s_total = 0;
25
static uint32_t    s_dropped = 0;
26
27
static io_trace_context_fn s_context = NULL;
28
29
void
30
io_trace_set_context_provider(io_trace_context_fn fn)
31
{
32
	s_context = fn;
33
}
34
35
bool
36
io_trace_init(int capacity)
37
{
38
	if (capacity < IO_TRACE_CAPACITY_MIN) {
39
		capacity = IO_TRACE_CAPACITY_MIN;
40
	} else if (capacity > IO_TRACE_CAPACITY_MAX) {
41
		capacity = IO_TRACE_CAPACITY_MAX;
42
	}
43
44
	io_event_t *ring = (io_event_t *)calloc((size_t)capacity, sizeof(io_event_t));
45
	if (ring == NULL) {
46
		return false;
47
	}
48
49
	free(s_ring);
50
	s_ring  = ring;
51
	s_cap   = capacity;
52
	s_count = 0;
53
	s_head  = 0;
54
	// s_seq, s_total and s_dropped deliberately survive: the sequence number is
55
	// the user's only stable handle on "when", and resetting it on a capacity
56
	// change would make two events look simultaneous.
57
	return true;
58
}
59
60
void
61
io_trace_shutdown(void)
62
{
63
	free(s_ring);
64
	s_ring  = NULL;
65
	s_cap   = 0;
66
	s_count = 0;
67
	s_head  = 0;
68
}
69
70
void
71
io_trace_clear(void)
72
{
73
	s_count = 0;
74
	s_head  = 0;
75
	s_dropped = 0;
76
}
77
78
int
79
io_trace_capacity(void)
80
{
81
	return s_cap;
82
}
83
84
int
85
io_trace_count(void)
86
{
87
	return s_count;
88
}
89
90
uint32_t
91
io_trace_total(void)
92
{
93
	return s_total;
94
}
95
96
uint32_t
97
io_trace_dropped(void)
98
{
99
	return s_dropped;
100
}
101
102
const io_event_t *
103
io_trace_at(int index)
104
{
105
	if (s_ring == NULL || index < 0 || index >= s_count) {
106
		return NULL;
107
	}
108
	const int oldest = (s_head - s_count + s_cap) % s_cap;
109
	return &s_ring[(oldest + index) % s_cap];
110
}
111
112
// Claims the next slot, stamped with the CPU context and a fresh sequence
113
// number. Returns NULL when the ring was never allocated, which is what keeps
114
// every call site safe before io_trace_init().
115
static io_event_t *
116
push(io_device_t dev, io_event_kind_t kind)
117
{
118
	if (s_ring == NULL) {
119
		// Allocate on first use so that simply enabling capture works, rather
120
		// than silently recording nothing until someone remembers to init.
121
		if (!io_trace_init(IO_TRACE_CAPACITY_DEFAULT)) {
122
			return NULL;
123
		}
124
	}
125
126
	io_event_t *ev = &s_ring[s_head];
127
	s_head         = (s_head + 1) % s_cap;
128
	if (s_count < s_cap) {
129
		s_count++;
130
	} else {
131
		s_dropped++;
132
	}
133
134
	memset(ev, 0, sizeof(*ev));
135
	ev->seq    = ++s_seq;
136
	ev->device = (uint8_t)dev;
137
	ev->kind   = (uint8_t)kind;
138
	s_total++;
139
140
	if (s_context != NULL) {
141
		s_context(&ev->pc, &ev->pc_bank, &ev->cycles);
142
	}
143
	return ev;
144
}
145
146
void
147
io_trace_access(io_device_t dev, uint16_t addr, uint8_t value, bool is_write)
148
{
149
	io_event_t *ev = push(dev, IO_EVENT_ACCESS);
150
	if (ev == NULL) {
151
		return;
152
	}
153
	ev->addr     = addr;
154
	ev->value    = value;
155
	ev->is_write = is_write;
156
	ev->has_addr = true;
157
}
158
159
static void
160
format_into(io_event_t *ev, const char *fmt, va_list ap)
161
{
162
	int n = vsnprintf(ev->text, sizeof(ev->text), fmt, ap);
163
	if (n < 0) {
164
		ev->text[0] = '\0';
165
	}
166
}
167
168
void
169
io_trace_event(io_device_t dev, const char *fmt, ...)
170
{
171
	io_event_t *ev = push(dev, IO_EVENT_DECODED);
172
	if (ev == NULL) {
173
		return;
174
	}
175
	va_list ap;
176
	va_start(ap, fmt);
177
	format_into(ev, fmt, ap);
178
	va_end(ap);
179
}
180
181
void
182
io_trace_event_at(io_device_t dev, uint16_t addr, bool is_write, const char *fmt, ...)
183
{
184
	io_event_t *ev = push(dev, IO_EVENT_DECODED);
185
	if (ev == NULL) {
186
		return;
187
	}
188
	ev->addr     = addr;
189
	ev->is_write = is_write;
190
	ev->has_addr = true;
191
192
	va_list ap;
193
	va_start(ap, fmt);
194
	format_into(ev, fmt, ap);
195
	va_end(ap);
196
}
197
198
const char *
199
io_trace_device_name(io_device_t dev)
200
{
201
	switch (dev) {
202
		case IO_DEV_VIA1: return "VIA1";
203
		case IO_DEV_VIA2: return "VIA2";
204
		case IO_DEV_VERA: return "VERA";
205
		case IO_DEV_SPI: return "SPI";
206
		case IO_DEV_YM: return "YM2151";
207
		case IO_DEV_EMU: return "Emu";
208
		case IO_DEV_MIDI: return "MIDI";
209
		case IO_DEV_OPENBUS: return "open bus";
210
		case IO_DEV_SDCARD: return "SD card";
211
		case IO_DEV_IEEE: return "Files";
212
		case IO_DEV_I2C: return "I2C";
213
		case IO_DEV_JOYSTICK: return "Joystick";
214
		case IO_DEV_SERIAL: return "Serial";
215
		default: return "?";
216
	}
217
}