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 
198 {
199  return *context;
200 }
201 void HardwareConfig::setFileContext(unique_ptr<FileContext> context_)
202 {
203  context = move(context_);
204 }
205 
206 const XMLElement& HardwareConfig::getDevices() const
207 {
208  return getConfig().getChild("devices");
209 }
210 
212 {
213  return loadConfig(getFilename(type, name));
214 }
215 
216 XMLElement HardwareConfig::loadConfig(const string& filename)
217 {
218  try {
219  LocalFileReference fileRef(filename);
220  return XMLLoader::load(fileRef.getFilename(), "msxconfig2.dtd");
221  } catch (XMLException& e) {
222  throw MSXException(
223  "Loading of hardware configuration failed: " +
224  e.getMessage());
225  }
226 }
227 
228 string HardwareConfig::getFilename(string_ref type, string_ref name)
229 {
230  SystemFileContext context;
231  try {
232  // try <name>.xml
233  return context.resolve(FileOperations::join(
234  type, name + ".xml"));
235  } catch (MSXException& e) {
236  // backwards-compatibility:
237  // also try <name>/hardwareconfig.xml
238  try {
239  return context.resolve(FileOperations::join(
240  type, name, "hardwareconfig.xml"));
241  } catch (MSXException&) {
242  throw e; // signal first error
243  }
244  }
245 }
246 
247 void HardwareConfig::load(string_ref type)
248 {
249  string filename = getFilename(type, hwName);
250  setConfig(loadConfig(filename));
251 
252  assert(!userName.empty());
253  const auto& baseName = FileOperations::getBaseName(filename);
254  setFileContext(make_unique<ConfigFileContext>(
255  baseName, hwName, userName));
256 }
257 
259 {
260  // TODO this code does parsing for both 'expanded' and 'external' slots
261  // once machine and extensions are parsed separately move parsing
262  // of 'expanded' to MSXCPUInterface
263  //
264  for (auto& psElem : getDevices().getChildren("primary")) {
265  const auto& primSlot = psElem->getAttribute("slot");
266  int ps = CartridgeSlotManager::getSlotNum(primSlot);
267  if (psElem->getAttributeAsBool("external", false)) {
268  if (ps < 0) {
269  throw MSXException(
270  "Cannot mark unspecified primary slot '" +
271  primSlot + "' as external");
272  }
273  createExternalSlot(ps);
274  continue;
275  }
276  for (auto& ssElem : psElem->getChildren("secondary")) {
277  const auto& secSlot = ssElem->getAttribute("slot");
278  int ss = CartridgeSlotManager::getSlotNum(secSlot);
279  if (ss < 0) {
280  if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
281  motherBoard.getCPUInterface().isExpanded(ps)) {
282  ss += 128;
283  } else {
284  continue;
285  }
286  }
287  if (ps < 0) {
288  ps = getFreePrimarySlot();
289  auto mutableElem = const_cast<XMLElement*>(psElem);
290  mutableElem->setAttribute("slot", StringOp::toString(ps));
291  }
292  createExpandedSlot(ps);
293  if (ssElem->getAttributeAsBool("external", false)) {
294  createExternalSlot(ps, ss);
295  }
296  }
297  }
298 }
299 
301 {
302  createDevices(getDevices(), nullptr, nullptr);
303 }
304 
306  const XMLElement* primary, const XMLElement* secondary)
307 {
308  for (auto& c : elem.getChildren()) {
309  const auto& name = c.getName();
310  if (name == "primary") {
311  createDevices(c, &c, secondary);
312  } else if (name == "secondary") {
313  createDevices(c, primary, &c);
314  } else {
315  auto device = DeviceFactory::create(
316  DeviceConfig(*this, c, primary, secondary));
317  if (device) {
318  addDevice(move(device));
319  } else {
320  motherBoard.getMSXCliComm().printWarning(
321  "Deprecated device: \"" +
322  name + "\", please upgrade your "
323  "hardware descriptions.");
324  }
325  }
326  }
327 }
328 
329 void HardwareConfig::createExternalSlot(int ps)
330 {
331  motherBoard.getSlotManager().createExternalSlot(ps);
332  assert(!externalPrimSlots[ps]);
333  externalPrimSlots[ps] = true;
334 }
335 
336 void HardwareConfig::createExternalSlot(int ps, int ss)
337 {
338  motherBoard.getSlotManager().createExternalSlot(ps, ss);
339  assert(!externalSlots[ps][ss]);
340  externalSlots[ps][ss] = true;
341 }
342 
343 void HardwareConfig::createExpandedSlot(int ps)
344 {
345  if (!expandedSlots[ps]) {
346  motherBoard.getCPUInterface().setExpanded(ps);
347  expandedSlots[ps] = true;
348  }
349 }
350 
351 int HardwareConfig::getFreePrimarySlot()
352 {
353  int ps;
354  motherBoard.getSlotManager().allocatePrimarySlot(ps, *this);
355  assert(!allocatedPrimarySlots[ps]);
356  allocatedPrimarySlots[ps] = true;
357  return ps;
358 }
359 
360 void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
361 {
362  motherBoard.addDevice(*device);
363  devices.push_back(move(device));
364 }
365 
366 void HardwareConfig::setName(string_ref proposedName)
367 {
368  if (!motherBoard.findExtension(proposedName)) {
369  name = proposedName.str();
370  } else {
371  unsigned n = 0;
372  do {
373  name = StringOp::Builder() << proposedName << " (" << ++n << ')';
374  } while (motherBoard.findExtension(name));
375  }
376 }
377 
378 void HardwareConfig::setSlot(string_ref slotname)
379 {
380  for (auto& psElem : getDevices().getChildren("primary")) {
381  const auto& primSlot = psElem->getAttribute("slot");
382  if (primSlot == "any") {
383  auto& mutableElem = const_cast<XMLElement*&>(psElem);
384  mutableElem->setAttribute("slot", slotname);
385  }
386  }
387 }
388 
389 // version 1: initial version
390 // version 2: moved FileContext here (was part of config)
391 // version 3: hold 'config' by-value instead of by-pointer
392 template<typename Archive>
393 void HardwareConfig::serialize(Archive& ar, unsigned version)
394 {
395  // filled-in by constructor:
396  // motherBoard, hwName, userName
397  // filled-in by parseSlots()
398  // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
399 
400  if (ar.versionBelow(version, 2)) {
401  XMLElement::getLastSerializedFileContext(); // clear any previous value
402  }
403  ar.serialize("config", config); // fills in getLastSerializedFileContext()
404  if (ar.versionAtLeast(version, 2)) {
405  ar.serialize("context", context);
406  } else {
408  assert(context);
409  }
410  if (ar.isLoader()) {
411  if (!motherBoard.getMachineConfig()) {
412  // must be done before parseSlots()
413  motherBoard.setMachineConfig(this);
414  } else {
415  // already set because this is an extension
416  }
417  parseSlots();
418  createDevices();
419  }
420  // only (polymorphically) initialize devices, they are already created
421  for (auto& d : devices) {
422  ar.serializePolymorphic("device", *d);
423  }
424  ar.serialize("name", name);
425 }
427 
428 } // namespace openmsx
const FileContext & getFileContext() const
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.
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.
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