// license:BSD-3-Clause
// copyright-holders:smf
/*
* zs01.c
*
* Secure SerialFlash
*
* This is a high level emulation of the PIC used in some of the System 573 security cartridges.
*
*/
#include "emu.h"
#include "machine/zs01.h"
#define VERBOSE_LEVEL ( 0 )
inline void ATTR_PRINTF( 3, 4 ) zs01_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: zs01(%s) %s", machine().describe_context(), tag(), buf );
}
}
// device type definition
DEFINE_DEVICE_TYPE(ZS01, zs01_device, "zs01", "Konami ZS01 PIC")
zs01_device::zs01_device( const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock ) :
device_t(mconfig, ZS01, tag, owner, clock),
device_nvram_interface(mconfig, *this),
m_ds2401(*this, finder_base::DUMMY_TAG),
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 )
{
}
void zs01_device::device_start()
{
if( !m_ds2401 )
logerror( "ds2401 '%s' not found\n", m_ds2401.finder_tag() );
memset( m_write_buffer, 0, sizeof( m_write_buffer ) );
memset( m_read_buffer, 0, sizeof( m_read_buffer ) );
memset( m_response_key, 0, sizeof( m_response_key ) );
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_write_buffer ) );
save_item( NAME( m_read_buffer ) );
save_item( NAME( m_response_key ) );
save_item( NAME( m_response_to_reset ) );
save_item( NAME( m_command_key ) );
save_item( NAME( m_data_key ) );
save_item( NAME( m_data ) );
}
WRITE_LINE_MEMBER( zs01_device::write_rst )
{
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;
}
WRITE_LINE_MEMBER( zs01_device::write_cs )
{
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 zs01_device::decrypt( uint8_t *destination, uint8_t *source, int length, uint8_t *key, uint8_t previous_byte )
{
uint32_t a0;
uint32_t v1;
uint32_t v0;
uint32_t a1;
uint32_t t1;
uint32_t t0;
length--;
if( length >= 0 )
{
do
{
t1 = source[ length ];
a1 = 7;
t0 = t1;
do
{
v1 = key[ a1 ];
a1--;
v0 = v1 & 0x1f;
v0 = t0 - v0;
v1 >>= 5;
v0 &= 0xff;
a0 = (signed)v0 >> v1;
v1 = 8 - v1;
v1 &= 7;
v0 = (signed)v0 << v1;
t0 = a0 | v0;
} while( a1 > 0 );
v1 = key[ 0 ];
a0 = previous_byte;
v0 = t0 & 0xff;
previous_byte = t1;
v0 = v0 - v1;
v0 = v0 ^ a0;
destination[ length ] = v0;
length--;
}
while( length >= 0 );
}
}
void zs01_device::decrypt2( uint8_t *destination, uint8_t *source, int length, uint8_t *key, uint8_t previous_byte )
{
uint32_t a0;
uint32_t v1;
uint32_t v0;
uint32_t a1;
uint32_t t2;
uint32_t t1;
uint32_t t0;
t2 = 0;
if( length >= 0 )
{
do
{
t1 = source[ t2 ];
a1 = 7;
t0 = t1;
do
{
v1 = key[ a1 ];
a1--;
v0 = v1 & 0x1f;
v0 = t0 - v0;
v1 >>= 5;
v0 &= 0xff;
a0 = (signed)v0 >> v1;
v1 = 8 - v1;
v1 &= 7;
v0 = (signed)v0 << v1;
t0 = a0 | v0;
} while( a1 > 0 );
v1 = key[ 0 ];
a0 = previous_byte;
v0 = t0 & 0xff;
previous_byte = t1;
v0 = v0 - v1;
v0 = v0 ^ a0;
destination[ t2 ] = v0;
t2++;
} while( t2 < length );
}
}
void zs01_device::encrypt( uint8_t *destination, uint8_t *source, int length, uint8_t *key, uint32_t previous_byte )
{
uint32_t t0;
uint32_t v0;
uint32_t v1;
uint32_t a0;
uint32_t a1;
length--;
if( length >= 0 )
{
do
{
t0 = 1;
v0 = source[ length ];
v1 = previous_byte;
a0 = key[ 0 ];
v0 ^= v1;
a0 += v0;
do
{
a1 = key[ t0 ];
t0++;
a0 &= 0xff;
v0 = a1 >> 5;
v1 = a0 << v0;
v0 = 8 - v0;
v0 &= 7;
a0 = (signed) a0 >> v0;
v1 |= a0;
v1 &= 0xff;
a1 &= 0x1f;
v1 += a1;
v0 = (signed) t0 < 8;
a0 = v1;
} while( v0 != 0 );
previous_byte = v1;
destination[ length ] = a0;
length--;
} while( length >= 0 );
}
}
uint16_t zs01_device::calc_crc( uint8_t *buffer, uint32_t length )
{
uint32_t v1;
uint32_t a3;
uint32_t v0;
uint32_t a2;
v1 = 0xffff;
a3 = 0;
if( length > 0 )
{
do
{
v0 = buffer[ a3 ];
a2 = 7;
v0 = v0 << 8;
v1 = v1 ^ v0;
v0 = v1 & 0x8000;
do
{
if( v0 != 0 )
{
v0 = v1 << 1;
v1 = v0 ^ 0x1021;
}
else
{
v0 = v1 << 1;
v1 = v1 << 1;
}
a2--;
v0 = v1 & 0x8000;
} while( (signed) a2 >= 0 );
a3++;
v0 = (signed) a3 < (signed) length;
} while ( v0 != 0 );
}
v0 = ~v1 ;
v0 = v0 & 0xffff;
return v0;
}
int zs01_device::data_offset()
{
int block = ( ( m_write_buffer[ 0 ] & 2 ) << 7 ) | m_write_buffer[ 1 ];
return block * SIZE_DATA_BUFFER;
}
WRITE_LINE_MEMBER( zs01_device::write_scl )
{
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 )
{
if( m_bit == 0 )
{
m_shift = m_response_to_reset[ m_byte ];
verboselog( 1, "<- response_to_reset[ %d ]: %02x\n", m_byte, m_shift );
}
m_sdar = ( m_shift >> 7 ) & 1;
m_shift <<= 1;
m_bit++;
if( m_bit == 8 )
{
m_bit = 0;
m_byte++;
if( m_byte == sizeof( m_response_to_reset ) )
{
m_sdar = 1;
verboselog( 1, "goto stop\n" );
m_state = STATE_STOP;
}
}
}
break;
case STATE_LOAD_COMMAND:
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_write_buffer[ m_byte ] = m_shift;
verboselog( 2, "-> write_buffer[ %d ]: %02x\n", m_byte, m_write_buffer[ m_byte ] );
m_byte++;
if( m_byte == sizeof( m_write_buffer ) )
{
decrypt( m_write_buffer, m_write_buffer, sizeof( m_write_buffer ), m_command_key, 0xff );
if( ( m_write_buffer[ 0 ] & 4 ) != 0 )
{
decrypt2( &m_write_buffer[ 2 ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER, m_data_key, 0x00 );
}
uint16_t crc = calc_crc( m_write_buffer, 10 );
if( crc == ( ( m_write_buffer[ 10 ] << 8 ) | m_write_buffer[ 11 ] ) )
{
verboselog( 1, "-> command: %02x\n", m_write_buffer[ 0 ] );
verboselog( 1, "-> address: %02x\n", m_write_buffer[ 1 ] );
verboselog( 1, "-> data: %02x%02x%02x%02x%02x%02x%02x%02x\n",
m_write_buffer[ 2 ], m_write_buffer[ 3 ], m_write_buffer[ 4 ], m_write_buffer[ 5 ],
m_write_buffer[ 6 ], m_write_buffer[ 7 ], m_write_buffer[ 8 ], m_write_buffer[ 9 ] );
verboselog( 1, "-> crc: %02x%02x\n", m_write_buffer[ 10 ], m_write_buffer[ 11 ] );
switch( m_write_buffer[ 0 ] & 1 )
{
case COMMAND_WRITE:
memcpy( &m_data[ data_offset() ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER );
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0, sizeof( m_write_buffer ) );
break;
case COMMAND_READ:
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0, 2 );
switch( m_write_buffer[ 1 ] )
{
case 0xfd:
{
/* TODO: use read/write to talk to the ds2401, which will require a timer. */
for( int i = 0; i < SIZE_DATA_BUFFER; i++ )
{
m_read_buffer[ 2 + i ] = m_ds2401->direct_read( SIZE_DATA_BUFFER - i - 1 );
}
}
break;
default:
memcpy( &m_read_buffer[ 2 ], &m_data[ data_offset() ], SIZE_DATA_BUFFER );
break;
}
memcpy( m_response_key, &m_write_buffer[ 2 ], sizeof( m_response_key ) );
break;
}
}
else
{
verboselog( 0, "bad crc\n" );
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0xff, 2 );
}
verboselog( 1, "<- status: %02x%02x\n",
m_read_buffer[ 0 ], m_read_buffer[ 1 ] );
verboselog( 1, "<- data: %02x%02x%02x%02x%02x%02x%02x%02x\n",
m_read_buffer[ 2 ], m_read_buffer[ 3 ], m_read_buffer[ 4 ], m_read_buffer[ 5 ],
m_read_buffer[ 6 ], m_read_buffer[ 7 ], m_read_buffer[ 8 ], m_read_buffer[ 9 ] );
crc = calc_crc( m_read_buffer, 10 );
m_read_buffer[ 10 ] = crc >> 8;
m_read_buffer[ 11 ] = crc & 255;
encrypt( m_read_buffer, m_read_buffer, sizeof( m_read_buffer ), m_response_key, 0xff );
m_byte = 0;
m_state = STATE_READ_DATA;
}
break;
}
m_bit = 0;
m_shift = 0;
}
}
break;
case STATE_READ_DATA:
if( m_scl == 0 && state != 0 )
{
if( m_bit < 8 )
{
if( m_bit == 0 )
{
switch( m_state )
{
case STATE_READ_DATA:
m_shift = m_read_buffer[ m_byte ];
verboselog( 2, "<- read_buffer[ %d ]: %02x\n", m_byte, 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++;
if( m_byte == sizeof( m_read_buffer ) )
{
m_byte = 0;
m_sdar = 1;
m_state = STATE_LOAD_COMMAND;
}
}
else
{
verboselog( 2, "nak <-\n" );
}
}
}
break;
}
}
m_scl = state;
}
WRITE_LINE_MEMBER( zs01_device::write_sda )
{
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;
// default:
// verboselog( 1, "skipped start (default)\n" );
// break;
}
m_bit = 0;
m_byte = 0;
m_shift = 0;
m_sdar = 0;
}
}
m_sdaw = state;
}
READ_LINE_MEMBER( zs01_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 zs01_device::nvram_default()
{
memset( m_response_to_reset, 0, sizeof( m_response_to_reset ) );
memset( m_command_key, 0, sizeof( m_command_key ) );
memset( m_data_key, 0, sizeof( m_data_key ) );
memset( m_data, 0, sizeof( m_data ) );
int expected_bytes = sizeof( m_response_to_reset ) + sizeof( m_command_key ) + sizeof( m_data_key ) + sizeof( m_data );
if (!m_region.found())
{
logerror( "zs01(%s) region not found\n", tag() );
}
else if( m_region->bytes() != expected_bytes )
{
logerror( "zs01(%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_command_key, region, sizeof( m_command_key ) ); region += sizeof( m_command_key );
memcpy( m_data_key, region, sizeof( m_data_key ) ); region += sizeof( m_data_key );
memcpy( m_data, region, sizeof( m_data ) ); region += sizeof( m_data );
}
}
void zs01_device::nvram_read( emu_file &file )
{
file.read( m_response_to_reset, sizeof( m_response_to_reset ) );
file.read( m_command_key, sizeof( m_command_key ) );
file.read( m_data_key, sizeof( m_data_key ) );
file.read( m_data, sizeof( m_data ) );
}
void zs01_device::nvram_write( emu_file &file )
{
file.write( m_response_to_reset, sizeof( m_response_to_reset ) );
file.write( m_command_key, sizeof( m_command_key ) );
file.write( m_data_key, sizeof( m_data_key ) );
file.write( m_data, sizeof( m_data ) );
}