openMSX
AmdFlash.cc
Go to the documentation of this file.
1#include "AmdFlash.hh"
2#include "Rom.hh"
3#include "SRAM.hh"
4#include "MSXMotherBoard.hh"
5#include "MSXCPU.hh"
6#include "MSXDevice.hh"
7#include "CliComm.hh"
8#include "HardwareConfig.hh"
9#include "MSXException.hh"
10#include "one_of.hh"
11#include "ranges.hh"
12#include "serialize.hh"
13#include "xrange.hh"
14#include <bit>
15#include <cstring>
16#include <cassert>
17#include <iterator>
18#include <memory>
19
20namespace openmsx {
21
22AmdFlash::AmdFlash(const Rom& rom, std::span<const SectorInfo> sectorInfo_,
23 word ID_, Addressing addressing_,
24 const DeviceConfig& config, Load load)
25 : motherBoard(config.getMotherBoard())
26 , sectorInfo(std::move(sectorInfo_))
27 , size(sum(sectorInfo, &SectorInfo::size))
28 , ID(ID_)
29 , addressing(addressing_)
30{
31 init(rom.getName() + "_flash", config, load, &rom);
32}
33
34AmdFlash::AmdFlash(const std::string& name, std::span<const SectorInfo> sectorInfo_,
35 word ID_, Addressing addressing_,
36 const DeviceConfig& config)
37 : motherBoard(config.getMotherBoard())
38 , sectorInfo(std::move(sectorInfo_))
39 , size(sum(sectorInfo, &SectorInfo::size))
40 , ID(ID_)
41 , addressing(addressing_)
42{
43 init(name, config, Load::NORMAL, nullptr);
44}
45
46[[nodiscard]] static bool sramEmpty(const SRAM& ram)
47{
48 return ranges::all_of(xrange(ram.getSize()),
49 [&](auto i) { return ram[i] == 0xFF; });
50}
51
52void AmdFlash::init(const std::string& name, const DeviceConfig& config, Load load, const Rom* rom)
53{
54 assert(std::has_single_bit(getSize()));
55
56 auto numSectors = sectorInfo.size();
57
58 unsigned writableSize = 0;
59 unsigned readOnlySize = 0;
60 writeAddress.resize(numSectors);
61 for (auto i : xrange(numSectors)) {
62 if (sectorInfo[i].writeProtected) {
63 writeAddress[i] = -1;
64 readOnlySize += sectorInfo[i].size;
65 } else {
66 writeAddress[i] = writableSize;
67 writableSize += sectorInfo[i].size;
68 }
69 }
70 assert((writableSize + readOnlySize) == getSize());
71
72 bool loaded = false;
73 if (writableSize) {
74 if (load == Load::NORMAL) {
75 ram = std::make_unique<SRAM>(
76 name, "flash rom",
77 writableSize, config, nullptr, &loaded);
78 } else {
79 // Hack for 'Matra INK', flash chip is wired-up so that
80 // writes are never visible to the MSX (but the flash
81 // is not made write-protected). In this case it doesn't
82 // make sense to load/save the SRAM file.
83 ram = std::make_unique<SRAM>(
84 name, "flash rom",
85 writableSize, config, SRAM::DontLoadTag{});
86 }
87 }
88 if (readOnlySize) {
89 // If some part of the flash is read-only we require a ROM
90 // constructor parameter.
91 assert(rom);
92 }
93
94 const auto* romTag = config.getXML()->findChild("rom");
95 bool initialContentSpecified = romTag && romTag->findChild("sha1");
96
97 // check whether the loaded SRAM is empty, whilst initial content was specified
98 if (!rom && loaded && initialContentSpecified && sramEmpty(*ram)) {
99 config.getCliComm().printInfo(
100 "This flash device (", config.getHardwareConfig().getName(),
101 ") has initial content specified, but this content "
102 "was not loaded, because there was already content found "
103 "and loaded from persistent storage. However, this "
104 "content is blank (it was probably created automatically "
105 "when the specified initial content could not be loaded "
106 "when this device was used for the first time). If you "
107 "still wish to load the specified initial content, "
108 "please remove the blank persistent storage file: ",
109 ram->getLoadedFilename());
110 }
111
112 std::unique_ptr<Rom> rom_;
113 if (!rom && !loaded) {
114 // If we don't have a ROM constructor parameter and there was
115 // no sram content loaded (= previous persistent flash
116 // content), then try to load some initial content. This
117 // represents the original content of the flash when the device
118 // ships. This ROM is optional, if it's not found, then the
119 // initial flash content is all 0xFF.
120 try {
121 rom_ = std::make_unique<Rom>(
122 "", "", // dummy name and description
123 config);
124 rom = rom_.get();
125 config.getCliComm().printInfo(
126 "Loaded initial content for flash ROM from ",
127 rom->getFilename());
128 } catch (MSXException& e) {
129 // ignore error
130 assert(rom == nullptr); // 'rom' remains nullptr
131 // only if an actual sha1sum was given, tell the user we failed to use it
132 if (initialContentSpecified) {
133 config.getCliComm().printWarning(
134 "Could not load specified initial content "
135 "for flash ROM: ", e.getMessage());
136 }
137 }
138 }
139
140 readAddress.resize(numSectors);
141 unsigned romSize = rom ? rom->getSize() : 0;
142 unsigned offset = 0;
143 for (auto i : xrange(numSectors)) {
144 unsigned sectorSize = sectorInfo[i].size;
145 if (isSectorWritable(unsigned(i))) {
146 readAddress[i] = &(*ram)[writeAddress[i]];
147 if (!loaded) {
148 auto* ramPtr = const_cast<byte*>(
149 &(*ram)[writeAddress[i]]);
150 if (offset >= romSize) {
151 // completely past end of rom
152 memset(ramPtr, 0xFF, sectorSize);
153 } else if (offset + sectorSize >= romSize) {
154 // partial overlap
155 unsigned last = romSize - offset;
156 unsigned missing = sectorSize - last;
157 const byte* romPtr = &(*rom)[offset];
158 memcpy(ramPtr, romPtr, last);
159 memset(ramPtr + last, 0xFF, missing);
160 } else {
161 // completely before end of rom
162 const byte* romPtr = &(*rom)[offset];
163 memcpy(ramPtr, romPtr, sectorSize);
164 }
165 }
166 } else {
167 assert(rom); // must have rom constructor parameter
168 if ((offset + sectorSize) <= romSize) {
169 readAddress[i] = &(*rom)[offset];
170 } else {
171 readAddress[i] = nullptr;
172 }
173 }
174 offset += sectorSize;
175 }
176 assert(offset == getSize());
177
178 reset();
179}
180
181AmdFlash::~AmdFlash() = default;
182
183AmdFlash::GetSectorInfoResult AmdFlash::getSectorInfo(unsigned address) const
184{
185 address &= getSize() - 1;
186 auto it = sectorInfo.begin();
187 unsigned sector = 0;
188 while (address >= it->size) {
189 address -= it->size;
190 ++sector;
191 ++it;
192 assert(it != sectorInfo.end());
193 }
194 unsigned sectorSize = it->size;
195 unsigned offset = address;
196 return {sector, sectorSize, offset};
197}
198
200{
201 cmdIdx = 0;
202 setState(ST_IDLE);
203}
204
205void AmdFlash::setState(State newState)
206{
207 if (state == newState) return;
208 state = newState;
209 motherBoard.getCPU().invalidateAllSlotsRWCache(0x0000, 0x10000);
210}
211
212byte AmdFlash::peek(unsigned address) const
213{
214 auto [sector, sectorSize, offset] = getSectorInfo(address);
215 if (state == ST_IDLE) {
216 if (const byte* addr = readAddress[sector]) {
217 return addr[offset];
218 } else {
219 return 0xFF;
220 }
221 } else {
222 if (addressing == Addressing::BITS_12) {
223 // convert the address to the '11 bit case'
224 address >>= 1;
225 }
226 switch (address & 3) {
227 case 0:
228 return ID >> 8;
229 case 1:
230 return ID & 0xFF;
231 case 2:
232 // 1 -> write protected
233 return isSectorWritable(sector) ? 0 : 1;
234 case 3:
235 default:
236 // TODO what is this? According to this it reads as '1'
237 // http://www.msx.org/forumtopicl8329.html
238 return 1;
239 }
240 }
241}
242
243bool AmdFlash::isSectorWritable(unsigned sector) const
244{
245 return vppWpPinLow && (sector == one_of(0u, 1u)) ? false : (writeAddress[sector] != -1) ;
246}
247
248byte AmdFlash::read(unsigned address) const
249{
250 // note: after a read we stay in the same mode
251 return peek(address);
252}
253
254const byte* AmdFlash::getReadCacheLine(unsigned address) const
255{
256 if (state == ST_IDLE) {
257 auto [sector, sectorSize, offset] = getSectorInfo(address);
258 const byte* addr = readAddress[sector];
259 return addr ? &addr[offset] : MSXDevice::unmappedRead;
260 } else {
261 return nullptr;
262 }
263}
264
265void AmdFlash::write(unsigned address, byte value)
266{
267 assert(cmdIdx < MAX_CMD_SIZE);
268 cmd[cmdIdx].addr = address;
269 cmd[cmdIdx].value = value;
270 ++cmdIdx;
271 if (checkCommandManufacturer() ||
272 checkCommandEraseSector() ||
273 checkCommandProgram() ||
274 checkCommandDoubleByteProgram() ||
275 checkCommandQuadrupleByteProgram() ||
276 checkCommandEraseChip() ||
277 checkCommandReset()) {
278 // do nothing, we're still matching a command, but it is not complete yet
279 } else {
280 reset();
281 }
282}
283
284// The checkCommandXXX() methods below return
285// true -> if the command sequence still matches, but is not complete yet
286// false -> if the command was fully matched or does not match with
287// the current command sequence.
288// If there was a full match, the command is also executed.
289bool AmdFlash::checkCommandReset()
290{
291 if (cmd[0].value == 0xf0) {
292 reset();
293 }
294 return false;
295}
296
297bool AmdFlash::checkCommandEraseSector()
298{
299 static constexpr byte cmdSeq[] = { 0xaa, 0x55, 0x80, 0xaa, 0x55 };
300 if (partialMatch(5, cmdSeq)) {
301 if (cmdIdx < 6) return true;
302 if (cmd[5].value == 0x30) {
303 unsigned addr = cmd[5].addr;
304 auto [sector, sectorSize, offset] = getSectorInfo(addr);
305 if (isSectorWritable(sector)) {
306 ram->memset(writeAddress[sector],
307 0xff, sectorSize);
308 }
309 }
310 }
311 return false;
312}
313
314bool AmdFlash::checkCommandEraseChip()
315{
316 static constexpr byte cmdSeq[] = { 0xaa, 0x55, 0x80, 0xaa, 0x55 };
317 if (partialMatch(5, cmdSeq)) {
318 if (cmdIdx < 6) return true;
319 if (cmd[5].value == 0x10) {
320 if (ram) ram->memset(0, 0xff, ram->getSize());
321 }
322 }
323 return false;
324}
325
326bool AmdFlash::checkCommandProgramHelper(unsigned numBytes, const byte* cmdSeq, size_t cmdLen)
327{
328 if (partialMatch(cmdLen, cmdSeq)) {
329 if (cmdIdx < (cmdLen + numBytes)) return true;
330 for (auto i : xrange(cmdLen, cmdLen + numBytes)) {
331 unsigned addr = cmd[i].addr;
332 auto [sector, sectorSize, offset] = getSectorInfo(addr);
333 if (isSectorWritable(sector)) {
334 unsigned ramAddr = writeAddress[sector] + offset;
335 ram->write(ramAddr, (*ram)[ramAddr] & cmd[i].value);
336 }
337 }
338 }
339 return false;
340}
341
342bool AmdFlash::checkCommandProgram()
343{
344 static constexpr byte cmdSeq[] = { 0xaa, 0x55, 0xa0 };
345 return checkCommandProgramHelper(1, cmdSeq, std::size(cmdSeq));
346}
347
348bool AmdFlash::checkCommandDoubleByteProgram()
349{
350 static constexpr byte cmdSeq[] = { 0x50 };
351 return checkCommandProgramHelper(2, cmdSeq, std::size(cmdSeq));
352}
353
354bool AmdFlash::checkCommandQuadrupleByteProgram()
355{
356 static constexpr byte cmdSeq[] = { 0x56 };
357 return checkCommandProgramHelper(4, cmdSeq, std::size(cmdSeq));
358}
359
360bool AmdFlash::checkCommandManufacturer()
361{
362 static constexpr byte cmdSeq[] = { 0xaa, 0x55, 0x90 };
363 if (partialMatch(3, cmdSeq)) {
364 if (cmdIdx == 3) {
365 setState(ST_IDENT);
366 }
367 if (cmdIdx < 4) return true;
368 }
369 return false;
370}
371
372bool AmdFlash::partialMatch(size_t len, const byte* dataSeq) const
373{
374 static constexpr unsigned addrSeq[] = { 0, 1, 0, 0, 1 };
375 unsigned cmdAddr[2] = { 0x555, 0x2aa };
376
377 assert(len <= 5);
378 for (auto i : xrange(std::min(unsigned(len), cmdIdx))) {
379 // convert the address to the '11 bit case'
380 unsigned addr = (addressing == Addressing::BITS_12) ? cmd[i].addr >> 1 : cmd[i].addr;
381 if (((addr & 0x7FF) != cmdAddr[addrSeq[i]]) ||
382 (cmd[i].value != dataSeq[i])) {
383 return false;
384 }
385 }
386 return true;
387}
388
389
390static constexpr std::initializer_list<enum_string<AmdFlash::State>> stateInfo = {
391 { "IDLE", AmdFlash::ST_IDLE },
392 { "IDENT", AmdFlash::ST_IDENT }
393};
395
396template<typename Archive>
397void AmdFlash::AmdCmd::serialize(Archive& ar, unsigned /*version*/)
398{
399 ar.serialize("address", addr,
400 "value", value);
401}
402
403template<typename Archive>
404void AmdFlash::serialize(Archive& ar, unsigned version)
405{
406 ar.serialize("ram", *ram,
407 "cmd", cmd,
408 "cmdIdx", cmdIdx,
409 "state", state);
410 if (ar.versionAtLeast(version, 2)) {
411 ar.serialize("vppWpPinLow", vppWpPinLow);
412 }
413}
415
416} // namespace openmsx
Definition: one_of.hh:7
unsigned getSize() const
Definition: AmdFlash.hh:65
void write(unsigned address, byte value)
Definition: AmdFlash.cc:265
void serialize(Archive &ar, unsigned version)
Definition: AmdFlash.cc:404
byte peek(unsigned address) const
Definition: AmdFlash.cc:212
byte read(unsigned address) const
Definition: AmdFlash.cc:248
AmdFlash(const Rom &rom, std::span< const SectorInfo > sectorInfo, word ID, Addressing addressing, const DeviceConfig &config, Load load=Load::NORMAL)
Create AmdFlash with given configuration.
Definition: AmdFlash.cc:22
const byte * getReadCacheLine(unsigned address) const
Definition: AmdFlash.cc:254
void invalidateAllSlotsRWCache(word start, unsigned size)
Invalidate the CPU its cache for the interval [start, start + size) For example MSXMemoryMapper and M...
Definition: MSXCPU.cc:180
static byte unmappedRead[0x10000]
Definition: MSXDevice.hh:301
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
const std::string & getName() const
Definition: Rom.hh:37
const std::string & getLoadedFilename() const
Definition: SRAM.hh:36
void write(unsigned addr, byte value)
Definition: SRAM.cc:64
unsigned getSize() const
Definition: SRAM.hh:33
void memset(unsigned addr, byte c, unsigned size)
Definition: SRAM.cc:73
constexpr double e
Definition: Math.hh:18
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:265
SDLSurfacePtr load(const std::string &filename, bool want32bpp)
Load the given PNG file in a SDL_Surface.
Definition: PNG.cc:95
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr byte ID
Definition: MSXKanji12.cc:8
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:163
STL namespace.
size_t size(std::string_view utf8)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
auto sum(InputRange &&range, Proj proj={})
Definition: stl.hh:236
void serialize(Archive &ar, unsigned version)
Definition: AmdFlash.cc:397
constexpr auto xrange(T e)
Definition: xrange.hh:133