/* * x76f041.c * * Secure SerialFlash * * The X76F041 is a Password Access Security Supervisor, containing four 128 x 8 bit SecureFlash arrays. * Access can be controlled by three 64-bit programmable passwords, one for read operations, one for write * operations and one for device configuration. * * The data sheet has an incorrect diagrams for sequential read with password, there shouldn't be an extra address after the 0xc0 command. * */ #include "emu.h" #include "machine/x76f041.h" #define VERBOSE_LEVEL 0 inline void ATTR_PRINTF(3,4) x76f041_device::verboselog(int n_level, const char *s_fmt, ...) { if(VERBOSE_LEVEL >= n_level) { va_list v; char buf[32768]; va_start(v, s_fmt); vsprintf(buf, s_fmt, v); va_end(v); logerror("x76f041 %s %s: %s", tag(), machine().describe_context(), buf); } } // device type definition const device_type X76F041 = &device_creator; x76f041_device::x76f041_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_secure_serial_flash(mconfig, X76F041, "X76F041", tag, owner, clock) { } void x76f041_device::device_start() { device_secure_serial_flash::device_start(); save_item(NAME(state)); save_item(NAME(shift)); save_item(NAME(bit)); save_item(NAME(byte)); save_item(NAME(command)); save_item(NAME(address)); save_item(NAME(write_buffer)); save_item(NAME(response_to_reset)); save_item(NAME(write_password)); save_item(NAME(read_password)); save_item(NAME(configuration_password)); save_item(NAME(configuration_registers)); save_item(NAME(data)); } void x76f041_device::device_reset() { device_secure_serial_flash::device_reset(); state = STATE_STOP; shift = 0; bit = 0; byte = 0; command = 0; address = 0; memset(write_buffer, 0, SIZE_WRITE_BUFFER); } void x76f041_device::nvram_default() { // region always wins if(m_region) { // Ensure the size is correct though if(m_region->bytes() != SIZE_RESPONSE_TO_RESET+SIZE_WRITE_PASSWORD+ SIZE_READ_PASSWORD+SIZE_CONFIGURATION_PASSWORD+SIZE_CONFIGURATION_REGISTERS+SIZE_DATA) logerror("X76F041: Wrong region length for initialization data, expected 0x%x, got 0x%x\n", SIZE_RESPONSE_TO_RESET+SIZE_WRITE_PASSWORD+ SIZE_READ_PASSWORD+SIZE_CONFIGURATION_PASSWORD+SIZE_CONFIGURATION_REGISTERS+SIZE_DATA, m_region->bytes()); else { UINT8 *rb = m_region->base(); int offset = 0; memcpy(response_to_reset, rb + offset, SIZE_RESPONSE_TO_RESET); offset += SIZE_RESPONSE_TO_RESET; memcpy(write_password, rb + offset, SIZE_WRITE_PASSWORD); offset += SIZE_WRITE_PASSWORD; memcpy(read_password, rb + offset, SIZE_READ_PASSWORD); offset += SIZE_READ_PASSWORD; memcpy(configuration_password, rb + offset, SIZE_CONFIGURATION_PASSWORD); offset += SIZE_CONFIGURATION_PASSWORD; memcpy(configuration_registers, rb + offset, SIZE_CONFIGURATION_REGISTERS); offset += SIZE_CONFIGURATION_REGISTERS; memcpy(data, rb + offset, SIZE_DATA); offset += SIZE_DATA; return; } } // That chip isn't really usable without the passwords, so bitch // if there's no region logerror("X76F041: Warning, no default data provided, chip is unusable.\n"); memset(response_to_reset, 0, SIZE_RESPONSE_TO_RESET); memset(write_password, 0, SIZE_WRITE_PASSWORD); memset(read_password, 0, SIZE_READ_PASSWORD); memset(configuration_password, 0, SIZE_CONFIGURATION_PASSWORD); memset(configuration_registers, 0, SIZE_CONFIGURATION_REGISTERS); memset(data, 0, SIZE_DATA); } void x76f041_device::cs_0() { /* enable chip */ state = STATE_STOP; } void x76f041_device::cs_1() { /* disable chip */ state = STATE_STOP; /* high impendence? */ sdar = false; } void x76f041_device::rst_0() { } void x76f041_device::rst_1() { if(!cs) { verboselog(1, "goto response to reset\n"); state = STATE_RESPONSE_TO_RESET; bit = 0; byte = 0; } } UINT8 *x76f041_device::password() { switch(command & 0xe0) { case COMMAND_WRITE: return write_password; case COMMAND_READ: return read_password; default: return configuration_password; } } void x76f041_device::password_ok() { switch(command & 0xe0) { case COMMAND_WRITE: state = STATE_WRITE_DATA; break; case COMMAND_READ: state = STATE_READ_DATA; break; case COMMAND_WRITE_USE_CONFIGURATION_PASSWORD: state = STATE_WRITE_DATA; break; case COMMAND_READ_USE_CONFIGURATION_PASSWORD: state = STATE_READ_DATA; break; case COMMAND_CONFIGURATION: switch( address ) { case CONFIGURATION_PROGRAM_WRITE_PASSWORD: break; case CONFIGURATION_PROGRAM_READ_PASSWORD: break; case CONFIGURATION_PROGRAM_CONFIGURATION_PASSWORD: break; case CONFIGURATION_RESET_WRITE_PASSWORD: break; case CONFIGURATION_RESET_READ_PASSWORD: break; case CONFIGURATION_PROGRAM_CONFIGURATION_REGISTERS: state = STATE_WRITE_CONFIGURATION_REGISTERS; byte = 0; break; case CONFIGURATION_READ_CONFIGURATION_REGISTERS: state = STATE_READ_CONFIGURATION_REGISTERS; byte = 0; break; case CONFIGURATION_MASS_PROGRAM: break; case CONFIGURATION_MASS_ERASE: break; default: break; } } } void x76f041_device::load_address() { /* todo: handle other bcr bits */ int bcr; address = shift; verboselog(1, "-> address: %02x\n", address); if(!(command & 1 )) bcr = configuration_registers[CONFIG_BCR1]; else bcr = configuration_registers[CONFIG_BCR2]; if(address & 0x80) bcr >>= 4; if(((command & 0xe0) == COMMAND_READ && (bcr & BCR_Z) && (bcr & BCR_T)) || ((command & 0xe0) == COMMAND_WRITE && (bcr & BCR_Z))) { /* todo: find out when this is really checked. */ verboselog(1, "command not allowed\n"); state = STATE_STOP; sdar = false; } else if(((command & 0xe0) == COMMAND_WRITE && !(bcr & BCR_X)) || ((command & 0xe0) == COMMAND_READ && !(bcr & BCR_Y))) { verboselog(1, "password not required\n"); password_ok(); } else { verboselog(1, "send password\n"); state = STATE_LOAD_PASSWORD; byte = 0; } } int x76f041_device::data_offset() { int block_offset = ((command & 1) << 8) + address; // TODO: confirm block_start doesn't wrap. return (block_offset & 0x180) | ((block_offset + byte) & 0x7f); } void x76f041_device::scl_0() { if(!cs) { switch(state) { case STATE_RESPONSE_TO_RESET: sdar = (response_to_reset[byte] >> bit) & 1; verboselog(2, "in response to reset %d (%d/%d)\n", sdar, byte, bit); bit++; if(bit == 8) { bit = 0; byte++; if( byte == 4 ) byte = 0; } break; } } } void x76f041_device::scl_1() { if(!cs) { switch(state) { case STATE_STOP: break; case STATE_RESPONSE_TO_RESET: break; case STATE_LOAD_COMMAND: case STATE_LOAD_ADDRESS: case STATE_LOAD_PASSWORD: case STATE_VERIFY_PASSWORD: case STATE_WRITE_DATA: case STATE_WRITE_CONFIGURATION_REGISTERS: if(bit < 8) { verboselog(2, "clock\n"); shift <<= 1; if(sdaw) shift |= 1; bit++; } else { sdar = false; switch(state) { case STATE_LOAD_COMMAND: command = shift; verboselog(1, "-> command: %02x\n", command); /* todo: verify command is valid? */ state = STATE_LOAD_ADDRESS; break; case STATE_LOAD_ADDRESS: load_address(); break; case STATE_LOAD_PASSWORD: verboselog(1, "-> password: %02x\n", shift ); write_buffer[byte++] = shift; if(byte == SIZE_WRITE_BUFFER) state = STATE_VERIFY_PASSWORD; break; case STATE_VERIFY_PASSWORD: verboselog(1, "-> verify password: %02x\n", shift); /* todo: this should probably be handled as a command */ if(shift == 0xc0) { /* todo: this should take 10ms before it returns ok. */ if(!memcmp(password(), write_buffer, SIZE_WRITE_BUFFER)) password_ok(); else sdar = true; } break; case STATE_WRITE_DATA: verboselog(1, "-> data: %02x\n", shift); write_buffer[byte++] = shift; if(byte == SIZE_WRITE_BUFFER) { for(byte = 0; byte < SIZE_WRITE_BUFFER; byte++) data[data_offset()] = write_buffer[byte]; byte = 0; verboselog(1, "data flushed\n"); } break; case STATE_WRITE_CONFIGURATION_REGISTERS: verboselog(1, "-> configuration register: %02x\n", shift); /* todo: write after all bytes received? */ configuration_registers[byte++] = shift; if(byte == SIZE_CONFIGURATION_REGISTERS) byte = 0; break; } bit = 0; shift = 0; } break; case STATE_READ_DATA: case STATE_READ_CONFIGURATION_REGISTERS: if(bit < 8) { if(bit == 0) { switch(state) { case STATE_READ_DATA: shift = data[data_offset()]; verboselog(1, "<- data: %02x\n", shift); break; case STATE_READ_CONFIGURATION_REGISTERS: shift = configuration_registers[byte & 7]; verboselog(1, "<- configuration register: %02x\n", shift ); break; } } sdar = ( shift >> 7 ) & 1; shift <<= 1; bit++; } else { bit = 0; sdar = false; if(!sdaw) { verboselog(2, "ack <-\n"); byte++; } else { verboselog(2, "nak <-\n"); } } break; } } } void x76f041_device::sda_0() { if(!cs && scl) { switch(state) { case STATE_STOP: verboselog(1, "goto start (1)\n"); state = STATE_LOAD_COMMAND; break; case STATE_LOAD_PASSWORD: /* todo: this will be the 0xc0 command, but it's not handled as a command yet. */ verboselog(1, "goto start (2)\n"); break; case STATE_READ_DATA: verboselog(1, "goto load address\n"); state = STATE_LOAD_ADDRESS; break; default: verboselog(1, "skipped start (default)\n"); break; } bit = 0; byte = 0; shift = 0; sdar = false; } } void x76f041_device::sda_1() { if(!cs && scl) { verboselog(1, "goto stop\n"); state = STATE_STOP; sdar = false; } } void x76f041_device::nvram_read(emu_file &file) { file.read(response_to_reset, SIZE_RESPONSE_TO_RESET); file.read(write_password, SIZE_WRITE_PASSWORD); file.read(read_password, SIZE_READ_PASSWORD); file.read(configuration_password, SIZE_CONFIGURATION_PASSWORD); file.read(configuration_registers, SIZE_CONFIGURATION_REGISTERS); file.read(data, SIZE_DATA); } void x76f041_device::nvram_write(emu_file &file) { file.write(response_to_reset, SIZE_RESPONSE_TO_RESET); file.write(write_password, SIZE_WRITE_PASSWORD); file.write(read_password, SIZE_READ_PASSWORD); file.write(configuration_password, SIZE_CONFIGURATION_PASSWORD); file.write(configuration_registers, SIZE_CONFIGURATION_REGISTERS); file.write(data, SIZE_DATA); }