1 /*
2  * plus60k.c - PLUS60K EXPANSION HACK emulation.
3  *
4  * Written by
5  *  Marco van den Heuvel <blackystardust68@yahoo.com>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 /* Introduction :
28  *
29  * +60K is a kind of memory expansion for C64 proposed in late '90s by Polish
30  * sceners, for sceners. Basically the whole idea was to add another bank of
31  * memory and provide a shared area to exchange data between the banks.
32  *
33  *
34  * Hardware :
35  *
36  * +60K circuit is somewhat complicated because quite a few new ICs have to mounted
37  * inside a C64 but it is not very hard to build. I will not get into details and
38  * schematics because it was described quite well in disk magazines.
39  *
40  *
41  * Software :
42  *
43  * - VIC address space is divided into 4 parts: $d000-$d0ff, $d100-$d1ff, $d200-$d2ff
44  *   and $d300-$d3ff
45  * - only $d000-$d0ff is still visible in I/O space as VIC
46  * - $d100-$d1ff returns $ff on read
47  * - $d200-$d3ff is unconnected and returns random values
48  * - register latch for +60K is active in all $d100-$d1ff space, but programs should
49  *   use $d100 only
50  * - only data bit 7 is connected to the latch, but programs should use 0 as bits 0-6
51  * - VIC fetches data only from bank 0 RAM (onboard)
52 
53  * +60K is controlled by a write-only register at $d100. There are only two possible
54  * values that can be written there:
55 
56  * value     | $1000-$ffff RAM area
57  * ---------------------------------------------
58  * %0xxxxxxx | comes from onboard RAM (bank 0)
59  * %1xxxxxxx | comes from additional RAM (bank 1)
60 
61  * x - reserved bit, it seems that all existing +60K-enabled programs use 0 here
62 
63  * RAM/ROM/IO is still controlled as usual by $0001. The only thing that changes is
64  * where $1000-$ffff RAM comes from. The $0000-$0fff is the shared space and always
65  * comes from onboard RAM.
66  * It is important to say that VIC cannot see additional RAM. It still fetches data
67  * from onboard RAM thus it is possible to keep gfx data in bank 0 and code with
68  * sound data in bank 1.
69  * The $d100 control register returns $ff on read. Although such usage is forbidden
70  * I've seen at least one example of switching to bank 0 by "INC $d100" instruction
71  * so it is emulated too.
72  *
73  */
74 
75 #include "vice.h"
76 
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 
81 #include "c64cart.h"
82 #include "c64mem.h"
83 #include "cartio.h"
84 #include "cartridge.h"
85 #include "cmdline.h"
86 #include "lib.h"
87 #include "log.h"
88 #include "machine.h"
89 #include "mem.h"
90 #include "monitor.h"
91 #include "resources.h"
92 #include "plus60k.h"
93 #include "snapshot.h"
94 #include "types.h"
95 #include "uiapi.h"
96 #include "util.h"
97 #include "vicii.h"
98 #include "vicii-mem.h"
99 
100 /* PLUS60K registers */
101 static uint8_t plus60k_reg = 0;
102 
103 static log_t plus60k_log = LOG_ERR;
104 
105 static int plus60k_activate(void);
106 static int plus60k_deactivate(void);
107 
108 int plus60k_enabled = 0;
109 
110 int plus60k_base = 0xd100;
111 
112 /* Filename of the +60K image.  */
113 static char *plus60k_filename = NULL;
114 
115 static uint8_t *plus60k_ram;
116 
plus60k_dump(void)117 static int plus60k_dump(void)
118 {
119     mon_out("$1000-$FFFF bank: %d\n", plus60k_reg);
120     return 0;
121 }
122 
plus60k_ff_read(uint16_t addr)123 static uint8_t plus60k_ff_read(uint16_t addr)
124 {
125     return 0xff;
126 }
127 
plus60k_peek(uint16_t addr)128 static uint8_t plus60k_peek(uint16_t addr)
129 {
130     return plus60k_reg << 7;
131 }
132 
plus60k_vicii_store(uint16_t addr,uint8_t value)133 static void plus60k_vicii_store(uint16_t addr, uint8_t value)
134 {
135     plus60k_reg = (value & 0x80) >> 7;
136 }
137 
138 /* When the +60K device is active and the device base is at $d040, this device is used instead of the default VICII device */
139 static io_source_t vicii_d000_device = {
140     "VIC-II",              /* name of the device */
141     IO_DETACH_NEVER,       /* chip is never involved in collisions, so no detach */
142     IO_DETACH_NO_RESOURCE, /* does not use a resource for detach */
143     0xd000, 0xd03f, 0x3f,  /* range for the device, regs:$d000-$d03f */
144     1,                     /* read is always valid */
145     vicii_store,           /* store function */
146     NULL,                  /* NO poke function */
147     vicii_read,            /* read function */
148     vicii_peek,            /* peek function */
149     vicii_dump,            /* device state information dump function */
150     0,                     /* dummy (not a cartridge) */
151     IO_PRIO_HIGH,          /* high priority, device is never involved in collisions */
152     0                      /* insertion order, gets filled in by the registration function */
153 };
154 
155 /* When the +60K device is active and the device base is at $d100, this device is used instead of the default VICII device */
156 static io_source_t vicii_d000_full_device = {
157     "VIC-II",              /* name of the device */
158     IO_DETACH_NEVER,       /* chip is never involved in collisions, so no detach */
159     IO_DETACH_NO_RESOURCE, /* does not use a resource for detach */
160     0xd000, 0xd0ff, 0x3f,  /* range for the device, regs:$d000-$d03f, mirrors:$d040-$d0ff */
161     1,                     /* read is always valid */
162     vicii_store,           /* store function */
163     NULL,                  /* NO poke function */
164     vicii_read,            /* read function */
165     vicii_peek,            /* peek function */
166     vicii_dump,            /* device state information dump function */
167     0,                     /* dummy (not a cartridge) */
168     IO_PRIO_HIGH,          /* high priority, device and mirrors are never involved in collisions */
169     0                      /* insertion order, gets filled in by the registration function */
170 };
171 
172 static io_source_t vicii_d040_device = {
173     "+60K",               /* name of the device */
174     IO_DETACH_RESOURCE,   /* use resource to detach the device when involved in a read-collision */
175     "PLUS60K",            /* resource to set to '0' */
176     0xd040, 0xd0ff, 0x00, /* range for the device, address is ignored, reg:$d040, mirrors:$d041-$d0ff */
177     1,                    /* read is always valid */
178     plus60k_vicii_store,  /* store function */
179     NULL,                 /* NO poke function */
180     plus60k_ff_read,      /* read function */
181     plus60k_peek,         /* peek function */
182     plus60k_dump,         /* device state information dump function */
183     CARTRIDGE_PLUS60K,    /* cartridge ID */
184     IO_PRIO_NORMAL,       /* normal priority, device read needs to be checked for collisions */
185     0                     /* insertion order, gets filled in by the registration function */
186 };
187 
188 static io_source_t vicii_d100_device = {
189     "+60K",               /* name of the device */
190     IO_DETACH_RESOURCE,   /* use resource to detach the device when involved in a read-collision */
191     "PLUS60K",            /* resource to set to '0' */
192     0xd100, 0xd1ff, 0x00, /* range for the device, address is ignored, reg:$d100, mirrors:$d101-$d1ff */
193     1,                    /* read is always valid */
194     plus60k_vicii_store,  /* store function */
195     NULL,                 /* NO poke function */
196     plus60k_ff_read,      /* read function */
197     plus60k_peek,         /* peek function */
198     plus60k_dump,         /* device state information dump function */
199     CARTRIDGE_PLUS60K,    /* cartridge ID */
200     IO_PRIO_NORMAL,       /* normal priority, device read needs to be checked for collisions */
201     0                     /* insertion order, gets filled in by the registration function */
202 };
203 
204 static io_source_list_t *vicii_d000_list_item = NULL;
205 static io_source_list_t *vicii_d000_full_list_item = NULL;
206 static io_source_list_t *vicii_d040_list_item = NULL;
207 static io_source_list_t *vicii_d100_list_item = NULL;
208 
set_plus60k_enabled(int value,int disable_reset)209 int set_plus60k_enabled(int value, int disable_reset)
210 {
211     int val = value ? 1 : 0;
212 
213     if (val == plus60k_enabled) {
214         return 0;
215     }
216 
217     if (!val) {
218         if (plus60k_deactivate() < 0) {
219             return -1;
220         }
221 
222         if (!disable_reset) {
223             machine_trigger_reset(MACHINE_RESET_MODE_HARD);
224         }
225         plus60k_enabled = 0;
226         return 0;
227     } else {
228         if (plus60k_activate() < 0) {
229             return -1;
230         }
231         plus60k_enabled = 1;
232         if (!disable_reset) {
233             machine_trigger_reset(MACHINE_RESET_MODE_HARD);
234         }
235         return 0;
236     }
237 }
238 
set_plus60k_filename(const char * name,void * param)239 static int set_plus60k_filename(const char *name, void *param)
240 {
241     if (plus60k_filename != NULL && name != NULL && strcmp(name, plus60k_filename) == 0) {
242         return 0;
243     }
244 
245     if (name != NULL && *name != '\0') {
246         if (util_check_filename_access(name) < 0) {
247             return -1;
248         }
249     }
250 
251     if (plus60k_enabled) {
252         plus60k_deactivate();
253         util_string_set(&plus60k_filename, name);
254         plus60k_activate();
255     } else {
256         util_string_set(&plus60k_filename, name);
257     }
258 
259     return 0;
260 }
261 
set_plus60k_base(int val,void * param)262 static int set_plus60k_base(int val, void *param)
263 {
264     if (val == plus60k_base) {
265         return 0;
266     }
267 
268     switch (val) {
269         case 0xd040:
270         case 0xd100:
271             break;
272         default:
273             log_message(plus60k_log, "Unknown PLUS60K base address $%X.",
274                     (unsigned int)val);
275             return -1;
276     }
277 
278     if (plus60k_enabled) {
279         plus60k_deactivate();
280         plus60k_base = val;
281         plus60k_activate();
282     } else {
283         plus60k_base = val;
284     }
285 
286     return 0;
287 }
288 
289 static const resource_string_t resources_string[] = {
290     { "PLUS60Kfilename", "", RES_EVENT_NO, NULL,
291       &plus60k_filename, set_plus60k_filename, NULL },
292     RESOURCE_STRING_LIST_END
293 };
294 
295 static const resource_int_t resources_int[] = {
296     { "PLUS60Kbase", 0xd100, RES_EVENT_NO, NULL,
297       &plus60k_base, set_plus60k_base, NULL },
298     RESOURCE_INT_LIST_END
299 };
300 
plus60k_resources_init(void)301 int plus60k_resources_init(void)
302 {
303     if (resources_register_string(resources_string) < 0) {
304         return -1;
305     }
306 
307     return resources_register_int(resources_int);
308 }
309 
plus60k_resources_shutdown(void)310 void plus60k_resources_shutdown(void)
311 {
312     lib_free(plus60k_filename);
313 }
314 
315 /* ------------------------------------------------------------------------- */
316 
317 static const cmdline_option_t cmdline_options[] =
318 {
319     { "-plus60kimage", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
320       NULL, NULL, "PLUS60Kfilename", NULL,
321       "<Name>", "Specify name of PLUS60K image" },
322     { "-plus60kbase", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
323       NULL, NULL, "PLUS60Kbase", NULL,
324       "<Base address>", "Base address of the PLUS60K expansion. (0xD040/0xD100)" },
325     CMDLINE_LIST_END
326 };
327 
plus60k_cmdline_options_init(void)328 int plus60k_cmdline_options_init(void)
329 {
330     return cmdline_register_options(cmdline_options);
331 }
332 
333 /* ------------------------------------------------------------------------- */
334 
plus60k_init(void)335 void plus60k_init(void)
336 {
337     plus60k_log = log_open("PLUS60");
338 }
339 
plus60k_reset(void)340 void plus60k_reset(void)
341 {
342     plus60k_reg = 0;
343 }
344 
plus60k_activate(void)345 static int plus60k_activate(void)
346 {
347     plus60k_ram = lib_realloc((void *)plus60k_ram, (size_t)0xf000);
348 
349     log_message(plus60k_log, "PLUS60K expansion installed.");
350 
351     if (!util_check_null_string(plus60k_filename)) {
352         if (util_file_load(plus60k_filename, plus60k_ram, (size_t)0xf000, UTIL_FILE_LOAD_RAW) < 0) {
353             log_message(plus60k_log, "Reading PLUS60K image %s failed.", plus60k_filename);
354             if (util_file_save(plus60k_filename, plus60k_ram, 0xf000) < 0) {
355                 log_message(plus60k_log, "Creating PLUS60K image %s failed.", plus60k_filename);
356                 return -1;
357             }
358             log_message(plus60k_log, "Creating PLUS60K image %s.", plus60k_filename);
359         } else {
360             log_message(plus60k_log, "Reading PLUS60K image %s.", plus60k_filename);
361         }
362     }
363 
364     plus60k_reset();
365 
366     c64io_vicii_deinit();
367     if (plus60k_base == 0xd100) {
368         vicii_d000_full_list_item = io_source_register(&vicii_d000_full_device);
369         vicii_d100_list_item = io_source_register(&vicii_d100_device);
370     } else {
371         vicii_d000_list_item = io_source_register(&vicii_d000_device);
372         vicii_d040_list_item = io_source_register(&vicii_d040_device);
373     }
374     return 0;
375 }
376 
plus60k_deactivate(void)377 static int plus60k_deactivate(void)
378 {
379     if (!util_check_null_string(plus60k_filename)) {
380         if (util_file_save(plus60k_filename, plus60k_ram, 0xf000) < 0) {
381             log_message(plus60k_log, "Writing PLUS60K image %s failed.", plus60k_filename);
382             return -1;
383         }
384         log_message(plus60k_log, "Writing PLUS60K image %s.", plus60k_filename);
385     }
386     lib_free(plus60k_ram);
387     plus60k_ram = NULL;
388 
389     if (vicii_d000_list_item != NULL) {
390         io_source_unregister(vicii_d000_list_item);
391         vicii_d000_list_item = NULL;
392     }
393 
394     if (vicii_d000_full_list_item != NULL) {
395         io_source_unregister(vicii_d000_full_list_item);
396         vicii_d000_full_list_item = NULL;
397     }
398 
399     if (vicii_d040_list_item != NULL) {
400         io_source_unregister(vicii_d040_list_item);
401         vicii_d040_list_item = NULL;
402     }
403 
404     if (vicii_d100_list_item != NULL) {
405         io_source_unregister(vicii_d100_list_item);
406         vicii_d100_list_item = NULL;
407     }
408     c64io_vicii_reinit();
409 
410     return 0;
411 }
412 
plus60k_shutdown(void)413 void plus60k_shutdown(void)
414 {
415     if (plus60k_enabled) {
416         plus60k_deactivate();
417     }
418 }
419 
420 /* ------------------------------------------------------------------------- */
421 
plus60k_memory_store(uint16_t addr,uint8_t value)422 static void plus60k_memory_store(uint16_t addr, uint8_t value)
423 {
424     plus60k_ram[addr - 0x1000] = value;
425 }
426 
vicii_mem_vbank_store_wrapper(uint16_t addr,uint8_t value)427 static void vicii_mem_vbank_store_wrapper(uint16_t addr, uint8_t value)
428 {
429     vicii_mem_vbank_store(addr, value);
430 }
431 
vicii_mem_vbank_39xx_store_wrapper(uint16_t addr,uint8_t value)432 static void vicii_mem_vbank_39xx_store_wrapper(uint16_t addr, uint8_t value)
433 {
434     vicii_mem_vbank_39xx_store(addr, value);
435 }
436 
vicii_mem_vbank_3fxx_store_wrapper(uint16_t addr,uint8_t value)437 static void vicii_mem_vbank_3fxx_store_wrapper(uint16_t addr, uint8_t value)
438 {
439     vicii_mem_vbank_3fxx_store(addr, value);
440 }
441 
ram_hi_store_wrapper(uint16_t addr,uint8_t value)442 static void ram_hi_store_wrapper(uint16_t addr, uint8_t value)
443 {
444     ram_hi_store(addr, value);
445 }
446 
447 static store_func_ptr_t plus60k_mem_write_tab[] = {
448     vicii_mem_vbank_store_wrapper,
449     plus60k_memory_store,
450     vicii_mem_vbank_39xx_store_wrapper,
451     plus60k_memory_store,
452     vicii_mem_vbank_3fxx_store_wrapper,
453     plus60k_memory_store,
454     ram_hi_store_wrapper,
455     plus60k_memory_store
456 };
457 
plus60k_vicii_mem_vbank_store(uint16_t addr,uint8_t value)458 void plus60k_vicii_mem_vbank_store(uint16_t addr, uint8_t value)
459 {
460     plus60k_mem_write_tab[plus60k_reg](addr, value);
461 }
462 
plus60k_vicii_mem_vbank_39xx_store(uint16_t addr,uint8_t value)463 void plus60k_vicii_mem_vbank_39xx_store(uint16_t addr, uint8_t value)
464 {
465     plus60k_mem_write_tab[plus60k_reg + 2](addr, value);
466 }
467 
plus60k_vicii_mem_vbank_3fxx_store(uint16_t addr,uint8_t value)468 void plus60k_vicii_mem_vbank_3fxx_store(uint16_t addr, uint8_t value)
469 {
470     plus60k_mem_write_tab[plus60k_reg + 4](addr, value);
471 }
472 
plus60k_ram_hi_store(uint16_t addr,uint8_t value)473 void plus60k_ram_hi_store(uint16_t addr, uint8_t value)
474 {
475     plus60k_mem_write_tab[plus60k_reg + 6](addr, value);
476 }
477 
plus60k_ram_read(uint16_t addr)478 uint8_t plus60k_ram_read(uint16_t addr)
479 {
480     if (plus60k_enabled && addr >= 0x1000 && plus60k_reg == 1) {
481         return plus60k_ram[addr - 0x1000];
482     } else {
483         return mem_ram[addr];
484     }
485 }
486 
plus60k_ram_store(uint16_t addr,uint8_t value)487 void plus60k_ram_store(uint16_t addr, uint8_t value)
488 {
489     if (plus60k_enabled && addr >= 0x1000 && plus60k_reg == 1) {
490         plus60k_ram[addr - 0x1000] = value;
491     } else {
492         mem_ram[addr] = value;
493     }
494 }
495 
plus60k_ram_inject(uint16_t addr,uint8_t value)496 void plus60k_ram_inject(uint16_t addr, uint8_t value)
497 {
498     plus60k_ram_store(addr, value);
499 }
500 
501 /* ------------------------------------------------------------------------- */
502 
503 /* PLUS60K snapshot module format:
504 
505    type  | name     | description
506    --------------------------------------
507    WORD  | base     | base address of register
508    BYTE  | register | register
509    ARRAY | RAM      | 61440 BYTES of RAM data
510 
511    Note: for some reason this snapshot module started at 0.1, so there never was a 0.0
512  */
513 
514 static char snap_module_name[] = "PLUS60K";
515 #define SNAP_MAJOR   0
516 #define SNAP_MINOR   1
517 
plus60k_snapshot_write(struct snapshot_s * s)518 int plus60k_snapshot_write(struct snapshot_s *s)
519 {
520     snapshot_module_t *m;
521 
522     m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
523 
524     if (m == NULL) {
525         return -1;
526     }
527 
528     if (0
529         || SMW_W (m, (uint16_t)plus60k_base) < 0
530         || SMW_B (m, plus60k_reg) < 0
531         || SMW_BA(m, plus60k_ram, 0xf000) < 0) {
532         snapshot_module_close(m);
533         return -1;
534     }
535 
536     return snapshot_module_close(m);
537 }
538 
plus60k_snapshot_read(struct snapshot_s * s)539 int plus60k_snapshot_read(struct snapshot_s *s)
540 {
541     snapshot_module_t *m;
542     uint8_t vmajor, vminor;
543 
544     m = snapshot_module_open(s, snap_module_name, &vmajor, &vminor);
545 
546     if (m == NULL) {
547         return -1;
548     }
549 
550     /* Do not accept versions higher than current */
551     if (snapshot_version_is_bigger(vmajor, vminor, SNAP_MAJOR, SNAP_MINOR)) {
552         snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
553         goto fail;
554     }
555 
556     if (SMR_W_INT(m, &plus60k_base) < 0) {
557         goto fail;
558     }
559 
560     /* enable plus60k, without reset */
561     set_plus60k_enabled(1, 1);
562 
563     if (0
564         || SMR_B(m, &plus60k_reg) < 0
565         || SMR_BA(m, plus60k_ram, 0xf000) < 0) {
566         goto fail;
567     }
568 
569     return snapshot_module_close(m);
570 
571 fail:
572     snapshot_module_close(m);
573 
574     /* disable plus60k, without reset */
575     set_plus60k_enabled(0, 1);
576 
577     return -1;
578 }
579