/* bios & nvmem related code */ #pragma once #include #include "types.h" #include "stdclass.h" struct MemChip { u8* data; u32 size; u32 mask; u32 write_protect_size; std::string load_filename; MemChip(u32 size, u32 write_protect_size = 0) { this->data=new u8[size]; this->size=size; this->mask=size-1;//must be power of 2 this->write_protect_size = write_protect_size; } virtual ~MemChip() { delete[] data; } virtual u8 Read8(u32 addr) { return data[addr & mask]; } u32 Read(u32 addr,u32 sz) { addr &= mask; u32 rv = 0; for (u32 i = 0; i < sz; i++) rv |= Read8(addr + i) << (i * 8); return rv; } virtual void Write(u32 addr, u32 data, u32 size) { die("Method not supported"); } bool Load(const std::string& file) { FILE* f=fopen(file.c_str(),"rb"); if (f) { bool rv = fread(data + write_protect_size, 1, size - write_protect_size, f) == size - write_protect_size; fclose(f); if (rv) this->load_filename = file; return rv; } return false; } bool Reload() { return Load(this->load_filename); } void Save(const std::string& file) { FILE* f=fopen(file.c_str(),"wb"); if (f) { fwrite(data + write_protect_size, 1, size - write_protect_size, f); fclose(f); } } bool Load(const std::string& prefix, const std::string& names_ro, const std::string& title) { const size_t npos = std::string::npos; size_t start = 0; while (start < names_ro.size()) { size_t semicolon = names_ro.find(';', start); std::string name = names_ro.substr(start, semicolon == npos ? semicolon : semicolon - start); size_t percent = name.find('%'); if (percent != npos) name = name.replace(percent, 1, prefix); std::string fullpath = get_readonly_data_path(name); if (file_exists(fullpath) && Load(fullpath)) { INFO_LOG(FLASHROM, "Loaded %s as %s", fullpath.c_str(), title.c_str()); return true; } start = semicolon; if (start != npos) start++; } return false; } void Save(const std::string& prefix, const std::string& name_ro, const std::string& title) { std::string path = get_writable_data_path(prefix + name_ro); Save(path); INFO_LOG(FLASHROM, "Saved %s as %s", path.c_str(), title.c_str()); } virtual void Reset() {} virtual bool Serialize(void **data, unsigned int *total_size) { return true; } virtual bool Unserialize(void **data, unsigned int *total_size) { return true; } }; struct RomChip : MemChip { RomChip(u32 sz, u32 write_protect_size = 0) : MemChip(sz, write_protect_size) {} }; struct SRamChip : MemChip { SRamChip(u32 sz, u32 write_protect_size = 0) : MemChip(sz, write_protect_size) {} void Write(u32 addr,u32 val,u32 sz) override { addr&=mask; if (addr < write_protect_size) return; switch (sz) { case 1: data[addr]=(u8)val; return; case 2: *(u16*)&data[addr]=(u16)val; return; case 4: *(u32*)&data[addr]=val; return; default: die("invalid access size"); } } virtual bool Serialize(void **data, unsigned int *total_size) override { REICAST_SA(&this->data[write_protect_size], size - write_protect_size); return true; } virtual bool Unserialize(void **data, unsigned int *total_size) override { REICAST_USA(&this->data[write_protect_size], size - write_protect_size); return true; } }; // // Flash block handling code borrowed from redream (https://github.com/inolen/redream) // // magic cookie every block-allocated partition begins with #define FLASH_MAGIC_COOKIE "KATANA_FLASH____" #define FLASH_BLOCK_SIZE 0x40 // each bitmap is 64 bytes in length, and each byte can record the state of 8 // physical blocks (one per bit), therefore, each bitmap can represent up to // 512 physical blocks. these 512 blocks are each 64-bytes in length, meaning // each partition would need partition_size / 32768 bitmap blocks to represent // all of its physical blocks #define FLASH_BITMAP_BLOCKS (FLASH_BLOCK_SIZE * 8) #define FLASH_BITMAP_BYTES (FLASH_BITMAP_BLOCKS * 64) // flash partitions #define FLASH_PT_FACTORY 0 #define FLASH_PT_RESERVED 1 #define FLASH_PT_USER 2 #define FLASH_PT_GAME 3 #define FLASH_PT_UNKNOWN 4 #define FLASH_PT_NUM 5 // flash logical blocks #define FLASH_USER_SYSCFG 0x05 #define FLASH_USER_INET 0x80 #define FLASH_USER_ISP1 0xC0 #define FLASH_USER_ISP2 0xC6 struct flash_syscfg_block { u16 block_id; // last set time (seconds since 1/1/1950 00:00) u16 time_lo; u16 time_hi; u8 unknown1; u8 lang; u8 mono; u8 autostart; u8 unknown2[4]; u8 reserved[50]; }; struct flash_isp1_block { u16 block_id; u8 _unknown[4]; char sega[4]; char username[28]; char password[16]; char phone[8]; u16 crc; }; struct flash_isp2_block { u16 block_id; char sega[4]; char username[28]; char password[16]; char phone[12]; u16 crc; }; // header block in block-allocated partition struct flash_header_block { char magic[16]; u8 part_id; u8 version; u8 reserved[46]; }; // user block in block-allocated partition struct flash_user_block { u16 block_id; u8 data[60]; u16 crc; }; // Macronix 29LV160TMC // AtomisWave uses a custom 29L001mc model struct DCFlashChip : MemChip { DCFlashChip(u32 sz, u32 write_protect_size = 0): MemChip(sz, write_protect_size), state(FS_Normal) { } enum FlashState { FS_Normal, FS_ReadAMDID1, FS_ReadAMDID2, FS_ByteProgram, FS_EraseAMD1, FS_EraseAMD2, FS_EraseAMD3, FS_SelectMode, }; FlashState state; virtual void Reset() override { //reset the flash chip state state = FS_Normal; } void Write(u32 addr,u32 val,u32 sz) override { if (sz != 1) { INFO_LOG(FLASHROM, "invalid access size %d", sz); return; } addr &= mask; switch(state) { case FS_Normal: switch (val & 0xff) { case 0xf0: case 0xff: // reset chip mode state = FS_Normal; break; case 0xaa: // AMD ID select part 1 if ((addr & 0xfff) == 0x555 || (addr & 0xfff) == 0xaaa) state = FS_ReadAMDID1; break; default: INFO_LOG(FLASHROM, "Unknown FlashWrite mode: %x\n", val); break; } break; case FS_ReadAMDID1: if ((addr & 0xffff) == 0x02aa && (val & 0xff) == 0x55) state = FS_ReadAMDID2; else if ((addr & 0xffff) == 0x2aaa && (val & 0xff) == 0x55) state = FS_ReadAMDID2; else if ((addr & 0xfff) == 0x555 && (val & 0xff) == 0x55) state = FS_ReadAMDID2; else { if (val != 0xf0) WARN_LOG(FLASHROM, "FlashRom: ReadAMDID1 unexpected write @ %x: %x", addr, val); state = FS_Normal; } break; case FS_ReadAMDID2: if ((addr & 0xffff) == 0x0555 && (val & 0xff) == 0x80) state = FS_EraseAMD1; else if ((addr & 0xffff) == 0x5555 && (val & 0xff) == 0x80) state = FS_EraseAMD1; else if ((addr & 0xfff) == 0xaaa && (val & 0xff) == 0x80) state = FS_EraseAMD1; else if ((addr & 0xffff) == 0x0555 && (val & 0xff) == 0xa0) state = FS_ByteProgram; else if ((addr & 0xffff) == 0x5555 && (val & 0xff) == 0xa0) state = FS_ByteProgram; else if ((addr & 0xfff) == 0xaaa && (val & 0xff) == 0xa0) state = FS_ByteProgram; else if ((addr & 0xffff) == 0x5555 && (val & 0xff) == 0x90) state = FS_SelectMode; else { if (val != 0xf0) WARN_LOG(FLASHROM, "FlashRom: ReadAMDID2 unexpected write @ %x: %x", addr, val); state = FS_Normal; } break; case FS_ByteProgram: if ((addr & 0x1e000) != 0x1a000 && addr >= write_protect_size) data[addr] &= val; state = FS_Normal; break; case FS_EraseAMD1: if ((addr & 0xfff) == 0x555 && (val & 0xff) == 0xaa) state = FS_EraseAMD2; else if ((addr & 0xfff) == 0xaaa && (val & 0xff) == 0xaa) state = FS_EraseAMD2; else { if (val != 0xf0) WARN_LOG(FLASHROM, "FlashRom: EraseAMD1 unexpected write @ %x: %x", addr, val); state = FS_Normal; } break; case FS_EraseAMD2: if ((addr & 0xffff) == 0x02aa && (val & 0xff) == 0x55) state = FS_EraseAMD3; else if ((addr & 0xffff) == 0x2aaa && (val & 0xff) == 0x55) state = FS_EraseAMD3; else if ((addr & 0xfff) == 0x555 && (val & 0xff) == 0x55) state = FS_EraseAMD3; else { if (val != 0xf0) WARN_LOG(FLASHROM, "FlashRom: EraseAMD2 unexpected write @ %x: %x", addr, val); state = FS_Normal; } break; case FS_EraseAMD3: if (((addr & 0xfff) == 0x555 && (val & 0xff) == 0x10) || ((addr & 0xfff) == 0xaaa && (val & 0xff) == 0x10)) { // chip erase INFO_LOG(FLASHROM, "Erasing Chip!"); u8 save[0x2000]; // this area is write-protected memcpy(save, data + 0x1a000, 0x2000); memset(data + write_protect_size, 0xff, size - write_protect_size); memcpy(data + 0x1a000, save, 0x2000); } else if ((val & 0xff) == 0x30) { // sector erase if (addr >= write_protect_size) { void *start; u32 len; switch (addr & ~0x1FFF) { case 0x00000: // SA0 start = &data[0]; len = 0x10000; break; case 0x10000: // SA1 start = &data[0x10000]; len = 0x8000; break; case 0x18000: // SA2 start = &data[0x18000]; len = 0x2000; break; case 0x1a000: // SA3 start = nullptr; len = 0; break; case 0x1c000: // SA4 start = &data[0x1c000]; len = 0x4000; break; default: start = nullptr; len = 0; break; } INFO_LOG(FLASHROM, "Erase Sector %08X!", addr); if (start != nullptr) memset(start, 0xFF, len); } } else if (val != 0xf0) WARN_LOG(FLASHROM, "FlashRom: EraseAMD3 unexpected write @ %x: %x", addr, val); state = FS_Normal; break; default: WARN_LOG(FLASHROM, "FlashRom: invalid state. write @ %x: %x", addr, val); state = FS_Normal; break; } } u8 Read8(u32 addr) override { if (state == FS_SelectMode) { state = FS_Normal; switch (addr & 0x43) { case 0: // manufacturer's code return 4; // or 0x20 or 1 case 1: // device code return 0xb0; // or 0x40 or 0x3e case 2: // sector protection verification // sector protection DEBUG_LOG(FLASHROM, "Sector protection address %x", addr); return (addr & 0x1e000) == 0x1a000; default: WARN_LOG(FLASHROM, "SelectMode unknown address %x", addr); return 0; } } return MemChip::Read8(addr); } int WriteBlock(u32 part_id, u32 block_id, const void *data) { int offset, size; GetPartitionInfo(part_id, &offset, &size); if (!validate_header(offset, part_id)) return 0; // the real system libraries allocate and write to a new physical block each // time a logical block is updated. the reason being that, flash memory can // only be programmed once, and after that the entire sector must be reset in // order to reprogram it. flash storage has a finite number of these erase // operations before its integrity deteriorates, so the libraries try to // minimize how often they occur by writing to a new physical block until the // partition is completely full // // this limitation of the original hardware isn't a problem for us, so try and // just update an existing logical block if it exists int phys_id = lookup_block(offset, size, block_id); if (!phys_id) { phys_id = alloc_block(offset, size); if (!phys_id) return 0; } // update the block's crc before writing it back out struct flash_user_block user; memcpy(&user, data, sizeof(user)); user.block_id = block_id; user.crc = crc_block(&user); write_physical_block(offset, phys_id, &user); return 1; } int ReadBlock(u32 part_id, u32 block_id, void *data) { int offset, size; GetPartitionInfo(part_id, &offset, &size); if (!validate_header(offset, part_id)) return 0; int phys_id = lookup_block(offset, size, block_id); if (!phys_id) return 0; read_physical_block(offset, phys_id, data); return 1; } void GetPartitionInfo(int part_id, int *offset, int *size) { switch (part_id) { case FLASH_PT_FACTORY: *offset = 0x1a000; *size = 8 * 1024; break; case FLASH_PT_RESERVED: *offset = 0x18000; *size = 8 * 1024; break; case FLASH_PT_USER: *offset = 0x1c000; *size = 16 * 1024; break; case FLASH_PT_GAME: *offset = 0x10000; *size = 32 * 1024; break; case FLASH_PT_UNKNOWN: *offset = 0x00000; *size = 64 * 1024; break; default: die("unknown partition"); break; } } void Validate() { // validate partition 0 (factory settings) bool valid = true; char sysinfo[16]; for (size_t i = 0; i < sizeof(sysinfo); i++) sysinfo[i] = Read8(0x1a000 + i); valid = valid && memcmp(&sysinfo[5], "Dreamcast ", 11) == 0; for (size_t i = 0; i < sizeof(sysinfo); i++) sysinfo[i] = Read8(0x1a0a0 + i); valid = valid && memcmp(&sysinfo[5], "Dreamcast ", 11) == 0; if (!valid) { INFO_LOG(FLASHROM, "DCFlashChip::Validate resetting FLASH_PT_FACTORY"); memcpy(sysinfo, "00000Dreamcast ", sizeof(sysinfo)); erase_partition(FLASH_PT_FACTORY); memcpy(data + 0x1a000, sysinfo, sizeof(sysinfo)); memcpy(data + 0x1a0a0, sysinfo, sizeof(sysinfo)); } // validate partition 1 (reserved) erase_partition(FLASH_PT_RESERVED); // validate partition 2 (user settings, block allocated) if (!validate_header(FLASH_PT_USER)) { INFO_LOG(FLASHROM, "DCFlashChip::Validate resetting FLASH_PT_USER"); erase_partition(FLASH_PT_USER); write_header(FLASH_PT_USER); } // validate partition 3 (game settings, block allocated) if (!validate_header(FLASH_PT_GAME)) { INFO_LOG(FLASHROM, "DCFlashChip::Validate resetting FLASH_PT_GAME"); erase_partition(FLASH_PT_GAME); write_header(FLASH_PT_GAME); } // validate partition 4 (unknown, block allocated) if (!validate_header(FLASH_PT_UNKNOWN)) { INFO_LOG(FLASHROM, "DCFlashChip::Validate resetting FLASH_PT_UNKNOWN"); erase_partition(FLASH_PT_UNKNOWN); write_header(FLASH_PT_UNKNOWN); } } private: int crc_block(struct flash_user_block *block) { const u8 *buf = (const u8 *)block; int size = 62; int n = 0xffff; for (int i = 0; i < size; i++) { n ^= (buf[i] << 8); for (int c = 0; c < 8; c++) { if (n & 0x8000) { n = (n << 1) ^ 4129; } else { n = (n << 1); } } } return (~n) & 0xffff; } int validate_crc(struct flash_user_block *user) { return user->crc == crc_block(user); } inline int num_physical_blocks(u32 size) { return size / FLASH_BLOCK_SIZE; } inline int num_bitmap_blocks(u32 size) { return (int)ceil(size / (float)FLASH_BITMAP_BYTES); } inline int num_user_blocks(u32 size) { return num_physical_blocks(size) - num_bitmap_blocks(size) - 1; } inline int is_allocated(u8 *bitmap, u32 phys_id) { int index = (phys_id - 1) % FLASH_BITMAP_BLOCKS; return (bitmap[index / 8] & (0x80 >> (index % 8))) == 0x0; } inline void set_allocated(u8 *bitmap, u32 phys_id) { int index = (phys_id - 1) % FLASH_BITMAP_BLOCKS; bitmap[index / 8] &= ~(0x80 >> (index % 8)); } void write_physical_block(u32 offset, u32 phys_id, const void *data) { memcpy(&this->data[offset + phys_id * FLASH_BLOCK_SIZE], data, FLASH_BLOCK_SIZE); } void read_physical_block(u32 offset, u32 phys_id, void *data) { memcpy(data, &this->data[offset + phys_id * FLASH_BLOCK_SIZE], FLASH_BLOCK_SIZE); } int validate_header(u32 offset, u32 part_id) { struct flash_header_block header; read_physical_block(offset, 0, &header); if (memcmp(header.magic, FLASH_MAGIC_COOKIE, sizeof(header.magic)) != 0) return 0; if (header.part_id != part_id) return 0; return 1; } int validate_header(u32 part_id) { int offset, size; GetPartitionInfo(part_id, &offset, &size); return validate_header(offset, part_id); } int alloc_block(u32 offset, u32 size) { u8 bitmap[FLASH_BLOCK_SIZE]; int blocks = num_user_blocks(size); int bitmap_id = blocks; int phys_id = 1; int phys_end = 1 + blocks; while (phys_id < phys_end) { // read the next bitmap every FLASH_BITMAP_BLOCKS if (phys_id % FLASH_BITMAP_BLOCKS == 1) { read_physical_block(offset, ++bitmap_id, bitmap); } // use the first unallocated block if (!is_allocated(bitmap, phys_id)) { break; } // if the current block has been rewritten, use it if (lookup_block(offset, size, *(u16*)&this->data[offset + phys_id * FLASH_BLOCK_SIZE]) != phys_id) break; phys_id++; } if (phys_id >= phys_end) { WARN_LOG(FLASHROM, "Cannot allocate block in flash. Full?"); return 0; } // mark the block as allocated set_allocated(bitmap, phys_id); write_physical_block(offset, bitmap_id, bitmap); return phys_id; } int lookup_block(u32 offset, u32 size, u32 block_id) { u8 bitmap[FLASH_BLOCK_SIZE]; int blocks = num_user_blocks(size); int bitmap_id = 1 + blocks; int phys_id = 1; int phys_end = bitmap_id; // in order to lookup a logical block, all physical blocks must be iterated. // since physical blocks are allocated linearly, the physical block with the // highest address takes precedence int result = 0; while (phys_id < phys_end) { // read the next bitmap every FLASH_BITMAP_BLOCKS if (phys_id % FLASH_BITMAP_BLOCKS == 1) { read_physical_block(offset, bitmap_id++, bitmap); } // being that physical blocks are allocated linearly, stop processing once // the first unallocated block is hit if (!is_allocated(bitmap, phys_id)) break; struct flash_user_block user; read_physical_block(offset, phys_id, &user); if (user.block_id == block_id) { if (!validate_crc(&user)) WARN_LOG(FLASHROM, "flash_lookup_block physical block %d has an invalid crc", phys_id); else result = phys_id; } phys_id++; } return result; } virtual bool Serialize(void **data, unsigned int *total_size) override { REICAST_S(state); REICAST_SA(&this->data[write_protect_size], size - write_protect_size); return true; } virtual bool Unserialize(void **data, unsigned int *total_size) override { REICAST_US(state); REICAST_USA(&this->data[write_protect_size], size - write_protect_size); return true; } void erase_partition(u32 part_id) { int offset, size; GetPartitionInfo(part_id, &offset, &size); memset(data + offset, 0xFF, size); } void write_header(int part_id) { int offset, size; GetPartitionInfo(part_id, &offset, &size); struct flash_header_block header; memset(&header, 0xff, sizeof(header)); memcpy(header.magic, FLASH_MAGIC_COOKIE, sizeof(header.magic)); header.part_id = part_id; header.version = 0; write_physical_block(offset, 0, &header); } };