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