1 /* opus.c: Routines for handling the Opus Discovery interface
2    Copyright (c) 1999-2011 Stuart Brady, Fredrick Meunier, Philip Kendall,
3    Dmitry Sanarin, Darren Salt, Michael D Wynne, Gergely Szasz
4 
5    $Id: opus.c 4926 2013-05-05 07:58:18Z sbaldovi $
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License along
18    with this program; if not, write to the Free Software Foundation, Inc.,
19    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 
21    Author contact information:
22 
23    Philip: philip-fuse@shadowmagic.org.uk
24 
25    Stuart: stuart.brady@gmail.com
26 
27 */
28 
29 #include <config.h>
30 
31 #include <libspectrum.h>
32 
33 #include <string.h>
34 
35 #include "compat.h"
36 #include "machine.h"
37 #include "module.h"
38 #include "opus.h"
39 #include "peripherals/printer.h"
40 #include "settings.h"
41 #include "ui/ui.h"
42 #include "unittests/unittests.h"
43 #include "utils.h"
44 #include "wd_fdc.h"
45 #include "options.h"	/* needed for get combo options */
46 #include "z80/z80.h"
47 
48 #define DISK_TRY_MERGE(heads) ( option_enumerate_diskoptions_disk_try_merge() == 2 || \
49 				( option_enumerate_diskoptions_disk_try_merge() == 1 && heads == 1 ) )
50 
51 /* FIXME: this is wrong. Opus has only 2 Kb of RAM, but we can't handle
52    anything less than our page size */
53 #define OPUS_RAM_SIZE 0x1000
54 
55 #define OPUS_RAM_PAGES ( OPUS_RAM_SIZE / MEMORY_PAGE_SIZE \
56   ? OPUS_RAM_SIZE / MEMORY_PAGE_SIZE : 1 )
57 
58 #define TRUE_OPUS_RAM_SIZE 0x800
59 
60 static int opus_rom_memory_source, opus_ram_memory_source;
61 
62 /* Two memory chunks accessible by the Z80 when /ROMCS is low */
63 static memory_page opus_memory_map_romcs_rom[ MEMORY_PAGES_IN_8K ];
64 static memory_page opus_memory_map_romcs_ram[ OPUS_RAM_PAGES ];
65 
66 int opus_available = 0;
67 int opus_active = 0;
68 
69 static int opus_index_pulse;
70 
71 static int index_event;
72 
73 #define OPUS_NUM_DRIVES 2
74 
75 static wd_fdc *opus_fdc;
76 static wd_fdc_drive opus_drives[ OPUS_NUM_DRIVES ];
77 
78 static libspectrum_byte opus_ram[ OPUS_RAM_SIZE ];
79 
80 /* 6821 PIA internal registers */
81 static libspectrum_byte data_reg_a, data_dir_a, control_a;
82 static libspectrum_byte data_reg_b, data_dir_b, control_b;
83 
84 static void opus_reset( int hard_reset );
85 static void opus_memory_map( void );
86 static void opus_enabled_snapshot( libspectrum_snap *snap );
87 static void opus_from_snapshot( libspectrum_snap *snap );
88 static void opus_to_snapshot( libspectrum_snap *snap );
89 static void opus_event_index( libspectrum_dword last_tstates, int type,
90 			       void *user_data );
91 
92 static module_info_t opus_module_info = {
93 
94   opus_reset,
95   opus_memory_map,
96   opus_enabled_snapshot,
97   opus_from_snapshot,
98   opus_to_snapshot,
99 
100 };
101 
102 static const periph_t opus_periph = {
103   &settings_current.opus,
104   NULL,
105   1,
106   NULL
107 };
108 
109 void
opus_page(void)110 opus_page( void )
111 {
112   opus_active = 1;
113   machine_current->ram.romcs = 1;
114   machine_current->memory_map();
115 }
116 
117 void
opus_unpage(void)118 opus_unpage( void )
119 {
120   opus_active = 0;
121   machine_current->ram.romcs = 0;
122   machine_current->memory_map();
123 }
124 
125 static void
opus_memory_map(void)126 opus_memory_map( void )
127 {
128   if( !opus_active ) return;
129 
130   memory_map_romcs_8k( 0x0000, opus_memory_map_romcs_rom );
131   memory_map_romcs_4k( 0x2000, opus_memory_map_romcs_ram );
132 }
133 
134 static void
opus_set_datarq(struct wd_fdc * f)135 opus_set_datarq( struct wd_fdc *f )
136 {
137   event_add( 0, z80_nmi_event );
138 }
139 
140 void
opus_init(void)141 opus_init( void )
142 {
143   int i;
144   wd_fdc_drive *d;
145 
146   opus_fdc = wd_fdc_alloc_fdc( WD1770, 0, WD_FLAG_OPUS );
147 
148   for( i = 0; i < OPUS_NUM_DRIVES; i++ ) {
149     d = &opus_drives[ i ];
150     fdd_init( &d->fdd, FDD_SHUGART, NULL, 0 );	/* drive geometry 'autodetect' */
151     d->disk.flag = DISK_FLAG_NONE;
152   }
153 
154   opus_fdc->current_drive = &opus_drives[ 0 ];
155   fdd_select( &opus_drives[ 0 ].fdd, 1 );
156   opus_fdc->dden = 1;
157   opus_fdc->set_intrq = NULL;
158   opus_fdc->reset_intrq = NULL;
159   opus_fdc->set_datarq = opus_set_datarq;
160   opus_fdc->reset_datarq = NULL;
161   opus_fdc->iface = NULL;
162 
163   index_event = event_register( opus_event_index, "Opus index" );
164 
165   module_register( &opus_module_info );
166 
167   opus_rom_memory_source = memory_source_register( "Opus ROM" );
168   opus_ram_memory_source = memory_source_register( "Opus RAM" );
169   for( i = 0; i < MEMORY_PAGES_IN_8K; i++ )
170     opus_memory_map_romcs_rom[i].source = opus_rom_memory_source;
171   for( i = 0; i < OPUS_RAM_PAGES; i++ )
172     opus_memory_map_romcs_ram[i].source = opus_ram_memory_source;
173 
174   periph_register( PERIPH_TYPE_OPUS, &opus_periph );
175 }
176 
177 static void
opus_reset(int hard_reset)178 opus_reset( int hard_reset )
179 {
180   int i;
181   wd_fdc_drive *d;
182   const fdd_params_t *dt;
183 
184   opus_active = 0;
185   opus_available = 0;
186 
187   event_remove_type( index_event );
188 
189   if( !periph_is_active( PERIPH_TYPE_OPUS ) ) {
190     return;
191   }
192 
193   if( machine_load_rom_bank( opus_memory_map_romcs_rom, 0,
194                              settings_current.rom_opus,
195                              settings_default.rom_opus, 0x2000 ) ) {
196     settings_current.opus = 0;
197     periph_activate_type( PERIPH_TYPE_OPUS, 0 );
198     return;
199   }
200 
201   for( i = 0; i < OPUS_RAM_PAGES; i++ ) {
202     struct memory_page *page =
203       &opus_memory_map_romcs_ram[ i ];
204     page->page = opus_ram + i * MEMORY_PAGE_SIZE;
205     page->offset = i * MEMORY_PAGE_SIZE;
206   }
207 
208   machine_current->ram.romcs = 0;
209 
210   for( i = 0; i < OPUS_RAM_PAGES; i++ )
211     opus_memory_map_romcs_ram[ i ].writable = 1;
212 
213   data_reg_a = 0;
214   data_dir_a = 0;
215   control_a  = 0;
216   data_reg_b = 0;
217   data_dir_b = 0;
218   control_b  = 0;
219 
220   opus_available = 1;
221   opus_index_pulse = 0;
222 
223   if( hard_reset )
224     memset( opus_ram, 0, TRUE_OPUS_RAM_SIZE );
225 
226   wd_fdc_master_reset( opus_fdc );
227 
228   for( i = 0; i < OPUS_NUM_DRIVES; i++ ) {
229     d = &opus_drives[ i ];
230 
231     d->index_pulse = 0;
232     d->index_interrupt = 0;
233   }
234 
235   /* We can eject disks only if they are currently present */
236   dt = &fdd_params[ option_enumerate_diskoptions_drive_opus1_type() + 1 ];	/* +1 => there is no `Disabled' */
237   fdd_init( &opus_drives[ OPUS_DRIVE_1 ].fdd, FDD_SHUGART, dt, 1 );
238   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1, dt->enabled );
239   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_EJECT,
240 		    opus_drives[ OPUS_DRIVE_1 ].fdd.loaded );
241   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_FLIP_SET,
242 		    !opus_drives[ OPUS_DRIVE_1 ].fdd.upsidedown );
243   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_WP_SET,
244 		    !opus_drives[ OPUS_DRIVE_1 ].fdd.wrprot );
245 
246 
247   dt = &fdd_params[ option_enumerate_diskoptions_drive_opus2_type() ];
248   fdd_init( &opus_drives[ OPUS_DRIVE_2 ].fdd, dt->enabled ? FDD_SHUGART : FDD_TYPE_NONE, dt, 1 );
249   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2, dt->enabled );
250   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_EJECT,
251 		    opus_drives[ OPUS_DRIVE_2 ].fdd.loaded );
252   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_FLIP_SET,
253 		    !opus_drives[ OPUS_DRIVE_2 ].fdd.upsidedown );
254   ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_WP_SET,
255 		    !opus_drives[ OPUS_DRIVE_2 ].fdd.wrprot );
256 
257 
258   opus_fdc->current_drive = &opus_drives[ 0 ];
259   fdd_select( &opus_drives[ 0 ].fdd, 1 );
260   machine_current->memory_map();
261   opus_event_index( 0, index_event, NULL );
262 
263   ui_statusbar_update( UI_STATUSBAR_ITEM_DISK, UI_STATUSBAR_STATE_INACTIVE );
264 }
265 
266 void
opus_end(void)267 opus_end( void )
268 {
269   opus_available = 0;
270   free( opus_fdc );
271 }
272 
273 /*
274  * opus_6821_access( reg, data, dir )
275  *
276  * reg - register to access:
277  *
278  * data - if dir = 1 the value being written else ignored
279  *
280  * dir - direction of data. 0 = read, 1 = write
281  *
282  * returns: value of register if dir = 0 else 0
283  *
284  * Mostly borrowed from EightyOne - A Windows ZX80/81/clone emulator
285  */
286 
287 static libspectrum_byte
opus_6821_access(libspectrum_byte reg,libspectrum_byte data,libspectrum_byte dir)288 opus_6821_access( libspectrum_byte reg, libspectrum_byte data,
289                   libspectrum_byte dir )
290 {
291   int drive, side;
292   int i;
293 
294   switch( reg & 0x03 ) {
295   case 0:
296     if( dir ) {
297       if( control_a & 0x04 ) {
298         data_reg_a = data;
299 
300         drive = ( data & 0x02 ) == 2 ? 1 : 0;
301         side = ( data & 0x10 )>>4 ? 1 : 0;
302 
303         for( i = 0; i < OPUS_NUM_DRIVES; i++ ) {
304           fdd_set_head( &opus_drives[ i ].fdd, side );
305         }
306 
307         fdd_select( &opus_drives[ (!drive) ].fdd, 0 );
308         fdd_select( &opus_drives[ drive ].fdd, 1 );
309 
310         if( opus_fdc->current_drive != &opus_drives[ drive ] ) {
311           if( opus_fdc->current_drive->fdd.motoron ) {        /* swap motoron */
312             fdd_motoron( &opus_drives[ (!drive) ].fdd, 0 );
313             fdd_motoron( &opus_drives[ drive ].fdd, 1 );
314           }
315           opus_fdc->current_drive = &opus_drives[ drive ];
316         }
317       } else {
318         data_dir_a = data;
319       }
320     } else {
321       if( control_a & 0x04 ) {
322         /* printer never busy (bit 6) */
323         data_reg_a &= ~0x40;
324         return data_reg_a;
325       } else {
326         return data_dir_a;
327       }
328     }
329     break;
330   case 1:
331     if( dir ) {
332       control_a = data;
333     } else {
334       /* Always return bit 6 set to ACK parallel port actions */
335       return control_a | 0x40;
336     }
337     break;
338   case 2:
339     if( dir ) {
340       if( control_b & 0x04 ) {
341         data_reg_b = data;
342         printer_parallel_write( 0x00, data );
343         /* Don't worry about emulating the strobes from the ROM, they are
344            all bound up with checking current printer busy status which we
345            don't emulate, so just send the char now */
346         printer_parallel_strobe_write( 0 );
347         printer_parallel_strobe_write( 1 );
348         printer_parallel_strobe_write( 0 );
349       } else {
350         data_dir_b = data;
351       }
352     } else {
353       if( control_b & 0x04 ) {
354         return data_reg_b;
355       } else {
356         return data_dir_b;
357       }
358     }
359     break;
360   case 3:
361     if( dir ) {
362       control_b = data;
363     } else {
364       return control_b;
365     }
366     break;
367   }
368 
369   return 0;
370 }
371 
372 int
opus_disk_insert(opus_drive_number which,const char * filename,int autoload)373 opus_disk_insert( opus_drive_number which, const char *filename,
374 		   int autoload )
375 {
376   int error;
377   wd_fdc_drive *d;
378   const fdd_params_t *dt;
379 
380   if( which >= OPUS_NUM_DRIVES ) {
381     ui_error( UI_ERROR_ERROR, "opus_disk_insert: unknown drive %d",
382 	      which );
383     fuse_abort();
384   }
385 
386   d = &opus_drives[ which ];
387 
388   /* Eject any disk already in the drive */
389   if( d->fdd.loaded ) {
390     /* Abort the insert if we want to keep the current disk */
391     if( opus_disk_eject( which ) ) return 0;
392   }
393 
394   if( filename ) {
395     error = disk_open( &d->disk, filename, 0, DISK_TRY_MERGE( d->fdd.fdd_heads ) );
396     if( error != DISK_OK ) {
397       ui_error( UI_ERROR_ERROR, "Failed to open disk image: %s",
398 				disk_strerror( error ) );
399       return 1;
400     }
401   } else {
402     switch( which ) {
403     case 0:
404       /* +1 => there is no `Disabled' */
405       dt = &fdd_params[ option_enumerate_diskoptions_drive_opus1_type() + 1 ];
406       break;
407     case 1:
408     default:
409       dt = &fdd_params[ option_enumerate_diskoptions_drive_opus2_type() ];
410       break;
411     }
412     error = disk_new( &d->disk, dt->heads, dt->cylinders, DISK_DENS_AUTO, DISK_UDI );
413     if( error != DISK_OK ) {
414       ui_error( UI_ERROR_ERROR, "Failed to create disk image: %s",
415 				disk_strerror( error ) );
416       return 1;
417     }
418   }
419 
420   fdd_load( &d->fdd, &d->disk, 0 );
421 
422   /* Set the 'eject' item active */
423   switch( which ) {
424   case OPUS_DRIVE_1:
425     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_EJECT, 1 );
426     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_FLIP_SET,
427 		      !opus_drives[ OPUS_DRIVE_1 ].fdd.upsidedown );
428     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_WP_SET,
429 		      !opus_drives[ OPUS_DRIVE_1 ].fdd.wrprot );
430     break;
431   case OPUS_DRIVE_2:
432     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_EJECT, 1 );
433     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_FLIP_SET,
434 		      !opus_drives[ OPUS_DRIVE_2 ].fdd.upsidedown );
435     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_WP_SET,
436 		      !opus_drives[ OPUS_DRIVE_2 ].fdd.wrprot );
437     break;
438   }
439 
440   if( filename && autoload ) {
441     /* XXX */
442   }
443 
444   return 0;
445 }
446 
447 int
opus_disk_eject(opus_drive_number which)448 opus_disk_eject( opus_drive_number which )
449 {
450   wd_fdc_drive *d;
451 
452   if( which >= OPUS_NUM_DRIVES )
453     return 1;
454 
455   d = &opus_drives[ which ];
456 
457   if( d->disk.type == DISK_TYPE_NONE )
458     return 0;
459 
460   if( d->disk.dirty ) {
461 
462     ui_confirm_save_t confirm = ui_confirm_save(
463       "Disk in Opus Discovery drive %c has been modified.\n"
464       "Do you want to save it?",
465       which == OPUS_DRIVE_1 ? '1' : '2'
466     );
467 
468     switch( confirm ) {
469 
470     case UI_CONFIRM_SAVE_SAVE:
471       if( opus_disk_save( which, 0 ) ) return 1;	/* first save */
472       break;
473 
474     case UI_CONFIRM_SAVE_DONTSAVE: break;
475     case UI_CONFIRM_SAVE_CANCEL: return 1;
476 
477     }
478   }
479 
480   fdd_unload( &d->fdd );
481   disk_close( &d->disk );
482 
483   /* Set the 'eject' item inactive */
484   switch( which ) {
485   case OPUS_DRIVE_1:
486     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_EJECT, 0 );
487     break;
488   case OPUS_DRIVE_2:
489     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_EJECT, 0 );
490     break;
491   }
492   return 0;
493 }
494 
495 int
opus_disk_save(opus_drive_number which,int saveas)496 opus_disk_save( opus_drive_number which, int saveas )
497 {
498   wd_fdc_drive *d;
499 
500   if( which >= OPUS_NUM_DRIVES )
501     return 1;
502 
503   d = &opus_drives[ which ];
504 
505   if( d->disk.type == DISK_TYPE_NONE )
506     return 0;
507 
508   if( d->disk.filename == NULL ) saveas = 1;
509   if( ui_opus_disk_write( which, saveas ) ) return 1;
510   d->disk.dirty = 0;
511   return 0;
512 }
513 
514 int
opus_disk_flip(opus_drive_number which,int flip)515 opus_disk_flip( opus_drive_number which, int flip )
516 {
517   wd_fdc_drive *d;
518 
519   if( which >= OPUS_NUM_DRIVES )
520     return 1;
521 
522   d = &opus_drives[ which ];
523 
524   if( !d->fdd.loaded )
525     return 1;
526 
527   fdd_flip( &d->fdd, flip );
528 
529   /* Update the 'write flip' menu item */
530   switch( which ) {
531   case OPUS_DRIVE_1:
532     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_FLIP_SET,
533 		      !opus_drives[ OPUS_DRIVE_1 ].fdd.upsidedown );
534     break;
535   case OPUS_DRIVE_2:
536     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_FLIP_SET,
537 		      !opus_drives[ OPUS_DRIVE_2 ].fdd.upsidedown );
538     break;
539   }
540   return 0;
541 }
542 
543 int
opus_disk_writeprotect(opus_drive_number which,int wrprot)544 opus_disk_writeprotect( opus_drive_number which, int wrprot )
545 {
546   wd_fdc_drive *d;
547 
548   if( which >= OPUS_NUM_DRIVES )
549     return 1;
550 
551   d = &opus_drives[ which ];
552 
553   if( !d->fdd.loaded )
554     return 1;
555 
556   fdd_wrprot( &d->fdd, wrprot );
557 
558   /* Update the 'write protect' menu item */
559   switch( which ) {
560   case OPUS_DRIVE_1:
561     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_1_WP_SET,
562 		      !opus_drives[ OPUS_DRIVE_1 ].fdd.wrprot );
563     break;
564   case OPUS_DRIVE_2:
565     ui_menu_activate( UI_MENU_ITEM_MEDIA_DISK_OPUS_2_WP_SET,
566 		      !opus_drives[ OPUS_DRIVE_2 ].fdd.wrprot );
567     break;
568   }
569   return 0;
570 }
571 
572 int
opus_disk_write(opus_drive_number which,const char * filename)573 opus_disk_write( opus_drive_number which, const char *filename )
574 {
575   wd_fdc_drive *d = &opus_drives[ which ];
576   int error;
577 
578   d->disk.type = DISK_TYPE_NONE;
579   if( filename == NULL ) filename = d->disk.filename;	/* write over original file */
580   error = disk_write( &d->disk, filename );
581 
582   if( error != DISK_OK ) {
583     ui_error( UI_ERROR_ERROR, "couldn't write '%s' file: %s", filename,
584 	      disk_strerror( error ) );
585     return 1;
586   }
587 
588   if( d->disk.filename && strcmp( filename, d->disk.filename ) ) {
589     free( d->disk.filename );
590     d->disk.filename = utils_safe_strdup( filename );
591   }
592   return 0;
593 }
594 
595 fdd_t *
opus_get_fdd(opus_drive_number which)596 opus_get_fdd( opus_drive_number which )
597 {
598   return &( opus_drives[ which ].fdd );
599 }
600 
601 static void
opus_event_index(libspectrum_dword last_tstates,int type GCC_UNUSED,void * user_data GCC_UNUSED)602 opus_event_index( libspectrum_dword last_tstates, int type GCC_UNUSED,
603                    void *user_data GCC_UNUSED )
604 {
605   int next_tstates;
606   int i;
607 
608   opus_index_pulse = !opus_index_pulse;
609   for( i = 0; i < OPUS_NUM_DRIVES; i++ ) {
610     wd_fdc_drive *d = &opus_drives[ i ];
611 
612     d->index_pulse = opus_index_pulse;
613     if( !opus_index_pulse && d->index_interrupt ) {
614       wd_fdc_set_intrq( opus_fdc );
615       d->index_interrupt = 0;
616     }
617   }
618   next_tstates = ( opus_index_pulse ? 10 : 190 ) *
619     machine_current->timings.processor_speed / 1000;
620   event_add( last_tstates + next_tstates, index_event );
621 }
622 
623 libspectrum_byte
opus_read(libspectrum_word address)624 opus_read( libspectrum_word address )
625 {
626   libspectrum_byte data = 0xff;
627 
628   if( address >= 0x3800 ) data = 0xff; /* Undefined on Opus */
629   else if( address >= 0x3000 )         /* 6821 PIA */
630     data = opus_6821_access( address, 0, 0 );
631   else if( address >= 0x2800 ) {       /* WD1770 FDC */
632     switch( address & 0x03 ) {
633     case 0:
634       data = wd_fdc_sr_read( opus_fdc );
635       break;
636     case 1:
637       data = wd_fdc_tr_read( opus_fdc );
638       break;
639     case 2:
640       data = wd_fdc_sec_read( opus_fdc );
641       break;
642     case 3:
643       data = wd_fdc_dr_read( opus_fdc );
644       break;
645     }
646   }
647 
648   return data;
649 }
650 
651 void
opus_write(libspectrum_word address,libspectrum_byte b)652 opus_write( libspectrum_word address, libspectrum_byte b )
653 {
654   if( address < 0x2000 ) return;
655   if( address >= 0x3800 ) return;
656 
657   if( address >= 0x3000 ) {
658     opus_6821_access( address, b, 1 );
659   } else if( address >= 0x2800 ) {
660     switch( address & 0x03 ) {
661     case 0:
662       wd_fdc_cr_write( opus_fdc, b );
663       break;
664     case 1:
665       wd_fdc_tr_write( opus_fdc, b );
666       break;
667     case 2:
668       wd_fdc_sec_write( opus_fdc, b );
669       break;
670     case 3:
671       wd_fdc_dr_write( opus_fdc, b );
672       break;
673     }
674   }
675 }
676 
677 static libspectrum_byte *
alloc_and_copy_page(libspectrum_byte * source_page)678 alloc_and_copy_page( libspectrum_byte* source_page )
679 {
680   libspectrum_byte *buffer;
681   buffer = malloc( MEMORY_PAGE_SIZE );
682   if( !buffer ) {
683     ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d", __FILE__,
684               __LINE__ );
685     return 0;
686   }
687 
688   memcpy( buffer, source_page, MEMORY_PAGE_SIZE );
689   return buffer;
690 }
691 
692 static void
opus_enabled_snapshot(libspectrum_snap * snap)693 opus_enabled_snapshot( libspectrum_snap *snap )
694 {
695   if( libspectrum_snap_opus_active( snap ) )
696     settings_current.opus = 1;
697 }
698 
699 static void
opus_from_snapshot(libspectrum_snap * snap)700 opus_from_snapshot( libspectrum_snap *snap )
701 {
702   if( !libspectrum_snap_opus_active( snap ) ) return;
703 
704   if( libspectrum_snap_opus_custom_rom( snap ) &&
705       libspectrum_snap_opus_rom( snap, 0 ) &&
706       machine_load_rom_bank_from_buffer(
707                              opus_memory_map_romcs_rom, 0,
708                              libspectrum_snap_opus_rom( snap, 0 ),
709                              0x2000, 1 ) )
710     return;
711 
712   if( libspectrum_snap_opus_ram( snap, 0 ) ) {
713     memcpy( opus_ram,
714             libspectrum_snap_opus_ram( snap, 0 ), TRUE_OPUS_RAM_SIZE );
715   }
716 
717   /* ignore drive count for now, there will be an issue with loading snaps where
718      drives have been disabled
719   libspectrum_snap_opus_drive_count( snap )
720    */
721 
722   opus_fdc->direction = libspectrum_snap_opus_direction( snap );
723 
724   wd_fdc_cr_write ( opus_fdc, libspectrum_snap_opus_status ( snap ) );
725   wd_fdc_tr_write ( opus_fdc, libspectrum_snap_opus_track  ( snap ) );
726   wd_fdc_sec_write( opus_fdc, libspectrum_snap_opus_sector ( snap ) );
727   wd_fdc_dr_write ( opus_fdc, libspectrum_snap_opus_data   ( snap ) );
728   data_reg_a = libspectrum_snap_opus_data_reg_a( snap );
729   data_dir_a = libspectrum_snap_opus_data_dir_a( snap );
730   control_a  = libspectrum_snap_opus_control_a ( snap );
731   data_reg_b = libspectrum_snap_opus_data_reg_b( snap );
732   data_dir_b = libspectrum_snap_opus_data_dir_b( snap );
733   control_b  = libspectrum_snap_opus_control_b ( snap );
734 
735   if( libspectrum_snap_opus_paged( snap ) ) {
736     opus_page();
737   } else {
738     opus_unpage();
739   }
740 }
741 
742 static void
opus_to_snapshot(libspectrum_snap * snap)743 opus_to_snapshot( libspectrum_snap *snap )
744 {
745   libspectrum_byte *buffer;
746   int drive_count = 0;
747 
748   if( !periph_is_active( PERIPH_TYPE_OPUS ) ) return;
749 
750   libspectrum_snap_set_opus_active( snap, 1 );
751 
752   buffer = alloc_and_copy_page( opus_memory_map_romcs_rom[0].page );
753   if( !buffer ) return;
754   libspectrum_snap_set_opus_rom( snap, 0, buffer );
755   if( opus_memory_map_romcs_rom[0].save_to_snapshot )
756     libspectrum_snap_set_opus_custom_rom( snap, 1 );
757 
758   buffer = alloc_and_copy_page( opus_ram );
759   if( !buffer ) return;
760   libspectrum_snap_set_opus_ram( snap, 0, buffer );
761 
762   drive_count++; /* Drive 1 is not removable */
763   if( option_enumerate_diskoptions_drive_opus2_type() > 0 ) drive_count++;
764   libspectrum_snap_set_opus_drive_count( snap, drive_count );
765 
766   libspectrum_snap_set_opus_paged     ( snap, opus_active );
767   libspectrum_snap_set_opus_direction ( snap, opus_fdc->direction );
768   libspectrum_snap_set_opus_status    ( snap, opus_fdc->status_register );
769   libspectrum_snap_set_opus_track     ( snap, opus_fdc->track_register );
770   libspectrum_snap_set_opus_sector    ( snap, opus_fdc->sector_register );
771   libspectrum_snap_set_opus_data      ( snap, opus_fdc->data_register );
772   libspectrum_snap_set_opus_data_reg_a( snap, data_reg_a );
773   libspectrum_snap_set_opus_data_dir_a( snap, data_dir_a );
774   libspectrum_snap_set_opus_control_a ( snap, control_a );
775   libspectrum_snap_set_opus_data_reg_b( snap, data_reg_b );
776   libspectrum_snap_set_opus_data_dir_b( snap, data_dir_b );
777   libspectrum_snap_set_opus_control_b ( snap, control_b );
778 }
779 
780 int
opus_unittest(void)781 opus_unittest( void )
782 {
783   int r = 0;
784 
785   opus_page();
786 
787   r += unittests_assert_8k_page( 0x0000, opus_rom_memory_source, 0 );
788   r += unittests_assert_4k_page( 0x2000, opus_ram_memory_source, 0 );
789   r += unittests_assert_4k_page( 0x3000, memory_source_rom, 0 );
790   r += unittests_assert_16k_ram_page( 0x4000, 5 );
791   r += unittests_assert_16k_ram_page( 0x8000, 2 );
792   r += unittests_assert_16k_ram_page( 0xc000, 0 );
793 
794   opus_unpage();
795 
796   r += unittests_paging_test_48( 2 );
797 
798   return r;
799 }
800 
801