OXYGENHUB
◆ main openmsx / src / MSXDevice.hh
12.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef MSXDEVICE_HH
2
#define MSXDEVICE_HH
3
4
#include "DeviceConfig.hh"
5
#include "EmuTime.hh"
6
7
#include "IterableBitSet.hh"
8
#include "narrow.hh"
9
#include "serialize_meta.hh"
10
11
#include <array>
12
#include <cassert>
13
#include <span>
14
#include <string>
15
#include <vector>
16
17
namespace openmsx {
18
19
using byte = uint8_t;  // TODO over time deprecate and remove these
20
21
class XMLElement;
22
class MSXMotherBoard;
23
class MSXCPU;
24
class MSXCPUInterface;
25
class Scheduler;
26
class MSXCliComm;
27
class Reactor;
28
class CommandController;
29
class LedStatus;
30
class PluggingController;
31
class HardwareConfig;
32
class TclObject;
33
34
/** An MSXDevice is an emulated hardware component connected to the bus
35
  * of the emulated MSX. There is no communication among devices, only
36
  * between devices and the CPU.
37
  */
38
class MSXDevice
39
{
40
public:
41
	MSXDevice(const MSXDevice&) = delete;
42
	MSXDevice(MSXDevice&&) = delete;
43
	MSXDevice& operator=(const MSXDevice&) = delete;
44
	MSXDevice& operator=(MSXDevice&&) = delete;
45
46
	using Devices = std::vector<MSXDevice*>;
47
48
	virtual ~MSXDevice() = 0;
49
50
	/** Returns the hardwareconfig this device belongs to.
51
	  */
52
	[[nodiscard]] const HardwareConfig& getHardwareConfig() const {
53
		return deviceConfig.getHardwareConfig();
54
	}
55
	[[nodiscard]] HardwareConfig& getHardwareConfig() {
56
		return deviceConfig.getHardwareConfig();
57
	}
58
59
	/** Checks whether this device can be removed (no other device has a
60
	  * reference to it). Throws an exception if it can't be removed.
61
	  */
62
	void testRemove(std::span<const std::unique_ptr<MSXDevice>> removed) const;
63
64
	/**
65
	 * This method is called on reset.
66
	 * Default implementation does nothing.
67
	 */
68
	virtual void reset(EmuTime time);
69
70
	/**
71
	 * Gets IRQ vector used in IM2. This method only exists to support
72
	 * YamahaSfg05. There is no way for several devices to coordinate which
73
	 * vector is actually send to the CPU. But this IM is anyway not really
74
	 * supported in the MSX standard.
75
	 * Default implementation returns 0xFF.
76
	 */
77
	[[nodiscard]] virtual byte readIRQVector();
78
79
	/**
80
	 * This method is called when MSX is powered down. The default
81
	 * implementation does nothing, this is usually ok. Typically devices
82
	 * that need to turn off LEDs need to reimplement this method.
83
	 * @param time The moment in time the power down occurs.
84
	 */
85
	virtual void powerDown(EmuTime time);
86
87
	/**
88
	 * This method is called when MSX is powered up. The default
89
	 * implementation calls reset(), this is usually ok.
90
	 * @param time The moment in time the power up occurs.
91
	 */
92
	virtual void powerUp(EmuTime time);
93
94
	/**
95
	 * Returns a human-readable name for this device.
96
	 * Default implementation is normally ok.
97
	 */
98
	[[nodiscard]] virtual const std::string& getName() const;
99
100
	/** Returns list of name(s) of this device.
101
	 * This is normally the same as getName() (but formatted as a Tcl list)
102
	 * except for multi-{mem,io}-devices.
103
	 */
104
	virtual void getNameList(TclObject& result) const;
105
106
	/** Get device info.
107
	  * Used by the 'machine_info device' command.
108
	  */
109
	void getDeviceInfo(TclObject& result) const;
110
111
	/** Returns the range where this device is visible in memory.
112
	  * This is the union of the "mem" tags inside the device tag in
113
	  * hardwareconfig.xml (though practically always there is only one
114
	  * "mem" tag). Information on possible holes in this range (when there
115
	  * are multiple "mem" tags) is not returned by this method.
116
	  */
117
	void getVisibleMemRegion(unsigned& base, unsigned& size) const;
118
119
	// IO
120
121
	/**
122
	 * Read a byte from an IO port at a certain time from this device.
123
	 * The default implementation returns 255.
124
	 */
125
	[[nodiscard]] virtual byte readIO(uint16_t port, EmuTime time);
126
127
	/**
128
	 * Write a byte to a given IO port at a certain time to this
129
	 * device.
130
	 * The default implementation ignores the write (does nothing)
131
	 */
132
	virtual void writeIO(uint16_t port, byte value, EmuTime time);
133
134
	/**
135
	 * Read a byte from a given IO port. Reading via this method has no
136
	 * side effects (doesn't change the device status). If safe reading
137
	 * is not possible this method returns 0xFF.
138
	 * This method is not used by the emulation. It can however be used
139
	 * by a debugger.
140
	 * The default implementation just returns 0xFF.
141
	 */
142
	[[nodiscard]] virtual byte peekIO(uint16_t port, EmuTime time) const;
143
144
145
	// Memory
146
147
	/**
148
	 * Read a byte from a location at a certain time from this
149
	 * device.
150
	 * The default implementation returns 255.
151
	 */
152
	[[nodiscard]] virtual byte readMem(uint16_t address, EmuTime time);
153
154
	/**
155
	 * Write a given byte to a given location at a certain time
156
	 * to this device.
157
	 * The default implementation ignores the write (does nothing).
158
	 */
159
	virtual void writeMem(uint16_t address, byte value, EmuTime time);
160
161
	/**
162
	 * Test that the memory in the interval [start, start +
163
	 * CacheLine::SIZE) is cacheable for reading. If it is, a pointer to a
164
	 * buffer containing this interval must be returned. If not, a null
165
	 * pointer must be returned.
166
	 * Cacheable for reading means the data may be read directly
167
	 * from the buffer, thus bypassing the readMem() method, and
168
	 * thus also ignoring EmuTime.
169
	 * The default implementation always returns a null pointer.
170
	 * The start of the interval is CacheLine::SIZE aligned.
171
	 */
172
	[[nodiscard]] virtual const byte* getReadCacheLine(uint16_t start) const;
173
174
	/**
175
	 * Test that the memory in the interval [start, start +
176
	 * CacheLine::SIZE) is cacheable for writing. If it is, a pointer to a
177
	 * buffer containing this interval must be returned. If not, a null
178
	 * pointer must be returned.
179
	 * Cacheable for writing means the data may be written directly
180
	 * to the buffer, thus bypassing the writeMem() method, and
181
	 * thus also ignoring EmuTime.
182
	 * The default implementation always returns a null pointer.
183
	 * The start of the interval is CacheLine::SIZE aligned.
184
	 */
185
	[[nodiscard]] virtual byte* getWriteCacheLine(uint16_t start);
186
187
	/**
188
	 * Read a byte from a given memory location. Reading memory
189
	 * via this method has no side effects (doesn't change the
190
	 * device status). If save reading is not possible this
191
	 * method returns 0xFF.
192
	 * This method is not used by the emulation. It can however
193
	 * be used by a debugger.
194
	 * The default implementation uses the cache mechanism
195
	 * (getReadCacheLine() method). If a certain region is not
196
	 * cacheable you cannot read it by default, Override this
197
	 * method if you want to improve this behaviour.
198
	 */
199
	[[nodiscard]] virtual byte peekMem(uint16_t address, EmuTime time) const;
200
201
	/** Global writes.
202
	  * Some devices violate the MSX standard by ignoring the SLOT-SELECT
203
	  * signal; they react to writes to a certain address in _any_ slot.
204
	  * Luckily the only known example so far is 'Super Lode Runner'.
205
	  * This method is triggered for such 'global' writes.
206
	  * You need to register each address for which you want this method
207
	  * to be triggered.
208
	  */
209
	virtual void globalWrite(uint16_t address, byte value, EmuTime time);
210
211
	/** Global reads. Similar to globalWrite() but then for reads.
212
	  * 'Carnivore2' is an example of a device that monitors the MSX bus
213
	  * for reads in any slot.
214
	  */
215
	virtual void globalRead(uint16_t address, EmuTime time);
216
217
	/** Calls MSXCPUInterface::invalidateXXCache() for the specific (part
218
	  * of) the slot that this device is located in.
219
	  */
220
	void invalidateDeviceRWCache() { invalidateDeviceRWCache(0x0000, 0x10000); }
221
	void invalidateDeviceRCache()  { invalidateDeviceRCache (0x0000, 0x10000); }
222
	void invalidateDeviceWCache()  { invalidateDeviceWCache (0x0000, 0x10000); }
223
	void invalidateDeviceRWCache(unsigned start, unsigned size);
224
	void invalidateDeviceRCache (unsigned start, unsigned size);
225
	void invalidateDeviceWCache (unsigned start, unsigned size);
226
227
	/** Calls MSXCPUInterface::fillXXCache() for the specific (part of) the
228
	  * slot that this device is located in.
229
	  */
230
	void fillDeviceRWCache(unsigned start, unsigned size, byte* rwData);
231
	void fillDeviceRWCache(unsigned start, unsigned size, const byte* rData, byte* wData);
232
	void fillDeviceRCache (unsigned start, unsigned size, const byte* rData);
233
	void fillDeviceWCache (unsigned start, unsigned size, byte* wData);
234
235
	/** Get the mother board this device belongs to
236
	  */
237
	[[nodiscard]] MSXMotherBoard& getMotherBoard() const;
238
239
	/** Get the configuration section for this device.
240
	  * This was passed as a constructor argument.
241
	  */
242
	[[nodiscard]] const XMLElement& getDeviceConfig() const {
243
		return *deviceConfig.getXML();
244
	}
245
	[[nodiscard]] XMLElement& getDeviceConfig() {
246
		return *deviceConfig.getXML();
247
	}
248
	[[nodiscard]] const DeviceConfig& getDeviceConfig2() const { // TODO
249
		return deviceConfig;
250
	}
251
	[[nodiscard]] DeviceConfig& getDeviceConfig2() { // TODO
252
		return deviceConfig;
253
	}
254
255
	/** Get the device references that are specified for this device
256
	 */
257
	[[nodiscard]] const Devices& getReferences() const;
258
259
	// convenience functions, these delegate to MSXMotherBoard
260
	[[nodiscard]] EmuTime getCurrentTime() const;
261
	[[nodiscard]] MSXCPU& getCPU() const;
262
	[[nodiscard]] MSXCPUInterface& getCPUInterface() const;
263
	[[nodiscard]] Scheduler& getScheduler() const;
264
	[[nodiscard]] MSXCliComm& getCliComm() const;
265
	[[nodiscard]] Reactor& getReactor() const;
266
	[[nodiscard]] CommandController& getCommandController() const;
267
	[[nodiscard]] PluggingController& getPluggingController() const;
268
	[[nodiscard]] LedStatus& getLedStatus() const;
269
270
	template<typename Archive>
271
	void serialize(Archive& ar, unsigned version);
272
273
protected:
274
	/** Every MSXDevice has a config entry; this constructor gets
275
	  * some device properties from that config entry.
276
	  * @param config config entry for this device.
277
	  * @param name The name for the MSXDevice (will be made unique)
278
	  */
279
	MSXDevice(const DeviceConfig& config, std::string_view name);
280
	explicit MSXDevice(const DeviceConfig& config);
281
282
	/** Constructing a MSXDevice is a 2-step process, after the constructor
283
	  * is called this init() method must be called. The reason is exception
284
	  * safety (init() might throw and we use the destructor to clean up
285
	  * some stuff, this is more difficult when everything is done in the
286
	  * constructor).
287
	  * This is also a non-public method. This means you can only construct
288
	  * MSXDevices via DeviceFactory.
289
	  * In rare cases you need to override this method, for example when you
290
	  * need to access the referenced devices already during construction
291
	  * of this device (e.g. ADVram)
292
	  */
293
	friend class DeviceFactory;
294
	virtual void init();
295
296
	/** The 'base' and 'size' attribute values need to be at least aligned
297
	  * to CacheLine::SIZE. Though some devices may need a stricter
298
	  * alignment. In that case they must override this method.
299
	  */
300
	[[nodiscard]] virtual unsigned getBaseSizeAlignment() const;
301
302
	/** By default we don't allow unaligned <mem> specifications in the
303
	  * config file. Though for a machine like 'Victor HC-95A' is it useful
304
	  * to model it with combinations of unaligned devices. So we do allow
305
	  * it for a select few devices: devices that promise to not call any
306
	  * of the 'fillDeviceXXXCache()' methods.
307
	  */
308
	[[nodiscard]] virtual bool allowUnaligned() const { return false; }
309
310
	/** @see getDeviceInfo()
311
	 * Default implementation does nothing. Subclasses can override this
312
	 * method to add extra info (like subtypes).
313
	 */
314
	virtual void getExtraDeviceInfo(TclObject& result) const;
315
316
public:
317
	// public to allow non-MSXDevices to use these same arrays
318
	static inline std::array<byte, 0x10000> unmappedRead;  // Read only
319
	static inline std::array<byte, 0x10000> unmappedWrite; // Write only
320
321
private:
322
	template<typename Action, typename... Args>
323
	void clip(unsigned start, unsigned size, Action action, Args... args);
324
325
	void initName(std::string_view name);
326
	static void staticInit();
327
328
	void lockDevices();
329
	void unlockDevices();
330
331
	void registerSlots();
332
	void unregisterSlots();
333
334
protected:
335
	void registerPorts();
336
	void doRegisterPorts();
337
	void unregisterPorts();
338
339
	mutable std::string deviceName; // MSXMultiXxxDevice has a dynamic (lazy) name
340
341
	[[nodiscard]] byte getPrimarySlot() const {
342
		// must already be resolved to an actual slot
343
		assert((0 <= ps) && (ps <= 3));
344
		return narrow_cast<byte>(ps);
345
	}
346
347
	IterableBitSet<256> inPorts;
348
	IterableBitSet<256> outPorts;
349
350
private:
351
	struct BaseSize {
352
		unsigned base;
353
		unsigned size;
354
		[[nodiscard]] unsigned end() const { return base + size; }
355
	};
356
	using MemRegions = std::vector<BaseSize>;
357
	MemRegions memRegions;
358
359
	DeviceConfig deviceConfig;
360
361
	Devices references;
362
	Devices referencedBy;
363
364
	int ps = 0;
365
	int ss = 0;
366
};
367
368
REGISTER_BASE_NAME_HELPER(MSXDevice, "Device");
369
370
#define REGISTER_MSXDEVICE(CLASS, NAME) \
371
REGISTER_POLYMORPHIC_INITIALIZER(MSXDevice, CLASS, NAME);
372
373
} // namespace openmsx
374
375
#endif