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 "MSXCliComm.hh"
8#include "HardwareConfig.hh"
9#include "MSXException.hh"
10#include "narrow.hh"
11#include "one_of.hh"
12#include "ranges.hh"
13#include "serialize.hh"
14#include "xrange.hh"
15#include <bit>
16#include <cassert>
17#include <iterator>
18#include <memory>
19
20namespace openmsx {
21
22AmdFlash::AmdFlash(const Rom& rom, std::span<const SectorInfo> sectorInfo_,
23 uint16_t ID_, Addressing addressing_,
24 const DeviceConfig& config, Load load)
25 : motherBoard(config.getMotherBoard())
26 , sectorInfo(std::move(sectorInfo_))
27 , sz(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 uint16_t ID_, Addressing addressing_,
36 const DeviceConfig& config)
37 : motherBoard(config.getMotherBoard())
38 , sectorInfo(std::move(sectorInfo_))
39 , sz(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.size()),
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(size()));
55
56 auto numSectors = sectorInfo.size();
57
58 size_t writableSize = 0;
59 size_t 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] = narrow<ptrdiff_t>(writableSize);
67 writableSize += sectorInfo[i].size;
68 }
69 }
70 assert((writableSize + readOnlySize) == size());
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 auto romSize = rom ? rom->size() : 0;
142 size_t offset = 0;
143 for (auto i : xrange(numSectors)) {
144 auto sectorSize = sectorInfo[i].size;
145 if (isSectorWritable(i)) {
146 readAddress[i] = &(*ram)[writeAddress[i]];
147 if (!loaded) {
148 auto* ramPtr = const_cast<uint8_t*>(
149 &(*ram)[writeAddress[i]]);
150 if (offset >= romSize) {
151 // completely past end of rom
152 ranges::fill(std::span{ramPtr, sectorSize}, 0xFF);
153 } else if (offset + sectorSize >= romSize) {
154 // partial overlap
155 auto last = romSize - offset;
156 auto missing = sectorSize - last;
157 const uint8_t* romPtr = &(*rom)[offset];
158 ranges::copy(std::span{romPtr, last}, ramPtr);
159 ranges::fill(std::span{&ramPtr[last], missing}, 0xFF);
160 } else {
161 // completely before end of rom
162 const uint8_t* romPtr = &(*rom)[offset];
163 ranges::copy(std::span{romPtr, sectorSize}, ramPtr);
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 == size());
177
178 reset();
179}
180
181AmdFlash::~AmdFlash() = default;
182
183AmdFlash::GetSectorInfoResult AmdFlash::getSectorInfo(size_t address) const
184{
185 address &= size() - 1;
186 auto it = sectorInfo.begin();
187 size_t sector = 0;
188 while (address >= it->size) {
189 address -= it->size;
190 ++sector;
191 ++it;
192 assert(it != sectorInfo.end());
193 }
194 auto sectorSize = it->size;
195 auto 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
212uint8_t AmdFlash::peek(size_t address) const
213{
214 auto [sector, sectorSize, offset] = getSectorInfo(address);
215 if (state == ST_IDLE) {
216 if (const uint8_t* 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 narrow_cast<uint8_t>(ID >> 8);
229 case 1:
230 return narrow_cast<uint8_t>(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(size_t sector) const
244{
245 return vppWpPinLow && (sector == one_of(0u, 1u)) ? false : (writeAddress[sector] != -1) ;
246}
247
248uint8_t AmdFlash::read(size_t address) const
249{
250 // note: after a read we stay in the same mode
251 return peek(address);
252}
253
254const uint8_t* AmdFlash::getReadCacheLine(size_t address) const
255{
256 if (state == ST_IDLE) {
257 auto [sector, sectorSize, offset] = getSectorInfo(address);
258 const uint8_t* addr = readAddress[sector];
259 return addr ? &addr[offset] : MSXDevice::unmappedRead.data();
260 } else {
261 return nullptr;
262 }
263}
264
265void AmdFlash::write(size_t address, uint8_t 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 std::array<uint8_t, 5> cmdSeq = {0xaa, 0x55, 0x80, 0xaa, 0x55};
300 if (partialMatch(cmdSeq)) {
301 if (cmdIdx < 6) return true;
302 if (cmd[5].value == 0x30) {
303 auto 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 std::array<uint8_t, 5> cmdSeq = {0xaa, 0x55, 0x80, 0xaa, 0x55};
317 if (partialMatch(cmdSeq)) {
318 if (cmdIdx < 6) return true;
319 if (cmd[5].value == 0x10) {
320 if (ram) ram->memset(0, 0xff, ram->size());
321 }
322 }
323 return false;
324}
325
326bool AmdFlash::checkCommandProgramHelper(size_t numBytes, std::span<const uint8_t> cmdSeq)
327{
328 if (partialMatch(cmdSeq)) {
329 if (cmdIdx < (cmdSeq.size() + numBytes)) return true;
330 for (auto i : xrange(cmdSeq.size(), cmdSeq.size() + numBytes)) {
331 auto addr = cmd[i].addr;
332 auto [sector, sectorSize, offset] = getSectorInfo(addr);
333 if (isSectorWritable(sector)) {
334 auto 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 std::array<uint8_t, 3> cmdSeq = {0xaa, 0x55, 0xa0};
345 return checkCommandProgramHelper(1, cmdSeq);
346}
347
348bool AmdFlash::checkCommandDoubleByteProgram()
349{
350 static constexpr std::array<uint8_t, 1> cmdSeq = {0x50};
351 return checkCommandProgramHelper(2, cmdSeq);
352}
353
354bool AmdFlash::checkCommandQuadrupleByteProgram()
355{
356 static constexpr std::array<uint8_t, 1> cmdSeq = {0x56};
357 return checkCommandProgramHelper(4, cmdSeq);
358}
359
360bool AmdFlash::checkCommandManufacturer()
361{
362 static constexpr std::array<uint8_t, 3> cmdSeq = {0xaa, 0x55, 0x90};
363 if (partialMatch(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(std::span<const uint8_t> dataSeq) const
373{
374 static constexpr std::array<unsigned, 5> addrSeq = {0, 1, 0, 0, 1};
375 static constexpr std::array<unsigned, 2> cmdAddr = {0x555, 0x2aa};
376
377 assert(dataSeq.size() <= 5);
378 for (auto i : xrange(std::min(unsigned(dataSeq.size()), cmdIdx))) {
379 // convert the address to the '11 bit case'
380 auto 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
void write(size_t address, uint8_t value)
Definition AmdFlash.cc:265
AmdFlash(const Rom &rom, std::span< const SectorInfo > sectorInfo, uint16_t ID, Addressing addressing, const DeviceConfig &config, Load load=Load::NORMAL)
Create AmdFlash with given configuration.
Definition AmdFlash.cc:22
void serialize(Archive &ar, unsigned version)
Definition AmdFlash.cc:404
const uint8_t * getReadCacheLine(size_t address) const
Definition AmdFlash.cc:254
size_t size() const
Definition AmdFlash.hh:67
uint8_t peek(size_t address) const
Definition AmdFlash.cc:212
uint8_t read(size_t address) const
Definition AmdFlash.cc:248
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:181
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:304
void resize(size_t size)
Grow or shrink the memory block.
Definition MemBuffer.hh:111
const T * data() const
Returns pointer to the start of the memory buffer.
Definition MemBuffer.hh:81
const std::string & getName() const
Definition Rom.hh:41
const std::string & getLoadedFilename() const
Definition SRAM.hh:36
void write(size_t addr, byte value)
Definition SRAM.cc:63
size_t size() const
Definition SRAM.hh:33
void memset(size_t addr, byte c, size_t size)
Definition SRAM.cc:72
constexpr double e
Definition Math.hh:21
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition gl_vec.hh:314
This file implemented 3 utility functions:
Definition Autofire.cc:9
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:186
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:250
STL namespace.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
auto sum(InputRange &&range, Proj proj={})
Definition stl.hh:245
void serialize(Archive &ar, unsigned version)
Definition AmdFlash.cc:397
constexpr auto xrange(T e)
Definition xrange.hh:132