OXYGENHUB
◆ main openmsx / src / MSXDevice.cc
14.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MSXDevice.hh"
2
3
#include "CacheLine.hh"
4
#include "CartridgeSlotManager.hh"
5
#include "HardwareConfig.hh"
6
#include "MSXCPUInterface.hh"
7
#include "MSXException.hh"
8
#include "MSXMotherBoard.hh"
9
#include "TclObject.hh"
10
#include "XMLElement.hh"
11
12
#include "one_of.hh"
13
#include "serialize.hh"
14
#include "stl.hh"
15
#include "unreachable.hh"
16
17
#include <algorithm>
18
#include <bit>
19
#include <cassert>
20
21
namespace openmsx {
22
23
MSXDevice::MSXDevice(const DeviceConfig& config, std::string_view name)
24
	: deviceConfig(config)
25
{
26
	initName(name);
27
}
28
29
MSXDevice::MSXDevice(const DeviceConfig& config)
30
	: deviceConfig(config)
31
{
32
	initName(getDeviceConfig().getAttributeValue("id"));
33
}
34
35
void MSXDevice::initName(std::string_view name)
36
{
37
	deviceName = name;
38
	if (getMotherBoard().findDevice(deviceName)) {
39
		unsigned n = 0;
40
		do {
41
			deviceName = strCat(name, " (", ++n, ')');
42
		} while (getMotherBoard().findDevice(deviceName));
43
	}
44
}
45
46
void MSXDevice::init()
47
{
48
	staticInit();
49
50
	lockDevices();
51
	registerSlots();
52
	registerPorts();
53
}
54
55
MSXDevice::~MSXDevice()
56
{
57
	unregisterPorts();
58
	unregisterSlots();
59
	unlockDevices();
60
	assert(referencedBy.empty());
61
}
62
63
void MSXDevice::staticInit()
64
{
65
	static bool alreadyInit = false;
66
	if (alreadyInit) return;
67
	alreadyInit = true;
68
69
	std::ranges::fill(unmappedRead, 0xFF);
70
}
71
72
MSXMotherBoard& MSXDevice::getMotherBoard() const
73
{
74
	return getHardwareConfig().getMotherBoard();
75
}
76
77
void MSXDevice::testRemove(std::span<const std::unique_ptr<MSXDevice>> removed) const
78
{
79
	// Typically 'referencedBy' contains very few elements, so a simple
80
	// O(n*m) algorithm is fine.
81
	std::string err;
82
	for (const auto* dev : referencedBy) {
83
		if (!contains(removed, dev, [](const auto& d) { return d.get(); })) {
84
			strAppend(err, ' ', dev->getName());
85
		}
86
	}
87
	if (!err.empty()) {
88
		throw MSXException("Still in use by:", err);
89
	}
90
}
91
92
void MSXDevice::lockDevices()
93
{
94
	// This code can only handle backward references: the thing that is
95
	// referenced must already be instantiated, we don't try to change the
96
	// instantiation order. For the moment this is good enough (only ADVRAM
97
	// (an extension) uses it to refer to the VDP (inside a machine)). If
98
	// needed we can implement something more sophisticated later without
99
	// changing the format of the config files.
100
	for (const auto* c : getDeviceConfig().getChildren("device")) {
101
		auto name = c->getAttributeValue("idref");
102
		auto* dev = getMotherBoard().findDevice(name);
103
		if (!dev) {
104
			throw MSXException(
105
				"Unsatisfied dependency: '", getName(),
106
				"' depends on unavailable device '",
107
				name, "'.");
108
		}
109
		references.push_back(dev);
110
		dev->referencedBy.push_back(this);
111
	}
112
}
113
114
void MSXDevice::unlockDevices()
115
{
116
	for (auto& r : references) {
117
		move_pop_back(r->referencedBy, rfind_unguarded(r->referencedBy, this));
118
	}
119
}
120
121
const MSXDevice::Devices& MSXDevice::getReferences() const
122
{
123
	// init() must already be called
124
	return references;
125
}
126
127
EmuTime MSXDevice::getCurrentTime() const
128
{
129
	return getMotherBoard().getCurrentTime();
130
}
131
MSXCPU& MSXDevice::getCPU() const
132
{
133
	return getMotherBoard().getCPU();
134
}
135
MSXCPUInterface& MSXDevice::getCPUInterface() const
136
{
137
	return getMotherBoard().getCPUInterface();
138
}
139
Scheduler& MSXDevice::getScheduler() const
140
{
141
	return getMotherBoard().getScheduler();
142
}
143
MSXCliComm& MSXDevice::getCliComm() const
144
{
145
	return getMotherBoard().getMSXCliComm();
146
}
147
Reactor& MSXDevice::getReactor() const
148
{
149
	return getMotherBoard().getReactor();
150
}
151
CommandController& MSXDevice::getCommandController() const
152
{
153
	return getMotherBoard().getCommandController();
154
}
155
LedStatus& MSXDevice::getLedStatus() const
156
{
157
	return getMotherBoard().getLedStatus();
158
}
159
PluggingController& MSXDevice::getPluggingController() const
160
{
161
	return getMotherBoard().getPluggingController();
162
}
163
164
void MSXDevice::registerSlots()
165
{
166
	MemRegions tmpMemRegions;
167
	unsigned align = getBaseSizeAlignment();
168
	assert(std::has_single_bit(align));
169
	for (const auto* m : getDeviceConfig().getChildren("mem")) {
170
		unsigned base = m->getAttributeValueAsInt("base", 0);
171
		unsigned size = m->getAttributeValueAsInt("size", 0);
172
		if ((base >= 0x10000) || (size > 0x10000) || ((base + size) > 0x10000)) {
173
			throw MSXException(
174
				"Invalid memory specification for device ",
175
				getName(), " should be in range "
176
				"[0x0000,0x10000).");
177
		}
178
		if (((base & (align - 1)) || (size & (align - 1))) &&
179
		    !allowUnaligned()) {
180
			throw MSXException(
181
				"invalid memory specification for device ",
182
				getName(), " should be aligned on at least 0x",
183
				hex_string<4>(align), '.');
184
		}
185
		tmpMemRegions.push_back({.base = base, .size = size});
186
	}
187
	if (tmpMemRegions.empty()) {
188
		return;
189
	}
190
191
	// find primary and secondary slot specification
192
	auto& slotManager = getMotherBoard().getSlotManager();
193
	auto* primaryConfig   = getDeviceConfig2().getPrimary();
194
	auto* secondaryConfig = getDeviceConfig2().getSecondary();
195
	if (primaryConfig) {
196
		ps = slotManager.getSlotNum(primaryConfig->getAttributeValue("slot"));
197
	} else {
198
		throw MSXException("Invalid memory specification");
199
	}
200
	if (secondaryConfig) {
201
		auto ss_str = secondaryConfig->getAttributeValue("slot");
202
		ss = slotManager.getSlotNum(ss_str);
203
		if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
204
			throw MSXException(
205
				"Invalid secondary slot specification: \"",
206
				ss_str, "\".");
207
		}
208
	} else {
209
		ss = 0;
210
	}
211
212
	// This is only for backwards compatibility: in the past we added extra
213
	// attributes "primary_slot" and "secondary_slot" (in each MSXDevice
214
	// config) instead of changing the 'any' value of the slot attribute of
215
	// the (possibly shared) <primary> and <secondary> tags. When loading
216
	// an old savestate these tags can still occur, so keep this code. Also
217
	// remove these attributes to convert to the new format.
218
	auto& mutableConfig = getDeviceConfig();
219
	if (auto** primSlotPtr = mutableConfig.findAttributePointer("primary_slot")) {
220
		ps = slotManager.getSlotNum((*primSlotPtr)->getValue());
221
		mutableConfig.removeAttribute(primSlotPtr);
222
		if (auto** secSlotPtr = mutableConfig.findAttributePointer("secondary_slot")) {
223
			ss = slotManager.getSlotNum((*secSlotPtr)->getValue());
224
			mutableConfig.removeAttribute(secSlotPtr);
225
		}
226
	}
227
228
	// decode special values for 'ss'
229
	if ((-128 <= ss) && (ss < 0)) {
230
		if ((0 <= ps) && (ps < 4) &&
231
		    getCPUInterface().isExpanded(ps)) {
232
			ss += 128;
233
		} else {
234
			ss = 0;
235
		}
236
	}
237
238
	// decode special values for 'ps'
239
	if (ps == -256) {
240
		slotManager.getAnyFreeSlot(ps, ss);
241
	} else if (ps < 0) {
242
		// specified slot by name (carta, cartb, ...)
243
		slotManager.getSpecificSlot(-ps - 1, ps, ss);
244
	} else {
245
		// numerical specified slot (0, 1, 2, 3)
246
	}
247
	assert((0 <= ps) && (ps <= 3));
248
249
	if (!getCPUInterface().isExpanded(ps)) {
250
		ss = -1;
251
	}
252
253
	// Store actual slot in config. This has two purposes:
254
	//  - Make sure that devices that are grouped under the same
255
	//    <primary>/<secondary> tags actually use the same slot. (This
256
	//    matters when the value of some of the slot attributes is "any").
257
	//  - Fix the slot number so that it remains the same after a
258
	//    savestate/loadstate.
259
	assert(primaryConfig);
260
	XMLDocument& doc = deviceConfig.getXMLDocument();
261
	doc.setAttribute(*primaryConfig, "slot", doc.allocateString(tmpStrCat(ps)));
262
	if (secondaryConfig) {
263
		doc.setAttribute(*secondaryConfig, "slot", doc.allocateString(
264
			(ss == -1) ? std::string_view("X") : tmpStrCat(ss)));
265
	} else {
266
		if (ss != -1) {
267
			throw MSXException(
268
				"Missing <secondary> tag for device", getName());
269
		}
270
	}
271
272
	int logicalSS = (ss == -1) ? 0 : ss;
273
	for (const auto& r : tmpMemRegions) {
274
		getCPUInterface().registerMemDevice(
275
			*this, ps, logicalSS, r.base, r.size);
276
		memRegions.push_back(r);
277
	}
278
279
	// Mark the slot as 'in-use' so that future searches for free external
280
	// slots don't return this slot anymore. If the slot was not an
281
	// external slot, this call has no effect. Multiple MSXDevices from the
282
	// same extension (the same HardwareConfig) can all allocate the same
283
	// slot (later they should also all free this slot).
284
	slotManager.allocateSlot(ps, ss, getHardwareConfig());
285
}
286
287
void MSXDevice::unregisterSlots()
288
{
289
	if (memRegions.empty()) return;
290
291
	int logicalSS = (ss == -1) ? 0 : ss;
292
	for (const auto& [base, size] : memRegions) {
293
		getCPUInterface().unregisterMemDevice(
294
			*this, ps, logicalSS, base, size);
295
	}
296
297
	// See comments above about allocateSlot() for more details:
298
	//  - has no effect for non-external slots
299
	//  - can be called multiple times for the same slot
300
	getMotherBoard().getSlotManager().freeSlot(ps, ss, getHardwareConfig());
301
}
302
303
void MSXDevice::getVisibleMemRegion(unsigned& base, unsigned& size) const
304
{
305
	// init() must already be called
306
	if (memRegions.empty()) {
307
		base = 0;
308
		size = 0;
309
		return;
310
	}
311
312
	auto lowest  = min_value(memRegions, &BaseSize::base);
313
	auto highest = max_value(memRegions, &BaseSize::end);
314
	assert(lowest <= highest);
315
	base = lowest;
316
	size = highest - lowest;
317
}
318
319
void MSXDevice::registerPorts()
320
{
321
	// First calculate 'inPort' and 'outPorts' ..
322
	for (const auto* i : getDeviceConfig().getChildren("io")) {
323
		unsigned base = i->getAttributeValueAsInt("base", 0);
324
		unsigned num  = i->getAttributeValueAsInt("num", 0);
325
		auto type = i->getAttributeValue("type", "IO");
326
		if (((base + num) > 256) || (num == 0) ||
327
		    (type != one_of("I", "O", "IO"))) {
328
			throw MSXException("Invalid IO port specification");
329
		}
330
		if (type != "O") { // "I" or "IO"
331
			inPorts.setPosN(base, num);
332
		}
333
		if (type != "I") { // "O" or "IO"
334
			outPorts.setPosN(base, num);
335
		}
336
	}
337
	// .. and only then register the ports. This filters possible overlaps.
338
	doRegisterPorts();
339
}
340
341
void MSXDevice::doRegisterPorts()
342
{
343
	inPorts.foreachSetBit([&](auto port) {
344
		getCPUInterface().register_IO_In(narrow_cast<byte>(port), this);
345
	});
346
	outPorts.foreachSetBit([&](auto port) {
347
		getCPUInterface().register_IO_Out(narrow_cast<byte>(port), this);
348
	});
349
}
350
351
void MSXDevice::unregisterPorts()
352
{
353
	inPorts.foreachSetBit([&](auto port) {
354
		getCPUInterface().unregister_IO_In(narrow_cast<byte>(port), this);
355
	});
356
	outPorts.foreachSetBit([&](auto port) {
357
		getCPUInterface().unregister_IO_Out(narrow_cast<byte>(port), this);
358
	});
359
}
360
361
362
void MSXDevice::reset(EmuTime /*time*/)
363
{
364
	// nothing
365
}
366
367
byte MSXDevice::readIRQVector()
368
{
369
	return 0xFF;
370
}
371
372
void MSXDevice::powerDown(EmuTime /*time*/)
373
{
374
	// nothing
375
}
376
377
void MSXDevice::powerUp(EmuTime time)
378
{
379
	reset(time);
380
}
381
382
const std::string& MSXDevice::getName() const
383
{
384
	return deviceName;
385
}
386
387
void MSXDevice::getNameList(TclObject& result) const
388
{
389
	result.addListElement(getName());
390
}
391
392
void MSXDevice::getDeviceInfo(TclObject& result) const
393
{
394
	result.addDictKeyValue("type", getDeviceConfig().getName());
395
	getExtraDeviceInfo(result);
396
}
397
398
void MSXDevice::getExtraDeviceInfo(TclObject& /*result*/) const
399
{
400
	// nothing
401
}
402
403
unsigned MSXDevice::getBaseSizeAlignment() const
404
{
405
	return CacheLine::SIZE;
406
}
407
408
409
byte MSXDevice::readIO(uint16_t /*port*/, EmuTime /*time*/)
410
{
411
	// read from unmapped IO
412
	return 0xFF;
413
}
414
415
void MSXDevice::writeIO(uint16_t /*port*/, byte /*value*/, EmuTime /*time*/)
416
{
417
	// write to unmapped IO, do nothing
418
}
419
420
byte MSXDevice::peekIO(uint16_t /*port*/, EmuTime /*time*/) const
421
{
422
	return 0xFF;
423
}
424
425
426
byte MSXDevice::readMem(uint16_t /*address*/, EmuTime /*time*/)
427
{
428
	// read from unmapped memory
429
	return 0xFF;
430
}
431
432
const byte* MSXDevice::getReadCacheLine(uint16_t /*start*/) const
433
{
434
	return nullptr; // uncacheable
435
}
436
437
void MSXDevice::writeMem(uint16_t /*address*/, byte /*value*/,
438
                         EmuTime /*time*/)
439
{
440
	// write to unmapped memory, do nothing
441
}
442
443
byte MSXDevice::peekMem(uint16_t address, EmuTime /*time*/) const
444
{
445
	uint16_t base = address & CacheLine::HIGH;
446
	if (const byte* cache = getReadCacheLine(base)) {
447
		uint16_t offset = address & CacheLine::LOW;
448
		return cache[offset];
449
	} else {
450
		// peek not supported for this device
451
		return 0xFF;
452
	}
453
}
454
455
void MSXDevice::globalWrite(uint16_t /*address*/, byte /*value*/,
456
                            EmuTime /*time*/)
457
{
458
	UNREACHABLE;
459
}
460
461
void MSXDevice::globalRead(uint16_t /*address*/, EmuTime /*time*/)
462
{
463
	UNREACHABLE;
464
}
465
466
byte* MSXDevice::getWriteCacheLine(uint16_t /*start*/)
467
{
468
	return nullptr; // uncacheable
469
}
470
471
472
// calls 'action(<start2>, <size2>, args..., ps, ss)'
473
// with 'start', 'size' clipped to each of the ranges in 'memRegions'
474
template<typename Action, typename... Args>
475
void MSXDevice::clip(unsigned start, unsigned size, Action action, Args... args)
476
{
477
	assert(start < 0x10000);
478
	int ss2 = (ss != -1) ? ss : 0;
479
	unsigned end = start + size;
480
	for (auto [base, fullBsize] : memRegions) {
481
		// split on 16kB boundaries
482
		while (fullBsize > 0) {
483
			unsigned bsize = std::min(fullBsize, ((base + 0x4000) & ~0x3fff) - base);
484
485
			unsigned baseEnd = base + bsize;
486
			// intersect [start, end) with [base, baseEnd) -> [clipStart, clipEnd)
487
			unsigned clipStart = std::max(start, base);
488
			unsigned clipEnd   = std::min(end, baseEnd);
489
			if (clipStart < clipEnd) { // non-empty
490
				unsigned clipSize = clipEnd - clipStart;
491
				action(narrow<uint16_t>(clipStart), clipSize, args..., ps, ss2);
492
			}
493
494
			base += bsize;
495
			fullBsize -= bsize;
496
		}
497
	}
498
}
499
500
void MSXDevice::invalidateDeviceRWCache(unsigned start, unsigned size)
501
{
502
	clip(start, size, [&](auto... args) { getCPUInterface().invalidateRWCache(args...); });
503
}
504
void MSXDevice::invalidateDeviceRCache(unsigned start, unsigned size)
505
{
506
	clip(start, size, [&](auto... args) { getCPUInterface().invalidateRCache(args...); });
507
}
508
void MSXDevice::invalidateDeviceWCache(unsigned start, unsigned size)
509
{
510
	clip(start, size, [&](auto... args) { getCPUInterface().invalidateWCache(args...); });
511
}
512
513
void MSXDevice::fillDeviceRWCache(unsigned start, unsigned size, byte* rwData)
514
{
515
	fillDeviceRWCache(start, size, rwData, rwData);
516
}
517
void MSXDevice::fillDeviceRWCache(unsigned start, unsigned size, const byte* rData, byte* wData)
518
{
519
	assert(!allowUnaligned());
520
	clip(start, size, [&](auto... args) { getCPUInterface().fillRWCache(args...); }, rData, wData);
521
}
522
void MSXDevice::fillDeviceRCache(unsigned start, unsigned size, const byte* rData)
523
{
524
	assert(!allowUnaligned());
525
	clip(start, size, [&](auto... args) { getCPUInterface().fillRCache(args...); }, rData);
526
}
527
void MSXDevice::fillDeviceWCache(unsigned start, unsigned size, byte* wData)
528
{
529
	assert(!allowUnaligned());
530
	clip(start, size, [&](auto... args) { getCPUInterface().fillWCache(args...); }, wData);
531
}
532
533
template<typename Archive>
534
void MSXDevice::serialize(Archive& ar, unsigned /*version*/)
535
{
536
	// When this method is called, the method init() has already been
537
	// called (thus also registerSlots() and registerPorts()).
538
	ar.serialize("name", deviceName);
539
}
540
INSTANTIATE_SERIALIZE_METHODS(MSXDevice);
541
542
} // namespace openmsx