// license:BSD-3-Clause // copyright-holders:smf /* * 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 "x76f041.h" #include #include #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( "%s: x76f041(%s) %s", machine().describe_context(), tag(), buf ); } } // device type definition DEFINE_DEVICE_TYPE(X76F041, x76f041_device, "x76f041", "X76F041 Secure SerialFlash") x76f041_device::x76f041_device( const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock ) : device_t( mconfig, X76F041, tag, owner, clock ), device_nvram_interface(mconfig, *this), m_region(*this, DEVICE_SELF), m_cs( 0 ), m_rst( 0 ), m_scl( 0 ), m_sdaw( 0 ), m_sdar( 0 ), m_state( STATE_STOP ), m_shift( 0 ), m_bit( 0 ), m_byte( 0 ), m_command( 0 ), m_address( 0 ), m_is_password_accepted ( false ) { } void x76f041_device::device_start() { std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 ); std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 ); save_item( NAME( m_cs ) ); save_item( NAME( m_rst ) ); save_item( NAME( m_scl ) ); save_item( NAME( m_sdaw ) ); save_item( NAME( m_sdar ) ); save_item( NAME( m_state ) ); save_item( NAME( m_shift ) ); save_item( NAME( m_bit ) ); save_item( NAME( m_byte ) ); save_item( NAME( m_command ) ); save_item( NAME( m_address ) ); save_item( NAME( m_is_password_accepted ) ); save_item( NAME( m_write_buffer ) ); save_item( NAME( m_response_to_reset ) ); save_item( NAME( m_write_password ) ); save_item( NAME( m_read_password ) ); save_item( NAME( m_configuration_password ) ); save_item( NAME( m_configuration_registers ) ); save_item( NAME( m_data ) ); save_item( NAME( m_password_temp ) ); } void x76f041_device::device_reset() { std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 ); std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 ); m_cs = 0; m_rst = 0; m_scl = 0; m_sdaw = 0; m_sdar = 0; m_state = STATE_STOP; m_shift = 0; m_bit = 0; m_byte = 0; m_command = 0; m_address = 0; m_is_password_accepted = false; } void x76f041_device::write_cs(int state) { if( m_cs != state ) { verboselog( 2, "cs=%d\n", state ); } if( m_cs != 0 && state == 0 ) { /* enable chip */ m_state = STATE_STOP; } if( m_cs == 0 && state != 0 ) { /* disable chip */ m_state = STATE_STOP; /* high impendence? */ m_sdar = 0; } m_cs = state; } void x76f041_device::write_rst(int state) { if( m_rst != state ) { verboselog( 2, "rst=%d\n", state ); } if( m_rst == 0 && state != 0 && m_cs == 0 ) { verboselog( 1, "goto response to reset\n" ); m_state = STATE_RESPONSE_TO_RESET; m_bit = 0; m_byte = 0; } m_rst = state; } uint8_t *x76f041_device::password() { switch( m_command & 0xe0 ) { case COMMAND_WRITE: return m_write_password; case COMMAND_READ: return m_read_password; case COMMAND_CONFIGURATION: if( m_address == CONFIGURATION_PROGRAM_WRITE_PASSWORD ) return m_write_password; if( m_address == CONFIGURATION_PROGRAM_READ_PASSWORD ) return m_read_password; return m_configuration_password; default: return m_configuration_password; } } void x76f041_device::password_ok() { if( m_configuration_registers[CONFIG_CR] & CR_RETRY_COUNTER_RESET_BIT ) m_configuration_registers[CONFIG_RC] = 0; switch( m_command & 0xe0 ) { case COMMAND_WRITE: m_state = STATE_WRITE_DATA; break; case COMMAND_READ: m_state = STATE_READ_DATA; break; case COMMAND_WRITE_USE_CONFIGURATION_PASSWORD: m_state = STATE_CONFIGURATION_WRITE_DATA; break; case COMMAND_READ_USE_CONFIGURATION_PASSWORD: m_state = STATE_READ_DATA; break; case COMMAND_CONFIGURATION: switch( m_address ) { case CONFIGURATION_PROGRAM_WRITE_PASSWORD: m_state = STATE_PROGRAM_WRITE_PASSWORD; m_byte = 0; break; case CONFIGURATION_PROGRAM_READ_PASSWORD: m_state = STATE_PROGRAM_READ_PASSWORD; m_byte = 0; break; case CONFIGURATION_PROGRAM_CONFIGURATION_PASSWORD: m_state = STATE_PROGRAM_CONFIGURATION_PASSWORD; m_byte = 0; break; case CONFIGURATION_RESET_WRITE_PASSWORD: m_state = STATE_RESET_WRITE_PASSWORD; break; case CONFIGURATION_RESET_READ_PASSWORD: m_state = STATE_RESET_READ_PASSWORD; break; case CONFIGURATION_PROGRAM_CONFIGURATION_REGISTERS: m_state = STATE_WRITE_CONFIGURATION_REGISTERS; m_byte = 0; break; case CONFIGURATION_READ_CONFIGURATION_REGISTERS: m_state = STATE_READ_CONFIGURATION_REGISTERS; m_byte = 0; break; case CONFIGURATION_MASS_PROGRAM: m_state = STATE_MASS_PROGRAM; break; case CONFIGURATION_MASS_ERASE: m_state = STATE_MASS_ERASE; break; default: break; } } } void x76f041_device::load_address() { m_address = m_shift; verboselog( 1, "-> address: %02x\n", m_address ); if( ( m_configuration_registers[CONFIG_CR] & CR_RETRY_COUNTER_ENABLE_BIT ) != 0 && m_configuration_registers[CONFIG_RR] == m_configuration_registers[CONFIG_RC] && ( m_configuration_registers[CONFIG_CR] & CR_UNAUTHORIZED_ACCESS_BITS ) == 0x80 ) { // No commands are allowed verboselog( 1, "unauthorized access rejected\n" ); m_state = STATE_STOP; m_sdar = 1; m_byte = 0; return; } if( ( m_command & 0xe0 ) == COMMAND_CONFIGURATION ) { // Configuration commands can be used regardless of array control register bits if( m_address == CONFIGURATION_RESET_WRITE_PASSWORD || m_address == CONFIGURATION_RESET_READ_PASSWORD || m_address == CONFIGURATION_MASS_PROGRAM || m_address == CONFIGURATION_MASS_ERASE ) { verboselog( 1, "password not required\n" ); password_ok(); } else { verboselog( 1, "send password\n" ); m_state = STATE_LOAD_PASSWORD; m_byte = 0; } return; } if( ( m_configuration_registers[CONFIG_CR] & CR_RETRY_COUNTER_ENABLE_BIT ) != 0 && m_configuration_registers[CONFIG_RR] == m_configuration_registers[CONFIG_RC] && ( m_configuration_registers[CONFIG_CR] & CR_UNAUTHORIZED_ACCESS_BITS ) != 0x80 ) { // Only configuration commands are allowed verboselog( 1, "unauthorized access rejected\n" ); m_state = STATE_STOP; m_sdar = 1; m_byte = 0; return; } int bcr = m_configuration_registers[( m_command & 1 ) ? CONFIG_BCR2 : CONFIG_BCR1]; if( ( m_address & 0x80 ) != 0 ) { bcr >>= 4; } if( ( ( m_command & 0xe0 ) == COMMAND_READ && ( bcr & BCR_Z ) != 0 && ( bcr & BCR_T ) != 0 ) || ( ( m_command & 0xe0 ) == COMMAND_WRITE && ( bcr & BCR_Z ) != 0 ) ) { /* TODO: find out when this is really checked. */ verboselog( 1, "command not allowed\n" ); m_state = STATE_STOP; m_sdar = 1; m_byte = 0; } else if( ( ( m_command & 0xe0 ) == COMMAND_WRITE && ( bcr & BCR_X ) == 0 ) || ( ( m_command & 0xe0 ) == COMMAND_READ && ( bcr & BCR_Y ) == 0 ) ) { verboselog( 1, "password not required\n" ); password_ok(); } else { verboselog( 1, "send password\n" ); m_state = STATE_LOAD_PASSWORD; m_byte = 0; } } int x76f041_device::data_offset() { int block_offset = ( ( m_command & 1 ) << 8 ) + m_address; // TODO: confirm block_start doesn't wrap. return ( block_offset & 0x180 ) | ( ( block_offset + m_byte ) & 0x7f ); } void x76f041_device::write_scl(int state) { if( m_scl != state ) { verboselog( 2, "scl=%d\n", state ); } if( m_cs == 0 ) { switch( m_state ) { case STATE_STOP: break; case STATE_RESPONSE_TO_RESET: if( m_scl != 0 && state == 0 ) { m_sdar = ( m_response_to_reset[m_byte] >> m_bit ) & 1; verboselog( 2, "in response to reset %d (%d/%d)\n", m_sdar, m_byte, m_bit ); m_bit++; if( m_bit == 8 ) { m_bit = 0; m_byte++; if( m_byte == sizeof( m_response_to_reset ) ) { m_byte = 0; } } } break; case STATE_LOAD_COMMAND: case STATE_LOAD_ADDRESS: case STATE_LOAD_PASSWORD: case STATE_VERIFY_PASSWORD: case STATE_WRITE_DATA: case STATE_CONFIGURATION_WRITE_DATA: case STATE_WRITE_CONFIGURATION_REGISTERS: case STATE_PROGRAM_WRITE_PASSWORD: case STATE_PROGRAM_READ_PASSWORD: case STATE_PROGRAM_CONFIGURATION_PASSWORD: case STATE_RESET_WRITE_PASSWORD: case STATE_RESET_READ_PASSWORD: case STATE_MASS_PROGRAM: case STATE_MASS_ERASE: // FIXME: Processing on the rising edge of the clock causes sda to change state while clock is high // which is not allowed. Also need to ensure that only valid device-id's and commands // are acknowledged. if( m_scl == 0 && state != 0 ) { if( m_bit < 8 ) { verboselog( 2, "clock\n" ); m_shift <<= 1; if( m_sdaw != 0 ) { m_shift |= 1; } m_bit++; } else { m_sdar = 0; switch( m_state ) { case STATE_LOAD_COMMAND: m_command = m_shift; verboselog( 1, "-> command: %02x\n", m_command ); /* TODO: verify command is valid? */ m_state = STATE_LOAD_ADDRESS; break; case STATE_LOAD_ADDRESS: load_address(); break; case STATE_LOAD_PASSWORD: verboselog( 1, "-> password: %02x\n", m_shift ); m_write_buffer[m_byte++] = m_shift; if( m_byte == sizeof( m_write_buffer ) ) { m_state = STATE_VERIFY_PASSWORD; // Perform the password acceptance check before verify password because // password verify ack is spammed and will quickly overflow the // retry counter. m_is_password_accepted = memcmp( password(), m_write_buffer, sizeof( m_write_buffer ) ) == 0; if( !m_is_password_accepted ) { if( m_configuration_registers[CONFIG_CR] & CR_RETRY_COUNTER_ENABLE_BIT ) m_configuration_registers[CONFIG_RC]++; } } break; case STATE_VERIFY_PASSWORD: verboselog( 1, "-> verify password: %02x\n", m_shift ); /* TODO: this should probably be handled as a command */ if( m_shift == 0xc0 ) { /* TODO: this should take 10ms before it returns ok. */ if( m_is_password_accepted ) { password_ok(); } else { m_sdar = 1; } } break; case STATE_WRITE_DATA: verboselog( 2, "-> data: %02x\n", m_shift ); m_write_buffer[m_byte++] = m_shift; if( m_byte == sizeof( m_write_buffer ) ) { int bcr = m_configuration_registers[( m_command & 1 ) ? CONFIG_BCR2 : CONFIG_BCR1]; if( ( m_address & 0x80 ) != 0 ) { bcr >>= 4; } if( ( bcr & ( BCR_Z | BCR_T ) ) == BCR_T ) { // Bits in the data can only be set, not cleared, when in program only mode bool is_unauthorized_write = false; for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ ) { int offset = data_offset(); if( m_write_buffer[m_byte] < m_data[offset] ) { verboselog( 1, "tried to unset bits while in program only mode\n" ); is_unauthorized_write = true; break; } } if( is_unauthorized_write ) { m_sdar = 1; m_byte = 0; break; } } for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ ) { int offset = data_offset(); verboselog( 1, "-> data[%03x]: %02x\n", offset, m_write_buffer[m_byte] ); m_data[offset] = m_write_buffer[m_byte]; } m_byte = 0; verboselog( 1, "data flushed\n" ); } break; case STATE_CONFIGURATION_WRITE_DATA: // Unlike normal writes, configuration writes aren't required to be exactly 8 bytes // TODO: Store data in a temporary buffer until the proper end of the command before writing verboselog( 2, "-> data: %02x\n", m_shift ); m_data[data_offset()] = m_shift; m_byte++; break; case STATE_WRITE_CONFIGURATION_REGISTERS: verboselog( 1, "-> configuration register[%d]: %02x\n", m_byte, m_shift ); /* TODO: write after all bytes received? */ m_configuration_registers[m_byte++] = m_shift; if( m_byte == sizeof( m_configuration_registers ) ) { m_byte = 0; } break; case STATE_PROGRAM_WRITE_PASSWORD: verboselog( 1, "-> program write password[%d]: %02x\n", m_byte, m_shift ); m_password_temp[m_byte++] = m_shift; if( m_byte == sizeof( m_password_temp ) ) { // Read in the password twice and if the two copies match then write it to the password field if( memcmp( &m_password_temp[0], &m_password_temp[8], sizeof( m_write_password ) ) == 0 ) { std::copy_n( std::begin( m_password_temp ), sizeof( m_write_password ), std::begin ( m_write_password ) ); } else { m_sdar = 1; } std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 ); m_byte = 0; } break; case STATE_PROGRAM_READ_PASSWORD: verboselog( 1, "-> program read password[%d]: %02x\n", m_byte, m_shift ); m_password_temp[m_byte++] = m_shift; if( m_byte == sizeof( m_password_temp ) ) { if( memcmp( &m_password_temp[0], &m_password_temp[8], sizeof( m_read_password ) ) == 0 ) { std::copy_n( std::begin( m_password_temp ), sizeof( m_read_password ), std::begin ( m_read_password ) ); } else { m_sdar = 1; } std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 ); m_byte = 0; } break; case STATE_PROGRAM_CONFIGURATION_PASSWORD: verboselog( 1, "-> program configuration password[%d]: %02x\n", m_byte, m_shift ); m_password_temp[m_byte++] = m_shift; if( m_byte == sizeof( m_password_temp ) ) { if( memcmp( &m_password_temp[0], &m_password_temp[8], sizeof( m_configuration_password ) ) == 0 ) { std::copy_n( std::begin( m_password_temp ), sizeof( m_configuration_password ), std::begin ( m_configuration_password ) ); } else { m_sdar = 1; } std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 ); m_byte = 0; } break; case STATE_RESET_WRITE_PASSWORD: verboselog( 1, "-> reset write password\n" ); std::fill( std::begin( m_write_password ), std::end( m_write_password ), 0 ); break; case STATE_RESET_READ_PASSWORD: verboselog( 1, "-> reset read password\n" ); std::fill( std::begin( m_read_password ), std::end( m_read_password ), 0 ); break; case STATE_MASS_PROGRAM: case STATE_MASS_ERASE: { const uint8_t fill = m_state == STATE_MASS_ERASE ? 0xff : 0; verboselog( 1, "-> mass erase %02x\n", fill ); std::fill( std::begin( m_data ), std::end( m_data ), fill ); std::fill( std::begin( m_configuration_password ), std::end( m_configuration_password ), fill ); std::fill( std::begin( m_configuration_registers ), std::end( m_configuration_registers ), fill ); std::fill( std::begin( m_write_password ), std::end( m_write_password ), fill ); std::fill( std::begin( m_read_password ), std::end( m_read_password ), fill ); break; } } m_bit = 0; m_shift = 0; } } break; case STATE_READ_DATA: case STATE_READ_CONFIGURATION_REGISTERS: // FIXME: Processing on the rising edge of the clock causes sda to change state while clock is high // which is not allowed. if( m_scl == 0 && state != 0 ) { if( m_bit < 8 ) { if( m_bit == 0 ) { int offset; switch( m_state ) { case STATE_READ_DATA: offset = data_offset(); m_shift = m_data[offset]; verboselog( 1, "<- data[%03x]: %02x\n", offset, m_shift ); break; case STATE_READ_CONFIGURATION_REGISTERS: offset = m_byte & 7; m_shift = m_configuration_registers[offset]; verboselog( 1, "<- configuration register[%d]: %02x\n", offset, m_shift ); break; } } m_sdar = ( m_shift >> 7 ) & 1; m_shift <<= 1; m_bit++; } else { m_bit = 0; m_sdar = 0; if( m_sdaw == 0 ) { verboselog( 2, "ack <-\n" ); m_byte++; } else { verboselog( 2, "nak <-\n" ); } } } break; } } m_scl = state; } void x76f041_device::write_sda(int state) { if( m_sdaw != state ) { verboselog( 2, "sdaw=%d\n", state ); } if( m_cs == 0 && m_scl != 0 ) { if( m_sdaw == 0 && state != 0 ) { verboselog( 1, "goto stop\n" ); m_state = STATE_STOP; m_sdar = 0; } if( m_sdaw != 0 && state == 0 ) { switch( m_state ) { case STATE_STOP: verboselog( 1, "goto start\n" ); m_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\n" ); break; case STATE_READ_DATA: verboselog( 1, "goto load address\n" ); m_state = STATE_LOAD_ADDRESS; break; default: verboselog( 1, "skipped start (default)\n" ); break; } m_bit = 0; m_byte = 0; m_shift = 0; m_sdar = 0; } } m_sdaw = state; } int x76f041_device::read_sda() { if( m_cs != 0 ) { verboselog( 2, "not selected\n" ); return 1; } verboselog( 2, "sdar=%d\n", m_sdar ); return m_sdar; } void x76f041_device::nvram_default() { m_response_to_reset[0] = 0x19; m_response_to_reset[1] = 0x55; m_response_to_reset[2] = 0xaa; m_response_to_reset[3] = 0x55, memset( m_write_password, 0, sizeof( m_write_password ) ); memset( m_read_password, 0, sizeof( m_read_password ) ); memset( m_configuration_password, 0, sizeof( m_configuration_password ) ); memset( m_configuration_registers, 0, sizeof( m_configuration_registers ) ); memset( m_data, 0, sizeof( m_data ) ); int expected_bytes = sizeof( m_response_to_reset ) + sizeof( m_write_password ) + sizeof( m_read_password ) + sizeof( m_configuration_password ) + sizeof( m_configuration_registers ) + sizeof( m_data ); if (!m_region.found()) { logerror( "x76f041(%s) region not found\n", tag() ); } else if( m_region->bytes() != expected_bytes ) { logerror( "x76f041(%s) region length 0x%x expected 0x%x\n", tag(), m_region->bytes(), expected_bytes ); } else { uint8_t *region = m_region->base(); memcpy( m_response_to_reset, region, sizeof( m_response_to_reset ) ); region += sizeof( m_response_to_reset ); memcpy( m_write_password, region, sizeof( m_write_password ) ); region += sizeof( m_write_password ); memcpy( m_read_password, region, sizeof( m_read_password ) ); region += sizeof( m_read_password ); memcpy( m_configuration_password, region, sizeof( m_configuration_password ) ); region += sizeof( m_configuration_password ); memcpy( m_configuration_registers, region, sizeof( m_configuration_registers ) ); region += sizeof( m_configuration_registers ); memcpy( m_data, region, sizeof( m_data ) ); region += sizeof( m_data ); } } bool x76f041_device::nvram_read( util::read_stream &file ) { std::error_condition err; size_t actual; std::tie( err, actual ) = read( file, m_response_to_reset, sizeof( m_response_to_reset ) ); if( err || ( sizeof( m_response_to_reset ) != actual ) ) return false; std::tie( err, actual ) = read( file, m_write_password, sizeof( m_write_password ) ); if( err || ( sizeof( m_write_password ) != actual ) ) return false; std::tie( err, actual ) = read( file, m_read_password, sizeof( m_read_password ) ); if( err || ( sizeof( m_read_password ) != actual ) ) return false; std::tie( err, actual ) = read( file, m_configuration_password, sizeof( m_configuration_password ) ); if( err || ( sizeof( m_configuration_password ) != actual ) ) return false; std::tie( err, actual ) = read( file, m_configuration_registers, sizeof( m_configuration_registers ) ); if( err || ( sizeof( m_configuration_registers ) != actual ) ) return false; std::tie( err, actual ) = read( file, m_data, sizeof( m_data ) ); if( err || ( sizeof( m_data ) != actual ) ) return false; return true; } bool x76f041_device::nvram_write( util::write_stream &file ) { std::error_condition err; size_t actual; std::tie( err, actual ) = write( file, m_response_to_reset, sizeof( m_response_to_reset ) ); if (err) return false; std::tie( err, actual ) = write( file, m_write_password, sizeof( m_write_password ) ); if (err) return false; std::tie( err, actual ) = write( file, m_read_password, sizeof( m_read_password ) ); if (err) return false; std::tie( err, actual ) = write( file, m_configuration_password, sizeof( m_configuration_password ) ); if (err) return false; std::tie( err, actual ) = write( file, m_configuration_registers, sizeof( m_configuration_registers ) ); if (err) return false; std::tie( err, actual ) = write( file, m_data, sizeof( m_data ) ); if (err) return false; return true; }