diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 089cf3b1..ddb6ce1e 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -28,7 +28,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.63"; + static const string Version = "106.64"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/sfc/coprocessor/mcc/mcc.cpp b/higan/sfc/coprocessor/mcc/mcc.cpp index 3fd27651..be973f95 100644 --- a/higan/sfc/coprocessor/mcc/mcc.cpp +++ b/higan/sfc/coprocessor/mcc/mcc.cpp @@ -22,23 +22,19 @@ auto MCC::power() -> void { w.exEnableLo = 1; w.exEnableHi = 0; w.exMapping = 1; - w.bsQueryable = 0; - w.bsFlashable = 0; - x.enable = 0; - x.value = 0b00111111; + w.internallyWritable = 0; + w.externallyWritable = 0; commit(); } auto MCC::commit() -> void { - r = w; //memory::copy(&r, &w, sizeof(Registers)); - bsmemory.queryable(r.bsQueryable); - bsmemory.flashable(r.bsFlashable); + r = w; + bsmemory.writable(r.externallyWritable); } auto MCC::read(uint24 address, uint8 data) -> uint8 { if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff uint4 index = address.bits(16,19); - if(x.enable) return x.value.bit(index & 7); switch(index) { case 0: return irq.flag << 7; case 1: return irq.enable << 7; @@ -52,10 +48,10 @@ auto MCC::read(uint24 address, uint8 data) -> uint8 { case 9: return r.exEnableLo << 7; case 10: return r.exEnableHi << 7; case 11: return r.exMapping << 7; - case 12: return r.bsQueryable << 7; - case 13: return r.bsFlashable << 7; + case 12: return r.internallyWritable << 7; + case 13: return r.externallyWritable << 7; case 14: return 0; //commit (always zero) - case 15: return 0; //x.enable (always zero) + case 15: return 0; //unknown (always zero) } } @@ -65,7 +61,6 @@ auto MCC::read(uint24 address, uint8 data) -> uint8 { auto MCC::write(uint24 address, uint8 data) -> void { if((address & 0xf0f000) == 0x005000) { //$00-0f:5000-5fff uint4 index = address.bits(16,19); - if(x.enable) return x.value.bit(index & 7) = data.bit(7), void(); switch(index) { case 1: irq.enable = data.bit(7); break; case 2: w.mapping = data.bit(7); break; @@ -78,10 +73,9 @@ auto MCC::write(uint24 address, uint8 data) -> void { case 9: w.exEnableLo = data.bit(7); break; case 10: w.exEnableHi = data.bit(7); break; case 11: w.exMapping = data.bit(7); break; - case 12: w.bsQueryable = data.bit(7); break; - case 13: w.bsFlashable = data.bit(7); break; + case 12: w.internallyWritable = data.bit(7); break; + case 13: w.externallyWritable = data.bit(7); break; case 14: if(data.bit(7)) commit(); break; - case 15: x.enable = data.bit(7); break; } } } @@ -258,6 +252,7 @@ auto MCC::exAccess(bool mode, uint24 address, uint8 data) -> uint8 { auto MCC::bsAccess(bool mode, uint24 address, uint8 data) -> uint8 { address = bus.mirror(address, bsmemory.size()); if(mode == 0) return bsmemory.read(address, data); + if(!r.internallyWritable) return data; return bsmemory.write(address, data), data; } diff --git a/higan/sfc/coprocessor/mcc/mcc.hpp b/higan/sfc/coprocessor/mcc/mcc.hpp index ca22e8f1..6b79b28d 100644 --- a/higan/sfc/coprocessor/mcc/mcc.hpp +++ b/higan/sfc/coprocessor/mcc/mcc.hpp @@ -32,25 +32,21 @@ private: } irq; struct Registers { - uint1 mapping; //bit 2 (0 = ignore A15; 1 = use A15) - uint1 psramEnableLo; //bit 3 - uint1 psramEnableHi; //bit 4 - uint2 psramMapping; //bits 5-6 - uint1 romEnableLo; //bit 7 - uint1 romEnableHi; //bit 8 - uint1 exEnableLo; //bit 9 - uint1 exEnableHi; //bit 10 - uint1 exMapping; //bit 11 - uint1 bsQueryable; //bit 12 - uint1 bsFlashable; //bit 13 + uint1 mapping; //bit 2 (0 = ignore A15; 1 = use A15) + uint1 psramEnableLo; //bit 3 + uint1 psramEnableHi; //bit 4 + uint2 psramMapping; //bits 5-6 + uint1 romEnableLo; //bit 7 + uint1 romEnableHi; //bit 8 + uint1 exEnableLo; //bit 9 + uint1 exEnableHi; //bit 10 + uint1 exMapping; //bit 11 + uint1 internallyWritable; //bit 12 (1 = MCC allows writes to BS Memory Cassette) + uint1 externallyWritable; //bit 13 (1 = BS Memory Cassette allows writes to flash memory) } r, w; - //bit 14 (commit) - - struct ExtendedRegisters { - uint1 enable; //bit 15 - uint8 value; //bits 24-31 - } x; + //bit 14 = commit + //bit 15 = unknown (test register interface?) }; extern MCC mcc; diff --git a/higan/sfc/coprocessor/mcc/serialization.cpp b/higan/sfc/coprocessor/mcc/serialization.cpp index 4b40bf84..0876c69f 100644 --- a/higan/sfc/coprocessor/mcc/serialization.cpp +++ b/higan/sfc/coprocessor/mcc/serialization.cpp @@ -1,7 +1,9 @@ auto MCC::serialize(serializer& s) -> void { s.array(psram.data(), psram.size()); + s.integer(irq.flag); s.integer(irq.enable); + s.integer(r.mapping); s.integer(r.psramEnableLo); s.integer(r.psramEnableHi); @@ -11,8 +13,9 @@ auto MCC::serialize(serializer& s) -> void { s.integer(r.exEnableLo); s.integer(r.exEnableHi); s.integer(r.exMapping); - s.integer(r.bsQueryable); - s.integer(r.bsFlashable); + s.integer(r.internallyWritable); + s.integer(r.externallyWritable); + s.integer(w.mapping); s.integer(w.psramEnableLo); s.integer(w.psramEnableHi); @@ -22,8 +25,6 @@ auto MCC::serialize(serializer& s) -> void { s.integer(w.exEnableLo); s.integer(w.exEnableHi); s.integer(w.exMapping); - s.integer(w.bsQueryable); - s.integer(w.bsFlashable); - s.integer(x.enable); - s.integer(x.value); + s.integer(w.internallyWritable); + s.integer(w.externallyWritable); } diff --git a/higan/sfc/slot/bsmemory/bsmemory.cpp b/higan/sfc/slot/bsmemory/bsmemory.cpp index 347bd040..5c51354f 100644 --- a/higan/sfc/slot/bsmemory/bsmemory.cpp +++ b/higan/sfc/slot/bsmemory/bsmemory.cpp @@ -2,21 +2,121 @@ namespace SuperFamicom { -#include "serialization.cpp" BSMemory bsmemory; +#include "serialization.cpp" -auto BSMemory::load() -> void { - queryable(true); - flashable(true); +BSMemory::BSMemory() { + page.self = this; + uint blockID = 0; + for(auto& block : blocks) block.self = this, block.id = blockID++; + block.self = this; +} + +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 { + Thread::step(clocks); + synchronize(cpu); +} + +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 = 0; + 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 { - memory.writable(false); - io = {}; + 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* { @@ -29,70 +129,442 @@ auto BSMemory::size() const -> uint { auto BSMemory::read(uint24 address, uint8 data) -> uint8 { if(!size()) return data; - address = bus.mirror(address, size()); - if(!pin.queryable) return memory.read(address, data); + if(ROM) return memory.read(bus.mirror(address, size())); - if(io.mode == 0x70) { - return 0x80; + if(mode == Mode::Chip) { + if(address == 0) return chip.vendor.byte(0); //only appears once + if(address == 1) return chip.device.byte(0); //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(io.mode == 0x71) { - if((uint16)address == 0x0002) return 0x80; - if((uint16)address == 0x0004) return 0x87; - if((uint16)address == 0x0006) return 0x00; //unknown purpose (not always zero) - return 0x00; + if(mode == Mode::Page) { + return page.read(address); } - if(io.mode == 0x75) { - if((uint8)address == 0x00) return 0x4d; //'M' (memory) - if((uint8)address == 0x02) return 0x50; //'P' (pack) - if((uint8)address == 0x04) return 0x04; //unknown purpose - if((uint8)address == 0x06) return Type << 4 | (uint4)log2(size() >> 10); - return random(); //not actually random, but not ROM data either, yet varies per cartridge + if(mode == Mode::CompatibleStatus) { + return compatible.status(); } - return memory.read(address, data); + if(mode == Mode::ExtendedStatus) { + if((uint16)address == 0x0002) return block(address >> block.bits()).status(); + if((uint16)address == 0x0004) return global.status(); + return 0x00; //reserved: always zero + } + + return block(address >> block.bits()).read(address); //Mode::Flash } auto BSMemory::write(uint24 address, uint8 data) -> void { - if(!size() || !pin.queryable) return; - address = bus.mirror(address, size()); + 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.byte(0) = queue.data(!queue.address(1).bit(0) ? 1 : 2); + count.byte(1) = queue.data(!queue.address(1).bit(0) ? 2 : 1); + uint24 address = queue.address(2); + do { + block(address >> block.bits()).write(address, page.read(address)); + address++; + } while(count--); + page.swap(); + mode = Mode::CompatibleStatus; + return queue.flush(); + } //write byte - if(io.mode == 0x10 || io.mode == 0x40) { - if(!pin.flashable) return; - memory.writable(true); - memory.write(address, memory.read(address) & data); //writes can only clear bits - memory.writable(false); - io.mode = 0x70; + if(queue.data(0) == 0x10) { + if(queue.size() < 2) return; + block(queue.address(1) >> block.bits()).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.bits()).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.bits()).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.bits()).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.bits(0,1) * 0x08; //verified for LH28F800SUT-ZI + address += id.bits(2,3) * 0x40; //verified for LH28F800SUT-ZI + address += id.bit ( 4) * 0x20; //guessed for LH28F016SU + address += id.bit ( 5) * 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(byte)); //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.byte(0) = queue.data(1); //endian order not affected by queue.address(1).bit(0) + count.byte(1) = queue.data(2); + page.write(queue.address(3), queue.data(3)); + if(count--) { + queue.data(1) = count.byte(0); + queue.data(2) = count.byte(1); + 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.byte(0) = queue.data(!queue.address(1).bit(0) ? 1 : 2); + value.byte(1) = queue.data(!queue.address(1).bit(0) ? 2 : 1); + //writes are always word-aligned: a0 toggles, rather than increments + block(queue.address(2) >> block.bits()).write(queue.address(2) ^ 0, value.byte(0)); + block(queue.address(2) >> block.bits()).write(queue.address(2) ^ 1, value.byte(1)); + 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::bits() const -> uint { return 16; } +auto BSMemory::BlockInformation::bytes() const -> uint { return 1 << bits(); } +auto BSMemory::BlockInformation::count() const -> uint { return self->size() >> bits(); } + +// + +auto BSMemory::Block::read(uint24 address) -> uint8 { + address &= bytes() - 1; + return self->memory.read(id << bits() | address); +} + +auto BSMemory::Block::write(uint24 address, uint8 data) -> void { + if(!self->writable() && status.locked) { + status.failed = 1; + return self->failed(); + } + + //writes to flash can only clear bits + address &= bytes() - 1; + data &= self->memory.read(id << bits() | address); + self->memory.write(id << bits() | 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; } - //erase 64KB page - if(io.mode == 0x20) { - //completes even if !pin.flashable - memory.writable(true); - for(uint offset : range(1 << 16)) memory.write(address & 0xff0000 | offset, 0xff); - memory.writable(false); - io.mode = 0x70; + 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; } - //erase all pages - if(io.mode == 0xa7) { - //completes even if !pin.flashable - if(Type == 3) return; //Type 3 doesn't support this command - - memory.writable(true); - for(uint offset : range(size())) memory.write(offset, 0xff); - memory.writable(false); - io.mode = 0x70; - return; + for(uint24 address : range(bytes())) { + self->memory.write(id << bits() | address, 0xff); } - if((uint16)address == 0x0000) { - io.mode = data; + 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; } } diff --git a/higan/sfc/slot/bsmemory/bsmemory.hpp b/higan/sfc/slot/bsmemory/bsmemory.hpp index 795321bc..32c9d30d 100644 --- a/higan/sfc/slot/bsmemory/bsmemory.hpp +++ b/higan/sfc/slot/bsmemory/bsmemory.hpp @@ -1,9 +1,34 @@ -struct BSMemory : Memory { - auto queryable(bool queryable) { pin.queryable = !ROM && queryable; } - auto flashable(bool flashable) { pin.flashable = !ROM && flashable; } +//MaskROMs supported: +// Sharp LH5S4TNI (MaskROM 512K x 8-bit) [BSMC-CR-01: BSMC-ZS5J-JPN, BSMC-YS5J-JPN] +// Sharp LH534VNF (MaskROM 512K x 8-bit) [BSMC-BR-01: BSMC-ZX3J-JPN] + +//Flash chips supported: (16-bit modes unsupported) +// Sharp LH28F800SUT-ZI (Flash 16 x 65536 x 8-bit) [BSMC-AF-01: BSMC-HM-JPN] +// Sharp LH28F016SU ??? (Flash 32 x 65536 x 8-bit) [unreleased: experimental] +// Sharp LH28F032SU ??? (Flash 64 x 65536 x 8-bit) [unreleased: experimental] + +//unsupported: +// Sharp LH28F400SU ??? (Flash 32 x 16384 x 8-bit) [unreleased] {vendor ID: 0x00'b0; device ID: 0x66'21} + +//notes: +//timing emulation is only present for block erase commands +//other commands generally complete so quickly that it's unnecessary (eg 70-120ns for writes) +//suspend, resume, abort, ready/busy modes are not supported + +struct BSMemory : Thread, Memory { + uint pathID = 0; + uint ROM = 1; + + auto writable() const { return pin.writable; } + auto writable(bool writable) { pin.writable = !ROM && writable; } //bsmemory.cpp - auto load() -> void; + BSMemory(); + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; + + auto load() -> bool; auto unload() -> void; auto power() -> void; @@ -15,27 +40,128 @@ struct BSMemory : Memory { //serialization.cpp auto serialize(serializer&) -> void; - uint pathID = 0; - - //0 = Flash; 1 = MaskROM - uint ROM = 1; - - //valid types are 1,2,3,4 - //type 2 is currently unsupported - //type 1 is the only type to exist (all flash-based BS Memory Cassettes are type 1) - uint Type = 1; - - ProtectableMemory memory; + WritableMemory memory; private: struct Pin { - uint1 queryable; - uint1 flashable; + uint1 writable; // => /WP } pin; - struct IO { - uint8 mode; - } io; + struct Chip { + uint16 vendor; + uint16 device; + uint48 serial; + } chip; + + struct Page { + BSMemory* self = nullptr; + + auto swap() -> void; + auto read(uint8 address) -> uint8; + auto write(uint8 address, uint8 data) -> void; + + uint8 buffer[2][256]; + } page; + + struct BlockInformation { + BSMemory* self = nullptr; + + inline auto bits() const -> uint; + inline auto bytes() const -> uint; + inline auto count() const -> uint; + }; + + struct Block : BlockInformation { + auto read(uint24 address) -> uint8; + auto write(uint24 address, uint8 data) -> void; + auto erase() -> void; + auto lock() -> void; + auto update() -> void; + + uint4 id; + uint32 erased; + uint1 locked; + uint1 erasing; + + struct Status { + auto operator()() -> uint8; + + uint1 vppLow; + uint1 queueFull; + uint1 aborted; + uint1 failed; + uint1 locked = 1; + uint1 ready = 1; + } status; + } blocks[64]; //8mbit = 16; 16mbit = 32; 32mbit = 64 + + struct Blocks : BlockInformation { + auto operator()(uint6 id) -> Block&; + } block; + + struct Compatible { + struct Status { + auto operator()() -> uint8; + + uint1 vppLow; + uint1 writeFailed; + uint1 eraseFailed; + uint1 eraseSuspended; + uint1 ready = 1; + } status; + } compatible; + + struct Global { + struct Status { + auto operator()() -> uint8; + + uint1 page; + uint1 pageReady = 1; + uint1 pageAvailable = 1; + uint1 queueFull; + uint1 sleeping; + uint1 failed; + uint1 suspended; + uint1 ready = 1; + } status; + } global; + + struct Mode { enum : uint { + Flash, + Chip, + Page, + CompatibleStatus, + ExtendedStatus, + };}; + uint3 mode; + + struct ReadyBusyMode { enum : uint { + EnableToLevelMode, + PulseOnWrite, + PulseOnErase, + Disable, + };}; + uint2 readyBusyMode; + + struct Queue { + auto flush() -> void; + auto pop() -> void; + auto push(uint24 address, uint8 data) -> void; + auto size() -> uint; + auto address(uint index) -> uint24; + auto data(uint index) -> uint8; + + //serialization.cpp + auto serialize(serializer&) -> void; + + struct History { + uint1 valid; + uint24 address; + uint8 data; + } history[4]; + } queue; + + auto failed() -> void; }; extern BSMemory bsmemory; diff --git a/higan/sfc/slot/bsmemory/serialization.cpp b/higan/sfc/slot/bsmemory/serialization.cpp index fa306f15..70779e67 100644 --- a/higan/sfc/slot/bsmemory/serialization.cpp +++ b/higan/sfc/slot/bsmemory/serialization.cpp @@ -1,6 +1,67 @@ auto BSMemory::serialize(serializer& s) -> void { - if(!ROM) s.array(memory.data(), memory.size()); - s.integer(pin.queryable); - s.integer(pin.flashable); - s.integer(io.mode); + Thread::serialize(s); + if(ROM) return; + + s.array(memory.data(), memory.size()); + + s.integer(pin.writable); + + s.integer(chip.vendor); + s.integer(chip.device); + s.integer(chip.serial); + + s.array(page.buffer[0]); + s.array(page.buffer[1]); + + for(auto& block : blocks) { + s.integer(block.id); + s.integer(block.erased); + s.integer(block.locked); + s.integer(block.erasing); + s.integer(block.status.vppLow); + s.integer(block.status.queueFull); + s.integer(block.status.aborted); + s.integer(block.status.failed); + s.integer(block.status.locked); + s.integer(block.status.ready); + } + + s.integer(compatible.status.vppLow); + s.integer(compatible.status.writeFailed); + s.integer(compatible.status.eraseFailed); + s.integer(compatible.status.eraseSuspended); + s.integer(compatible.status.ready); + + s.integer(global.status.page); + s.integer(global.status.pageReady); + s.integer(global.status.pageAvailable); + s.integer(global.status.queueFull); + s.integer(global.status.sleeping); + s.integer(global.status.failed); + s.integer(global.status.suspended); + s.integer(global.status.ready); + + s.integer(mode); + + s.integer(readyBusyMode); + + queue.serialize(s); +} + +auto BSMemory::Queue::serialize(serializer& s) -> void { + s.integer(history[0].valid); + s.integer(history[0].address); + s.integer(history[0].data); + + s.integer(history[1].valid); + s.integer(history[1].address); + s.integer(history[1].data); + + s.integer(history[2].valid); + s.integer(history[2].address); + s.integer(history[2].data); + + s.integer(history[3].valid); + s.integer(history[3].address); + s.integer(history[3].data); } diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index bd861966..f6a1380b 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -126,6 +126,7 @@ auto System::power(bool reset) -> void { if(cartridge.has.SharpRTC) cpu.coprocessors.append(&sharprtc); if(cartridge.has.SPC7110) cpu.coprocessors.append(&spc7110); if(cartridge.has.MSU1) cpu.coprocessors.append(&msu1); + if(cartridge.has.BSMemorySlot) cpu.coprocessors.append(&bsmemory); scheduler.primary(cpu); diff --git a/higan/target-bsnes/program/game-rom.cpp b/higan/target-bsnes/program/game-rom.cpp index 05216297..1dc9207e 100644 --- a/higan/target-bsnes/program/game-rom.cpp +++ b/higan/target-bsnes/program/game-rom.cpp @@ -124,7 +124,7 @@ auto Program::openRomSuperFamicom(string name, vfs::file::mode mode) -> vfs::sha } if(name.match("msu1/track-*.pcm")) { - name.trimLeft("msu1/track-", 1L); + name.trimLeft("msu1/track", 1L); return vfs::fs::file::open({Location::notsuffix(superFamicom.location), name}, mode); }