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  //auto et = devices.end();
165  auto* et = devices.data() + devices.size();
166  for (auto rit = devices.rbegin(), ret = devices.rend();
167  rit != ret; ++rit) {
168  // c++20: std::span alreadyRemoved{rit.base(), et};
169  span<const std::unique_ptr<MSXDevice>> alreadyRemoved{devices.data() + (rit.base() - devices.begin()), et};
170  (*rit)->testRemove(alreadyRemoved);
171  }
172 
173  auto& slotManager = motherBoard.getSlotManager();
174  for (auto ps : xrange(4)) {
175  for (auto ss : xrange(4)) {
176  if (externalSlots[ps][ss]) {
177  slotManager.testRemoveExternalSlot(ps, ss, *this);
178  }
179  }
180  if (externalPrimSlots[ps]) {
181  slotManager.testRemoveExternalSlot(ps, *this);
182  }
183  if (expandedSlots[ps]) {
184  motherBoard.getCPUInterface().testUnsetExpanded(
185  ps, devices);
186  }
187  }
188 }
189 
190 const XMLElement& HardwareConfig::getDevicesElem() const
191 {
192  return getConfig().getChild("devices");
193 }
194 
195 static XMLElement loadHelper(const string& filename)
196 {
197  try {
198  return XMLLoader::load(filename, "msxconfig2.dtd");
199  } catch (XMLException& e) {
200  throw MSXException(
201  "Loading of hardware configuration failed: ",
202  e.getMessage());
203  }
204 }
205 
206 static string getFilename(string_view type, string_view name)
207 {
208  auto context = systemFileContext();
209  try {
210  // try <name>.xml
211  return context.resolve(tmpStrCat(
212  type, '/', name, ".xml"));
213  } catch (MSXException& e) {
214  // backwards-compatibility:
215  // also try <name>/hardwareconfig.xml
216  try {
217  return context.resolve(tmpStrCat(
218  type, '/', name, "/hardwareconfig.xml"));
219  } catch (MSXException&) {
220  throw e; // signal first error
221  }
222  }
223 }
224 
225 XMLElement HardwareConfig::loadConfig(string_view type_, string_view name)
226 {
227  return loadHelper(getFilename(type_, name));
228 }
229 
230 void HardwareConfig::load(string_view type_)
231 {
232  string filename = getFilename(type_, hwName);
233  setConfig(loadHelper(filename));
234 
235  assert(!userName.empty());
236  const auto& dirname = FileOperations::getDirName(filename);
237  setFileContext(configFileContext(dirname, hwName, userName));
238 }
239 
241 {
242  // TODO this code does parsing for both 'expanded' and 'external' slots
243  // once machine and extensions are parsed separately move parsing
244  // of 'expanded' to MSXCPUInterface
245  //
246  for (auto& psElem : getDevicesElem().getChildren("primary")) {
247  const auto& primSlot = psElem->getAttribute("slot");
248  int ps = CartridgeSlotManager::getSlotNum(primSlot);
249  if (psElem->getAttributeAsBool("external", false)) {
250  if (ps < 0) {
251  throw MSXException(
252  "Cannot mark unspecified primary slot '",
253  primSlot, "' as external");
254  }
255  if (psElem->hasChildren()) {
256  throw MSXException(
257  "Primary slot ", ps,
258  " is marked as external, but that would only "
259  "make sense if its <primary> tag would be "
260  "empty.");
261  }
262  createExternalSlot(ps);
263  continue;
264  }
265  for (auto& ssElem : psElem->getChildren("secondary")) {
266  const auto& secSlot = ssElem->getAttribute("slot");
267  int ss = CartridgeSlotManager::getSlotNum(secSlot);
268  if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
269  throw MSXException(
270  "Invalid secondary slot specification: \"",
271  secSlot, "\".");
272  }
273  if (ss < 0) {
274  if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
275  motherBoard.getCPUInterface().isExpanded(ps)) {
276  ss += 128;
277  } else {
278  continue;
279  }
280  }
281  if (ps < 0) {
282  if (ps == -256) {
283  ps = getAnyFreePrimarySlot();
284  } else {
285  ps = getSpecificFreePrimarySlot(-ps - 1);
286  }
287  auto* mutableElem = const_cast<XMLElement*>(psElem);
288  mutableElem->setAttribute("slot", strCat(ps));
289  }
290  createExpandedSlot(ps);
291  if (ssElem->getAttributeAsBool("external", false)) {
292  if (ssElem->hasChildren()) {
293  throw MSXException(
294  "Secondary slot ", ps, '-', ss,
295  " is marked as external, but that would "
296  "only make sense if its <secondary> tag "
297  "would be empty.");
298  }
299  createExternalSlot(ps, ss);
300  }
301  }
302  }
303 }
304 
306 {
307  byte initialPrimarySlots = 0;
308  if (const auto* slotmap = getConfig().findChild("slotmap")) {
309  for (const auto* child : slotmap->getChildren("map")) {
310  int page = child->getAttributeAsInt("page", -1);
311  if (page < 0 || page > 3) {
312  throw MSXException("Invalid or missing page in slotmap entry");
313  }
314  int slot = child->getAttributeAsInt("slot", -1);
315  if (slot < 0 || slot > 3) {
316  throw MSXException("Invalid or missing slot in slotmap entry");
317  }
318  unsigned offset = page * 2;
319  initialPrimarySlots &= ~(3 << offset);
320  initialPrimarySlots |= slot << offset;
321  }
322  }
323  return initialPrimarySlots;
324 }
325 
327 {
328  createDevices(getDevicesElem(), nullptr, nullptr);
329 }
330 
332  const XMLElement* primary, const XMLElement* secondary)
333 {
334  for (const auto& c : elem.getChildren()) {
335  const auto& childName = c.getName();
336  if (childName == "primary") {
337  createDevices(c, &c, secondary);
338  } else if (childName == "secondary") {
339  createDevices(c, primary, &c);
340  } else {
341  auto device = DeviceFactory::create(
342  DeviceConfig(*this, c, primary, secondary));
343  if (device) {
344  addDevice(move(device));
345  } else {
346  // device is nullptr, so we are apparently ignoring it on purpose
347  }
348  }
349  }
350 }
351 
352 void HardwareConfig::createExternalSlot(int ps)
353 {
354  motherBoard.getSlotManager().createExternalSlot(ps);
355  assert(!externalPrimSlots[ps]);
356  externalPrimSlots[ps] = true;
357 }
358 
359 void HardwareConfig::createExternalSlot(int ps, int ss)
360 {
361  motherBoard.getSlotManager().createExternalSlot(ps, ss);
362  assert(!externalSlots[ps][ss]);
363  externalSlots[ps][ss] = true;
364 }
365 
366 void HardwareConfig::createExpandedSlot(int ps)
367 {
368  if (!expandedSlots[ps]) {
369  motherBoard.getCPUInterface().setExpanded(ps);
370  expandedSlots[ps] = true;
371  }
372 }
373 
374 int HardwareConfig::getAnyFreePrimarySlot()
375 {
376  int ps = motherBoard.getSlotManager().allocateAnyPrimarySlot(*this);
377  assert(!allocatedPrimarySlots[ps]);
378  allocatedPrimarySlots[ps] = true;
379  return ps;
380 }
381 
382 int HardwareConfig::getSpecificFreePrimarySlot(unsigned slot)
383 {
384  int ps = motherBoard.getSlotManager().allocateSpecificPrimarySlot(slot, *this);
385  assert(!allocatedPrimarySlots[ps]);
386  allocatedPrimarySlots[ps] = true;
387  return ps;
388 }
389 
390 void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
391 {
392  motherBoard.addDevice(*device);
393  devices.push_back(move(device));
394 }
395 
396 void HardwareConfig::setName(string_view proposedName)
397 {
398  if (!motherBoard.findExtension(proposedName)) {
399  name = proposedName;
400  } else {
401  unsigned n = 0;
402  do {
403  name = strCat(proposedName, " (", ++n, ')');
404  } while (motherBoard.findExtension(name));
405  }
406 }
407 
408 void HardwareConfig::setSlot(std::string_view slotname)
409 {
410  for (auto& psElem : getDevicesElem().getChildren("primary")) {
411  const auto& primSlot = psElem->getAttribute("slot");
412  if (primSlot == "any") {
413  auto& mutableElem = const_cast<XMLElement*&>(psElem);
414  mutableElem->setAttribute("slot", slotname);
415  }
416  }
417 }
418 
419 static constexpr std::initializer_list<enum_string<HardwareConfig::Type>> configTypeInfo = {
420  { "MACHINE", HardwareConfig::Type::MACHINE },
421  { "EXTENSION", HardwareConfig::Type::EXTENSION },
422  { "ROM", HardwareConfig::Type::ROM },
423 };
425 
426 // version 1: initial version
427 // version 2: moved FileContext here (was part of config)
428 // version 3: hold 'config' by-value instead of by-pointer
429 // version 4: hold 'context' by-value instead of by-pointer
430 // version 5: added hwconfig type info
431 template<typename Archive>
432 void HardwareConfig::serialize(Archive& ar, unsigned version)
433 {
434  // filled-in by constructor:
435  // motherBoard, hwName, userName
436  // filled-in by parseSlots()
437  // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
438 
439  if (ar.versionBelow(version, 2)) {
440  XMLElement::getLastSerializedFileContext(); // clear any previous value
441  }
442  ar.serialize("config", config); // fills in getLastSerializedFileContext()
443  if (ar.versionAtLeast(version, 2)) {
444  if (ar.versionAtLeast(version, 4)) {
445  ar.serialize("context", context);
446  } else {
447  std::unique_ptr<FileContext> ctxt;
448  ar.serialize("context", ctxt);
449  if (ctxt) context = *ctxt;
450  }
451  } else {
453  assert(ctxt);
454  context = *ctxt;
455  }
456  if constexpr (Archive::IS_LOADER) {
457  if (!motherBoard.getMachineConfig()) {
458  // must be done before parseSlots()
459  motherBoard.setMachineConfig(this);
460  } else {
461  // already set because this is an extension
462  }
463  parseSlots();
464  createDevices();
465  }
466  // only (polymorphically) initialize devices, they are already created
467  for (auto& d : devices) {
468  ar.serializePolymorphic("device", *d);
469  }
470  ar.serialize("name", name);
471  if (ar.versionAtLeast(version, 5)) {
472  ar.serialize("type", type);
473  } else {
474  assert(Archive::IS_LOADER);
476  }
477 }
479 
480 } // 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, span< const std::unique_ptr< 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
#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