1 /*
2 * c64dtvdma.c - C64DTV DMA controller
3 *
4 * Written by
5 * M.Kiesel <mayne@users.sourceforge.net>
6 * Hannu Nuotio <hannu.nuotio@tut.fi>
7 * Daniel Kahlin <daniel@kahlin.net>
8 * Based on code by
9 * Marco van den Heuvel <blackystardust68@yahoo.com>
10 *
11 * This file is part of VICE, the Versatile Commodore Emulator.
12 * See README for copyright notice.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27 * 02111-1307 USA.
28 *
29 */
30
31 #include "vice.h"
32
33 #include "c64mem.h"
34 #include "c64dtvmem.h"
35 #include "c64dtvflash.h"
36 #include "c64dtvdma.h"
37 #include "cmdline.h"
38 #include "debug.h"
39 #include "lib.h"
40 #include "log.h"
41 #include "util.h"
42 #include "resources.h"
43 #include "maincpu.h"
44 #include "interrupt.h"
45 #include "snapshot.h"
46
47 #ifdef DEBUG
48 static log_t c64dtvdma_log = LOG_ERR;
49 #endif
50
51 static unsigned int c64dtv_dma_int_num;
52
53 /* I/O of the DMA engine ($D3XX) */
54 uint8_t c64dtvmem_dma[0x20];
55
56 int dma_active;
57 int dma_on_irq;
58
59 static int dma_busy;
60
61 static int dma_source_off;
62 static int dma_dest_off;
63 static int dma_irq;
64
65 #ifdef DEBUG
66 static int dma_log_enabled = 0;
67 #endif
68
69 static uint8_t dma_data;
70 static uint8_t dma_data_swap;
71 static int dma_count;
72 static enum { DMA_IDLE, DMA_READ, DMA_READ_SWAP, DMA_WRITE_SWAP, DMA_WRITE } dma_state;
73 static int source_line_off = 0;
74 static int dest_line_off = 0;
75 static uint8_t source_memtype = 0x00;
76 static uint8_t dest_memtype = 0x00;
77
78 #define GET_REG24(a) ((c64dtvmem_dma[a + 2] << 16) | (c64dtvmem_dma[a + 1] << 8) | c64dtvmem_dma[a])
79 #define GET_REG16(a) ((c64dtvmem_dma[a + 1] << 8) | c64dtvmem_dma[a])
80 #define GET_REG8(a) (c64dtvmem_dma[a])
81
82 /* ------------------------------------------------------------------------- */
83
c64dtvdma_init(void)84 void c64dtvdma_init(void)
85 {
86 #ifdef DEBUG
87 if (c64dtvdma_log == LOG_ERR) {
88 c64dtvdma_log = log_open("C64DTVDMA");
89 }
90 #endif
91
92 /* init DMA IRQ */
93 c64dtv_dma_int_num = interrupt_cpu_status_int_new(maincpu_int_status, "C64DTVDMA");
94 }
95
c64dtvdma_shutdown(void)96 void c64dtvdma_shutdown(void)
97 {
98 }
99
c64dtvdma_reset(void)100 void c64dtvdma_reset(void)
101 {
102 int i;
103
104 #ifdef DEBUG
105 if (dma_log_enabled) {
106 log_message(c64dtvdma_log, "reset");
107 }
108 #endif
109
110 /* TODO move register file initialization somewhere else? */
111 for (i = 0; i < 0x20; ++i) {
112 c64dtvmem_dma[i] = 0;
113 }
114
115 dma_source_off = 0;
116 source_memtype = 0x00;
117 dma_dest_off = 0;
118 dest_memtype = 0x00;
119 dma_busy = 0;
120 dma_irq = 0;
121 dma_on_irq = 0;
122 dma_active = 0;
123
124 dma_count = 0;
125 dma_state = DMA_IDLE;
126 dma_data = 0x00;
127 source_line_off = 0;
128 dest_line_off = 0;
129 }
130
131 /* ------------------------------------------------------------------------- */
132 /* DMA transfer state machine */
133
do_dma_read(int swap)134 static inline void do_dma_read(int swap)
135 {
136 uint8_t data;
137 int offs;
138 int memtype;
139
140 if (!swap) {
141 offs = dma_source_off;
142 memtype = source_memtype;
143 } else {
144 offs = dma_dest_off;
145 memtype = dest_memtype;
146 }
147 offs &= 0x1fffff;
148
149 switch (memtype) {
150 case 0x00: /* ROM */
151 data = c64dtvflash_read(offs);
152 break;
153 case 0x40: /* RAM */
154 data = mem_ram[offs];
155 break;
156 case 0x80: /* RAM+registers */
157 if ((offs >= 0xd000) && (offs < 0xe000)) {
158 data = _mem_read_tab_ptr[offs >> 8]((uint16_t)offs);
159 } else {
160 data = mem_ram[offs];
161 }
162 break;
163 case 0xc0: /* unknown */
164 data = 0;
165 break;
166 default:
167 #ifdef DEBUG
168 log_message(c64dtvdma_log, "invalid memtype in do_dma_read()");
169 #endif
170 data = 0;
171 break;
172 }
173
174 if (!swap) {
175 dma_data = data;
176 } else {
177 dma_data_swap = data;
178 }
179 }
180
do_dma_write(int swap)181 static inline void do_dma_write(int swap)
182 {
183 uint8_t data;
184 int offs;
185 int memtype;
186
187 if (!swap) {
188 offs = dma_dest_off;
189 memtype = dest_memtype;
190 data = dma_data;
191 } else {
192 offs = dma_source_off;
193 memtype = source_memtype;
194 data = dma_data_swap;
195 }
196 offs &= 0x1fffff;
197
198 switch (memtype) {
199 case 0x00: /* ROM */
200 c64dtvflash_store(offs, data);
201 break;
202 case 0x40: /* RAM */
203 mem_ram[offs] = data;
204 break;
205 case 0x80: /* RAM+registers */
206 if ((offs >= 0xd000) && (offs < 0xe000)) {
207 _mem_write_tab_ptr[offs >> 8]((uint16_t)offs, data);
208 } else {
209 mem_ram[offs] = data;
210 }
211 break;
212 case 0xc0: /* unknown */
213 break;
214 default:
215 #ifdef DEBUG
216 log_message(c64dtvdma_log, "invalid memtype in do_dma_write()");
217 #endif
218 break;
219 }
220 }
221
update_counters(void)222 static inline void update_counters(void)
223 {
224 int source_step = GET_REG16(0x06);
225 int dest_step = GET_REG16(0x08);
226 int source_modulo = GET_REG16(0x0c);
227 int dest_modulo = GET_REG16(0x0e);
228 int source_line_length = GET_REG16(0x10);
229 int dest_line_length = GET_REG16(0x12);
230 int source_modulo_enable = GET_REG8(0x1e) & 0x01;
231 int dest_modulo_enable = GET_REG8(0x1e) & 0x02;
232 int source_direction = (GET_REG8(0x1f) & 0x04) ? +1 : -1;
233 int dest_direction = (GET_REG8(0x1f) & 0x08) ? +1 : -1;
234
235 /* update offsets */
236 if (source_modulo_enable && (source_line_off >= source_line_length)) {
237 source_line_off = 0;
238 dma_source_off += source_modulo * source_direction;
239 } else {
240 source_line_off++;
241 dma_source_off += source_step * source_direction;
242 }
243
244 if (dest_modulo_enable && (dest_line_off >= dest_line_length)) {
245 dest_line_off = 0;
246 dma_dest_off += dest_modulo * dest_direction;
247 } else {
248 dest_line_off++;
249 dma_dest_off += dest_step * dest_direction;
250 }
251 }
252
perform_dma_cycle(void)253 static inline void perform_dma_cycle(void)
254 {
255 int swap = GET_REG8(0x1f) & 0x02;
256
257 switch (dma_state) {
258 case DMA_IDLE:
259 break;
260 case DMA_READ:
261 if (dma_count == 0) {
262 dma_state = DMA_IDLE;
263 break;
264 }
265 do_dma_read(0);
266 if (swap) {
267 dma_state = DMA_READ_SWAP;
268 } else {
269 dma_state = DMA_WRITE;
270 }
271 break;
272 case DMA_READ_SWAP:
273 do_dma_read(1);
274 dma_state = DMA_WRITE_SWAP;
275 break;
276 case DMA_WRITE_SWAP:
277 do_dma_write(1);
278 dma_state = DMA_WRITE;
279 break;
280 case DMA_WRITE:
281 do_dma_write(0);
282 update_counters();
283
284 dma_count--;
285 if (dma_count == 0) {
286 dma_state = DMA_IDLE;
287 } else {
288 dma_state = DMA_READ;
289 }
290 break;
291 default:
292 #ifdef DEBUG
293 log_message(c64dtvdma_log, "invalid state in perform_dma_cycle()");
294 #endif
295 dma_state = DMA_IDLE;
296 break;
297 }
298 }
299
300 /* ------------------------------------------------------------------------- */
301
302 /* These are the $D3xx DMA register engine handlers */
303
c64dtvdma_trigger_dma(void)304 void c64dtvdma_trigger_dma(void)
305 {
306 if (!dma_active) {
307 int source_continue = GET_REG8(0x1d) & 0x02;
308 int dest_continue = GET_REG8(0x1d) & 0x08;
309
310 if (!source_continue) {
311 dma_source_off = GET_REG24(0x00) & 0x3fffff;
312 source_memtype = GET_REG8(0x02) & 0xc0;
313 }
314 if (!dest_continue) {
315 dma_dest_off = GET_REG24(0x03) & 0x3fffff;
316 dest_memtype = GET_REG8(0x05) & 0xc0;
317 }
318
319 /* total number of bytes to transfer */
320 dma_count = GET_REG16(0x0a);
321 /* length=0 means 64 Kb */
322 if (dma_count == 0) {
323 dma_count = 0x10000;
324 }
325
326 #ifdef DEBUG
327 if (dma_log_enabled && (source_continue || dest_continue)) {
328 log_message(c64dtvdma_log, "Source continue %s, dest continue %s", source_continue ? "on" : "off", dest_continue ? "on" : "off");
329 }
330 #endif
331
332 /* initialize state variables */
333 source_line_off = 0;
334 dest_line_off = 0;
335
336 dma_state = DMA_READ;
337
338 if (GET_REG8(0x1f) & 0x80) {
339 dma_irq = 1;
340 } else {
341 dma_irq = 0;
342 }
343
344 dma_busy = 1;
345 dma_active = 1;
346 }
347 }
348
c64dtv_dma_done(void)349 static inline void c64dtv_dma_done(void)
350 {
351 if (dma_irq) {
352 maincpu_set_irq(c64dtv_dma_int_num, 1);
353 dma_busy = 2;
354 }
355 dma_busy &= 0xfe;
356 dma_active = 0;
357 }
358
c64dtv_dma_read(uint16_t addr)359 uint8_t c64dtv_dma_read(uint16_t addr)
360 {
361 if (addr == 0x1f) {
362 return dma_busy;
363 /* the default return value is 0x00 too but I have seen some strangeness
364 here. I've seen something that looks like DMAed data. - tlr */
365 }
366 return 0x00;
367 }
368
c64dtv_dma_store(uint16_t addr,uint8_t value)369 void c64dtv_dma_store(uint16_t addr, uint8_t value)
370 {
371 /* Store first, then check whether DMA access has been
372 requested, perform if necessary. */
373 c64dtvmem_dma[addr] = value;
374
375 dma_on_irq = GET_REG8(0x1f) & 0x70;
376
377 /* Clear DMA IRQ */
378 if ((GET_REG8(0x1d) & 0x01) && (dma_busy == 2)) {
379 #ifdef DEBUG
380 if (dma_log_enabled) {
381 log_message(c64dtvdma_log, "Clear IRQ");
382 }
383 #endif
384 dma_busy &= 0xfd;
385 c64dtvmem_dma[0x1f] = 0;
386 maincpu_set_irq(c64dtv_dma_int_num, 0);
387 dma_irq = 0;
388 /* reset clear IRQ strobe bit */
389 c64dtvmem_dma[0x1d] &= 0xfe;
390 }
391
392 if (dma_on_irq && (dma_busy == 0)) {
393 dma_busy = 1;
394 #ifdef DEBUG
395 if (dma_log_enabled) {
396 log_message(c64dtvdma_log, "Scheduled DMA (%02x).",
397 (unsigned int)dma_on_irq);
398 }
399 #endif
400 return;
401 }
402
403 /* Force DMA start */
404 if (GET_REG8(0x1f) & 0x01) {
405 c64dtvdma_trigger_dma();
406 /* reset force start strobe bit */
407 c64dtvmem_dma[0x1f] &= 0xfe;
408 }
409 }
410
411
c64dtvdma_perform_dma(void)412 void c64dtvdma_perform_dma(void)
413 {
414 /* set maincpu_rmw_flag to 0 during DMA */
415 int dma_maincpu_rmw = maincpu_rmw_flag;
416 maincpu_rmw_flag = 0;
417 perform_dma_cycle();
418 maincpu_rmw_flag = dma_maincpu_rmw;
419
420 #ifdef DEBUG
421 if (dma_log_enabled && (dma_state == DMA_WRITE)) {
422 log_message(c64dtvdma_log,
423 "%s from %x (%s) to %x (%s), %d to go",
424 GET_REG8(0x1f) & 0x02 ? "Swapped" : "Copied",
425 (unsigned int)dma_source_off,
426 source_memtype == 0 ? "Flash" : "RAM",
427 (unsigned int)dma_dest_off,
428 dest_memtype == 0 ? "Flash" : "RAM",
429 dma_count - 1);
430 }
431 #endif
432
433 if (dma_state == DMA_IDLE) {
434 c64dtv_dma_done();
435 }
436 }
437
438
439 /* ------------------------------------------------------------------------- */
440
441 #ifdef DEBUG
set_dma_log(int val,void * param)442 static int set_dma_log(int val, void *param)
443 {
444 dma_log_enabled = val ? 1 : 0;
445
446 return 0;
447 }
448 #endif
449
450 static const resource_int_t resources_int[] = {
451 #ifdef DEBUG
452 { "DtvDMALog", 0, RES_EVENT_NO, (resource_value_t)0,
453 &dma_log_enabled, set_dma_log, NULL },
454 #endif
455 RESOURCE_INT_LIST_END
456 };
457
c64dtvdma_resources_init(void)458 int c64dtvdma_resources_init(void)
459 {
460 return resources_register_int(resources_int);
461 }
462
c64dtvdma_resources_shutdown(void)463 void c64dtvdma_resources_shutdown(void)
464 {
465 }
466
467 static const cmdline_option_t cmdline_options[] =
468 {
469 #ifdef DEBUG
470 { "-dtvdmalog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
471 NULL, NULL, "DtvDMALog", (resource_value_t)1,
472 NULL, "Enable DTV DMA logs." },
473 { "+dtvdmalog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
474 NULL, NULL, "DtvDMALog", (resource_value_t)0,
475 NULL, "Disable DTV DMA logs." },
476 #endif
477 CMDLINE_LIST_END
478 };
479
c64dtvdma_cmdline_options_init(void)480 int c64dtvdma_cmdline_options_init(void)
481 {
482 return cmdline_register_options(cmdline_options);
483 }
484
485 /* ------------------------------------------------------------------------- */
486
487 /* C64DTVDMA snapshot module format:
488
489 type | name | description
490 -------------------------------------
491 ARRAY | regs | 32 BYTES of register data
492 DWORD | source off | source offset
493 DWORD | dest off | destination offset
494 DWORD | busy | DMA busy flag
495 DWORD | IRQ | DMA IRQ state
496 DWORD | on IRQ | on IRQ
497 DWORD | active | DMA active flag
498 BYTE | data | DMA data
499 BYTE | data swap | DMA data swap flag
500 DWORD | count | DMA counter
501 DWORD | state | DMA state
502 DWORD | source line off | source line offset
503 DWORD | dest line off | destination line offset
504 BYTE | source mem type | source memory type
505 BYTE | dest mem type | destination memory type
506 */
507
508 static const char snap_module_name[] = "C64DTVDMA";
509 #define SNAP_MAJOR 0
510 #define SNAP_MINOR 0
511
512 /* static log_t c64_snapshot_log = LOG_ERR; */
513
c64dtvdma_snapshot_write_module(snapshot_t * s)514 int c64dtvdma_snapshot_write_module(snapshot_t *s)
515 {
516 snapshot_module_t *m;
517
518 /* DMA module. */
519 m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
520
521 if (m == NULL) {
522 return -1;
523 }
524
525 if (0
526 || SMW_BA(m, c64dtvmem_dma, 0x20) < 0
527 || SMW_DW(m, dma_source_off) < 0
528 || SMW_DW(m, dma_dest_off) < 0
529 || SMW_DW(m, dma_busy) < 0
530 || SMW_DW(m, dma_irq) < 0
531 || SMW_DW(m, dma_on_irq) < 0
532 || SMW_DW(m, dma_active) < 0
533 || SMW_B(m, dma_data) < 0
534 || SMW_B(m, dma_data_swap) < 0
535 || SMW_DW(m, dma_count) < 0
536 || SMW_DW(m, dma_state) < 0
537 || SMW_DW(m, source_line_off) < 0
538 || SMW_DW(m, dest_line_off) < 0
539 || SMW_B(m, source_memtype) < 0
540 || SMW_B(m, dest_memtype) < 0) {
541 snapshot_module_close(m);
542 return -1;
543 }
544
545 return snapshot_module_close(m);
546 }
547
c64dtvdma_snapshot_read_module(snapshot_t * s)548 int c64dtvdma_snapshot_read_module(snapshot_t *s)
549 {
550 uint8_t major_version, minor_version;
551 snapshot_module_t *m;
552 int temp_dma_state;
553
554 /* DMA module. */
555 m = snapshot_module_open(s, snap_module_name, &major_version, &minor_version);
556
557 if (m == NULL) {
558 return -1;
559 }
560
561 /* Do not accept versions higher than current */
562 if (snapshot_version_is_bigger(major_version, minor_version, SNAP_MAJOR, SNAP_MINOR)) {
563 snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
564 goto fail;
565 }
566
567 if (0
568 || SMR_BA(m, c64dtvmem_dma, 0x20) < 0
569 || SMR_DW_INT(m, &dma_source_off) < 0
570 || SMR_DW_INT(m, &dma_dest_off) < 0
571 || SMR_DW_INT(m, &dma_busy) < 0
572 || SMR_DW_INT(m, &dma_irq) < 0
573 || SMR_DW_INT(m, &dma_on_irq) < 0
574 || SMR_DW_INT(m, &dma_active) < 0
575 || SMR_B(m, &dma_data) < 0
576 || SMR_B(m, &dma_data_swap) < 0
577 || SMR_DW_INT(m, &dma_count) < 0
578 || SMR_DW_INT(m, &temp_dma_state) < 0
579 || SMR_DW_INT(m, &source_line_off) < 0
580 || SMR_DW_INT(m, &dest_line_off) < 0
581 || SMR_B(m, &source_memtype) < 0
582 || SMR_B(m, &dest_memtype) < 0) {
583 goto fail;
584 }
585
586 dma_state = temp_dma_state;
587
588 return snapshot_module_close(m);
589
590 fail:
591 snapshot_module_close(m);
592 return -1;
593 }
594