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