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