bsnes-hd/bsnes/sfc/slot/bsmemory/bsmemory.cpp
2020-01-12 22:12:37 +01:00

577 lines
16 KiB
C++

#include <sfc/sfc.hpp>
namespace SuperFamicom {
BSMemory bsmemory;
#include "serialization.cpp"
BSMemory::BSMemory() {
page.self = this;
uint blockID = 0;
for(auto& block : blocks) block.self = this, block.id = blockID++;
block.self = this;
}
auto BSMemory::synchronizeCPU() -> void {
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto BSMemory::Enter() -> void {
while(true) {
scheduler.synchronize();
bsmemory.main();
}
}
auto BSMemory::main() -> void {
if(ROM) return step(1'000'000); //1 second
for(uint6 id : range(block.count())) {
if(block(id).erasing) return block(id).erase();
block(id).status.ready = 1;
}
compatible.status.ready = 1;
global.status.ready = 1;
step(10'000); //10 milliseconds
}
auto BSMemory::step(uint clocks) -> void {
clock += clocks * (uint64_t)cpu.frequency;
synchronizeCPU();
}
auto BSMemory::load() -> bool {
if(ROM) return true;
if(size() != 0x100000 && size() != 0x200000 && size() != 0x400000) {
memory.reset();
return false;
}
chip.vendor = 0x00'b0; //Sharp
if(size() == 0x100000) chip.device = 0x66'a8; //LH28F800SU
if(size() == 0x200000) chip.device = 0x66'88; //LH28F016SU
if(size() == 0x400000) chip.device = 0x66'88; //LH28F032SU (same device ID as LH28F016SU per datasheet)
chip.serial = 0x00'01'23'45'67'89ull; //serial# should be unique for every cartridge ...
//page buffer values decay to random noise upon losing power to the flash chip
//the randomness is high entropy (at least compared to SNES SRAM/DRAM chips)
for(auto& byte : page.buffer[0]) byte = random();
for(auto& byte : page.buffer[1]) byte = random();
for(auto& block : blocks) {
block.erased = 1;
block.locked = 1;
}
if(auto fp = platform->open(pathID, "metadata.bml", File::Read, File::Optional)) {
auto document = BML::unserialize(fp->reads());
if(auto node = document["flash/vendor"]) {
chip.vendor = node.natural();
}
if(auto node = document["flash/device"]) {
chip.device = node.natural();
}
if(auto node = document["flash/serial"]) {
chip.serial = node.natural();
}
for(uint id : range(block.count())) {
if(auto node = document[{"flash/block(id=", id, ")"}]) {
if(auto erased = node["erased"]) {
block(id).erased = erased.natural();
}
if(auto locked = node["locked"]) {
block(id).locked = locked.boolean();
}
}
}
}
return true;
}
auto BSMemory::unload() -> void {
if(ROM) return memory.reset();
if(auto fp = platform->open(pathID, "metadata.bml", File::Write, File::Optional)) {
string manifest;
manifest.append("flash\n");
manifest.append(" vendor: 0x", hex(chip.vendor, 4L), "\n");
manifest.append(" device: 0x", hex(chip.device, 4L), "\n");
manifest.append(" serial: 0x", hex(chip.serial, 12L), "\n");
for(uint6 id : range(block.count())) {
manifest.append(" block\n");
manifest.append(" id: ", id, "\n");
manifest.append(" erased: ", (uint)block(id).erased, "\n");
manifest.append(" locked: ", (bool)block(id).locked, "\n");
}
fp->writes(manifest);
}
memory.reset();
}
auto BSMemory::power() -> void {
create(Enter, 1'000'000); //microseconds
for(auto& block : blocks) {
block.erasing = 0;
block.status = {};
}
compatible.status = {};
global.status = {};
mode = Mode::Flash;
readyBusyMode = ReadyBusyMode::Disable;
queue.flush();
}
auto BSMemory::data() -> uint8* {
return memory.data();
}
auto BSMemory::size() const -> uint {
return memory.size();
}
auto BSMemory::read(uint address, uint8 data) -> uint8 {
if(!size()) return data;
if(ROM) return memory.read(bus.mirror(address, size()));
if(mode == Mode::Chip) {
if(address == 0) return chip.vendor; //only appears once
if(address == 1) return chip.device; //only appears once
if((uint3)address == 2) return 0x63; //unknown constant: repeats every eight bytes
return 0x20; //unknown constant: fills in all remaining bytes
}
if(mode == Mode::Page) {
return page.read(address);
}
if(mode == Mode::CompatibleStatus) {
return compatible.status();
}
if(mode == Mode::ExtendedStatus) {
if((uint16)address == 0x0002) return block(address >> block.bitCount()).status();
if((uint16)address == 0x0004) return global.status();
return 0x00; //reserved: always zero
}
return block(address >> block.bitCount()).read(address); //Mode::Flash
}
auto BSMemory::write(uint address, uint8 data) -> void {
if(!size() || ROM) return;
queue.push(address, data);
//write page to flash
if(queue.data(0) == 0x0c) {
if(queue.size() < 3) return;
uint16 count; //1 - 65536
count = queue.data(!(queue.address(1) & 1) ? 1 : 2) << 0;
count |= queue.data(!(queue.address(1) & 1) ? 2 : 1) << 8;
uint address = queue.address(2);
do {
block(address >> block.bitCount()).write(address, page.read(address));
address++;
} while(count--);
page.swap();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//write byte
if(queue.data(0) == 0x10) {
if(queue.size() < 2) return;
block(queue.address(1) >> block.bitCount()).write(queue.address(1), queue.data(1));
mode = Mode::CompatibleStatus;
return queue.flush();
}
//erase block
if(queue.data(0) == 0x20) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
block(queue.address(1) >> block.bitCount()).erase();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//LH28F800SUT-ZI specific? (undocumented / unavailable? for the LH28F800SU)
//write signature, identifier, serial# into current page buffer, then swap page buffers
if(queue.data(0) == 0x38) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
page.write(0x00, 0x4d); //'M' (memory)
page.write(0x02, 0x50); //'P' (pack)
page.write(0x04, 0x04); //unknown constant (maybe block count? eg 1<<4 = 16 blocks)
page.write(0x06, 0x10 | (uint4)log2(size() >> 10)); //d0-d3 = size; d4-d7 = type (1)
page.write(0x08, chip.serial.byte(5)); //serial# (big endian; BCD format)
page.write(0x0a, chip.serial.byte(4)); //smallest observed value:
page.write(0x0c, chip.serial.byte(3)); // 0x00'00'10'62'62'39
page.write(0x0e, chip.serial.byte(2)); //largest observed value:
page.write(0x10, chip.serial.byte(1)); // 0x00'91'90'70'31'03
page.write(0x12, chip.serial.byte(0)); //most values are: 0x00'0x'xx'xx'xx'xx
page.swap();
return queue.flush();
}
//write byte
if(queue.data(0) == 0x40) {
if(queue.size() < 2) return;
block(queue.address(1) >> block.bitCount()).write(queue.address(1), queue.data(1));
mode = Mode::CompatibleStatus;
return queue.flush();
}
//clear status register
if(queue.data(0) == 0x50) {
for(uint6 id : range(block.count())) {
block(id).status.vppLow = 0;
block(id).status.failed = 0;
}
compatible.status.vppLow = 0;
compatible.status.writeFailed = 0;
compatible.status.eraseFailed = 0;
global.status.failed = 0;
return queue.flush();
}
//read compatible status register
if(queue.data(0) == 0x70) {
mode = Mode::CompatibleStatus;
return queue.flush();
}
//read extended status registers
if(queue.data(0) == 0x71) {
mode = Mode::ExtendedStatus;
return queue.flush();
}
//page buffer swap
if(queue.data(0) == 0x72) {
page.swap();
return queue.flush();
}
//single load to page buffer
if(queue.data(0) == 0x74) {
if(queue.size() < 2) return;
page.write(queue.address(1), queue.data(1));
return queue.flush();
}
//read page buffer
if(queue.data(0) == 0x75) {
mode = Mode::Page;
return queue.flush();
}
//lock block
if(queue.data(0) == 0x77) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
block(queue.address(1) >> block.bitCount()).lock();
return queue.flush();
}
//abort
//(unsupported)
if(queue.data(0) == 0x80) {
global.status.sleeping = 1; //abort seems to put the chip into sleep mode
return queue.flush();
}
//read chip identifiers
if(queue.data(0) == 0x90) {
mode = Mode::Chip;
return queue.flush();
}
//update ry/by mode
//(unsupported)
if(queue.data(0) == 0x96) {
if(queue.size() < 2) return;
if(queue.data(1) == 0x01) readyBusyMode = ReadyBusyMode::EnableToLevelMode;
if(queue.data(1) == 0x02) readyBusyMode = ReadyBusyMode::PulseOnWrite;
if(queue.data(1) == 0x03) readyBusyMode = ReadyBusyMode::PulseOnErase;
if(queue.data(1) == 0x04) readyBusyMode = ReadyBusyMode::Disable;
return queue.flush();
}
//upload lock status bits
if(queue.data(0) == 0x97) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
for(uint6 id : range(block.count())) block(id).update();
return queue.flush();
}
//upload device information (number of erase cycles per block)
if(queue.data(0) == 0x99) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
page.write(0x06, 0x06); //unknown constant
page.write(0x07, 0x00); //unknown constant
for(uint6 id : range(block.count())) {
uint8 address;
address += (id >> 0 & 3) * 0x08; //verified for LH28F800SUT-ZI
address += (id >> 2 & 3) * 0x40; //verified for LH28F800SUT-ZI
address += (id >> 4 & 1) * 0x20; //guessed for LH28F016SU
address += (id >> 5 & 1) * 0x04; //guessed for LH28F032SU; will overwrite unknown constants
uint32 erased = 1 << 31 | block(id).erased; //unknown if d31 is set when erased == 0
for(uint2 byte : range(4)) {
page.write(address + byte, erased >> byte * 8); //little endian
}
}
page.swap();
return queue.flush();
}
//erase all blocks
if(queue.data(0) == 0xa7) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
for(uint6 id : range(block.count())) block(id).erase();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//erase suspend/resume
//(unsupported)
if(queue.data(0) == 0xb0) {
if(queue.size() < 2) return;
if(queue.data(1) != 0xd0) return failed(), queue.flush();
mode = Mode::CompatibleStatus;
return queue.flush();
}
//sequential load to page buffer
if(queue.data(0) == 0xe0) {
if(queue.size() < 4) return; //command length = 3 + count
uint16 count; //1 - 65536
count = queue.data(1) << 0; //endian order not affected by queue.address(1).bit(0)
count |= queue.data(2) << 8;
page.write(queue.address(3), queue.data(3));
if(count--) {
queue.history[1].data = count >> 0;
queue.history[2].data = count >> 8;
return queue.pop(); //hack to avoid needing a 65539-entry queue
} else {
return queue.flush();
}
}
//sleep
//(unsupported)
if(queue.data(0) == 0xf0) {
//it is currently unknown how to exit sleep mode; other than via chip reset
global.status.sleeping = 1;
return queue.flush();
}
//write word
if(queue.data(0) == 0xfb) {
if(queue.size() < 3) return;
uint16 value;
value = queue.data(!(queue.address(1) & 1) ? 1 : 2) << 0;
value |= queue.data(!(queue.address(1) & 1) ? 2 : 1) << 8;
//writes are always word-aligned: a0 toggles, rather than increments
block(queue.address(2) >> block.bitCount()).write(queue.address(2) ^ 0, value >> 0);
block(queue.address(2) >> block.bitCount()).write(queue.address(2) ^ 1, value >> 8);
mode = Mode::CompatibleStatus;
return queue.flush();
}
//read flash memory
if(queue.data(0) == 0xff) {
mode = Mode::Flash;
return queue.flush();
}
//unknown command
return queue.flush();
}
//
auto BSMemory::failed() -> void {
compatible.status.writeFailed = 1; //datasheet specifies these are for write/erase failures
compatible.status.eraseFailed = 1; //yet all errors seem to set both of these bits ...
global.status.failed = 1;
}
//
auto BSMemory::Page::swap() -> void {
self->global.status.page ^= 1;
}
auto BSMemory::Page::read(uint8 address) -> uint8 {
return buffer[self->global.status.page][address];
}
auto BSMemory::Page::write(uint8 address, uint8 data) -> void {
buffer[self->global.status.page][address] = data;
}
//
auto BSMemory::BlockInformation::bitCount() const -> uint { return 16; }
auto BSMemory::BlockInformation::byteCount() const -> uint { return 1 << bitCount(); }
auto BSMemory::BlockInformation::count() const -> uint { return self->size() >> bitCount(); }
//
auto BSMemory::Block::read(uint address) -> uint8 {
address &= byteCount() - 1;
return self->memory.read(id << bitCount() | address);
}
auto BSMemory::Block::write(uint address, uint8 data) -> void {
if(!self->writable() && status.locked) {
status.failed = 1;
return self->failed();
}
//writes to flash can only clear bits
address &= byteCount() - 1;
data &= self->memory.read(id << bitCount() | address);
self->memory.write(id << bitCount() | address, data);
}
auto BSMemory::Block::erase() -> void {
if(cpu.active()) {
//erase command runs even if the block is not currently writable
erasing = 1;
status.ready = 0;
self->compatible.status.ready = 0;
self->global.status.ready = 0;
return;
}
self->step(300'000); //300 milliseconds are required to erase one block
erasing = 0;
if(!self->writable() && status.locked) {
//does not set any failure bits when unsuccessful ...
return;
}
for(uint address : range(byteCount())) {
self->memory.write(id << bitCount() | address, 0xff);
}
erased++;
locked = 0;
status.locked = 0;
}
auto BSMemory::Block::lock() -> void {
if(!self->writable()) {
//produces a failure result even if the page was already locked
status.failed = 1;
return self->failed();
}
locked = 1;
status.locked = 1;
}
//at reset, the locked status bit is set
//this command refreshes the true locked status bit from the device
auto BSMemory::Block::update() -> void {
status.locked = locked;
}
//
auto BSMemory::Blocks::operator()(uint6 id) -> Block& {
return self->blocks[id & count() - 1];
}
//
auto BSMemory::Block::Status::operator()() -> uint8 {
return ( //d0-d1 are reserved; always return zero
vppLow << 2
| queueFull << 3
| aborted << 4
| failed << 5
|!locked << 6 //note: technically the unlocked flag; so locked is inverted here
| ready << 7
);
}
//
auto BSMemory::Compatible::Status::operator()() -> uint8 {
return ( //d0-d2 are reserved; always return zero
vppLow << 3
| writeFailed << 4
| eraseFailed << 5
| eraseSuspended << 6
| ready << 7
);
}
//
auto BSMemory::Global::Status::operator()() -> uint8 {
return (
page << 0
| pageReady << 1
| pageAvailable << 2
| queueFull << 3
| sleeping << 4
| failed << 5
| suspended << 6
| ready << 7
);
}
//
auto BSMemory::Queue::flush() -> void {
history[0] = {};
history[1] = {};
history[2] = {};
history[3] = {};
}
auto BSMemory::Queue::pop() -> void {
if(history[3].valid) { history[3] = {}; return; }
if(history[2].valid) { history[2] = {}; return; }
if(history[1].valid) { history[1] = {}; return; }
if(history[0].valid) { history[0] = {}; return; }
}
auto BSMemory::Queue::push(uint24 address, uint8 data) -> void {
if(!history[0].valid) { history[0] = {true, address, data}; return; }
if(!history[1].valid) { history[1] = {true, address, data}; return; }
if(!history[2].valid) { history[2] = {true, address, data}; return; }
if(!history[3].valid) { history[3] = {true, address, data}; return; }
}
auto BSMemory::Queue::size() -> uint {
if(history[3].valid) return 4;
if(history[2].valid) return 3;
if(history[1].valid) return 2;
if(history[0].valid) return 1;
return 0;
}
auto BSMemory::Queue::address(uint index) -> uint24 {
if(index > 3 || !history[index].valid) return 0;
return history[index].address;
}
auto BSMemory::Queue::data(uint index) -> uint8 {
if(index > 3 || !history[index].valid) return 0;
return history[index].data;
}
}