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