OXYGENHUB
◆ main enhanced-x16-emulator / src / io_trace.h
6.6 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// Commander X16 Emulator — I/O event trace ring buffer.
2
//
3
// THE PROBLEM: the debugger can show you the CPU, memory, VERA and the audio
4
// sources, but I/O is invisible. When a program talks to the SD card, the
5
// joysticks, the SMC or the IEEE bus and gets the wrong answer back, there is
6
// nothing to look at. The state is spread across a dozen `.c` files as file
7
// statics, and the interesting part is not the state anyway -- it is the
8
// *sequence*: which command went out, what came back, in what order.
9
//
10
// THIS MODULE is the sequence half. It is a fixed-capacity ring of I/O events,
11
// fed from two places:
12
//
13
//   1. the $9F00-$9FFF dispatch in memory.c, which records a raw register
14
//      access (IO_EVENT_ACCESS), and
15
//   2. the devices themselves, which record decoded, human-readable events
16
//      (IO_EVENT_DECODED) such as "CMD17 READ_SINGLE_BLOCK lba=74216".
17
//
18
// WHY IT IS PER-DEVICE GATED: VERA's data ports are touched thousands of times
19
// per frame. Capturing them by default would push every other device out of the
20
// ring within a frame or two, so each device has its own capture bit and VERA
21
// starts off. `io_trace_wants()` is the inline predicate call sites use, so a
22
// device whose bit is clear costs one test-and-branch.
23
//
24
// WHY THIS FILE HAS NO EMULATOR DEPENDENCIES: memory.c is linked into
25
// tests/test_debugon_contract.c and tests/test_watchpoint_purity.c against
26
// recording fakes rather than the real devices. Anything memory.c calls has to
27
// link in that environment too, so this module includes nothing but libc and
28
// its own header. The CPU context stamped onto each event (PC, bank, cycle
29
// count) therefore arrives through a caller-installed provider rather than by
30
// reaching for `regs` and `clockticks6502` directly.
31
//
32
// THREADING: the emulator steps devices and renders the debugger on the same
33
// thread, so the ring needs no locking. The SDL audio callback thread runs
34
// independently but touches none of these devices. Do not call into this
35
// module from the audio callback.
36
#ifndef _IO_TRACE_H_
37
#define _IO_TRACE_H_
38
39
#include <stdbool.h>
40
#include <stdint.h>
41
42
#ifdef __cplusplus
43
extern "C" {
44
#endif
45
46
// The devices a trace event can be attributed to. The first group corresponds
47
// to address ranges in the I/O page; the second to devices that sit behind
48
// another device's registers and can only report themselves.
49
typedef enum {
50
	IO_DEV_VIA1 = 0,
51
	IO_DEV_VIA2,
52
	IO_DEV_VERA,
53
	IO_DEV_SPI, // VERA $9F3E/$9F3F -- the SD card data path, see below
54
	IO_DEV_YM,
55
	IO_DEV_EMU,
56
	IO_DEV_MIDI,
57
	IO_DEV_OPENBUS,
58
	IO_DEV_SDCARD, // decoded SD commands and responses
59
	IO_DEV_IEEE,   // host-filesystem file access, with real filenames
60
	IO_DEV_I2C,    // the I2C bus, and the SMC/RTC behind it
61
	IO_DEV_JOYSTICK,
62
	IO_DEV_SERIAL,
63
	IO_DEV_COUNT
64
} io_device_t;
65
66
// $9F3E/$9F3F are VERA registers, but they are also the *only* way the SD card
67
// is reached. They get their own device so that turning VERA capture off -- the
68
// default, because of the data ports -- does not also blind you to SD traffic.
69
// This is a deliberate departure from a strict address-range mapping.
70
71
typedef enum {
72
	IO_EVENT_ACCESS = 0, // a raw register read or write
73
	IO_EVENT_DECODED,    // a device-decoded event; `text` carries the meaning
74
} io_event_kind_t;
75
76
#define IO_TRACE_TEXT_MAX 72
77
78
typedef struct {
79
	uint32_t seq;     // monotonic, never reused; survives capacity changes
80
	uint32_t cycles;  // clockticks6502 when recorded (wraps, and $9FB8 resets it)
81
	uint16_t pc;      // CPU PC at the time of the access
82
	uint16_t addr;    // register address; 0 for decoded events with no register
83
	uint8_t  pc_bank; // 65C816 program bank
84
	uint8_t  value;
85
	uint8_t  device;  // io_device_t
86
	uint8_t  kind;    // io_event_kind_t
87
	bool     is_write;
88
	bool     has_addr; // false for decoded events that name no register
89
	char     text[IO_TRACE_TEXT_MAX];
90
} io_event_t;
91
92
// Master switch and the per-device capture bits. Both are read directly by the
93
// inline predicate below, so flipping either takes effect on the next access.
94
extern bool     io_trace_enabled;
95
extern uint32_t io_trace_device_mask;
96
97
#define IO_TRACE_BIT(dev) (1u << (unsigned)(dev))
98
99
// Everything except VERA's registers and open-bus reads. See the header comment
100
// for why VERA is excluded: it would otherwise be the only thing in the ring.
101
#define IO_TRACE_MASK_DEFAULT \
102
	(~(IO_TRACE_BIT(IO_DEV_VERA) | IO_TRACE_BIT(IO_DEV_OPENBUS)))
103
104
// The predicate every call site uses. Kept inline and side-effect-free so that
105
// a disabled trace costs a load and a branch on the I/O hot path.
106
static inline bool
107
io_trace_wants(io_device_t dev)
108
{
109
	return io_trace_enabled && (io_trace_device_mask & IO_TRACE_BIT(dev)) != 0;
110
}
111
112
// Supplies the CPU context stamped onto each event. Installed by memory.c;
113
// when absent, events are recorded with a zero PC and cycle count rather than
114
// not at all, so the module stays usable in unit tests.
115
typedef void (*io_trace_context_fn)(uint16_t *pc, uint8_t *pc_bank, uint32_t *cycles);
116
void io_trace_set_context_provider(io_trace_context_fn fn);
117
118
// Allocates the ring. Safe to call again to resize; existing events are
119
// discarded because their ordering relative to a new ring is not meaningful.
120
// Returns false if the allocation failed, in which case capture stays off.
121
bool io_trace_init(int capacity);
122
void io_trace_shutdown(void);
123
void io_trace_clear(void);
124
125
int      io_trace_capacity(void);
126
int      io_trace_count(void);
127
uint32_t io_trace_total(void);   // events ever recorded, including overwritten
128
uint32_t io_trace_dropped(void); // events overwritten by the ring wrapping
129
130
// Index 0 is the OLDEST retained event. Returns NULL when out of range.
131
const io_event_t *io_trace_at(int index);
132
133
// Record a raw register access. Call AFTER the device handler for reads, so the
134
// value is the one the machine actually saw; reads have side effects (VERA
135
// auto-increment, SPI byte consumption) and must never be repeated to observe
136
// them. Call BEFORE the handler for writes, so that any decoded event the
137
// device emits in response is ordered after the access that caused it.
138
void io_trace_access(io_device_t dev, uint16_t addr, uint8_t value, bool is_write);
139
140
// Record a decoded event. `fmt` is printf-style and the result is truncated to
141
// IO_TRACE_TEXT_MAX. Call sites should still guard with io_trace_wants() to
142
// avoid formatting work that will be thrown away.
143
void io_trace_event(io_device_t dev, const char *fmt, ...);
144
145
// As io_trace_event(), but attributed to a specific register.
146
void io_trace_event_at(io_device_t dev, uint16_t addr, bool is_write, const char *fmt, ...);
147
148
const char *io_trace_device_name(io_device_t dev);
149
150
#ifdef __cplusplus
151
}
152
#endif
153
154
#endif