openMSX
MSXDevice.cc
Go to the documentation of this file.
1 #include "MSXDevice.hh"
2 #include "XMLElement.hh"
3 #include "MSXMotherBoard.hh"
4 #include "HardwareConfig.hh"
6 #include "MSXCPUInterface.hh"
7 #include "MSXCPU.hh"
8 #include "CacheLine.hh"
9 #include "TclObject.hh"
10 #include "MSXException.hh"
11 #include "ranges.hh"
12 #include "serialize.hh"
13 #include "stl.hh"
14 #include "unreachable.hh"
15 #include "view.hh"
16 #include <cassert>
17 #include <cstring>
18 #include <iterator> // for back_inserter
19 
20 using std::string;
21 
22 namespace openmsx {
23 
24 MSXDevice::MSXDevice(const DeviceConfig& config, const string& name)
25  : deviceConfig(config)
26 {
27  initName(name);
28 }
29 
31  : deviceConfig(config)
32 {
33  initName(getDeviceConfig().getAttribute("id"));
34 }
35 
36 void MSXDevice::initName(const string& name)
37 {
38  deviceName = name;
39  if (getMotherBoard().findDevice(deviceName)) {
40  unsigned n = 0;
41  do {
42  deviceName = strCat(name, " (", ++n, ')');
43  } while (getMotherBoard().findDevice(deviceName));
44  }
45 }
46 
48 {
49  staticInit();
50 
51  lockDevices();
52  registerSlots();
53  registerPorts();
54 }
55 
57 {
58  unregisterPorts();
59  unregisterSlots();
60  unlockDevices();
61  assert(referencedBy.empty());
62 }
63 
64 void MSXDevice::staticInit()
65 {
66  static bool alreadyInit = false;
67  if (alreadyInit) return;
68  alreadyInit = true;
69 
70  memset(unmappedRead, 0xFF, sizeof(unmappedRead));
71 }
72 
74 {
76 }
77 
78 void MSXDevice::testRemove(Devices removed) const
79 {
80  auto all = referencedBy;
81  ranges::sort(all);
82  ranges::sort(removed);
83  Devices rest;
84  ranges::set_difference(all, removed, back_inserter(rest));
85  if (!rest.empty()) {
86  string msg = "Still in use by";
87  for (auto& d : rest) {
88  strAppend(msg, ' ', d->getName());
89  }
90  throw MSXException(std::move(msg));
91  }
92 }
93 
94 void MSXDevice::lockDevices()
95 {
96  // This code can only handle backward references: the thing that is
97  // referenced must already be instantiated, we don't try to change the
98  // instantiation order. For the moment this is good enough (only ADVRAM
99  // (an extension) uses it to refer to the VDP (inside a machine)). If
100  // needed we can implement something more sophisticated later without
101  // changing the format of the config files.
102  for (auto& c : getDeviceConfig().getChildren("device")) {
103  const auto& name = c->getAttribute("idref");
104  auto* dev = getMotherBoard().findDevice(name);
105  if (!dev) {
106  throw MSXException(
107  "Unsatisfied dependency: '", getName(),
108  "' depends on unavailable device '",
109  name, "'.");
110  }
111  references.push_back(dev);
112  dev->referencedBy.push_back(this);
113  }
114 }
115 
116 void MSXDevice::unlockDevices()
117 {
118  for (auto& r : references) {
119  move_pop_back(r->referencedBy, rfind_unguarded(r->referencedBy, this));
120  }
121 }
122 
124 {
125  // init() must already be called
126  return references;
127 }
128 
129 EmuTime::param MSXDevice::getCurrentTime() const
130 {
131  return getMotherBoard().getCurrentTime();
132 }
134 {
135  return getMotherBoard().getCPU();
136 }
138 {
139  return getMotherBoard().getCPUInterface();
140 }
142 {
143  return getMotherBoard().getScheduler();
144 }
146 {
147  return getMotherBoard().getMSXCliComm();
148 }
150 {
151  return getMotherBoard().getReactor();
152 }
154 {
156 }
158 {
159  return getMotherBoard().getLedStatus();
160 }
162 {
164 }
165 
166 void MSXDevice::registerSlots()
167 {
168  MemRegions tmpMemRegions;
169  for (auto& m : getDeviceConfig().getChildren("mem")) {
170  unsigned base = m->getAttributeAsInt("base");
171  unsigned size = m->getAttributeAsInt("size");
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  tmpMemRegions.emplace_back(base, size);
179  }
180  if (tmpMemRegions.empty()) {
181  return;
182  }
183 
184  // find primary and secondary slot specification
185  auto& slotManager = getMotherBoard().getSlotManager();
186  auto* primaryConfig = getDeviceConfig2().getPrimary();
187  auto* secondaryConfig = getDeviceConfig2().getSecondary();
188  if (primaryConfig) {
189  ps = slotManager.getSlotNum(primaryConfig->getAttribute("slot"));
190  } else {
191  throw MSXException("Invalid memory specification");
192  }
193  if (secondaryConfig) {
194  const auto& ss_str = secondaryConfig->getAttribute("slot");
195  ss = slotManager.getSlotNum(ss_str);
196  if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
197  throw MSXException(
198  "Invalid secondary slot specification: \"",
199  ss_str, "\".");
200  }
201  } else {
202  ss = 0;
203  }
204 
205  // This is only for backwards compatibility: in the past we added extra
206  // attributes "primary_slot" and "secondary_slot" (in each MSXDevice
207  // config) instead of changing the 'any' value of the slot attribute of
208  // the (possibly shared) <primary> and <secondary> tags. When loading
209  // an old savestate these tags can still occur, so keep this code. Also
210  // remove these attributes to convert to the new format.
211  const auto& config = getDeviceConfig();
212  if (config.hasAttribute("primary_slot")) {
213  auto& mutableConfig = const_cast<XMLElement&>(config);
214  const auto& primSlot = config.getAttribute("primary_slot");
215  ps = slotManager.getSlotNum(primSlot);
216  mutableConfig.removeAttribute("primary_slot");
217  if (config.hasAttribute("secondary_slot")) {
218  const auto& secondSlot = config.getAttribute("secondary_slot");
219  ss = slotManager.getSlotNum(secondSlot);
220  mutableConfig.removeAttribute("secondary_slot");
221  }
222  }
223 
224  // decode special values for 'ss'
225  if ((-128 <= ss) && (ss < 0)) {
226  if ((0 <= ps) && (ps < 4) &&
227  getCPUInterface().isExpanded(ps)) {
228  ss += 128;
229  } else {
230  ss = 0;
231  }
232  }
233 
234  // decode special values for 'ps'
235  if (ps == -256) {
236  slotManager.getAnyFreeSlot(ps, ss);
237  } else if (ps < 0) {
238  // specified slot by name (carta, cartb, ...)
239  slotManager.getSpecificSlot(-ps - 1, ps, ss);
240  } else {
241  // numerical specified slot (0, 1, 2, 3)
242  }
243  assert((0 <= ps) && (ps <= 3));
244 
245  if (!getCPUInterface().isExpanded(ps)) {
246  ss = -1;
247  }
248 
249  // Store actual slot in config. This has two purposes:
250  // - Make sure that devices that are grouped under the same
251  // <primary>/<secondary> tags actually use the same slot. (This
252  // matters when the value of some of the slot attributes is "any").
253  // - Fix the slot number so that it remains the same after a
254  // savestate/loadstate.
255  assert(primaryConfig);
256  primaryConfig->setAttribute("slot", strCat(ps));
257  if (secondaryConfig) {
258  string slot = (ss == -1) ? "X" : strCat(ss);
259  secondaryConfig->setAttribute("slot", slot);
260  } else {
261  if (ss != -1) {
262  throw MSXException(
263  "Missing <secondary> tag for device", getName());
264  }
265  }
266 
267  int logicalSS = (ss == -1) ? 0 : ss;
268  for (auto& r : tmpMemRegions) {
270  *this, ps, logicalSS, r.first, r.second);
271  memRegions.push_back(r);
272  }
273 
274  // Mark the slot as 'in-use' so that future searches for free external
275  // slots don't return this slot anymore. If the slot was not an
276  // external slot, this call has no effect. Multiple MSXDevices from the
277  // same extension (the same HardwareConfig) can all allocate the same
278  // slot (later they should also all free this slot).
279  slotManager.allocateSlot(ps, ss, getHardwareConfig());
280 }
281 
282 void MSXDevice::unregisterSlots()
283 {
284  if (memRegions.empty()) return;
285 
286  int logicalSS = (ss == -1) ? 0 : ss;
287  for (const auto& [base, size] : memRegions) {
289  *this, ps, logicalSS, base, size);
290  }
291 
292  // See comments above about allocateSlot() for more details:
293  // - has no effect for non-external slots
294  // - can be called multiple times for the same slot
296 }
297 
298 void MSXDevice::getVisibleMemRegion(unsigned& base, unsigned& size) const
299 {
300  // init() must already be called
301  if (memRegions.empty()) {
302  base = 0;
303  size = 0;
304  return;
305  }
306 
307  auto lowest = min_value(view::transform(memRegions,
308  [](auto& r) { return r.first; }));
309  auto highest = max_value(view::transform(memRegions,
310  [](auto& r) { return r.first + r.second; }));
311  assert(lowest <= highest);
312  base = lowest;
313  size = highest - lowest;
314 }
315 
316 void MSXDevice::registerPorts()
317 {
318  for (auto& i : getDeviceConfig().getChildren("io")) {
319  unsigned base = i->getAttributeAsInt("base");
320  unsigned num = i->getAttributeAsInt("num");
321  const auto& type = i->getAttribute("type", "IO");
322  if (((base + num) > 256) || (num == 0) ||
323  ((type != "I") && (type != "O") && (type != "IO"))) {
324  throw MSXException("Invalid IO port specification");
325  }
326  for (unsigned port = base; port < base + num; ++port) {
327  if ((type == "I") || (type == "IO")) {
328  getCPUInterface().register_IO_In(port, this);
329  inPorts.push_back(port);
330  }
331  if ((type == "O") || (type == "IO")) {
332  getCPUInterface().register_IO_Out(port, this);
333  outPorts.push_back(port);
334  }
335  }
336  }
337 }
338 
339 void MSXDevice::unregisterPorts()
340 {
341  for (auto& p : inPorts) {
343  }
344  for (auto& p : outPorts) {
346  }
347 }
348 
349 
350 void MSXDevice::reset(EmuTime::param /*time*/)
351 {
352  // nothing
353 }
354 
356 {
357  return 0xFF;
358 }
359 
360 void MSXDevice::powerDown(EmuTime::param /*time*/)
361 {
362  // nothing
363 }
364 
365 void MSXDevice::powerUp(EmuTime::param time)
366 {
367  reset(time);
368 }
369 
370 string MSXDevice::getName() const
371 {
372  return deviceName;
373 }
374 
376 {
377  result.addListElement(getName());
378 }
379 
381 {
382  result.addDictKeyValue("type", getDeviceConfig().getName());
383  getExtraDeviceInfo(result);
384 }
385 
387 {
388  // nothing
389 }
390 
391 
392 byte MSXDevice::readIO(word /*port*/, EmuTime::param /*time*/)
393 {
394  // read from unmapped IO
395  return 0xFF;
396 }
397 
398 void MSXDevice::writeIO(word /*port*/, byte /*value*/, EmuTime::param /*time*/)
399 {
400  // write to unmapped IO, do nothing
401 }
402 
403 byte MSXDevice::peekIO(word /*port*/, EmuTime::param /*time*/) const
404 {
405  return 0xFF;
406 }
407 
408 
409 byte MSXDevice::readMem(word /*address*/, EmuTime::param /*time*/)
410 {
411  // read from unmapped memory
412  return 0xFF;
413 }
414 
415 const byte* MSXDevice::getReadCacheLine(word /*start*/) const
416 {
417  return nullptr; // uncacheable
418 }
419 
420 void MSXDevice::writeMem(word /*address*/, byte /*value*/,
421  EmuTime::param /*time*/)
422 {
423  // write to unmapped memory, do nothing
424 }
425 
426 byte MSXDevice::peekMem(word address, EmuTime::param /*time*/) const
427 {
428  word base = address & CacheLine::HIGH;
429  if (const byte* cache = getReadCacheLine(base)) {
430  word offset = address & CacheLine::LOW;
431  return cache[offset];
432  } else {
433  // peek not supported for this device
434  return 0xFF;
435  }
436 }
437 
438 void MSXDevice::globalWrite(word /*address*/, byte /*value*/,
439  EmuTime::param /*time*/)
440 {
441  UNREACHABLE;
442 }
443 
444 void MSXDevice::globalRead(word /*address*/, EmuTime::param /*time*/)
445 {
446  UNREACHABLE;
447 }
448 
450 {
451  return nullptr; // uncacheable
452 }
453 
455 {
456  getCPU().invalidateMemCache(start, size);
457 }
458 
459 template<typename Archive>
460 void MSXDevice::serialize(Archive& ar, unsigned /*version*/)
461 {
462  // When this method is called, the method init() has already been
463  // called (thus also registerSlots() and registerPorts()).
464  ar.serialize("name", deviceName);
465 }
467 
468 } // namespace openmsx
virtual void init()
Definition: MSXDevice.cc:47
const DeviceConfig & getDeviceConfig2() const
Definition: MSXDevice.hh:221
void freeSlot(int ps, int ss, const HardwareConfig &hwConfig)
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:306
Contains the main loop of openMSX.
Definition: Reactor.hh:66
virtual byte * getWriteCacheLine(word start) const
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing...
Definition: MSXDevice.cc:449
void registerMemDevice(MSXDevice &device, int ps, int ss, int base, int size)
Devices can register themself in the MSX slotstructure.
PluggingController & getPluggingController()
CliComm & getCliComm() const
Definition: MSXDevice.cc:145
MSXCPUInterface & getCPUInterface()
const Devices & getReferences() const
Get the device references that are specified for this device.
Definition: MSXDevice.cc:123
auto min_value(InputIterator first, InputIterator last)
Definition: stl.hh:248
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
constexpr unsigned LOW
Definition: CacheLine.hh:9
virtual byte readMem(word address, EmuTime::param time)
Read a byte from a location at a certain time from this device.
Definition: MSXDevice.cc:409
virtual void writeMem(word address, byte value, EmuTime::param time)
Write a given byte to a given location at a certain time to this device.
Definition: MSXDevice.cc:420
virtual void globalWrite(word address, byte value, EmuTime::param time)
Global writes.
Definition: MSXDevice.cc:438
MSXMotherBoard & getMotherBoard() const
virtual byte readIRQVector()
Gets IRQ vector used in IM2.
Definition: MSXDevice.cc:355
constexpr unsigned HIGH
Definition: CacheLine.hh:10
virtual std::string getName() const
Returns a human-readable name for this device.
Definition: MSXDevice.cc:370
virtual byte readIO(word port, EmuTime::param time)
Read a byte from an IO port at a certain time from this device.
Definition: MSXDevice.cc:392
Central administration of Connectors and Pluggables.
void unregister_IO_In(byte port, MSXDevice *device)
size_t size(std::string_view utf8)
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:177
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
void invalidateMemCache(word start, unsigned size)
Invalidate the CPU its cache for the interval [start, start + size) For example MSXMemoryMapper and M...
Definition: MSXCPU.cc:147
void serialize(Archive &ar, unsigned version)
Definition: MSXDevice.cc:460
virtual void globalRead(word address, EmuTime::param time)
Global reads.
Definition: MSXDevice.cc:444
void getDeviceInfo(TclObject &result) const
Get device info.
Definition: MSXDevice.cc:380
MSXDevice * findDevice(std::string_view name)
Find a MSXDevice by name.
virtual void powerUp(EmuTime::param time)
This method is called when MSX is powered up.
Definition: MSXDevice.cc:365
virtual byte peekMem(word address, EmuTime::param time) const
Read a byte from a given memory location.
Definition: MSXDevice.cc:426
void register_IO_In(byte port, MSXDevice *device)
Devices can register their In ports.
static byte unmappedRead[0x10000]
Definition: MSXDevice.hh:274
auto max_value(InputIterator first, InputIterator last)
Definition: stl.hh:267
void register_IO_Out(byte port, MSXDevice *device)
Devices can register their Out ports.
virtual void getExtraDeviceInfo(TclObject &result) const
Definition: MSXDevice.cc:386
void unregisterMemDevice(MSXDevice &device, int ps, int ss, int base, int size)
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:129
LedStatus & getLedStatus() const
Definition: MSXDevice.cc:157
virtual const byte * getReadCacheLine(word start) const
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading...
Definition: MSXDevice.cc:415
CartridgeSlotManager & getSlotManager()
const std::string & getAttribute(std::string_view attName) const
virtual void powerDown(EmuTime::param time)
This method is called when MSX is powered down.
Definition: MSXDevice.cc:360
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX...
Definition: MSXDevice.hh:31
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:73
virtual void writeIO(word port, byte value, EmuTime::param time)
Write a byte to a given IO port at a certain time to this device.
Definition: MSXDevice.cc:398
CommandController & getCommandController()
void unregister_IO_Out(byte port, MSXDevice *device)
auto set_difference(InputRange1 &&range1, InputRange2 &&range2, OutputIter out)
Definition: ranges.hh:221
MSXCPU & getCPU() const
Definition: MSXDevice.cc:133
XMLElement * getSecondary() const
Definition: DeviceConfig.hh:55
Scheduler & getScheduler() const
Definition: MSXDevice.cc:141
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
CommandController & getCommandController() const
Definition: MSXDevice.cc:153
std::vector< MSXDevice * > Devices
Definition: MSXDevice.hh:37
void addListElement(T t)
Definition: TclObject.hh:121
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
const HardwareConfig & getHardwareConfig() const
Returns the hardwareconfig this device belongs to.
Definition: MSXDevice.hh:43
PluggingController & getPluggingController() const
Definition: MSXDevice.cc:161
XMLElement * getPrimary() const
Definition: DeviceConfig.hh:51
MSXCPUInterface & getCPUInterface() const
Definition: MSXDevice.cc:137
virtual byte peekIO(word port, EmuTime::param time) const
Read a byte from a given IO port.
Definition: MSXDevice.cc:403
virtual void reset(EmuTime::param time)
This method is called on reset.
Definition: MSXDevice.cc:350
virtual void getNameList(TclObject &result) const
Returns list of name(s) of this device.
Definition: MSXDevice.cc:375
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:135
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
Definition: MSXDevice.hh:218
void getVisibleMemRegion(unsigned &base, unsigned &size) const
Returns the range where this device is visible in memory.
Definition: MSXDevice.cc:298
auto rfind_unguarded(RANGE &range, const VAL &val)
Similar to the find(_if)_unguarded functions above, but searches from the back to front...
Definition: stl.hh:152
void testRemove(Devices alreadyRemoved) const
Checks whether this device can be removed (no other device has a reference to it).
Definition: MSXDevice.cc:78
MSXDevice(const MSXDevice &)=delete
void invalidateMemCache(word start, unsigned size)
Invalidate CPU memory-mapping cache.
Definition: MSXDevice.cc:454
virtual ~MSXDevice()=0
Definition: MSXDevice.cc:56
Reactor & getReactor() const
Definition: MSXDevice.cc:149
#define UNREACHABLE
Definition: unreachable.hh:38