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