MSXDevice.cc 14 KB

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