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