1 /* periph.c: code for handling peripherals
2    Copyright (c) 2005-2016 Philip Kendall
3    Copyright (c) 2015 Stuart Brady
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License along
16    with this program; if not, write to the Free Software Foundation, Inc.,
17    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 
19    Author contact information:
20 
21    E-mail: philip-fuse@shadowmagic.org.uk
22 
23 */
24 
25 #include <config.h>
26 
27 #include <libspectrum.h>
28 
29 #include "debugger/debugger.h"
30 #include "event.h"
31 #include "fuse.h"
32 #include "periph.h"
33 #include "peripherals/if1.h"
34 #include "peripherals/multiface.h"
35 #include "peripherals/ula.h"
36 #include "rzx.h"
37 #include "settings.h"
38 #include "ui/ui.h"
39 
40 /*
41  * General peripheral list handling routines
42  */
43 
44 typedef struct periph_private_t {
45   /* Can this peripheral ever be present on the currently emulated machine? */
46   periph_present present;
47   /* Is this peripheral currently active? */
48   int active;
49   /* The actual peripheral data */
50   const periph_t *periph;
51 } periph_private_t;
52 
53 /* All the peripherals we know about */
54 static GHashTable *peripherals = NULL;
55 
56 /* Wrapper to pair up a port response with the peripheral it came from */
57 typedef struct periph_port_private_t {
58   /* The peripheral this came from */
59   periph_type type;
60   /* The port response */
61   periph_port_t port;
62 } periph_port_private_t;
63 
64 /* The list of currently active ports */
65 static GSList *ports = NULL;
66 
67 /* The strings used for debugger events */
68 static const char * const page_event_string = "page",
69   * const unpage_event_string = "unpage";
70 
71 /* Place one port response in the list of currently active ones */
72 static void
port_register(periph_type type,const periph_port_t * port)73 port_register( periph_type type, const periph_port_t *port )
74 {
75   periph_port_private_t *private;
76 
77   private = libspectrum_new( periph_port_private_t, 1 );
78 
79   private->type = type;
80   private->port = *port;
81 
82   ports = g_slist_append( ports, private );
83 }
84 
85 /* Register a peripheral with the system */
86 void
periph_register(periph_type type,const periph_t * periph)87 periph_register( periph_type type, const periph_t *periph )
88 {
89   periph_private_t *private;
90 
91   if( !peripherals )
92     peripherals = g_hash_table_new_full( NULL, NULL, NULL, libspectrum_free );
93 
94   private = libspectrum_new( periph_private_t, 1 );
95 
96   private->present = PERIPH_PRESENT_NEVER;
97   private->active = 0;
98   private->periph = periph;
99 
100   g_hash_table_insert( peripherals, GINT_TO_POINTER( type ), private );
101 }
102 
103 /* Get the data about one peripheral */
104 static gint
find_by_type(gconstpointer data,gconstpointer user_data)105 find_by_type( gconstpointer data, gconstpointer user_data )
106 {
107   const periph_port_private_t *periph = data;
108   periph_type type = GPOINTER_TO_INT( user_data );
109   return periph->type - type;
110 }
111 
112 /* Set whether a peripheral can be present on this machine or not */
113 void
periph_set_present(periph_type type,periph_present present)114 periph_set_present( periph_type type, periph_present present )
115 {
116   periph_private_t *type_data = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
117   if( type_data ) type_data->present = present;
118 }
119 
120 /* Mark a specific peripheral as (in)active */
121 int
periph_activate_type(periph_type type,int active)122 periph_activate_type( periph_type type, int active )
123 {
124   periph_private_t *private = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
125   if( !private || private->active == active ) return 0;
126 
127   private->active = active;
128 
129   if( active ) {
130     const periph_port_t *ptr;
131     if( private->periph->activate )
132       private->periph->activate();
133     for( ptr = private->periph->ports; ptr && ptr->mask != 0; ptr++ )
134       port_register( type, ptr );
135   } else {
136     GSList *found;
137     while( ( found = g_slist_find_custom( ports, GINT_TO_POINTER( type ), find_by_type ) ) != NULL )
138       ports = g_slist_remove( ports, found->data );
139   }
140 
141   return 1;
142 }
143 
144 /* Is a specific peripheral active at the moment? */
145 int
periph_is_active(periph_type type)146 periph_is_active( periph_type type )
147 {
148   periph_private_t *type_data = g_hash_table_lookup( peripherals, GINT_TO_POINTER( type ) );
149   return type_data ? type_data->active : 0;
150 }
151 
152 /* Work out whether a peripheral is present on this machine, and mark it
153    (in)active as appropriate */
154 static void
set_activity(gpointer key,gpointer value,gpointer user_data)155 set_activity( gpointer key, gpointer value, gpointer user_data )
156 {
157   periph_type type = GPOINTER_TO_INT( key );
158   periph_private_t *private = value;
159   int active = 0;
160   int *needs_hard_reset = (int *)user_data;
161 
162   switch ( private->present ) {
163   case PERIPH_PRESENT_NEVER: active = 0; break;
164   case PERIPH_PRESENT_OPTIONAL:
165     active = private->periph->option ? *(private->periph->option) : 0; break;
166   case PERIPH_PRESENT_ALWAYS: active = 1; break;
167   }
168 
169   *needs_hard_reset =
170     ( periph_activate_type( type, active ) && private->periph->hard_reset ) ||
171     *needs_hard_reset;
172 }
173 
174 /* Work out whether a peripheral needs a hard reset without (de)activate */
175 static void
get_hard_reset(gpointer key,gpointer value,gpointer user_data)176 get_hard_reset( gpointer key, gpointer value, gpointer user_data )
177 {
178   periph_private_t *private = value;
179   int active = 0;
180   int *machine_hard_reset = (int *)user_data;
181   int periph_hard_reset = 0;
182 
183   switch ( private->present ) {
184   case PERIPH_PRESENT_NEVER: active = 0; break;
185   case PERIPH_PRESENT_OPTIONAL:
186     active = private->periph->option ? *(private->periph->option) : 0; break;
187   case PERIPH_PRESENT_ALWAYS: active = 1; break;
188   }
189 
190   periph_hard_reset = ( private && ( private->active != active ) &&
191                         private->periph->hard_reset );
192 
193   *machine_hard_reset = ( periph_hard_reset || *machine_hard_reset );
194 }
195 
196 static void
disable_optional(gpointer key,gpointer value,gpointer user_data)197 disable_optional( gpointer key, gpointer value, gpointer user_data )
198 {
199   periph_private_t *private = value;
200 
201   switch ( private->present ) {
202   case PERIPH_PRESENT_NEVER:
203   case PERIPH_PRESENT_OPTIONAL:
204     if( private->periph->option ) *(private->periph->option) = 0;
205     break;
206   default: break;
207   }
208 }
209 
210 /* Free the memory used by a peripheral-port response pair */
211 static void
free_peripheral(gpointer data,gpointer user_data GCC_UNUSED)212 free_peripheral( gpointer data, gpointer user_data GCC_UNUSED )
213 {
214   periph_port_private_t *private = data;
215   libspectrum_free( private );
216 }
217 
218 /* Make a peripheral as being never present on this machine */
219 static void
set_type_inactive(gpointer key,gpointer value,gpointer user_data)220 set_type_inactive( gpointer key, gpointer value, gpointer user_data )
221 {
222   periph_private_t *type_data = value;
223   type_data->present = PERIPH_PRESENT_NEVER;
224   type_data->active = 0;
225 }
226 
227 /* Mark all peripherals as being never present on this machine */
228 static void
set_types_inactive(void)229 set_types_inactive( void )
230 {
231   g_hash_table_foreach( peripherals, set_type_inactive, NULL );
232 }
233 
234 /* Empty out the list of peripherals */
235 void
periph_clear(void)236 periph_clear( void )
237 {
238   g_slist_foreach( ports, free_peripheral, NULL );
239   g_slist_free( ports );
240   ports = NULL;
241   set_types_inactive();
242 }
243 
244 /* Tidy-up function called at end of emulation */
245 void
periph_end(void)246 periph_end( void )
247 {
248   g_slist_foreach( ports, free_peripheral, NULL );
249   g_slist_free( ports );
250   ports = NULL;
251 
252   g_hash_table_destroy( peripherals );
253   peripherals = NULL;
254 }
255 
256 /*
257  * The actual routines to read and write a port
258  */
259 
260 /* Internal type used for passing to read_peripheral and write_peripheral */
261 struct peripheral_data_t {
262 
263   libspectrum_word port;
264 
265   libspectrum_byte attached;
266   libspectrum_byte value;
267 };
268 
269 /* Read a byte from a port, taking the appropriate time */
270 libspectrum_byte
readport(libspectrum_word port)271 readport( libspectrum_word port )
272 {
273   libspectrum_byte b;
274 
275   ula_contend_port_early( port );
276   ula_contend_port_late( port );
277   b = readport_internal( port );
278 
279   /* Very ugly to put this here, but unless anything else needs this
280      "writeback" mechanism, no point producing a general framework */
281   if( ( port & 0x8002 ) == 0 &&
282       ( machine_current->machine == LIBSPECTRUM_MACHINE_128   ||
283 	machine_current->machine == LIBSPECTRUM_MACHINE_PLUS2    ) )
284     writeport_internal( 0x7ffd, b );
285 
286   tstates++;
287 
288   return b;
289 }
290 
291 /* Read a byte from a specific port response */
292 static void
read_peripheral(gpointer data,gpointer user_data)293 read_peripheral( gpointer data, gpointer user_data )
294 {
295   periph_port_private_t *private = data;
296   struct peripheral_data_t *callback_info = user_data;
297   libspectrum_byte last_attached;
298 
299   periph_port_t *port = &( private->port );
300 
301   if( port->read &&
302       ( ( callback_info->port & port->mask ) == port->value ) ) {
303     last_attached = callback_info->attached;
304     callback_info->value &= (   port->read( callback_info->port,
305 					    &( callback_info->attached ) )
306 			      | last_attached );
307   }
308 }
309 
310 /* Read a byte from a port, taking no time */
311 libspectrum_byte
readport_internal(libspectrum_word port)312 readport_internal( libspectrum_word port )
313 {
314   struct peripheral_data_t callback_info;
315 
316   /* Trigger the debugger if wanted */
317   if( debugger_mode != DEBUGGER_MODE_INACTIVE )
318     debugger_check( DEBUGGER_BREAKPOINT_TYPE_PORT_READ, port );
319 
320   /* If we're doing RZX playback, get a byte from the RZX file */
321   if( rzx_playback ) {
322 
323     libspectrum_error error;
324     libspectrum_byte value;
325 
326     error = libspectrum_rzx_playback( rzx, &value );
327     if( error ) {
328       rzx_stop_playback( 1 );
329 
330       /* Add a null event to mean we pick up the RZX state change in
331 	 z80_do_opcodes() */
332       event_add( tstates, event_type_null );
333       return readport_internal( port );
334     }
335 
336     return value;
337   }
338 
339   /* If we're not doing RZX playback, get the byte normally */
340   callback_info.port = port;
341   callback_info.attached = 0x00;
342   callback_info.value = 0xff;
343 
344   g_slist_foreach( ports, read_peripheral, &callback_info );
345 
346   if( callback_info.attached != 0xff )
347     callback_info.value =
348       periph_merge_floating_bus( callback_info.value, callback_info.attached,
349                                  machine_current->unattached_port() );
350 
351   /* If we're RZX recording, store this byte */
352   if( rzx_recording ) rzx_store_byte( callback_info.value );
353 
354   return callback_info.value;
355 }
356 
357 /* Merge the read value with the floating bus. Deliberately doesn't take
358    a callback_info structure to enable it to be unit tested */
359 libspectrum_byte
periph_merge_floating_bus(libspectrum_byte value,libspectrum_byte attached,libspectrum_byte floating_bus)360 periph_merge_floating_bus( libspectrum_byte value, libspectrum_byte attached,
361 			   libspectrum_byte floating_bus )
362 {
363   return value & (floating_bus | attached);
364 }
365 
366 /* Write a byte to a port, taking the appropriate time */
367 void
writeport(libspectrum_word port,libspectrum_byte b)368 writeport( libspectrum_word port, libspectrum_byte b )
369 {
370   ula_contend_port_early( port );
371   writeport_internal( port, b );
372   ula_contend_port_late( port ); tstates++;
373 }
374 
375 /* Write a byte to a specific port response */
376 static void
write_peripheral(gpointer data,gpointer user_data)377 write_peripheral( gpointer data, gpointer user_data )
378 {
379   periph_port_private_t *private = data;
380   struct peripheral_data_t *callback_info = user_data;
381 
382   periph_port_t *port = &( private->port );
383 
384   if( port->write &&
385       ( ( callback_info->port & port->mask ) == port->value ) )
386     port->write( callback_info->port, callback_info->value );
387 }
388 
389 /* Write a byte to a port, taking no time */
390 void
writeport_internal(libspectrum_word port,libspectrum_byte b)391 writeport_internal( libspectrum_word port, libspectrum_byte b )
392 {
393   struct peripheral_data_t callback_info;
394 
395   /* Trigger the debugger if wanted */
396   if( debugger_mode != DEBUGGER_MODE_INACTIVE )
397     debugger_check( DEBUGGER_BREAKPOINT_TYPE_PORT_WRITE, port );
398 
399   callback_info.port = port;
400   callback_info.value = b;
401 
402   g_slist_foreach( ports, write_peripheral, &callback_info );
403 }
404 
405 /*
406  * The more Fuse-specific peripheral handling routines
407  */
408 
409 static void
update_cartridge_menu(void)410 update_cartridge_menu( void )
411 {
412   int cartridge, dock, if2;
413 
414   dock = machine_current->capabilities &
415          LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_DOCK;
416   if2 = periph_is_active( PERIPH_TYPE_INTERFACE2 );
417 
418   cartridge = dock || if2;
419 
420   ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE, cartridge );
421   ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_DOCK, dock );
422   ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_IF2, if2 );
423 }
424 
425 static void
update_ide_menu(void)426 update_ide_menu( void )
427 {
428   int ide, simpleide, zxatasp, zxcf, divide, divmmc, zxmmc;
429 
430   simpleide = settings_current.simpleide_active;
431   zxatasp = settings_current.zxatasp_active;
432   zxcf = settings_current.zxcf_active;
433   divide = settings_current.divide_enabled;
434   divmmc = settings_current.divmmc_enabled;
435   zxmmc = settings_current.zxmmc_enabled;
436 
437   ide = simpleide || zxatasp || zxcf || divide || divmmc || zxmmc;
438 
439   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE, ide );
440   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_SIMPLE8BIT, simpleide );
441   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXATASP, zxatasp );
442   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXCF, zxcf );
443   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_DIVIDE, divide );
444   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_DIVMMC, divmmc );
445   ui_menu_activate( UI_MENU_ITEM_MEDIA_IDE_ZXMMC, zxmmc );
446 }
447 
448 static void
update_peripherals_status(void)449 update_peripherals_status( void )
450 {
451   ui_menu_activate( UI_MENU_ITEM_MEDIA_IF1,
452                     periph_is_active( PERIPH_TYPE_INTERFACE1 ) );
453   ui_menu_activate( UI_MENU_ITEM_MEDIA_CARTRIDGE_IF2,
454                     periph_is_active( PERIPH_TYPE_INTERFACE2 ) );
455 
456   update_cartridge_menu();
457   update_ide_menu();
458   if1_update_menu();
459   multiface_status_update();
460   specplus3_765_update_fdd();
461 }
462 
463 void
periph_disable_optional(void)464 periph_disable_optional( void )
465 {
466   if( ui_mouse_present && ui_mouse_grabbed ) {
467     ui_mouse_grabbed = ui_mouse_release( 1 );
468   }
469 
470   g_hash_table_foreach( peripherals, disable_optional, NULL );
471 
472   update_peripherals_status();
473 }
474 
475 int
periph_update(void)476 periph_update( void )
477 {
478   int needs_hard_reset = 0;
479 
480   if( ui_mouse_present ) {
481     if( settings_current.kempston_mouse ) {
482       if( !ui_mouse_grabbed ) ui_mouse_grabbed = ui_mouse_grab( 1 );
483     } else {
484       if(  ui_mouse_grabbed ) ui_mouse_grabbed = ui_mouse_release( 1 );
485     }
486   }
487 
488   g_hash_table_foreach( peripherals, set_activity, &needs_hard_reset );
489 
490   update_peripherals_status();
491   machine_current->memory_map();
492 
493   return needs_hard_reset;
494 }
495 
496 void
periph_posthook(void)497 periph_posthook( void )
498 {
499   if( periph_update() ) {
500     machine_reset( 1 );
501   }
502 }
503 
504 int
periph_postcheck(void)505 periph_postcheck( void )
506 {
507   int needs_hard_reset = 0;
508 
509   /* Detect if a hard reset is needed without (de)activating peripherals */
510   g_hash_table_foreach( peripherals, get_hard_reset, &needs_hard_reset );
511 
512   return needs_hard_reset;
513 }
514 
515 /* Register debugger page/unpage events for a peripheral */
516 void
periph_register_paging_events(const char * type_string,int * page_event,int * unpage_event)517 periph_register_paging_events( const char *type_string, int *page_event,
518 			       int *unpage_event )
519 {
520   *page_event = debugger_event_register( type_string, page_event_string );
521   *unpage_event = debugger_event_register( type_string, unpage_event_string );
522 }
523