mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-27 01:41:34 +03:00
523 lines
14 KiB
C
523 lines
14 KiB
C
/* periph.c: code for handling peripherals
|
|
Copyright (c) 2005-2016 Philip Kendall
|
|
Copyright (c) 2015 Stuart Brady
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
Author contact information:
|
|
|
|
E-mail: philip-fuse@shadowmagic.org.uk
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "libspectrum.h"
|
|
|
|
#include "debugger/debugger.h"
|
|
#include "event.h"
|
|
#include "fuse.h"
|
|
#include "periph.h"
|
|
#include "peripherals/if1.h"
|
|
#include "peripherals/multiface.h"
|
|
#include "peripherals/ula.h"
|
|
#include "rzx.h"
|
|
#include "settings.h"
|
|
#include "ui/ui.h"
|
|
|
|
/*
|
|
* General peripheral list handling routines
|
|
*/
|
|
|
|
typedef struct periph_private_t {
|
|
/* Can this peripheral ever be present on the currently emulated machine? */
|
|
periph_present present;
|
|
/* Is this peripheral currently active? */
|
|
int active;
|
|
/* The actual peripheral data */
|
|
const periph_t *periph;
|
|
} periph_private_t;
|
|
|
|
/* All the peripherals we know about */
|
|
static GHashTable *peripherals = NULL;
|
|
|
|
/* Wrapper to pair up a port response with the peripheral it came from */
|
|
typedef struct periph_port_private_t {
|
|
/* The peripheral this came from */
|
|
periph_type type;
|
|
/* The port response */
|
|
periph_port_t port;
|
|
} periph_port_private_t;
|
|
|
|
/* The list of currently active ports */
|
|
static GSList *ports = NULL;
|
|
|
|
/* The strings used for debugger events */
|
|
static const char * const page_event_string = "page",
|
|
* const unpage_event_string = "unpage";
|
|
|
|
/* Place one port response in the list of currently active ones */
|
|
static void
|
|
port_register( periph_type type, const periph_port_t *port )
|
|
{
|
|
periph_port_private_t *private;
|
|
|
|
private = libspectrum_new( periph_port_private_t, 1 );
|
|
|
|
private->type = type;
|
|
private->port = *port;
|
|
|
|
ports = g_slist_append( ports, private );
|
|
}
|
|
|
|
/* Register a peripheral with the system */
|
|
void
|
|
periph_register( periph_type type, const periph_t *periph )
|
|
{
|
|
periph_private_t *private;
|
|
|
|
if( !peripherals )
|
|
peripherals = g_hash_table_new_full( NULL, NULL, NULL, libspectrum_free );
|
|
|
|
private = libspectrum_new( periph_private_t, 1 );
|
|
|
|
private->present = PERIPH_PRESENT_NEVER;
|
|
private->active = 0;
|
|
private->periph = periph;
|
|
|
|
g_hash_table_insert( peripherals, GINT_TO_POINTER( type ), private );
|
|
}
|
|
|
|
/* Get the data about one peripheral */
|
|
static gint
|
|
find_by_type( gconstpointer data, gconstpointer user_data )
|
|
{
|
|
const periph_port_private_t *periph = data;
|
|
periph_type type = GPOINTER_TO_INT( user_data );
|
|
return periph->type - type;
|
|
}
|
|
|
|
/* Set whether a peripheral can be present on this machine or not */
|
|
void
|
|
periph_set_present( periph_type type, periph_present present )
|
|
{
|
|
periph_private_t *type_data = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
|
|
if( type_data ) type_data->present = present;
|
|
}
|
|
|
|
/* Mark a specific peripheral as (in)active */
|
|
int
|
|
periph_activate_type( periph_type type, int active )
|
|
{
|
|
periph_private_t *private = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
|
|
if( !private || private->active == active ) return 0;
|
|
|
|
private->active = active;
|
|
|
|
if( active ) {
|
|
const periph_port_t *ptr;
|
|
if( private->periph->activate )
|
|
private->periph->activate();
|
|
for( ptr = private->periph->ports; ptr && ptr->mask != 0; ptr++ )
|
|
port_register( type, ptr );
|
|
} else {
|
|
GSList *found;
|
|
while( ( found = g_slist_find_custom( ports, GINT_TO_POINTER( type ), find_by_type ) ) != NULL )
|
|
ports = g_slist_remove( ports, found->data );
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Is a specific peripheral active at the moment? */
|
|
int
|
|
periph_is_active( periph_type type )
|
|
{
|
|
periph_private_t *type_data = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
|
|
return type_data ? type_data->active : 0;
|
|
}
|
|
|
|
/* Work out whether a peripheral is present on this machine, and mark it
|
|
(in)active as appropriate */
|
|
static void
|
|
set_activity( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
periph_type type = GPOINTER_TO_INT( key );
|
|
periph_private_t *private = value;
|
|
int active = 0;
|
|
int *needs_hard_reset = (int *)user_data;
|
|
|
|
switch ( private->present ) {
|
|
case PERIPH_PRESENT_NEVER: active = 0; break;
|
|
case PERIPH_PRESENT_OPTIONAL:
|
|
active = private->periph->option ? *(private->periph->option) : 0; break;
|
|
case PERIPH_PRESENT_ALWAYS: active = 1; break;
|
|
}
|
|
|
|
*needs_hard_reset =
|
|
( periph_activate_type( type, active ) && private->periph->hard_reset ) ||
|
|
*needs_hard_reset;
|
|
}
|
|
|
|
/* Work out whether a peripheral needs a hard reset without (de)activate */
|
|
static void
|
|
get_hard_reset( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
periph_private_t *private = value;
|
|
int active = 0;
|
|
int *machine_hard_reset = (int *)user_data;
|
|
int periph_hard_reset = 0;
|
|
|
|
switch ( private->present ) {
|
|
case PERIPH_PRESENT_NEVER: active = 0; break;
|
|
case PERIPH_PRESENT_OPTIONAL:
|
|
active = private->periph->option ? *(private->periph->option) : 0; break;
|
|
case PERIPH_PRESENT_ALWAYS: active = 1; break;
|
|
}
|
|
|
|
periph_hard_reset = ( private && ( private->active != active ) &&
|
|
private->periph->hard_reset );
|
|
|
|
*machine_hard_reset = ( periph_hard_reset || *machine_hard_reset );
|
|
}
|
|
|
|
static void
|
|
disable_optional( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
periph_private_t *private = value;
|
|
|
|
switch ( private->present ) {
|
|
case PERIPH_PRESENT_NEVER:
|
|
case PERIPH_PRESENT_OPTIONAL:
|
|
if( private->periph->option ) *(private->periph->option) = 0;
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* Free the memory used by a peripheral-port response pair */
|
|
static void
|
|
free_peripheral( gpointer data, gpointer user_data GCC_UNUSED )
|
|
{
|
|
periph_port_private_t *private = data;
|
|
libspectrum_free( private );
|
|
}
|
|
|
|
/* Make a peripheral as being never present on this machine */
|
|
static void
|
|
set_type_inactive( gpointer key, gpointer value, gpointer user_data )
|
|
{
|
|
periph_private_t *type_data = value;
|
|
type_data->present = PERIPH_PRESENT_NEVER;
|
|
type_data->active = 0;
|
|
}
|
|
|
|
/* Mark all peripherals as being never present on this machine */
|
|
static void
|
|
set_types_inactive( void )
|
|
{
|
|
g_hash_table_foreach( peripherals, set_type_inactive, NULL );
|
|
}
|
|
|
|
/* Empty out the list of peripherals */
|
|
void
|
|
periph_clear( void )
|
|
{
|
|
g_slist_foreach( ports, free_peripheral, NULL );
|
|
g_slist_free( ports );
|
|
ports = NULL;
|
|
set_types_inactive();
|
|
}
|
|
|
|
/* Tidy-up function called at end of emulation */
|
|
void
|
|
periph_end( void )
|
|
{
|
|
g_slist_foreach( ports, free_peripheral, NULL );
|
|
g_slist_free( ports );
|
|
ports = NULL;
|
|
|
|
g_hash_table_destroy( peripherals );
|
|
peripherals = NULL;
|
|
}
|
|
|
|
/*
|
|
* The actual routines to read and write a port
|
|
*/
|
|
|
|
/* Internal type used for passing to read_peripheral and write_peripheral */
|
|
struct peripheral_data_t {
|
|
|
|
libspectrum_word port;
|
|
|
|
libspectrum_byte attached;
|
|
libspectrum_byte value;
|
|
};
|
|
|
|
/* Read a byte from a port, taking the appropriate time */
|
|
libspectrum_byte
|
|
readport( libspectrum_word port )
|
|
{
|
|
libspectrum_byte b;
|
|
|
|
ula_contend_port_early( port );
|
|
ula_contend_port_late( port );
|
|
b = readport_internal( port );
|
|
|
|
/* Very ugly to put this here, but unless anything else needs this
|
|
"writeback" mechanism, no point producing a general framework */
|
|
if( ( port & 0x8002 ) == 0 &&
|
|
( machine_current->machine == LIBSPECTRUM_MACHINE_128 ||
|
|
machine_current->machine == LIBSPECTRUM_MACHINE_PLUS2 ) )
|
|
writeport_internal( 0x7ffd, b );
|
|
|
|
tstates++;
|
|
|
|
return b;
|
|
}
|
|
|
|
/* Read a byte from a specific port response */
|
|
static void
|
|
read_peripheral( gpointer data, gpointer user_data )
|
|
{
|
|
periph_port_private_t *private = data;
|
|
struct peripheral_data_t *callback_info = user_data;
|
|
libspectrum_byte last_attached;
|
|
|
|
periph_port_t *port = &( private->port );
|
|
|
|
if( port->read &&
|
|
( ( callback_info->port & port->mask ) == port->value ) ) {
|
|
last_attached = callback_info->attached;
|
|
callback_info->value &= ( port->read( callback_info->port,
|
|
&( callback_info->attached ) )
|
|
| last_attached );
|
|
}
|
|
}
|
|
|
|
/* Read a byte from a port, taking no time */
|
|
libspectrum_byte
|
|
readport_internal( libspectrum_word port )
|
|
{
|
|
struct peripheral_data_t callback_info;
|
|
|
|
/* Trigger the debugger if wanted */
|
|
if( debugger_mode != DEBUGGER_MODE_INACTIVE )
|
|
debugger_check( DEBUGGER_BREAKPOINT_TYPE_PORT_READ, port );
|
|
|
|
/* If we're doing RZX playback, get a byte from the RZX file */
|
|
if( rzx_playback ) {
|
|
|
|
libspectrum_error error;
|
|
libspectrum_byte value;
|
|
|
|
error = libspectrum_rzx_playback( rzx, &value );
|
|
if( error ) {
|
|
rzx_stop_playback( 1 );
|
|
|
|
/* Add a null event to mean we pick up the RZX state change in
|
|
z80_do_opcodes() */
|
|
event_add( tstates, event_type_null );
|
|
return readport_internal( port );
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/* If we're not doing RZX playback, get the byte normally */
|
|
callback_info.port = port;
|
|
callback_info.attached = 0x00;
|
|
callback_info.value = 0xff;
|
|
|
|
g_slist_foreach( ports, read_peripheral, &callback_info );
|
|
|
|
if( callback_info.attached != 0xff )
|
|
callback_info.value =
|
|
periph_merge_floating_bus( callback_info.value, callback_info.attached,
|
|
machine_current->unattached_port() );
|
|
|
|
/* If we're RZX recording, store this byte */
|
|
if( rzx_recording ) rzx_store_byte( callback_info.value );
|
|
|
|
return callback_info.value;
|
|
}
|
|
|
|
/* Merge the read value with the floating bus. Deliberately doesn't take
|
|
a callback_info structure to enable it to be unit tested */
|
|
libspectrum_byte
|
|
periph_merge_floating_bus( libspectrum_byte value, libspectrum_byte attached,
|
|
libspectrum_byte floating_bus )
|
|
{
|
|
return value & (floating_bus | attached);
|
|
}
|
|
|
|
/* Write a byte to a port, taking the appropriate time */
|
|
void
|
|
writeport( libspectrum_word port, libspectrum_byte b )
|
|
{
|
|
ula_contend_port_early( port );
|
|
writeport_internal( port, b );
|
|
ula_contend_port_late( port ); tstates++;
|
|
}
|
|
|
|
/* Write a byte to a specific port response */
|
|
static void
|
|
write_peripheral( gpointer data, gpointer user_data )
|
|
{
|
|
periph_port_private_t *private = data;
|
|
struct peripheral_data_t *callback_info = user_data;
|
|
|
|
periph_port_t *port = &( private->port );
|
|
|
|
if( port->write &&
|
|
( ( callback_info->port & port->mask ) == port->value ) )
|
|
port->write( callback_info->port, callback_info->value );
|
|
}
|
|
|
|
/* Write a byte to a port, taking no time */
|
|
void
|
|
writeport_internal( libspectrum_word port, libspectrum_byte b )
|
|
{
|
|
struct peripheral_data_t callback_info;
|
|
|
|
/* Trigger the debugger if wanted */
|
|
if( debugger_mode != DEBUGGER_MODE_INACTIVE )
|
|
debugger_check( DEBUGGER_BREAKPOINT_TYPE_PORT_WRITE, port );
|
|
|
|
callback_info.port = port;
|
|
callback_info.value = b;
|
|
|
|
g_slist_foreach( ports, write_peripheral, &callback_info );
|
|
}
|
|
|
|
/*
|
|
* The more Fuse-specific peripheral handling routines
|
|
*/
|
|
|
|
static void
|
|
update_cartridge_menu( void )
|
|
{
|
|
int cartridge, dock, if2;
|
|
|
|
dock = machine_current->capabilities &
|
|
LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_DOCK;
|
|
if2 = periph_is_active( PERIPH_TYPE_INTERFACE2 );
|
|
|
|
cartridge = dock || if2;
|
|
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE, cartridge );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_DOCK, dock );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_IF2, if2 );
|
|
}
|
|
|
|
static void
|
|
update_ide_menu( void )
|
|
{
|
|
int ide, simpleide, zxatasp, zxcf, divide, divmmc, zxmmc;
|
|
|
|
simpleide = settings_current.simpleide_active;
|
|
zxatasp = settings_current.zxatasp_active;
|
|
zxcf = settings_current.zxcf_active;
|
|
divide = settings_current.divide_enabled;
|
|
divmmc = settings_current.divmmc_enabled;
|
|
zxmmc = settings_current.zxmmc_enabled;
|
|
|
|
ide = simpleide || zxatasp || zxcf || divide || divmmc || zxmmc;
|
|
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE, ide );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_SIMPLE8BIT, simpleide );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP, zxatasp );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXCF, zxcf );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_DIVIDE, divide );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_DIVMMC, divmmc );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXMMC, zxmmc );
|
|
}
|
|
|
|
static void
|
|
update_peripherals_status( void )
|
|
{
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_IF1,
|
|
periph_is_active( PERIPH_TYPE_INTERFACE1 ) );
|
|
ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_IF2,
|
|
periph_is_active( PERIPH_TYPE_INTERFACE2 ) );
|
|
|
|
update_cartridge_menu();
|
|
update_ide_menu();
|
|
if1_update_menu();
|
|
multiface_status_update();
|
|
specplus3_765_update_fdd();
|
|
}
|
|
|
|
void
|
|
periph_disable_optional( void )
|
|
{
|
|
if( ui_mouse_present && ui_mouse_grabbed ) {
|
|
ui_mouse_grabbed = ui_mouse_release( 1 );
|
|
}
|
|
|
|
g_hash_table_foreach( peripherals, disable_optional, NULL );
|
|
|
|
update_peripherals_status();
|
|
}
|
|
|
|
int
|
|
periph_update( void )
|
|
{
|
|
int needs_hard_reset = 0;
|
|
|
|
if( ui_mouse_present ) {
|
|
if( settings_current.kempston_mouse ) {
|
|
if( !ui_mouse_grabbed ) ui_mouse_grabbed = ui_mouse_grab( 1 );
|
|
} else {
|
|
if( ui_mouse_grabbed ) ui_mouse_grabbed = ui_mouse_release( 1 );
|
|
}
|
|
}
|
|
|
|
g_hash_table_foreach( peripherals, set_activity, &needs_hard_reset );
|
|
|
|
update_peripherals_status();
|
|
machine_current->memory_map();
|
|
|
|
return needs_hard_reset;
|
|
}
|
|
|
|
void
|
|
periph_posthook( void )
|
|
{
|
|
if( periph_update() ) {
|
|
machine_reset( 1 );
|
|
}
|
|
}
|
|
|
|
int
|
|
periph_postcheck( void )
|
|
{
|
|
int needs_hard_reset = 0;
|
|
|
|
/* Detect if a hard reset is needed without (de)activating peripherals */
|
|
g_hash_table_foreach( peripherals, get_hard_reset, &needs_hard_reset );
|
|
|
|
return needs_hard_reset;
|
|
}
|
|
|
|
/* Register debugger page/unpage events for a peripheral */
|
|
void
|
|
periph_register_paging_events( const char *type_string, int *page_event,
|
|
int *unpage_event )
|
|
{
|
|
*page_event = debugger_event_register( type_string, page_event_string );
|
|
*unpage_event = debugger_event_register( type_string, unpage_event_string );
|
|
}
|