openMSX
HardwareConfig.cc
Go to the documentation of this file.
1#include "HardwareConfig.hh"
2
5#include "DeviceConfig.hh"
6#include "DeviceFactory.hh"
7#include "FileOperations.hh"
8#include "MSXCPUInterface.hh"
9#include "MSXMotherBoard.hh"
10#include "TclArgParser.hh"
11#include "XMLException.hh"
12#include "serialize.hh"
13#include "serialize_stl.hh"
14
15#include "unreachable.hh"
16#include "xrange.hh"
17
18#include <array>
19#include <cassert>
20#include <iostream>
21#include <memory>
22#include <version> // for _LIBCPP_VERSION
23
24namespace openmsx {
25
26std::unique_ptr<HardwareConfig> HardwareConfig::createMachineConfig(
27 MSXMotherBoard& motherBoard, std::string machineName)
28{
29 auto result = std::make_unique<HardwareConfig>(
30 motherBoard, std::move(machineName));
31 result->type = HardwareConfig::Type::MACHINE;
32 result->load("machines");
33 return result;
34}
35
36std::unique_ptr<HardwareConfig> HardwareConfig::createExtensionConfig(
37 MSXMotherBoard& motherBoard, std::string extensionName, std::string_view slotName)
38{
39 auto result = std::make_unique<HardwareConfig>(
40 motherBoard, std::move(extensionName));
41 result->load("extensions");
42 result->setName(result->hwName);
44 result->setSlot(slotName);
45 return result;
46}
47
48std::unique_ptr<HardwareConfig> HardwareConfig::createRomConfig(
49 MSXMotherBoard& motherBoard, std::string_view romFile,
50 std::string_view slotName, std::span<const TclObject> options)
51{
52 auto result = std::make_unique<HardwareConfig>(motherBoard, "rom");
53 result->setName(romFile);
54 result->type = HardwareConfig::Type::ROM;
55
56 std::vector<std::string_view> ipsFiles;
57 std::string mapper;
58 std::array info = {
59 valueArg("-ips", ipsFiles),
60 valueArg("-romtype", mapper),
61 };
62 auto& interp = motherBoard.getCommandController().getInterpreter();
63 if (auto args = parseTclArgs(interp, options, info);
64 !args.empty()) {
65 throw MSXException("Invalid option \"", args.front().getString(), '\"');
66 }
67
68 const auto& sramfile = FileOperations::getFilename(romFile);
69 auto context = userFileContext(tmpStrCat("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 std::string resolvedFilename = FileOperations::getAbsolutePath(
77 context.resolve(romFile));
78 if (!FileOperations::isRegularFile(resolvedFilename)) {
79 throw MSXException("Invalid ROM file: ", resolvedFilename);
80 }
81
82 //<extension>
83 // <devices>
84 // <primary slot="...">
85 // <secondary slot="...">
86 // <ROM id="MSXRom">
87 // <mem base="0x0000" size="0x10000"/>
88 // <sound>
89 // <volume>9000</volume>
90 // </sound>
91 // <mappertype>...</mappertype>
92 // <sramname>...SRAM</sramname>
93 // <rom>
94 // <resolvedFilename>...</resolvedFilename>
95 // <filename>...</filename>
96 // <patches>
97 // <ips>...</ips>
98 // ...
99 // </patches>
100 // </rom>
101 // </ROM>
102 // </secondary>
103 // </primary>
104 // </devices>
105 //</extension>
106 XMLDocument& doc = result->config;
107 auto* extension = doc.allocateElement("extension");
108 auto* devices = extension->setFirstChild(doc.allocateElement("devices"));
109 auto* primary = devices->setFirstChild(doc.allocateElement("primary"));
110 const char* slotName2 = doc.allocateString(slotName);
111 primary->setFirstAttribute(doc.allocateAttribute("slot", slotName2));
112 auto* secondary = primary->setFirstChild(doc.allocateElement("secondary"));
113 secondary->setFirstAttribute(doc.allocateAttribute("slot", slotName2));
114 auto* device = secondary->setFirstChild(doc.allocateElement("ROM"));
115 device->setFirstAttribute(doc.allocateAttribute("id", "MSXRom"));
116 auto* mem = device->setFirstChild(doc.allocateElement("mem"));
117 mem->setFirstAttribute(doc.allocateAttribute("base", "0x0000"))
118 ->setNextAttribute (doc.allocateAttribute("size", "0x10000"));
119 auto* sound = mem->setNextSibling(doc.allocateElement("sound"));
120 sound->setFirstChild(doc.allocateElement("volume", "9000"));
121 auto* mapperType = sound->setNextSibling(doc.allocateElement("mappertype"));
122 mapperType->setData(mapper.empty() ? "auto" : doc.allocateString(mapper));
123 auto* sramName = mapperType->setNextSibling(doc.allocateElement("sramname"));
124 sramName->setData(doc.allocateString(tmpStrCat(sramfile, ".SRAM")));
125 auto* rom = sramName->setNextSibling(doc.allocateElement("rom"));
126 auto* rfName = rom->setFirstChild(doc.allocateElement("resolvedFilename"));
127 rfName->setData(doc.allocateString(resolvedFilename));
128 auto* fName = rfName->setNextSibling(doc.allocateElement("filename"));
129 fName->setData(doc.allocateString(romFile));
130 if (!ipsFiles.empty()) {
131 auto* patches = fName->setNextSibling(doc.allocateElement("patches"));
132 doc.generateList(*patches, "ips", ipsFiles, [&](XMLElement* n, auto& s) {
133 n->setData(doc.allocateString(s));
134 });
135 }
136
137 result->setConfig(extension);
138 result->setFileContext(std::move(context));
139 return result;
140}
141
142std::string_view HardwareConfig::getRomFilename() const
143{
144 // often this will give the same result as 'getName()', except when the same ROM file is used twice.
145 assert(type == Type::ROM && "should only be used on ROM extensions");
146 const auto* d = getConfig().findChild("devices"); assert(d);
147 const auto* p = d->findChild("primary"); assert(p);
148 const auto* s = p->findChild("secondary"); assert(s);
149 const auto* R = s->findChild("ROM"); assert(R);
150 const auto* r = R->findChild("rom"); assert(r);
151 const auto* f = r->findChild("filename"); assert(f);
152 return f->getData();
153}
154
155HardwareConfig::HardwareConfig(MSXMotherBoard& motherBoard_, std::string hwName_)
156 : motherBoard(motherBoard_)
157 , hwName(std::move(hwName_))
158{
159 for (auto& sub : externalSlots) {
160 ranges::fill(sub, false);
161 }
162 ranges::fill(externalPrimSlots, false);
163 ranges::fill(expandedSlots, false);
164 ranges::fill(allocatedPrimarySlots, false);
165 userName = motherBoard.getUserName(hwName);
166}
167
169{
170 motherBoard.freeUserName(hwName, userName);
171#ifndef NDEBUG
172 try {
173 testRemove();
174 } catch (MSXException& e) {
175 std::cerr << e.getMessage() << '\n';
176 assert(false);
177 }
178#endif
179 while (!devices.empty()) {
180 motherBoard.removeDevice(*devices.back());
181 devices.pop_back();
182 }
183 auto& slotManager = motherBoard.getSlotManager();
184 for (auto ps : xrange(4)) {
185 for (auto ss : xrange(4)) {
186 if (externalSlots[ps][ss]) {
187 slotManager.removeExternalSlot(ps, ss);
188 }
189 }
190 if (externalPrimSlots[ps]) {
191 slotManager.removeExternalSlot(ps);
192 }
193 if (expandedSlots[ps]) {
194 motherBoard.getCPUInterface().unsetExpanded(ps);
195 }
196 if (allocatedPrimarySlots[ps]) {
197 slotManager.freePrimarySlot(ps, *this);
198 }
199 }
200}
201
203{
204 auto et = devices.end();
205 for (auto rit = devices.rbegin(), ret = devices.rend();
206 rit != ret; ++rit) {
207#ifdef _LIBCPP_VERSION
208 // Workaround clang-13/libc++ bug
209 // Don't generally use this workaround, because '*rit.base()'
210 // triggers an error in a debug-STL build.
211 std::span alreadyRemoved(std::to_address(rit.base()), et - rit.base());
212#else
213 std::span alreadyRemoved{rit.base(), et};
214#endif
215 (*rit)->testRemove(alreadyRemoved);
216 }
217
218 const auto& slotManager = motherBoard.getSlotManager();
219 for (auto ps : xrange(4)) {
220 for (auto ss : xrange(4)) {
221 if (externalSlots[ps][ss]) {
222 slotManager.testRemoveExternalSlot(ps, ss, *this);
223 }
224 }
225 if (externalPrimSlots[ps]) {
226 slotManager.testRemoveExternalSlot(ps, *this);
227 }
228 if (expandedSlots[ps]) {
230 ps, devices);
231 }
232 }
233}
234
236{
237 return getConfig().getChild("devices");
238}
239
240static void loadHelper(XMLDocument& doc, const std::string& filename)
241{
242 try {
243 doc.load(filename, "msxconfig2.dtd");
244 } catch (XMLException& e) {
245 throw MSXException(
246 "Loading of hardware configuration failed: ",
247 e.getMessage());
248 }
249}
250
251static std::string getFilename(std::string_view type, std::string_view name)
252{
253 const auto& context = systemFileContext();
254 try {
255 // try <name>.xml
256 return context.resolve(tmpStrCat(
257 type, '/', name, ".xml"));
258 } catch (MSXException& e) {
259 // backwards-compatibility:
260 // also try <name>/hardwareconfig.xml
261 try {
262 return context.resolve(tmpStrCat(
263 type, '/', name, "/hardwareconfig.xml"));
264 } catch (MSXException&) {
265 throw e; // signal first error
266 }
267 }
268}
269
270void HardwareConfig::loadConfig(XMLDocument& doc, std::string_view type, std::string_view name)
271{
272 loadHelper(doc, getFilename(type, name));
273}
274
275void HardwareConfig::load(std::string_view type_)
276{
277 std::string filename = getFilename(type_, hwName);
278 loadHelper(config, filename);
279
280 assert(!userName.empty());
281 const auto& dirname = FileOperations::getDirName(filename);
282 setFileContext(configFileContext(dirname, hwName, userName));
283}
284
286{
287 // TODO this code does parsing for both 'expanded' and 'external' slots
288 // once machine and extensions are parsed separately move parsing
289 // of 'expanded' to MSXCPUInterface
290 //
291 for (const auto* psElem : getDevicesElem().getChildren("primary")) {
292 const auto& primSlot = psElem->getAttribute("slot");
293 int ps = CartridgeSlotManager::getSlotNum(primSlot.getValue());
294 if (psElem->getAttributeValueAsBool("external", false)) {
295 if (ps < 0) {
296 throw MSXException(
297 "Cannot mark unspecified primary slot '",
298 primSlot.getValue(), "' as external");
299 }
300 if (psElem->hasChildren()) {
301 throw MSXException(
302 "Primary slot ", ps,
303 " is marked as external, but that would only "
304 "make sense if its <primary> tag would be "
305 "empty.");
306 }
307 createExternalSlot(ps);
308 continue;
309 }
310 for (const auto* ssElem : psElem->getChildren("secondary")) {
311 auto secSlot = ssElem->getAttributeValue("slot");
312 int ss = CartridgeSlotManager::getSlotNum(secSlot);
313 if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
314 throw MSXException(
315 "Invalid secondary slot specification: \"",
316 secSlot, "\".");
317 }
318 if (ss < 0) {
319 if ((ss >= -128) && (0 <= ps) && (ps < 4) &&
320 motherBoard.getCPUInterface().isExpanded(ps)) {
321 ss += 128;
322 } else {
323 continue;
324 }
325 }
326 if (ps < 0) {
327 if (ps == -256) {
328 ps = getAnyFreePrimarySlot();
329 } else {
330 ps = getSpecificFreePrimarySlot(-ps - 1);
331 }
332 auto& mutablePrimSlot = const_cast<XMLAttribute&>(primSlot);
333 mutablePrimSlot.setValue(config.allocateString(strCat(ps)));
334 }
335 createExpandedSlot(ps);
336 if (ssElem->getAttributeValueAsBool("external", false)) {
337 if (ssElem->hasChildren()) {
338 throw MSXException(
339 "Secondary slot ", ps, '-', ss,
340 " is marked as external, but that would "
341 "only make sense if its <secondary> tag "
342 "would be empty.");
343 }
344 createExternalSlot(ps, ss);
345 }
346 }
347 }
348}
349
351{
352 byte initialPrimarySlots = 0;
353 if (const auto* slotMap = getConfig().findChild("slotmap")) {
354 for (const auto* child : slotMap->getChildren("map")) {
355 int page = child->getAttributeValueAsInt("page", -1);
356 if (page < 0 || page > 3) {
357 throw MSXException("Invalid or missing page in slotmap entry");
358 }
359 int slot = child->getAttributeValueAsInt("slot", -1);
360 if (slot < 0 || slot > 3) {
361 throw MSXException("Invalid or missing slot in slotmap entry");
362 }
363 unsigned offset = page * 2;
364 initialPrimarySlots &= byte(~(3 << offset));
365 initialPrimarySlots |= byte(slot << offset);
366 }
367 }
368 return initialPrimarySlots;
369}
370
372{
373 createDevices(getDevicesElem(), nullptr, nullptr);
374}
375
377 const XMLElement* primary, const XMLElement* secondary)
378{
379 for (const auto& c : elem.getChildren()) {
380 const auto& childName = c.getName();
381 if (childName == "primary") {
382 createDevices(c, &c, secondary);
383 } else if (childName == "secondary") {
384 createDevices(c, primary, &c);
385 } else {
386 auto device = DeviceFactory::create(
387 DeviceConfig(*this, c, primary, secondary));
388 if (device) {
389 addDevice(std::move(device));
390 } else {
391 // device is nullptr, so we are apparently ignoring it on purpose
392 }
393 }
394 }
395}
396
397void HardwareConfig::createExternalSlot(int ps)
398{
399 motherBoard.getSlotManager().createExternalSlot(ps);
400 assert(!externalPrimSlots[ps]);
401 externalPrimSlots[ps] = true;
402}
403
404void HardwareConfig::createExternalSlot(int ps, int ss)
405{
406 motherBoard.getSlotManager().createExternalSlot(ps, ss);
407 assert(!externalSlots[ps][ss]);
408 externalSlots[ps][ss] = true;
409}
410
411void HardwareConfig::createExpandedSlot(int ps)
412{
413 if (!expandedSlots[ps]) {
414 motherBoard.getCPUInterface().setExpanded(ps);
415 expandedSlots[ps] = true;
416 }
417}
418
419int HardwareConfig::getAnyFreePrimarySlot()
420{
421 int ps = motherBoard.getSlotManager().allocateAnyPrimarySlot(*this);
422 assert(!allocatedPrimarySlots[ps]);
423 allocatedPrimarySlots[ps] = true;
424 return ps;
425}
426
427int HardwareConfig::getSpecificFreePrimarySlot(unsigned slot)
428{
429 int ps = motherBoard.getSlotManager().allocateSpecificPrimarySlot(slot, *this);
430 assert(!allocatedPrimarySlots[ps]);
431 allocatedPrimarySlots[ps] = true;
432 return ps;
433}
434
435void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device)
436{
437 motherBoard.addDevice(*device);
438 devices.push_back(std::move(device));
439}
440
441void HardwareConfig::setName(std::string_view proposedName)
442{
443 if (!motherBoard.findExtension(proposedName)) {
444 name = proposedName;
445 } else {
446 unsigned n = 0;
447 do {
448 name = strCat(proposedName, " (", ++n, ')');
449 } while (motherBoard.findExtension(name));
450 }
451}
452
453void HardwareConfig::setSlot(std::string_view slotName)
454{
455 for (const auto* psElem : getDevicesElem().getChildren("primary")) {
456 const auto& primSlot = psElem->getAttribute("slot");
457 if (primSlot.getValue() == "any") {
458 auto& mutablePrimSlot = const_cast<XMLAttribute&>(primSlot);
459 mutablePrimSlot.setValue(config.allocateString(slotName));
460 }
461 }
462}
463
464static constexpr std::initializer_list<enum_string<HardwareConfig::Type>> configTypeInfo = {
465 { "MACHINE", HardwareConfig::Type::MACHINE },
466 { "EXTENSION", HardwareConfig::Type::EXTENSION },
467 { "ROM", HardwareConfig::Type::ROM },
468};
470
471// version 1: initial version
472// version 2: moved FileContext here (was part of config)
473// version 3: hold 'config' by-value instead of by-pointer
474// version 4: hold 'context' by-value instead of by-pointer
475// version 5: added hwconfig type info
476// version 6: switch from old to new XMLElement
477template<typename Archive>
478void HardwareConfig::serialize(Archive& ar, unsigned version)
479{
480 // filled-in by constructor:
481 // motherBoard, hwName, userName
482 // filled-in by parseSlots()
483 // externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
484
485 if (ar.versionAtLeast(version, 6)) {
486 ar.serialize("config", config);
487 } else {
488 OldXMLElement elem;
489 ar.serialize("config", elem); // fills in getLastSerializedFileContext()
490 config.load(elem);
491 }
492 if (ar.versionAtLeast(version, 2)) {
493 if (ar.versionAtLeast(version, 4)) {
494 ar.serialize("context", context);
495 } else {
496 std::unique_ptr<FileContext> ctxt;
497 ar.serialize("context", ctxt);
498 if (ctxt) context = *ctxt;
499 }
500 } else {
502 assert(ctxt);
503 context = *ctxt;
504 }
505 if constexpr (Archive::IS_LOADER) {
506 if (!motherBoard.getMachineConfig()) {
507 // must be done before parseSlots()
508 motherBoard.setMachineConfig(this);
509 } else {
510 // already set because this is an extension
511 }
512 parseSlots();
514 }
515 // only (polymorphically) initialize devices, they are already created
516 for (auto& d : devices) {
517 ar.serializePolymorphic("device", *d);
518 }
519 ar.serialize("name", name);
520 if (ar.versionAtLeast(version, 5)) {
521 ar.serialize("type", type);
522 } else {
523 assert(Archive::IS_LOADER);
525 }
526}
528
529} // 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
std::string_view getRomFilename() const
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, std::string_view romFile, std::string_view slotName, std::span< const TclObject > options)
static std::unique_ptr< HardwareConfig > createMachineConfig(MSXMotherBoard &motherBoard, std::string machineName)
void serialize(Archive &ar, unsigned version)
void setFileContext(FileContext &&ctxt)
HardwareConfig(MSXMotherBoard &motherBoard, std::string hwName)
const XMLElement & getConfig() const
byte parseSlotMap() const
Parses a slot mapping.
static void loadConfig(XMLDocument &doc, std::string_view type, std::string_view name)
static std::unique_ptr< HardwareConfig > createExtensionConfig(MSXMotherBoard &motherBoard, std::string extensionName, std::string_view slotName)
void testRemove() const
Checks whether this HardwareConfig can be deleted.
const XMLElement & getDevicesElem() const
void testUnsetExpanded(int ps, std::span< const std::unique_ptr< MSXDevice > > allowed) const
bool isExpanded(int ps) const
HardwareConfig * findExtension(std::string_view extensionName)
void setMachineConfig(HardwareConfig *machineConfig)
void freeUserName(const std::string &hwName, const std::string &userName)
const HardwareConfig * getMachineConfig() const
MSXCPUInterface & getCPUInterface()
std::string getUserName(const std::string &hwName)
Keep track of which 'usernames' are in use.
CommandController & getCommandController()
CartridgeSlotManager & getSlotManager()
void removeDevice(MSXDevice &device)
void addDevice(MSXDevice &device)
All MSXDevices should be registered by the MotherBoard.
void setValue(const char *value_)
Definition XMLElement.hh:58
void generateList(XMLElement &parent, const char *itemName, Range &&range, UnaryOp op)
const char * allocateString(std::string_view str)
XMLElement * allocateElement(const char *name)
XMLAttribute * allocateAttribute(const char *name, const char *value)
void load(const std::string &filename, std::string_view systemID)
XMLElement * setData(const char *data_)
const XMLElement * findChild(std::string_view childName) const
Definition XMLElement.cc:21
const XMLElement & getChild(std::string_view childName) const
Definition XMLElement.cc:55
ChildRange getChildren() const
constexpr double e
Definition Math.hh:21
std::optional< Context > context
Definition GLContext.cc:10
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.
This file implemented 3 utility functions:
Definition Autofire.cc:11
const FileContext & systemFileContext()
uint8_t byte
8 bit unsigned integer
Definition openmsx.hh:26
FileContext configFileContext(string_view path, string_view hwDescr, string_view userName)
ArgsInfo valueArg(std::string_view name, T &value)
std::vector< TclObject > parseTclArgs(Interpreter &interp, std::span< const TclObject > inArgs, std::span< const ArgsInfo > table)
const FileContext & userFileContext()
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
STL namespace.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
static std::unique_ptr< FileContext > getLastSerializedFileContext()
constexpr auto xrange(T e)
Definition xrange.hh:132