OXYGENHUB
◆ main openmsx / src / cpu / MSXCPUInterface.cc
40.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MSXCPUInterface.hh"
2
3
#include "BooleanSetting.hh"
4
#include "CartridgeSlotManager.hh"
5
#include "CommandException.hh"
6
#include "DeviceFactory.hh"
7
#include "DummyDevice.hh"
8
#include "Event.hh"
9
#include "EventDistributor.hh"
10
#include "GlobalSettings.hh"
11
#include "HardwareConfig.hh"
12
#include "Interpreter.hh"
13
#include "MSXCPU.hh"
14
#include "MSXCliComm.hh"
15
#include "MSXException.hh"
16
#include "MSXMotherBoard.hh"
17
#include "MSXMultiIODevice.hh"
18
#include "MSXMultiMemDevice.hh"
19
#include "Reactor.hh"
20
#include "ReadOnlySetting.hh"
21
#include "RealTime.hh"
22
#include "Scheduler.hh"
23
#include "StateChangeDistributor.hh"
24
#include "TclObject.hh"
25
#include "VDPIODelay.hh"
26
#include "WatchPoint.hh"
27
#include "serialize.hh"
28
29
#include "narrow.hh"
30
#include "outer.hh"
31
#include "stl.hh"
32
#include "unreachable.hh"
33
#include "xrange.hh"
34
35
#include <algorithm>
36
#include <array>
37
#include <iostream>
38
#include <iterator>
39
#include <memory>
40
#include <optional>
41
42
namespace openmsx {
43
44
static std::optional<ReadOnlySetting> breakedSetting;
45
static unsigned breakedSettingCount = 0;
46
47
48
// Bitfields used in the disallowReadCache and disallowWriteCache arrays
49
static constexpr uint8_t SECONDARY_SLOT_BIT = 0x01;
50
static constexpr uint8_t MEMORY_WATCH_BIT   = 0x02;
51
static constexpr uint8_t GLOBAL_RW_BIT      = 0x04;
52
53
std::ostream& operator<<(std::ostream& os, EnumTypeName<CacheLineCounters>)
54
{
55
	return os << "CacheLineCounters";
56
}
57
std::ostream& operator<<(std::ostream& os, EnumValueName<CacheLineCounters> evn)
58
{
59
	std::array<std::string_view, size_t(CacheLineCounters::NUM)> names = {
60
		"NonCachedRead",
61
		"NonCachedWrite",
62
		"GetReadCacheLine",
63
		"GetWriteCacheLine",
64
		"SlowRead",
65
		"SlowWrite",
66
		"DisallowCacheRead",
67
		"DisallowCacheWrite",
68
		"InvalidateAllSlots",
69
		"InvalidateReadWrite",
70
		"InvalidateRead",
71
		"InvalidateWrite",
72
		"FillReadWrite",
73
		"FillRead",
74
		"FillWrite",
75
	};
76
	return os << names[size_t(evn.e)];
77
}
78
79
MSXCPUInterface::MSXCPUInterface(MSXMotherBoard& motherBoard_)
80
	: memoryDebug       (motherBoard_)
81
	, slottedMemoryDebug(motherBoard_)
82
	, ioDebug           (motherBoard_)
83
	, slotInfo(motherBoard_.getMachineInfoCommand())
84
	, subSlottedInfo(motherBoard_.getMachineInfoCommand())
85
	, externalSlotInfo(motherBoard_.getMachineInfoCommand())
86
	, inputPortInfo (motherBoard_.getMachineInfoCommand())
87
	, outputPortInfo(motherBoard_.getMachineInfoCommand())
88
	, dummyDevice(DeviceFactory::createDummyDevice(
89
		*motherBoard_.getMachineConfig()))
90
	, msxcpu(motherBoard_.getCPU())
91
	, cliComm(motherBoard_.getMSXCliComm())
92
	, motherBoard(motherBoard_)
93
	, pauseSetting(motherBoard.getReactor().getGlobalSettings().getPauseSetting())
94
{
95
	std::ranges::fill(primarySlotState, 0);
96
	std::ranges::fill(secondarySlotState, 0);
97
	std::ranges::fill(expanded, 0);
98
	std::ranges::fill(subSlotRegister, 0);
99
	std::ranges::fill(IO_In,  dummyDevice.get());
100
	std::ranges::fill(IO_Out, dummyDevice.get());
101
	std::ranges::fill(visibleDevices, dummyDevice.get());
102
	for (auto& sub1 : slotLayout) {
103
		for (auto& sub2 : sub1) {
104
			std::ranges::fill(sub2, dummyDevice.get());
105
		}
106
	}
107
108
	// initially allow all regions to be cached
109
	std::ranges::fill(disallowReadCache,  0);
110
	std::ranges::fill(disallowWriteCache, 0);
111
112
	initialPrimarySlots = motherBoard.getMachineConfig()->parseSlotMap();
113
	// Note: SlotState is initialised at reset
114
115
	msxcpu.setInterface(this);
116
117
	if (motherBoard.isTurboR()) {
118
		// TODO also MSX2+ needs (slightly different) VDPIODelay
119
		delayDevice = DeviceFactory::createVDPIODelay(
120
			*motherBoard.getMachineConfig(), *this);
121
		for (auto port : xrange(0x98, 0x9c)) {
122
			assert(IO_In [port] == dummyDevice.get());
123
			assert(IO_Out[port] == dummyDevice.get());
124
			IO_In [port] = delayDevice.get();
125
			IO_Out[port] = delayDevice.get();
126
		}
127
	}
128
129
	if (breakedSettingCount++ == 0) {
130
		assert(!breakedSetting);
131
		breakedSetting.emplace(
132
			motherBoard.getReactor().getCommandController(),
133
			"breaked", "Similar to 'debug breaked'",
134
			TclObject("false"));
135
	}
136
	reset();
137
}
138
139
MSXCPUInterface::~MSXCPUInterface()
140
{
141
	if (--breakedSettingCount == 0) {
142
		assert(breakedSetting);
143
		breakedSetting.reset();
144
	}
145
146
	removeAllWatchPoints();
147
148
	if (delayDevice) {
149
		for (auto port : xrange(0x98, 0x9c)) {
150
			assert(IO_In [port] == delayDevice.get());
151
			assert(IO_Out[port] == delayDevice.get());
152
			IO_In [port] = dummyDevice.get();
153
			IO_Out[port] = dummyDevice.get();
154
		}
155
	}
156
157
	msxcpu.setInterface(nullptr);
158
159
	#ifndef NDEBUG
160
	for (auto port : xrange(256)) {
161
		if (IO_In[port] != dummyDevice.get()) {
162
			std::cout << "In-port " << port << " still registered "
163
			          << IO_In[port]->getName() << '\n';
164
			assert(false);
165
		}
166
		if (IO_Out[port] != dummyDevice.get()) {
167
			std::cout << "Out-port " << port << " still registered "
168
			          << IO_Out[port]->getName() << '\n';
169
			assert(false);
170
		}
171
	}
172
	for (auto primSlot : xrange(4)) {
173
		assert(!isExpanded(primSlot));
174
		for (auto secSlot : xrange(4)) {
175
			for (auto page : xrange(4)) {
176
				assert(slotLayout[primSlot][secSlot][page] == dummyDevice.get());
177
			}
178
		}
179
	}
180
	#endif
181
}
182
183
void MSXCPUInterface::removeAllWatchPoints()
184
{
185
	while (!watchPoints.empty()) {
186
		removeWatchPoint(watchPoints.back());
187
	}
188
}
189
190
uint8_t MSXCPUInterface::readMemSlow(uint16_t address, EmuTime time)
191
{
192
	tick(CacheLineCounters::DisallowCacheRead);
193
	// something special in this region?
194
	if (disallowReadCache[address >> CacheLine::BITS]) [[unlikely]] {
195
		// slot-select-ignore reads (e.g. used in 'Carnivore2')
196
		for (auto& g : globalReads) {
197
			// very primitive address selection mechanism,
198
			// but more than enough for now
199
			if (g.addr == address) [[unlikely]] {
200
				g.device->globalRead(address, time);
201
			}
202
		}
203
		// execute read watches before actual read
204
		if (readWatchSet[address >> CacheLine::BITS]
205
		                [address &  CacheLine::LOW]) {
206
			executeMemWatch(WatchPoint::Type::READ_MEM, address);
207
		}
208
	}
209
	if ((address == 0xFFFF) && isExpanded(primarySlotState[3])) [[unlikely]] {
210
		return 0xFF ^ subSlotRegister[primarySlotState[3]];
211
	} else {
212
		return visibleDevices[address >> 14]->readMem(address, time);
213
	}
214
}
215
216
void MSXCPUInterface::writeMemSlow(uint16_t address, uint8_t value, EmuTime time)
217
{
218
	tick(CacheLineCounters::DisallowCacheWrite);
219
	if ((address == 0xFFFF) && isExpanded(primarySlotState[3])) [[unlikely]] {
220
		setSubSlot(primarySlotState[3], value);
221
		// Confirmed on turboR GT machine: write does _not_ also go to
222
		// the underlying (hidden) device. But it's theoretically
223
		// possible other slot-expanders behave different.
224
	} else {
225
		visibleDevices[address>>14]->writeMem(address, value, time);
226
	}
227
	// something special in this region?
228
	if (disallowWriteCache[address >> CacheLine::BITS]) [[unlikely]] {
229
		// slot-select-ignore writes (Super Lode Runner)
230
		for (auto& g : globalWrites) {
231
			// very primitive address selection mechanism,
232
			// but more than enough for now
233
			if (g.addr == address) [[unlikely]] {
234
				g.device->globalWrite(address, value, time);
235
			}
236
		}
237
		// Execute write watches after actual write.
238
		//
239
		// But first advance time for the tiniest amount, this makes
240
		// sure that later on a possible replay we also replay recorded
241
		// commands after the actual memory write (e.g. this matters
242
		// when that command is also a memory write)
243
		motherBoard.getScheduler().schedule(time + EmuDuration::epsilon());
244
		if (writeWatchSet[address >> CacheLine::BITS]
245
		                 [address &  CacheLine::LOW]) {
246
			executeMemWatch(WatchPoint::Type::WRITE_MEM, address, value);
247
		}
248
	}
249
}
250
251
void MSXCPUInterface::setExpanded(int ps)
252
{
253
	if (expanded[ps] == 0) {
254
		for (auto page : xrange(4)) {
255
			if (slotLayout[ps][0][page] != dummyDevice.get()) {
256
				throw MSXException("Can't expand slot because "
257
				                   "it's already in use.");
258
			}
259
		}
260
	}
261
	expanded[ps]++;
262
	changeExpanded(isExpanded(primarySlotState[3]));
263
}
264
265
void MSXCPUInterface::testUnsetExpanded(
266
		int ps,
267
		std::span<const std::unique_ptr<MSXDevice>> allowed) const
268
{
269
	assert(isExpanded(ps));
270
	if (expanded[ps] != 1) return; // ok, still expanded after this
271
272
	std::vector<const MSXDevice*> inUse;
273
274
	auto isAllowed = [&](const MSXDevice* dev) {
275
		return (dev == dummyDevice.get()) ||
276
		       contains(allowed, dev, [](const auto& d) { return d.get(); });
277
	};
278
	auto check = [&](const MSXDevice* dev) {
279
		if (!isAllowed(dev)) {
280
			if (!contains(inUse, dev)) { // filter duplicates
281
				inUse.push_back(dev);
282
			}
283
		}
284
	};
285
286
	for (auto ss : xrange(4)) {
287
		for (auto page : xrange(4)) {
288
			const MSXDevice* device = slotLayout[ps][ss][page];
289
			if (const auto* memDev = dynamic_cast<const MSXMultiMemDevice*>(device)) {
290
				for (const auto* dev : memDev->getDevices()) {
291
					check(dev);
292
				}
293
			} else {
294
				check(device);
295
			}
296
297
		}
298
	}
299
	if (inUse.empty()) return; // ok, no more devices in use
300
301
	auto msg = strCat("Can't remove slot expander from slot ", ps,
302
	                  " because the following devices are still inserted:");
303
	for (const auto& d : inUse) {
304
		strAppend(msg, ' ', d->getName());
305
	}
306
	strAppend(msg, '.');
307
	throw MSXException(std::move(msg));
308
}
309
310
void MSXCPUInterface::unsetExpanded(int ps)
311
{
312
#ifndef NDEBUG
313
	try {
314
		std::span<const std::unique_ptr<MSXDevice>> dummy;
315
		testUnsetExpanded(ps, dummy);
316
	} catch (...) {
317
		UNREACHABLE;
318
	}
319
#endif
320
	expanded[ps]--;
321
	changeExpanded(isExpanded(primarySlotState[3]));
322
}
323
324
void MSXCPUInterface::changeExpanded(bool newExpanded)
325
{
326
	if (newExpanded) {
327
		disallowReadCache [0xFF] |=  SECONDARY_SLOT_BIT;
328
		disallowWriteCache[0xFF] |=  SECONDARY_SLOT_BIT;
329
	} else {
330
		disallowReadCache [0xFF] &= ~SECONDARY_SLOT_BIT;
331
		disallowWriteCache[0xFF] &= ~SECONDARY_SLOT_BIT;
332
	}
333
	msxcpu.invalidateAllSlotsRWCache(0xFFFF & CacheLine::HIGH, 0x100);
334
}
335
336
MSXDevice*& MSXCPUInterface::getDevicePtr(uint8_t port, bool isIn)
337
{
338
	MSXDevice** devicePtr = isIn ? &IO_In[port] : &IO_Out[port];
339
	while (auto* watch = dynamic_cast<MSXWatchIODevice*>(*devicePtr)) {
340
		devicePtr = &watch->getDevicePtr();
341
	}
342
	if (*devicePtr == delayDevice.get()) {
343
		devicePtr = isIn ? &delayDevice->getInDevicePtr (port)
344
		                 : &delayDevice->getOutDevicePtr(port);
345
	}
346
	return *devicePtr;
347
}
348
349
void MSXCPUInterface::register_IO_In(uint8_t port, MSXDevice* device)
350
{
351
	MSXDevice*& devicePtr = getDevicePtr(port, true); // in
352
	register_IO(port, true, devicePtr, device); // in
353
}
354
355
void MSXCPUInterface::unregister_IO_In(uint8_t port, MSXDevice* device)
356
{
357
	MSXDevice*& devicePtr = getDevicePtr(port, true); // in
358
	unregister_IO(devicePtr, device);
359
}
360
361
void MSXCPUInterface::register_IO_Out(uint8_t port, MSXDevice* device)
362
{
363
	MSXDevice*& devicePtr = getDevicePtr(port, false); // out
364
	register_IO(port, false, devicePtr, device); // out
365
}
366
367
void MSXCPUInterface::unregister_IO_Out(uint8_t port, MSXDevice* device)
368
{
369
	MSXDevice*& devicePtr = getDevicePtr(port, false); // out
370
	unregister_IO(devicePtr, device);
371
}
372
373
void MSXCPUInterface::register_IO_InOut(uint8_t port, MSXDevice* device)
374
{
375
	register_IO_In(port, device);
376
	register_IO_Out(port, device);
377
}
378
void MSXCPUInterface::unregister_IO_InOut(uint8_t port, MSXDevice* device)
379
{
380
	unregister_IO_In(port, device);
381
	unregister_IO_Out(port, device);
382
}
383
void MSXCPUInterface::register_IO_In_range(uint8_t port, unsigned num, MSXDevice* device)
384
{
385
	for (auto i : xrange(num)) register_IO_In(narrow<uint8_t>(port + i), device);
386
}
387
void MSXCPUInterface::register_IO_Out_range(uint8_t port, unsigned num, MSXDevice* device)
388
{
389
	for (auto i : xrange(num)) register_IO_Out(narrow<uint8_t>(port + i), device);
390
}
391
void MSXCPUInterface::register_IO_InOut_range(uint8_t port, unsigned num, MSXDevice* device)
392
{
393
	for (auto i : xrange(num)) register_IO_InOut(narrow<uint8_t>(port + i), device);
394
}
395
void MSXCPUInterface::unregister_IO_In_range(uint8_t port, unsigned num, MSXDevice* device)
396
{
397
	for (auto i : xrange(num)) unregister_IO_In(narrow<uint8_t>(port + i), device);
398
}
399
void MSXCPUInterface::unregister_IO_Out_range(uint8_t port, unsigned num, MSXDevice* device)
400
{
401
	for (auto i : xrange(num)) unregister_IO_Out(narrow<uint8_t>(port + i), device);
402
}
403
void MSXCPUInterface::unregister_IO_InOut_range(uint8_t port, unsigned num, MSXDevice* device)
404
{
405
	for (auto i : xrange(num)) unregister_IO_InOut(narrow<uint8_t>(port + i), device);
406
}
407
408
void MSXCPUInterface::register_IO(int port, bool isIn,
409
                                  MSXDevice*& devicePtr, MSXDevice* device)
410
{
411
	if (devicePtr == dummyDevice.get()) {
412
		// first, replace DummyDevice
413
		devicePtr = device;
414
	} else {
415
		if (auto* multi = dynamic_cast<MSXMultiIODevice*>(devicePtr)) {
416
			// third or more, add to existing MultiIO device
417
			multi->addDevice(device);
418
		} else {
419
			// second, create a MultiIO device
420
			multi = new MSXMultiIODevice(device->getHardwareConfig());
421
			multi->addDevice(devicePtr);
422
			multi->addDevice(device);
423
			devicePtr = multi;
424
		}
425
		if (isIn) {
426
			const auto& devices = motherBoard.getMachineConfig()->getDevicesElem();
427
			if (devices.getAttributeValueAsBool("overlap_warning", true)) {
428
				cliComm.printWarning(
429
					"Conflicting input port 0x",
430
					hex_string<2>(port),
431
					" for devices ", devicePtr->getName());
432
			}
433
		}
434
	}
435
}
436
437
void MSXCPUInterface::unregister_IO(MSXDevice*& devicePtr, MSXDevice* device)
438
{
439
	if (auto* multi = dynamic_cast<MSXMultiIODevice*>(devicePtr)) {
440
		// remove from MultiIO device
441
		multi->removeDevice(device);
442
		auto& devices = multi->getDevices();
443
		if (devices.size() == 1) {
444
			// only one remaining, remove MultiIO device
445
			devicePtr = devices.front();
446
			devices.pop_back();
447
			delete multi;
448
		}
449
	} else {
450
		// remove last, put back DummyDevice
451
		assert(devicePtr == device);
452
		devicePtr = dummyDevice.get();
453
	}
454
}
455
456
bool MSXCPUInterface::replace_IO_In(
457
	uint8_t port, MSXDevice* oldDevice, MSXDevice* newDevice)
458
{
459
	MSXDevice*& devicePtr = getDevicePtr(port, true); // in
460
	if (devicePtr != oldDevice) {
461
		// error, this was not the expected device
462
		return false;
463
	}
464
	devicePtr = newDevice;
465
	return true;
466
}
467
bool MSXCPUInterface::replace_IO_Out(
468
	uint8_t port, MSXDevice* oldDevice, MSXDevice* newDevice)
469
{
470
	MSXDevice*& devicePtr = getDevicePtr(port, false); // out
471
	if (devicePtr != oldDevice) {
472
		// error, this was not the expected device
473
		return false;
474
	}
475
	devicePtr = newDevice;
476
	return true;
477
}
478
479
[[noreturn]] static void reportMemOverlap(int ps, int ss, const MSXDevice& dev1, const MSXDevice& dev2)
480
{
481
	throw MSXException(
482
		"Overlapping memory devices in slot ", ps, '.', ss,
483
		": ", dev1.getName(), " and ", dev2.getName(), '.');
484
}
485
486
void MSXCPUInterface::testRegisterSlot(
487
	const MSXDevice& device, int ps, int ss, unsigned base, unsigned size)
488
{
489
	auto page = base >> 14;
490
	MSXDevice*& slot = slotLayout[ps][ss][page];
491
	if (size == 0x4000) {
492
		// full 16kb, directly register device (no multiplexer)
493
		if (slot != dummyDevice.get()) {
494
			reportMemOverlap(ps, ss, *slot, device);
495
		}
496
	} else {
497
		// partial page
498
		if (slot == dummyDevice.get()) {
499
			// first, ok
500
		} else if (auto* multi = dynamic_cast<MSXMultiMemDevice*>(slot)) {
501
			// second (or more), check for overlap
502
			if (!multi->canAdd(base, size)) {
503
				reportMemOverlap(ps, ss, *slot, device);
504
			}
505
		} else {
506
			// conflict with 'full ranged' device
507
			reportMemOverlap(ps, ss, *slot, device);
508
		}
509
	}
510
}
511
512
void MSXCPUInterface::registerSlot(
513
	MSXDevice& device, int ps, int ss, unsigned base, unsigned size)
514
{
515
	auto page = narrow<uint8_t>(base >> 14);
516
	MSXDevice*& slot = slotLayout[ps][ss][page];
517
	if (size == 0x4000) {
518
		// full 16kb, directly register device (no multiplexer)
519
		assert(slot == dummyDevice.get());
520
		slot = &device;
521
	} else {
522
		// partial page
523
		if (slot == dummyDevice.get()) {
524
			// first
525
			auto* multi = new MSXMultiMemDevice(device.getHardwareConfig());
526
			multi->add(device, base, size);
527
			slot = multi;
528
		} else if (auto* multi = dynamic_cast<MSXMultiMemDevice*>(slot)) {
529
			// second or more
530
			assert(multi->canAdd(base, size));
531
			multi->add(device, base, size);
532
		} else {
533
			// conflict with 'full ranged' device
534
			assert(false);
535
		}
536
	}
537
	invalidateRWCache(narrow<uint16_t>(base), size, ps, ss);
538
	updateVisible(page);
539
}
540
541
void MSXCPUInterface::unregisterSlot(
542
	MSXDevice& device, int ps, int ss, unsigned base, unsigned size)
543
{
544
	auto page = narrow<uint8_t>(base >> 14);
545
	MSXDevice*& slot = slotLayout[ps][ss][page];
546
	if (auto* multi = dynamic_cast<MSXMultiMemDevice*>(slot)) {
547
		// partial range
548
		multi->remove(device, base, size);
549
		if (multi->empty()) {
550
			delete multi;
551
			slot = dummyDevice.get();
552
		}
553
	} else {
554
		// full 16kb range
555
		assert(slot == &device);
556
		slot = dummyDevice.get();
557
	}
558
	invalidateRWCache(narrow<uint16_t>(base), size, ps, ss);
559
	updateVisible(page);
560
}
561
562
void MSXCPUInterface::registerMemDevice(
563
	MSXDevice& device, int ps, int ss, unsigned base_, unsigned size_)
564
{
565
	if (!isExpanded(ps) && (ss != 0)) {
566
		throw MSXException(
567
			"Slot ", ps, '.', ss,
568
			" does not exist because slot is not expanded.");
569
	}
570
571
	// split range on 16kb borders
572
	// first check if registration is possible
573
	auto base = base_;
574
	auto size = size_;
575
	while (size != 0) {
576
		auto partialSize = std::min(size, ((base + 0x4000) & ~0x3FFF) - base);
577
		testRegisterSlot(device, ps, ss, base, partialSize);
578
		base += partialSize;
579
		size -= partialSize;
580
	}
581
	// if all checks are successful, only then actually register
582
	base = base_;
583
	size = size_;
584
	while (size != 0) {
585
		auto partialSize = std::min(size, ((base + 0x4000) & ~0x3FFF) - base);
586
		registerSlot(device, ps, ss, base, partialSize);
587
		base += partialSize;
588
		size -= partialSize;
589
	}
590
}
591
592
void MSXCPUInterface::unregisterMemDevice(
593
	MSXDevice& device, int ps, int ss, unsigned base, unsigned size)
594
{
595
	// split range on 16kb borders
596
	while (size != 0) {
597
		auto partialSize = std::min(size, ((base + 0x4000) & ~0x3FFF) - base);
598
		unregisterSlot(device, ps, ss, base, partialSize);
599
		base += partialSize;
600
		size -= partialSize;
601
	}
602
}
603
604
void MSXCPUInterface::registerGlobalWrite(MSXDevice& device, uint16_t address)
605
{
606
	globalWrites.push_back({&device, address});
607
608
	disallowWriteCache[address >> CacheLine::BITS] |= GLOBAL_RW_BIT;
609
	msxcpu.invalidateAllSlotsRWCache(address & CacheLine::HIGH, 0x100);
610
}
611
612
void MSXCPUInterface::unregisterGlobalWrite(MSXDevice& device, uint16_t address)
613
{
614
	GlobalRwInfo info = { .device = &device, .addr = address };
615
	move_pop_back(globalWrites, rfind_unguarded(globalWrites, info));
616
617
	for (const auto& g : globalWrites) {
618
		if ((g.addr >> CacheLine::BITS) ==
619
		    (address  >> CacheLine::BITS)) {
620
			// there is still a global write in this region
621
			return;
622
		}
623
	}
624
	disallowWriteCache[address >> CacheLine::BITS] &= ~GLOBAL_RW_BIT;
625
	msxcpu.invalidateAllSlotsRWCache(address & CacheLine::HIGH, 0x100);
626
}
627
628
void MSXCPUInterface::registerGlobalRead(MSXDevice& device, uint16_t address)
629
{
630
	globalReads.push_back({&device, address});
631
632
	disallowReadCache[address >> CacheLine::BITS] |= GLOBAL_RW_BIT;
633
	msxcpu.invalidateAllSlotsRWCache(address & CacheLine::HIGH, 0x100);
634
}
635
636
void MSXCPUInterface::unregisterGlobalRead(MSXDevice& device, uint16_t address)
637
{
638
	GlobalRwInfo info = { .device = &device, .addr = address };
639
	move_pop_back(globalReads, rfind_unguarded(globalReads, info));
640
641
	for (const auto& g : globalReads) {
642
		if ((g.addr >> CacheLine::BITS) ==
643
		    (address  >> CacheLine::BITS)) {
644
			// there is still a global write in this region
645
			return;
646
		}
647
	}
648
	disallowReadCache[address >> CacheLine::BITS] &= ~GLOBAL_RW_BIT;
649
	msxcpu.invalidateAllSlotsRWCache(address & CacheLine::HIGH, 0x100);
650
}
651
652
ALWAYS_INLINE void MSXCPUInterface::updateVisible(uint8_t page, uint8_t ps, uint8_t ss)
653
{
654
	MSXDevice* newDevice = slotLayout[ps][ss][page];
655
	if (visibleDevices[page] != newDevice) {
656
		visibleDevices[page] = newDevice;
657
		msxcpu.updateVisiblePage(page, ps, ss);
658
	}
659
}
660
void MSXCPUInterface::updateVisible(uint8_t page)
661
{
662
	updateVisible(page, primarySlotState[page], secondarySlotState[page]);
663
}
664
665
void MSXCPUInterface::invalidateRWCache(uint16_t start, unsigned size, int ps, int ss)
666
{
667
	tick(CacheLineCounters::InvalidateReadWrite);
668
	msxcpu.invalidateRWCache(start, size, ps, ss, disallowReadCache, disallowWriteCache);
669
}
670
void MSXCPUInterface::invalidateRCache (uint16_t start, unsigned size, int ps, int ss)
671
{
672
	tick(CacheLineCounters::InvalidateRead);
673
	msxcpu.invalidateRCache(start, size, ps, ss, disallowReadCache, disallowWriteCache);
674
}
675
void MSXCPUInterface::invalidateWCache (uint16_t start, unsigned size, int ps, int ss)
676
{
677
	tick(CacheLineCounters::InvalidateWrite);
678
	msxcpu.invalidateWCache(start, size, ps, ss, disallowReadCache, disallowWriteCache);
679
}
680
681
void MSXCPUInterface::fillRWCache(unsigned start, unsigned size, const uint8_t* rData, uint8_t* wData, int ps, int ss)
682
{
683
	tick(CacheLineCounters::FillReadWrite);
684
	msxcpu.fillRWCache(start, size, rData, wData, ps, ss, disallowReadCache, disallowWriteCache);
685
}
686
void MSXCPUInterface::fillRCache(unsigned start, unsigned size, const uint8_t* rData, int ps, int ss)
687
{
688
	tick(CacheLineCounters::FillRead);
689
	msxcpu.fillRCache(start, size, rData, ps, ss, disallowReadCache, disallowWriteCache);
690
}
691
void MSXCPUInterface::fillWCache(unsigned start, unsigned size, uint8_t* wData, int ps, int ss)
692
{
693
	tick(CacheLineCounters::FillWrite);
694
	msxcpu.fillWCache(start, size, wData, ps, ss, disallowReadCache, disallowWriteCache);
695
}
696
697
void MSXCPUInterface::reset()
698
{
699
	for (auto i : xrange(uint8_t(4))) {
700
		setSubSlot(i, 0);
701
	}
702
	setPrimarySlots(initialPrimarySlots);
703
}
704
705
uint8_t MSXCPUInterface::readIRQVector() const
706
{
707
	return motherBoard.readIRQVector();
708
}
709
710
void MSXCPUInterface::setPrimarySlots(uint8_t value)
711
{
712
	// Change the slot structure.
713
	// Originally the code below was a loop over the 4 pages, and the check
714
	// for (un)expanded-slot was done unconditionally at the end. I've
715
	// completely unrolled the loop and only check for (un)expanded slot
716
	// when the slot in page 3 has changed. I've also added checks for slot
717
	// changes for the other 3 pages. Usually when this register is written
718
	// only one of the 4 pages actually changes, so these extra checks do
719
	// pay off. This does make the code a bit more complex (and the
720
	// generated code slightly bigger), but it does make a measurable speed
721
	// difference.  Changing the slots several hundreds of times per
722
	// (EmuTime) is not unusual. So this routine ended up quite high
723
	// (top-10) in some profile results.
724
	if (uint8_t ps0 = (value >> 0) & 3; primarySlotState[0] != ps0) [[unlikely]] {
725
		primarySlotState[0] = ps0;
726
		uint8_t ss0 = (subSlotRegister[ps0] >> 0) & 3;
727
		secondarySlotState[0] = ss0;
728
		updateVisible(0, ps0, ss0);
729
	}
730
	if (uint8_t ps1 = (value >> 2) & 3; primarySlotState[1] != ps1) [[unlikely]] {
731
		primarySlotState[1] = ps1;
732
		uint8_t ss1 = (subSlotRegister[ps1] >> 2) & 3;
733
		secondarySlotState[1] = ss1;
734
		updateVisible(1, ps1, ss1);
735
	}
736
	if (uint8_t ps2 = (value >> 4) & 3; primarySlotState[2] != ps2) [[unlikely]] {
737
		primarySlotState[2] = ps2;
738
		uint8_t ss2 = (subSlotRegister[ps2] >> 4) & 3;
739
		secondarySlotState[2] = ss2;
740
		updateVisible(2, ps2, ss2);
741
	}
742
	if (uint8_t ps3 = (value >> 6) & 3; primarySlotState[3] != ps3) [[unlikely]] {
743
		bool oldExpanded = isExpanded(primarySlotState[3]);
744
		bool newExpanded = isExpanded(ps3);
745
		primarySlotState[3] = ps3;
746
		uint8_t ss3 = (subSlotRegister[ps3] >> 6) & 3;
747
		secondarySlotState[3] = ss3;
748
		updateVisible(3, ps3, ss3);
749
		if (oldExpanded != newExpanded) [[unlikely]] {
750
			changeExpanded(newExpanded);
751
		}
752
	}
753
}
754
755
void MSXCPUInterface::setSubSlot(uint8_t primSlot, uint8_t value)
756
{
757
	subSlotRegister[primSlot] = value;
758
	for (uint8_t page = 0; page < 4; ++page, value >>= 2) {
759
		if (primSlot == primarySlotState[page]) {
760
			secondarySlotState[page] = value & 3;
761
			// Change the visible devices
762
			updateVisible(page);
763
		}
764
	}
765
}
766
767
uint8_t MSXCPUInterface::peekMem(uint16_t address, EmuTime time) const
768
{
769
	if ((address == 0xFFFF) && isExpanded(primarySlotState[3])) {
770
		return 0xFF ^ subSlotRegister[primarySlotState[3]];
771
	} else {
772
		return visibleDevices[address >> 14]->peekMem(address, time);
773
	}
774
}
775
776
// Similar to peekSlottedMem(), but can read a whole block at once
777
void MSXCPUInterface::peekSlottedMemBlock(unsigned address, std::span<uint8_t> output, EmuTime time) const
778
{
779
	auto getCacheLine = [&]() -> const uint8_t* {
780
		uint8_t primSlot = (address & 0xC0000) >> 18;
781
		bool exp = isExpanded(primSlot);
782
		uint16_t offset = (address & (0xFFFF & CacheLine::HIGH)); // includes page
783
		if ((offset == (0xFFFF & CacheLine::HIGH)) && exp) {
784
			return nullptr;
785
		} else {
786
			uint8_t subSlot = exp ? ((address & 0x30000) >> 16) : 0;
787
			uint8_t page = (address & 0x0C000) >> 14;
788
			return slotLayout[primSlot][subSlot][page]->getReadCacheLine(offset);
789
		}
790
	};
791
	auto processChunk = [&](size_t start, size_t n) {
792
		assert(start < CacheLine::SIZE);
793
		assert((start + n) <= CacheLine::SIZE);
794
795
		if (const auto* line = getCacheLine()) {
796
			copy_to_range(std::span{line + start, n}, output);
797
		} else {
798
			for (auto i : xrange(n)) {
799
				output[i] = peekSlottedMem(narrow<unsigned>(address + i), time);
800
			}
801
		}
802
		output = output.subspan(n);
803
		address += n;
804
	};
805
806
	if (auto l = address & CacheLine::LOW) { // start not aligned on cacheline boundary
807
		auto n = std::min<size_t>(output.size(), CacheLine::SIZE - l);
808
		processChunk(l, n);
809
	}
810
	while (output.size() >= CacheLine::SIZE) { // full cachelines
811
		processChunk(0, CacheLine::SIZE);
812
	}
813
	if (auto n = output.size()) { // trailing partial cache line
814
		processChunk(0, n);
815
	}
816
	assert(output.empty()); // fully processed
817
}
818
819
uint8_t MSXCPUInterface::peekSlottedMem(unsigned address, EmuTime time) const
820
{
821
	uint8_t primSlot = (address & 0xC0000) >> 18;
822
	bool exp = isExpanded(primSlot);
823
	uint16_t offset = (address & 0xFFFF); // includes page
824
825
	if ((offset == 0xFFFF) && exp) {
826
		return 0xFF ^ subSlotRegister[primSlot];
827
	} else {
828
		uint8_t subSlot = exp ? ((address & 0x30000) >> 16) : 0;
829
		uint8_t page = (address & 0x0C000) >> 14;
830
		return slotLayout[primSlot][subSlot][page]->peekMem(offset, time);
831
	}
832
}
833
834
uint8_t MSXCPUInterface::readSlottedMem(unsigned address, EmuTime time)
835
{
836
	uint8_t primSlot = (address & 0xC0000) >> 18;
837
	uint8_t subSlot = (address & 0x30000) >> 16;
838
	uint8_t page = (address & 0x0C000) >> 14;
839
	uint16_t offset = (address & 0xFFFF); // includes page
840
	if (!isExpanded(primSlot)) {
841
		subSlot = 0;
842
	}
843
844
	if ((offset == 0xFFFF) && isExpanded(primSlot)) {
845
		return 0xFF ^ subSlotRegister[primSlot];
846
	} else {
847
		return slotLayout[primSlot][subSlot][page]->peekMem(offset, time);
848
	}
849
}
850
851
void MSXCPUInterface::writeSlottedMem(unsigned address, uint8_t value,
852
                                      EmuTime time)
853
{
854
	uint8_t primSlot = (address & 0xC0000) >> 18;
855
	uint8_t subSlot = (address & 0x30000) >> 16;
856
	uint8_t page = (address & 0x0C000) >> 14;
857
	uint16_t offset = (address & 0xFFFF); // includes page
858
	if (!isExpanded(primSlot)) {
859
		subSlot = 0;
860
	}
861
862
	if ((offset == 0xFFFF) && isExpanded(primSlot)) {
863
		setSubSlot(primSlot, value);
864
	} else {
865
		slotLayout[primSlot][subSlot][page]->writeMem(offset, value, time);
866
	}
867
}
868
869
void MSXCPUInterface::insertBreakPoint(BreakPoint bp)
870
{
871
	cliComm.update(CliComm::UpdateType::DEBUG_UPDT, bp.getIdStr(), "add");
872
	breakPoints.push_back(std::move(bp));
873
}
874
875
void MSXCPUInterface::removeBreakPoint(const BreakPoint& bp)
876
{
877
	cliComm.update(CliComm::UpdateType::DEBUG_UPDT, bp.getIdStr(), "remove");
878
	breakPoints.erase(find_unguarded(breakPoints, &bp, [](const BreakPoint& i) { return &i; }));
879
}
880
void MSXCPUInterface::removeBreakPoint(unsigned id)
881
{
882
	if (auto it = std::ranges::find(breakPoints, id, &BreakPoint::getId);
883
	    // could be ==end for a breakpoint that removes itself AND has the -once flag set
884
	    it != breakPoints.end()) {
885
		cliComm.update(CliComm::UpdateType::DEBUG_UPDT, it->getIdStr(), "remove");
886
		breakPoints.erase(it);
887
	}
888
}
889
890
bool MSXCPUInterface::checkBreakPoints(unsigned pc)
891
{
892
	// create copy for the case that breakpoint/condition removes itself
893
	//  - avoids iterating over a changing collection
894
	std::vector<BreakPoint> bpCopy;
895
	for (const auto& bp : breakPoints) {
896
		if (bp.isEnabled() && bp.getAddress() == pc) bpCopy.push_back(bp);
897
	}
898
	std::vector<DebugCondition> condCopy;
899
	for (const auto& cond : conditions) {
900
		if (cond.isEnabled()) condCopy.push_back(cond);
901
	}
902
	if (bpCopy.empty() && condCopy.empty()) return false;
903
904
	auto& globalCliComm = motherBoard.getReactor().getGlobalCliComm();
905
	auto& interp        = motherBoard.getReactor().getInterpreter();
906
	auto scopedBlock = motherBoard.getStateChangeDistributor().tempBlockNewEventsDuringReplay();
907
	for (auto& p : bpCopy) {
908
		bool remove = p.checkAndExecute(globalCliComm, interp);
909
		if (remove) {
910
			removeBreakPoint(p.getId());
911
		}
912
	}
913
	for (auto& c : condCopy) {
914
		bool remove = c.checkAndExecute(globalCliComm, interp);
915
		if (remove) {
916
			removeCondition(c.getId());
917
		}
918
	}
919
	return isBreaked();
920
}
921
922
void MSXCPUInterface::registerWatchPoint(WatchPoint& wp)
923
{
924
	auto type = wp.getType();
925
	switch (type) {
926
	using enum WatchPoint::Type;
927
	case READ_IO:
928
		wp.registerIOWatch(motherBoard, IO_In);
929
		break;
930
	case WRITE_IO:
931
		wp.registerIOWatch(motherBoard, IO_Out);
932
		break;
933
	case READ_MEM:
934
	case WRITE_MEM:
935
		updateMemWatch(type);
936
		break;
937
	default:
938
		UNREACHABLE;
939
	}
940
}
941
942
void MSXCPUInterface::unregisterWatchPoint(WatchPoint& wp)
943
{
944
	auto type = wp.getType();
945
	switch (type) {
946
	using enum WatchPoint::Type;
947
	case READ_IO:
948
		wp.unregisterIOWatch(IO_In);
949
		break;
950
	case WRITE_IO:
951
		wp.unregisterIOWatch(IO_Out);
952
		break;
953
	case READ_MEM:
954
	case WRITE_MEM:
955
		updateMemWatch(type);
956
		break;
957
	default:
958
		UNREACHABLE;
959
	}
960
}
961
962
void MSXCPUInterface::setWatchPoint(const std::shared_ptr<WatchPoint>& watchPoint)
963
{
964
	cliComm.update(CliComm::UpdateType::DEBUG_UPDT, watchPoint->getIdStr(), "add");
965
	watchPoints.push_back(watchPoint);
966
	registerWatchPoint(*watchPoint);
967
}
968
969
void MSXCPUInterface::removeWatchPoint(std::shared_ptr<WatchPoint> watchPoint)
970
{
971
	// Pass shared_ptr by value to keep the object alive for the duration
972
	// of this function, otherwise it gets deleted as soon as it's removed
973
	// from the watchPoints collection.
974
	if (auto it = std::ranges::find(watchPoints, watchPoint);
975
	    it != end(watchPoints)) {
976
		cliComm.update(CliComm::UpdateType::DEBUG_UPDT, watchPoint->getIdStr(), "remove");
977
		// remove before calling updateMemWatch()
978
		watchPoints.erase(it);
979
		unregisterWatchPoint(*watchPoint);
980
	}
981
}
982
983
void MSXCPUInterface::removeWatchPoint(unsigned id)
984
{
985
	if (auto it = std::ranges::find(watchPoints, id, &WatchPoint::getId);
986
	    it != watchPoints.end()) {
987
		removeWatchPoint(*it); // not efficient, does a 2nd search, but good enough
988
	}
989
}
990
991
void MSXCPUInterface::setCondition(DebugCondition cond)
992
{
993
	cliComm.update(CliComm::UpdateType::DEBUG_UPDT, cond.getIdStr(), "add");
994
	conditions.push_back(std::move(cond));
995
}
996
997
void MSXCPUInterface::removeCondition(const DebugCondition& cond)
998
{
999
	cliComm.update(CliComm::UpdateType::DEBUG_UPDT, cond.getIdStr(), "remove");
1000
	conditions.erase(rfind_unguarded(conditions, &cond,
1001
	                                 [](auto& e) { return &e; }));
1002
}
1003
1004
void MSXCPUInterface::removeCondition(unsigned id)
1005
{
1006
	if (auto it = std::ranges::find(conditions, id, &DebugCondition::getId);
1007
	    // could be ==end for a condition that removes itself AND has the -once flag set
1008
	    it != conditions.end()) {
1009
		cliComm.update(CliComm::UpdateType::DEBUG_UPDT, it->getIdStr(), "remove");
1010
		conditions.erase(it);
1011
	}
1012
}
1013
1014
void MSXCPUInterface::updateMemWatch(WatchPoint::Type type)
1015
{
1016
	std::span<std::bitset<CacheLine::SIZE>, CacheLine::NUM> watchSet =
1017
		(type == WatchPoint::Type::READ_MEM) ? readWatchSet : writeWatchSet;
1018
	for (auto i : xrange(CacheLine::NUM)) {
1019
		watchSet[i].reset();
1020
	}
1021
	for (const auto& w : watchPoints) {
1022
		if (w->getType() == type) {
1023
			auto begin = w->getBeginAddress();
1024
			auto end = w->getEndAddress();
1025
			if (!begin || !end) continue;
1026
			assert(begin <= end);
1027
			for (unsigned addr = *begin; addr <= *end; ++addr) {
1028
				watchSet[addr >> CacheLine::BITS].set(
1029
				         addr  & CacheLine::LOW);
1030
			}
1031
		}
1032
	}
1033
	for (auto i : xrange(CacheLine::NUM)) {
1034
		if (readWatchSet [i].any()) {
1035
			disallowReadCache [i] |=  MEMORY_WATCH_BIT;
1036
		} else {
1037
			disallowReadCache [i] &= ~MEMORY_WATCH_BIT;
1038
		}
1039
		if (writeWatchSet[i].any()) {
1040
			disallowWriteCache[i] |=  MEMORY_WATCH_BIT;
1041
		} else {
1042
			disallowWriteCache[i] &= ~MEMORY_WATCH_BIT;
1043
		}
1044
	}
1045
	msxcpu.invalidateAllSlotsRWCache(0x0000, 0x10000);
1046
}
1047
1048
void MSXCPUInterface::executeMemWatch(WatchPoint::Type type,
1049
                                      unsigned address, unsigned value)
1050
{
1051
	assert(!watchPoints.empty());
1052
	if (isFastForward()) return;
1053
1054
	auto& globalCliComm = motherBoard.getReactor().getGlobalCliComm();
1055
	auto& interp        = motherBoard.getReactor().getInterpreter();
1056
	interp.setVariable(TclObject("wp_last_address"),
1057
	                   TclObject(int(address)));
1058
	if (value != ~0u) {
1059
		interp.setVariable(TclObject("wp_last_value"),
1060
		                   TclObject(int(value)));
1061
	}
1062
1063
	auto scopedBlock = motherBoard.getStateChangeDistributor().tempBlockNewEventsDuringReplay();
1064
	for( auto wpCopy = watchPoints; auto& w : wpCopy) {
1065
		if ((w->getBeginAddress() <= address) &&
1066
		    (w->getEndAddress()   >= address) &&
1067
		    (w->getType()         == type)) {
1068
			bool remove = w->checkAndExecute(globalCliComm, interp);
1069
			if (remove) {
1070
				removeWatchPoint(w);
1071
			}
1072
		}
1073
	}
1074
1075
	interp.unsetVariable("wp_last_address");
1076
	interp.unsetVariable("wp_last_value");
1077
}
1078
1079
1080
void MSXCPUInterface::doBreak()
1081
{
1082
	assert(!isFastForward());
1083
	if (breaked) return;
1084
	breaked = true;
1085
	msxcpu.exitCPULoopSync();
1086
1087
	Reactor& reactor = motherBoard.getReactor();
1088
	reactor.block();
1089
	breakedSetting->setReadOnlyValue(TclObject("true"));
1090
	reactor.getCliComm().update(CliComm::UpdateType::STATUS, "cpu", "suspended");
1091
	reactor.getEventDistributor().distributeEvent(BreakEvent());
1092
}
1093
1094
void MSXCPUInterface::doStep()
1095
{
1096
	assert(!isFastForward());
1097
	DebugCondition dc; // cmd = debug break
1098
	dc.setOnce(true);
1099
	setCondition(std::move(dc));
1100
	doContinue();
1101
}
1102
1103
void MSXCPUInterface::doContinue()
1104
{
1105
	assert(!isFastForward());
1106
	pauseSetting.setBoolean(false); // unpause
1107
	if (breaked) {
1108
		breaked = false;
1109
1110
		Reactor& reactor = motherBoard.getReactor();
1111
		breakedSetting->setReadOnlyValue(TclObject("false"));
1112
		reactor.getCliComm().update(CliComm::UpdateType::STATUS, "cpu", "running");
1113
		reactor.getEventDistributor().distributeEvent(ContinueEvent());
1114
		reactor.unblock();
1115
		motherBoard.getRealTime().resync();
1116
	}
1117
}
1118
1119
void MSXCPUInterface::cleanup()
1120
{
1121
	// before the Tcl interpreter is destroyed, we must delete all
1122
	// TclObjects. Breakpoints and conditions contain such objects
1123
	// for the condition and action.
1124
	// TODO it would be nicer if breakpoints and conditions were not
1125
	//      global objects.
1126
	breakPoints.clear();
1127
	conditions.clear();
1128
}
1129
1130
MSXDevice* MSXCPUInterface::getMSXDevice(int ps, int ss, int page)
1131
{
1132
	assert(0 <= ps && ps < 4);
1133
	assert(0 <= ss && ss < 4);
1134
	assert(0 <= page && page < 4);
1135
	return slotLayout[ps][ss][page];
1136
}
1137
1138
// class MemoryDebug
1139
1140
MSXCPUInterface::MemoryDebug::MemoryDebug(MSXMotherBoard& motherBoard_)
1141
	: SimpleDebuggable(motherBoard_, "memory",
1142
	                   "The memory currently visible for the CPU.", 0x10000)
1143
{
1144
}
1145
1146
uint8_t MSXCPUInterface::MemoryDebug::read(unsigned address, EmuTime time)
1147
{
1148
	const auto& interface = OUTER(MSXCPUInterface, memoryDebug);
1149
	return interface.peekMem(narrow<uint16_t>(address), time);
1150
}
1151
1152
void MSXCPUInterface::MemoryDebug::write(unsigned address, uint8_t value,
1153
                                         EmuTime time)
1154
{
1155
	auto& interface = OUTER(MSXCPUInterface, memoryDebug);
1156
	interface.writeMem(narrow<uint16_t>(address), value, time);
1157
}
1158
1159
1160
// class SlottedMemoryDebug
1161
1162
MSXCPUInterface::SlottedMemoryDebug::SlottedMemoryDebug(
1163
		MSXMotherBoard& motherBoard_)
1164
	: SimpleDebuggable(motherBoard_, "slotted memory",
1165
	                   "The memory in slots and subslots.", 0x10000 * 4 * 4)
1166
{
1167
}
1168
1169
void MSXCPUInterface::SlottedMemoryDebug::readBlock(unsigned start, std::span<uint8_t> output)
1170
{
1171
	const auto& interface = OUTER(MSXCPUInterface, slottedMemoryDebug);
1172
	interface.peekSlottedMemBlock(start, output, getMotherBoard().getCurrentTime());
1173
1174
#ifdef DEBUG
1175
	auto time = getMotherBoard().getCurrentTime();
1176
	for (auto i : xrange(output.size())) {
1177
		assert(output[i] == read(start + i, time));
1178
	}
1179
#endif
1180
}
1181
1182
uint8_t MSXCPUInterface::SlottedMemoryDebug::read(unsigned address, EmuTime time)
1183
{
1184
	const auto& interface = OUTER(MSXCPUInterface, slottedMemoryDebug);
1185
	return interface.peekSlottedMem(address, time);
1186
}
1187
1188
void MSXCPUInterface::SlottedMemoryDebug::write(unsigned address, uint8_t value,
1189
                                                EmuTime time)
1190
{
1191
	auto& interface = OUTER(MSXCPUInterface, slottedMemoryDebug);
1192
	interface.writeSlottedMem(address, value, time);
1193
}
1194
1195
1196
// class SlotInfo
1197
1198
static unsigned getSlot(
1199
	Interpreter& interp, const TclObject& token, const std::string& itemName)
1200
{
1201
	unsigned slot = token.getInt(interp);
1202
	if (slot >= 4) {
1203
		throw CommandException(itemName, " must be in range 0..3");
1204
	}
1205
	return slot;
1206
}
1207
1208
MSXCPUInterface::SlotInfo::SlotInfo(
1209
		InfoCommand& machineInfoCommand)
1210
	: InfoTopic(machineInfoCommand, "slot")
1211
{
1212
}
1213
1214
void MSXCPUInterface::SlotInfo::execute(std::span<const TclObject> tokens,
1215
                                        TclObject& result) const
1216
{
1217
	checkNumArgs(tokens, 5, Prefix{2}, "primary secondary page");
1218
	auto& interp = getInterpreter();
1219
	unsigned ps   = getSlot(interp, tokens[2], "Primary slot");
1220
	unsigned ss   = getSlot(interp, tokens[3], "Secondary slot");
1221
	unsigned page = getSlot(interp, tokens[4], "Page");
1222
	auto& interface = OUTER(MSXCPUInterface, slotInfo);
1223
	if (!interface.isExpanded(narrow<int>(ps))) {
1224
		ss = 0;
1225
	}
1226
	interface.slotLayout[ps][ss][page]->getNameList(result);
1227
}
1228
1229
std::string MSXCPUInterface::SlotInfo::help(std::span<const TclObject> /*tokens*/) const
1230
{
1231
	return "Retrieve name of the device inserted in given "
1232
	       "primary slot / secondary slot / page.";
1233
}
1234
1235
1236
// class SubSlottedInfo
1237
1238
MSXCPUInterface::SubSlottedInfo::SubSlottedInfo(
1239
		InfoCommand& machineInfoCommand)
1240
	: InfoTopic(machineInfoCommand, "issubslotted")
1241
{
1242
}
1243
1244
void MSXCPUInterface::SubSlottedInfo::execute(std::span<const TclObject> tokens,
1245
                                              TclObject& result) const
1246
{
1247
	checkNumArgs(tokens, 3, "primary");
1248
	const auto& interface = OUTER(MSXCPUInterface, subSlottedInfo);
1249
	result = interface.isExpanded(narrow<int>(
1250
		getSlot(getInterpreter(), tokens[2], "Slot")));
1251
}
1252
1253
std::string MSXCPUInterface::SubSlottedInfo::help(
1254
	std::span<const TclObject> /*tokens*/) const
1255
{
1256
	return "Indicates whether a certain primary slot is expanded.";
1257
}
1258
1259
1260
// class ExternalSlotInfo
1261
1262
MSXCPUInterface::ExternalSlotInfo::ExternalSlotInfo(
1263
		InfoCommand& machineInfoCommand)
1264
	: InfoTopic(machineInfoCommand, "isexternalslot")
1265
{
1266
}
1267
1268
void MSXCPUInterface::ExternalSlotInfo::execute(
1269
	std::span<const TclObject> tokens, TclObject& result) const
1270
{
1271
	checkNumArgs(tokens, Between{3, 4}, "primary ?secondary?");
1272
	int ps = 0;
1273
	int ss = 0;
1274
	auto& interp = getInterpreter();
1275
	switch (tokens.size()) {
1276
	case 4:
1277
		ss = narrow<int>(getSlot(interp, tokens[3], "Secondary slot"));
1278
		[[fallthrough]];
1279
	case 3:
1280
		ps = narrow<int>(getSlot(interp, tokens[2], "Primary slot"));
1281
		break;
1282
	}
1283
	const auto& interface = OUTER(MSXCPUInterface, externalSlotInfo);
1284
	const auto& manager = interface.motherBoard.getSlotManager();
1285
	result = manager.isExternalSlot(ps, ss, true);
1286
}
1287
1288
std::string MSXCPUInterface::ExternalSlotInfo::help(
1289
	std::span<const TclObject> /*tokens*/) const
1290
{
1291
	return "Indicates whether a certain slot is external or internal.";
1292
}
1293
1294
1295
// class IODebug
1296
1297
MSXCPUInterface::IODebug::IODebug(MSXMotherBoard& motherBoard_)
1298
	: SimpleDebuggable(motherBoard_, "ioports", "IO ports.", 0x100)
1299
{
1300
}
1301
1302
uint8_t MSXCPUInterface::IODebug::read(unsigned address, EmuTime time)
1303
{
1304
	auto& interface = OUTER(MSXCPUInterface, ioDebug);
1305
	return interface.IO_In[address & 0xFF]->peekIO(narrow<uint16_t>(address), time);
1306
}
1307
1308
void MSXCPUInterface::IODebug::write(unsigned address, uint8_t value, EmuTime time)
1309
{
1310
	auto& interface = OUTER(MSXCPUInterface, ioDebug);
1311
	interface.writeIO(uint16_t(address), value, time);
1312
}
1313
1314
1315
// class IOInfo
1316
1317
MSXCPUInterface::IOInfo::IOInfo(InfoCommand& machineInfoCommand, const char* name_)
1318
	: InfoTopic(machineInfoCommand, name_)
1319
{
1320
}
1321
1322
void MSXCPUInterface::IOInfo::helper(
1323
	std::span<const TclObject> tokens, TclObject& result, std::span<MSXDevice*, 256> devices) const
1324
{
1325
	checkNumArgs(tokens, 3, "port");
1326
	unsigned port = tokens[2].getInt(getInterpreter());
1327
	if (port >= 256) {
1328
		throw CommandException("Port must be in range 0..255");
1329
	}
1330
	devices[port]->getNameList(result);
1331
}
1332
void MSXCPUInterface::IInfo::execute(
1333
	std::span<const TclObject> tokens, TclObject& result) const
1334
{
1335
	auto& interface = OUTER(MSXCPUInterface, inputPortInfo);
1336
	helper(tokens, result, interface.IO_In);
1337
}
1338
void MSXCPUInterface::OInfo::execute(
1339
	std::span<const TclObject> tokens, TclObject& result) const
1340
{
1341
	auto& interface = OUTER(MSXCPUInterface, outputPortInfo);
1342
	helper(tokens, result, interface.IO_Out);
1343
}
1344
1345
std::string MSXCPUInterface::IOInfo::help(std::span<const TclObject> /*tokens*/) const
1346
{
1347
	return "Return the name of the device connected to the given IO port.";
1348
}
1349
1350
1351
template<typename Archive>
1352
void MSXCPUInterface::serialize(Archive& ar, unsigned /*version*/)
1353
{
1354
	// TODO watchPoints ???
1355
1356
	// primary and 4 secondary slot select registers
1357
	uint8_t prim = 0;
1358
	if constexpr (!Archive::IS_LOADER) {
1359
		for (auto i : xrange(4)) {
1360
			prim |= uint8_t(primarySlotState[i] << (2 * i));
1361
		}
1362
	}
1363
	ar.serialize("primarySlots", prim,
1364
	             "subSlotRegs",  subSlotRegister);
1365
	if constexpr (Archive::IS_LOADER) {
1366
		setPrimarySlots(prim);
1367
		for (auto i : xrange(uint8_t(4))) {
1368
			setSubSlot(i, subSlotRegister[i]);
1369
		}
1370
	}
1371
1372
	if (delayDevice) {
1373
		ar.serialize("vdpDelay", *delayDevice);
1374
	}
1375
}
1376
INSTANTIATE_SERIALIZE_METHODS(MSXCPUInterface);
1377
1378
} // namespace openmsx