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