openMSX
HardwareConfig.cc
Go to the documentation of this file.
1 #include "HardwareConfig.hh"
2 #include "XMLException.hh"
3 #include "DeviceConfig.hh"
4 #include "FileOperations.hh"
5 #include "MSXMotherBoard.hh"
7 #include "MSXCPUInterface.hh"
8 #include "CommandController.hh"
9 #include "DeviceFactory.hh"
10 #include "TclArgParser.hh"
11 #include "serialize.hh"
12 #include "serialize_stl.hh"
13 #include "unreachable.hh"
14 #include "xrange.hh"
15 #include <cassert>
16 #include <iostream>
17 #include <memory>
18 #include <version> // for _LIBCPP_VERSION
19 
20 using std::string;
21 
22 namespace openmsx {
23 
24 std::unique_ptr<HardwareConfig> HardwareConfig::createMachineConfig(
25  MSXMotherBoard& motherBoard, string machineName)
26 {
27  auto result = std::make_unique<HardwareConfig>(
28  motherBoard, std::move(machineName));
29  result->type = HardwareConfig::Type::MACHINE;
30  result->load("machines");
31  return result;
32 }
33 
34 std::unique_ptr<HardwareConfig> HardwareConfig::createExtensionConfig(
35  MSXMotherBoard& motherBoard, string extensionName, std::string_view slotname)
36 {
37  auto result = std::make_unique<HardwareConfig>(
38  motherBoard, std::move(extensionName));
39  result->load("extensions");
40  result->setName(result->hwName);
41  result->type = HardwareConfig::Type::EXTENSION;
42  result->setSlot(slotname);
43  return result;
44 }
45 
46 std::unique_ptr<HardwareConfig> HardwareConfig::createRomConfig(
47  MSXMotherBoard& motherBoard, string romfile,
48  string slotname, std::span<const TclObject> options)
49 {
50  auto result = std::make_unique<HardwareConfig>(motherBoard, "rom");
51  result->setName(romfile);
52  result->type = HardwareConfig::Type::ROM;
53 
54  std::vector<std::string_view> ipsfiles;
55  string mapper;
56  ArgsInfo info[] = {
57  valueArg("-ips", ipsfiles),
58  valueArg("-romtype", mapper),
59  };
60  auto& interp = motherBoard.getCommandController().getInterpreter();
61  auto args = parseTclArgs(interp, options, info);
62  if (!args.empty()) {
63  throw MSXException("Invalid option \"", args.front().getString(), '\"');
64  }
65 
66  const auto& sramfile = FileOperations::getFilename(romfile);
67  auto context = userFileContext(tmpStrCat("roms/", sramfile));
68  for (const auto& ips : ipsfiles) {
69  if (!FileOperations::isRegularFile(context.resolve(ips))) {
70  throw MSXException("Invalid IPS file: ", ips);
71  }
72  }
73 
74  string resolvedFilename = FileOperations::getAbsolutePath(
75  context.resolve(romfile));
76  if (!FileOperations::isRegularFile(resolvedFilename)) {
77  throw MSXException("Invalid ROM file: ", resolvedFilename);
78  }
79 
80  //<extension>
81  // <devices>
82  // <primary slot="...">
83  // <secondary slot="...">
84  // <ROM id="MSXRom">
85  // <mem base="0x0000" size="0x10000"/>
86  // <sound>
87  // <volume>9000</volume>
88  // </sound>
89  // <mappertype>...</mappertype>
90  // <sramname>...SRAM</sramname>
91  // <rom>
92  // <resolvedFilename>...</resolvedFilename>
93  // <filename>...</filename>
94  // <patches>
95  // <ips>...</ips>
96  // ...
97  // </patches>
98  // </rom>
99  // </ROM>
100  // </secondary>
101  // </primary>
102  // </devices>
103  //</extension>
104  XMLDocument& doc = result->config;
105  auto* extension = doc.allocateElement("extension");
106  auto* devices = extension->setFirstChild(doc.allocateElement("devices"));
107  auto* primary = devices->setFirstChild(doc.allocateElement("primary"));
108  const char* slotName2 = doc.allocateString(slotname);
109  primary->setFirstAttribute(doc.allocateAttribute("slot", slotName2));
110  auto* secondary = primary->setFirstChild(doc.allocateElement("secondary"));
111  secondary->setFirstAttribute(doc.allocateAttribute("slot", slotName2));
112  auto* device = secondary->setFirstChild(doc.allocateElement("ROM"));
113  device->setFirstAttribute(doc.allocateAttribute("id", "MSXRom"));
114  auto* mem = device->setFirstChild(doc.allocateElement("mem"));
115  mem->setFirstAttribute(doc.allocateAttribute("base", "0x0000"))
116  ->setNextAttribute (doc.allocateAttribute("size", "0x10000"));
117  auto* sound = mem->setNextSibling(doc.allocateElement("sound"));
118  sound->setFirstChild(doc.allocateElement("volume", "9000"));
119  auto* mappertype = sound->setNextSibling(doc.allocateElement("mappertype"));
120  mappertype->setData(mapper.empty() ? "auto" : doc.allocateString(mapper));
121  auto* sramname = mappertype->setNextSibling(doc.allocateElement("sramname"));
122  sramname->setData(doc.allocateString(tmpStrCat(sramfile, ".SRAM")));
123  auto* rom = sramname->setNextSibling(doc.allocateElement("rom"));
124  auto* rfName = rom->setFirstChild(doc.allocateElement("resolvedFilename"));
125  rfName->setData(doc.allocateString(resolvedFilename));
126  auto* fName = rfName->setNextSibling(doc.allocateElement("filename"));
127  fName->setData(doc.allocateString(romfile));
128  if (!ipsfiles.empty()) {
129  auto* patches = fName->setNextSibling(doc.allocateElement("patches"));
130  doc.generateList(*patches, "ips", ipsfiles, [&](XMLElement* n, auto& s) {
131  n->setData(doc.allocateString(s));
132  });
133  }
134 
135  result->setConfig(extension);
136  result->setFileContext(std::move(context));
137  return result;
138 }
139 
140 HardwareConfig::HardwareConfig(MSXMotherBoard& motherBoard_, string hwName_)
141  : motherBoard(motherBoard_)
142  , hwName(std::move(hwName_))
143  , config(8192) // tweak: initial allocator buffer size
144 {
145  for (auto& sub : externalSlots) {
146  ranges::fill(sub, false);
147  }
148  ranges::fill(externalPrimSlots, false);
149  ranges::fill(expandedSlots, false);
150  ranges::fill(allocatedPrimarySlots, false);
151  userName = motherBoard.getUserName(hwName);
152 }
153 
155 {
156  motherBoard.freeUserName(hwName, userName);
157 #ifndef NDEBUG
158  try {
159  testRemove();
160  } catch (MSXException& e) {
161  std::cerr << e.getMessage() << '\n';
162  UNREACHABLE;
163  }
164 #endif
165  while (!devices.empty()) {
166  motherBoard.removeDevice(*devices.back());
167  devices.pop_back();
168  }
169  auto& slotManager = motherBoard.getSlotManager();
170  for (auto ps : xrange(4)) {
171  for (auto ss : xrange(4)) {
172  if (externalSlots[ps][ss]) {
173  slotManager.removeExternalSlot(ps, ss);
174  }
175  }
176  if (externalPrimSlots[ps]) {
177  slotManager.removeExternalSlot(ps);
178  }
179  if (expandedSlots[ps]) {
180  motherBoard.getCPUInterface().unsetExpanded(ps);
181  }
182  if (allocatedPrimarySlots[ps]) {
183  slotManager.freePrimarySlot(ps, *this);
184  }
185  }
186 }
187 
189 {
190  auto et = devices.end();
191  for (auto rit = devices.rbegin(), ret = devices.rend();
192  rit != ret; ++rit) {
193 #ifdef _LIBCPP_VERSION
194  // Workaround clang-13/libc++ bug
195  // Don't generally use this workaround, because '*rit.base()'
196  // triggers an error in a debug-STL build.
197  std::span alreadyRemoved(&*rit.base(), et - rit.base());
198 #else
199  std::span alreadyRemoved{rit.base(), et};
200 #endif
201  (*rit)->testRemove(alreadyRemoved);
202  }
203 
204  auto& slotManager = motherBoard.getSlotManager();
205  for (auto ps : xrange(4)) {
206  for (auto ss : xrange(4)) {
207  if (externalSlots[ps][ss]) {
208  slotManager.testRemoveExternalSlot(ps, ss, *this);
209  }
210  }
211  if (externalPrimSlots[ps]) {
212  slotManager.testRemoveExternalSlot(ps, *this);
213  }
214  if (expandedSlots[ps]) {
215  motherBoard.getCPUInterface().testUnsetExpanded(
216  ps, devices);
217  }
218  }
219 }
220 
222 {
223  return getConfig().getChild("devices");
224 }
225 
226 static void loadHelper(XMLDocument& doc, std::string filename)
227 {
228  try {
229  doc.load(filename, "msxconfig2.dtd");
230  } catch (XMLException& e) {
231  throw MSXException(
232  "Loading of hardware configuration failed: ",
233  e.getMessage());
234  }
235 }
236 
237 static string getFilename(std::string_view type, std::string_view name)
238 {
239  const auto& context = systemFileContext();
240  try {
241  // try <name>.xml
242  return context.resolve(tmpStrCat(
243  type, '/', name, ".xml"));
244  } catch (MSXException& e) {
245  // backwards-compatibility:
246  // also try <name>/hardwareconfig.xml
247  try {
248  return context.resolve(tmpStrCat(
249  type, '/', name, "/hardwareconfig.xml"));
250  } catch (MSXException&) {
251  throw e; // signal first error
252  }
253  }
254 }
255 
256 void HardwareConfig::loadConfig(XMLDocument& doc, std::string_view type, std::string_view name)
257 {
258  loadHelper(doc, getFilename(type, name));
259 }
260 
261 void HardwareConfig::load(std::string_view type_)
262 {
263  string filename = getFilename(type_, hwName);
264  loadHelper(config, filename);
265 
266  assert(!userName.empty());
267  const auto& dirname = FileOperations::getDirName(filename);
268  setFileContext(configFileContext(dirname, hwName, userName));
269 }
270 
272 {
273  // TODO this code does parsing for both 'expanded' and 'external' slots
274  // once machine and extensions are parsed separately move parsing
275  // of 'expanded' to MSXCPUInterface
276  //
277  for (const auto* psElem : getDevicesElem().getChildren("primary")) {
278  const auto& primSlot = psElem->getAttribute("slot");
279  int ps = CartridgeSlotManager::getSlotNum(primSlot.getValue());
280  if (psElem->getAttributeValueAsBool("external", false)) {
281  if (ps < 0) {
282  throw MSXException(
283  "Cannot mark unspecified primary slot '",
284  primSlot.getValue(), "' as external");
285  }
286  if (psElem->hasChildren()) {
287  throw MSXException(
288  "Primary slot ", ps,
289  " is marked as external, but that would only "
290  "make sense if its <primary> tag would be "
291  "empty.");
292  }
293  createExternalSlot(ps);
294  continue;
295  }
296  for (const auto* ssElem : psElem->getChildren("secondary")) {
297  auto secSlot = ssElem->getAttributeValue("slot");
298  int ss = CartridgeSlotManager::getSlotNum(secSlot);
299  if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
300  throw MSXException(
301  "Invalid secondary slot specification: \"",
302  secSlot, "\".");
303  }
304  if (ss < 0) {
305  if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
306  motherBoard.getCPUInterface().isExpanded(ps)) {
307  ss += 128;
308  } else {
309  continue;
310  }
311  }
312  if (ps < 0) {
313  if (ps == -256) {
314  ps = getAnyFreePrimarySlot();
315  } else {
316  ps = getSpecificFreePrimarySlot(-ps - 1);
317  }
318  auto& mutablePrimSlot = const_cast<XMLAttribute&>(primSlot);
319  mutablePrimSlot.setValue(config.allocateString(strCat(ps)));
320  }
321  createExpandedSlot(ps);
322  if (ssElem->getAttributeValueAsBool("external", false)) {
323  if (ssElem->hasChildren()) {
324  throw MSXException(
325  "Secondary slot ", ps, '-', ss,
326  " is marked as external, but that would "
327  "only make sense if its <secondary> tag "
328  "would be empty.");
329  }
330  createExternalSlot(ps, ss);
331  }
332  }
333  }
334 }
335 
337 {
338  byte initialPrimarySlots = 0;
339  if (const auto* slotmap = getConfig().findChild("slotmap")) {
340  for (const auto* child : slotmap->getChildren("map")) {
341  int page = child->getAttributeValueAsInt("page", -1);
342  if (page < 0 || page > 3) {
343  throw MSXException("Invalid or missing page in slotmap entry");
344  }
345  int slot = child->getAttributeValueAsInt("slot", -1);
346  if (slot < 0 || slot > 3) {
347  throw MSXException("Invalid or missing slot in slotmap entry");
348  }
349  unsigned offset = page * 2;
350  initialPrimarySlots &= ~(3 << offset);
351  initialPrimarySlots |= slot << offset;
352  }
353  }
354  return initialPrimarySlots;
355 }
356 
358 {
359  createDevices(getDevicesElem(), nullptr, nullptr);
360 }
361 
363  const XMLElement* primary, const XMLElement* secondary)
364 {
365  for (const auto& c : elem.getChildren()) {
366  const auto& childName = c.getName();
367  if (childName == "primary") {
368  createDevices(c, &c, secondary);
369  } else if (childName == "secondary") {
370  createDevices(c, primary, &c);
371  } else {
372  auto device = DeviceFactory::create(
373  DeviceConfig(*this, c, primary, secondary));
374  if (device) {
375  addDevice(std::move(device));
376  } else {
377  // device is nullptr, so we are apparently ignoring it on purpose
378  }
379  }
380  }
381 }
382 
383 void HardwareConfig::createExternalSlot(int ps)
384 {
385  motherBoard.getSlotManager().createExternalSlot(ps);
386  assert(!externalPrimSlots[ps]);
387  externalPrimSlots[ps] = true;
388 }
389 
390 void HardwareConfig::createExternalSlot(int ps, int ss)
391 {
392  motherBoard.getSlotManager().createExternalSlot(ps, ss);
393  assert(!externalSlots[ps][ss]);
394  externalSlots[ps][ss] = true;
395 }
396 
397 void HardwareConfig::createExpandedSlot(int ps)
398 {
399  if (!expandedSlots[ps]) {
400  motherBoard.getCPUInterface().setExpanded(ps);
401  expandedSlots[ps] = true;
402  }
403 }
404 
405 int HardwareConfig::getAnyFreePrimarySlot()
406 {
407  int ps = motherBoard.getSlotManager().allocateAnyPrimarySlot(*this);
408  assert(!allocatedPrimarySlots[ps]);
409  allocatedPrimarySlots[ps] = true;
410  return ps;
411 }
412 
413 int HardwareConfig::getSpecificFreePrimarySlot(unsigned slot)
414 {
415  int ps = motherBoard.getSlotManager().allocateSpecificPrimarySlot(slot, *this);
416  assert(!allocatedPrimarySlots[ps]);
417  allocatedPrimarySlots[ps] = true;
418  return ps;
419 }
420 
421 void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
422 {
423  motherBoard.addDevice(*device);
424  devices.push_back(std::move(device));
425 }
426 
427 void HardwareConfig::setName(std::string_view proposedName)
428 {
429  if (!motherBoard.findExtension(proposedName)) {
430  name = proposedName;
431  } else {
432  unsigned n = 0;
433  do {
434  name = strCat(proposedName, " (", ++n, ')');
435  } while (motherBoard.findExtension(name));
436  }
437 }
438 
439 void HardwareConfig::setSlot(std::string_view slotname)
440 {
441  for (const auto* psElem : getDevicesElem().getChildren("primary")) {
442  const auto& primSlot = psElem->getAttribute("slot");
443  if (primSlot.getValue() == "any") {
444  auto& mutablePrimSlot = const_cast<XMLAttribute&>(primSlot);
445  mutablePrimSlot.setValue(config.allocateString(slotname));
446  }
447  }
448 }
449 
450 static constexpr std::initializer_list<enum_string<HardwareConfig::Type>> configTypeInfo = {
451  { "MACHINE", HardwareConfig::Type::MACHINE },
452  { "EXTENSION", HardwareConfig::Type::EXTENSION },
453  { "ROM", HardwareConfig::Type::ROM },
454 };
456 
457 // version 1: initial version
458 // version 2: moved FileContext here (was part of config)
459 // version 3: hold 'config' by-value instead of by-pointer
460 // version 4: hold 'context' by-value instead of by-pointer
461 // version 5: added hwconfig type info
462 // version 6: switch from old to new XMLElement
463 template<typename Archive>
464 void HardwareConfig::serialize(Archive& ar, unsigned version)
465 {
466  // filled-in by constructor:
467  // motherBoard, hwName, userName
468  // filled-in by parseSlots()
469  // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
470 
471  if (ar.versionAtLeast(version, 6)) {
472  ar.serialize("config", config);
473  } else {
474  OldXMLElement elem;
475  ar.serialize("config", elem); // fills in getLastSerializedFileContext()
476  config.load(elem);
477  }
478  if (ar.versionAtLeast(version, 2)) {
479  if (ar.versionAtLeast(version, 4)) {
480  ar.serialize("context", context);
481  } else {
482  std::unique_ptr<FileContext> ctxt;
483  ar.serialize("context", ctxt);
484  if (ctxt) context = *ctxt;
485  }
486  } else {
488  assert(ctxt);
489  context = *ctxt;
490  }
491  if constexpr (Archive::IS_LOADER) {
492  if (!motherBoard.getMachineConfig()) {
493  // must be done before parseSlots()
494  motherBoard.setMachineConfig(this);
495  } else {
496  // already set because this is an extension
497  }
498  parseSlots();
499  createDevices();
500  }
501  // only (polymorphically) initialize devices, they are already created
502  for (auto& d : devices) {
503  ar.serializePolymorphic("device", *d);
504  }
505  ar.serialize("name", name);
506  if (ar.versionAtLeast(version, 5)) {
507  ar.serialize("type", type);
508  } else {
509  assert(Archive::IS_LOADER);
511  }
512 }
514 
515 } // namespace openmsx
static int getSlotNum(std::string_view slot)
void testRemoveExternalSlot(int ps, const HardwareConfig &allowed) const
int allocateAnyPrimarySlot(const HardwareConfig &hwConfig)
int allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig &hwConfig)
virtual Interpreter & getInterpreter()=0
static std::unique_ptr< MSXDevice > create(const DeviceConfig &conf)
std::string resolve(std::string_view filename) const
Definition: FileContext.cc:79
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, std::string romfile, std::string slotname, std::span< const TclObject > options)
const XMLElement & getConfig() const
HardwareConfig(const HardwareConfig &)=delete
static std::unique_ptr< HardwareConfig > createMachineConfig(MSXMotherBoard &motherBoard, std::string machineName)
void serialize(Archive &ar, unsigned version)
void setFileContext(FileContext &&ctxt)
static std::unique_ptr< HardwareConfig > createExtensionConfig(MSXMotherBoard &motherBoard, std::string extensionName, std::string_view slotname)
byte parseSlotMap() const
Parses a slot mapping.
static void loadConfig(XMLDocument &doc, std::string_view type, std::string_view name)
void testRemove() const
Checks whether this HardwareConfig can be deleted.
const XMLElement & getDevicesElem() const
void testUnsetExpanded(int ps, std::span< const std::unique_ptr< MSXDevice >> allowed) const
bool isExpanded(int ps) const
HardwareConfig * findExtension(std::string_view extensionName)
void setMachineConfig(HardwareConfig *machineConfig)
void freeUserName(const std::string &hwName, const std::string &userName)
MSXCPUInterface & getCPUInterface()
std::string getUserName(const std::string &hwName)
Keep track of which 'usernames' are in use.
CartridgeSlotManager & getSlotManager()
CommandController & getCommandController()
void removeDevice(MSXDevice &device)
const HardwareConfig * getMachineConfig() const
void addDevice(MSXDevice &device)
All MSXDevices should be registered by the MotherBoard.
void setValue(const char *value_)
Definition: XMLElement.hh:56
void generateList(XMLElement &parent, const char *itemName, Range &&range, UnaryOp op)
Definition: XMLElement.hh:282
const char * allocateString(std::string_view str)
Definition: XMLElement.cc:311
XMLElement * allocateElement(const char *name)
Definition: XMLElement.cc:293
XMLAttribute * allocateAttribute(const char *name, const char *value)
Definition: XMLElement.cc:305
void load(const std::string &filename, std::string_view systemID)
Definition: XMLElement.cc:320
XMLElement * setData(const char *data_)
Definition: XMLElement.hh:176
const XMLElement & getChild(std::string_view childName) const
Definition: XMLElement.cc:53
ChildRange getChildren() const
Definition: XMLElement.hh:194
constexpr double e
Definition: Math.hh:18
std::optional< Context > context
Definition: GLContext.cc:9
string_view getDirName(string_view path)
Returns the directory portion of a path.
bool isRegularFile(const Stat &st)
string getAbsolutePath(string_view path)
Transform given path into an absolute path.
string_view getFilename(string_view path)
Returns the file portion of a path name.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
const FileContext & systemFileContext()
Definition: FileContext.cc:155
FileContext configFileContext(string_view path, string_view hwDescr, string_view userName)
Definition: FileContext.cc:149
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
ArgsInfo valueArg(std::string_view name, T &value)
Definition: TclArgParser.hh:85
std::vector< TclObject > parseTclArgs(Interpreter &interp, std::span< const TclObject > inArgs, std::span< const ArgsInfo > table)
constexpr const char *const filename
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:171
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:256
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:617
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:519
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:133