1 /* divmmc.c: DivMMC interface routines
2 Copyright (c) 2005-2017 Matthew Westcott, 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 Kendall <philip-fuse@shadowmagic.org.uk>
22
23 */
24
25 #include <config.h>
26
27 #include <libspectrum.h>
28
29 #include <string.h>
30
31 #include "debugger/debugger.h"
32 #include "ide.h"
33 #include "infrastructure/startup_manager.h"
34 #include "machine.h"
35 #include "module.h"
36 #include "periph.h"
37 #include "settings.h"
38 #include "ui/ui.h"
39 #include "unittests/unittests.h"
40 #include "divmmc.h"
41 #include "divxxx.h"
42
43 /* Private function prototypes */
44
45 static void divmmc_control_write( libspectrum_word port, libspectrum_byte data );
46 static void divmmc_card_select( libspectrum_word port, libspectrum_byte data );
47 static libspectrum_byte divmmc_mmc_read( libspectrum_word port, libspectrum_byte *attached );
48 static void divmmc_mmc_write( libspectrum_word port, libspectrum_byte data );
49 static void divmmc_activate( void );
50 static libspectrum_dword get_control_register( void );
51 static void set_control_register( libspectrum_dword value );
52
53 /* Data */
54
55 static const periph_port_t divmmc_ports[] = {
56 { 0x00ff, 0x00e3, NULL, divmmc_control_write },
57 { 0x00ff, 0x00e7, NULL, divmmc_card_select },
58 { 0x00ff, 0x00eb, divmmc_mmc_read, divmmc_mmc_write },
59 { 0, 0, NULL, NULL }
60 };
61
62 static const periph_t divmmc_periph = {
63 /* .option = */ &settings_current.divmmc_enabled,
64 /* .ports = */ divmmc_ports,
65 /* .hard_reset = */ 1,
66 /* .activate = */ divmmc_activate,
67 };
68
69 static divxxx_t *divmmc_state;
70
71 /* The card inserted into the DivMMC. For now, we emulate only one card. */
72 static libspectrum_mmc_card *card;
73
74 /* The card currently selected via the "card select" call */
75 static libspectrum_mmc_card *current_card;
76
77 /* *Our* DivMMC has 128 Kb of RAM */
78 #define DIVMMC_PAGES 16
79 #define DIVMMC_PAGE_LENGTH 0x2000
80
81 static void divmmc_reset( int hard_reset );
82 static void divmmc_memory_map( void );
83 static void divmmc_enabled_snapshot( libspectrum_snap *snap );
84 static void divmmc_from_snapshot( libspectrum_snap *snap );
85 static void divmmc_to_snapshot( libspectrum_snap *snap );
86
87 static module_info_t divmmc_module_info = {
88
89 /* .reset = */ divmmc_reset,
90 /* .romcs = */ divmmc_memory_map,
91 /* .snapshot_enabled = */ divmmc_enabled_snapshot,
92 /* .snapshot_from = */ divmmc_from_snapshot,
93 /* .snapshot_to = */ divmmc_to_snapshot,
94
95 };
96
97 /* Debugger events */
98 static const char * const event_type_string = "divmmc";
99
100 /* Debugger system variables */
101 static const char * const debugger_type_string = "divmmc";
102 static const char * const control_register_detail_string = "control";
103
104 /* Eject menu item */
105 static const ui_menu_item eject_menu_item = UI_MENU_ITEM_MEDIA_IDE_DIVMMC_EJECT;
106
107 /* Housekeeping functions */
108
109 static int
divmmc_init(void * context)110 divmmc_init( void *context )
111 {
112 card = libspectrum_mmc_alloc();
113
114 ui_menu_activate( eject_menu_item, 0 );
115
116 if( settings_current.divmmc_file ) {
117 int error;
118
119 error =
120 libspectrum_mmc_insert( card, settings_current.divmmc_file );
121 if( error ) return error;
122
123 error = ui_menu_activate( eject_menu_item, 1 );
124 if( error ) return error;
125 }
126
127 module_register( &divmmc_module_info );
128
129 periph_register( PERIPH_TYPE_DIVMMC, &divmmc_periph );
130
131 divmmc_state = divxxx_alloc( "DivMMC EPROM", DIVMMC_PAGES, "DivMMC RAM",
132 event_type_string, &settings_current.divmmc_enabled,
133 &settings_current.divmmc_wp );
134
135 debugger_system_variable_register(
136 debugger_type_string, control_register_detail_string, get_control_register,
137 set_control_register );
138
139 return 0;
140 }
141
142 static void
divmmc_end(void)143 divmmc_end( void )
144 {
145 divxxx_free( divmmc_state );
146 libspectrum_mmc_free( card );
147 }
148
149 void
divmmc_register_startup(void)150 divmmc_register_startup( void )
151 {
152 startup_manager_module dependencies[] = {
153 STARTUP_MANAGER_MODULE_DEBUGGER,
154 STARTUP_MANAGER_MODULE_DISPLAY,
155 STARTUP_MANAGER_MODULE_MEMORY,
156 STARTUP_MANAGER_MODULE_SETUID,
157 };
158 startup_manager_register( STARTUP_MANAGER_MODULE_DIVMMC, dependencies,
159 ARRAY_SIZE( dependencies ), divmmc_init, NULL,
160 divmmc_end );
161 }
162
163 static void
divmmc_reset(int hard_reset)164 divmmc_reset( int hard_reset )
165 {
166 divxxx_reset( divmmc_state, hard_reset );
167 libspectrum_mmc_reset( card );
168 }
169
170 static int
dirty_fn_wrapper(void * context)171 dirty_fn_wrapper( void *context )
172 {
173 return libspectrum_mmc_dirty( (libspectrum_mmc_card*)context );
174 }
175
176 static libspectrum_error
commit_fn_wrapper(void * context)177 commit_fn_wrapper( void *context )
178 {
179 libspectrum_mmc_commit( (libspectrum_mmc_card*)context );
180 return LIBSPECTRUM_ERROR_NONE;
181 }
182
183 static libspectrum_error
eject_fn_wrapper(void * context)184 eject_fn_wrapper( void *context )
185 {
186 libspectrum_mmc_eject( (libspectrum_mmc_card*)context );
187 return LIBSPECTRUM_ERROR_NONE;
188 }
189
190 static int
mmc_eject(libspectrum_mmc_card * card)191 mmc_eject( libspectrum_mmc_card *card )
192 {
193 return ide_eject_mass_storage( dirty_fn_wrapper, commit_fn_wrapper,
194 eject_fn_wrapper, card,
195 "Card has been modified.\nDo you want to save it?",
196 &settings_current.divmmc_file,
197 eject_menu_item );
198 }
199
200 int
divmmc_insert(const char * filename)201 divmmc_insert( const char *filename )
202 {
203 int error;
204
205 /* Remove any currently inserted card; abort if we want to keep the current
206 card */
207 if( settings_current.divmmc_file )
208 if( mmc_eject( card ) )
209 return 0;
210
211 settings_set_string( &settings_current.divmmc_file, filename );
212
213 error = libspectrum_mmc_insert( card, filename );
214 if( error ) return error;
215 return ui_menu_activate( eject_menu_item, 1 );
216 }
217
218 void
divmmc_commit(void)219 divmmc_commit( void )
220 {
221 libspectrum_mmc_commit( card );
222 }
223
224 int
divmmc_eject(void)225 divmmc_eject( void )
226 {
227 return mmc_eject( card );
228 }
229
230 /* Port read/writes */
231
232 static void
divmmc_control_write(libspectrum_word port GCC_UNUSED,libspectrum_byte data)233 divmmc_control_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
234 {
235 divxxx_control_write( divmmc_state, data );
236 }
237
238 static void
divmmc_card_select(libspectrum_word port GCC_UNUSED,libspectrum_byte data)239 divmmc_card_select( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
240 {
241 // printf("divmmc_card_select( 0x%02x )\n", data );
242
243 /* D0 = MMC0, D1 = MMC1, active LOW
244 somehow logic prevents enabling both cards at the same time */
245 switch( data & 0x03 ) {
246 case 0x02:
247 current_card = card;
248 break;
249 case 0x01:
250 /* TODO: select second card */
251 current_card = NULL;
252 break;
253 default:
254 current_card = NULL;
255 break;
256 }
257 }
258
259 static libspectrum_byte
divmmc_mmc_read(libspectrum_word port GCC_UNUSED,libspectrum_byte * attached)260 divmmc_mmc_read( libspectrum_word port GCC_UNUSED, libspectrum_byte *attached )
261 {
262 *attached = 0xff;
263
264 return current_card ? libspectrum_mmc_read( card ) : 0xff;
265 }
266
267 static void
divmmc_mmc_write(libspectrum_word port GCC_UNUSED,libspectrum_byte data)268 divmmc_mmc_write( libspectrum_word port GCC_UNUSED, libspectrum_byte data )
269 {
270 if( current_card ) libspectrum_mmc_write( card, data );
271 }
272
273 void
divmmc_set_automap(int state)274 divmmc_set_automap( int state )
275 {
276 divxxx_set_automap( divmmc_state, state );
277 }
278
279 void
divmmc_refresh_page_state(void)280 divmmc_refresh_page_state( void )
281 {
282 divxxx_refresh_page_state( divmmc_state );
283 }
284
285 void
divmmc_memory_map(void)286 divmmc_memory_map( void )
287 {
288 divxxx_memory_map( divmmc_state );
289 }
290
291 static void
divmmc_enabled_snapshot(libspectrum_snap * snap)292 divmmc_enabled_snapshot( libspectrum_snap *snap )
293 {
294 if( libspectrum_snap_divmmc_active( snap ) )
295 settings_current.divmmc_enabled = 1;
296 }
297
298 static void
divmmc_from_snapshot(libspectrum_snap * snap)299 divmmc_from_snapshot( libspectrum_snap *snap )
300 {
301 size_t i;
302
303 if( !libspectrum_snap_divmmc_active( snap ) ) return;
304
305 settings_current.divmmc_wp =
306 libspectrum_snap_divmmc_eprom_writeprotect( snap );
307 divxxx_control_write_internal( divmmc_state, libspectrum_snap_divmmc_control( snap ) );
308
309 if( libspectrum_snap_divmmc_eprom( snap, 0 ) ) {
310 memcpy( divxxx_get_eprom( divmmc_state ),
311 libspectrum_snap_divmmc_eprom( snap, 0 ), DIVMMC_PAGE_LENGTH );
312 }
313
314 for( i = 0; i < libspectrum_snap_divmmc_pages( snap ); i++ )
315 if( libspectrum_snap_divmmc_ram( snap, i ) ) {
316 libspectrum_byte *ram = divxxx_get_ram( divmmc_state, i );
317 memcpy( ram, libspectrum_snap_divmmc_ram( snap, i ), DIVMMC_PAGE_LENGTH );
318 }
319
320 if( libspectrum_snap_divmmc_paged( snap ) ) {
321 divxxx_page( divmmc_state );
322 } else {
323 divxxx_unpage( divmmc_state );
324 }
325 }
326
327 static void
divmmc_to_snapshot(libspectrum_snap * snap)328 divmmc_to_snapshot( libspectrum_snap *snap )
329 {
330 size_t i;
331 libspectrum_byte *buffer;
332
333 if( !settings_current.divmmc_enabled ) return;
334
335 libspectrum_snap_set_divmmc_active( snap, 1 );
336 libspectrum_snap_set_divmmc_eprom_writeprotect( snap,
337 settings_current.divmmc_wp );
338 libspectrum_snap_set_divmmc_paged( snap, divxxx_get_active( divmmc_state ) );
339 libspectrum_snap_set_divmmc_control( snap, divxxx_get_control( divmmc_state ) );
340
341 buffer = libspectrum_new( libspectrum_byte, DIVMMC_PAGE_LENGTH );
342
343 memcpy( buffer, divxxx_get_eprom( divmmc_state ), DIVMMC_PAGE_LENGTH );
344 libspectrum_snap_set_divmmc_eprom( snap, 0, buffer );
345
346 libspectrum_snap_set_divmmc_pages( snap, DIVMMC_PAGES );
347
348 for( i = 0; i < DIVMMC_PAGES; i++ ) {
349
350 buffer = libspectrum_new( libspectrum_byte, DIVMMC_PAGE_LENGTH );
351
352 memcpy( buffer, divxxx_get_ram( divmmc_state, i ), DIVMMC_PAGE_LENGTH );
353 libspectrum_snap_set_divmmc_ram( snap, i, buffer );
354 }
355 }
356
357 static void
divmmc_activate(void)358 divmmc_activate( void )
359 {
360 divxxx_activate( divmmc_state );
361 }
362
363 static libspectrum_dword
get_control_register(void)364 get_control_register( void )
365 {
366 return divxxx_get_control( divmmc_state );
367 }
368
369 static void
set_control_register(libspectrum_dword value)370 set_control_register( libspectrum_dword value )
371 {
372 divxxx_control_write_internal( divmmc_state, value & 0xff );
373 }
374
375 int
divmmc_unittest(void)376 divmmc_unittest( void )
377 {
378 int r = 0;
379 int eprom_memory_source = divxxx_get_eprom_memory_source( divmmc_state );
380 int ram_memory_source = divxxx_get_ram_memory_source( divmmc_state );
381
382 divmmc_set_automap( 1 );
383
384 divmmc_control_write( 0x00e3, 0x80 );
385 r += unittests_assert_8k_page( 0x0000, eprom_memory_source, 0 );
386 r += unittests_assert_8k_page( 0x2000, ram_memory_source, 0 );
387 r += unittests_assert_16k_ram_page( 0x4000, 5 );
388 r += unittests_assert_16k_ram_page( 0x8000, 2 );
389 r += unittests_assert_16k_ram_page( 0xc000, 0 );
390
391 divmmc_control_write( 0x00e3, 0x83 );
392 r += unittests_assert_8k_page( 0x0000, eprom_memory_source, 0 );
393 r += unittests_assert_8k_page( 0x2000, ram_memory_source, 3 );
394 r += unittests_assert_16k_ram_page( 0x4000, 5 );
395 r += unittests_assert_16k_ram_page( 0x8000, 2 );
396 r += unittests_assert_16k_ram_page( 0xc000, 0 );
397
398 divmmc_control_write( 0x00e3, 0x40 );
399 r += unittests_assert_8k_page( 0x0000, ram_memory_source, 3 );
400 r += unittests_assert_8k_page( 0x2000, ram_memory_source, 0 );
401 r += unittests_assert_16k_ram_page( 0x4000, 5 );
402 r += unittests_assert_16k_ram_page( 0x8000, 2 );
403 r += unittests_assert_16k_ram_page( 0xc000, 0 );
404
405 divmmc_control_write( 0x00e3, 0x02 );
406 r += unittests_assert_8k_page( 0x0000, ram_memory_source, 3 );
407 r += unittests_assert_8k_page( 0x2000, ram_memory_source, 2 );
408 r += unittests_assert_16k_ram_page( 0x4000, 5 );
409 r += unittests_assert_16k_ram_page( 0x8000, 2 );
410 r += unittests_assert_16k_ram_page( 0xc000, 0 );
411
412 /* We have only 128 Kb of RAM (16 x 8 Kb pages), so setting all of bits 0-5
413 results in page 15 being selected */
414 divmmc_control_write( 0x00e3, 0x3f );
415 r += unittests_assert_8k_page( 0x0000, ram_memory_source, 3 );
416 r += unittests_assert_8k_page( 0x2000, ram_memory_source, 15 );
417 r += unittests_assert_16k_ram_page( 0x4000, 5 );
418 r += unittests_assert_16k_ram_page( 0x8000, 2 );
419 r += unittests_assert_16k_ram_page( 0xc000, 0 );
420
421 divmmc_set_automap( 0 );
422
423 r += unittests_paging_test_48( 2 );
424
425 return r;
426 }
427