OXYGENHUB
◆ main openmsx / src / memory / RomPlain.cc
5.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "RomPlain.hh"
2
3
#include "MSXException.hh"
4
#include "XMLElement.hh"
5
#include "serialize.hh"
6
7
#include "one_of.hh"
8
#include "unreachable.hh"
9
#include "xrange.hh"
10
11
#include <array>
12
13
namespace openmsx {
14
15
// return x inside [start, start+len)
16
[[nodiscard]] static constexpr bool isInside(unsigned x, unsigned start, unsigned len)
17
{
18
	unsigned tmp = x - start;
19
	return tmp < len;
20
}
21
22
[[nodiscard]] static std::string toString(unsigned start, unsigned len)
23
{
24
	return strCat("[0x", hex_string<4>(start), ", "
25
	               "0x", hex_string<4>(start + len), ')');
26
}
27
28
RomPlain::RomPlain(const DeviceConfig& config, Rom&& rom_, RomType type)
29
	: Rom8kBBlocks(config, std::move(rom_))
30
{
31
	unsigned windowBase =  0x0000;
32
	unsigned windowSize = 0x10000;
33
	if (const auto* mem = config.findChild("mem")) {
34
		windowBase = mem->getAttributeValueAsInt("base", 0);
35
		windowSize = mem->getAttributeValueAsInt("size", 0);
36
	}
37
38
	auto romSize_ = rom.size();
39
	if ((romSize_ > 0x10000) || (romSize_ & 0x1FFF)) {
40
		throw MSXException(rom.getName(),
41
			": invalid rom size: must be smaller than or equal to 64kB "
42
			"and must be a multiple of 8kB.");
43
	}
44
	auto romSize = narrow<unsigned>(romSize_);
45
46
	using enum RomType;
47
	const int start = [&] {
48
		switch (type) {
49
			case MIRRORED: return -1;
50
			case NORMAL:   return -1;
51
			case MIRRORED0000: return 0x0000;
52
			case MIRRORED4000: return 0x4000;
53
			case MIRRORED8000: return 0x8000;
54
			case MIRROREDC000: return 0xC000;
55
			case NORMAL0000: return 0x0000;
56
			case NORMAL4000: return 0x4000;
57
			case NORMAL8000: return 0x8000;
58
			case NORMALC000: return 0xC000;
59
			default: UNREACHABLE;
60
		}
61
	}();
62
	const bool mirrored = type == one_of(MIRRORED,
63
	                                     MIRRORED0000, MIRRORED4000,
64
	                                     MIRRORED8000, MIRROREDC000);
65
66
	unsigned romBase = (start == -1)
67
	                 ? guessLocation(windowBase, windowSize)
68
	                 : unsigned(start);
69
	if ((start == -1) &&
70
	    (!isInside(romBase,               windowBase, windowSize) ||
71
	     !isInside(romBase + romSize - 1, windowBase, windowSize))) {
72
		// ROM must fall inside the boundaries given by the <mem>
73
		// tag (this code only looks at one <mem> tag), but only
74
		// check when the start address was not explicitly specified
75
		throw MSXException(rom.getName(),
76
			": invalid rom position: interval ",
77
			toString(romBase, romSize), " must fit in ",
78
			toString(windowBase, windowSize), '.');
79
	}
80
	if (romBase & 0x1FFF) {
81
		throw MSXException(rom.getName(),
82
			": invalid rom position: must start at a 8kB boundary.");
83
	}
84
85
	unsigned firstPage = romBase / 0x2000;
86
	unsigned numPages = romSize / 0x2000;
87
	for (auto page : xrange(8)) {
88
		unsigned romPage = page - firstPage;
89
		if (romPage < numPages) {
90
			setRom(page, romPage);
91
		} else {
92
			if (mirrored) {
93
				setRom(page, romPage & (numPages - 1));
94
			} else {
95
				setUnmapped(page);
96
			}
97
		}
98
	}
99
	// RomPlain is currently implemented as a subclass of Rom8kBBlocks,
100
	// because of that it inherits a 8kB alignment requirement on the
101
	// base/size attributes in the <mem> tag in the hardware description.
102
	// But for example for the 'Boosted_audio' extension this requirement
103
	// is too strict. Except for the calls to setRom() and setUnmapped() in
104
	// this constructor, RomPlain doesn't dynamically change the memory
105
	// layout. So if we undo the cache-pre-filling stuff done by setRom()
106
	// it is OK to relax the alignment requirements again.
107
	//
108
	// An alternative is to not inherit from Rom8kBBlocks but from MSXRom.
109
	// That would simplify the alignment stuff, but OTOH then we have to
110
	// reimplement the debuggable stuff. Also the format of savestates will
111
	// change (so it would require extra backwards compatibility code).
112
	// Therefor, at least for now, I've not chosen this alternative.
113
	invalidateDeviceRCache();
114
}
115
116
void RomPlain::guessHelper(unsigned offset, std::span<int, 3> pages) const
117
{
118
	if ((rom[offset + 0] == 'A') && (rom[offset + 1] == 'B')) {
119
		for (auto i : xrange(4)) {
120
			if (auto addr = rom[offset + 2 + 2 * i + 0] +
121
			                rom[offset + 2 + 2 * i + 1] * 256) {
122
				unsigned page = (addr >> 14) - (offset >> 14);
123
				if (page <= 2) {
124
					pages[page]++;
125
				}
126
			}
127
		}
128
	}
129
}
130
131
unsigned RomPlain::guessLocation(unsigned windowBase, unsigned windowSize) const
132
{
133
	std::array<int, 3> pages = {0, 0, 0};
134
135
	// count number of possible routine pointers
136
	if (rom.size() >= 0x0010) {
137
		guessHelper(0x0000, pages);
138
	}
139
	if (rom.size() >= 0x4010) {
140
		guessHelper(0x4000, pages);
141
	}
142
143
	// start address must be inside memory window
144
	if (!isInside(0x0000, windowBase, windowSize)) pages[0] = 0;
145
	if (!isInside(0x4000, windowBase, windowSize)) pages[1] = 0;
146
	if (!isInside(0x8000, windowBase, windowSize)) pages[2] = 0;
147
148
	// we prefer 0x4000, then 0x000 and then 0x8000
149
	if (pages[1] && (pages[1] >= pages[0]) && (pages[1] >= pages[2])) {
150
		return 0x4000;
151
	} else if (pages[0] && pages[0] >= pages[2]) {
152
		return 0x0000;
153
	} else if (pages[2]) {
154
		return 0x8000;
155
	}
156
157
	// heuristics didn't work, return start of window
158
	return windowBase;
159
}
160
161
unsigned RomPlain::getBaseSizeAlignment() const
162
{
163
	// Because this mapper has no switchable banks, we can relax the
164
	// alignment requirements again. See also the comment at the end of the
165
	// constructor.
166
	return MSXRom::getBaseSizeAlignment();
167
}
168
169
REGISTER_MSXDEVICE(RomPlain, "RomPlain");
170
171
} // namespace openmsx