mirror of https://github.com/bsnes-emu/bsnes.git
Update to v106r64 release.
byuu says: Changelog: - sfc: completed BS Memory Cassette emulation (sans bugs, of course -- testing appreciated) - bsnes: don't strip - on MSU1 track names in game ROM mode [hex_usr] I'm going with "metadata.bml" for the flash metadata filename for the time being, but I'll say that it's subject to change. I'll have to make a new extension for it to be supported with bsnes.
This commit is contained in:
parent
c58169945c
commit
336d20123f
|
@ -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/";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue