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