/* 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 ); }