openMSX
MSXMotherBoard.cc
Go to the documentation of this file.
1 #include "MSXMotherBoard.hh"
2 #include "Reactor.hh"
3 #include "MSXDevice.hh"
4 #include "ReverseManager.hh"
5 #include "HardwareConfig.hh"
6 #include "ConfigException.hh"
7 #include "XMLElement.hh"
8 #include "MSXCliComm.hh"
9 #include "GlobalCliComm.hh"
10 #include "MSXCommandController.hh"
11 #include "Scheduler.hh"
12 #include "Schedulable.hh"
13 #include "CartridgeSlotManager.hh"
14 #include "EventDistributor.hh"
15 #include "Debugger.hh"
16 #include "SimpleDebuggable.hh"
17 #include "MSXMixer.hh"
18 #include "PluggingController.hh"
19 #include "MSXCPUInterface.hh"
20 #include "MSXCPU.hh"
21 #include "PanasonicMemory.hh"
22 #include "MSXDeviceSwitch.hh"
23 #include "MSXMapperIO.hh"
24 #include "CassettePort.hh"
25 #include "JoystickPort.hh"
26 #include "RenShaTurbo.hh"
27 #include "LedStatus.hh"
28 #include "MSXEventDistributor.hh"
30 #include "EventDelay.hh"
31 #include "RealTime.hh"
32 #include "DeviceFactory.hh"
33 #include "BooleanSetting.hh"
34 #include "GlobalSettings.hh"
35 #include "Command.hh"
36 #include "CommandException.hh"
37 #include "InfoTopic.hh"
38 #include "FileException.hh"
39 #include "TclObject.hh"
40 #include "Observer.hh"
41 #include "serialize.hh"
42 #include "serialize_stl.hh"
43 #include "ScopedAssign.hh"
44 #include "one_of.hh"
45 #include "ranges.hh"
46 #include "stl.hh"
47 #include "unreachable.hh"
48 #include "view.hh"
49 #include <cassert>
50 #include <functional>
51 #include <iostream>
52 #include <memory>
53 
54 using std::make_unique;
55 using std::string;
56 
57 namespace openmsx {
58 
60 {
61 public:
62  explicit AddRemoveUpdate(MSXMotherBoard& motherBoard);
64  AddRemoveUpdate(const AddRemoveUpdate&) = delete;
66 private:
67  MSXMotherBoard& motherBoard;
68 };
69 
70 class ResetCmd final : public RecordedCommand
71 {
72 public:
73  explicit ResetCmd(MSXMotherBoard& motherBoard);
74  void execute(std::span<const TclObject> tokens, TclObject& result,
75  EmuTime::param time) override;
76  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
77 private:
78  MSXMotherBoard& motherBoard;
79 };
80 
81 class LoadMachineCmd final : public Command
82 {
83 public:
84  explicit LoadMachineCmd(MSXMotherBoard& motherBoard);
85  void execute(std::span<const TclObject> tokens, TclObject& result) override;
86  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
87  void tabCompletion(std::vector<string>& tokens) const override;
88 private:
89  MSXMotherBoard& motherBoard;
90 };
91 
92 class ListExtCmd final : public Command
93 {
94 public:
95  explicit ListExtCmd(MSXMotherBoard& motherBoard);
96  void execute(std::span<const TclObject> tokens, TclObject& result) override;
97  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
98 private:
99  MSXMotherBoard& motherBoard;
100 };
101 
102 class RemoveExtCmd final : public RecordedCommand
103 {
104 public:
105  explicit RemoveExtCmd(MSXMotherBoard& motherBoard);
106  void execute(std::span<const TclObject> tokens, TclObject& result,
107  EmuTime::param time) override;
108  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
109  void tabCompletion(std::vector<string>& tokens) const override;
110 private:
111  MSXMotherBoard& motherBoard;
112 };
113 
114 class MachineNameInfo final : public InfoTopic
115 {
116 public:
117  explicit MachineNameInfo(MSXMotherBoard& motherBoard);
118  void execute(std::span<const TclObject> tokens,
119  TclObject& result) const override;
120  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
121 private:
122  MSXMotherBoard& motherBoard;
123 };
124 
125 class MachineTypeInfo final : public InfoTopic
126 {
127 public:
128  explicit MachineTypeInfo(MSXMotherBoard& motherBoard);
129  void execute(std::span<const TclObject> tokens,
130  TclObject& result) const override;
131  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
132 private:
133  MSXMotherBoard& motherBoard;
134 };
135 
136 class MachineExtensionInfo final : public InfoTopic
137 {
138 public:
139  explicit MachineExtensionInfo(MSXMotherBoard& motherBoard);
140  void execute(std::span<const TclObject> tokens,
141  TclObject& result) const override;
142  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
143  void tabCompletion(std::vector<string>& tokens) const override;
144 private:
145  MSXMotherBoard& motherBoard;
146 };
147 
148 class DeviceInfo final : public InfoTopic
149 {
150 public:
151  explicit DeviceInfo(MSXMotherBoard& motherBoard);
152  void execute(std::span<const TclObject> tokens,
153  TclObject& result) const override;
154  [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
155  void tabCompletion(std::vector<string>& tokens) const override;
156 private:
157  MSXMotherBoard& motherBoard;
158 };
159 
160 class FastForwardHelper final : private Schedulable
161 {
162 public:
163  explicit FastForwardHelper(MSXMotherBoard& motherBoard);
164  void setTarget(EmuTime::param targetTime);
165 private:
166  void executeUntil(EmuTime::param time) override;
167  MSXMotherBoard& motherBoard;
168 };
169 
171 {
172 public:
173  explicit JoyPortDebuggable(MSXMotherBoard& motherBoard);
174  [[nodiscard]] byte read(unsigned address, EmuTime::param time) override;
175  void write(unsigned address, byte value) override;
176 };
177 
178 class SettingObserver final : public Observer<Setting>
179 {
180 public:
181  explicit SettingObserver(MSXMotherBoard& motherBoard);
182  void update(const Setting& setting) noexcept override;
183 private:
184  MSXMotherBoard& motherBoard;
185 };
186 
187 
188 static unsigned machineIDCounter = 0;
189 
191  : reactor(reactor_)
192  , machineID(strCat("machine", ++machineIDCounter))
193  , mapperIOCounter(0)
194  , machineConfig(nullptr)
195  , msxCliComm(make_unique<MSXCliComm>(*this, reactor.getGlobalCliComm()))
196  , msxEventDistributor(make_unique<MSXEventDistributor>())
197  , stateChangeDistributor(make_unique<StateChangeDistributor>())
198  , msxCommandController(make_unique<MSXCommandController>(
199  reactor.getGlobalCommandController(), reactor,
200  *this, *msxEventDistributor, machineID))
201  , scheduler(make_unique<Scheduler>())
202  , msxMixer(make_unique<MSXMixer>(
203  reactor.getMixer(), *this,
204  reactor.getGlobalSettings()))
205  , videoSourceSetting(*msxCommandController)
206  , fastForwardHelper(make_unique<FastForwardHelper>(*this))
207  , settingObserver(make_unique<SettingObserver>(*this))
208  , powerSetting(reactor.getGlobalSettings().getPowerSetting())
209  , powered(false)
210  , active(false)
211  , fastForwarding(false)
212 {
213  slotManager = make_unique<CartridgeSlotManager>(*this);
214  reverseManager = make_unique<ReverseManager>(*this);
215  resetCommand = make_unique<ResetCmd>(*this);
216  loadMachineCommand = make_unique<LoadMachineCmd>(*this);
217  listExtCommand = make_unique<ListExtCmd>(*this);
218  extCommand = make_unique<ExtCmd>(*this, "ext");
219  removeExtCommand = make_unique<RemoveExtCmd>(*this);
220  machineNameInfo = make_unique<MachineNameInfo>(*this);
221  machineTypeInfo = make_unique<MachineTypeInfo>(*this);
222  machineExtensionInfo = make_unique<MachineExtensionInfo>(*this);
223  deviceInfo = make_unique<DeviceInfo>(*this);
224  debugger = make_unique<Debugger>(*this);
225 
226  msxMixer->mute(); // powered down
227 
228  // Do this before machine-specific settings are created, otherwise
229  // a setting-info clicomm message is send with a machine id that hasn't
230  // been announced yet over clicomm.
231  addRemoveUpdate = make_unique<AddRemoveUpdate>(*this);
232 
233  // TODO: Initialization of this field cannot be done much earlier because
234  // EventDelay creates a setting, calling getMSXCliComm()
235  // on MSXMotherBoard, so "pimpl" has to be set up already.
236  eventDelay = make_unique<EventDelay>(
237  *scheduler, *msxCommandController,
238  reactor.getEventDistributor(), *msxEventDistributor,
239  *reverseManager);
240  realTime = make_unique<RealTime>(
241  *this, reactor.getGlobalSettings(), *eventDelay);
242 
243  powerSetting.attach(*settingObserver);
244 }
245 
247 {
248  powerSetting.detach(*settingObserver);
249  deleteMachine();
250 
251  assert(mapperIOCounter == 0);
252  assert(availableDevices.empty());
253  assert(extensions.empty());
254  assert(!machineConfig2);
255  assert(!getMachineConfig());
256 }
257 
258 void MSXMotherBoard::deleteMachine()
259 {
260  while (!extensions.empty()) {
261  try {
262  removeExtension(*extensions.back());
263  } catch (MSXException& e) {
264  std::cerr << "Internal error: failed to remove "
265  "extension while deleting a machine: "
266  << e.getMessage() << '\n';
267  UNREACHABLE;
268  }
269  }
270 
271  machineConfig2.reset();
272  machineConfig = nullptr;
273 }
274 
276 {
277  assert(!getMachineConfig());
278  machineConfig = machineConfig_;
279 
280  // make sure the CPU gets instantiated from the main thread
281  assert(!msxCpu);
282  msxCpu = make_unique<MSXCPU>(*this);
283  msxCpuInterface = make_unique<MSXCPUInterface>(*this);
284 }
285 
286 std::string_view MSXMotherBoard::getMachineType() const
287 {
288  if (const HardwareConfig* machine = getMachineConfig()) {
289  if (const auto* info = machine->getConfig().findChild("info")) {
290  if (const auto* type = info->findChild("type")) {
291  return type->getData();
292  }
293  }
294  }
295  return "";
296 }
297 
299 {
300  const HardwareConfig* config = getMachineConfig();
301  assert(config);
302  return config->getConfig().getChild("devices").findChild("S1990") != nullptr;
303 }
304 
306 {
307  const HardwareConfig* config = getMachineConfig();
308  assert(config);
309  const XMLElement& devices = config->getConfig().getChild("devices");
310  return devices.findChild("T7775") != nullptr ||
311  devices.findChild("T7937") != nullptr ||
312  devices.findChild("T9763") != nullptr ||
313  devices.findChild("T9769") != nullptr;
314 }
315 
316 string MSXMotherBoard::loadMachine(const string& machine)
317 {
318  assert(machineName.empty());
319  assert(extensions.empty());
320  assert(!machineConfig2);
321  assert(!getMachineConfig());
322 
323  try {
324  machineConfig2 = HardwareConfig::createMachineConfig(*this, machine);
325  setMachineConfig(machineConfig2.get());
326  } catch (FileException& e) {
327  throw MSXException("Machine \"", machine, "\" not found: ",
328  e.getMessage());
329  } catch (MSXException& e) {
330  throw MSXException("Error in \"", machine, "\" machine: ",
331  e.getMessage());
332  }
333  try {
334  machineConfig->parseSlots();
335  machineConfig->createDevices();
336  } catch (MSXException& e) {
337  throw MSXException("Error in \"", machine, "\" machine: ",
338  e.getMessage());
339  }
340  if (powerSetting.getBoolean()) {
341  powerUp();
342  }
343  machineName = machine;
344  return machineName;
345 }
346 
347 string MSXMotherBoard::loadExtension(std::string_view name, std::string_view slotname)
348 {
349  std::unique_ptr<HardwareConfig> extension;
350  try {
352  *this, string(name), slotname);
353  } catch (FileException& e) {
354  throw MSXException(
355  "Extension \"", name, "\" not found: ", e.getMessage());
356  } catch (MSXException& e) {
357  throw MSXException(
358  "Error in \"", name, "\" extension: ", e.getMessage());
359  }
360  return insertExtension(name, std::move(extension));
361 }
362 
364  std::string_view name, std::unique_ptr<HardwareConfig> extension)
365 {
366  try {
367  extension->parseSlots();
368  extension->createDevices();
369  } catch (MSXException& e) {
370  throw MSXException(
371  "Error in \"", name, "\" extension: ", e.getMessage());
372  }
373  string result = extension->getName();
374  extensions.push_back(std::move(extension));
375  getMSXCliComm().update(CliComm::EXTENSION, result, "add");
376  return result;
377 }
378 
379 HardwareConfig* MSXMotherBoard::findExtension(std::string_view extensionName)
380 {
381  auto it = ranges::find(extensions, extensionName, &HardwareConfig::getName);
382  return (it != end(extensions)) ? it->get() : nullptr;
383 }
384 
386 {
387  extension.testRemove();
388  getMSXCliComm().update(CliComm::EXTENSION, extension.getName(), "remove");
389  auto it = rfind_unguarded(extensions, &extension,
390  [](auto& e) { return e.get(); });
391  extensions.erase(it);
392 }
393 
395 {
396  return *msxCliComm;
397 }
398 
400 {
401  assert(getMachineConfig()); // needed for PluggableFactory::createAll()
402  if (!pluggingController) {
403  pluggingController = make_unique<PluggingController>(*this);
404  }
405  return *pluggingController;
406 }
407 
409 {
410  assert(getMachineConfig()); // because CPU needs to know if we're
411  // emulating turbor or not
412  return *msxCpu;
413 }
414 
416 {
417  assert(getMachineConfig());
418  return *msxCpuInterface;
419 }
420 
422 {
423  if (!panasonicMemory) {
424  panasonicMemory = make_unique<PanasonicMemory>(*this);
425  }
426  return *panasonicMemory;
427 }
428 
430 {
431  if (!deviceSwitch) {
433  }
434  return *deviceSwitch;
435 }
436 
438 {
439  if (!cassettePort) {
440  assert(getMachineConfig());
441  if (getMachineConfig()->getConfig().findChild("CassettePort")) {
442  cassettePort = make_unique<CassettePort>(*getMachineConfig());
443  } else {
444  cassettePort = make_unique<DummyCassettePort>();
445  }
446  }
447  return *cassettePort;
448 }
449 
451 {
452  assert(port < 2);
453  if (!joystickPort[0]) {
454  assert(getMachineConfig());
455  // some MSX machines only have 1 instead of 2 joystick ports
456  std::string_view ports = getMachineConfig()->getConfig().getChildData(
457  "JoystickPorts", "AB");
458  if (ports != one_of("AB", "", "A", "B")) {
459  throw ConfigException(
460  "Invalid JoystickPorts specification, "
461  "should be one of '', 'A', 'B' or 'AB'.");
462  }
464  if (ports == one_of("AB", "A")) {
465  joystickPort[0] = make_unique<JoystickPort>(
466  ctrl, "joyporta", "MSX Joystick port A");
467  } else {
468  joystickPort[0] = make_unique<DummyJoystickPort>();
469  }
470  if (ports == one_of("AB", "B")) {
471  joystickPort[1] = make_unique<JoystickPort>(
472  ctrl, "joyportb", "MSX Joystick port B");
473  } else {
474  joystickPort[1] = make_unique<DummyJoystickPort>();
475  }
476  joyPortDebuggable = make_unique<JoyPortDebuggable>(*this);
477  }
478  return *joystickPort[port];
479 }
480 
482 {
483  if (!renShaTurbo) {
484  assert(getMachineConfig());
485  renShaTurbo = make_unique<RenShaTurbo>(
486  *this,
487  getMachineConfig()->getConfig());
488  }
489  return *renShaTurbo;
490 }
491 
493 {
494  if (!ledStatus) {
495  (void)getMSXCliComm(); // force init, to be on the safe side
496  ledStatus = make_unique<LedStatus>(
497  reactor.getRTScheduler(),
498  *msxCommandController,
499  *msxCliComm);
500  }
501  return *ledStatus;
502 }
503 
505 {
506  return *msxCommandController;
507 }
508 
510 {
511  return msxCommandController->getMachineInfoCommand();
512 }
513 
515 {
516  return scheduler->getCurrentTime();
517 }
518 
520 {
521  if (!powered) {
522  return false;
523  }
524  assert(getMachineConfig()); // otherwise powered cannot be true
525 
526  getCPU().execute(false);
527  return true;
528 }
529 
530 void MSXMotherBoard::fastForward(EmuTime::param time, bool fast)
531 {
532  assert(powered);
533  assert(getMachineConfig());
534 
535  if (time <= getCurrentTime()) return;
536 
537  ScopedAssign sa(fastForwarding, fast);
538  realTime->disable();
539  msxMixer->mute();
540  fastForwardHelper->setTarget(time);
541  while (time > getCurrentTime()) {
542  // note: this can run (slightly) past the requested time
543  getCPU().execute(true); // fast-forward mode
544  }
545  realTime->enable();
546  msxMixer->unmute();
547 }
548 
550 {
551  if (getMachineConfig()) {
552  getCPU().setPaused(true);
553  }
554  msxMixer->mute();
555 }
556 
558 {
559  if (getMachineConfig()) {
560  getCPU().setPaused(false);
561  }
562  msxMixer->unmute();
563 }
564 
566 {
567  availableDevices.push_back(&device);
568 }
569 
571 {
572  move_pop_back(availableDevices, rfind_unguarded(availableDevices, &device));
573 }
574 
576 {
577  if (!powered) return;
578  assert(getMachineConfig());
579 
580  EmuTime::param time = getCurrentTime();
582  for (auto& d : availableDevices) {
583  d->reset(time);
584  }
585  getCPU().doReset(time);
586  // let everyone know we're booting, note that the fact that this is
587  // done after the reset call to the devices is arbitrary here
589  Event::create<BootEvent>());
590 }
591 
593 {
594  byte result = 0xff;
595  for (auto& d : availableDevices) {
596  result &= d->readIRQVector();
597  }
598  return result;
599 }
600 
602 {
603  if (powered) return;
604  if (!getMachineConfig()) return;
605 
606  powered = true;
607  // TODO: If our "powered" field is always equal to the power setting,
608  // there is not really a point in keeping it.
609  // TODO: assert disabled see note in Reactor::run() where this method
610  // is called
611  //assert(powerSetting.getBoolean() == powered);
612  powerSetting.setBoolean(true);
613  // TODO: We could make the power LED a device, so we don't have to handle
614  // it separately here.
616 
617  EmuTime::param time = getCurrentTime();
619  for (auto& d : availableDevices) {
620  d->powerUp(time);
621  }
622  getCPU().doReset(time);
623  msxMixer->unmute();
624  // let everyone know we're booting, note that the fact that this is
625  // done after the reset call to the devices is arbitrary here
627  Event::create<BootEvent>());
628 }
629 
630 void MSXMotherBoard::powerDown()
631 {
632  if (!powered) return;
633 
634  powered = false;
635  // TODO: This assertion fails in 1 case: when quitting with a running MSX.
636  // How do we want the Reactor to shutdown: immediately or after
637  // handling all pending commands/events/updates?
638  //assert(powerSetting.getBoolean() == powered);
639  powerSetting.setBoolean(false);
641 
642  msxMixer->mute();
643 
644  EmuTime::param time = getCurrentTime();
645  for (auto& d : availableDevices) {
646  d->powerDown(time);
647  }
648 }
649 
650 void MSXMotherBoard::activate(bool active_)
651 {
652  active = active_;
653  auto event = active ? Event::create<MachineActivatedEvent>()
654  : Event::create<MachineDeactivatedEvent>();
655  msxEventDistributor->distributeEvent(event, scheduler->getCurrentTime());
656  if (active) {
657  realTime->resync();
658  }
659 }
660 
662 {
663  if (getMachineConfig()) {
665  }
666 }
667 
669 {
671 }
672 
673 MSXDevice* MSXMotherBoard::findDevice(std::string_view name)
674 {
675  auto it = ranges::find(availableDevices, name, &MSXDevice::getName);
676  return (it != end(availableDevices)) ? *it : nullptr;
677 }
678 
680 {
681  if (mapperIOCounter == 0) {
683 
684  MSXCPUInterface& cpuInterface = getCPUInterface();
685  cpuInterface.register_IO_Out(0xFC, mapperIO.get());
686  cpuInterface.register_IO_Out(0xFD, mapperIO.get());
687  cpuInterface.register_IO_Out(0xFE, mapperIO.get());
688  cpuInterface.register_IO_Out(0xFF, mapperIO.get());
689  cpuInterface.register_IO_In (0xFC, mapperIO.get());
690  cpuInterface.register_IO_In (0xFD, mapperIO.get());
691  cpuInterface.register_IO_In (0xFE, mapperIO.get());
692  cpuInterface.register_IO_In (0xFF, mapperIO.get());
693  }
694  ++mapperIOCounter;
695  return *mapperIO;
696 }
697 
699 {
700  assert(mapperIO);
701  assert(mapperIOCounter);
702  --mapperIOCounter;
703  if (mapperIOCounter == 0) {
704  MSXCPUInterface& cpuInterface = getCPUInterface();
705  cpuInterface.unregister_IO_Out(0xFC, mapperIO.get());
706  cpuInterface.unregister_IO_Out(0xFD, mapperIO.get());
707  cpuInterface.unregister_IO_Out(0xFE, mapperIO.get());
708  cpuInterface.unregister_IO_Out(0xFF, mapperIO.get());
709  cpuInterface.unregister_IO_In (0xFC, mapperIO.get());
710  cpuInterface.unregister_IO_In (0xFD, mapperIO.get());
711  cpuInterface.unregister_IO_In (0xFE, mapperIO.get());
712  cpuInterface.unregister_IO_In (0xFF, mapperIO.get());
713 
714  mapperIO.reset();
715  }
716 }
717 
718 string MSXMotherBoard::getUserName(const string& hwName)
719 {
720  auto& s = userNames[hwName];
721  unsigned n = 0;
722  string userName;
723  do {
724  userName = strCat("untitled", ++n);
725  } while (contains(s, userName));
726  s.push_back(userName);
727  return userName;
728 }
729 
730 void MSXMotherBoard::freeUserName(const string& hwName, const string& userName)
731 {
732  auto& s = userNames[hwName];
733  move_pop_back(s, rfind_unguarded(s, userName));
734 }
735 
736 // AddRemoveUpdate
737 
739  : motherBoard(motherBoard_)
740 {
741  motherBoard.getReactor().getGlobalCliComm().update(
742  CliComm::HARDWARE, motherBoard.getMachineID(), "add");
743 }
744 
746 {
747  motherBoard.getReactor().getGlobalCliComm().update(
748  CliComm::HARDWARE, motherBoard.getMachineID(), "remove");
749 }
750 
751 
752 // ResetCmd
754  : RecordedCommand(motherBoard_.getCommandController(),
755  motherBoard_.getStateChangeDistributor(),
756  motherBoard_.getScheduler(),
757  "reset")
758  , motherBoard(motherBoard_)
759 {
760 }
761 
762 void ResetCmd::execute(std::span<const TclObject> /*tokens*/, TclObject& /*result*/,
763  EmuTime::param /*time*/)
764 {
765  motherBoard.doReset();
766 }
767 
768 string ResetCmd::help(std::span<const TclObject> /*tokens*/) const
769 {
770  return "Resets the MSX.";
771 }
772 
773 
774 // LoadMachineCmd
776  : Command(motherBoard_.getCommandController(), "load_machine")
777  , motherBoard(motherBoard_)
778 {
779  // The load_machine command should always directly follow a
780  // create_machine command:
781  // - It's not allowed to use load_machine on a machine that has
782  // already a machine configuration loaded earlier.
783  // - We also disallow executing most machine-specifc commands on an
784  // 'empty machine' (an 'empty machine', is a machine returned by
785  // create_machine before the load_machine command is executed, so a
786  // machine without a machine configuration). The only exception is
787  // this load_machine command and machine_info.
788  //
789  // So if the only allowed command on an empty machine is
790  // 'load_machine', (and an empty machine by itself isn't very useful),
791  // then why isn't create_machine and load_machine merged into a single
792  // command? The only reason for this is that load_machine sends out
793  // events (machine specific) and maybe you already want to know the ID
794  // of the new machine (this is returned by create_machine) before those
795  // events will be send.
796  //
797  // Why not allow all commands on an empty machine? In the past we did
798  // allow this, though it often was the source of bugs. We could in each
799  // command (when needed) check for an empty machine and then return
800  // some dummy/empty result or some error. But because I can't think of
801  // any really useful command for an empty machine, it seemed easier to
802  // just disallow most commands.
804 }
805 
806 void LoadMachineCmd::execute(std::span<const TclObject> tokens, TclObject& result)
807 {
808  checkNumArgs(tokens, 2, "machine");
809  if (motherBoard.getMachineConfig()) {
810  throw CommandException("Already loaded a config in this machine.");
811  }
812  result = motherBoard.loadMachine(string(tokens[1].getString()));
813 }
814 
815 string LoadMachineCmd::help(std::span<const TclObject> /*tokens*/) const
816 {
817  return "Load a msx machine configuration into an empty machine.";
818 }
819 
820 void LoadMachineCmd::tabCompletion(std::vector<string>& tokens) const
821 {
822  completeString(tokens, Reactor::getHwConfigs("machines"));
823 }
824 
825 
826 // ListExtCmd
828  : Command(motherBoard_.getCommandController(), "list_extensions")
829  , motherBoard(motherBoard_)
830 {
831 }
832 
833 void ListExtCmd::execute(std::span<const TclObject> /*tokens*/, TclObject& result)
834 {
835  result.addListElements(
836  view::transform(motherBoard.getExtensions(),
837  [&](auto& e) { return e->getName(); }));
838 }
839 
840 string ListExtCmd::help(std::span<const TclObject> /*tokens*/) const
841 {
842  return "Return a list of all inserted extensions.";
843 }
844 
845 
846 // ExtCmd
847 ExtCmd::ExtCmd(MSXMotherBoard& motherBoard_, std::string commandName_)
848  : RecordedCommand(motherBoard_.getCommandController(),
849  motherBoard_.getStateChangeDistributor(),
850  motherBoard_.getScheduler(),
851  commandName_)
852  , motherBoard(motherBoard_)
853  , commandName(std::move(commandName_))
854 {
855 }
856 
857 void ExtCmd::execute(std::span<const TclObject> tokens, TclObject& result,
858  EmuTime::param /*time*/)
859 {
860  checkNumArgs(tokens, 2, "extension");
861  try {
862  auto slotname = (commandName.size() == 4)
863  ? std::string_view(&commandName[3], 1)
864  : "any";
865  result = motherBoard.loadExtension(
866  tokens[1].getString(), slotname);
867  } catch (MSXException& e) {
868  throw CommandException(std::move(e).getMessage());
869  }
870 }
871 
872 string ExtCmd::help(std::span<const TclObject> /*tokens*/) const
873 {
874  return "Insert a hardware extension.";
875 }
876 
877 void ExtCmd::tabCompletion(std::vector<string>& tokens) const
878 {
879  completeString(tokens, Reactor::getHwConfigs("extensions"));
880 }
881 
882 
883 // RemoveExtCmd
885  : RecordedCommand(motherBoard_.getCommandController(),
886  motherBoard_.getStateChangeDistributor(),
887  motherBoard_.getScheduler(),
888  "remove_extension")
889  , motherBoard(motherBoard_)
890 {
891 }
892 
893 void RemoveExtCmd::execute(std::span<const TclObject> tokens, TclObject& /*result*/,
894  EmuTime::param /*time*/)
895 {
896  checkNumArgs(tokens, 2, "extension");
897  std::string_view extName = tokens[1].getString();
898  HardwareConfig* extension = motherBoard.findExtension(extName);
899  if (!extension) {
900  throw CommandException("No such extension: ", extName);
901  }
902  try {
903  motherBoard.removeExtension(*extension);
904  } catch (MSXException& e) {
905  throw CommandException("Can't remove extension '", extName,
906  "': ", e.getMessage());
907  }
908 }
909 
910 string RemoveExtCmd::help(std::span<const TclObject> /*tokens*/) const
911 {
912  return "Remove an extension from the MSX machine.";
913 }
914 
915 void RemoveExtCmd::tabCompletion(std::vector<string>& tokens) const
916 {
917  if (tokens.size() == 2) {
919  motherBoard.getExtensions(),
920  [](auto& e) -> std::string_view { return e->getName(); }));
921  }
922 }
923 
924 
925 // MachineNameInfo
926 
928  : InfoTopic(motherBoard_.getMachineInfoCommand(), "config_name")
929  , motherBoard(motherBoard_)
930 {
931 }
932 
933 void MachineNameInfo::execute(std::span<const TclObject> /*tokens*/,
934  TclObject& result) const
935 {
936  result = motherBoard.getMachineName();
937 }
938 
939 string MachineNameInfo::help(std::span<const TclObject> /*tokens*/) const
940 {
941  return "Returns the configuration name for this machine.";
942 }
943 
944 // MachineTypeInfo
945 
947  : InfoTopic(motherBoard_.getMachineInfoCommand(), "type")
948  , motherBoard(motherBoard_)
949 {
950 }
951 
952 void MachineTypeInfo::execute(std::span<const TclObject> /*tokens*/,
953  TclObject& result) const
954 {
955  result = motherBoard.getMachineType();
956 }
957 
958 string MachineTypeInfo::help(std::span<const TclObject> /*tokens*/) const
959 {
960  return "Returns the machine type for this machine.";
961 }
962 
963 
964 // MachineExtensionInfo
965 
967  : InfoTopic(motherBoard_.getMachineInfoCommand(), "extension")
968  , motherBoard(motherBoard_)
969 {
970 }
971 
972 void MachineExtensionInfo::execute(std::span<const TclObject> tokens,
973  TclObject& result) const
974 {
975  checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?extension-instance-name?");
976  if (tokens.size() == 2) {
977  result.addListElements(
978  view::transform(motherBoard.getExtensions(),
979  [&](auto& e) { return e->getName(); }));
980  } else if (tokens.size() == 3) {
981  std::string_view extName = tokens[2].getString();
982  HardwareConfig* extension = motherBoard.findExtension(extName);
983  if (!extension) {
984  throw CommandException("No such extension: ", extName);
985  }
986  if (extension->getType() == HardwareConfig::Type::EXTENSION) {
987  // A 'true' extension, as specified in an XML file
988  result.addDictKeyValue("config", extension->getConfigName());
989  } else {
990  assert(extension->getType() == HardwareConfig::Type::ROM);
991  // A ROM cartridge, peek into the internal config for the original filename
992  const auto& filename = extension->getConfig()
993  .getChild("devices").getChild("primary").getChild("secondary")
994  .getChild("ROM").getChild("rom").getChildData("filename");
995  result.addDictKeyValue("rom", filename);
996  }
997  TclObject deviceList;
998  deviceList.addListElements(
999  view::transform(extension->getDevices(),
1000  [&](auto& e) { return e->getName(); }));
1001  result.addDictKeyValue("devices", deviceList);
1002  }
1003 }
1004 
1005 string MachineExtensionInfo::help(std::span<const TclObject> /*tokens*/) const
1006 {
1007  return "Returns information about the given extension instance.";
1008 }
1009 
1010 void MachineExtensionInfo::tabCompletion(std::vector<string>& tokens) const
1011 {
1012  if (tokens.size() == 3) {
1014  motherBoard.getExtensions(),
1015  [](auto& e) -> std::string_view { return e->getName(); }));
1016  }
1017 }
1018 
1019 
1020 // DeviceInfo
1021 
1023  : InfoTopic(motherBoard_.getMachineInfoCommand(), "device")
1024  , motherBoard(motherBoard_)
1025 {
1026 }
1027 
1028 void DeviceInfo::execute(std::span<const TclObject> tokens, TclObject& result) const
1029 {
1030  checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?device?");
1031  switch (tokens.size()) {
1032  case 2:
1033  result.addListElements(
1034  view::transform(motherBoard.availableDevices,
1035  [](auto& d) { return d->getName(); }));
1036  break;
1037  case 3: {
1038  std::string_view deviceName = tokens[2].getString();
1039  MSXDevice* device = motherBoard.findDevice(deviceName);
1040  if (!device) {
1041  throw CommandException("No such device: ", deviceName);
1042  }
1043  device->getDeviceInfo(result);
1044  break;
1045  }
1046  }
1047 }
1048 
1049 string DeviceInfo::help(std::span<const TclObject> /*tokens*/) const
1050 {
1051  return "Without any arguments, returns the list of used device names.\n"
1052  "With a device name as argument, returns the type (and for some "
1053  "devices the subtype) of the given device.";
1054 }
1055 
1056 void DeviceInfo::tabCompletion(std::vector<string>& tokens) const
1057 {
1058  if (tokens.size() == 3) {
1060  motherBoard.availableDevices,
1061  [](auto& d) -> std::string_view { return d->getName(); }));
1062  }
1063 }
1064 
1065 
1066 // FastForwardHelper
1067 
1069  : Schedulable(motherBoard_.getScheduler())
1070  , motherBoard(motherBoard_)
1071 {
1072 }
1073 
1074 void FastForwardHelper::setTarget(EmuTime::param targetTime)
1075 {
1076  setSyncPoint(targetTime);
1077 }
1078 
1079 void FastForwardHelper::executeUntil(EmuTime::param /*time*/)
1080 {
1081  motherBoard.exitCPULoopSync();
1082 }
1083 
1084 
1085 // class JoyPortDebuggable
1086 
1088  : SimpleDebuggable(motherBoard_, "joystickports", "MSX Joystick Ports", 2)
1089 {
1090 }
1091 
1092 byte JoyPortDebuggable::read(unsigned address, EmuTime::param time)
1093 {
1094  return getMotherBoard().getJoystickPort(address).read(time);
1095 }
1096 
1097 void JoyPortDebuggable::write(unsigned /*address*/, byte /*value*/)
1098 {
1099  // ignore
1100 }
1101 
1102 
1103 // class SettingObserver
1104 
1106  : motherBoard(motherBoard_)
1107 {
1108 }
1109 
1111 {
1112  if (&setting == &motherBoard.powerSetting) {
1113  if (motherBoard.powerSetting.getBoolean()) {
1114  motherBoard.powerUp();
1115  } else {
1116  motherBoard.powerDown();
1117  }
1118  } else {
1119  UNREACHABLE;
1120  }
1121 }
1122 
1123 
1124 // serialize
1125 // version 1: initial version
1126 // version 2: added reRecordCount
1127 // version 3: removed reRecordCount (moved to ReverseManager)
1128 // version 4: moved joystickportA/B from MSXPSG to here
1129 // version 5: do serialize renShaTurbo
1130 template<typename Archive>
1131 void MSXMotherBoard::serialize(Archive& ar, unsigned version)
1132 {
1133  // don't serialize:
1134  // machineID, userNames, availableDevices, addRemoveUpdate,
1135  // sharedStuffMap, msxCliComm, msxEventDistributor,
1136  // msxCommandController, slotManager, eventDelay,
1137  // debugger, msxMixer, panasonicMemory, ledStatus
1138 
1139  // Scheduler must come early so that devices can query current time
1140  ar.serialize("scheduler", *scheduler);
1141  // MSXMixer has already set syncpoints, those are invalid now
1142  // the following call will fix this
1143  if constexpr (Archive::IS_LOADER) {
1144  msxMixer->reInit();
1145  }
1146 
1147  ar.serialize("name", machineName);
1148  ar.serializeWithID("config", machineConfig2, std::ref(*this));
1149  assert(getMachineConfig() == machineConfig2.get());
1150  ar.serializeWithID("extensions", extensions, std::ref(*this));
1151 
1152  if (mapperIO) ar.serialize("mapperIO", *mapperIO);
1153 
1154  MSXDeviceSwitch& devSwitch = getDeviceSwitch();
1155  if (devSwitch.hasRegisteredDevices()) {
1156  ar.serialize("deviceSwitch", devSwitch);
1157  }
1158 
1159  if (getMachineConfig()) {
1160  ar.serialize("cpu", getCPU());
1161  }
1162  ar.serialize("cpuInterface", getCPUInterface());
1163 
1164  if (auto* port = dynamic_cast<CassettePort*>(&getCassettePort())) {
1165  ar.serialize("cassetteport", *port);
1166  }
1167  if (ar.versionAtLeast(version, 4)) {
1168  if (auto* port = dynamic_cast<JoystickPort*>(
1169  joystickPort[0].get())) {
1170  ar.serialize("joystickportA", *port);
1171  }
1172  if (auto* port = dynamic_cast<JoystickPort*>(
1173  joystickPort[1].get())) {
1174  ar.serialize("joystickportB", *port);
1175  }
1176  }
1177  if (ar.versionAtLeast(version, 5)) {
1178  if (renShaTurbo) ar.serialize("renShaTurbo", *renShaTurbo);
1179  }
1180 
1181  if constexpr (Archive::IS_LOADER) {
1182  powered = true; // must come before changing power setting
1183  powerSetting.setBoolean(true);
1185  msxMixer->unmute();
1186  }
1187 
1188  if (version == 2) {
1189  assert(Archive::IS_LOADER);
1190  unsigned reRecordCount = 0; // silence warning
1191  ar.serialize("reRecordCount", reRecordCount);
1192  getReverseManager().setReRecordCount(reRecordCount);
1193  }
1194 }
1196 
1197 } // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
Assign new value to some variable and restore the original value when this object goes out of scope.
Definition: ScopedAssign.hh:8
Definition: one_of.hh:7
AddRemoveUpdate(const AddRemoveUpdate &)=delete
AddRemoveUpdate(MSXMotherBoard &motherBoard)
AddRemoveUpdate & operator=(const AddRemoveUpdate &)=delete
bool getBoolean() const noexcept
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
void setAllowedInEmptyMachine(bool value)
Definition: Command.hh:65
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
void checkNumArgs(std::span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:176
static std::unique_ptr< MSXDeviceSwitch > createDeviceSwitch(const HardwareConfig &hwConf)
static std::unique_ptr< MSXMapperIO > createMapperIO(const HardwareConfig &hwConf)
DeviceInfo(MSXMotherBoard &motherBoard)
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
void tabCompletion(std::vector< string > &tokens) const override
Attempt tab completion for this topic.
void distributeEvent(Event &&event)
Schedule the given event for delivery.
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
ExtCmd(MSXMotherBoard &motherBoard, std::string commandName)
void execute(std::span< const TclObject > tokens, TclObject &result, EmuTime::param time) override
This is like the execute() method of the Command class, it only has an extra time parameter.
std::string help(std::span< const TclObject > tokens) const override
Print help for this command.
void setTarget(EmuTime::param targetTime)
FastForwardHelper(MSXMotherBoard &motherBoard)
void update(UpdateType type, std::string_view name, std::string_view value) override
const XMLElement & getConfig() const
const auto & getDevices() const
const std::string & getConfigName() const
static std::unique_ptr< HardwareConfig > createMachineConfig(MSXMotherBoard &motherBoard, std::string machineName)
const std::string & getName() const
static std::unique_ptr< HardwareConfig > createExtensionConfig(MSXMotherBoard &motherBoard, std::string extensionName, std::string_view slotname)
void testRemove() const
Checks whether this HardwareConfig can be deleted.
void write(unsigned address, byte value) override
JoyPortDebuggable(MSXMotherBoard &motherBoard)
byte read(unsigned address, EmuTime::param time) override
virtual byte read(EmuTime::param time)=0
void setLed(Led led, bool status)
Definition: LedStatus.cc:40
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
ListExtCmd(MSXMotherBoard &motherBoard)
string help(std::span< const TclObject > tokens) const override
Print help for this command.
LoadMachineCmd(MSXMotherBoard &motherBoard)
string help(std::span< const TclObject > tokens) const override
Print help for this command.
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
void tabCompletion(std::vector< string > &tokens) const override
Attempt tab completion for this command.
void register_IO_Out(byte port, MSXDevice *device)
Devices can register their Out ports.
void register_IO_In(byte port, MSXDevice *device)
Devices can register their In ports.
void unregister_IO_In(byte port, MSXDevice *device)
void reset()
Reset (the slot state)
void unregister_IO_Out(byte port, MSXDevice *device)
void exitCPULoopSync()
See CPUCore::exitCPULoopsync()
Definition: MSXCPU.cc:128
void doReset(EmuTime::param time)
Reset CPU.
Definition: MSXCPU.cc:82
void setPaused(bool paused)
(un)pause CPU.
Definition: MSXCPU.cc:361
void exitCPULoopAsync()
See CPUCore::exitCPULoopAsync()
Definition: MSXCPU.cc:133
bool hasRegisteredDevices() const
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:33
void getDeviceInfo(TclObject &result) const
Get device info.
Definition: MSXDevice.cc:386
virtual const std::string & getName() const
Returns a human-readable name for this device.
Definition: MSXDevice.cc:376
HardwareConfig * findExtension(std::string_view extensionName)
PluggingController & getPluggingController()
void activate(bool active)
void setMachineConfig(HardwareConfig *machineConfig)
RenShaTurbo & getRenShaTurbo()
void freeUserName(const std::string &hwName, const std::string &userName)
bool execute()
Run emulation.
MSXCPUInterface & getCPUInterface()
std::string getUserName(const std::string &hwName)
Keep track of which 'usernames' are in use.
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
std::string loadMachine(const std::string &machine)
std::string_view getMachineName() const
ReverseManager & getReverseManager()
MSXDevice * findDevice(std::string_view name)
Find a MSXDevice by name.
CommandController & getCommandController()
void pause()
Pause MSX machine.
const Extensions & getExtensions() const
std::string insertExtension(std::string_view name, std::unique_ptr< HardwareConfig > extension)
void fastForward(EmuTime::param time, bool fast)
Run emulation until a certain time in fast forward mode.
void exitCPULoopAsync()
See CPU::exitCPULoopAsync().
MSXMotherBoard(const MSXMotherBoard &)=delete
MSXDeviceSwitch & getDeviceSwitch()
void serialize(Archive &ar, unsigned version)
void removeDevice(MSXDevice &device)
std::string_view getMachineID() const
const HardwareConfig * getMachineConfig() const
void removeExtension(const HardwareConfig &extension)
JoystickPortIf & getJoystickPort(unsigned port)
std::string loadExtension(std::string_view extensionName, std::string_view slotname)
std::string_view getMachineType() const
CassettePortInterface & getCassettePort()
void addDevice(MSXDevice &device)
All MSXDevices should be registered by the MotherBoard.
PanasonicMemory & getPanasonicMemory()
MSXMapperIO & createMapperIO()
All memory mappers in one MSX machine share the same four (logical) memory mapper registers.
InfoCommand & getMachineInfoCommand()
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
void tabCompletion(std::vector< string > &tokens) const override
Attempt tab completion for this topic.
MachineExtensionInfo(MSXMotherBoard &motherBoard)
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
MachineNameInfo(MSXMotherBoard &motherBoard)
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
MachineTypeInfo(MSXMotherBoard &motherBoard)
Generic Gang-of-Four Observer class, templatized edition.
Definition: Observer.hh:10
Central administration of Connectors and Pluggables.
Contains the main loop of openMSX.
Definition: Reactor.hh:68
EventDistributor & getEventDistributor()
Definition: Reactor.hh:82
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:104
GlobalCliComm & getGlobalCliComm()
Definition: Reactor.hh:83
RTScheduler & getRTScheduler()
Definition: Reactor.hh:81
static std::vector< std::string > getHwConfigs(std::string_view type)
Definition: Reactor.cc:332
Commands that directly influence the MSX state should send and events so that they can be recorded by...
RemoveExtCmd(MSXMotherBoard &motherBoard)
void tabCompletion(std::vector< string > &tokens) const override
Attempt tab completion for this command.
void execute(std::span< const TclObject > tokens, TclObject &result, EmuTime::param time) override
This is like the execute() method of the Command class, it only has an extra time parameter.
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Ren-Sha Turbo is the autofire in several MSX 2+ models and in the MSX turbo R.
Definition: RenShaTurbo.hh:21
string help(std::span< const TclObject > tokens) const override
Print help for this command.
void execute(std::span< const TclObject > tokens, TclObject &result, EmuTime::param time) override
This is like the execute() method of the Command class, it only has an extra time parameter.
ResetCmd(MSXMotherBoard &motherBoard)
void setReRecordCount(unsigned count)
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:34
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
SettingObserver(MSXMotherBoard &motherBoard)
void update(const Setting &setting) noexcept override
MSXMotherBoard & getMotherBoard() const
void detach(Observer< T > &observer)
Definition: Subject.hh:56
void attach(Observer< T > &observer)
Definition: Subject.hh:50
void addListElements(ITER first, ITER last)
Definition: TclObject.hh:128
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:141
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:19
const XMLElement & getChild(std::string_view childName) const
Definition: XMLElement.cc:53
std::string_view getChildData(std::string_view childName) const
Definition: XMLElement.cc:62
constexpr double e
Definition: Math.hh:18
This file implemented 3 utility functions:
Definition: Autofire.cc:9
const T & get(const Event &event)
Definition: Event.hh:724
constexpr const char *const filename
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:144
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:378
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:100
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:23
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto end(const zstring_view &x)