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 "FileOperations.hh"
7 #include "MSXMotherBoard.hh"
9 #include "MSXCPUInterface.hh"
10 #include "CommandController.hh"
11 #include "DeviceFactory.hh"
12 #include "TclArgParser.hh"
13 #include "serialize.hh"
14 #include "serialize_stl.hh"
15 #include "unreachable.hh"
16 #include "view.hh"
17 #include "xrange.hh"
18 #include <cassert>
19 #include <iostream>
20 #include <memory>
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 = std::make_unique<HardwareConfig>(motherBoard, machineName);
33  result->load("machines");
34  return result;
35 }
36 
37 unique_ptr<HardwareConfig> HardwareConfig::createExtensionConfig(
38  MSXMotherBoard& motherBoard, string_view extensionName, string_view slotname)
39 {
40  auto result = std::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_view romfile,
49  string_view slotname, span<const TclObject> options)
50 {
51  vector<string_view> ipsfiles;
52  string_view mapper;
53  ArgsInfo info[] = {
54  valueArg("-ips", ipsfiles),
55  valueArg("-romtype", mapper),
56  };
57  auto& interp = motherBoard.getCommandController().getInterpreter();
58  auto args = parseTclArgs(interp, options, info);
59  if (!args.empty()) {
60  throw MSXException("Invalid option \"", args.front().getString(), '\"');
61  }
62 
63  const auto& sramfile = FileOperations::getFilename(romfile);
64  auto context = userFileContext(strCat("roms/", sramfile));
65  for (const auto& ips : ipsfiles) {
66  if (!FileOperations::isRegularFile(context.resolve(ips))) {
67  throw MSXException("Invalid IPS file: ", ips);
68  }
69  }
70 
71  string resolvedFilename = FileOperations::getAbsolutePath(
72  context.resolve(romfile));
73  if (!FileOperations::isRegularFile(resolvedFilename)) {
74  throw MSXException("Invalid ROM file: ", resolvedFilename);
75  }
76 
77  XMLElement extension("extension");
78  auto& devices = extension.addChild("devices");
79  auto& primary = devices.addChild("primary");
80  primary.addAttribute("slot", slotname);
81  auto& secondary = primary.addChild("secondary");
82  secondary.addAttribute("slot", slotname);
83  auto& device = secondary.addChild("ROM");
84  device.addAttribute("id", "MSXRom");
85  auto& mem = device.addChild("mem");
86  mem.addAttribute("base", "0x0000");
87  mem.addAttribute("size", "0x10000");
88  auto& rom = device.addChild("rom");
89  rom.addChild("resolvedFilename", resolvedFilename);
90  rom.addChild("filename", romfile);
91  if (!ipsfiles.empty()) {
92  auto& patches = rom.addChild("patches");
93  for (auto& s : ipsfiles) {
94  patches.addChild("ips", s);
95  }
96  }
97  device.addChild("sound").addChild("volume", "9000");
98  device.addChild("mappertype", mapper.empty() ? "auto" : mapper);
99  device.addChild("sramname", strCat(sramfile, ".SRAM"));
100 
101  auto result = std::make_unique<HardwareConfig>(motherBoard, "rom");
102  result->setConfig(move(extension));
103  result->setName(romfile);
104  result->setFileContext(move(context));
105  return result;
106 }
107 
108 HardwareConfig::HardwareConfig(MSXMotherBoard& motherBoard_, string hwName_)
109  : motherBoard(motherBoard_)
110  , hwName(std::move(hwName_))
111 {
112  for (auto ps : xrange(4)) {
113  for (auto ss : xrange(4)) {
114  externalSlots[ps][ss] = false;
115  }
116  externalPrimSlots[ps] = false;
117  expandedSlots[ps] = false;
118  allocatedPrimarySlots[ps] = false;
119  }
120  userName = motherBoard.getUserName(hwName);
121 }
122 
124 {
125  motherBoard.freeUserName(hwName, userName);
126 #ifndef NDEBUG
127  try {
128  testRemove();
129  } catch (MSXException& e) {
130  std::cerr << e.getMessage() << '\n';
131  UNREACHABLE;
132  }
133 #endif
134  while (!devices.empty()) {
135  motherBoard.removeDevice(*devices.back());
136  devices.pop_back();
137  }
138  auto& slotManager = motherBoard.getSlotManager();
139  for (auto ps : xrange(4)) {
140  for (auto ss : xrange(4)) {
141  if (externalSlots[ps][ss]) {
142  slotManager.removeExternalSlot(ps, ss);
143  }
144  }
145  if (externalPrimSlots[ps]) {
146  slotManager.removeExternalSlot(ps);
147  }
148  if (expandedSlots[ps]) {
149  motherBoard.getCPUInterface().unsetExpanded(ps);
150  }
151  if (allocatedPrimarySlots[ps]) {
152  slotManager.freePrimarySlot(ps, *this);
153  }
154  }
155 }
156 
158 {
159  std::vector<MSXDevice*> alreadyRemoved;
160  for (auto& dev : view::reverse(devices)) {
161  dev->testRemove(alreadyRemoved);
162  alreadyRemoved.push_back(dev.get());
163  }
164  auto& slotManager = motherBoard.getSlotManager();
165  for (auto ps : xrange(4)) {
166  for (auto ss : xrange(4)) {
167  if (externalSlots[ps][ss]) {
168  slotManager.testRemoveExternalSlot(ps, ss, *this);
169  }
170  }
171  if (externalPrimSlots[ps]) {
172  slotManager.testRemoveExternalSlot(ps, *this);
173  }
174  if (expandedSlots[ps]) {
175  motherBoard.getCPUInterface().testUnsetExpanded(
176  ps, alreadyRemoved);
177  }
178  }
179 }
180 
181 const XMLElement& HardwareConfig::getDevices() const
182 {
183  return getConfig().getChild("devices");
184 }
185 
187 {
188  return loadConfig(getFilename(type, name));
189 }
190 
191 XMLElement HardwareConfig::loadConfig(const string& filename)
192 {
193  try {
194  return XMLLoader::load(filename, "msxconfig2.dtd");
195  } catch (XMLException& e) {
196  throw MSXException(
197  "Loading of hardware configuration failed: ",
198  e.getMessage());
199  }
200 }
201 
202 string HardwareConfig::getFilename(string_view type, string_view name)
203 {
204  auto context = systemFileContext();
205  try {
206  // try <name>.xml
207  return context.resolve(FileOperations::join(
208  type, strCat(name, ".xml")));
209  } catch (MSXException& e) {
210  // backwards-compatibility:
211  // also try <name>/hardwareconfig.xml
212  try {
213  return context.resolve(FileOperations::join(
214  type, name, "hardwareconfig.xml"));
215  } catch (MSXException&) {
216  throw e; // signal first error
217  }
218  }
219 }
220 
221 void HardwareConfig::load(string_view type)
222 {
223  string filename = getFilename(type, hwName);
224  setConfig(loadConfig(filename));
225 
226  assert(!userName.empty());
227  const auto& dirname = FileOperations::getDirName(filename);
228  setFileContext(configFileContext(dirname, hwName, userName));
229 }
230 
232 {
233  // TODO this code does parsing for both 'expanded' and 'external' slots
234  // once machine and extensions are parsed separately move parsing
235  // of 'expanded' to MSXCPUInterface
236  //
237  for (auto& psElem : getDevices().getChildren("primary")) {
238  const auto& primSlot = psElem->getAttribute("slot");
239  int ps = CartridgeSlotManager::getSlotNum(primSlot);
240  if (psElem->getAttributeAsBool("external", false)) {
241  if (ps < 0) {
242  throw MSXException(
243  "Cannot mark unspecified primary slot '",
244  primSlot, "' as external");
245  }
246  if (psElem->hasChildren()) {
247  throw MSXException(
248  "Primary slot ", ps,
249  " is marked as external, but that would only "
250  "make sense if its <primary> tag would be "
251  "empty.");
252  }
253  createExternalSlot(ps);
254  continue;
255  }
256  for (auto& ssElem : psElem->getChildren("secondary")) {
257  const auto& secSlot = ssElem->getAttribute("slot");
258  int ss = CartridgeSlotManager::getSlotNum(secSlot);
259  if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
260  throw MSXException(
261  "Invalid secondary slot specification: \"",
262  secSlot, "\".");
263  }
264  if (ss < 0) {
265  if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
266  motherBoard.getCPUInterface().isExpanded(ps)) {
267  ss += 128;
268  } else {
269  continue;
270  }
271  }
272  if (ps < 0) {
273  if (ps == -256) {
274  ps = getAnyFreePrimarySlot();
275  } else {
276  ps = getSpecificFreePrimarySlot(-ps - 1);
277  }
278  auto mutableElem = const_cast<XMLElement*>(psElem);
279  mutableElem->setAttribute("slot", strCat(ps));
280  }
281  createExpandedSlot(ps);
282  if (ssElem->getAttributeAsBool("external", false)) {
283  if (ssElem->hasChildren()) {
284  throw MSXException(
285  "Secondary slot ", ps, '-', ss,
286  " is marked as external, but that would "
287  "only make sense if its <secondary> tag "
288  "would be empty.");
289  }
290  createExternalSlot(ps, ss);
291  }
292  }
293  }
294 }
295 
297 {
298  byte initialPrimarySlots = 0;
299  if (auto* slotmap = getConfig().findChild("slotmap")) {
300  for (auto* child : slotmap->getChildren("map")) {
301  int page = child->getAttributeAsInt("page", -1);
302  if (page < 0 || page > 3) {
303  throw MSXException("Invalid or missing page in slotmap entry");
304  }
305  int slot = child->getAttributeAsInt("slot", -1);
306  if (slot < 0 || slot > 3) {
307  throw MSXException("Invalid or missing slot in slotmap entry");
308  }
309  unsigned offset = page * 2;
310  initialPrimarySlots &= ~(3 << offset);
311  initialPrimarySlots |= slot << offset;
312  }
313  }
314  return initialPrimarySlots;
315 }
316 
318 {
319  createDevices(getDevices(), nullptr, nullptr);
320 }
321 
323  const XMLElement* primary, const XMLElement* secondary)
324 {
325  for (auto& c : elem.getChildren()) {
326  const auto& childName = c.getName();
327  if (childName == "primary") {
328  createDevices(c, &c, secondary);
329  } else if (childName == "secondary") {
330  createDevices(c, primary, &c);
331  } else {
332  auto device = DeviceFactory::create(
333  DeviceConfig(*this, c, primary, secondary));
334  if (device) {
335  addDevice(move(device));
336  } else {
337  // device is nullptr, so we are apparently ignoring it on purpose
338  }
339  }
340  }
341 }
342 
343 void HardwareConfig::createExternalSlot(int ps)
344 {
345  motherBoard.getSlotManager().createExternalSlot(ps);
346  assert(!externalPrimSlots[ps]);
347  externalPrimSlots[ps] = true;
348 }
349 
350 void HardwareConfig::createExternalSlot(int ps, int ss)
351 {
352  motherBoard.getSlotManager().createExternalSlot(ps, ss);
353  assert(!externalSlots[ps][ss]);
354  externalSlots[ps][ss] = true;
355 }
356 
357 void HardwareConfig::createExpandedSlot(int ps)
358 {
359  if (!expandedSlots[ps]) {
360  motherBoard.getCPUInterface().setExpanded(ps);
361  expandedSlots[ps] = true;
362  }
363 }
364 
365 int HardwareConfig::getAnyFreePrimarySlot()
366 {
367  int ps = motherBoard.getSlotManager().allocateAnyPrimarySlot(*this);
368  assert(!allocatedPrimarySlots[ps]);
369  allocatedPrimarySlots[ps] = true;
370  return ps;
371 }
372 
373 int HardwareConfig::getSpecificFreePrimarySlot(unsigned slot)
374 {
375  int ps = motherBoard.getSlotManager().allocateSpecificPrimarySlot(slot, *this);
376  assert(!allocatedPrimarySlots[ps]);
377  allocatedPrimarySlots[ps] = true;
378  return ps;
379 }
380 
381 void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
382 {
383  motherBoard.addDevice(*device);
384  devices.push_back(move(device));
385 }
386 
387 void HardwareConfig::setName(string_view proposedName)
388 {
389  if (!motherBoard.findExtension(proposedName)) {
390  name = proposedName.str();
391  } else {
392  unsigned n = 0;
393  do {
394  name = strCat(proposedName, " (", ++n, ')');
395  } while (motherBoard.findExtension(name));
396  }
397 }
398 
399 void HardwareConfig::setSlot(string_view slotname)
400 {
401  for (auto& psElem : getDevices().getChildren("primary")) {
402  const auto& primSlot = psElem->getAttribute("slot");
403  if (primSlot == "any") {
404  auto& mutableElem = const_cast<XMLElement*&>(psElem);
405  mutableElem->setAttribute("slot", slotname);
406  }
407  }
408 }
409 
410 // version 1: initial version
411 // version 2: moved FileContext here (was part of config)
412 // version 3: hold 'config' by-value instead of by-pointer
413 // version 4: hold 'context' by-value instead of by-pointer
414 template<typename Archive>
415 void HardwareConfig::serialize(Archive& ar, unsigned version)
416 {
417  // filled-in by constructor:
418  // motherBoard, hwName, userName
419  // filled-in by parseSlots()
420  // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
421 
422  if (ar.versionBelow(version, 2)) {
423  XMLElement::getLastSerializedFileContext(); // clear any previous value
424  }
425  ar.serialize("config", config); // fills in getLastSerializedFileContext()
426  if (ar.versionAtLeast(version, 2)) {
427  if (ar.versionAtLeast(version, 4)) {
428  ar.serialize("context", context);
429  } else {
430  std::unique_ptr<FileContext> ctxt;
431  ar.serialize("context", ctxt);
432  if (ctxt) context = *ctxt;
433  }
434  } else {
436  assert(ctxt);
437  context = *ctxt;
438  }
439  if (ar.isLoader()) {
440  if (!motherBoard.getMachineConfig()) {
441  // must be done before parseSlots()
442  motherBoard.setMachineConfig(this);
443  } else {
444  // already set because this is an extension
445  }
446  parseSlots();
447  createDevices();
448  }
449  // only (polymorphically) initialize devices, they are already created
450  for (auto& d : devices) {
451  ar.serializePolymorphic("device", *d);
452  }
453  ar.serialize("name", name);
454 }
456 
457 } // namespace openmsx
string_view getFilename(string_view path)
Returns the file portion of a path name.
void serialize(Archive &ar, unsigned version)
bool isRegularFile(const Stat &st)
string join(string_view part1, string_view part2)
Join two paths.
FileContext configFileContext(string_view path, string_view hwDescr, string_view userName)
Definition: FileContext.cc:141
int allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig &hwConfig)
static XMLElement loadConfig(string_view type, string_view name)
const std::string & getMessage() const &
Definition: MSXException.hh:23
auto xrange(T e)
Definition: xrange.hh:170
byte parseSlotMap() const
Parses a slot mapping.
MSXCPUInterface & getCPUInterface()
FileContext systemFileContext()
Definition: FileContext.cc:148
Definition: span.hh:34
static std::unique_ptr< HardwareConfig > createExtensionConfig(MSXMotherBoard &motherBoard, string_view extensionName, string_view slotname)
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
void removeDevice(MSXDevice &device)
XMLElement & addChild(string_view name)
Definition: XMLElement.cc:30
STL namespace.
void freeUserName(const std::string &hwName, const std::string &userName)
void testUnsetExpanded(int ps, std::vector< MSXDevice *> allowed) const
void setAttribute(string_view name, string_view value)
Definition: XMLElement.cc:64
static std::unique_ptr< MSXDevice > create(const DeviceConfig &conf)
std::string resolve(string_view filename) const
Definition: FileContext.cc:76
ArgsInfo valueArg(string_view name, T &value)
Definition: TclArgParser.hh:85
static std::unique_ptr< HardwareConfig > createMachineConfig(MSXMotherBoard &motherBoard, const std::string &machineName)
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, string_view romfile, string_view slotname, span< const TclObject > options)
void testRemove() const
Checks whether this HardwareConfig can be deleted.
HardwareConfig * findExtension(string_view extensionName)
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:160
void testRemoveExternalSlot(int ps, const HardwareConfig &allowed) const
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:339
auto reverse(Range &&range)
Definition: view.hh:306
const HardwareConfig * getMachineConfig() const
void setMachineConfig(HardwareConfig *machineConfig)
virtual Interpreter & getInterpreter()=0
static int getSlotNum(string_view slot)
HardwareConfig(const HardwareConfig &)=delete
CartridgeSlotManager & getSlotManager()
const XMLElement & getConfig() const
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
XMLElement load(string_view filename, string_view systemID)
Definition: XMLLoader.cc:31
CommandController & getCommandController()
std::string getUserName(const std::string &hwName)
Keep track of which &#39;usernames&#39; are in use.
string getAbsolutePath(string_view path)
Transform given path into an absolute path.
bool empty() const
Definition: string_view.hh:45
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
std::string str() const
Definition: string_view.cc:12
bool isExpanded(int ps) const
void setFileContext(FileContext &&ctxt)
int allocateAnyPrimarySlot(const HardwareConfig &hwConfig)
string_view getDirName(string_view path)
Returns the directory portion of a path.
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
void addDevice(MSXDevice &device)
All MSXDevices should be registered by the MotherBoard.
const Children & getChildren() const
Definition: XMLElement.hh:47
const XMLElement & getChild(string_view name) const
Definition: XMLElement.cc:162
std::vector< TclObject > parseTclArgs(Interpreter &interp, span< const TclObject > inArgs, span< const ArgsInfo > table)
#define UNREACHABLE
Definition: unreachable.hh:38