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 "LocalFileReference.hh"
7 #include "FileContext.hh"
8 #include "FileOperations.hh"
9 #include "MSXMotherBoard.hh"
10 #include "CartridgeSlotManager.hh"
11 #include "MSXCPUInterface.hh"
12 #include "DeviceFactory.hh"
13 #include "CliComm.hh"
14 #include "serialize.hh"
15 #include "serialize_stl.hh"
16 #include "StringOp.hh"
17 #include "memory.hh"
18 #include "unreachable.hh"
19 #include "xrange.hh"
20 #include <cassert>
21 #include <iostream>
22 
23 using std::string;
24 using std::vector;
25 using std::unique_ptr;
26 using std::move;
27 
28 namespace openmsx {
29 
30 unique_ptr<HardwareConfig> HardwareConfig::createMachineConfig(
31  MSXMotherBoard& motherBoard, const string& machineName)
32 {
33  auto result = make_unique<HardwareConfig>(motherBoard, machineName);
34  result->load("machines");
35  return result;
36 }
37 
38 unique_ptr<HardwareConfig> HardwareConfig::createExtensionConfig(
39  MSXMotherBoard& motherBoard, const string& extensionName)
40 {
41  auto result = make_unique<HardwareConfig>(motherBoard, extensionName);
42  result->load("extensions");
43  result->setName(extensionName);
44  return result;
45 }
46 
47 unique_ptr<HardwareConfig> HardwareConfig::createRomConfig(
48  MSXMotherBoard& motherBoard, const string& romfile,
49  const string& slotname, const vector<string>& options)
50 {
51  auto result = make_unique<HardwareConfig>(motherBoard, "rom");
52  const auto& sramfile = FileOperations::getFilename(romfile);
53  auto context = make_unique<UserFileContext>("roms/" + sramfile);
54 
55  vector<string> ipsfiles;
56  string mapper;
57 
58  bool romTypeOptionFound = false;
59 
60  // parse options
61  for (auto it = options.begin(); it != options.end(); ++it) {
62  const auto& option = *it++;
63  if (it == options.end()) {
64  throw MSXException("Missing argument for option \"" +
65  option + '\"');
66  }
67  if (option == "-ips") {
68  if (!FileOperations::isRegularFile(context->resolve(*it))) {
69  throw MSXException("Invalid IPS file: " + *it);
70  }
71  ipsfiles.push_back(*it);
72  } else if (option == "-romtype") {
73  if (!romTypeOptionFound) {
74  mapper = *it;
75  romTypeOptionFound = true;
76  } else {
77  throw MSXException("Only one -romtype option is allowed");
78  }
79  } else {
80  throw MSXException("Invalid option \"" + option + '\"');
81  }
82  }
83 
84  string resolvedFilename = FileOperations::getAbsolutePath(
85  context->resolve(romfile));
86  if (!FileOperations::isRegularFile(resolvedFilename)) {
87  throw MSXException("Invalid ROM file: " + resolvedFilename);
88  }
89 
90  XMLElement extension("extension");
91  XMLElement devices("devices");
92  XMLElement primary("primary");
93  primary.addAttribute("slot", slotname);
94  XMLElement secondary("secondary");
95  secondary.addAttribute("slot", slotname);
96  XMLElement device("ROM");
97  device.addAttribute("id", "MSXRom");
98  XMLElement mem("mem");
99  mem.addAttribute("base", "0x0000");
100  mem.addAttribute("size", "0x10000");
101  device.addChild(move(mem));
102  XMLElement rom("rom");
103  rom.addChild(XMLElement("resolvedFilename", resolvedFilename));
104  rom.addChild(XMLElement("filename", romfile));
105  if (!ipsfiles.empty()) {
106  XMLElement patches("patches");
107  for (auto& s : ipsfiles) {
108  patches.addChild(XMLElement("ips", s));
109  }
110  rom.addChild(move(patches));
111  }
112  device.addChild(move(rom));
113  XMLElement sound("sound");
114  sound.addChild(XMLElement("volume", "9000"));
115  device.addChild(move(sound));
116  device.addChild(XMLElement(
117  "mappertype", mapper.empty() ? "auto" : mapper));
118  device.addChild(XMLElement("sramname", sramfile + ".SRAM"));
119 
120  secondary.addChild(move(device));
121  primary.addChild(move(secondary));
122  devices.addChild(move(primary));
123  extension.addChild(move(devices));
124 
125  result->setConfig(move(extension));
126  result->setName(romfile);
127  result->setFileContext(move(context));
128 
129  return result;
130 }
131 
132 HardwareConfig::HardwareConfig(MSXMotherBoard& motherBoard_, const string& hwName_)
133  : motherBoard(motherBoard_)
134  , hwName(hwName_)
135 {
136  for (auto ps : xrange(4)) {
137  for (auto ss : xrange(4)) {
138  externalSlots[ps][ss] = false;
139  }
140  externalPrimSlots[ps] = false;
141  expandedSlots[ps] = false;
142  allocatedPrimarySlots[ps] = false;
143  }
144  userName = motherBoard.getUserName(hwName);
145 }
146 
148 {
149  motherBoard.freeUserName(hwName, userName);
150 #ifndef NDEBUG
151  try {
152  testRemove();
153  } catch (MSXException& e) {
154  std::cerr << e.getMessage() << std::endl;
155  UNREACHABLE;
156  }
157 #endif
158  while (!devices.empty()) {
159  motherBoard.removeDevice(*devices.back());
160  devices.pop_back();
161  }
162  auto& slotManager = motherBoard.getSlotManager();
163  for (auto ps : xrange(4)) {
164  for (auto ss : xrange(4)) {
165  if (externalSlots[ps][ss]) {
166  slotManager.removeExternalSlot(ps, ss);
167  }
168  }
169  if (externalPrimSlots[ps]) {
170  slotManager.removeExternalSlot(ps);
171  }
172  if (expandedSlots[ps]) {
173  motherBoard.getCPUInterface().unsetExpanded(ps);
174  }
175  if (allocatedPrimarySlots[ps]) {
176  slotManager.freePrimarySlot(ps, *this);
177  }
178  }
179 }
180 
182 {
183  std::vector<MSXDevice*> alreadyRemoved;
184  for (auto it = devices.rbegin(); it != devices.rend(); ++it) {
185  (*it)->testRemove(alreadyRemoved);
186  alreadyRemoved.push_back(it->get());
187  }
188  auto& slotManager = motherBoard.getSlotManager();
189  for (auto ps : xrange(4)) {
190  for (auto ss : xrange(4)) {
191  if (externalSlots[ps][ss]) {
192  slotManager.testRemoveExternalSlot(ps, ss, *this);
193  }
194  }
195  if (externalPrimSlots[ps]) {
196  slotManager.testRemoveExternalSlot(ps, *this);
197  }
198  if (expandedSlots[ps]) {
199  motherBoard.getCPUInterface().testUnsetExpanded(
200  ps, alreadyRemoved);
201  }
202  }
203 }
204 
206 {
207  return *context;
208 }
209 void HardwareConfig::setFileContext(unique_ptr<FileContext> context_)
210 {
211  context = move(context_);
212 }
213 
214 const XMLElement& HardwareConfig::getDevices() const
215 {
216  return getConfig().getChild("devices");
217 }
218 
220 {
221  return loadConfig(getFilename(type, name));
222 }
223 
224 XMLElement HardwareConfig::loadConfig(const string& filename)
225 {
226  try {
227  LocalFileReference fileRef(filename);
228  return XMLLoader::load(fileRef.getFilename(), "msxconfig2.dtd");
229  } catch (XMLException& e) {
230  throw MSXException(
231  "Loading of hardware configuration failed: " +
232  e.getMessage());
233  }
234 }
235 
236 string HardwareConfig::getFilename(string_ref type, string_ref name)
237 {
238  SystemFileContext context;
239  try {
240  // try <name>.xml
241  return context.resolve(FileOperations::join(
242  type, name + ".xml"));
243  } catch (MSXException& e) {
244  // backwards-compatibility:
245  // also try <name>/hardwareconfig.xml
246  try {
247  return context.resolve(FileOperations::join(
248  type, name, "hardwareconfig.xml"));
249  } catch (MSXException&) {
250  throw e; // signal first error
251  }
252  }
253 }
254 
255 void HardwareConfig::load(string_ref type)
256 {
257  string filename = getFilename(type, hwName);
258  setConfig(loadConfig(filename));
259 
260  assert(!userName.empty());
261  const auto& baseName = FileOperations::getBaseName(filename);
262  setFileContext(make_unique<ConfigFileContext>(
263  baseName, hwName, userName));
264 }
265 
267 {
268  // TODO this code does parsing for both 'expanded' and 'external' slots
269  // once machine and extensions are parsed separately move parsing
270  // of 'expanded' to MSXCPUInterface
271  //
272  for (auto& psElem : getDevices().getChildren("primary")) {
273  const auto& primSlot = psElem->getAttribute("slot");
274  int ps = CartridgeSlotManager::getSlotNum(primSlot);
275  if (psElem->getAttributeAsBool("external", false)) {
276  if (ps < 0) {
277  throw MSXException(
278  "Cannot mark unspecified primary slot '" +
279  primSlot + "' as external");
280  }
281  createExternalSlot(ps);
282  continue;
283  }
284  for (auto& ssElem : psElem->getChildren("secondary")) {
285  const auto& secSlot = ssElem->getAttribute("slot");
286  int ss = CartridgeSlotManager::getSlotNum(secSlot);
287  if (ss < 0) {
288  if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
289  motherBoard.getCPUInterface().isExpanded(ps)) {
290  ss += 128;
291  } else {
292  continue;
293  }
294  }
295  if (ps < 0) {
296  ps = getFreePrimarySlot();
297  auto mutableElem = const_cast<XMLElement*>(psElem);
298  mutableElem->setAttribute("slot", StringOp::toString(ps));
299  }
300  createExpandedSlot(ps);
301  if (ssElem->getAttributeAsBool("external", false)) {
302  createExternalSlot(ps, ss);
303  }
304  }
305  }
306 }
307 
309 {
310  createDevices(getDevices(), nullptr, nullptr);
311 }
312 
314  const XMLElement* primary, const XMLElement* secondary)
315 {
316  for (auto& c : elem.getChildren()) {
317  const auto& name = c.getName();
318  if (name == "primary") {
319  createDevices(c, &c, secondary);
320  } else if (name == "secondary") {
321  createDevices(c, primary, &c);
322  } else {
323  auto device = DeviceFactory::create(
324  DeviceConfig(*this, c, primary, secondary));
325  if (device.get()) {
326  addDevice(move(device));
327  } else {
328  motherBoard.getMSXCliComm().printWarning(
329  "Deprecated device: \"" +
330  name + "\", please upgrade your "
331  "hardware descriptions.");
332  }
333  }
334  }
335 }
336 
337 void HardwareConfig::createExternalSlot(int ps)
338 {
339  motherBoard.getSlotManager().createExternalSlot(ps);
340  assert(!externalPrimSlots[ps]);
341  externalPrimSlots[ps] = true;
342 }
343 
344 void HardwareConfig::createExternalSlot(int ps, int ss)
345 {
346  motherBoard.getSlotManager().createExternalSlot(ps, ss);
347  assert(!externalSlots[ps][ss]);
348  externalSlots[ps][ss] = true;
349 }
350 
351 void HardwareConfig::createExpandedSlot(int ps)
352 {
353  if (!expandedSlots[ps]) {
354  motherBoard.getCPUInterface().setExpanded(ps);
355  expandedSlots[ps] = true;
356  }
357 }
358 
359 int HardwareConfig::getFreePrimarySlot()
360 {
361  int ps;
362  motherBoard.getSlotManager().allocatePrimarySlot(ps, *this);
363  assert(!allocatedPrimarySlots[ps]);
364  allocatedPrimarySlots[ps] = true;
365  return ps;
366 }
367 
368 void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
369 {
370  motherBoard.addDevice(*device);
371  devices.push_back(move(device));
372 }
373 
374 const string& HardwareConfig::getName() const
375 {
376  return name;
377 }
378 
379 void HardwareConfig::setName(const string& proposedName)
380 {
381  if (!motherBoard.findExtension(proposedName)) {
382  name = proposedName;
383  } else {
384  unsigned n = 0;
385  do {
386  name = StringOp::Builder() << proposedName << " (" << ++n << ')';
387  } while (motherBoard.findExtension(name));
388  }
389 }
390 
391 
392 // version 1: initial version
393 // version 2: moved FileContext here (was part of config)
394 // version 3: hold 'config' by-value instead of by-pointer
395 template<typename Archive>
396 void HardwareConfig::serialize(Archive& ar, unsigned version)
397 {
398  // filled-in by constructor:
399  // motherBoard, hwName, userName
400  // filled-in by parseSlots()
401  // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
402 
403  if (ar.versionBelow(version, 2)) {
404  XMLElement::getLastSerializedFileContext(); // clear any previous value
405  }
406  ar.serialize("config", config); // fills in getLastSerializedFileContext()
407  if (ar.versionAtLeast(version, 2)) {
408  ar.serialize("context", context);
409  } else {
411  assert(context.get());
412  }
413  if (ar.isLoader()) {
414  if (!motherBoard.getMachineConfig()) {
415  // must be done before parseSlots()
416  motherBoard.setMachineConfig(this);
417  } else {
418  // already set because this is an extension
419  }
420  parseSlots();
421  createDevices();
422  }
423  // only (polymorphically) initialize devices, they are already created
424  for (auto& d : devices) {
425  ar.serializePolymorphic("device", *d);
426  }
427  ar.serialize("name", name);
428 }
430 
431 } // namespace openmsx