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