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