mame/src/emu/cpu/psx/dma.c
smf- d1b109625d "You have taken your first step into a larger world." ―Obi-Wan Kenobi
Converted PlayStation DMA to an internal device to the CPU core. DMA to external devices can be set in the machine config, the old calls are still there until the rest of the code is converted. [smf]

The following MAME core changes have been required to allow internal devices to be configurable by the main machine config & to work with internal memory maps.
 device.machine_config_additions() are now processed as soon as the device is added, so sub devices can be configured straight away. 
 replacing or removing a device removes any devices owned by the device being removed, as now they are added straight away.
 device_t::subdevice() uses the machine config device list instead of the machine to find the device as the machine is not created until after all the devices have been created.
 devices in an internal address map are assumed to be owned by the CPU, while devices in a standard address maps are assumed to be siblings of the CPU.


A code review and regression test would be a good idea.
2011-05-06 23:55:53 +00:00

429 lines
11 KiB
C

/*
* PlayStation DMA emulator
*
* Copyright 2003-2011 smf
*
*/
#include "emu.h"
#include "dma.h"
#include "includes/psx.h"
#define VERBOSE_LEVEL ( 0 )
INLINE void ATTR_PRINTF(3,4) verboselog( running_machine& machine, 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: %s", machine.describe_context(), buf );
}
}
const device_type PSX_DMA = &device_creator<psxdma_device>;
psxdma_device::psxdma_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
: device_t(mconfig, PSX_DMA, "PSX DMA", tag, owner, clock)
{
for( int index = 0; index < 7; index++ )
{
psx_dma_channel *dma = &channel[ index ];
dma->fn_read = NULL;
dma->fn_write = NULL;
}
}
void psxdma_device::device_reset()
{
int n;
n_dpcp = 0;
n_dicr = 0;
for( n = 0; n < 7; n++ )
{
dma_stop_timer( n );
}
}
void psxdma_device::device_post_load()
{
int n;
for( n = 0; n < 7; n++ )
{
dma_timer_adjust( n );
}
}
void psxdma_device::device_start()
{
for( int index = 0; index < 7; index++ )
{
psx_dma_channel *dma = &channel[ index ];
dma->timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(psxdma_device::dma_finished_callback), this));
machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_base) );
machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_blockcontrol) );
machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_channelcontrol) );
machine().save().save_item( "psxdma", tag(), index, NAME(dma->n_ticks) );
machine().save().save_item( "psxdma", tag(), index, NAME(dma->b_running) );
}
save_item( NAME(n_dpcp) );
save_item( NAME(n_dicr) );
}
void psxdma_device::dma_start_timer( int index, UINT32 n_ticks )
{
psx_dma_channel *dma = &channel[ index ];
dma->timer->adjust( attotime::from_hz(33868800) * n_ticks, index);
dma->n_ticks = n_ticks;
dma->b_running = 1;
}
void psxdma_device::dma_stop_timer( int index )
{
psx_dma_channel *dma = &channel[ index ];
dma->timer->adjust( attotime::never);
dma->b_running = 0;
}
void psxdma_device::dma_timer_adjust( int index )
{
psx_dma_channel *dma = &channel[ index ];
if( dma->b_running )
{
dma_start_timer( index, dma->n_ticks );
}
else
{
dma_stop_timer( index );
}
}
void psxdma_device::dma_interrupt_update()
{
int n_int;
int n_mask;
n_int = ( n_dicr >> 24 ) & 0x7f;
n_mask = ( n_dicr >> 16 ) & 0xff;
if( ( n_mask & 0x80 ) != 0 && ( n_int & n_mask ) != 0 )
{
verboselog( machine(), 2, "dma_interrupt_update( %02x, %02x ) interrupt triggered\n", n_int, n_mask );
n_dicr |= 0x80000000;
psx_irq_set( machine(), PSX_IRQ_DMA );
}
else if( n_int != 0 )
{
verboselog( machine(), 2, "dma_interrupt_update( %02x, %02x ) interrupt not enabled\n", n_int, n_mask );
}
n_dicr &= 0x00ffffff | ( n_dicr << 8 );
}
void psxdma_device::dma_finished( int index )
{
psx_machine *p_psx = machine().driver_data<psx_state>()->m_p_psx;
UINT32 *p_n_psxram = p_psx->p_n_psxram;
psx_dma_channel *dma = &channel[ index ];
if( dma->n_channelcontrol == 0x01000401 && index == 2 )
{
UINT32 n_size;
UINT32 n_total;
UINT32 n_address = ( dma->n_base & 0xffffff );
UINT32 n_adrmask = p_psx->n_psxramsize - 1;
UINT32 n_nextaddress;
if( n_address != 0xffffff )
{
n_total = 0;
for( ;; )
{
if( n_address == 0xffffff )
{
dma->n_base = n_address;
dma_start_timer( index, 19000 );
return;
}
if( n_total > 65535 )
{
dma->n_base = n_address;
//FIXME:
// 16000 below is based on try and error.
// Mametesters.org: sfex20103red
//dma_start_timer( index, 16 );
dma_start_timer( index, 16000 );
return;
}
n_address &= n_adrmask;
n_nextaddress = p_n_psxram[ n_address / 4 ];
n_size = n_nextaddress >> 24;
(*dma->fn_write)( machine(), n_address + 4, n_size );
//FIXME:
// The following conditions will cause an endless loop.
// If stopping the transfer is correct I cannot judge
// The patch is meant as a hint for somebody who knows
// the hardware.
// Mametesters.org: psyforce0105u5red, raystorm0111u1red
if ((n_nextaddress & 0xffffff) != 0xffffff)
if (n_address == p_n_psxram[ (n_nextaddress & 0xffffff) / 4])
break;
if (n_address == (n_nextaddress & 0xffffff) )
break;
n_address = ( n_nextaddress & 0xffffff );
n_total += ( n_size + 1 );
}
}
}
dma->n_channelcontrol &= ~( ( 1L << 0x18 ) | ( 1L << 0x1c ) );
n_dicr |= 1 << ( 24 + index );
dma_interrupt_update();
dma_stop_timer( index );
}
void psxdma_device::dma_finished_callback(void *ptr, int param)
{
dma_finished(param);
}
void psxdma_device::install_read_handler( int index, psx_dma_read_handler p_fn_dma_read )
{
channel[ index ].fn_read = p_fn_dma_read;
}
void psxdma_device::install_write_handler( int index, psx_dma_read_handler p_fn_dma_write )
{
channel[ index ].fn_write = p_fn_dma_write;
}
WRITE32_MEMBER( psxdma_device::write )
{
psx_machine *p_psx = machine().driver_data<psx_state>()->m_p_psx;
UINT32 *p_n_psxram = p_psx->p_n_psxram;
int index = offset / 4;
psx_dma_channel *dma = &channel[ index ];
if( index < 7 )
{
switch( offset % 4 )
{
case 0:
verboselog( machine(), 2, "dmabase( %d ) = %08x\n", index, data );
dma->n_base = data;
break;
case 1:
verboselog( machine(), 2, "dmablockcontrol( %d ) = %08x\n", index, data );
dma->n_blockcontrol = data;
break;
case 2:
verboselog( machine(), 2, "dmachannelcontrol( %d ) = %08x\n", index, data );
dma->n_channelcontrol = data;
if( ( dma->n_channelcontrol & ( 1L << 0x18 ) ) != 0 && ( n_dpcp & ( 1 << ( 3 + ( index * 4 ) ) ) ) != 0 )
{
INT32 n_size;
UINT32 n_address;
UINT32 n_nextaddress;
UINT32 n_adrmask;
n_adrmask = p_psx->n_psxramsize - 1;
n_address = ( dma->n_base & n_adrmask );
n_size = dma->n_blockcontrol;
if( ( dma->n_channelcontrol & 0x200 ) != 0 )
{
UINT32 n_ba;
n_ba = dma->n_blockcontrol >> 16;
if( n_ba == 0 )
{
n_ba = 0x10000;
}
n_size = ( n_size & 0xffff ) * n_ba;
}
if( dma->n_channelcontrol == 0x01000000 &&
dma->fn_read != NULL )
{
verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size );
(*dma->fn_read)( machine(), n_address, n_size );
dma_finished( index );
}
else if (dma->n_channelcontrol == 0x11000000 && // CD DMA
dma->fn_read != NULL )
{
verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size );
// pSX's CD DMA size calc formula
int oursize = (dma->n_blockcontrol>>16);
oursize = (oursize > 1) ? oursize : 1;
oursize *= (dma->n_blockcontrol&0xffff);
(*dma->fn_read)( machine(), n_address, oursize );
dma_finished( index );
}
else if( dma->n_channelcontrol == 0x01000200 &&
dma->fn_read != NULL )
{
verboselog( machine(), 1, "dma %d read block %08x %08x\n", index, n_address, n_size );
(*dma->fn_read)( machine(), n_address, n_size );
if( index == 1 )
{
dma_start_timer( index, 26000 );
}
else
{
dma_finished( index );
}
}
else if( dma->n_channelcontrol == 0x01000201 &&
dma->fn_write != NULL )
{
verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size );
(*dma->fn_write)( machine(), n_address, n_size );
dma_finished( index );
}
else if( dma->n_channelcontrol == 0x11050100 &&
dma->fn_write != NULL )
{
/* todo: check this is a write not a read... */
verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size );
(*dma->fn_write)( machine(), n_address, n_size );
dma_finished( index );
}
else if( dma->n_channelcontrol == 0x11150100 &&
dma->fn_write != NULL )
{
/* todo: check this is a write not a read... */
verboselog( machine(), 1, "dma %d write block %08x %08x\n", index, n_address, n_size );
(*dma->fn_write)( machine(), n_address, n_size );
dma_finished( index );
}
else if( dma->n_channelcontrol == 0x01000401 &&
index == 2 &&
dma->fn_write != NULL )
{
verboselog( machine(), 1, "dma %d write linked list %08x\n",
index, dma->n_base );
dma_finished( index );
}
else if( dma->n_channelcontrol == 0x11000002 &&
index == 6 )
{
verboselog( machine(), 1, "dma 6 reverse clear %08x %08x\n",
dma->n_base, dma->n_blockcontrol );
if( n_size > 0 )
{
n_size--;
while( n_size > 0 )
{
n_nextaddress = ( n_address - 4 ) & 0xffffff;
p_n_psxram[ n_address / 4 ] = n_nextaddress;
n_address = n_nextaddress;
n_size--;
}
p_n_psxram[ n_address / 4 ] = 0xffffff;
}
dma_start_timer( index, 2150 );
}
else
{
verboselog( machine(), 1, "dma %d unknown mode %08x\n", index, dma->n_channelcontrol );
}
}
else if( dma->n_channelcontrol != 0 )
{
verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) channel not enabled\n", offset, dma->n_channelcontrol, mem_mask );
}
break;
default:
verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) Unknown dma channel register\n", offset, data, mem_mask );
break;
}
}
else
{
switch( offset % 4 )
{
case 0x0:
verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) dpcp\n", offset, data, mem_mask );
n_dpcp = ( n_dpcp & ~mem_mask ) | data;
break;
case 0x1:
n_dicr = ( n_dicr & ( 0x80000000 | ~mem_mask ) ) |
( n_dicr & ~data & 0x7f000000 & mem_mask ) |
( data & 0x00ffffff & mem_mask );
if( ( n_dicr & 0x80000000 ) != 0 && ( n_dicr & 0x7f000000 ) == 0 )
{
verboselog( machine(), 2, "dma interrupt cleared\n" );
n_dicr &= ~0x80000000;
}
verboselog( machine(), 1, "psx_dma_w( %04x, %08x, %08x ) dicr -> %08x\n", offset, data, mem_mask, n_dicr );
break;
default:
verboselog( machine(), 0, "psx_dma_w( %04x, %08x, %08x ) Unknown dma control register\n", offset, data, mem_mask );
break;
}
}
}
READ32_MEMBER( psxdma_device::read )
{
int index = offset / 4;
psx_dma_channel *dma = &channel[ index ];
if( index < 7 )
{
switch( offset % 4 )
{
case 0:
verboselog( machine(), 1, "psx_dma_r dmabase[ %d ] ( %08x )\n", index, dma->n_base );
return dma->n_base;
case 1:
verboselog( machine(), 1, "psx_dma_r dmablockcontrol[ %d ] ( %08x )\n", index, dma->n_blockcontrol );
return dma->n_blockcontrol;
case 2:
verboselog( machine(), 1, "psx_dma_r dmachannelcontrol[ %d ] ( %08x )\n", index, dma->n_channelcontrol );
return dma->n_channelcontrol;
default:
verboselog( machine(), 0, "psx_dma_r( %08x, %08x ) Unknown dma channel register\n", offset, mem_mask );
break;
}
}
else
{
switch( offset % 4 )
{
case 0x0:
verboselog( machine(), 1, "psx_dma_r dpcp ( %08x )\n", n_dpcp );
return n_dpcp;
case 0x1:
verboselog( machine(), 1, "psx_dma_r dicr ( %08x )\n", n_dicr );
return n_dicr;
default:
verboselog( machine(), 0, "psx_dma_r( %08x, %08x ) Unknown dma control register\n", offset, mem_mask );
break;
}
}
return 0;
}