OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / src / debug_ui / panels / io_panel.cpp
71.4 KiB · C++ · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// I/O panel — what the machine is doing to its ports, and to files.
2
//
3
// The other panels answer "what is the state of X". This one mostly answers
4
// "what just happened", because I/O is a conversation: a command goes out, a
5
// status comes back, a block is read, a file is opened. A register dump of the
6
// SD card taken between commands tells you almost nothing; the sequence tells
7
// you everything.
8
//
9
// ON THE TWO FILE PATHS, because this is the panel where the difference bites:
10
// the emulator can serve files from the host filesystem OR from an attached SD
11
// card image, and by default those are mutually exclusive -- passing -sdcard
12
// turns host-filesystem access off unless -hostfsdev asks for it back. Only the
13
// host path knows filenames; an SD image is 512-byte blocks, with the
14
// filesystem parsed by ROM inside the emulated machine. So the SD half of the
15
// Files tab depends on sdcard_fat.c parsing the image on our behalf.
16
//
17
// ON TOOLTIPS: this panel describes hardware most people do not have memorised,
18
// and a bare hex byte with no way to find out what it means is not debugging
19
// information. Every label and every value is hoverable, and the pair share one
20
// explanation, so a reader can point at whichever half confused them.
21
#include "imgui.h"
22
#include "debug_ui_panels.h"
23
#include "debug_ui_bridge.h"
24
#include "debug_ui_settings.h"
25
#include "debug_ui_widgets.h"
26
#include "io_reg_info.h"
27
28
#include <stdarg.h>
29
#include <stdint.h>
30
#include <stdio.h>
31
#include <string.h>
32
33
namespace {
34
35
const ImVec4 COL_READ    = ImVec4(0.55f, 0.78f, 1.00f, 1.0f);
36
const ImVec4 COL_WRITE   = ImVec4(1.00f, 0.72f, 0.42f, 1.0f);
37
const ImVec4 COL_DECODED = ImVec4(0.62f, 0.90f, 0.62f, 1.0f);
38
const ImVec4 COL_DIM     = ImVec4(0.70f, 0.70f, 0.78f, 1.0f);
39
const ImVec4 COL_BAD     = ImVec4(0.95f, 0.55f, 0.45f, 1.0f);
40
const ImVec4 COL_ON      = ImVec4(0.45f, 0.95f, 0.50f, 1.0f);
41
42
// Tooltip on whatever widget was just submitted.
43
void
44
tip(const char *text)
45
{
46
    if (ImGui::IsItemHovered()) {
47
        ImGui::BeginTooltip();
48
        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 32.0f);
49
        ImGui::TextUnformatted(text);
50
        ImGui::PopTextWrapPos();
51
        ImGui::EndTooltip();
52
    }
53
}
54
55
// Attaches an explanation to the item just drawn. Named `help` for the places
56
// that read as "explain this"; identical to tip().
57
//
58
// It deliberately does NOT draw a "(?)" marker. Thirty of those in one panel is
59
// clutter, and it puts the explanation on a decoration rather than on the thing
60
// being explained -- the label and the value are what a reader reaches for.
61
// Discoverability is handled once, by the hint at the top of the panel.
62
void
63
help(const char *text)
64
{
65
    tip(text);
66
}
67
68
// A heading that is itself hoverable, so the explanation is on the thing being
69
// explained rather than on a marker beside it.
70
void
71
heading(const char *text, const char *tip_text)
72
{
73
    ImGui::TextUnformatted(text);
74
    tip(tip_text);
75
}
76
77
// The tooltip for the row currently being drawn, so the value cell can carry
78
// the same explanation as its label without the caller repeating it.
79
const char *g_row_tip = nullptr;
80
81
// Start a label/value row. The label is hoverable; call row_end() after drawing
82
// the value to make that hoverable too.
83
void
84
row_begin(const char *label, const char *tip_text)
85
{
86
    ImGui::TableNextRow();
87
    ImGui::TableSetColumnIndex(0);
88
    ImGui::TextColored(COL_DIM, "%s", label);
89
    tip(tip_text);
90
    ImGui::TableSetColumnIndex(1);
91
    g_row_tip = tip_text;
92
}
93
94
void
95
row_end()
96
{
97
    tip(g_row_tip);
98
}
99
100
// The common case: a formatted value with the same tooltip on both cells.
101
void
102
row_kv(const char *label, const char *tip_text, const char *fmt, ...)
103
{
104
    row_begin(label, tip_text);
105
    va_list ap;
106
    va_start(ap, fmt);
107
    ImGui::TextV(fmt, ap);
108
    va_end(ap);
109
    row_end();
110
}
111
112
// A lit/unlit indicator, for things that are physically a lamp or a bus line.
113
void
114
lamp(bool on, const char *label)
115
{
116
    ImGui::TextColored(on ? COL_ON : COL_DIM, "%s %s", on ? "\xe2\x97\x8f" : "\xe2\x97\x8b", label);
117
}
118
119
// ---------------------------------------------------------------------------
120
// Capture gating
121
// ---------------------------------------------------------------------------
122
123
// The settings own the capture preferences; io_trace owns the mask the hot path
124
// actually tests. Pushing one into the other once per frame keeps the hot path
125
// reading a single word, rather than chasing the settings struct on every I/O
126
// access.
127
void
128
apply_capture_settings()
129
{
130
    const DebugUiSettings &s = debug_ui_settings();
131
132
    uint32_t mask = 0;
133
    if (s.io_cap_via) {
134
        mask |= IO_TRACE_BIT(IO_DEV_VIA1) | IO_TRACE_BIT(IO_DEV_VIA2);
135
    }
136
    if (s.io_cap_vera) {
137
        mask |= IO_TRACE_BIT(IO_DEV_VERA);
138
    }
139
    if (s.io_cap_spi) {
140
        mask |= IO_TRACE_BIT(IO_DEV_SPI);
141
    }
142
    if (s.io_cap_ym) {
143
        mask |= IO_TRACE_BIT(IO_DEV_YM);
144
    }
145
    if (s.io_cap_emu) {
146
        mask |= IO_TRACE_BIT(IO_DEV_EMU);
147
    }
148
    if (s.io_cap_midi) {
149
        mask |= IO_TRACE_BIT(IO_DEV_MIDI);
150
    }
151
    if (s.io_cap_openbus) {
152
        mask |= IO_TRACE_BIT(IO_DEV_OPENBUS);
153
    }
154
    if (s.io_cap_sd) {
155
        mask |= IO_TRACE_BIT(IO_DEV_SDCARD);
156
    }
157
    if (s.io_cap_files) {
158
        mask |= IO_TRACE_BIT(IO_DEV_IEEE);
159
    }
160
    if (s.io_cap_i2c) {
161
        mask |= IO_TRACE_BIT(IO_DEV_I2C);
162
    }
163
    if (s.io_cap_joy) {
164
        mask |= IO_TRACE_BIT(IO_DEV_JOYSTICK);
165
    }
166
    mask |= IO_TRACE_BIT(IO_DEV_SERIAL);
167
168
    io_trace_device_mask = mask;
169
    io_trace_enabled     = s.io_trace_capture;
170
171
    // The index is built by sdcard_attach(), which is C and cannot see the
172
    // settings struct; this flag is how the preference reaches it.
173
    sdcard_fat_autoindex = s.io_fat_autoindex;
174
175
    if (io_trace_capacity() != 0 && io_trace_capacity() != s.io_trace_capacity) {
176
        io_trace_init(s.io_trace_capacity);
177
        // io_trace clamps; write back what was actually allocated so the
178
        // settings field shows the real number rather than the wish.
179
        debug_ui_settings().io_trace_capacity = io_trace_capacity();
180
    }
181
}
182
183
// ---------------------------------------------------------------------------
184
// Activity
185
// ---------------------------------------------------------------------------
186
187
char s_filter[64]  = "";
188
bool s_autoscroll  = true;
189
bool s_show_reads  = true;
190
bool s_show_writes = true;
191
192
bool
193
event_matches(const io_event_t &ev)
194
{
195
    if (ev.kind == IO_EVENT_ACCESS) {
196
        if (ev.is_write && !s_show_writes) {
197
            return false;
198
        }
199
        if (!ev.is_write && !s_show_reads) {
200
            return false;
201
        }
202
    }
203
    if (s_filter[0] == '\0') {
204
        return true;
205
    }
206
207
    // Match against what the user can actually see in the row, so a filter that
208
    // hides a visible row would be a bug rather than a subtlety.
209
    char addr[16];
210
    snprintf(addr, sizeof(addr), "$%04X", ev.addr);
211
212
    return (strstr(io_trace_device_name((io_device_t)ev.device), s_filter) != nullptr) ||
213
           (ev.text[0] != '\0' && strstr(ev.text, s_filter) != nullptr) ||
214
           (ev.has_addr && strstr(addr, s_filter) != nullptr) ||
215
           (ev.has_addr && strstr(io_reg_name(ev.addr), s_filter) != nullptr);
216
}
217
218
struct DeviceToggle {
219
    const char *label;
220
    bool DebugUiSettings::*field;
221
    const char *tip;
222
};
223
224
// Every one of these is off by default. The tooltips exist because "should I
225
// tick VERA?" is only answerable if you know both what it adds over the VERA
226
// panel and what it costs.
227
const DeviceToggle RAW_TOGGLES[] = {
228
    {"VIA", &DebugUiSettings::io_cap_via,
229
     "$9F00-$9F1F. Both VIAs: the joystick latch and clock, the I2C lines to the "
230
     "SMC and RTC, the IEC serial lines, and the two timers.\n\n"
231
     "Adds over the VIA tab: the order and timing of accesses. The tab shows the "
232
     "registers as they are now; this shows the polling loop that got them there.\n\n"
233
     "Rate: moderate. The KERNAL touches the VIA every frame."},
234
    {"VERA", &DebugUiSettings::io_cap_vera,
235
     "$9F20-$9F3F. All 32 VERA registers.\n\n"
236
     "Adds over the VERA tab: that tab decodes current state, but says nothing "
237
     "about the sequence that produced it. Only the log shows the address-then-data "
238
     "handshake, a mid-frame register change, or a raster split as it happens.\n\n"
239
     "Rate: VERY HIGH. DATA0/DATA1 auto-increment, so a screen fill is one access "
240
     "per byte -- thousands per frame. Expect this alone to fill the ring."},
241
    {"SPI", &DebugUiSettings::io_cap_spi,
242
     "$9F3E/$9F3F only. These are VERA registers, but they are also the only path "
243
     "to the SD card, so they are captured separately -- ticking SPI does not drag "
244
     "in VERA's data ports.\n\n"
245
     "Adds over the SD Card tab: the raw byte stream, for when you need the SPI "
246
     "conversation itself rather than the decoded command.\n\n"
247
     "Rate: high while the card is busy -- one access per byte, 512 per block."},
248
    {"YM", &DebugUiSettings::io_cap_ym,
249
     "$9F40-$9F5F. The YM2151's register-select latch and data port.\n\n"
250
     "Adds over the YM2151 tab: which registers a music driver writes and in what "
251
     "order, per frame. The tab shows the resulting channel state.\n\n"
252
     "Rate: moderate; a music driver typically writes a burst every frame."},
253
    {"Emu", &DebugUiSettings::io_cap_emu,
254
     "$9FB0-$9FBF. The emulator's own control registers: debug toggles, the cycle "
255
     "counter, character output, and the \"16\" detection bytes.\n\n"
256
     "Nothing else shows these. Useful for watching a program probe for the "
257
     "emulator or drive its recording controls.\n\n"
258
     "Rate: very low."},
259
    {"MIDI", &DebugUiSettings::io_cap_midi,
260
     "The MIDI card, if one is fitted. Its base address is configurable, so this "
261
     "follows wherever it was placed.\n\n"
262
     "Rate: low, and zero when no card is fitted."},
263
    {"Open bus", &DebugUiSettings::io_cap_openbus,
264
     "Addresses in the I/O page that no device answers -- reads return $9F.\n\n"
265
     "Almost always noise, but it is the one way to catch a program reading a "
266
     "device it thinks exists and silently getting garbage back.\n\n"
267
     "Rate: normally zero."},
268
};
269
270
const DeviceToggle DECODED_TOGGLES[] = {
271
    {"SD", &DebugUiSettings::io_cap_sd,
272
     "Decoded SD card activity: each command by name (CMD17 READ_SINGLE_BLOCK), and "
273
     "every 512-byte block read or written -- resolved to a filename and offset when "
274
     "the image has been indexed.\n\n"
275
     "This is the one to leave on when you care about disk access.\n\n"
276
     "Rate: low. A handful per command, one per block."},
277
    {"Files", &DebugUiSettings::io_cap_files,
278
     "Host-filesystem file access with real filenames: opens, closes, directory "
279
     "listings, DOS commands and status codes.\n\n"
280
     "The Files tab keeps its own always-on history of these, so you do not need "
281
     "capture on to see them there. This only adds them to this log, in sequence "
282
     "with everything else.\n\n"
283
     "Rate: low -- bounded by real file operations."},
284
    {"I2C", &DebugUiSettings::io_cap_i2c,
285
     "One event per completed I2C transaction: which device, direction, and how many "
286
     "bytes moved.\n\n"
287
     "Rate: about 60/second and CONSTANT. The KERNAL polls the SMC for keyboard input "
288
     "every frame whether or not a key is pressed, so this will slowly push everything "
289
     "else out of the ring even on an idle machine."},
290
    {"Joystick", &DebugUiSettings::io_cap_joy,
291
     "One event each time the controllers are latched, with the sampled button masks.\n\n"
292
     "Rate: about 60/second and CONSTANT -- a game reads the pad every frame whether "
293
     "or not anything is pressed."},
294
};
295
296
void
297
draw_capture_controls()
298
{
299
    DebugUiSettings &s       = debug_ui_settings();
300
    bool             changed = false;
301
302
    changed |= ImGui::Checkbox("Capture", &s.io_trace_capture);
303
    tip("Record I/O events into the log below.\n\n"
304
        "Off by default. This is the only debugger feature the running machine pays "
305
        "for -- a test and a branch on every I/O access, and a copy on every captured "
306
        "one -- and the I/O page is the busiest address range in the system. Every "
307
        "other tab in this panel works without it.");
308
309
    ImGui::SameLine();
310
    if (ImGui::SmallButton("Clear")) {
311
        io_trace_clear();
312
    }
313
    tip("Discard everything currently in the log.");
314
315
    ImGui::SameLine();
316
    ImGui::Checkbox("Auto-scroll", &s_autoscroll);
317
    tip("Follow the newest event. Scroll up to stop following; scroll back to the "
318
        "bottom to resume.");
319
320
    ImGui::SameLine(0, 16);
321
    ImGui::SetNextItemWidth(dbgui_field_width("wwwwwwwwwwww"));
322
    ImGui::InputTextWithHint("##iofilter", "filter", s_filter, sizeof(s_filter));
323
    tip("Show only rows whose device, address, register name or text contains this. "
324
        "Case-sensitive substring match, e.g. \"DATA0\", \"$9F23\", \"SD\".");
325
326
    ImGui::SameLine(0, 16);
327
    ImGui::Checkbox("R", &s_show_reads);
328
    tip("Show reads.");
329
    ImGui::SameLine();
330
    ImGui::Checkbox("W", &s_show_writes);
331
    tip("Show writes.");
332
333
    ImGui::TextUnformatted("Registers:");
334
    help("Raw register accesses, by address range. These overlap the dedicated tabs "
335
         "only in subject, not in content: a tab shows you state, this log shows you "
336
         "sequence and timing -- what was written, in what order, and from which PC.");
337
    for (const DeviceToggle &t : RAW_TOGGLES) {
338
        ImGui::SameLine();
339
        changed |= ImGui::Checkbox(t.label, &(s.*(t.field)));
340
        tip(t.tip);
341
    }
342
343
    ImGui::TextUnformatted("Decoded: ");
344
    help("Device-level events rather than register accesses -- an SD command by name, "
345
         "a file being opened, an I2C transaction. These are what the raw bytes mean.");
346
    for (const DeviceToggle &t : DECODED_TOGGLES) {
347
        ImGui::SameLine();
348
        changed |= ImGui::Checkbox(t.label, &(s.*(t.field)));
349
        tip(t.tip);
350
    }
351
352
    if (changed) {
353
        debug_ui_settings_mark_dirty();
354
    }
355
356
    if (!s.io_trace_capture) {
357
        ImGui::TextColored(COL_DIM, "Capture is off. Tick Capture, then pick at least one device.");
358
    } else if (io_trace_device_mask == IO_TRACE_BIT(IO_DEV_SERIAL)) {
359
        ImGui::TextColored(COL_DIM, "No devices selected, so nothing is being recorded.");
360
    } else {
361
        ImGui::TextColored(COL_DIM, "%d/%d events", io_trace_count(), io_trace_capacity());
362
        const uint32_t dropped = io_trace_dropped();
363
        if (dropped > 0) {
364
            ImGui::SameLine();
365
            ImGui::TextColored(COL_DIM, "\xc2\xb7 %u overwritten", dropped);
366
            tip("The ring wrapped and older events were discarded. Raise the capacity in "
367
                "System > Settings, or capture fewer devices.");
368
        }
369
    }
370
}
371
372
void
373
draw_activity_tab()
374
{
375
    draw_capture_controls();
376
    ImGui::Separator();
377
378
    if (ImGui::BeginTable("io_activity", 7,
379
                          ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
380
                              ImGuiTableFlags_BordersInnerV | DBGUI_TABLE_FLAGS_RESIZABLE)) {
381
        ImGui::TableSetupScrollFreeze(0, 1);
382
        ImGui::TableSetupColumn("Seq", ImGuiTableColumnFlags_WidthFixed,
383
                                dbgui_col_width("Seq", "8888888"));
384
        ImGui::TableSetupColumn("PC", ImGuiTableColumnFlags_WidthFixed,
385
                                dbgui_col_width("PC", "88:8888"));
386
        ImGui::TableSetupColumn("Device", ImGuiTableColumnFlags_WidthFixed,
387
                                dbgui_col_width("Device", "Joystick"));
388
        ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed,
389
                                dbgui_col_width("Addr", "$FFFF"));
390
        ImGui::TableSetupColumn("Register", ImGuiTableColumnFlags_WidthFixed,
391
                                dbgui_col_width("Register", "L0_HSCROLL_L"));
392
        ImGui::TableSetupColumn("R/W", ImGuiTableColumnFlags_WidthFixed,
393
                                dbgui_col_width("R/W", "W"));
394
        ImGui::TableSetupColumn("Value / event", ImGuiTableColumnFlags_WidthStretch);
395
        ImGui::TableHeadersRow();
396
397
        // Oldest first, so the log reads downward like any other log and
398
        // auto-scroll means "follow the newest".
399
        const int total = io_trace_count();
400
        for (int i = 0; i < total; i++) {
401
            const io_event_t *evp = io_trace_at(i);
402
            if (evp == nullptr) {
403
                continue;
404
            }
405
            const io_event_t &ev = *evp;
406
            if (!event_matches(ev)) {
407
                continue;
408
            }
409
            const bool decoded = (ev.kind == IO_EVENT_DECODED);
410
411
            ImGui::TableNextRow();
412
            ImGui::TableSetColumnIndex(0);
413
            ImGui::TextColored(COL_DIM, "%u", ev.seq);
414
            if (ImGui::IsItemHovered()) {
415
                ImGui::SetTooltip("cycle %u", ev.cycles);
416
            }
417
418
            ImGui::TableSetColumnIndex(1);
419
            ImGui::TextColored(COL_DIM, "%02X:%04X", ev.pc_bank, ev.pc);
420
            tip("Where the CPU was when this happened.");
421
422
            ImGui::TableSetColumnIndex(2);
423
            ImGui::TextUnformatted(io_trace_device_name((io_device_t)ev.device));
424
425
            ImGui::TableSetColumnIndex(3);
426
            if (ev.has_addr) {
427
                ImGui::Text("$%04X", ev.addr);
428
                tip(io_reg_purpose(ev.addr));
429
            } else {
430
                ImGui::TextUnformatted("-");
431
            }
432
433
            ImGui::TableSetColumnIndex(4);
434
            if (ev.has_addr) {
435
                ImGui::TextColored(COL_DIM, "%s", io_reg_name(ev.addr));
436
                tip(io_reg_purpose(ev.addr));
437
            } else {
438
                ImGui::TextUnformatted(" ");
439
            }
440
441
            ImGui::TableSetColumnIndex(5);
442
            if (decoded && !ev.has_addr) {
443
                ImGui::TextUnformatted(" ");
444
            } else {
445
                ImGui::TextColored(ev.is_write ? COL_WRITE : COL_READ, "%s",
446
                                   ev.is_write ? "W" : "R");
447
            }
448
449
            ImGui::TableSetColumnIndex(6);
450
            if (decoded) {
451
                ImGui::TextColored(COL_DECODED, "%s", ev.text);
452
            } else {
453
                ImGui::Text("$%02X", ev.value);
454
                dbgui_hover_value_tooltip(io_reg_name(ev.addr), ev.value, 1);
455
            }
456
        }
457
458
        if (s_autoscroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 1.0f) {
459
            ImGui::SetScrollHereY(1.0f);
460
        }
461
        ImGui::EndTable();
462
    }
463
}
464
465
// ---------------------------------------------------------------------------
466
// SD card
467
// ---------------------------------------------------------------------------
468
469
const char *
470
sd_command_name(uint8_t cmd, bool is_acmd)
471
{
472
    if (is_acmd) {
473
        return (cmd == 41) ? "ACMD41 SD_SEND_OP_COND" : nullptr;
474
    }
475
    switch (cmd) {
476
        case 0:  return "CMD0 GO_IDLE_STATE";
477
        case 1:  return "CMD1 SEND_OP_COND";
478
        case 8:  return "CMD8 SEND_IF_COND";
479
        case 9:  return "CMD9 SEND_CSD";
480
        case 10: return "CMD10 SEND_CID";
481
        case 12: return "CMD12 STOP_TRANSMISSION";
482
        case 16: return "CMD16 SET_BLOCKLEN";
483
        case 17: return "CMD17 READ_SINGLE_BLOCK";
484
        case 18: return "CMD18 READ_MULTIPLE_BLOCK";
485
        case 24: return "CMD24 WRITE_BLOCK";
486
        case 25: return "CMD25 WRITE_MULTIPLE_BLOCK";
487
        case 55: return "CMD55 APP_CMD";
488
        case 58: return "CMD58 READ_OCR";
489
        default: return nullptr;
490
    }
491
}
492
493
// Shown wherever the absence of a card would otherwise look like a broken
494
// panel. The distinction is not obvious and costs people real time.
495
void
496
explain_no_sdcard()
497
{
498
    ImGui::TextColored(COL_DIM, "No SD card image is attached.");
499
    ImGui::Spacing();
500
    ImGui::TextUnformatted("Attach one with:");
501
    ImGui::Indent();
502
    ImGui::TextColored(COL_DECODED, "x16emu -sdcard mycard.img");
503
    ImGui::Unindent();
504
    ImGui::Spacing();
505
    ImGui::TextWrapped(
506
        "-fsroot does NOT attach a card. It sets the host directory the machine sees "
507
        "when it is using host-filesystem access, which is a different path entirely -- "
508
        "see the Files tab.");
509
    ImGui::Spacing();
510
    ImGui::TextWrapped(
511
        "The two are mutually exclusive by default: passing -sdcard turns host-filesystem "
512
        "access off unless you also pass -hostfsdev to keep it. So a machine either has a "
513
        "card and talks to it in 512-byte blocks, or has no card and gets its files by "
514
        "name from the host.");
515
}
516
517
void
518
draw_sdcard_tab()
519
{
520
    sdcard_debug_state_t sd;
521
    sdcard_debug_get_state(&sd);
522
523
    if (!sd.attached) {
524
        explain_no_sdcard();
525
        return;
526
    }
527
528
    if (ImGui::BeginTable("sd_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) {
529
        ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed,
530
                                dbgui_text_width("Multiblock read "));
531
        ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch);
532
533
        row_kv("Image", "The image file backing the emulated card.", "%s", sd.image_path);
534
        row_kv("Size", "Size of the image file on disk.", "%lld bytes",
535
               (long long)sd.image_size);
536
537
        row_kv("Chip select",
538
               "SPI slave select. The card only listens while this is asserted, so an idle "
539
               "line means the machine is not talking to it at all.",
540
               "%s", sd.selected ? "asserted" : "idle");
541
542
        {
543
            const char *name = sd_command_name(sd.last_cmd, sd.last_cmd_is_acmd);
544
            const char *ctip = "The most recent command decoded from the SPI stream. It "
545
                               "persists after the command completes, so this is the last "
546
                               "thing asked of the card, not necessarily something in "
547
                               "progress.";
548
            if (name != nullptr) {
549
                row_kv("Last command", ctip, "%s", name);
550
            } else {
551
                row_kv("Last command", ctip, "%sCMD%u", sd.last_cmd_is_acmd ? "A" : "",
552
                       sd.last_cmd);
553
            }
554
        }
555
556
        row_begin("Block (LBA)",
557
                  "Logical block address of the last block command. Blocks are 512 bytes, so "
558
                  "the byte offset into the image is this times 512.");
559
        ImGui::Text("%u", sd.last_lba);
560
        dbgui_hover_value_tooltip("byte offset", sd.last_lba * 512u, 4);
561
        row_end();
562
563
        row_kv("R1 status",
564
               "The two R1 response bits this emulator models. \"idle\" means the card is in "
565
               "its power-up idle state and will refuse data commands; the ROM clears it "
566
               "during start-up with ACMD41.",
567
               "idle=%d  initialised=%d", sd.is_idle ? 1 : 0, sd.is_initialized ? 1 : 0);
568
569
        row_kv("Multiblock read",
570
               "CMD18 streams blocks until CMD12 stops it. While this is in progress the card "
571
               "keeps sending without being asked again.",
572
               "%s", sd.ongoing_multiblock_read ? "in progress" : "no");
573
574
        row_kv("Response",
575
               "How far through its reply the card is. A stalled transfer usually shows as a "
576
               "partial count here.",
577
               "%d / %d bytes sent", sd.response_counter, sd.response_length);
578
579
        row_kv("Commands", "Commands decoded since the card was attached.", "%u", sd.commands);
580
        row_kv("Blocks read", "512-byte blocks read since the card was attached.",
581
               "%llu  (%llu bytes)", (unsigned long long)sd.blocks_read,
582
               (unsigned long long)sd.bytes_read);
583
        row_kv("Blocks written", "512-byte blocks written since the card was attached.",
584
               "%llu  (%llu bytes)", (unsigned long long)sd.blocks_written,
585
               (unsigned long long)sd.bytes_written);
586
587
        ImGui::EndTable();
588
    }
589
590
    ImGui::Separator();
591
592
    heading("Last block resolves to:",
593
            "What the last block access was, in filesystem terms. Needs the image to have "
594
            "been indexed -- the button is on Files > SD image.");
595
    ImGui::SameLine();
596
    if (!sdcard_fat_ready()) {
597
        ImGui::TextColored(COL_DIM, "(image not indexed)");
598
    } else {
599
        const sdcard_fat_file_t *file   = nullptr;
600
        uint64_t                 offset = 0;
601
        switch (sdcard_fat_lookup(sd.last_lba, &file, &offset)) {
602
            case SDCARD_FAT_REGION_FILE:
603
            case SDCARD_FAT_REGION_DIR:
604
                if (file != nullptr) {
605
                    ImGui::TextColored(COL_DECODED, "%s + %llu", file->path,
606
                                       (unsigned long long)offset);
607
                } else {
608
                    ImGui::TextColored(COL_DIM, "(unnamed)");
609
                }
610
                break;
611
            case SDCARD_FAT_REGION_MBR: ImGui::TextColored(COL_DIM, "<MBR>"); break;
612
            case SDCARD_FAT_REGION_RESERVED: ImGui::TextColored(COL_DIM, "<boot/reserved>"); break;
613
            case SDCARD_FAT_REGION_FAT: ImGui::TextColored(COL_DIM, "<FAT>"); break;
614
            case SDCARD_FAT_REGION_ROOTDIR: ImGui::TextColored(COL_DIM, "<root directory>"); break;
615
            case SDCARD_FAT_REGION_FREE: ImGui::TextColored(COL_DIM, "<unallocated>"); break;
616
            default: ImGui::TextColored(COL_DIM, "<unknown>"); break;
617
        }
618
    }
619
620
    ImGui::Separator();
621
    vera_spi_debug_state_t spi;
622
    vera_spi_debug_get_state(&spi);
623
    heading("SPI",
624
            "The VERA SPI port at $9F3E/$9F3F -- the only wire to the card. \"out\" is the "
625
            "byte being clocked to the card, \"in\" the byte that came back.");
626
    ImGui::SameLine();
627
    ImGui::Text("select=%d  busy=%d  auto-tx=%d  out=$%02X  in=$%02X", spi.ss ? 1 : 0,
628
                spi.busy ? 1 : 0, spi.autotx ? 1 : 0, spi.sending_byte, spi.received_byte);
629
    tip("The VERA SPI port at $9F3E/$9F3F -- the only wire to the card.");
630
631
    ImGui::Spacing();
632
    ImGui::TextColored(COL_DIM, "This tab is the card as a device -- commands, blocks and "
633
                                "status. For the files inside the image, see Files > SD image.");
634
}
635
636
// ---------------------------------------------------------------------------
637
// Files
638
// ---------------------------------------------------------------------------
639
640
char s_file_filter[64] = "";
641
642
const char *
643
ieee_op_label(uint8_t kind)
644
{
645
    switch (kind) {
646
        case IEEE_OP_OPEN: return "open";
647
        case IEEE_OP_OPEN_FAILED: return "open FAILED";
648
        case IEEE_OP_CLOSE: return "close";
649
        case IEEE_OP_DIR: return "dir";
650
        case IEEE_OP_COMMAND: return "command";
651
        case IEEE_OP_STATUS: return "status";
652
        default: return "?";
653
    }
654
}
655
656
const char *
657
ieee_op_tip(uint8_t kind)
658
{
659
    switch (kind) {
660
        case IEEE_OP_OPEN:
661
            return "A file was opened successfully on this channel.";
662
        case IEEE_OP_OPEN_FAILED:
663
            return "An open was attempted and refused. The Status column says why.";
664
        case IEEE_OP_CLOSE:
665
            return "A channel was closed. The byte counts are the totals moved while it "
666
                   "was open.";
667
        case IEEE_OP_DIR:
668
            return "A directory listing was opened. \"$\" is the pseudo-file the drive "
669
                   "serves a directory from -- this is what LOAD\"$\",8 does.";
670
        case IEEE_OP_COMMAND:
671
            return "A DOS command was sent to the command channel (15). The Meaning column "
672
                   "says what it asks for.";
673
        case IEEE_OP_STATUS:
674
            return "A DOS status that no single operation accounted for -- something set an "
675
                   "error outside an open or a command.";
676
        default:
677
            return "";
678
    }
679
}
680
681
// What the row means in words, so the reader is not left decoding CBM DOS
682
// punctuation. The filename prefixes are as much a part of the request as the
683
// command letters are.
684
void
685
ieee_meaning(const ieee_history_entry_t &h, char *out, size_t outsz)
686
{
687
    out[0] = '\0';
688
    switch (h.kind) {
689
        case IEEE_OP_COMMAND:
690
            snprintf(out, outsz, "%s", io_dos_command_meaning(h.name));
691
            break;
692
        case IEEE_OP_OPEN:
693
        case IEEE_OP_OPEN_FAILED: {
694
            const char *mode = h.read && h.write ? "read and write"
695
                               : h.write         ? "write"
696
                                                 : "read";
697
            // '@' is CBM DOS for "replace an existing file" -- the difference
698
            // between saving and failing with FILE EXISTS.
699
            snprintf(out, outsz, "%sopen for %s", (h.name[0] == '@') ? "overwrite, " : "",
700
                     mode);
701
            break;
702
        }
703
        case IEEE_OP_DIR:
704
            snprintf(out, outsz, "read the directory");
705
            break;
706
        case IEEE_OP_STATUS:
707
            snprintf(out, outsz, "not tied to one operation");
708
            break;
709
        default:
710
            break;
711
    }
712
}
713
714
void
715
draw_host_history(const ieee_debug_state_t &ie)
716
{
717
    if (ie.history_count == 0) {
718
        ImGui::TextColored(COL_DIM, "Nothing yet. Try LOAD\"$\",8,1 or a DOS command such as @$.");
719
        return;
720
    }
721
722
    if (!ImGui::BeginTable("host_hist", 7,
723
                           ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
724
                               ImGuiTableFlags_BordersInnerV | DBGUI_TABLE_FLAGS_RESIZABLE,
725
                           ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 11))) {
726
        return;
727
    }
728
729
    ImGui::TableSetupScrollFreeze(0, 1);
730
    ImGui::TableSetupColumn("What", ImGuiTableColumnFlags_WidthFixed,
731
                            dbgui_col_width("What", "open FAILED"));
732
    ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed, dbgui_col_width("Ch", "88"));
733
    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
734
    ImGui::TableSetupColumn("Meaning", ImGuiTableColumnFlags_WidthFixed,
735
                            dbgui_col_width("Meaning", "overwrite, open for read and write"));
736
    ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed,
737
                            dbgui_col_width("Read", "8888888"));
738
    ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed,
739
                            dbgui_col_width("Written", "8888888"));
740
    ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,
741
                            dbgui_col_width("Status", "62,FILE NOT FOUND,00,00"));
742
    ImGui::TableHeadersRow();
743
744
    // Newest first. There is no auto-scroll here, and the newest operation is
745
    // almost always the one being asked about, so it should not require a
746
    // scroll to reach.
747
    for (int i = ie.history_count - 1; i >= 0; i--) {
748
        const ieee_history_entry_t &h = ie.history[i];
749
        const bool failed = (h.kind == IEEE_OP_OPEN_FAILED) || (h.kind == IEEE_OP_STATUS);
750
751
        ImGui::PushID(i);
752
        ImGui::TableNextRow();
753
754
        ImGui::TableSetColumnIndex(0);
755
        ImGui::TextColored(failed ? COL_BAD : COL_DECODED, "%s", ieee_op_label(h.kind));
756
        tip(ieee_op_tip(h.kind));
757
758
        ImGui::TableSetColumnIndex(1);
759
        if (h.channel >= 0) {
760
            ImGui::Text("%d", h.channel);
761
            tip(h.channel == 15 ? "Channel 15 is the command and status channel -- commands "
762
                                  "go in and DOS status comes back."
763
                                : "The logical channel the machine used, as in OPEN 2,8,2.");
764
        } else {
765
            ImGui::TextDisabled("-");
766
            tip("Not tied to one channel.");
767
        }
768
769
        ImGui::TableSetColumnIndex(2);
770
        ImGui::TextUnformatted(h.name);
771
        if (h.name[0] != '\0') {
772
            tip(h.name[0] == '@'
773
                    ? "The name as the machine gave it. A leading @ asks to replace an "
774
                      "existing file; a leading : separates the path from the filename."
775
                    : "The name as the machine gave it, before the emulator resolved it "
776
                      "against the host directory.");
777
        }
778
779
        ImGui::TableSetColumnIndex(3);
780
        {
781
            char meaning[64];
782
            ieee_meaning(h, meaning, sizeof(meaning));
783
            if (meaning[0] != '\0') {
784
                ImGui::TextColored(COL_DIM, "%s", meaning);
785
                tip(ieee_op_tip(h.kind));
786
            }
787
        }
788
789
        ImGui::TableSetColumnIndex(4);
790
        if (h.kind == IEEE_OP_CLOSE) {
791
            ImGui::Text("%u", h.bytes_read);
792
            tip("Bytes the machine read from this file while it was open.");
793
        }
794
795
        ImGui::TableSetColumnIndex(5);
796
        if (h.kind == IEEE_OP_CLOSE) {
797
            ImGui::Text("%u", h.bytes_written);
798
            tip("Bytes the machine wrote to this file while it was open.");
799
        }
800
801
        ImGui::TableSetColumnIndex(6);
802
        if (h.status[0] != '\0') {
803
            const bool ok = (h.status[0] == '0' && h.status[1] == '0');
804
            ImGui::TextColored(ok ? COL_DIM : COL_BAD, "%s", h.status);
805
            tip("The DOS status this operation ended with, as code,text,track,sector. "
806
                "\"00,OK\" is success; 62 is FILE NOT FOUND, 63 FILE EXISTS, 26 WRITE "
807
                "PROTECT ON.");
808
        } else {
809
            ImGui::TextDisabled("-");
810
            tip("This operation does not report a status of its own. A close always "
811
                "succeeds once the file is open, so DOS has nothing to say about it.");
812
        }
813
814
        ImGui::PopID();
815
    }
816
    ImGui::EndTable();
817
}
818
819
void
820
draw_host_files()
821
{
822
    ieee_debug_state_t ie;
823
    ieee_debug_get_state(&ie);
824
825
    if (!ie.using_hostfs) {
826
        ImGui::TextColored(COL_DIM, "Host-filesystem access is off.");
827
        ImGui::Spacing();
828
        ImGui::TextWrapped(
829
            "The machine is using the attached SD card image instead -- passing -sdcard "
830
            "turns host access off by default. Its file activity is block traffic rather "
831
            "than named files; see the SD image sub-tab.");
832
        ImGui::Spacing();
833
        ImGui::TextWrapped("Pass -hostfsdev <unit> alongside -sdcard to keep both available.");
834
        return;
835
    }
836
837
    ImGui::Text("Unit %d", ie.ieee_unit);
838
    tip("The IEC device number the host filesystem answers as. LOAD\"X\",8 talks to unit 8. "
839
        "Change it with -hostfsdev.");
840
    ImGui::SameLine();
841
    ImGui::TextColored(COL_DIM, "\xc2\xb7 %s",
842
                       ie.hostfscwd[0] != '\0' ? ie.hostfscwd : "(no root set)");
843
    tip("The host directory the machine currently sees as its own. Set the starting point "
844
        "with -fsroot.");
845
846
    ImGui::Text("Bus: %s%s%s", ie.listening ? "LISTENING " : "", ie.talking ? "TALKING " : "",
847
                (!ie.listening && !ie.talking) ? "idle" : "");
848
    tip("IEC protocol state. LISTENING means the machine is sending to the drive, TALKING "
849
        "that it is receiving. Both are transient -- an idle bus between operations is "
850
        "normal, not a fault.");
851
852
    if (ie.error_str[0] != '\0') {
853
        ImGui::TextUnformatted("Status:");
854
        tip("The drive's current status line -- what reading channel 15 would return now.");
855
        ImGui::SameLine();
856
        // The DOS status line always starts with a two-digit code; anything
857
        // other than 00 is a complaint worth colouring.
858
        const bool ok = (ie.error_str[0] == '0' && ie.error_str[1] == '0');
859
        ImGui::TextColored(ok ? COL_DECODED : COL_BAD, "%s", ie.error_str);
860
        tip("Format is code,text,track,sector. \"00,OK\" means the last operation "
861
            "succeeded. This is the CURRENT status; the history below keeps the status "
862
            "each individual operation ended with.");
863
    }
864
865
    ImGui::Separator();
866
    heading("Recent activity",
867
            "Completed file operations, newest first. Kept whether or not I/O capture is on "
868
            "-- file operations are rare enough to record for free.\n\n"
869
            "This exists because a file operation erases itself when it finishes: the "
870
            "channel list below only shows what is open RIGHT NOW, and a directory listing "
871
            "or a failed open is over within a millisecond. Without this history, doing @$ "
872
            "or opening a missing file appeared to do nothing at all.");
873
    draw_host_history(ie);
874
875
    ImGui::Separator();
876
    heading("Open channels",
877
            "What is open right now. Usually empty -- the machine opens a file, reads it and "
878
            "closes it faster than anyone can look. The history above is the useful view.");
879
880
    bool any = false;
881
    for (int i = 0; i < 16; i++) {
882
        if (ie.channels[i].is_open || ie.channels[i].name[0] != '\0') {
883
            any = true;
884
            break;
885
        }
886
    }
887
    if (!any) {
888
        ImGui::TextColored(COL_DIM, "None open.");
889
        return;
890
    }
891
892
    if (ImGui::BeginTable("host_files", 5, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) {
893
        ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed,
894
                                dbgui_col_width("Ch", "88"));
895
        ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
896
        ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed,
897
                                dbgui_col_width("Mode", "read/write"));
898
        ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed,
899
                                dbgui_col_width("Read", "888888888"));
900
        ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed,
901
                                dbgui_col_width("Written", "888888888"));
902
        ImGui::TableHeadersRow();
903
904
        for (int i = 0; i < 16; i++) {
905
            const ieee_channel_debug_t &c = ie.channels[i];
906
            if (!c.is_open && c.name[0] == '\0') {
907
                continue;
908
            }
909
            ImGui::TableNextRow();
910
            ImGui::TableSetColumnIndex(0);
911
            ImGui::Text("%d", i);
912
            ImGui::TableSetColumnIndex(1);
913
            ImGui::TextColored(c.is_open ? COL_DECODED : COL_DIM, "%s",
914
                               c.name[0] != '\0' ? c.name : "(closing)");
915
            ImGui::TableSetColumnIndex(2);
916
            ImGui::TextUnformatted(c.read && c.write ? "read/write"
917
                                   : c.read         ? "read"
918
                                   : c.write        ? "write"
919
                                                    : "-");
920
            ImGui::TableSetColumnIndex(3);
921
            ImGui::Text("%u", c.bytes_read);
922
            ImGui::TableSetColumnIndex(4);
923
            ImGui::Text("%u", c.bytes_written);
924
        }
925
        ImGui::EndTable();
926
    }
927
}
928
929
void
930
draw_sd_files()
931
{
932
    sdcard_debug_state_t sd;
933
    sdcard_debug_get_state(&sd);
934
935
    if (!sd.attached) {
936
        explain_no_sdcard();
937
        return;
938
    }
939
940
    ImGui::TextWrapped(
941
        "The emulator only ever sees 512-byte blocks here -- the filesystem inside the "
942
        "image is parsed by the ROM running on the emulated machine, not by us. To put "
943
        "names to that traffic the emulator parses the image itself:");
944
    ImGui::TextColored(COL_DIM, "This sub-tab is the files inside the image. The SD Card tab "
945
                                "is the same card as a device -- commands, blocks and status.");
946
947
    if (ImGui::SmallButton(sdcard_fat_ready() ? "Rebuild index" : "Build index")) {
948
        sdcard_reindex();
949
    }
950
    tip("Read the image's partition table, boot sector, FATs and directory tree, and map "
951
        "every cluster back to a path.");
952
953
    if (!sdcard_fat_ready()) {
954
        ImGui::SameLine();
955
        ImGui::TextColored(COL_DIM, "not indexed");
956
        ImGui::Spacing();
957
        ImGui::TextWrapped("Only FAT16 and FAT32 images can be indexed. If the button leaves "
958
                           "this saying \"not indexed\", the image is neither.");
959
        return;
960
    }
961
962
    ImGui::SameLine();
963
    ImGui::TextColored(COL_DIM, "%s \xc2\xb7 %u bytes/cluster \xc2\xb7 %d entries",
964
                       sdcard_fat_type_name(), sdcard_fat_bytes_per_cluster(),
965
                       sdcard_fat_file_count());
966
967
    if (sdcard_fat_is_stale()) {
968
        ImGui::SameLine();
969
        ImGui::TextColored(COL_WRITE, "\xc2\xb7 STALE");
970
        tip("The machine wrote to the FAT or a directory since this index was built, so "
971
            "names and extents may no longer be right. Rebuild to refresh.\n\n"
972
            "The index is never refreshed automatically: quietly serving a filename that "
973
            "had since become wrong would be worse than saying so.");
974
    }
975
    if (sdcard_fat_truncated()) {
976
        ImGui::SameLine();
977
        ImGui::TextColored(COL_WRITE, "\xc2\xb7 TRUNCATED");
978
        tip("The image holds more files than the index will hold. What is listed is "
979
            "correct; it is just not everything.");
980
    }
981
982
    ImGui::SetNextItemWidth(dbgui_field_width("wwwwwwwwwwwwwwww"));
983
    ImGui::InputTextWithHint("##sdfilter", "filter", s_file_filter, sizeof(s_file_filter));
984
    tip("Show only paths containing this text.");
985
    ImGui::SameLine();
986
    ImGui::TextColored(COL_DIM, "files the machine has touched are listed first, in green");
987
988
    if (ImGui::BeginTable("sd_files", 4,
989
                          ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY |
990
                              DBGUI_TABLE_FLAGS_RESIZABLE)) {
991
        ImGui::TableSetupScrollFreeze(0, 1);
992
        ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch);
993
        ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
994
                                dbgui_col_width("Size", "888888888"));
995
        ImGui::TableSetupColumn("Read", ImGuiTableColumnFlags_WidthFixed,
996
                                dbgui_col_width("Read", "888888888"));
997
        ImGui::TableSetupColumn("Written", ImGuiTableColumnFlags_WidthFixed,
998
                                dbgui_col_width("Written", "888888888"));
999
        ImGui::TableHeadersRow();
1000
1001
        // Two passes so the files the machine has actually touched come first.
1002
        // The list is an inventory of the image, but the question being asked
1003
        // is almost always "what did it read", and that answer should not be
1004
        // somewhere in the middle of a few hundred alphabetical entries.
1005
        const int n = sdcard_fat_file_count();
1006
        for (int pass = 0; pass < 2; pass++) {
1007
            for (int i = 0; i < n; i++) {
1008
                const sdcard_fat_file_t *f = sdcard_fat_file_at(i);
1009
                if (f == nullptr || f->path == nullptr) {
1010
                    continue;
1011
                }
1012
                const bool touched = (f->bytes_read > 0 || f->bytes_written > 0);
1013
                if (touched != (pass == 0)) {
1014
                    continue;
1015
                }
1016
                if (s_file_filter[0] != '\0' && strstr(f->path, s_file_filter) == nullptr) {
1017
                    continue;
1018
                }
1019
1020
                ImGui::TableNextRow();
1021
                ImGui::TableSetColumnIndex(0);
1022
                if (touched) {
1023
                    ImGui::TextColored(COL_DECODED, "%s", f->path);
1024
                } else {
1025
                    ImGui::TextColored(f->is_dir ? COL_DIM
1026
                                                 : ImGui::GetStyle().Colors[ImGuiCol_Text],
1027
                                       "%s", f->path);
1028
                }
1029
                ImGui::TableSetColumnIndex(1);
1030
                if (f->is_dir) {
1031
                    ImGui::TextColored(COL_DIM, "<dir>");
1032
                } else {
1033
                    ImGui::Text("%u", f->size);
1034
                }
1035
                ImGui::TableSetColumnIndex(2);
1036
                ImGui::Text("%llu", (unsigned long long)f->bytes_read);
1037
                ImGui::TableSetColumnIndex(3);
1038
                ImGui::Text("%llu", (unsigned long long)f->bytes_written);
1039
            }
1040
        }
1041
        ImGui::EndTable();
1042
    }
1043
}
1044
1045
void
1046
draw_files_tab()
1047
{
1048
    // Shown as two sub-tabs rather than one merged list because which of them
1049
    // is live is decided at start-up, and a reader chasing a missing file needs
1050
    // to see which half is even in play.
1051
    if (ImGui::BeginTabBar("files_sub")) {
1052
        if (ImGui::BeginTabItem("Host filesystem")) {
1053
            draw_host_files();
1054
            ImGui::EndTabItem();
1055
        }
1056
        if (ImGui::BeginTabItem("SD image")) {
1057
            draw_sd_files();
1058
            ImGui::EndTabItem();
1059
        }
1060
        ImGui::EndTabBar();
1061
    }
1062
}
1063
1064
// ---------------------------------------------------------------------------
1065
// Joysticks
1066
// ---------------------------------------------------------------------------
1067
1068
struct JoyBit {
1069
    uint16_t    mask;
1070
    const char *name;
1071
};
1072
1073
const JoyBit JOY_DPAD[]  = {{JOY_BIT_DPAD_UP, "Up"},
1074
                            {JOY_BIT_DPAD_DOWN, "Down"},
1075
                            {JOY_BIT_DPAD_LEFT, "Left"},
1076
                            {JOY_BIT_DPAD_RIGHT, "Right"}};
1077
const JoyBit JOY_FACE[]  = {{JOY_BIT_A, "A"},
1078
                            {JOY_BIT_B, "B"},
1079
                            {JOY_BIT_X, "X"},
1080
                            {JOY_BIT_Y, "Y"}};
1081
const JoyBit JOY_OTHER[] = {{JOY_BIT_LEFT_SHOULDER, "L"},
1082
                            {JOY_BIT_RIGHT_SHOULDER, "R"},
1083
                            {JOY_BIT_SELECT, "Select"},
1084
                            {JOY_BIT_START, "Start"}};
1085
1086
// Buttons are active-low on the wire, so a pressed button is a ZERO bit.
1087
// Everything below inverts once, here, so no reader has to do it in their head.
1088
void
1089
draw_button_group(const char *label, const JoyBit *bits, size_t count, uint16_t mask)
1090
{
1091
    ImGui::TextColored(COL_DIM, "%-6s", label);
1092
    for (size_t b = 0; b < count; b++) {
1093
        ImGui::SameLine();
1094
        if ((mask & bits[b].mask) == 0) {
1095
            ImGui::TextColored(COL_ON, "[%s]", bits[b].name);
1096
        } else {
1097
            ImGui::TextDisabled("%s", bits[b].name);
1098
        }
1099
    }
1100
}
1101
1102
void
1103
draw_joystick_tab()
1104
{
1105
    joystick_debug_state_t js;
1106
    joystick_debug_get_state(&js);
1107
1108
    ImGui::TextUnformatted("How the machine reads a controller");
1109
    help("The controllers are shift registers, not a parallel port.\n\n"
1110
         "The VIA asserts NESLATCH (PA2), which snapshots every controller's buttons at "
1111
         "once. It then pulses NESCLK (PA3) sixteen times, and each pulse shifts the next "
1112
         "button bit onto that slot's data line -- NESDAT0..3 on PA7..PA4. All four "
1113
         "controllers are read in parallel, one bit at a time.\n\n"
1114
         "That is why the raw byte at $9F01 is hard to read directly: it holds one bit of "
1115
         "four different controllers, and which bit depends on how far through the shift "
1116
         "sequence you are. The decoded buttons below are the useful view.");
1117
1118
    ImGui::Spacing();
1119
    ImGui::TextUnformatted("Latch (PA2)");
1120
    ImGui::SameLine();
1121
    lamp(js.latch, js.latch ? "asserted -- sampling now" : "idle");
1122
    help("While asserted, the controllers reload their shift registers from the live "
1123
         "button state. A game pulses this once per frame.");
1124
1125
    ImGui::Text("Controller data bits: $%02X", js.data);
1126
    help("The joystick contribution to VIA1 port A -- bits 7-4, one per controller slot, "
1127
         "carrying whichever bit is currently being shifted out. Bits 3-0 are always zero "
1128
         "here; the I2C lines that share port A are merged in separately when the CPU "
1129
         "actually reads $9F01, so this is not the whole byte the CPU sees.");
1130
1131
    ImGui::Separator();
1132
1133
    for (int i = 0; i < NUM_JOYSTICKS; i++) {
1134
        const joystick_slot_debug_t &s = js.slots[i];
1135
        ImGui::PushID(i);
1136
1137
        ImGui::Text("Slot %d", i);
1138
        ImGui::SameLine();
1139
        if (!s.enabled) {
1140
            ImGui::TextDisabled("disabled");
1141
            help("Not enabled. Enable a slot with -joy1 .. -joy4 on the command line. A "
1142
                 "disabled slot reads to the machine as \"nothing pressed\".");
1143
        } else if (s.virtual_active) {
1144
            ImGui::TextColored(COL_ON, "driven by a debug client");
1145
            help("A DAP client is holding this controller through the x16/joystick request, "
1146
                 "which needs no host gamepad. It overrides any gamepad bound to the slot "
1147
                 "until the client releases it.");
1148
        } else if (!s.controller_bound) {
1149
            ImGui::TextColored(COL_DIM, "enabled, no gamepad connected");
1150
            help("The slot exists as far as the machine is concerned, but no host gamepad "
1151
                 "is bound to it, so it reports nothing pressed.");
1152
        } else {
1153
            ImGui::TextColored(COL_ON, "connected");
1154
        }
1155
1156
        if (s.enabled) {
1157
            ImGui::Indent();
1158
            draw_button_group("D-pad", JOY_DPAD, sizeof(JOY_DPAD) / sizeof(JOY_DPAD[0]),
1159
                              s.button_mask);
1160
            draw_button_group("Face", JOY_FACE, sizeof(JOY_FACE) / sizeof(JOY_FACE[0]),
1161
                              s.button_mask);
1162
            draw_button_group("Other", JOY_OTHER, sizeof(JOY_OTHER) / sizeof(JOY_OTHER[0]),
1163
                              s.button_mask);
1164
1165
            ImGui::TextColored(COL_DIM, "raw $%04X   shifting $%04X", s.button_mask,
1166
                               s.shift_mask);
1167
            help("Raw values, for when you are debugging the read routine rather than the "
1168
                 "input.\n\n"
1169
                 "\"raw\" is the latched button state, and a pressed button is a ZERO bit "
1170
                 "because the wire is active-low -- so an idle pad reads $FFFF.\n\n"
1171
                 "\"shifting\" is what remains to be clocked out. It advances on every "
1172
                 "NESCLK pulse, so part-way through a read it will not match the raw value.");
1173
            ImGui::Unindent();
1174
        }
1175
1176
        ImGui::PopID();
1177
        ImGui::Separator();
1178
    }
1179
}
1180
1181
// ---------------------------------------------------------------------------
1182
// VIA
1183
// ---------------------------------------------------------------------------
1184
1185
void
1186
draw_via_ports(int which, const via_debug_state_t &v)
1187
{
1188
    // Pin-level view: the register bytes are only meaningful once you know what
1189
    // each bit is soldered to, and on VIA1 every bit is something different --
1190
    // I2C, the joysticks and the IEC bus all share two ports.
1191
    ImGui::TextUnformatted("Port pins");
1192
    help("What each bit of each port is physically connected to, and whether the VIA is "
1193
         "currently driving it (output) or reading it (input) -- which is what the data "
1194
         "direction register decides.\n\n"
1195
         "Level is shown only for output pins. An input pin's level is composed at read "
1196
         "time from whatever device is on the other end, so the register file the debugger "
1197
         "snapshots does not hold it.");
1198
1199
    if (ImGui::BeginTable("via_pins", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) {
1200
        ImGui::TableSetupColumn("Pin", ImGuiTableColumnFlags_WidthFixed,
1201
                                dbgui_col_width("Pin", "PA0"));
1202
        ImGui::TableSetupColumn("Dir", ImGuiTableColumnFlags_WidthFixed,
1203
                                dbgui_col_width("Dir", "output"));
1204
        ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed,
1205
                                dbgui_col_width("Level", "0"));
1206
        ImGui::TableSetupColumn("Signal", ImGuiTableColumnFlags_WidthStretch);
1207
        ImGui::TableHeadersRow();
1208
1209
        const struct {
1210
            char    port;
1211
            uint8_t data;
1212
            uint8_t ddr;
1213
        } ports[2] = {{'A', v.regs[1], v.regs[3]}, {'B', v.regs[0], v.regs[2]}};
1214
1215
        for (int p = 0; p < 2; p++) {
1216
            for (int bit = 7; bit >= 0; bit--) {
1217
                const bool is_out = (ports[p].ddr >> bit) & 1;
1218
                const bool level  = (ports[p].data >> bit) & 1;
1219
1220
                ImGui::TableNextRow();
1221
                ImGui::TableSetColumnIndex(0);
1222
                ImGui::Text("P%c%d", ports[p].port, bit);
1223
                ImGui::TableSetColumnIndex(1);
1224
                ImGui::TextColored(COL_DIM, "%s", is_out ? "output" : "input");
1225
                ImGui::TableSetColumnIndex(2);
1226
                if (is_out) {
1227
                    ImGui::TextColored(level ? COL_ON : COL_DIM, "%d", level ? 1 : 0);
1228
                } else {
1229
                    // The snapshot carries the output latch, which says nothing
1230
                    // about an input pin -- on VIA1 the joystick and IEC input
1231
                    // pins are composed by via_read() from other devices
1232
                    // entirely. Showing the latch here would be a lamp stuck at
1233
                    // whatever was last written, presented as a live reading.
1234
                    ImGui::TextDisabled("-");
1235
                }
1236
                ImGui::TableSetColumnIndex(3);
1237
                ImGui::TextUnformatted(io_via_pin_name(which, ports[p].port, bit));
1238
            }
1239
        }
1240
        ImGui::EndTable();
1241
    }
1242
}
1243
1244
void
1245
draw_one_via(int which, const char *name)
1246
{
1247
    // Whether the second VIA is fitted is machine configuration, which via.c
1248
    // deliberately does not know -- see via.h.
1249
    const bool fitted = (which == 0) || has_via2;
1250
    if (!fitted) {
1251
        ImGui::TextColored(COL_DIM, "%s is not fitted.", name);
1252
        ImGui::Spacing();
1253
        ImGui::TextWrapped("The second VIA is optional on the X16 and drives the user port. "
1254
                           "This emulator wires its pins to nothing, so it is only useful "
1255
                           "for exercising the timers and interrupts.");
1256
        return;
1257
    }
1258
1259
    via_debug_state_t v;
1260
    via_debug_get_state(which, &v);
1261
1262
    const uint8_t acr = v.regs[0x0b];
1263
    const uint8_t ifr = v.regs[0x0d];
1264
    const uint8_t ier = v.regs[0x0e];
1265
1266
    ImGui::TextUnformatted("Timers");
1267
    help("Two 16-bit counters running down at the system clock. They are the usual source "
1268
         "of periodic interrupts on this machine.");
1269
1270
    ImGui::Indent();
1271
    ImGui::Text("T1  %5u  %s", v.timer_count[0], v.timer_running[0] ? "running" : "stopped");
1272
    ImGui::SameLine();
1273
    ImGui::TextColored(COL_DIM, "reload %u \xc2\xb7 %s", v.timer_latch[0],
1274
                       (acr & 0x40) ? "free-running" : "one-shot");
1275
    help("Timer 1. In one-shot mode it counts down once, raises its interrupt flag and "
1276
         "stops. In free-running mode it reloads from the latch and repeats, which is how "
1277
         "a steady interrupt tick is produced. ACR bit 6 chooses between them.\n\n"
1278
         "Writing T1C_H starts it; writing T1L_H only changes the reload value.");
1279
1280
    ImGui::Text("T2  %5u  %s", v.timer_count[1], v.timer_running[1] ? "running" : "stopped");
1281
    ImGui::SameLine();
1282
    ImGui::TextColored(COL_DIM, "reload %u \xc2\xb7 %s", v.timer_latch[1],
1283
                       (acr & 0x20) ? "counting PB6 pulses" : "one-shot");
1284
    help("Timer 2 is one-shot only. ACR bit 5 switches it from counting clock cycles to "
1285
         "counting falling edges on PB6, which is how external pulses get measured.");
1286
1287
    ImGui::TextUnformatted("PB7 output");
1288
    ImGui::SameLine();
1289
    if (acr & 0x80) {
1290
        lamp(v.pb7_output, v.pb7_output ? "high" : "low");
1291
    } else {
1292
        ImGui::TextDisabled("disabled");
1293
    }
1294
    help("With ACR bit 7 set, timer 1 toggles pin PB7 on every underflow, turning the timer "
1295
         "into a square-wave generator that needs no CPU attention at all. With it clear, "
1296
         "PB7 is an ordinary port pin.");
1297
    ImGui::Unindent();
1298
1299
    ImGui::Separator();
1300
    ImGui::TextUnformatted("Interrupts");
1301
    help("A source raises its flag in IFR. That only pulls the CPU's IRQ line if the same "
1302
         "bit is also enabled in IER -- so a set flag on its own does not mean an interrupt "
1303
         "fired. Look for a bit that is set in both columns.");
1304
1305
    if (ImGui::BeginTable("via_irq", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) {
1306
        ImGui::TableSetupColumn("Bit", ImGuiTableColumnFlags_WidthFixed,
1307
                                dbgui_col_width("Bit", "8"));
1308
        ImGui::TableSetupColumn("Flag", ImGuiTableColumnFlags_WidthFixed,
1309
                                dbgui_col_width("Flag", "set"));
1310
        ImGui::TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed,
1311
                                dbgui_col_width("Enabled", "yes"));
1312
        ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch);
1313
        ImGui::TableHeadersRow();
1314
1315
        for (int bit = 6; bit >= 0; bit--) {
1316
            const bool flag = (ifr >> bit) & 1;
1317
            const bool en   = (ier >> bit) & 1;
1318
            ImGui::TableNextRow();
1319
            ImGui::TableSetColumnIndex(0);
1320
            ImGui::TextColored(COL_DIM, "%d", bit);
1321
            ImGui::TableSetColumnIndex(1);
1322
            ImGui::TextColored(flag ? COL_WRITE : COL_DIM, "%s", flag ? "set" : "-");
1323
            ImGui::TableSetColumnIndex(2);
1324
            ImGui::TextColored(en ? COL_ON : COL_DIM, "%s", en ? "yes" : "no");
1325
            ImGui::TableSetColumnIndex(3);
1326
            ImGui::TextUnformatted(io_via_irq_bit_name(bit));
1327
        }
1328
        ImGui::EndTable();
1329
    }
1330
    // Bit 7 of IFR is synthesised by via_read() and never stored, so testing it
1331
    // in the snapshot would report "idle" even while the VIA is holding the
1332
    // IRQ line down. Compute it the way the VIA's own irq function does.
1333
    const bool irq_asserted = (ifr & ier & 0x7f) != 0;
1334
    ImGui::TextColored(irq_asserted ? COL_WRITE : COL_DIM, "IRQ line: %s",
1335
                       irq_asserted ? "asserted" : "idle");
1336
    help("Set when any flag above is both raised and enabled. While it is asserted, this "
1337
         "VIA is holding the CPU's interrupt line down.");
1338
1339
    ImGui::Separator();
1340
    draw_via_ports(which, v);
1341
1342
    ImGui::Separator();
1343
    ImGui::TextUnformatted("Registers");
1344
    help("The raw 65C22 register file -- hover a name for what it does.\n\n"
1345
         "Several of these have side effects when the CPU reads them: reading a timer "
1346
         "counter clears its interrupt flag. The debugger reads a snapshot instead, so "
1347
         "looking here changes nothing.");
1348
1349
    if (ImGui::BeginTable("via_regs", 4, ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) {
1350
        ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed,
1351
                                dbgui_col_width("Addr", "$FFFF"));
1352
        ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed,
1353
                                dbgui_col_width("Name", "ORA/IRA"));
1354
        ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed,
1355
                                dbgui_col_width("Value", "$FF"));
1356
        ImGui::TableSetupColumn("Binary", ImGuiTableColumnFlags_WidthStretch);
1357
        ImGui::TableHeadersRow();
1358
1359
        const uint16_t base = (which == 0) ? 0x9f00 : 0x9f10;
1360
        for (int r = 0; r < 15; r++) {
1361
            const uint16_t addr = (uint16_t)(base + r);
1362
            ImGui::TableNextRow();
1363
            ImGui::TableSetColumnIndex(0);
1364
            ImGui::TextColored(COL_DIM, "$%04X", addr);
1365
            ImGui::TableSetColumnIndex(1);
1366
            ImGui::TextUnformatted(io_reg_name(addr));
1367
            tip(io_reg_purpose(addr));
1368
            ImGui::TableSetColumnIndex(2);
1369
            ImGui::Text("$%02X", v.regs[r]);
1370
            dbgui_hover_value_tooltip(io_reg_name(addr), v.regs[r], 1);
1371
            ImGui::TableSetColumnIndex(3);
1372
            char bits[16];
1373
            dbgui_format_binary(bits, sizeof(bits), v.regs[r], 8);
1374
            ImGui::TextColored(COL_DIM, "%s", bits);
1375
        }
1376
        ImGui::EndTable();
1377
    }
1378
}
1379
1380
void
1381
draw_via_tab()
1382
{
1383
    if (ImGui::BeginTabBar("via_sub")) {
1384
        if (ImGui::BeginTabItem("VIA1")) {
1385
            ImGui::TextWrapped("VIA1 is where the I2C bus, the joysticks and the IEC serial "
1386
                               "bus all meet -- three unrelated things sharing two 8-bit "
1387
                               "ports.");
1388
            ImGui::Separator();
1389
            draw_one_via(0, "VIA1");
1390
            ImGui::EndTabItem();
1391
        }
1392
        if (ImGui::BeginTabItem("VIA2")) {
1393
            draw_one_via(1, "VIA2");
1394
            ImGui::EndTabItem();
1395
        }
1396
        ImGui::EndTabBar();
1397
    }
1398
}
1399
1400
// ---------------------------------------------------------------------------
1401
// I2C, and the SMC and RTC behind it
1402
// ---------------------------------------------------------------------------
1403
1404
void
1405
draw_i2c_bus()
1406
{
1407
    i2c_debug_state_t i2c;
1408
    i2c_debug_get_state(&i2c);
1409
1410
    ImGui::TextWrapped("A two-wire bus hanging off VIA1 port A. Exactly two devices answer on "
1411
                       "it: the SMC and the RTC. Everything the keyboard, mouse, power button "
1412
                       "and clock do passes through here.");
1413
    ImGui::Separator();
1414
1415
    if (ImGui::BeginTable("i2c_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) {
1416
        ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed,
1417
                                dbgui_text_width("Transactions "));
1418
        ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch);
1419
1420
        row_begin("State",
1421
                  "Where the bus is within the current byte. Transfers are bit-at-a-time, so "
1422
                  "catching it mid-byte is normal. \"idle\" means no transaction is in "
1423
                  "flight, which is where the bus spends most of its time.");
1424
        if (i2c.state < 0) {
1425
            ImGui::TextUnformatted("idle");
1426
        } else if (i2c.state == 0) {
1427
            ImGui::TextUnformatted("start condition");
1428
        } else {
1429
            ImGui::Text("shifting bit %d of 8", i2c.state);
1430
        }
1431
        row_end();
1432
1433
        row_begin("Device",
1434
                  "Which device the current transaction is addressed to. $42 is the SMC and "
1435
                  "$6F the RTC; anything else goes unacknowledged, which usually means "
1436
                  "software is probing for hardware that is not there.");
1437
        if (i2c.device_name[0] != '\0') {
1438
            ImGui::Text("$%02X (%s)", i2c.device, i2c.device_name);
1439
        } else if (i2c.device != 0) {
1440
            ImGui::TextColored(COL_BAD, "$%02X (not acknowledged)", i2c.device);
1441
        } else {
1442
            ImGui::TextDisabled("none");
1443
        }
1444
        row_end();
1445
1446
        row_kv("Direction",
1447
               "Which way the current transaction is moving data. A read is normally preceded "
1448
               "by a write that selects the register.",
1449
               "%s", i2c.read_mode ? "reading from device" : "writing to device");
1450
1451
        row_begin("Byte",
1452
                  "The byte being shifted in or out, and how far into the transaction it is. "
1453
                  "The first byte written is normally the register number.");
1454
        ImGui::Text("$%02X", i2c.value);
1455
        ImGui::SameLine();
1456
        ImGui::TextColored(COL_DIM, "(byte %d of this transaction)", i2c.count);
1457
        row_end();
1458
1459
        row_kv("Lines",
1460
               "The raw bus wires. SCL is the clock, always driven by the machine. SDA "
1461
               "carries data in both directions, so it has a separate in and out here.",
1462
               "SCL in %d   SDA in %d   SDA out %d", i2c.clk_in, i2c.data_in, i2c.data_out);
1463
1464
        row_kv("Transactions",
1465
               "Counted since start-up. A growing gap between the two means transactions are "
1466
               "being abandoned part-way, which normally indicates a bus problem.",
1467
               "%u started, %u completed", i2c.transactions_started,
1468
               i2c.transactions_completed);
1469
1470
        row_kv("Bytes", "Data bytes moved over the bus since start-up, excluding addresses.",
1471
               "%u read, %u written", i2c.bytes_read, i2c.bytes_written);
1472
        ImGui::EndTable();
1473
    }
1474
}
1475
1476
void
1477
draw_smc()
1478
{
1479
    smc_debug_state_t smc;
1480
    smc_debug_get_state(&smc);
1481
1482
    ImGui::TextWrapped("The System Management Controller: a small microcontroller that owns "
1483
                       "the keyboard, the mouse, the power and reset buttons and the case "
1484
                       "LEDs. The main CPU talks to it over I2C at address $42.");
1485
    ImGui::Separator();
1486
1487
    if (ImGui::BeginTable("smc_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) {
1488
        ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed,
1489
                                dbgui_text_width("Default read reg "));
1490
        ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch);
1491
1492
        row_begin("Default read reg",
1493
                  "Which register a bare read returns -- one where the machine reads from the "
1494
                  "SMC without first writing a register number. The KERNAL sets this once at "
1495
                  "start-up so its keyboard polling loop can be a single short read.\n\n"
1496
                  "$41 is the power-on default, and returns a keyboard byte.");
1497
        ImGui::Text("$%02X", smc.default_read_op);
1498
        ImGui::SameLine();
1499
        ImGui::TextColored(COL_DECODED, "%s", io_smc_reg_name(smc.default_read_op));
1500
        tip(io_smc_reg_purpose(smc.default_read_op));
1501
1502
        row_kv("Transfer offset",
1503
               "How many bytes into the current I2C transfer the SMC is. It resets at the "
1504
               "start of each transaction: byte 0 is the register number, byte 1 the value. "
1505
               "Anything other than 0 means a transfer is in flight right now.",
1506
               "%u", smc.i2c_data_pos);
1507
1508
        row_begin("Activity LED",
1509
                  "The case's disk-activity lamp. Real hardware dims it by PWM, but this "
1510
                  "emulator models it as simply on or off, switching at a value of 128.");
1511
        lamp(smc.activity_led >= 128, smc.activity_led >= 128 ? "on" : "off");
1512
        row_end();
1513
1514
        row_kv("Keyboard buffer",
1515
               "Key events the SMC is holding until the machine collects them. It is a "
1516
               "16-byte ring; if this sits full, the machine has stopped polling.",
1517
               "%u byte%s waiting", smc.kbd_fill, smc.kbd_fill == 1 ? "" : "s");
1518
1519
        row_kv("Mouse buffer",
1520
               "Mouse movement waiting to be collected. Packets are three bytes, or four when "
1521
               "a wheel is reported.",
1522
               "%u byte%s waiting", smc.mse_fill, smc.mse_fill == 1 ? "" : "s");
1523
1524
        row_kv("Mouse packets", "Complete mouse packets queued behind the byte count above.",
1525
               "%u", smc.mse_count);
1526
        ImGui::EndTable();
1527
    }
1528
1529
    if (smc.requested_reset) {
1530
        ImGui::TextColored(COL_BAD, "Reset requested");
1531
        tip("The SMC has been told to reboot the machine and is waiting for the emulator to "
1532
            "act on it.");
1533
    }
1534
}
1535
1536
void
1537
draw_rtc()
1538
{
1539
    rtc_debug_state_t rtc;
1540
    rtc_debug_get_state(&rtc);
1541
1542
    ImGui::TextWrapped("A battery-backed real-time clock (MCP7940N) on the same I2C bus, at "
1543
                       "address $6F. It also carries 64 bytes of non-volatile memory that "
1544
                       "survive a power cycle.");
1545
    ImGui::Separator();
1546
1547
    if (ImGui::BeginTable("rtc_state", 2, DBGUI_TABLE_FLAGS_RESIZABLE)) {
1548
        ImGui::TableSetupColumn("##k", ImGuiTableColumnFlags_WidthFixed,
1549
                                dbgui_text_width("Transfer offset "));
1550
        ImGui::TableSetupColumn("##v", ImGuiTableColumnFlags_WidthStretch);
1551
1552
        row_kv("Date and time",
1553
               "The clock's own idea of the time. It is stored on the device as BCD and shown "
1554
               "here decoded. The year field only holds 00-99, meaning 2000-2099.",
1555
               "20%02d-%02d-%02d  %02d:%02d:%02d", rtc.year, rtc.month, rtc.day, rtc.hours,
1556
               rtc.minutes, rtc.seconds);
1557
1558
        row_begin("Oscillator",
1559
                  "Whether the clock is ticking. Stopped is the normal state for an RTC that "
1560
                  "has never been set -- the machine starts it when it first writes the time.");
1561
        lamp(rtc.running, rtc.running ? "running" : "stopped");
1562
        row_end();
1563
1564
        row_kv("Hour format",
1565
               "A flag in the hours register. It only affects how the device reports the "
1566
               "hour, not how it counts.",
1567
               "%s", rtc.h24 ? "24-hour" : "12-hour");
1568
1569
        row_kv("Day of week",
1570
               "1-7, exactly as stored. The RTC does not derive this from the date -- "
1571
               "whatever set the time chose it, so it is free to disagree with the date "
1572
               "beside it.",
1573
               "%d", rtc.day_of_week);
1574
1575
        row_kv("Transfer offset",
1576
               "How many bytes into the current I2C transfer the RTC is. Byte 0 selects the "
1577
               "register, so anything other than 0 means a transfer is in progress.",
1578
               "%u", rtc.i2c_data_pos);
1579
1580
        row_kv("NVRAM",
1581
               "Non-volatile storage at RTC registers $20-$5F. The emulator writes it back to "
1582
               "the file given by -nvram. \"modified\" means there are changes not yet saved.",
1583
               "64 bytes, %s", rtc.nvram_dirty ? "modified" : "unmodified");
1584
        ImGui::EndTable();
1585
    }
1586
}
1587
1588
void
1589
draw_i2c_tab()
1590
{
1591
    if (ImGui::BeginTabBar("i2c_sub")) {
1592
        if (ImGui::BeginTabItem("Bus")) {
1593
            draw_i2c_bus();
1594
            ImGui::EndTabItem();
1595
        }
1596
        if (ImGui::BeginTabItem("SMC")) {
1597
            draw_smc();
1598
            ImGui::EndTabItem();
1599
        }
1600
        if (ImGui::BeginTabItem("RTC")) {
1601
            draw_rtc();
1602
            ImGui::EndTabItem();
1603
        }
1604
        ImGui::EndTabBar();
1605
    }
1606
}
1607
1608
// ---------------------------------------------------------------------------
1609
// Serial
1610
// ---------------------------------------------------------------------------
1611
1612
void
1613
draw_serial_line(const char *name, int level, const char *what)
1614
{
1615
    ImGui::TableNextRow();
1616
    ImGui::TableSetColumnIndex(0);
1617
    ImGui::TextColored(COL_DIM, "%s", name);
1618
    ImGui::TableSetColumnIndex(1);
1619
    ImGui::TextColored(level ? COL_ON : COL_DIM, "%d", level);
1620
    ImGui::TableSetColumnIndex(2);
1621
    ImGui::TextUnformatted(what);
1622
}
1623
1624
void
1625
draw_serial_tab()
1626
{
1627
    ImGui::TextWrapped("The IEC serial bus -- the Commodore disk-drive and printer bus. Three "
1628
                       "wires, driven bit by bit in software, wired to VIA1 port B.");
1629
    ImGui::Spacing();
1630
    ImGui::TextWrapped("Host-filesystem file access does NOT use these lines. It is "
1631
                       "intercepted at the KERNAL level, well above the wire, so the bus stays "
1632
                       "idle while files are being read. These only move when real IEC "
1633
                       "signalling happens, which needs -serial and a device on the bus.");
1634
    ImGui::Separator();
1635
1636
    if (ImGui::BeginTable("serial_lines", 3,
1637
                          ImGuiTableFlags_RowBg | DBGUI_TABLE_FLAGS_RESIZABLE)) {
1638
        ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed,
1639
                                dbgui_col_width("Line", "DATA out"));
1640
        ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed,
1641
                                dbgui_col_width("Level", "0"));
1642
        ImGui::TableSetupColumn("What it is for", ImGuiTableColumnFlags_WidthStretch);
1643
        ImGui::TableHeadersRow();
1644
1645
        draw_serial_line("ATN in", serial_port.in.atn,
1646
                         "Attention. The computer asserts it to interrupt every device on the "
1647
                         "bus and announce that a command, not data, is coming next.");
1648
        draw_serial_line("CLK in", serial_port.in.clk,
1649
                         "Clock, as driven by the computer. Each transition clocks one bit of "
1650
                         "the byte being sent.");
1651
        draw_serial_line("DATA in", serial_port.in.data,
1652
                         "Data, as driven by the computer.");
1653
        draw_serial_line("CLK out", serial_port.out.clk,
1654
                         "Clock, as driven by the attached device when it is the talker.");
1655
        draw_serial_line("DATA out", serial_port.out.data,
1656
                         "Data, as driven by the attached device -- both when sending bytes "
1657
                         "and when holding the line to say it is not ready yet.");
1658
        ImGui::EndTable();
1659
    }
1660
1661
    ImGui::Spacing();
1662
    ImGui::TextWrapped("The bus is open-collector and active-low: a device pulls a line down "
1663
                       "to assert it, so 0 is the asserted state and an undriven line floats "
1664
                       "to 1.");
1665
}
1666
1667
// ---------------------------------------------------------------------------
1668
1669
void
1670
io_panel_render(bool *p_open)
1671
{
1672
    if (!ImGui::Begin("I/O", p_open)) {
1673
        dbgui_window_end();
1674
        return;
1675
    }
1676
    dbgui_window_zoom("io");
1677
1678
    apply_capture_settings();
1679
1680
    ImGui::TextDisabled("Hover any label or value for what it means.");
1681
1682
    if (ImGui::BeginTabBar("io_tabs", ImGuiTabBarFlags_DrawSelectedOverline)) {
1683
        if (ImGui::BeginTabItem("Activity")) {
1684
            draw_activity_tab();
1685
            ImGui::EndTabItem();
1686
        }
1687
        if (ImGui::BeginTabItem("SD Card")) {
1688
            draw_sdcard_tab();
1689
            ImGui::EndTabItem();
1690
        }
1691
        if (ImGui::BeginTabItem("Files")) {
1692
            draw_files_tab();
1693
            ImGui::EndTabItem();
1694
        }
1695
        if (ImGui::BeginTabItem("Joysticks")) {
1696
            draw_joystick_tab();
1697
            ImGui::EndTabItem();
1698
        }
1699
        if (ImGui::BeginTabItem("VIA")) {
1700
            draw_via_tab();
1701
            ImGui::EndTabItem();
1702
        }
1703
        if (ImGui::BeginTabItem("I2C")) {
1704
            draw_i2c_tab();
1705
            ImGui::EndTabItem();
1706
        }
1707
        if (ImGui::BeginTabItem("Serial")) {
1708
            draw_serial_tab();
1709
            ImGui::EndTabItem();
1710
        }
1711
        ImGui::EndTabBar();
1712
    }
1713
1714
    dbgui_window_end();
1715
}
1716
1717
DebugPanelRegistration s_reg("I/O", io_panel_render, true);
1718
1719
} // namespace