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