Commit 336d2012 authored by Tim Allen's avatar Tim Allen

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.
parent c5816994
Pipeline #30097483 failed with stage
in 13 minutes and 7 seconds
......@@ -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;
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"
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;
}
auto BSMemory::load() -> void {
queryable(true);
flashable(true);
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(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 == 0x70) {
return 0x80;
if(mode == Mode::Page) {
return page.read(address);
}
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::CompatibleStatus) {
return compatible.status();
}
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::ExtendedStatus) {
if((uint16)address == 0x0002) return block(address >> block.bits()).status();
if((uint16)address == 0x0004) return global.status();
return 0x00; //reserved: always zero
}
return memory.read(address, data);
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;
return;
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;
}