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