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